config: support files for shared_secret, client_secret, cookie_secret and signing_key (#3453)

This commit is contained in:
Caleb Doxsey 2022-06-29 10:44:08 -06:00 committed by GitHub
parent 1eca93cc75
commit 86625a4ddb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 136 additions and 34 deletions

View file

@ -23,7 +23,11 @@ func ValidateOptions(o *config.Options) error {
if _, err := cryptutil.NewAEADCipher(sharedKey); err != nil { if _, err := cryptutil.NewAEADCipher(sharedKey); err != nil {
return fmt.Errorf("authenticate: 'SHARED_SECRET' invalid: %w", err) return fmt.Errorf("authenticate: 'SHARED_SECRET' invalid: %w", err)
} }
if _, err := cryptutil.NewAEADCipherFromBase64(o.CookieSecret); err != nil { cookieSecret, err := o.GetCookieSecret()
if err != nil {
return fmt.Errorf("authenticate: 'COOKIE_SECRET' invalid: %w", err)
}
if _, err := cryptutil.NewAEADCipher(cookieSecret); err != nil {
return fmt.Errorf("authenticate: 'COOKIE_SECRET' invalid %w", err) return fmt.Errorf("authenticate: 'COOKIE_SECRET' invalid %w", err)
} }
if o.AuthenticateCallbackPath == "" { if o.AuthenticateCallbackPath == "" {

View file

@ -19,7 +19,10 @@ func defaultGetIdentityProvider(options *config.Options, idpID string) (identity
} }
redirectURL.Path = options.AuthenticateCallbackPath redirectURL.Path = options.AuthenticateCallbackPath
idp := options.GetIdentityProviderForID(idpID) idp, err := options.GetIdentityProviderForID(idpID)
if err != nil {
return nil, err
}
return identity.NewAuthenticator(oauth.Options{ return identity.NewAuthenticator(oauth.Options{
RedirectURL: redirectURL, RedirectURL: redirectURL,
ProviderName: idp.GetType(), ProviderName: idp.GetType(),

View file

@ -101,7 +101,7 @@ func newAuthenticateStateFromConfig(cfg *config.Config) (*authenticateState, err
} }
// private state encoder setup, used to encrypt oauth2 tokens // private state encoder setup, used to encrypt oauth2 tokens
state.cookieSecret, err = base64.StdEncoding.DecodeString(cfg.Options.CookieSecret) state.cookieSecret, err = cfg.Options.GetCookieSecret()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -131,7 +131,11 @@ func newAuthenticateStateFromConfig(cfg *config.Config) (*authenticateState, err
state.sessionStore = cookieStore state.sessionStore = cookieStore
state.sessionLoaders = []sessions.SessionLoader{headerStore, cookieStore} state.sessionLoaders = []sessions.SessionLoader{headerStore, cookieStore}
state.jwk = new(jose.JSONWebKeySet) state.jwk = new(jose.JSONWebKeySet)
if cfg.Options.SigningKey != "" { signingKey, err := cfg.Options.GetSigningKey()
if err != nil {
return nil, err
}
if signingKey != "" {
decodedCert, err := base64.StdEncoding.DecodeString(cfg.Options.SigningKey) decodedCert, err := base64.StdEncoding.DecodeString(cfg.Options.SigningKey)
if err != nil { if err != nil {
return nil, fmt.Errorf("authenticate: failed to decode signing key: %w", err) return nil, fmt.Errorf("authenticate: failed to decode signing key: %w", err)

View file

@ -103,10 +103,15 @@ func newPolicyEvaluator(opts *config.Options, store *store.Store) (*evaluator.Ev
return nil, fmt.Errorf("authorize: invalid authenticate url: %w", err) return nil, fmt.Errorf("authorize: invalid authenticate url: %w", err)
} }
signingKey, err := opts.GetSigningKey()
if err != nil {
return nil, fmt.Errorf("authorize: invalid signing key: %w", err)
}
return evaluator.New(ctx, store, return evaluator.New(ctx, store,
evaluator.WithPolicies(opts.GetAllPolicies()), evaluator.WithPolicies(opts.GetAllPolicies()),
evaluator.WithClientCA(clientCA), evaluator.WithClientCA(clientCA),
evaluator.WithSigningKey(opts.SigningKey), evaluator.WithSigningKey(signingKey),
evaluator.WithAuthenticateURL(authenticateURL.String()), evaluator.WithAuthenticateURL(authenticateURL.String()),
evaluator.WithGoogleCloudServerlessAuthenticationServiceAccount(opts.GetGoogleCloudServerlessAuthenticationServiceAccount()), evaluator.WithGoogleCloudServerlessAuthenticationServiceAccount(opts.GetGoogleCloudServerlessAuthenticationServiceAccount()),
evaluator.WithJWTClaimsHeaders(opts.JWTClaimsHeaders), evaluator.WithJWTClaimsHeaders(opts.JWTClaimsHeaders),

View file

@ -166,7 +166,11 @@ func (a *Authorize) requireLoginResponse(
checkRequestURL.Scheme = "https" checkRequestURL.Scheme = "https"
q.Set(urlutil.QueryRedirectURI, checkRequestURL.String()) q.Set(urlutil.QueryRedirectURI, checkRequestURL.String())
q.Set(urlutil.QueryIdentityProviderID, opts.GetIdentityProviderForPolicy(request.Policy).GetId()) idp, err := opts.GetIdentityProviderForPolicy(request.Policy)
if err != nil {
return nil, err
}
q.Set(urlutil.QueryIdentityProviderID, idp.GetId())
signinURL.RawQuery = q.Encode() signinURL.RawQuery = q.Encode()
redirectTo := urlutil.NewSignedURL(state.sharedKey, signinURL).String() redirectTo := urlutil.NewSignedURL(state.sharedKey, signinURL).String()
@ -210,7 +214,11 @@ func (a *Authorize) requireWebAuthnResponse(
q.Set(urlutil.QueryDeviceType, webauthnutil.DefaultDeviceType) q.Set(urlutil.QueryDeviceType, webauthnutil.DefaultDeviceType)
} }
q.Set(urlutil.QueryRedirectURI, checkRequestURL.String()) q.Set(urlutil.QueryRedirectURI, checkRequestURL.String())
q.Set(urlutil.QueryIdentityProviderID, opts.GetIdentityProviderForPolicy(request.Policy).GetId()) idp, err := opts.GetIdentityProviderForPolicy(request.Policy)
if err != nil {
return nil, err
}
q.Set(urlutil.QueryIdentityProviderID, idp.GetId())
signinURL.RawQuery = q.Encode() signinURL.RawQuery = q.Encode()
redirectTo := urlutil.NewSignedURL(state.sharedKey, signinURL).String() redirectTo := urlutil.NewSignedURL(state.sharedKey, signinURL).String()

View file

@ -222,14 +222,18 @@ func (src *FileWatcherSource) check(ctx context.Context, cfg *Config) {
cfg.Options.CertFile, cfg.Options.CertFile,
cfg.Options.ClientCAFile, cfg.Options.ClientCAFile,
cfg.Options.ClientCRLFile, cfg.Options.ClientCRLFile,
cfg.Options.ClientSecretFile,
cfg.Options.CookieSecretFile,
cfg.Options.DataBrokerStorageCAFile, cfg.Options.DataBrokerStorageCAFile,
cfg.Options.DataBrokerStorageCertFile, cfg.Options.DataBrokerStorageCertFile,
cfg.Options.DataBrokerStorageCertKeyFile, cfg.Options.DataBrokerStorageCertKeyFile,
cfg.Options.KeyFile, cfg.Options.KeyFile,
cfg.Options.PolicyFile,
cfg.Options.MetricsClientCAFile,
cfg.Options.MetricsCertificateFile, cfg.Options.MetricsCertificateFile,
cfg.Options.MetricsCertificateKeyFile, cfg.Options.MetricsCertificateKeyFile,
cfg.Options.MetricsClientCAFile,
cfg.Options.PolicyFile,
cfg.Options.SharedSecretFile,
cfg.Options.SigningKeyFile,
} }
for _, pair := range cfg.Options.CertificateFiles { for _, pair := range cfg.Options.CertificateFiles {

View file

@ -6,11 +6,14 @@ import (
// GetIdentityProviderForID returns the identity provider associated with the given IDP id. // GetIdentityProviderForID returns the identity provider associated with the given IDP id.
// If none is found the default provider is returned. // If none is found the default provider is returned.
func (o *Options) GetIdentityProviderForID(idpID string) *identity.Provider { func (o *Options) GetIdentityProviderForID(idpID string) (*identity.Provider, error) {
for _, policy := range o.GetAllPolicies() { for _, policy := range o.GetAllPolicies() {
idp := o.GetIdentityProviderForPolicy(&policy) //nolint idp, err := o.GetIdentityProviderForPolicy(&policy) //nolint
if err != nil {
return nil, err
}
if idp.GetId() == idpID { if idp.GetId() == idpID {
return idp return idp, nil
} }
} }
@ -19,10 +22,15 @@ func (o *Options) GetIdentityProviderForID(idpID string) *identity.Provider {
// GetIdentityProviderForPolicy gets the identity provider associated with the given policy. // GetIdentityProviderForPolicy gets the identity provider associated with the given policy.
// If policy is nil, or changes none of the default settings, the default provider is returned. // If policy is nil, or changes none of the default settings, the default provider is returned.
func (o *Options) GetIdentityProviderForPolicy(policy *Policy) *identity.Provider { func (o *Options) GetIdentityProviderForPolicy(policy *Policy) (*identity.Provider, error) {
clientSecret, err := o.GetClientSecret()
if err != nil {
return nil, err
}
idp := &identity.Provider{ idp := &identity.Provider{
ClientId: o.ClientID, ClientId: o.ClientID,
ClientSecret: o.ClientSecret, ClientSecret: clientSecret,
Type: o.Provider, Type: o.Provider,
Scopes: o.Scopes, Scopes: o.Scopes,
ServiceAccount: o.ServiceAccount, ServiceAccount: o.ServiceAccount,
@ -38,5 +46,5 @@ func (o *Options) GetIdentityProviderForPolicy(policy *Policy) *identity.Provide
} }
} }
idp.Id = idp.Hash() idp.Id = idp.Hash()
return idp return idp, nil
} }

View file

@ -71,7 +71,8 @@ type Options struct {
// SharedKey is the shared secret authorization key used to mutually authenticate // SharedKey is the shared secret authorization key used to mutually authenticate
// requests between services. // requests between services.
SharedKey string `mapstructure:"shared_secret" yaml:"shared_secret,omitempty"` SharedKey string `mapstructure:"shared_secret" yaml:"shared_secret,omitempty"`
SharedSecretFile string `mapstructure:"shared_secret_file" yaml:"shared_secret_file,omitempty"`
// Services is a list enabled service mode. If none are selected, "all" is used. // Services is a list enabled service mode. If none are selected, "all" is used.
// Available options are : "all", "authenticate", "proxy". // Available options are : "all", "authenticate", "proxy".
@ -132,21 +133,23 @@ type Options struct {
// Session/Cookie management // Session/Cookie management
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie
CookieName string `mapstructure:"cookie_name" yaml:"cookie_name,omitempty"` CookieName string `mapstructure:"cookie_name" yaml:"cookie_name,omitempty"`
CookieSecret string `mapstructure:"cookie_secret" yaml:"cookie_secret,omitempty"` CookieSecret string `mapstructure:"cookie_secret" yaml:"cookie_secret,omitempty"`
CookieDomain string `mapstructure:"cookie_domain" yaml:"cookie_domain,omitempty"` CookieSecretFile string `mapstructure:"cookie_secret_file" yaml:"cookie_secret_file,omitempty"`
CookieSecure bool `mapstructure:"cookie_secure" yaml:"cookie_secure,omitempty"` CookieDomain string `mapstructure:"cookie_domain" yaml:"cookie_domain,omitempty"`
CookieHTTPOnly bool `mapstructure:"cookie_http_only" yaml:"cookie_http_only,omitempty"` CookieSecure bool `mapstructure:"cookie_secure" yaml:"cookie_secure,omitempty"`
CookieExpire time.Duration `mapstructure:"cookie_expire" yaml:"cookie_expire,omitempty"` CookieHTTPOnly bool `mapstructure:"cookie_http_only" yaml:"cookie_http_only,omitempty"`
CookieExpire time.Duration `mapstructure:"cookie_expire" yaml:"cookie_expire,omitempty"`
// Identity provider configuration variables as specified by RFC6749 // Identity provider configuration variables as specified by RFC6749
// https://openid.net/specs/openid-connect-basic-1_0.html#RFC6749 // https://openid.net/specs/openid-connect-basic-1_0.html#RFC6749
ClientID string `mapstructure:"idp_client_id" yaml:"idp_client_id,omitempty"` ClientID string `mapstructure:"idp_client_id" yaml:"idp_client_id,omitempty"`
ClientSecret string `mapstructure:"idp_client_secret" yaml:"idp_client_secret,omitempty"` ClientSecret string `mapstructure:"idp_client_secret" yaml:"idp_client_secret,omitempty"`
Provider string `mapstructure:"idp_provider" yaml:"idp_provider,omitempty"` ClientSecretFile string `mapstructure:"idp_client_secret_file" yaml:"idp_client_secret_file,omitempty"`
ProviderURL string `mapstructure:"idp_provider_url" yaml:"idp_provider_url,omitempty"` Provider string `mapstructure:"idp_provider" yaml:"idp_provider,omitempty"`
Scopes []string `mapstructure:"idp_scopes" yaml:"idp_scopes,omitempty"` ProviderURL string `mapstructure:"idp_provider_url" yaml:"idp_provider_url,omitempty"`
ServiceAccount string `mapstructure:"idp_service_account" yaml:"idp_service_account,omitempty"` Scopes []string `mapstructure:"idp_scopes" yaml:"idp_scopes,omitempty"`
ServiceAccount string `mapstructure:"idp_service_account" yaml:"idp_service_account,omitempty"`
// Identity provider refresh directory interval/timeout settings. // Identity provider refresh directory interval/timeout settings.
RefreshDirectoryTimeout time.Duration `mapstructure:"idp_refresh_directory_timeout" yaml:"idp_refresh_directory_timeout,omitempty"` RefreshDirectoryTimeout time.Duration `mapstructure:"idp_refresh_directory_timeout" yaml:"idp_refresh_directory_timeout,omitempty"`
RefreshDirectoryInterval time.Duration `mapstructure:"idp_refresh_directory_interval" yaml:"idp_refresh_directory_interval,omitempty"` RefreshDirectoryInterval time.Duration `mapstructure:"idp_refresh_directory_interval" yaml:"idp_refresh_directory_interval,omitempty"`
@ -173,7 +176,8 @@ type Options struct {
// SigningKey is the private key used to add a JWT-signature to upstream requests. // SigningKey is the private key used to add a JWT-signature to upstream requests.
// https://www.pomerium.com/docs/topics/getting-users-identity.html // https://www.pomerium.com/docs/topics/getting-users-identity.html
SigningKey string `mapstructure:"signing_key" yaml:"signing_key,omitempty"` SigningKey string `mapstructure:"signing_key" yaml:"signing_key,omitempty"`
SigningKeyFile string `mapstructure:"signing_key_file" yaml:"signing_key_file,omitempty"`
HeadersEnv string `yaml:",omitempty"` HeadersEnv string `yaml:",omitempty"`
// SetResponseHeaders to set on all proxied requests. Add a 'disable' key map to turn off. // SetResponseHeaders to set on all proxied requests. Add a 'disable' key map to turn off.
@ -895,12 +899,16 @@ func (o *Options) GetOauthOptions() (oauth.Options, error) {
redirectURL = redirectURL.ResolveReference(&url.URL{ redirectURL = redirectURL.ResolveReference(&url.URL{
Path: o.AuthenticateCallbackPath, Path: o.AuthenticateCallbackPath,
}) })
clientSecret, err := o.GetClientSecret()
if err != nil {
return oauth.Options{}, err
}
return oauth.Options{ return oauth.Options{
RedirectURL: redirectURL, RedirectURL: redirectURL,
ProviderName: o.Provider, ProviderName: o.Provider,
ProviderURL: o.ProviderURL, ProviderURL: o.ProviderURL,
ClientID: o.ClientID, ClientID: o.ClientID,
ClientSecret: o.ClientSecret, ClientSecret: clientSecret,
Scopes: o.Scopes, Scopes: o.Scopes,
ServiceAccount: o.ServiceAccount, ServiceAccount: o.ServiceAccount,
}, nil }, nil
@ -991,6 +999,13 @@ func (o *Options) GetCertificates() ([]tls.Certificate, error) {
// GetSharedKey gets the decoded shared key. // GetSharedKey gets the decoded shared key.
func (o *Options) GetSharedKey() ([]byte, error) { func (o *Options) GetSharedKey() ([]byte, error) {
sharedKey := o.SharedKey sharedKey := o.SharedKey
if o.SharedSecretFile != "" {
bs, err := os.ReadFile(o.SharedSecretFile)
if err != nil {
return nil, err
}
sharedKey = string(bs)
}
// mutual auth between services on the same host can be generated at runtime // mutual auth between services on the same host can be generated at runtime
if IsAll(o.Services) && o.SharedKey == "" && o.DataBrokerStorageType == StorageInMemoryName { if IsAll(o.Services) && o.SharedKey == "" && o.DataBrokerStorageType == StorageInMemoryName {
sharedKey = randomSharedKey sharedKey = randomSharedKey
@ -1174,6 +1189,49 @@ func (o *Options) GetAllRouteableHTTPDomainsForTLSServerName(tlsServerName strin
return domains.ToSlice(), nil return domains.ToSlice(), nil
} }
// GetClientSecret gets the client secret.
func (o *Options) GetClientSecret() (string, error) {
if o == nil {
return "", nil
}
if o.ClientSecretFile != "" {
bs, err := os.ReadFile(o.ClientSecretFile)
if err != nil {
return "", err
}
return string(bs), nil
}
return o.ClientSecret, nil
}
// GetCookieSecret gets the decoded cookie secret.
func (o *Options) GetCookieSecret() ([]byte, error) {
cookieSecret := o.CookieSecret
if o.CookieSecretFile != "" {
bs, err := os.ReadFile(o.CookieSecretFile)
if err != nil {
return nil, err
}
cookieSecret = string(bs)
}
return base64.StdEncoding.DecodeString(cookieSecret)
}
// GetSigningKey gets the signing key.
func (o *Options) GetSigningKey() (string, error) {
if o == nil {
return "", nil
}
if o.SigningKeyFile != "" {
bs, err := os.ReadFile(o.SigningKeyFile)
if err != nil {
return "", err
}
return string(bs), nil
}
return o.SigningKey, nil
}
// Checksum returns the checksum of the current options struct // Checksum returns the checksum of the current options struct
func (o *Options) Checksum() uint64 { func (o *Options) Checksum() uint64 {
return hashutil.MustHash(o) return hashutil.MustHash(o)

View file

@ -158,13 +158,18 @@ func (c *DataBroker) update(ctx context.Context, cfg *config.Config) error {
return fmt.Errorf("databroker: invalid oauth options: %w", err) return fmt.Errorf("databroker: invalid oauth options: %w", err)
} }
clientSecret, err := cfg.Options.GetClientSecret()
if err != nil {
return fmt.Errorf("databroker: error retrieving IPD client secret: %w", err)
}
directoryProvider := directory.GetProvider(directory.Options{ directoryProvider := directory.GetProvider(directory.Options{
ServiceAccount: cfg.Options.ServiceAccount, ServiceAccount: cfg.Options.ServiceAccount,
Provider: cfg.Options.Provider, Provider: cfg.Options.Provider,
ProviderURL: cfg.Options.ProviderURL, ProviderURL: cfg.Options.ProviderURL,
QPS: cfg.Options.GetQPS(), QPS: cfg.Options.GetQPS(),
ClientID: cfg.Options.ClientID, ClientID: cfg.Options.ClientID,
ClientSecret: cfg.Options.ClientSecret, ClientSecret: clientSecret,
}) })
c.mu.Lock() c.mu.Lock()
c.directoryProvider = directoryProvider c.directoryProvider = directoryProvider

View file

@ -38,7 +38,11 @@ func ValidateOptions(o *config.Options) error {
return fmt.Errorf("proxy: invalid 'SHARED_SECRET': %w", err) return fmt.Errorf("proxy: invalid 'SHARED_SECRET': %w", err)
} }
if _, err := cryptutil.NewAEADCipherFromBase64(o.CookieSecret); err != nil { cookieSecret, err := o.GetCookieSecret()
if err != nil {
return fmt.Errorf("proxy: invalid 'COOKIE_SECRET': %w", err)
}
if _, err := cryptutil.NewAEADCipher(cookieSecret); err != nil {
return fmt.Errorf("proxy: invalid 'COOKIE_SECRET': %w", err) return fmt.Errorf("proxy: invalid 'COOKIE_SECRET': %w", err)
} }

View file

@ -2,7 +2,6 @@ package proxy
import ( import (
"crypto/cipher" "crypto/cipher"
"encoding/base64"
"net/url" "net/url"
"sync/atomic" "sync/atomic"
@ -51,7 +50,7 @@ func newProxyStateFromConfig(cfg *config.Config) (*proxyState, error) {
return nil, err return nil, err
} }
state.cookieSecret, err = base64.StdEncoding.DecodeString(cfg.Options.CookieSecret) state.cookieSecret, err = cfg.Options.GetCookieSecret()
if err != nil { if err != nil {
return nil, err return nil, err
} }