feat: prevent call bursts on oidc provider refresh
All checks were successful
Cadoles/bouncer/pipeline/head This commit looks good
All checks were successful
Cadoles/bouncer/pipeline/head This commit looks good
This commit is contained in:
@ -13,10 +13,12 @@ import (
|
||||
"time"
|
||||
|
||||
"forge.cadoles.com/Cadoles/go-proxy/wildcard"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/cache"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/cache/memory"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/cache/ttl"
|
||||
"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"
|
||||
"forge.cadoles.com/cadoles/bouncer/internal/syncx"
|
||||
"github.com/coreos/go-oidc/v3/oidc"
|
||||
"github.com/gorilla/sessions"
|
||||
"github.com/pkg/errors"
|
||||
@ -25,10 +27,10 @@ import (
|
||||
)
|
||||
|
||||
type Authenticator struct {
|
||||
store sessions.Store
|
||||
httpTransport *http.Transport
|
||||
httpClientTimeout time.Duration
|
||||
oidcProviderCache cache.Cache[string, *oidc.Provider]
|
||||
store sessions.Store
|
||||
httpTransport *http.Transport
|
||||
httpClientTimeout time.Duration
|
||||
cachedOIDCProvider *syncx.CachedResource[string, *oidc.Provider]
|
||||
}
|
||||
|
||||
func (a *Authenticator) PreAuthentication(w http.ResponseWriter, r *http.Request, layer *store.Layer) error {
|
||||
@ -54,7 +56,7 @@ func (a *Authenticator) PreAuthentication(w http.ResponseWriter, r *http.Request
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
client, err := a.getClient(options, loginCallbackURL.String())
|
||||
client, err := a.getClient(ctx, options, loginCallbackURL.String())
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
@ -160,7 +162,7 @@ func (a *Authenticator) Authenticate(w http.ResponseWriter, r *http.Request, lay
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
client, err := a.getClient(options, loginCallbackURL.String())
|
||||
client, err := a.getClient(ctx, options, loginCallbackURL.String())
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
@ -362,9 +364,7 @@ func (a *Authenticator) templatize(rawTemplate string, proxyName store.ProxyName
|
||||
return raw.String(), nil
|
||||
}
|
||||
|
||||
func (a *Authenticator) getClient(options *LayerOptions, redirectURL string) (*Client, error) {
|
||||
ctx := context.Background()
|
||||
|
||||
func (a *Authenticator) getClient(ctx context.Context, options *LayerOptions, redirectURL string) (*Client, error) {
|
||||
transport := a.httpTransport.Clone()
|
||||
|
||||
if options.OIDC.TLSInsecureSkipVerify {
|
||||
@ -375,28 +375,24 @@ func (a *Authenticator) getClient(options *LayerOptions, redirectURL string) (*C
|
||||
transport.TLSClientConfig.InsecureSkipVerify = true
|
||||
}
|
||||
|
||||
if options.OIDC.SkipIssuerVerification {
|
||||
ctx = oidc.InsecureIssuerURLContext(ctx, options.OIDC.IssuerURL)
|
||||
}
|
||||
|
||||
httpClient := &http.Client{
|
||||
Timeout: a.httpClientTimeout,
|
||||
Transport: transport,
|
||||
}
|
||||
|
||||
provider, exists := a.oidcProviderCache.Get(options.OIDC.IssuerURL)
|
||||
if !exists {
|
||||
var err error
|
||||
ctx = oidc.ClientContext(ctx, httpClient)
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
provider, _, err := a.cachedOIDCProvider.Get(ctx, options.OIDC.IssuerURL)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not retrieve oidc provider")
|
||||
}
|
||||
|
||||
client := NewClient(
|
||||
@ -411,6 +407,17 @@ func (a *Authenticator) getClient(options *LayerOptions, redirectURL string) (*C
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func (a *Authenticator) getOIDCProvider(ctx context.Context, issuerURL string) (*oidc.Provider, error) {
|
||||
logger.Debug(ctx, "refreshing oidc provider", logger.F("issuerURL", issuerURL))
|
||||
|
||||
provider, err := oidc.NewProvider(ctx, issuerURL)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not create oidc provider")
|
||||
}
|
||||
|
||||
return provider, nil
|
||||
}
|
||||
|
||||
const defaultCookieNamePrefix = "_bouncer_authn_oidc"
|
||||
|
||||
func (a *Authenticator) getCookieName(cookieName string, proxyName store.ProxyName, layerName store.LayerName) string {
|
||||
@ -421,6 +428,25 @@ func (a *Authenticator) getCookieName(cookieName string, proxyName store.ProxyNa
|
||||
return strings.ToLower(fmt.Sprintf("%s_%s_%s", defaultCookieNamePrefix, proxyName, layerName))
|
||||
}
|
||||
|
||||
func NewAuthenticator(httpTransport *http.Transport, clientTimeout time.Duration, store sessions.Store, oidcProviderCacheTimeout time.Duration) *Authenticator {
|
||||
authenticator := &Authenticator{
|
||||
httpTransport: httpTransport,
|
||||
httpClientTimeout: clientTimeout,
|
||||
store: store,
|
||||
}
|
||||
|
||||
authenticator.cachedOIDCProvider = syncx.NewCachedResource(
|
||||
ttl.NewCache(
|
||||
memory.NewCache[string, *oidc.Provider](),
|
||||
memory.NewCache[string, time.Time](),
|
||||
oidcProviderCacheTimeout,
|
||||
),
|
||||
authenticator.getOIDCProvider,
|
||||
)
|
||||
|
||||
return authenticator
|
||||
}
|
||||
|
||||
var (
|
||||
_ authn.PreAuthentication = &Authenticator{}
|
||||
_ authn.Authenticator = &Authenticator{}
|
||||
|
@ -1,13 +1,8 @@
|
||||
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"
|
||||
)
|
||||
|
||||
@ -15,14 +10,11 @@ const LayerType store.LayerType = "authn-oidc"
|
||||
|
||||
func NewLayer(store sessions.Store, funcs ...OptionFunc) *authn.Layer {
|
||||
opts := NewOptions(funcs...)
|
||||
return authn.NewLayer(LayerType, &Authenticator{
|
||||
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...)
|
||||
authenticator := NewAuthenticator(
|
||||
opts.HTTPTransport,
|
||||
opts.HTTPClientTimeout,
|
||||
store,
|
||||
opts.OIDCProviderCacheTimeout,
|
||||
)
|
||||
return authn.NewLayer(LayerType, authenticator, opts.AuthnOptions...)
|
||||
}
|
||||
|
Reference in New Issue
Block a user