From 8229567213e8dbfeeff6cc95d8d202af40d1b861 Mon Sep 17 00:00:00 2001 From: Ralf Haferkamp Date: Fri, 22 Jul 2022 15:01:25 +0200 Subject: [PATCH] Allow to configure the JWKS refresh settings This exposes a couple for knobs for the jwks keyfunc module to adjust timeout and refresh intervals. --- services/proxy/pkg/command/server.go | 1 + services/proxy/pkg/config/config.go | 8 ++++++++ services/proxy/pkg/config/defaults/defaultconfig.go | 6 ++++++ services/proxy/pkg/middleware/authentication.go | 1 + services/proxy/pkg/middleware/oidc_auth.go | 11 ++++++----- services/proxy/pkg/middleware/options.go | 9 +++++++++ 6 files changed, 31 insertions(+), 5 deletions(-) diff --git a/services/proxy/pkg/command/server.go b/services/proxy/pkg/command/server.go index c25400801d..5ce0d1373a 100644 --- a/services/proxy/pkg/command/server.go +++ b/services/proxy/pkg/command/server.go @@ -190,6 +190,7 @@ func loadMiddlewares(ctx context.Context, logger log.Logger, cfg *config.Config) middleware.TokenCacheSize(cfg.OIDC.UserinfoCache.Size), middleware.TokenCacheTTL(time.Second*time.Duration(cfg.OIDC.UserinfoCache.TTL)), middleware.AccessTokenVerifyMethod(cfg.OIDC.AccessTokenVerifyMethod), + middleware.JWKSOptions(cfg.OIDC.JWKS), // basic Options middleware.Logger(logger), diff --git a/services/proxy/pkg/config/config.go b/services/proxy/pkg/config/config.go index 06ca1df8ef..6dbae2a2ba 100644 --- a/services/proxy/pkg/config/config.go +++ b/services/proxy/pkg/config/config.go @@ -94,6 +94,14 @@ type OIDC struct { Insecure bool `yaml:"insecure" env:"OCIS_INSECURE;PROXY_OIDC_INSECURE" desc:"Disable TLS certificate validation for connections to the IDP. Note that this is not recommended for production environments."` AccessTokenVerifyMethod string `yaml:"access_token_verify_method" env:"PROXY_OIDC_ACCESS_TOKEN_VERIFY_METHOD" desc:"Sets how OIDC access tokens should be verified. Possible values: 'none', which means that no special validation apart from using it for accessing the IPD's userinfo endpoint will be done. Or 'jwt', which tries to parse the access token as a jwt token and verifies the signature using the keys published on the IDP's 'jwks_uri'."` UserinfoCache UserinfoCache `yaml:"user_info_cache"` + JWKS JWKS `yaml:"jwks"` +} + +type JWKS struct { + RefreshInterval uint64 `yaml:"refresh_interval" env:"PROXY_OIDC_JWKS_REFRESH_INTERVAL" desc:"The interval for refreshing the JWKS in the background via a new HTTP request to the IDP in minutes."` + RefreshTimeout uint64 `yaml:"refresh_timeout" env:"PROXY_OIDC_JWKS_REFRESH_TIMEOUT" desc:"The timeout, in seconds, for and outgoing JWKS request."` + RefreshRateLimit uint64 `yaml:"refresh_limit" env:"PROXY_OIDC_JWKS_REFRESH_RATE_LIMIT" desc:"Limits the rate at which refresh requests are performed for unknown keys in seconds. (To prevent malicious client from imposing high network load on the IDP via ocis)"` + RefreshUnknownKID bool `yaml:"refresh_unknown_kid" env:"PROXY_OIDC_JWKS_REFRESH_UNKNOWN_KID" desc:"If true the JWKS refresh request will occur every time an unknown key id (kid) is seen. Always set a 'refresh_limit' when enabling this"` } // UserinfoCache is a TTL cache configuration. diff --git a/services/proxy/pkg/config/defaults/defaultconfig.go b/services/proxy/pkg/config/defaults/defaultconfig.go index 62ad229800..1c6f848934 100644 --- a/services/proxy/pkg/config/defaults/defaultconfig.go +++ b/services/proxy/pkg/config/defaults/defaultconfig.go @@ -41,6 +41,12 @@ func DefaultConfig() *config.Config { Size: 1024, TTL: 10, }, + JWKS: config.JWKS{ + RefreshInterval: 60, // minutes + RefreshRateLimit: 60, // seconds + RefreshTimeout: 10, // seconds + RefreshUnknownKID: true, + }, }, PolicySelector: nil, Reva: &config.Reva{ diff --git a/services/proxy/pkg/middleware/authentication.go b/services/proxy/pkg/middleware/authentication.go index 1a06157cd6..15fa4ec429 100644 --- a/services/proxy/pkg/middleware/authentication.go +++ b/services/proxy/pkg/middleware/authentication.go @@ -116,6 +116,7 @@ func newOIDCAuth(options Options) func(http.Handler) http.Handler { TokenCacheTTL(options.UserinfoCacheTTL), CredentialsByUserAgent(options.CredentialsByUserAgent), AccessTokenVerifyMethod(options.AccessTokenVerifyMethod), + JWKSOptions(options.JWKS), ) } diff --git a/services/proxy/pkg/middleware/oidc_auth.go b/services/proxy/pkg/middleware/oidc_auth.go index bc4ba99ebd..3caa6d591b 100644 --- a/services/proxy/pkg/middleware/oidc_auth.go +++ b/services/proxy/pkg/middleware/oidc_auth.go @@ -37,6 +37,7 @@ func OIDCAuth(optionSetters ...Option) func(next http.Handler) http.Handler { tokenCache: &tokenCache, tokenCacheTTL: options.UserinfoCacheTTL, accessTokenVerifyMethod: options.AccessTokenVerifyMethod, + jwksOptions: options.JWKS, } return func(next http.Handler) http.Handler { @@ -77,6 +78,7 @@ func OIDCAuth(optionSetters ...Option) func(next http.Handler) http.Handler { type oidcAuth struct { logger log.Logger provider OIDCProvider + jwksOptions config.JWKS jwks *keyfunc.JWKS providerFunc func() (OIDCProvider, error) httpClient *http.Client @@ -233,16 +235,15 @@ func (m *oidcAuth) getKeyfunc() *keyfunc.JWKS { return nil } m.logger.Debug().Str("jwks", j.JWKSURL).Msg("discovered jwks endpoint") - // FIXME: make configurable options := keyfunc.Options{ Client: m.httpClient, RefreshErrorHandler: func(err error) { m.logger.Error().Err(err).Msg("There was an error with the jwt.Keyfunc") }, - RefreshInterval: time.Hour, - RefreshRateLimit: time.Minute * 5, - RefreshTimeout: time.Second * 10, - RefreshUnknownKID: true, + RefreshInterval: time.Minute * time.Duration(m.jwksOptions.RefreshInterval), + RefreshRateLimit: time.Second * time.Duration(m.jwksOptions.RefreshRateLimit), + RefreshTimeout: time.Second * time.Duration(m.jwksOptions.RefreshTimeout), + RefreshUnknownKID: m.jwksOptions.RefreshUnknownKID, } m.jwks, err = keyfunc.Get(j.JWKSURL, options) if err != nil { diff --git a/services/proxy/pkg/middleware/options.go b/services/proxy/pkg/middleware/options.go index d963e50b24..2d07b2b772 100644 --- a/services/proxy/pkg/middleware/options.go +++ b/services/proxy/pkg/middleware/options.go @@ -58,6 +58,8 @@ type Options struct { // AccessTokenVerifyMethod configures how access_tokens should be verified but the oidc_auth middleware. // Possible values currently: "jwt" and "none" AccessTokenVerifyMethod string + // JWKS sets the options for fetching the JWKS from the IDP + JWKS config.JWKS } // newOptions initializes the available default options. @@ -203,3 +205,10 @@ func AccessTokenVerifyMethod(method string) Option { o.AccessTokenVerifyMethod = method } } + +// JWKS sets the options for fetching the JWKS from the IDP +func JWKSOptions(jo config.JWKS) Option { + return func(o *Options) { + o.JWKS = jo + } +}