Add an option to request certificate with Must-Staple. (#697)

This commit is contained in:
Yuchen Ying 2020-06-17 08:29:34 -07:00 committed by GitHub
parent 8856577f39
commit 8fc1e9cca8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 92 additions and 23 deletions

View file

@ -11,6 +11,31 @@ import (
"github.com/pomerium/pomerium/internal/log"
)
// AutocertOptions contains the options to control the behavior of autocert.
type AutocertOptions struct {
// Enable enables fully automated certificate management including issuance
// and renewal from LetsEncrypt. Must be used in conjunction with Folder.
Enable bool `mapstructure:"autocert" yaml:"autocert,omitempty"`
// UseStaging tells autocert to use Let's Encrypt's staging CA which
// has less strict usage limits then the (default) production CA.
//
// https://letsencrypt.org/docs/staging-environment/
UseStaging bool `mapstructure:"autocert_use_staging" yaml:"autocert_use_staging,omitempty"`
// MustStaple will cause autocert to request a certificate with
// status_request extension. This will allow the TLS client (the browser)
// to fail immediately if Pomerium failed to get an OCSP staple.
// See also https://tools.ietf.org/html/rfc7633
// Only used when Enable is true.
MustStaple bool `mapstructure:"autocert_must_staple" yaml:"autocert_must_staple,omitempty"`
// Folder specifies the location to store, and load autocert managed
// TLS certificates.
// defaults to $XDG_DATA_HOME/pomerium
Folder string `mapstructure:"autocert_dir" yaml:"autocert_dir,omitempty"`
}
// AutocertManager manages Let's Encrypt certificates based on configuration options.
var AutocertManager = newAutocertManager()
@ -32,10 +57,11 @@ func (mgr *autocertManager) getConfig(options *Options) (*certmagic.Config, erro
cm := mgr.certmagic
if cm == nil {
cm = certmagic.NewDefault()
cm.MustStaple = options.AutocertOptions.MustStaple
}
cm.OnDemand = nil // disable on-demand
cm.Storage = &certmagic.FileStorage{Path: options.AutoCertFolder}
cm.Storage = &certmagic.FileStorage{Path: options.AutocertOptions.Folder}
// add existing certs to the cache, and staple OCSP
for _, cert := range options.Certificates {
if err := cm.CacheUnmanagedTLSCertificate(cert, nil); err != nil {
@ -44,7 +70,7 @@ func (mgr *autocertManager) getConfig(options *Options) (*certmagic.Config, erro
}
acmeMgr := certmagic.NewACMEManager(cm, certmagic.DefaultACME)
acmeMgr.Agreed = true
if options.AutoCertUseStaging {
if options.AutocertOptions.UseStaging {
acmeMgr.CA = certmagic.LetsEncryptStagingCA
}
acmeMgr.DisableTLSALPNChallenge = true
@ -55,7 +81,7 @@ func (mgr *autocertManager) getConfig(options *Options) (*certmagic.Config, erro
}
func (mgr *autocertManager) update(options *Options) error {
if !options.AutoCert {
if !options.AutocertOptions.Enable {
return nil
}

View file

@ -69,21 +69,6 @@ type Options struct {
// This should be used only for testing.
InsecureServer bool `mapstructure:"insecure_server" yaml:"insecure_server,omitempty"`
// AutoCert enables fully automated certificate management including issuance
// and renewal from LetsEncrypt. Must be used in conjunction with AutoCertFolder.
AutoCert bool `mapstructure:"autocert" yaml:"autocert,omitempty"`
// AutoCertFolder specifies the location to store, and load autocert managed
// TLS certificates.
// defaults to $XDG_DATA_HOME/pomerium
AutoCertFolder string `mapstructure:"autocert_dir" yaml:"autocert_dir,omitempty"`
// AutoCertUseStaging tells autocert to use Let's Encrypt's staging CA which
// has less strict usage limits then the (default) production CA.
//
// https://letsencrypt.org/docs/staging-environment/
AutoCertUseStaging bool `mapstructure:"autocert_use_staging" yaml:"autocert_use_staging,omitempty"`
CertificateFiles []certificateFilePair `mapstructure:"certificates" yaml:"certificates,omitempty"`
// Cert and Key is the x509 certificate used to create the HTTPS server.
@ -245,6 +230,8 @@ type Options struct {
ClientCAFile string `mapstructure:"client_ca_file" yaml:"client_ca_file,omitempty"`
viper *viper.Viper
AutocertOptions `mapstructure:",squash" yaml:",inline"`
}
type certificateFilePair struct {
@ -280,8 +267,11 @@ var defaultOptions = Options{
GRPCServerMaxConnectionAgeGrace: 5 * time.Minute,
CacheStore: "autocache",
AuthenticateCallbackPath: "/oauth2/callback",
AutoCertFolder: dataDir(),
TracingSampleRate: 0.0001,
AutocertOptions: AutocertOptions{
Folder: dataDir(),
},
}
// NewDefaultOptions returns a copy the default options. It's the caller's
@ -448,6 +438,16 @@ func bindEnvs(o *Options, v *viper.Viper) error {
if err != nil {
return fmt.Errorf("failed to bind field 'HeadersEnv' to env var 'HEADERS': %w", err)
}
// autocert options
ao := reflect.TypeOf(o.AutocertOptions)
for i := 0; i < ao.NumField(); i++ {
field := ao.Field(i)
envName := field.Tag.Get(tagName)
err := v.BindEnv(envName)
if err != nil {
return fmt.Errorf("failed to bind field '%s' to env var '%s': %w", field.Name, envName, err)
}
}
return nil
}

View file

@ -314,6 +314,35 @@ func Test_NewOptionsFromConfigEnvVar(t *testing.T) {
}
}
func Test_AutoCertOptionsFromEnvVar(t *testing.T) {
envs := map[string]string{
"AUTOCERT": "true",
"AUTOCERT_DIR": "/test",
"AUTOCERT_MUST_STAPLE": "true",
"INSECURE_SERVER": "true",
}
for k, v := range envs {
os.Setenv(k, v)
defer os.Unsetenv(k)
}
o, err := NewOptionsFromConfig("")
if err != nil {
t.Fatal(err)
}
if !o.AutocertOptions.Enable {
t.Error("o.AutocertOptions.Enable: want true, got false")
}
if !o.AutocertOptions.MustStaple {
t.Error("o.AutocertOptions.MustStaple: want true, got false")
}
if o.AutocertOptions.Folder != "/test" {
t.Errorf("o.AutocertOptions.Folder: want /test, got %s", o.AutocertOptions.Folder)
}
}
type mockService struct {
fail bool
Updated bool

View file

@ -67,6 +67,24 @@ Autocert requires that ports `80`/`443` be accessible from the internet in order
:::
### Autocert Must-Staple
- Environmental Variable: `AUTOCERT_MUST_STAPLE`
- Config File Key: `autocert_must_staple`
- Type: `bool`
- Optional
If true, cause autocert to request a certificate with `status_request`
extension (commonly called `Must-Staple`). This allows the TLS client
(the browser) to fail immediately if the TLS handshake doesn't include
OCSP stapling information. Only used when [Autocert](./#autocert) is
true.
NOTE: this only takes effect the next time Pomerium renews your
certificates.
See also https://tools.ietf.org/html/rfc7633 for more context.
### Autocert Directory
- Environmental Variable: either `AUTOCERT_DIR`

4
go.sum
View file

@ -735,7 +735,6 @@ google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9Ywl
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
@ -788,12 +787,10 @@ gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQ
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/cookieo9/resources-go.v2 v2.0.0-20150225115733-d27c04069d0d h1:YjTGSRV59gG1DHCq68v2B771I9dGFxvMkugf7OKglpk=
gopkg.in/cookieo9/resources-go.v2 v2.0.0-20150225115733-d27c04069d0d/go.mod h1:kbUs813+JgwKQdecaTv87br/FZUaSEuPj8vbr2vq8sY=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/h2non/gock.v1 v1.0.15/go.mod h1:sX4zAkdYX1TRGJ2JY156cFspQn4yRWn6p9EMdODlynE=
gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
@ -806,7 +803,6 @@ gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w=
gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=