Merge pull request #275 from vmware-tanzu/fosite-storage-gc-prefactor
Fosite storage garbage collection prefactor
This commit is contained in:
commit
c001bb876e
@ -127,7 +127,7 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
|||||||
hmacSecret := []byte("some secret - must have at least 32 bytes")
|
hmacSecret := []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(hmacSecret), 32, "fosite requires that hmac secrets have at least 32 bytes")
|
||||||
jwksProviderIsUnused := jwks.NewDynamicJWKSProvider()
|
jwksProviderIsUnused := jwks.NewDynamicJWKSProvider()
|
||||||
oauthHelper := oidc.FositeOauth2Helper(oauthStore, downstreamIssuer, hmacSecret, jwksProviderIsUnused)
|
oauthHelper := oidc.FositeOauth2Helper(oauthStore, downstreamIssuer, hmacSecret, jwksProviderIsUnused, oidc.DefaultOIDCTimeoutsConfiguration())
|
||||||
|
|
||||||
happyCSRF := "test-csrf"
|
happyCSRF := "test-csrf"
|
||||||
happyPKCE := "test-pkce"
|
happyPKCE := "test-pkce"
|
||||||
|
@ -61,6 +61,7 @@ const (
|
|||||||
downstreamPKCEChallenge = "some-challenge"
|
downstreamPKCEChallenge = "some-challenge"
|
||||||
downstreamPKCEChallengeMethod = "S256"
|
downstreamPKCEChallengeMethod = "S256"
|
||||||
|
|
||||||
|
authCodeExpirationSeconds = 10 * 60 // Current, we set our auth code expiration to 10 minutes
|
||||||
timeComparisonFudgeFactor = time.Second * 15
|
timeComparisonFudgeFactor = time.Second * 15
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -460,7 +461,7 @@ func TestCallbackEndpoint(t *testing.T) {
|
|||||||
hmacSecret := []byte("some secret - must have at least 32 bytes")
|
hmacSecret := []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(hmacSecret), 32, "fosite requires that hmac secrets have at least 32 bytes")
|
||||||
jwksProviderIsUnused := jwks.NewDynamicJWKSProvider()
|
jwksProviderIsUnused := jwks.NewDynamicJWKSProvider()
|
||||||
oauthHelper := oidc.FositeOauth2Helper(oauthStore, downstreamIssuer, hmacSecret, jwksProviderIsUnused)
|
oauthHelper := oidc.FositeOauth2Helper(oauthStore, downstreamIssuer, hmacSecret, 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)
|
||||||
@ -768,7 +769,7 @@ func validateAuthcodeStorage(
|
|||||||
require.Empty(t, storedSessionFromAuthcode.Headers)
|
require.Empty(t, storedSessionFromAuthcode.Headers)
|
||||||
|
|
||||||
// The authcode that we are issuing should be good for the length of time that we declare in the fosite config.
|
// The authcode that we are issuing should be good for the length of time that we declare in the fosite config.
|
||||||
testutil.RequireTimeInDelta(t, time.Now().Add(time.Minute*3), storedSessionFromAuthcode.ExpiresAt[fosite.AuthorizeCode], timeComparisonFudgeFactor)
|
testutil.RequireTimeInDelta(t, time.Now().Add(authCodeExpirationSeconds*time.Second), storedSessionFromAuthcode.ExpiresAt[fosite.AuthorizeCode], timeComparisonFudgeFactor)
|
||||||
require.Len(t, storedSessionFromAuthcode.ExpiresAt, 1)
|
require.Len(t, storedSessionFromAuthcode.ExpiresAt, 1)
|
||||||
|
|
||||||
// Now confirm the ID token claims.
|
// Now confirm the ID token claims.
|
||||||
|
@ -91,30 +91,120 @@ func PinnipedCLIOIDCClient() *fosite.DefaultOpenIDConnectClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TimeoutsConfiguration struct {
|
||||||
|
// The length of time that our state param that we encrypt and pass to the upstream OIDC IDP should be considered
|
||||||
|
// valid. If a state param generated by the authorize endpoint is sent to the callback endpoint after this much
|
||||||
|
// time has passed, then the callback endpoint should reject it. This allows us to set a limit on how long
|
||||||
|
// the end user has to finish their login with the upstream IDP, including the time that it takes to fumble
|
||||||
|
// with password manager and two-factor authenticator apps, and also accounting for taking a coffee break while
|
||||||
|
// the browser is sitting at the upstream IDP's login page.
|
||||||
|
UpstreamStateParamLifespan time.Duration
|
||||||
|
|
||||||
|
// How long an authcode issued by the callback endpoint is valid. This determines how much time the end user
|
||||||
|
// has to come back to exchange the authcode for tokens at the token endpoint.
|
||||||
|
AuthorizeCodeLifespan time.Duration
|
||||||
|
|
||||||
|
// The lifetime of an downstream access token issued by the token endpoint. Access tokens should generally
|
||||||
|
// be fairly short-lived.
|
||||||
|
AccessTokenLifespan time.Duration
|
||||||
|
|
||||||
|
// The lifetime of an downstream ID token issued by the token endpoint. This should generally be the same
|
||||||
|
// as the AccessTokenLifespan, or longer if it would be useful for the user's proof of identity to be valid
|
||||||
|
// for longer than their proof of authorization.
|
||||||
|
IDTokenLifespan time.Duration
|
||||||
|
|
||||||
|
// The lifetime of an downstream refresh token issued by the token endpoint. This should generally be
|
||||||
|
// significantly longer than the access token lifetime, so it can be used to refresh the access token
|
||||||
|
// multiple times. Once the refresh token expires, the user's session is over and they will need
|
||||||
|
// to start a new authorization request, which will require them to log in again with the upstream IDP
|
||||||
|
// in their web browser.
|
||||||
|
RefreshTokenLifespan time.Duration
|
||||||
|
|
||||||
|
// AuthorizationCodeSessionStorageLifetime is the length of time after which an authcode is allowed to be garbage
|
||||||
|
// collected from storage. Authcodes are kept in storage after they are redeemed to allow the system to mark the
|
||||||
|
// authcode as already used, so it can reject any future uses of the same authcode with special case handling which
|
||||||
|
// include revoking the access and refresh tokens associated with the session. Therefore, this should be
|
||||||
|
// significantly longer than the AuthorizeCodeLifespan, and there is probably no reason to make it longer than
|
||||||
|
// the sum of the AuthorizeCodeLifespan and the RefreshTokenLifespan.
|
||||||
|
AuthorizationCodeSessionStorageLifetime time.Duration
|
||||||
|
|
||||||
|
// PKCESessionStorageLifetime is the length of time after which PKCE data is allowed to be garbage collected from
|
||||||
|
// storage. PKCE sessions are closely related to authorization code sessions. After the authcode is successfully
|
||||||
|
// redeemed, the PKCE session is explicitly deleted. After the authcode expires, the PKCE session is no longer needed,
|
||||||
|
// but it is not explicitly deleted. Therefore, this can be just slightly longer than the AuthorizeCodeLifespan. We'll
|
||||||
|
// avoid making it exactly the same as AuthorizeCodeLifespan to avoid any chance of the garbage collector deleting it
|
||||||
|
// while it is being used.
|
||||||
|
PKCESessionStorageLifetime time.Duration
|
||||||
|
|
||||||
|
// OIDCSessionStorageLifetime is the length of time after which the OIDC session data related to an authcode
|
||||||
|
// is allowed to be garbage collected from storage. Due to a bug in an underlying library, these are not explicitly
|
||||||
|
// deleted. Similar to the PKCE session, they are not needed anymore after the corresponding authcode has expired.
|
||||||
|
// Therefore, this can be just slightly longer than the AuthorizeCodeLifespan. We'll avoid making it exactly the same
|
||||||
|
// as AuthorizeCodeLifespan to avoid any chance of the garbage collector deleting it while it is being used.
|
||||||
|
OIDCSessionStorageLifetime time.Duration
|
||||||
|
|
||||||
|
// AccessTokenSessionStorageLifetime is the length of time after which an access token's session data is allowed
|
||||||
|
// to be garbage collected from storage. These must exist in storage for as long as the refresh token is valid.
|
||||||
|
// Therefore, this can be just slightly longer than the AccessTokenLifespan. Access tokens are handed back to
|
||||||
|
// the token endpoint for the token exchange use case. During a token exchange, if the access token is expired
|
||||||
|
// and still exists in storage, then the endpoint will be able to give a slightly more specific error message,
|
||||||
|
// rather than a more generic error that is returned when the token does not exist. If this is desirable, then
|
||||||
|
// the AccessTokenSessionStorageLifetime can be made to be significantly larger than AccessTokenLifespan, at the
|
||||||
|
// cost of slower cleanup.
|
||||||
|
AccessTokenSessionStorageLifetime time.Duration
|
||||||
|
|
||||||
|
// RefreshTokenSessionStorageLifetime is the length of time after which a refresh token's session data is allowed
|
||||||
|
// to be garbage collected from storage. These must exist in storage for as long as the refresh token is valid.
|
||||||
|
// Therefore, this can be just slightly longer than the RefreshTokenLifespan. We'll avoid making it exactly the same
|
||||||
|
// as RefreshTokenLifespan to avoid any chance of the garbage collector deleting it while it is being used.
|
||||||
|
// If an expired token is still stored when the user tries to refresh it, then they will get a more specific
|
||||||
|
// error message telling them that the token is expired, rather than a more generic error that is returned
|
||||||
|
// when the token does not exist. If this is desirable, then the RefreshTokenSessionStorageLifetime can be made
|
||||||
|
// to be significantly larger than RefreshTokenLifespan, at the cost of slower cleanup.
|
||||||
|
RefreshTokenSessionStorageLifetime time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the defaults for the Supervisor server.
|
||||||
|
func DefaultOIDCTimeoutsConfiguration() TimeoutsConfiguration {
|
||||||
|
accessTokenLifespan := 15 * time.Minute
|
||||||
|
authorizationCodeLifespan := 10 * time.Minute
|
||||||
|
refreshTokenLifespan := 9 * time.Hour
|
||||||
|
|
||||||
|
return TimeoutsConfiguration{
|
||||||
|
UpstreamStateParamLifespan: 90 * time.Minute,
|
||||||
|
AuthorizeCodeLifespan: authorizationCodeLifespan,
|
||||||
|
AccessTokenLifespan: accessTokenLifespan,
|
||||||
|
IDTokenLifespan: accessTokenLifespan,
|
||||||
|
RefreshTokenLifespan: refreshTokenLifespan,
|
||||||
|
AuthorizationCodeSessionStorageLifetime: authorizationCodeLifespan + refreshTokenLifespan,
|
||||||
|
PKCESessionStorageLifetime: authorizationCodeLifespan + (1 * time.Minute),
|
||||||
|
OIDCSessionStorageLifetime: authorizationCodeLifespan + (1 * time.Minute),
|
||||||
|
AccessTokenSessionStorageLifetime: accessTokenLifespan + (1 * time.Minute),
|
||||||
|
RefreshTokenSessionStorageLifetime: refreshTokenLifespan + accessTokenLifespan,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func FositeOauth2Helper(
|
func FositeOauth2Helper(
|
||||||
oauthStore interface{},
|
oauthStore interface{},
|
||||||
issuer string,
|
issuer string,
|
||||||
hmacSecretOfLengthAtLeast32 []byte,
|
hmacSecretOfLengthAtLeast32 []byte,
|
||||||
jwksProvider jwks.DynamicJWKSProvider,
|
jwksProvider jwks.DynamicJWKSProvider,
|
||||||
|
timeoutsConfiguration TimeoutsConfiguration,
|
||||||
) fosite.OAuth2Provider {
|
) fosite.OAuth2Provider {
|
||||||
oauthConfig := &compose.Config{
|
oauthConfig := &compose.Config{
|
||||||
AuthorizeCodeLifespan: 3 * time.Minute, // seems more than long enough to exchange a code
|
|
||||||
|
|
||||||
IDTokenLifespan: 5 * time.Minute, // match clientCertificateTTL since it has similar properties to this token
|
|
||||||
AccessTokenLifespan: 5 * time.Minute, // match clientCertificateTTL since it has similar properties to this token
|
|
||||||
|
|
||||||
RefreshTokenLifespan: 16 * time.Hour, // long enough for a single workday
|
|
||||||
|
|
||||||
IDTokenIssuer: issuer,
|
IDTokenIssuer: issuer,
|
||||||
|
|
||||||
ScopeStrategy: fosite.ExactScopeStrategy, // be careful and only support exact string matching for scopes
|
AuthorizeCodeLifespan: timeoutsConfiguration.AuthorizeCodeLifespan,
|
||||||
AudienceMatchingStrategy: nil, // I believe the default is fine
|
IDTokenLifespan: timeoutsConfiguration.IDTokenLifespan,
|
||||||
EnforcePKCE: true, // follow current set of best practices and always require PKCE
|
AccessTokenLifespan: timeoutsConfiguration.AccessTokenLifespan,
|
||||||
|
RefreshTokenLifespan: timeoutsConfiguration.RefreshTokenLifespan,
|
||||||
|
|
||||||
|
ScopeStrategy: fosite.ExactScopeStrategy,
|
||||||
|
AudienceMatchingStrategy: nil,
|
||||||
|
EnforcePKCE: true,
|
||||||
AllowedPromptValues: []string{"none"}, // TODO unclear what we should set here
|
AllowedPromptValues: []string{"none"}, // TODO unclear what we should set here
|
||||||
|
|
||||||
RefreshTokenScopes: []string{coreosoidc.ScopeOfflineAccess}, // as per https://openid.net/specs/openid-connect-core-1_0.html#OfflineAccess
|
RefreshTokenScopes: []string{coreosoidc.ScopeOfflineAccess}, // as per https://openid.net/specs/openid-connect-core-1_0.html#OfflineAccess
|
||||||
|
MinParameterEntropy: 32, // TODO is 256 bits too high?
|
||||||
MinParameterEntropy: 32, // 256 bits seems about right
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return compose.Compose(
|
return compose.Compose(
|
||||||
|
@ -79,10 +79,10 @@ func (m *Manager) SetProviders(oidcProviders ...*provider.OIDCProvider) {
|
|||||||
|
|
||||||
// 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, fositeHMACSecretForThisProvider, nil)
|
oauthHelperWithNullStorage := oidc.FositeOauth2Helper(oidc.NullStorage{}, issuer, fositeHMACSecretForThisProvider, nil, oidc.DefaultOIDCTimeoutsConfiguration())
|
||||||
|
|
||||||
// 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, fositeHMACSecretForThisProvider, m.dynamicJWKSProvider)
|
oauthHelperWithKubeStorage := oidc.FositeOauth2Helper(oidc.NewKubeStorage(m.secretsClient), issuer, fositeHMACSecretForThisProvider, m.dynamicJWKSProvider, oidc.DefaultOIDCTimeoutsConfiguration())
|
||||||
|
|
||||||
// TODO use different codecs for the state and the cookie, because:
|
// TODO use different codecs for the state and the cookie, because:
|
||||||
// 1. we would like to state to have an embedded expiration date while the cookie does not need that
|
// 1. we would like to state to have an embedded expiration date while the cookie does not need that
|
||||||
|
@ -58,9 +58,9 @@ const (
|
|||||||
|
|
||||||
hmacSecret = "this needs to be at least 32 characters to meet entropy requirements"
|
hmacSecret = "this needs to be at least 32 characters to meet entropy requirements"
|
||||||
|
|
||||||
authCodeExpirationSeconds = 3 * 60 // Current, we set our auth code expiration to 3 minutes
|
authCodeExpirationSeconds = 10 * 60 // Current, we set our auth code expiration to 10 minutes
|
||||||
accessTokenExpirationSeconds = 5 * 60 // Currently, we set our access token expiration to 5 minutes
|
accessTokenExpirationSeconds = 15 * 60 // Currently, we set our access token expiration to 15 minutes
|
||||||
idTokenExpirationSeconds = 5 * 60 // Currently, we set our ID token expiration to 5 minutes
|
idTokenExpirationSeconds = 15 * 60 // Currently, we set our ID token expiration to 15 minutes
|
||||||
|
|
||||||
timeComparisonFudgeSeconds = 15
|
timeComparisonFudgeSeconds = 15
|
||||||
)
|
)
|
||||||
@ -1346,7 +1346,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)
|
oauthHelper := oidc.FositeOauth2Helper(store, goodIssuer, []byte(hmacSecret), 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 +1378,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})
|
oauthHelper := oidc.FositeOauth2Helper(store, goodIssuer, []byte(hmacSecret), &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 +1397,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)
|
oauthHelper := oidc.FositeOauth2Helper(store, goodIssuer, []byte(hmacSecret), jwkProvider, oidc.DefaultOIDCTimeoutsConfiguration())
|
||||||
authResponder := simulateAuthEndpointHavingAlreadyRun(t, authRequest, oauthHelper)
|
authResponder := simulateAuthEndpointHavingAlreadyRun(t, authRequest, oauthHelper)
|
||||||
return oauthHelper, authResponder.GetCode(), nil
|
return oauthHelper, authResponder.GetCode(), nil
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/coreos/go-oidc"
|
coreosoidc "github.com/coreos/go-oidc"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
@ -25,6 +25,7 @@ import (
|
|||||||
configv1alpha1 "go.pinniped.dev/generated/1.19/apis/supervisor/config/v1alpha1"
|
configv1alpha1 "go.pinniped.dev/generated/1.19/apis/supervisor/config/v1alpha1"
|
||||||
idpv1alpha1 "go.pinniped.dev/generated/1.19/apis/supervisor/idp/v1alpha1"
|
idpv1alpha1 "go.pinniped.dev/generated/1.19/apis/supervisor/idp/v1alpha1"
|
||||||
"go.pinniped.dev/internal/certauthority"
|
"go.pinniped.dev/internal/certauthority"
|
||||||
|
"go.pinniped.dev/internal/oidc"
|
||||||
"go.pinniped.dev/internal/testutil"
|
"go.pinniped.dev/internal/testutil"
|
||||||
"go.pinniped.dev/pkg/oidcclient/nonce"
|
"go.pinniped.dev/pkg/oidcclient/nonce"
|
||||||
"go.pinniped.dev/pkg/oidcclient/pkce"
|
"go.pinniped.dev/pkg/oidcclient/pkce"
|
||||||
@ -69,7 +70,7 @@ func TestSupervisorLogin(t *testing.T) {
|
|||||||
return proxyURL, nil
|
return proxyURL, nil
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
oidcHTTPClientContext := oidc.ClientContext(ctx, httpClient)
|
oidcHTTPClientContext := coreosoidc.ClientContext(ctx, httpClient)
|
||||||
|
|
||||||
// Use the CA to issue a TLS server cert.
|
// Use the CA to issue a TLS server cert.
|
||||||
t.Logf("issuing test certificate")
|
t.Logf("issuing test certificate")
|
||||||
@ -110,9 +111,9 @@ func TestSupervisorLogin(t *testing.T) {
|
|||||||
}, idpv1alpha1.PhaseReady)
|
}, idpv1alpha1.PhaseReady)
|
||||||
|
|
||||||
// Perform OIDC discovery for our downstream.
|
// Perform OIDC discovery for our downstream.
|
||||||
var discovery *oidc.Provider
|
var discovery *coreosoidc.Provider
|
||||||
assert.Eventually(t, func() bool {
|
assert.Eventually(t, func() bool {
|
||||||
discovery, err = oidc.NewProvider(oidcHTTPClientContext, downstream.Spec.Issuer)
|
discovery, err = coreosoidc.NewProvider(oidcHTTPClientContext, downstream.Spec.Issuer)
|
||||||
return err == nil
|
return err == nil
|
||||||
}, 30*time.Second, 200*time.Millisecond)
|
}, 30*time.Second, 200*time.Millisecond)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -193,7 +194,7 @@ func TestSupervisorLogin(t *testing.T) {
|
|||||||
func verifyTokenResponse(
|
func verifyTokenResponse(
|
||||||
t *testing.T,
|
t *testing.T,
|
||||||
tokenResponse *oauth2.Token,
|
tokenResponse *oauth2.Token,
|
||||||
discovery *oidc.Provider,
|
discovery *coreosoidc.Provider,
|
||||||
downstreamOAuth2Config oauth2.Config,
|
downstreamOAuth2Config oauth2.Config,
|
||||||
upstreamIssuerName string,
|
upstreamIssuerName string,
|
||||||
nonceParam nonce.Nonce,
|
nonceParam nonce.Nonce,
|
||||||
@ -205,7 +206,7 @@ func verifyTokenResponse(
|
|||||||
// Verify the ID Token.
|
// Verify the ID Token.
|
||||||
rawIDToken, ok := tokenResponse.Extra("id_token").(string)
|
rawIDToken, ok := tokenResponse.Extra("id_token").(string)
|
||||||
require.True(t, ok, "expected to get an ID token but did not")
|
require.True(t, ok, "expected to get an ID token but did not")
|
||||||
var verifier = discovery.Verifier(&oidc.Config{ClientID: downstreamOAuth2Config.ClientID})
|
var verifier = discovery.Verifier(&coreosoidc.Config{ClientID: downstreamOAuth2Config.ClientID})
|
||||||
idToken, err := verifier.Verify(ctx, rawIDToken)
|
idToken, err := verifier.Verify(ctx, rawIDToken)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@ -215,7 +216,8 @@ func verifyTokenResponse(
|
|||||||
require.Greater(t, len(idToken.Subject), len(expectedSubjectPrefix),
|
require.Greater(t, len(idToken.Subject), len(expectedSubjectPrefix),
|
||||||
"the ID token Subject should include the upstream user ID after the upstream issuer name")
|
"the ID token Subject should include the upstream user ID after the upstream issuer name")
|
||||||
require.NoError(t, nonceParam.Validate(idToken))
|
require.NoError(t, nonceParam.Validate(idToken))
|
||||||
testutil.RequireTimeInDelta(t, time.Now().UTC().Add(time.Minute*5), idToken.Expiry, time.Second*30)
|
expectedIDTokenLifetime := oidc.DefaultOIDCTimeoutsConfiguration().IDTokenLifespan
|
||||||
|
testutil.RequireTimeInDelta(t, time.Now().UTC().Add(expectedIDTokenLifetime), idToken.Expiry, time.Second*30)
|
||||||
idTokenClaims := map[string]interface{}{}
|
idTokenClaims := map[string]interface{}{}
|
||||||
err = idToken.Claims(&idTokenClaims)
|
err = idToken.Claims(&idTokenClaims)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -229,7 +231,8 @@ func verifyTokenResponse(
|
|||||||
require.NotEmpty(t, tokenResponse.AccessToken)
|
require.NotEmpty(t, tokenResponse.AccessToken)
|
||||||
require.Equal(t, "bearer", tokenResponse.TokenType)
|
require.Equal(t, "bearer", tokenResponse.TokenType)
|
||||||
require.NotZero(t, tokenResponse.Expiry)
|
require.NotZero(t, tokenResponse.Expiry)
|
||||||
testutil.RequireTimeInDelta(t, time.Now().UTC().Add(time.Minute*5), tokenResponse.Expiry, time.Second*30)
|
expectedAccessTokenLifetime := oidc.DefaultOIDCTimeoutsConfiguration().AccessTokenLifespan
|
||||||
|
testutil.RequireTimeInDelta(t, time.Now().UTC().Add(expectedAccessTokenLifetime), tokenResponse.Expiry, time.Second*30)
|
||||||
|
|
||||||
require.NotEmpty(t, tokenResponse.RefreshToken)
|
require.NotEmpty(t, tokenResponse.RefreshToken)
|
||||||
}
|
}
|
||||||
@ -262,7 +265,7 @@ func (s *localCallbackServer) waitForCallback(timeout time.Duration) *http.Reque
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func doTokenExchange(t *testing.T, config *oauth2.Config, tokenResponse *oauth2.Token, httpClient *http.Client, provider *oidc.Provider) {
|
func doTokenExchange(t *testing.T, config *oauth2.Config, tokenResponse *oauth2.Token, httpClient *http.Client, provider *coreosoidc.Provider) {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute)
|
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
@ -289,7 +292,7 @@ func doTokenExchange(t *testing.T, config *oauth2.Config, tokenResponse *oauth2.
|
|||||||
}
|
}
|
||||||
require.NoError(t, json.NewDecoder(resp.Body).Decode(&respBody))
|
require.NoError(t, json.NewDecoder(resp.Body).Decode(&respBody))
|
||||||
|
|
||||||
var clusterVerifier = provider.Verifier(&oidc.Config{ClientID: "cluster-1234"})
|
var clusterVerifier = provider.Verifier(&coreosoidc.Config{ClientID: "cluster-1234"})
|
||||||
exchangedToken, err := clusterVerifier.Verify(ctx, respBody.AccessToken)
|
exchangedToken, err := clusterVerifier.Verify(ctx, respBody.AccessToken)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user