diff --git a/internal/config/layers.go b/internal/config/layers.go index 97fc5d3..c9ed558 100644 --- a/internal/config/layers.go +++ b/internal/config/layers.go @@ -20,6 +20,7 @@ func NewDefaultLayersConfig() LayersConfig { TransportConfig: NewDefaultTransportConfig(), Timeout: NewInterpolatedDuration(10 * time.Second), }, + ProviderCacheTimeout: NewInterpolatedDuration(time.Hour), }, Sessions: AuthnLayerSessionConfig{ TTL: NewInterpolatedDuration(time.Hour), @@ -45,7 +46,8 @@ type AuthnLayerSessionConfig struct { } type AuthnOIDCLayerConfig struct { - HTTPClient AuthnOIDCHTTPClientConfig `yaml:"httpClient"` + HTTPClient AuthnOIDCHTTPClientConfig `yaml:"httpClient"` + ProviderCacheTimeout *InterpolatedDuration `yaml:"providerCacheTimeout"` } type AuthnOIDCHTTPClientConfig struct { diff --git a/internal/proxy/director/layer/authn/oidc/authenticator.go b/internal/proxy/director/layer/authn/oidc/authenticator.go index ea1e1e6..abfc3a6 100644 --- a/internal/proxy/director/layer/authn/oidc/authenticator.go +++ b/internal/proxy/director/layer/authn/oidc/authenticator.go @@ -13,6 +13,7 @@ import ( "time" "forge.cadoles.com/Cadoles/go-proxy/wildcard" + "forge.cadoles.com/cadoles/bouncer/internal/cache" "forge.cadoles.com/cadoles/bouncer/internal/proxy/director" "forge.cadoles.com/cadoles/bouncer/internal/proxy/director/layer/authn" "forge.cadoles.com/cadoles/bouncer/internal/store" @@ -27,6 +28,7 @@ type Authenticator struct { store sessions.Store httpTransport *http.Transport httpClientTimeout time.Duration + oidcProviderCache cache.Cache[string, *oidc.Provider] } func (a *Authenticator) PreAuthentication(w http.ResponseWriter, r *http.Request, layer *store.Layer) error { @@ -378,15 +380,23 @@ func (a *Authenticator) getClient(options *LayerOptions, redirectURL string) (*C Transport: transport, } - ctx = oidc.ClientContext(ctx, httpClient) + provider, exists := a.oidcProviderCache.Get(options.OIDC.IssuerURL) + if !exists { + var err error + ctx = oidc.ClientContext(ctx, httpClient) - if options.OIDC.SkipIssuerVerification { - ctx = oidc.InsecureIssuerURLContext(ctx, options.OIDC.IssuerURL) - } + if options.OIDC.SkipIssuerVerification { + ctx = oidc.InsecureIssuerURLContext(ctx, options.OIDC.IssuerURL) + } - provider, err := oidc.NewProvider(ctx, options.OIDC.IssuerURL) - if err != nil { - return nil, errors.Wrap(err, "could not create oidc provider") + logger.Debug(ctx, "refreshing oidc provider", logger.F("issuerURL", options.OIDC.IssuerURL)) + + provider, err = oidc.NewProvider(ctx, options.OIDC.IssuerURL) + if err != nil { + return nil, errors.Wrap(err, "could not create oidc provider") + } + + a.oidcProviderCache.Set(options.OIDC.IssuerURL, provider) } client := NewClient( diff --git a/internal/proxy/director/layer/authn/oidc/layer.go b/internal/proxy/director/layer/authn/oidc/layer.go index 9509361..617988f 100644 --- a/internal/proxy/director/layer/authn/oidc/layer.go +++ b/internal/proxy/director/layer/authn/oidc/layer.go @@ -1,8 +1,13 @@ package oidc import ( + "time" + + "forge.cadoles.com/cadoles/bouncer/internal/cache/memory" + "forge.cadoles.com/cadoles/bouncer/internal/cache/ttl" "forge.cadoles.com/cadoles/bouncer/internal/proxy/director/layer/authn" "forge.cadoles.com/cadoles/bouncer/internal/store" + "github.com/coreos/go-oidc/v3/oidc" "github.com/gorilla/sessions" ) @@ -14,5 +19,10 @@ func NewLayer(store sessions.Store, funcs ...OptionFunc) *authn.Layer { httpTransport: opts.HTTPTransport, httpClientTimeout: opts.HTTPClientTimeout, store: store, + oidcProviderCache: ttl.NewCache( + memory.NewCache[string, *oidc.Provider](), + memory.NewCache[string, time.Time](), + opts.OIDCProviderCacheTimeout, + ), }, opts.AuthnOptions...) } diff --git a/internal/proxy/director/layer/authn/oidc/options.go b/internal/proxy/director/layer/authn/oidc/options.go index a2f0c14..cbad2f4 100644 --- a/internal/proxy/director/layer/authn/oidc/options.go +++ b/internal/proxy/director/layer/authn/oidc/options.go @@ -8,9 +8,10 @@ import ( ) type Options struct { - HTTPTransport *http.Transport - HTTPClientTimeout time.Duration - AuthnOptions []authn.OptionFunc + HTTPTransport *http.Transport + HTTPClientTimeout time.Duration + AuthnOptions []authn.OptionFunc + OIDCProviderCacheTimeout time.Duration } type OptionFunc func(opts *Options) @@ -33,11 +34,18 @@ func WithAuthnOptions(funcs ...authn.OptionFunc) OptionFunc { } } +func WithOIDCProviderCacheTimeout(timeout time.Duration) OptionFunc { + return func(opts *Options) { + opts.OIDCProviderCacheTimeout = timeout + } +} + func NewOptions(funcs ...OptionFunc) *Options { opts := &Options{ - HTTPTransport: http.DefaultTransport.(*http.Transport), - HTTPClientTimeout: 30 * time.Second, - AuthnOptions: make([]authn.OptionFunc, 0), + HTTPTransport: http.DefaultTransport.(*http.Transport), + HTTPClientTimeout: 30 * time.Second, + AuthnOptions: make([]authn.OptionFunc, 0), + OIDCProviderCacheTimeout: time.Hour, } for _, fn := range funcs { diff --git a/internal/setup/authn_oidc_layer.go b/internal/setup/authn_oidc_layer.go index 5db66fe..14e603a 100644 --- a/internal/setup/authn_oidc_layer.go +++ b/internal/setup/authn_oidc_layer.go @@ -37,5 +37,6 @@ func setupAuthnOIDCLayer(conf *config.Config) (director.Layer, error) { authn.WithTemplateDir(string(conf.Layers.Authn.TemplateDir)), authn.WithDebug(bool(conf.Layers.Authn.Debug)), ), + oidc.WithOIDCProviderCacheTimeout(time.Duration(*conf.Layers.Authn.OIDC.ProviderCacheTimeout)), ), nil }