From c8059e9be78918ae25040f3f9bb20ca4d92f4ab6 Mon Sep 17 00:00:00 2001 From: ahmethakanbesel Date: Tue, 19 Mar 2024 12:07:41 +0300 Subject: [PATCH 1/4] authn/oidc: add backoff strategy --- internal/authn/oidc/authn.go | 58 ++++++++++++++++++++++++++++-------- internal/config/config.go | 7 +++-- 2 files changed, 51 insertions(+), 14 deletions(-) diff --git a/internal/authn/oidc/authn.go b/internal/authn/oidc/authn.go index 08ff327fb..84d917eb9 100644 --- a/internal/authn/oidc/authn.go +++ b/internal/authn/oidc/authn.go @@ -40,6 +40,21 @@ type Authn struct { // httpClient is used to make HTTP requests, e.g., to fetch the JWKS. httpClient *http.Client + + // Last time the JWKS was fetched + lastKeyFetch time.Time + + // Last time the OIDC configuration was fetched + lastOIDCConfigFetch time.Time + + // KeyRefreshInterval is the interval to refresh the keys + keyRefreshInterval time.Duration + + // ConfigRefreshInterval is the interval to refresh the OIDC configuration + configRefreshInterval time.Duration + + // autoFetchKeysOnTokenNotFound is a flag to enable/disable auto fetching of keys when token is not found in the header + autoFetchKeysOnTokenNotFound bool } // NewOidcAuthn creates a new instance of Authn configured for OIDC authentication. @@ -54,13 +69,14 @@ func NewOidcAuthn(_ context.Context, conf config.Oidc) (*Authn, error) { // Create a new instance of Authn with the provided issuer URL and audience. // The httpClient is set to the standard net/http client wrapped with retry logic. oidc := &Authn{ - IssuerURL: conf.Issuer, - Audience: conf.Audience, - httpClient: client.StandardClient(), // Wrap retryable client as a standard http.Client + IssuerURL: conf.Issuer, + Audience: conf.Audience, + httpClient: client.StandardClient(), // Wrap retryable client as a standard http.Client + keyRefreshInterval: conf.KeyRefreshInterval, + configRefreshInterval: conf.ConfigRefreshInterval, + autoFetchKeysOnTokenNotFound: conf.AutoFetchKeysOnTokenNotFound, } - // Attempt to fetch the JWKS keys from the OIDC provider. - // This is crucial for setting up OIDC authentication as it enables token validation. err := oidc.fetchKeys() if err != nil { // If fetching keys fails, return an error to prevent initialization of a non-functional Authn instance. @@ -85,6 +101,20 @@ func (oidc *Authn) Authenticate(requestContext context.Context) error { // Parse and validate the JWT from the authentication header. token, err := jwtParser.Parse(authHeader, func(token *jwt.Token) (any, error) { + // If a presented token's KID is not found in the existing headers, initiate a JWKs fetch and validate the token. + if _, ok := token.Header["kid"].(string); !ok { + // Whem KID is absent in the header and it has been less than defaultKeyRefreshInterval since the last JWKs retrieval attempt, reject the token. + if !oidc.autoFetchKeysOnTokenNotFound && time.Since(oidc.lastKeyFetch) < oidc.keyRefreshInterval { + return nil, errors.New(base.ErrorCode_ERROR_CODE_INVALID_BEARER_TOKEN.String()) + } + + // Fetch the JWKS keys from the OIDC provider. + err := oidc.fetchKeys() + if err != nil { + return nil, err + } + } + // Use the JWKS from oidc to validate the JWT's signature. return oidc.JWKs.Keyfunc(token) }) @@ -118,19 +148,23 @@ func (oidc *Authn) Authenticate(requestContext context.Context) error { } func (oidc *Authn) fetchKeys() error { - oidcConfig, err := oidc.fetchOIDCConfiguration() - if err != nil { - return fmt.Errorf("error fetching OIDC configuration: %w", err) + if oidc.JwksURI == "" || time.Since(oidc.lastOIDCConfigFetch) > oidc.configRefreshInterval { + oidcConfig, err := oidc.fetchOIDCConfiguration() + if err != nil { + return fmt.Errorf("error fetching OIDC configuration: %w", err) + } + + oidc.JwksURI = oidcConfig.JWKsURI + oidc.lastOIDCConfigFetch = time.Now() } - oidc.JwksURI = oidcConfig.JWKsURI - jwks, err := oidc.GetKeys() if err != nil { return fmt.Errorf("error fetching OIDC keys: %w", err) } oidc.JWKs = jwks + oidc.lastKeyFetch = time.Now() return nil } @@ -141,8 +175,8 @@ func (oidc *Authn) GetKeys() (*keyfunc.JWKS, error) { // The keyfunc.Options struct is used to configure the HTTP client used for the request // and set a refresh interval for the keys. jwks, err := keyfunc.Get(oidc.JwksURI, keyfunc.Options{ - Client: oidc.httpClient, // Use the HTTP client configured in the Authn struct. - RefreshInterval: 48 * time.Hour, // Set the interval to refresh the keys every 48 hours. + Client: oidc.httpClient, // Use the HTTP client configured in the Authn struct. + RefreshInterval: oidc.keyRefreshInterval, // Set the interval to refresh the keys every 48 hours. }) if err != nil { return nil, fmt.Errorf("failed to fetch keys from '%s': %s", oidc.JwksURI, err) diff --git a/internal/config/config.go b/internal/config/config.go index 721436cb8..0a54c64f7 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -69,8 +69,11 @@ type ( // Oidc contains configuration for OIDC authentication. Oidc struct { - Issuer string `mapstructure:"issuer"` // OIDC issuer URL - Audience string `mapstructure:"audience"` // OIDC client ID + Issuer string `mapstructure:"issuer"` // OIDC issuer URL + Audience string `mapstructure:"audience"` // OIDC client ID + ConfigRefreshInterval time.Duration `mapstructure:"config_refresh_interval"` // Interval to refresh the OIDC configuration + KeyRefreshInterval time.Duration `mapstructure:"key_refresh_interval"` // Interval to refresh the keys + AutoFetchKeysOnTokenNotFound bool `mapstructure:"auto_fetch_keys_on_token_not_found"` // Flag to enable/disable auto fetching of keys when token is not found in the header } // Profiler contains configuration for the profiler. From 5644d7bb18138331338dcc85800ecc5cb7e699e1 Mon Sep 17 00:00:00 2001 From: ahmethakanbesel Date: Tue, 19 Mar 2024 12:49:13 +0300 Subject: [PATCH 2/4] authn/oidc: refactor `KID` backoff mechanism --- internal/authn/oidc/authn.go | 29 ++++++++++++----------------- internal/config/config.go | 10 +++++----- 2 files changed, 17 insertions(+), 22 deletions(-) diff --git a/internal/authn/oidc/authn.go b/internal/authn/oidc/authn.go index 84d917eb9..9e539f801 100644 --- a/internal/authn/oidc/authn.go +++ b/internal/authn/oidc/authn.go @@ -53,8 +53,8 @@ type Authn struct { // ConfigRefreshInterval is the interval to refresh the OIDC configuration configRefreshInterval time.Duration - // autoFetchKeysOnTokenNotFound is a flag to enable/disable auto fetching of keys when token is not found in the header - autoFetchKeysOnTokenNotFound bool + // refreshUnknownKID is a flag to refresh the JWKS when the KID is unknown + refreshUnknownKID bool } // NewOidcAuthn creates a new instance of Authn configured for OIDC authentication. @@ -69,12 +69,12 @@ func NewOidcAuthn(_ context.Context, conf config.Oidc) (*Authn, error) { // Create a new instance of Authn with the provided issuer URL and audience. // The httpClient is set to the standard net/http client wrapped with retry logic. oidc := &Authn{ - IssuerURL: conf.Issuer, - Audience: conf.Audience, - httpClient: client.StandardClient(), // Wrap retryable client as a standard http.Client - keyRefreshInterval: conf.KeyRefreshInterval, - configRefreshInterval: conf.ConfigRefreshInterval, - autoFetchKeysOnTokenNotFound: conf.AutoFetchKeysOnTokenNotFound, + IssuerURL: conf.Issuer, + Audience: conf.Audience, + httpClient: client.StandardClient(), // Wrap retryable client as a standard http.Client + keyRefreshInterval: conf.KeyRefreshInterval, + configRefreshInterval: conf.ConfigRefreshInterval, + refreshUnknownKID: conf.RefreshUnknownKID, } err := oidc.fetchKeys() @@ -104,15 +104,9 @@ func (oidc *Authn) Authenticate(requestContext context.Context) error { // If a presented token's KID is not found in the existing headers, initiate a JWKs fetch and validate the token. if _, ok := token.Header["kid"].(string); !ok { // Whem KID is absent in the header and it has been less than defaultKeyRefreshInterval since the last JWKs retrieval attempt, reject the token. - if !oidc.autoFetchKeysOnTokenNotFound && time.Since(oidc.lastKeyFetch) < oidc.keyRefreshInterval { + if !oidc.refreshUnknownKID && time.Since(oidc.lastKeyFetch) < oidc.keyRefreshInterval { return nil, errors.New(base.ErrorCode_ERROR_CODE_INVALID_BEARER_TOKEN.String()) } - - // Fetch the JWKS keys from the OIDC provider. - err := oidc.fetchKeys() - if err != nil { - return nil, err - } } // Use the JWKS from oidc to validate the JWT's signature. @@ -175,8 +169,9 @@ func (oidc *Authn) GetKeys() (*keyfunc.JWKS, error) { // The keyfunc.Options struct is used to configure the HTTP client used for the request // and set a refresh interval for the keys. jwks, err := keyfunc.Get(oidc.JwksURI, keyfunc.Options{ - Client: oidc.httpClient, // Use the HTTP client configured in the Authn struct. - RefreshInterval: oidc.keyRefreshInterval, // Set the interval to refresh the keys every 48 hours. + Client: oidc.httpClient, // Use the HTTP client configured in the Authn struct. + RefreshInterval: oidc.keyRefreshInterval, // Set the interval to refresh the keys. + RefreshUnknownKID: oidc.refreshUnknownKID, // Set the flag to refresh the JWKS when the KID is unknown. }) if err != nil { return nil, fmt.Errorf("failed to fetch keys from '%s': %s", oidc.JwksURI, err) diff --git a/internal/config/config.go b/internal/config/config.go index 0a54c64f7..2ce0f4bb1 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -69,11 +69,11 @@ type ( // Oidc contains configuration for OIDC authentication. Oidc struct { - Issuer string `mapstructure:"issuer"` // OIDC issuer URL - Audience string `mapstructure:"audience"` // OIDC client ID - ConfigRefreshInterval time.Duration `mapstructure:"config_refresh_interval"` // Interval to refresh the OIDC configuration - KeyRefreshInterval time.Duration `mapstructure:"key_refresh_interval"` // Interval to refresh the keys - AutoFetchKeysOnTokenNotFound bool `mapstructure:"auto_fetch_keys_on_token_not_found"` // Flag to enable/disable auto fetching of keys when token is not found in the header + Issuer string `mapstructure:"issuer"` // OIDC issuer URL + Audience string `mapstructure:"audience"` // OIDC client ID + ConfigRefreshInterval time.Duration `mapstructure:"config_refresh_interval"` // Interval to refresh the OIDC configuration + KeyRefreshInterval time.Duration `mapstructure:"key_refresh_interval"` // Interval to refresh the keys + RefreshUnknownKID bool `mapstructure:"refresh_unknown_kid"` // Flag to enable/disable auto fetching of keys when KID is not found in the header } // Profiler contains configuration for the profiler. From 3eb6b8bb6b201ef660f9ef8f769d1e9b05c79ffc Mon Sep 17 00:00:00 2001 From: ahmethakanbesel Date: Tue, 19 Mar 2024 14:46:32 +0300 Subject: [PATCH 3/4] authn/oidc: update a comment line --- internal/authn/oidc/authn.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/authn/oidc/authn.go b/internal/authn/oidc/authn.go index 9e539f801..fbc993f5d 100644 --- a/internal/authn/oidc/authn.go +++ b/internal/authn/oidc/authn.go @@ -103,7 +103,7 @@ func (oidc *Authn) Authenticate(requestContext context.Context) error { token, err := jwtParser.Parse(authHeader, func(token *jwt.Token) (any, error) { // If a presented token's KID is not found in the existing headers, initiate a JWKs fetch and validate the token. if _, ok := token.Header["kid"].(string); !ok { - // Whem KID is absent in the header and it has been less than defaultKeyRefreshInterval since the last JWKs retrieval attempt, reject the token. + // Whem KID is absent in the header and it has been less than the interval since the last JWKs retrieval attempt, reject the token. if !oidc.refreshUnknownKID && time.Since(oidc.lastKeyFetch) < oidc.keyRefreshInterval { return nil, errors.New(base.ErrorCode_ERROR_CODE_INVALID_BEARER_TOKEN.String()) } From 9eca68763d38456a05184ca540d616131fc1ae8f Mon Sep 17 00:00:00 2001 From: Tolga Ozen Date: Wed, 20 Mar 2024 19:28:59 +0300 Subject: [PATCH 4/4] feat(oidc): update existing implementation with lestrrat-go/jwx/jwk library --- go.mod | 22 +++- go.sum | 35 +++++- internal/authn/oidc/authn.go | 208 ++++++++++++++--------------------- internal/config/config.go | 14 ++- pkg/cmd/flags/serve.go | 18 ++- 5 files changed, 154 insertions(+), 143 deletions(-) diff --git a/go.mod b/go.mod index ba4a3f36b..95e0462d7 100644 --- a/go.mod +++ b/go.mod @@ -26,6 +26,7 @@ require ( github.com/jackc/pgtype v1.14.0 github.com/jackc/pgx/v5 v5.5.1 github.com/juju/ratelimit v1.0.2 + github.com/lestrrat-go/jwx v1.2.29 github.com/onsi/ginkgo/v2 v2.13.2 github.com/onsi/gomega v1.30.0 github.com/pkg/errors v0.9.1 @@ -37,7 +38,7 @@ require ( github.com/sony/gobreaker v0.5.0 github.com/spf13/cobra v1.8.0 github.com/spf13/viper v1.17.0 - github.com/stretchr/testify v1.8.4 + github.com/stretchr/testify v1.9.0 github.com/testcontainers/testcontainers-go v0.27.0 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1 go.opentelemetry.io/contrib/instrumentation/host v0.46.1 @@ -54,7 +55,7 @@ require ( go.opentelemetry.io/otel/sdk/metric v1.21.0 go.opentelemetry.io/otel/trace v1.21.0 golang.org/x/exp v0.0.0-20230905200255-921286631fa9 - golang.org/x/net v0.20.0 + golang.org/x/net v0.21.0 golang.org/x/sync v0.6.0 google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17 google.golang.org/grpc v1.60.1 @@ -63,7 +64,16 @@ require ( resenje.org/singleflight v0.4.1 ) -require github.com/mattn/go-runewidth v0.0.9 // indirect +require ( + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect + github.com/goccy/go-json v0.10.2 // indirect + github.com/lestrrat-go/backoff/v2 v2.0.8 // indirect + github.com/lestrrat-go/blackmagic v1.0.2 // indirect + github.com/lestrrat-go/httpcc v1.0.1 // indirect + github.com/lestrrat-go/iter v1.0.2 // indirect + github.com/lestrrat-go/option v1.0.1 // indirect + github.com/mattn/go-runewidth v0.0.9 // indirect +) require ( dario.cat/mergo v1.0.0 // indirect @@ -134,7 +144,7 @@ require ( github.com/spf13/cast v1.5.1 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/stoewer/go-strcase v1.3.0 // indirect - github.com/stretchr/objx v0.5.0 // indirect + github.com/stretchr/objx v0.5.2 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/tklauser/go-sysconf v0.3.12 // indirect github.com/tklauser/numcpus v0.6.1 // indirect @@ -143,9 +153,9 @@ require ( go.opentelemetry.io/proto/otlp v1.0.0 // indirect go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.9.0 // indirect - golang.org/x/crypto v0.19.0 // indirect + golang.org/x/crypto v0.21.0 // indirect golang.org/x/mod v0.13.0 // indirect - golang.org/x/sys v0.17.0 // indirect + golang.org/x/sys v0.18.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/tools v0.14.0 // indirect google.golang.org/genproto v0.0.0-20231030173426-d783a09b4405 // indirect diff --git a/go.sum b/go.sum index 70b8f9679..36065a9d9 100644 --- a/go.sum +++ b/go.sum @@ -95,6 +95,9 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8= github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA= @@ -141,6 +144,8 @@ github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiU github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= @@ -329,6 +334,19 @@ github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= +github.com/lestrrat-go/backoff/v2 v2.0.8 h1:oNb5E5isby2kiro9AgdHLv5N5tint1AnDVVf2E2un5A= +github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y= +github.com/lestrrat-go/blackmagic v1.0.2 h1:Cg2gVSc9h7sz9NOByczrbUvLopQmXrfFx//N+AkAr5k= +github.com/lestrrat-go/blackmagic v1.0.2/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU= +github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= +github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= +github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI= +github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4= +github.com/lestrrat-go/jwx v1.2.29 h1:QT0utmUJ4/12rmsVQrJ3u55bycPkKqGYuGT4tyRhxSQ= +github.com/lestrrat-go/jwx v1.2.29/go.mod h1:hU8k2l6WF0ncx20uQdOmik/Gjg6E3/wIRtXSNFeZuB8= +github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= +github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= +github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= @@ -454,8 +472,9 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -465,8 +484,9 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/testcontainers/testcontainers-go v0.27.0 h1:IeIrJN4twonTDuMuBNQdKZ+K97yd7VrmNGu+lDpYcDk= @@ -555,8 +575,9 @@ golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -632,8 +653,8 @@ golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= -golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -716,14 +737,16 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/internal/authn/oidc/authn.go b/internal/authn/oidc/authn.go index fbc993f5d..76ce03f31 100644 --- a/internal/authn/oidc/authn.go +++ b/internal/authn/oidc/authn.go @@ -8,12 +8,11 @@ import ( "io" "net/http" "strings" - "time" - "github.com/MicahParks/keyfunc" "github.com/golang-jwt/jwt/v4" grpcauth "github.com/grpc-ecosystem/go-grpc-middleware/auth" "github.com/hashicorp/go-retryablehttp" + "github.com/lestrrat-go/jwx/jwk" "github.com/Permify/permify/internal/config" base "github.com/Permify/permify/pkg/pb/base/v1" @@ -24,163 +23,126 @@ type Authenticator interface { Authenticate(ctx context.Context) error } -// Authn holds configuration for OIDC authentication, including issuer, audience, and key details. type Authn struct { - // IssuerURL is the URL of the OIDC issuer. + // URL of the issuer. This is typically the base URL of the identity provider. IssuerURL string - - // Audience is the intended audience of the tokens, typically the client ID. + // Audience for which the token is intended. It must match the audience in the JWT. Audience string - - // JwksURI is the URL to fetch the JSON Web Key Set (JWKS) from. + // URL of the JSON Web Key Set (JWKS). This URL hosts public keys used to verify JWT signatures. JwksURI string - - // JWKs holds the JWKS fetched from JwksURI for validating tokens. - JWKs *keyfunc.JWKS - - // httpClient is used to make HTTP requests, e.g., to fetch the JWKS. - httpClient *http.Client - - // Last time the JWKS was fetched - lastKeyFetch time.Time - - // Last time the OIDC configuration was fetched - lastOIDCConfigFetch time.Time - - // KeyRefreshInterval is the interval to refresh the keys - keyRefreshInterval time.Duration - - // ConfigRefreshInterval is the interval to refresh the OIDC configuration - configRefreshInterval time.Duration - - // refreshUnknownKID is a flag to refresh the JWKS when the KID is unknown - refreshUnknownKID bool + // Pointer to an AutoRefresh object from the JWKS library. It helps in automatically refreshing the JWKS at predefined intervals. + jwksSet *jwk.AutoRefresh + // List of valid signing methods. Specifies which signing algorithms are considered valid for the JWTs. + validMethods []string + // Pointer to a JWT parser object. This is used to parse and validate the JWT tokens. + jwtParser *jwt.Parser } -// NewOidcAuthn creates a new instance of Authn configured for OIDC authentication. -// It initializes the HTTP client with retry capabilities, sets up the OIDC issuer and audience, -// and attempts to fetch the JWKS keys from the issuer's JWKsURI. -func NewOidcAuthn(_ context.Context, conf config.Oidc) (*Authn, error) { - // Initialize a new retryable HTTP client to handle transient network errors - // by retrying failed HTTP requests. The logger is disabled for cleaner output. +// NewOidcAuthn initializes a new instance of the Authn struct with OpenID Connect (OIDC) configuration. +// It takes in a context for managing cancellation and a configuration object. It returns a pointer to an Authn instance or an error. +func NewOidcAuthn(ctx context.Context, conf config.Oidc) (*Authn, error) { + // Create a new HTTP client with retry capabilities. This client is used for making HTTP requests, particularly for fetching OIDC configuration. client := retryablehttp.NewClient() - client.Logger = nil // Disabling logging for the HTTP client + client.Logger = nil // Disable logging for the HTTP client to avoid noisy logs. + + // Fetch the OIDC configuration from the issuer's well-known configuration endpoint. + oidcConf, err := fetchOIDCConfiguration(client.StandardClient(), strings.TrimSuffix(conf.Issuer, "/")+"/.well-known/openid-configuration") + if err != nil { + // If there is an error fetching the OIDC configuration, return nil and the error. + return nil, fmt.Errorf("failed to fetch OIDC configuration: %w", err) + } + + // Set up automatic refresh of the JSON Web Key Set (JWKS) to ensure the public keys are always up-to-date. + ar := jwk.NewAutoRefresh(ctx) // Create a new AutoRefresh instance for the JWKS. + ar.Configure(oidcConf.JWKsURI, jwk.WithHTTPClient(client.StandardClient()), jwk.WithRefreshInterval(conf.RefreshInterval)) // Configure the auto-refresh parameters. - // Create a new instance of Authn with the provided issuer URL and audience. - // The httpClient is set to the standard net/http client wrapped with retry logic. + // Initialize the Authn struct with the OIDC configuration details and other relevant settings. oidc := &Authn{ - IssuerURL: conf.Issuer, - Audience: conf.Audience, - httpClient: client.StandardClient(), // Wrap retryable client as a standard http.Client - keyRefreshInterval: conf.KeyRefreshInterval, - configRefreshInterval: conf.ConfigRefreshInterval, - refreshUnknownKID: conf.RefreshUnknownKID, + IssuerURL: conf.Issuer, // URL of the token issuer. + Audience: conf.Audience, // Intended audience of the token. + JwksURI: oidcConf.JWKsURI, // URL of the JWKS endpoint. + validMethods: conf.ValidMethods, // List of acceptable signing methods for the tokens. + jwtParser: jwt.NewParser(jwt.WithValidMethods(conf.ValidMethods)), // JWT parser configured with the valid signing methods. + jwksSet: ar, // Set the JWKS auto-refresh instance. } - err := oidc.fetchKeys() + // Attempt to fetch the JWKS immediately to ensure it's available and valid. + _, err = oidc.jwksSet.Fetch(ctx, oidc.JwksURI) if err != nil { - // If fetching keys fails, return an error to prevent initialization of a non-functional Authn instance. - return nil, err + // If there is an error fetching the JWKS, return nil and the error. + return nil, fmt.Errorf("failed to fetch JWKS: %w", err) } - // Return the initialized Authn instance, ready for use in OIDC authentication. + // Return the initialized OIDC authentication object and no error. return oidc, nil } -// Authenticate validates the authentication token from the request context. +// Authenticate validates the JWT token found in the authorization header of the incoming request. +// It uses the OIDC configuration to validate the token against the issuer's public keys. func (oidc *Authn) Authenticate(requestContext context.Context) error { - // Extract the authentication header from the metadata in the request context. + // Extract the authorization header from the metadata of the incoming gRPC request. authHeader, err := grpcauth.AuthFromMD(requestContext, "Bearer") if err != nil { - // Return an error if the bearer token is missing from the authentication header. + // If the authorization header is missing or does not start with "Bearer", return an error. return errors.New(base.ErrorCode_ERROR_CODE_MISSING_BEARER_TOKEN.String()) } - // Initialize a new JWT parser with the RS256 signing method. - jwtParser := jwt.NewParser(jwt.WithValidMethods([]string{"RS256"})) + // Parse and validate the JWT token extracted from the authorization header. + parsedToken, err := oidc.jwtParser.Parse(authHeader, func(token *jwt.Token) (interface{}, error) { + // Fetch the public keys from the JWKS endpoint configured for the OIDC. + jwks, err := oidc.jwksSet.Fetch(requestContext, oidc.JwksURI) + if err != nil { + // If fetching the JWKS fails, return an error. + return nil, fmt.Errorf("failed to fetch JWKS: %w", err) + } - // Parse and validate the JWT from the authentication header. - token, err := jwtParser.Parse(authHeader, func(token *jwt.Token) (any, error) { - // If a presented token's KID is not found in the existing headers, initiate a JWKs fetch and validate the token. - if _, ok := token.Header["kid"].(string); !ok { - // Whem KID is absent in the header and it has been less than the interval since the last JWKs retrieval attempt, reject the token. - if !oidc.refreshUnknownKID && time.Since(oidc.lastKeyFetch) < oidc.keyRefreshInterval { - return nil, errors.New(base.ErrorCode_ERROR_CODE_INVALID_BEARER_TOKEN.String()) + // Retrieve the key ID from the JWT header and find the corresponding key in the JWKS. + if keyID, ok := token.Header["kid"].(string); ok { + if key, found := jwks.LookupKeyID(keyID); found { + // If the key is found, convert it to a usable format. + var k interface{} + if err := key.Raw(&k); err != nil { + return nil, fmt.Errorf("failed to get raw public key: %w", err) + } + return k, nil // Return the public key for JWT signature verification. } + // If the specified key ID is not found in the JWKS, return an error. + return nil, fmt.Errorf("kid %s not found", keyID) } - - // Use the JWKS from oidc to validate the JWT's signature. - return oidc.JWKs.Keyfunc(token) + // If the JWT does not contain a key ID, return an error. + return nil, errors.New("kid must be specified in the token header") }) if err != nil { - // Return an error if the token is invalid (e.g., expired, wrong signature). + // If token parsing or validation fails, return an error indicating the token is invalid. return errors.New(base.ErrorCode_ERROR_CODE_INVALID_BEARER_TOKEN.String()) } - // Check if the parsed token is valid. - if !token.Valid { - // Return an error if the token is not valid. + // Ensure the token is valid. + if !parsedToken.Valid { return errors.New(base.ErrorCode_ERROR_CODE_INVALID_BEARER_TOKEN.String()) } // Extract the claims from the token. - claims, ok := token.Claims.(jwt.MapClaims) + claims, ok := parsedToken.Claims.(jwt.MapClaims) if !ok { + // If the claims are in an incorrect format, return an error. return errors.New(base.ErrorCode_ERROR_CODE_INVALID_CLAIMS.String()) } + // Verify the issuer of the token matches the expected issuer. if ok := claims.VerifyIssuer(oidc.IssuerURL, true); !ok { return errors.New(base.ErrorCode_ERROR_CODE_INVALID_ISSUER.String()) } + // Verify the audience of the token matches the expected audience. if ok := claims.VerifyAudience(oidc.Audience, true); !ok { return errors.New(base.ErrorCode_ERROR_CODE_INVALID_AUDIENCE.String()) } - // If all checks pass, the token is considered valid, and the function returns nil. + // If all validations pass, return nil indicating the token is valid. return nil } -func (oidc *Authn) fetchKeys() error { - if oidc.JwksURI == "" || time.Since(oidc.lastOIDCConfigFetch) > oidc.configRefreshInterval { - oidcConfig, err := oidc.fetchOIDCConfiguration() - if err != nil { - return fmt.Errorf("error fetching OIDC configuration: %w", err) - } - - oidc.JwksURI = oidcConfig.JWKsURI - oidc.lastOIDCConfigFetch = time.Now() - } - - jwks, err := oidc.GetKeys() - if err != nil { - return fmt.Errorf("error fetching OIDC keys: %w", err) - } - - oidc.JWKs = jwks - oidc.lastKeyFetch = time.Now() - - return nil -} - -// GetKeys fetches the JSON Web Key Set (JWKS) from the configured JWKS URI. -func (oidc *Authn) GetKeys() (*keyfunc.JWKS, error) { - // Use the keyfunc package to fetch the JWKS from the JWKS URI. - // The keyfunc.Options struct is used to configure the HTTP client used for the request - // and set a refresh interval for the keys. - jwks, err := keyfunc.Get(oidc.JwksURI, keyfunc.Options{ - Client: oidc.httpClient, // Use the HTTP client configured in the Authn struct. - RefreshInterval: oidc.keyRefreshInterval, // Set the interval to refresh the keys. - RefreshUnknownKID: oidc.refreshUnknownKID, // Set the flag to refresh the JWKS when the KID is unknown. - }) - if err != nil { - return nil, fmt.Errorf("failed to fetch keys from '%s': %s", oidc.JwksURI, err) - } - - // Return the fetched JWKS and nil for the error if successful. - return jwks, nil -} - // Config holds OpenID Connect (OIDC) configuration details. type Config struct { // Issuer is the OIDC provider's unique identifier URL. @@ -189,29 +151,31 @@ type Config struct { JWKsURI string `json:"jwks_uri"` } -// Fetches OIDC configuration using the well-known endpoint. -func (oidc *Authn) fetchOIDCConfiguration() (*Config, error) { - wellKnownURL := oidc.getWellKnownURL() - body, err := oidc.doHTTPRequest(wellKnownURL) +// fetchOIDCConfiguration sends an HTTP request to the given URL to fetch the OpenID Connect (OIDC) configuration. +// It requires an HTTP client and the URL from which to fetch the configuration. +func fetchOIDCConfiguration(client *http.Client, url string) (*Config, error) { + // Send an HTTP GET request to the provided URL to fetch the OIDC configuration. + // This typically points to the well-known configuration endpoint of the OIDC provider. + body, err := doHTTPRequest(client, url) if err != nil { + // If there is an error in fetching the configuration (network error, bad response, etc.), return nil and the error. return nil, err } + // Parse the JSON response body into an OIDC Config struct. + // This involves unmarshalling the JSON into a struct that matches the expected fields of the OIDC configuration. oidcConfig, err := parseOIDCConfiguration(body) if err != nil { + // If there is an error in parsing the JSON response (missing fields, incorrect format, etc.), return nil and the error. return nil, err } + // Return the parsed OIDC configuration and nil as the error (indicating success). return oidcConfig, nil } -// Constructs the well-known URL for fetching OIDC configuration. -func (oidc *Authn) getWellKnownURL() string { - return strings.TrimSuffix(oidc.IssuerURL, "/") + "/.well-known/openid-configuration" -} - // doHTTPRequest makes an HTTP GET request to the specified URL and returns the response body. -func (oidc *Authn) doHTTPRequest(url string) ([]byte, error) { +func doHTTPRequest(client *http.Client, url string) ([]byte, error) { // Create a new HTTP GET request. req, err := http.NewRequest("GET", url, nil) if err != nil { @@ -219,7 +183,7 @@ func (oidc *Authn) doHTTPRequest(url string) ([]byte, error) { } // Send the request using the configured HTTP client. - res, err := oidc.httpClient.Do(req) + res, err := client.Do(req) if err != nil { return nil, fmt.Errorf("failed to execute HTTP request for OIDC configuration: %s", err) } @@ -261,7 +225,3 @@ func parseOIDCConfiguration(body []byte) (*Config, error) { // Return the successfully parsed configuration. return &oidcConfig, nil } - -func (oidc *Authn) Close() { - oidc.JWKs.EndBackground() -} diff --git a/internal/config/config.go b/internal/config/config.go index 2ce0f4bb1..8d4411e4e 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -69,11 +69,10 @@ type ( // Oidc contains configuration for OIDC authentication. Oidc struct { - Issuer string `mapstructure:"issuer"` // OIDC issuer URL - Audience string `mapstructure:"audience"` // OIDC client ID - ConfigRefreshInterval time.Duration `mapstructure:"config_refresh_interval"` // Interval to refresh the OIDC configuration - KeyRefreshInterval time.Duration `mapstructure:"key_refresh_interval"` // Interval to refresh the keys - RefreshUnknownKID bool `mapstructure:"refresh_unknown_kid"` // Flag to enable/disable auto fetching of keys when KID is not found in the header + Issuer string `mapstructure:"issuer"` // OIDC issuer URL + Audience string `mapstructure:"audience"` // OIDC client ID + RefreshInterval time.Duration `mapstructure:"refresh_interval"` + ValidMethods []string `mapstructure:"valid_methods"` } // Profiler contains configuration for the profiler. @@ -303,7 +302,10 @@ func DefaultConfig() *Config { Authn: Authn{ Enabled: false, Preshared: Preshared{}, - Oidc: Oidc{}, + Oidc: Oidc{ + RefreshInterval: time.Minute * 15, + ValidMethods: []string{"RS256", "HS256"}, + }, }, Database: Database{ Engine: "memory", diff --git a/pkg/cmd/flags/serve.go b/pkg/cmd/flags/serve.go index 3dfd506d5..2e255566c 100644 --- a/pkg/cmd/flags/serve.go +++ b/pkg/cmd/flags/serve.go @@ -195,7 +195,7 @@ func RegisterServeFlags(cmd *cobra.Command) { panic(err) } - flags.String("authn-oidc-audience", conf.Authn.Oidc.Audience, "") + flags.String("authn-oidc-audience", conf.Authn.Oidc.Audience, "intended audience of the OpenID Connect token") if err = viper.BindPFlag("authn.oidc.audience", flags.Lookup("authn-oidc-audience")); err != nil { panic(err) } @@ -203,6 +203,22 @@ func RegisterServeFlags(cmd *cobra.Command) { panic(err) } + flags.Duration("authn-oidc-refresh-interval", conf.Authn.Oidc.RefreshInterval, "refresh interval for the OpenID Connect configuration") + if err = viper.BindPFlag("authn.oidc.refresh_interval", flags.Lookup("authn-oidc-refresh-interval")); err != nil { + panic(err) + } + if err = viper.BindEnv("authn.oidc.refresh_interval", "PERMIFY_AUTHN_OIDC_REFRESH_INTERVAL"); err != nil { + panic(err) + } + + flags.StringSlice("authn-oidc-valid-methods", conf.Authn.Oidc.ValidMethods, "list of valid JWT signing methods for OpenID Connect") + if err = viper.BindPFlag("authn.oidc.valid_methods", flags.Lookup("authn-oidc-valid-methods")); err != nil { + panic(err) + } + if err = viper.BindEnv("authn.oidc.valid_methods", "PERMIFY_AUTHN_OIDC_VALID_METHODS"); err != nil { + panic(err) + } + // TRACER flags.Bool("tracer-enabled", conf.Tracer.Enabled, "switch option for tracing") if err = viper.BindPFlag("tracer.enabled", flags.Lookup("tracer-enabled")); err != nil {