Use just-in-time HMAC signing key fetching in our Fosite config
This pattern is similar to what we did in
58237d0e7d
.
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
This commit is contained in:
parent
a3285fc187
commit
9460b08873
@ -124,10 +124,10 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
|
|
||||||
// Configure fosite the same way that the production code would, using NullStorage to turn off storage.
|
// Configure fosite the same way that the production code would, using NullStorage to turn off storage.
|
||||||
oauthStore := oidc.NullStorage{}
|
oauthStore := oidc.NullStorage{}
|
||||||
hmacSecret := []byte("some secret - must have at least 32 bytes")
|
hmacSecretFunc := func() []byte { return []byte("some secret - must have at least 32 bytes") }
|
||||||
require.GreaterOrEqual(t, len(hmacSecret), 32, "fosite requires that hmac secrets have at least 32 bytes")
|
require.GreaterOrEqual(t, len(hmacSecretFunc()), 32, "fosite requires that hmac secrets have at least 32 bytes")
|
||||||
jwksProviderIsUnused := jwks.NewDynamicJWKSProvider()
|
jwksProviderIsUnused := jwks.NewDynamicJWKSProvider()
|
||||||
oauthHelper := oidc.FositeOauth2Helper(oauthStore, downstreamIssuer, hmacSecret, jwksProviderIsUnused, oidc.DefaultOIDCTimeoutsConfiguration())
|
oauthHelper := oidc.FositeOauth2Helper(oauthStore, downstreamIssuer, hmacSecretFunc, jwksProviderIsUnused, oidc.DefaultOIDCTimeoutsConfiguration())
|
||||||
|
|
||||||
happyCSRF := "test-csrf"
|
happyCSRF := "test-csrf"
|
||||||
happyPKCE := "test-pkce"
|
happyPKCE := "test-pkce"
|
||||||
|
@ -458,10 +458,10 @@ func TestCallbackEndpoint(t *testing.T) {
|
|||||||
// Configure fosite the same way that the production code would.
|
// Configure fosite the same way that the production code would.
|
||||||
// Inject this into our test subject at the last second so we get a fresh storage for every test.
|
// Inject this into our test subject at the last second so we get a fresh storage for every test.
|
||||||
oauthStore := oidc.NewKubeStorage(secrets)
|
oauthStore := oidc.NewKubeStorage(secrets)
|
||||||
hmacSecret := []byte("some secret - must have at least 32 bytes")
|
hmacSecretFunc := func() []byte { return []byte("some secret - must have at least 32 bytes") }
|
||||||
require.GreaterOrEqual(t, len(hmacSecret), 32, "fosite requires that hmac secrets have at least 32 bytes")
|
require.GreaterOrEqual(t, len(hmacSecretFunc()), 32, "fosite requires that hmac secrets have at least 32 bytes")
|
||||||
jwksProviderIsUnused := jwks.NewDynamicJWKSProvider()
|
jwksProviderIsUnused := jwks.NewDynamicJWKSProvider()
|
||||||
oauthHelper := oidc.FositeOauth2Helper(oauthStore, downstreamIssuer, hmacSecret, jwksProviderIsUnused, oidc.DefaultOIDCTimeoutsConfiguration())
|
oauthHelper := oidc.FositeOauth2Helper(oauthStore, downstreamIssuer, hmacSecretFunc, jwksProviderIsUnused, oidc.DefaultOIDCTimeoutsConfiguration())
|
||||||
|
|
||||||
idpListGetter := oidctestutil.NewIDPListGetter(&test.idp)
|
idpListGetter := oidctestutil.NewIDPListGetter(&test.idp)
|
||||||
subject := NewHandler(idpListGetter, oauthHelper, happyStateCodec, happyCookieCodec, happyUpstreamRedirectURI)
|
subject := NewHandler(idpListGetter, oauthHelper, happyStateCodec, happyCookieCodec, happyUpstreamRedirectURI)
|
||||||
|
98
internal/oidc/dynamic_oauth2_hmac_strategy.go
Normal file
98
internal/oidc/dynamic_oauth2_hmac_strategy.go
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package oidc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/ory/fosite"
|
||||||
|
"github.com/ory/fosite/compose"
|
||||||
|
"github.com/ory/fosite/handler/oauth2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// dynamicOauth2HMACStrategy is an oauth2.CoreStrategy that can dynamically load an HMAC key to sign
|
||||||
|
// stuff (access tokens, refresh tokens, and auth codes). We want this dynamic capability since our
|
||||||
|
// controllers for loading OIDCProvider's and signing keys run in parallel, and thus the signing key
|
||||||
|
// might not be ready when an OIDCProvider is otherwise ready.
|
||||||
|
//
|
||||||
|
// If we ever update OIDCProvider's to hold their signing key, we might not need this type, since we
|
||||||
|
// could have an invariant that routes to an OIDCProvider's endpoints are only wired up if an
|
||||||
|
// OIDCProvider has a valid signing key.
|
||||||
|
type dynamicOauth2HMACStrategy struct {
|
||||||
|
fositeConfig *compose.Config
|
||||||
|
keyFunc func() []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ oauth2.CoreStrategy = &dynamicOauth2HMACStrategy{}
|
||||||
|
|
||||||
|
func newDynamicOauth2HMACStrategy(
|
||||||
|
fositeConfig *compose.Config,
|
||||||
|
keyFunc func() []byte,
|
||||||
|
) *dynamicOauth2HMACStrategy {
|
||||||
|
return &dynamicOauth2HMACStrategy{
|
||||||
|
fositeConfig: fositeConfig,
|
||||||
|
keyFunc: keyFunc,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *dynamicOauth2HMACStrategy) AccessTokenSignature(token string) string {
|
||||||
|
return s.delegate().AccessTokenSignature(token)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *dynamicOauth2HMACStrategy) GenerateAccessToken(
|
||||||
|
ctx context.Context,
|
||||||
|
requester fosite.Requester,
|
||||||
|
) (token string, signature string, err error) {
|
||||||
|
return s.delegate().GenerateAccessToken(ctx, requester)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *dynamicOauth2HMACStrategy) ValidateAccessToken(
|
||||||
|
ctx context.Context,
|
||||||
|
requester fosite.Requester,
|
||||||
|
token string,
|
||||||
|
) (err error) {
|
||||||
|
return s.delegate().ValidateAccessToken(ctx, requester, token)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *dynamicOauth2HMACStrategy) RefreshTokenSignature(token string) string {
|
||||||
|
return s.delegate().RefreshTokenSignature(token)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *dynamicOauth2HMACStrategy) GenerateRefreshToken(
|
||||||
|
ctx context.Context,
|
||||||
|
requester fosite.Requester,
|
||||||
|
) (token string, signature string, err error) {
|
||||||
|
return s.delegate().GenerateRefreshToken(ctx, requester)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *dynamicOauth2HMACStrategy) ValidateRefreshToken(
|
||||||
|
ctx context.Context,
|
||||||
|
requester fosite.Requester,
|
||||||
|
token string,
|
||||||
|
) (err error) {
|
||||||
|
return s.delegate().ValidateRefreshToken(ctx, requester, token)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *dynamicOauth2HMACStrategy) AuthorizeCodeSignature(token string) string {
|
||||||
|
return s.delegate().AuthorizeCodeSignature(token)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *dynamicOauth2HMACStrategy) GenerateAuthorizeCode(
|
||||||
|
ctx context.Context,
|
||||||
|
requester fosite.Requester,
|
||||||
|
) (token string, signature string, err error) {
|
||||||
|
return s.delegate().GenerateAuthorizeCode(ctx, requester)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *dynamicOauth2HMACStrategy) ValidateAuthorizeCode(
|
||||||
|
ctx context.Context,
|
||||||
|
requester fosite.Requester,
|
||||||
|
token string,
|
||||||
|
) (err error) {
|
||||||
|
return s.delegate().ValidateAuthorizeCode(ctx, requester, token)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *dynamicOauth2HMACStrategy) delegate() *oauth2.HMACSHAStrategy {
|
||||||
|
return compose.NewOAuth2HMACStrategy(s.fositeConfig, s.keyFunc(), nil)
|
||||||
|
}
|
@ -187,7 +187,7 @@ func DefaultOIDCTimeoutsConfiguration() TimeoutsConfiguration {
|
|||||||
func FositeOauth2Helper(
|
func FositeOauth2Helper(
|
||||||
oauthStore interface{},
|
oauthStore interface{},
|
||||||
issuer string,
|
issuer string,
|
||||||
hmacSecretOfLengthAtLeast32 []byte,
|
hmacSecretOfLengthAtLeast32Func func() []byte,
|
||||||
jwksProvider jwks.DynamicJWKSProvider,
|
jwksProvider jwks.DynamicJWKSProvider,
|
||||||
timeoutsConfiguration TimeoutsConfiguration,
|
timeoutsConfiguration TimeoutsConfiguration,
|
||||||
) fosite.OAuth2Provider {
|
) fosite.OAuth2Provider {
|
||||||
@ -212,7 +212,7 @@ func FositeOauth2Helper(
|
|||||||
oauthStore,
|
oauthStore,
|
||||||
&compose.CommonStrategy{
|
&compose.CommonStrategy{
|
||||||
// Note that Fosite requires the HMAC secret to be at least 32 bytes.
|
// Note that Fosite requires the HMAC secret to be at least 32 bytes.
|
||||||
CoreStrategy: compose.NewOAuth2HMACStrategy(oauthConfig, hmacSecretOfLengthAtLeast32, nil),
|
CoreStrategy: newDynamicOauth2HMACStrategy(oauthConfig, hmacSecretOfLengthAtLeast32Func),
|
||||||
OpenIDConnectTokenStrategy: newDynamicOpenIDConnectECDSAStrategy(oauthConfig, jwksProvider),
|
OpenIDConnectTokenStrategy: newDynamicOpenIDConnectECDSAStrategy(oauthConfig, jwksProvider),
|
||||||
},
|
},
|
||||||
nil, // hasher, defaults to using BCrypt when nil. Used for hashing client secrets.
|
nil, // hasher, defaults to using BCrypt when nil. Used for hashing client secrets.
|
||||||
|
@ -92,13 +92,14 @@ func (m *Manager) SetProviders(oidcProviders ...*provider.OIDCProvider) {
|
|||||||
|
|
||||||
issuer := incomingProvider.Issuer()
|
issuer := incomingProvider.Issuer()
|
||||||
issuerHostWithPath := strings.ToLower(incomingProvider.IssuerHost()) + "/" + incomingProvider.IssuerPath()
|
issuerHostWithPath := strings.ToLower(incomingProvider.IssuerHost()) + "/" + incomingProvider.IssuerPath()
|
||||||
|
oidcTimeouts := oidc.DefaultOIDCTimeoutsConfiguration()
|
||||||
|
|
||||||
// Use NullStorage for the authorize endpoint because we do not actually want to store anything until
|
// Use NullStorage for the authorize endpoint because we do not actually want to store anything until
|
||||||
// the upstream callback endpoint is called later.
|
// the upstream callback endpoint is called later.
|
||||||
oauthHelperWithNullStorage := oidc.FositeOauth2Helper(oidc.NullStorage{}, issuer, providerCache.GetTokenHMACKey(), nil, oidc.DefaultOIDCTimeoutsConfiguration())
|
oauthHelperWithNullStorage := oidc.FositeOauth2Helper(oidc.NullStorage{}, issuer, providerCache.GetTokenHMACKey, nil, oidcTimeouts)
|
||||||
|
|
||||||
// For all the other endpoints, make another oauth helper with exactly the same settings except use real storage.
|
// For all the other endpoints, make another oauth helper with exactly the same settings except use real storage.
|
||||||
oauthHelperWithKubeStorage := oidc.FositeOauth2Helper(oidc.NewKubeStorage(m.secretsClient), issuer, providerCache.GetTokenHMACKey(), m.dynamicJWKSProvider, oidc.DefaultOIDCTimeoutsConfiguration())
|
oauthHelperWithKubeStorage := oidc.FositeOauth2Helper(oidc.NewKubeStorage(m.secretsClient), issuer, providerCache.GetTokenHMACKey, m.dynamicJWKSProvider, oidcTimeouts)
|
||||||
|
|
||||||
var upstreamStateEncoder = dynamiccodec.New(providerCache.GetStateEncoderHashKey, providerCache.GetStateEncoderBlockKey)
|
var upstreamStateEncoder = dynamiccodec.New(providerCache.GetStateEncoderHashKey, providerCache.GetStateEncoderBlockKey)
|
||||||
|
|
||||||
|
@ -69,6 +69,10 @@ var (
|
|||||||
goodAuthTime = time.Date(1, 2, 3, 4, 5, 6, 7, time.UTC)
|
goodAuthTime = time.Date(1, 2, 3, 4, 5, 6, 7, time.UTC)
|
||||||
goodRequestedAtTime = time.Date(7, 6, 5, 4, 3, 2, 1, time.UTC)
|
goodRequestedAtTime = time.Date(7, 6, 5, 4, 3, 2, 1, time.UTC)
|
||||||
|
|
||||||
|
hmacSecretFunc = func() []byte {
|
||||||
|
return []byte(hmacSecret)
|
||||||
|
}
|
||||||
|
|
||||||
fositeInvalidMethodErrorBody = func(actual string) string {
|
fositeInvalidMethodErrorBody = func(actual string) string {
|
||||||
return here.Docf(`
|
return here.Docf(`
|
||||||
{
|
{
|
||||||
@ -1346,7 +1350,7 @@ func makeHappyOauthHelper(
|
|||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
jwtSigningKey, jwkProvider := generateJWTSigningKeyAndJWKSProvider(t, goodIssuer)
|
jwtSigningKey, jwkProvider := generateJWTSigningKeyAndJWKSProvider(t, goodIssuer)
|
||||||
oauthHelper := oidc.FositeOauth2Helper(store, goodIssuer, []byte(hmacSecret), jwkProvider, oidc.DefaultOIDCTimeoutsConfiguration())
|
oauthHelper := oidc.FositeOauth2Helper(store, goodIssuer, hmacSecretFunc, jwkProvider, oidc.DefaultOIDCTimeoutsConfiguration())
|
||||||
authResponder := simulateAuthEndpointHavingAlreadyRun(t, authRequest, oauthHelper)
|
authResponder := simulateAuthEndpointHavingAlreadyRun(t, authRequest, oauthHelper)
|
||||||
return oauthHelper, authResponder.GetCode(), jwtSigningKey
|
return oauthHelper, authResponder.GetCode(), jwtSigningKey
|
||||||
}
|
}
|
||||||
@ -1378,7 +1382,7 @@ func makeOauthHelperWithJWTKeyThatWorksOnlyOnce(
|
|||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
jwtSigningKey, jwkProvider := generateJWTSigningKeyAndJWKSProvider(t, goodIssuer)
|
jwtSigningKey, jwkProvider := generateJWTSigningKeyAndJWKSProvider(t, goodIssuer)
|
||||||
oauthHelper := oidc.FositeOauth2Helper(store, goodIssuer, []byte(hmacSecret), &singleUseJWKProvider{DynamicJWKSProvider: jwkProvider}, oidc.DefaultOIDCTimeoutsConfiguration())
|
oauthHelper := oidc.FositeOauth2Helper(store, goodIssuer, hmacSecretFunc, &singleUseJWKProvider{DynamicJWKSProvider: jwkProvider}, oidc.DefaultOIDCTimeoutsConfiguration())
|
||||||
authResponder := simulateAuthEndpointHavingAlreadyRun(t, authRequest, oauthHelper)
|
authResponder := simulateAuthEndpointHavingAlreadyRun(t, authRequest, oauthHelper)
|
||||||
return oauthHelper, authResponder.GetCode(), jwtSigningKey
|
return oauthHelper, authResponder.GetCode(), jwtSigningKey
|
||||||
}
|
}
|
||||||
@ -1397,7 +1401,7 @@ func makeOauthHelperWithNilPrivateJWTSigningKey(
|
|||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
jwkProvider := jwks.NewDynamicJWKSProvider() // empty provider which contains no signing key for this issuer
|
jwkProvider := jwks.NewDynamicJWKSProvider() // empty provider which contains no signing key for this issuer
|
||||||
oauthHelper := oidc.FositeOauth2Helper(store, goodIssuer, []byte(hmacSecret), jwkProvider, oidc.DefaultOIDCTimeoutsConfiguration())
|
oauthHelper := oidc.FositeOauth2Helper(store, goodIssuer, hmacSecretFunc, jwkProvider, oidc.DefaultOIDCTimeoutsConfiguration())
|
||||||
authResponder := simulateAuthEndpointHavingAlreadyRun(t, authRequest, oauthHelper)
|
authResponder := simulateAuthEndpointHavingAlreadyRun(t, authRequest, oauthHelper)
|
||||||
return oauthHelper, authResponder.GetCode(), nil
|
return oauthHelper, authResponder.GetCode(), nil
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user