Backfill test for token endpoint error when JWK is not yet available

Signed-off-by: Ryan Richard <richardry@vmware.com>
This commit is contained in:
Aram Price 2020-12-07 11:53:24 -08:00 committed by Ryan Richard
parent e0b6133bf1
commit 648fa4b9ba
4 changed files with 77 additions and 16 deletions

View File

@ -50,7 +50,7 @@ func (s *dynamicOpenIDConnectECDSAStrategy) GenerateIDToken(
_, activeJwk := s.jwksProvider.GetJWKS(s.fositeConfig.IDTokenIssuer)
if activeJwk == nil {
plog.Debug("no JWK found for issuer", "issuer", s.fositeConfig.IDTokenIssuer)
return "", constable.Error("no JWK found for issuer")
return "", fosite.ErrTemporarilyUnavailable.WithCause(constable.Error("no JWK found for issuer"))
}
key, ok := activeJwk.Key.(*ecdsa.PrivateKey)
if !ok {
@ -65,7 +65,7 @@ func (s *dynamicOpenIDConnectECDSAStrategy) GenerateIDToken(
"actualType",
actualType,
)
return "", constable.Error("JWK must be of type ecdsa")
return "", fosite.ErrServerError.WithCause(constable.Error("JWK must be of type ecdsa"))
}
return compose.NewOpenIDConnectECDSAStrategy(s.fositeConfig, key).GenerateIDToken(ctx, requester)

View File

@ -9,6 +9,7 @@ import (
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"errors"
"net/url"
"testing"
@ -42,7 +43,8 @@ func TestDynamicOpenIDConnectECDSAStrategy(t *testing.T) {
name string
issuer string
jwksProvider func(jwks.DynamicJWKSProvider)
wantError string
wantErrorType *fosite.RFC6749Error
wantErrorCause string
wantSigningJWK *jose.JSONWebKey
}{
{
@ -63,9 +65,10 @@ func TestDynamicOpenIDConnectECDSAStrategy(t *testing.T) {
},
},
{
name: "jwks provider does not contain signing key for issuer",
issuer: goodIssuer,
wantError: "no JWK found for issuer",
name: "jwks provider does not contain signing key for issuer",
issuer: goodIssuer,
wantErrorType: fosite.ErrTemporarilyUnavailable,
wantErrorCause: "no JWK found for issuer",
},
{
name: "jwks provider contains signing key of wrong type for issuer",
@ -80,7 +83,8 @@ func TestDynamicOpenIDConnectECDSAStrategy(t *testing.T) {
},
)
},
wantError: "JWK must be of type ecdsa",
wantErrorType: fosite.ErrServerError,
wantErrorCause: "JWK must be of type ecdsa",
},
}
for _, test := range tests {
@ -111,8 +115,9 @@ func TestDynamicOpenIDConnectECDSAStrategy(t *testing.T) {
},
}
idToken, err := s.GenerateIDToken(context.Background(), requester)
if test.wantError != "" {
require.EqualError(t, err, test.wantError)
if test.wantErrorType != nil {
require.True(t, errors.Is(err, test.wantErrorType))
require.EqualError(t, err.(*fosite.RFC6749Error).Cause(), test.wantErrorCause)
} else {
require.NoError(t, err)

View File

@ -183,6 +183,15 @@ var (
"status_code": 400
}
`)
fositeTemporarilyUnavailableErrorBody = here.Doc(`
{
"error": "temporarily_unavailable",
"error_description": "The authorization server is currently unable to handle the request due to a temporary overloading or maintenance of the server",
"error_verbose": "The authorization server is currently unable to handle the request due to a temporary overloading or maintenance of the server",
"status_code": 503
}
`)
)
func TestTokenEndpoint(t *testing.T) {
@ -214,7 +223,18 @@ func TestTokenEndpoint(t *testing.T) {
},
authCode string,
)
request func(r *http.Request, authCode string)
request func(r *http.Request, authCode string)
makeOathHelper func(
t *testing.T,
authRequest *http.Request,
store interface {
oauth2.TokenRevocationStorage
oauth2.CoreStorage
openid.OpenIDConnectRequestStorage
pkce.PKCERequestStorage
fosite.ClientManager
},
) (fosite.OAuth2Provider, string, *ecdsa.PrivateKey)
wantStatus int
wantBodyFields []string
@ -381,6 +401,12 @@ func TestTokenEndpoint(t *testing.T) {
wantStatus: http.StatusBadRequest,
wantExactBody: fositeWrongPKCEVerifierErrorBody,
},
{
name: "private signing key for JWTs has not yet been provided by the controller who is responsible for dynamically providing it",
makeOathHelper: makeOauthHelperWithNilPrivateJWTSigningKey,
wantStatus: http.StatusServiceUnavailable,
wantExactBody: fositeTemporarilyUnavailableErrorBody,
},
}
for _, test := range tests {
test := test
@ -393,8 +419,16 @@ func TestTokenEndpoint(t *testing.T) {
client := fake.NewSimpleClientset()
secrets := client.CoreV1().Secrets("some-namespace")
var oauthHelper fosite.OAuth2Provider
var authCode string
var jwtSigningKey *ecdsa.PrivateKey
oauthStore := oidc.NewKubeStorage(secrets)
oauthHelper, authCode, jwtSigningKey := makeHappyOauthHelper(t, authRequest, oauthStore)
if test.makeOathHelper != nil {
oauthHelper, authCode, jwtSigningKey = test.makeOathHelper(t, authRequest, oauthStore)
} else {
oauthHelper, authCode, jwtSigningKey = makeHappyOauthHelper(t, authRequest, oauthStore)
}
if test.storage != nil {
test.storage(t, oauthStore, authCode)
@ -595,7 +629,30 @@ func makeHappyOauthHelper(
jwtSigningKey, jwkProvider := generateJWTSigningKeyAndJWKSProvider(t, goodIssuer)
oauthHelper := oidc.FositeOauth2Helper(store, goodIssuer, []byte(hmacSecret), jwkProvider)
authResponder := simulateAuthEndpointHavingAlreadyRun(t, authRequest, oauthHelper)
return oauthHelper, authResponder.GetCode(), jwtSigningKey
}
func makeOauthHelperWithNilPrivateJWTSigningKey(
t *testing.T,
authRequest *http.Request,
store interface {
oauth2.TokenRevocationStorage
oauth2.CoreStorage
openid.OpenIDConnectRequestStorage
pkce.PKCERequestStorage
fosite.ClientManager
},
) (fosite.OAuth2Provider, string, *ecdsa.PrivateKey) {
t.Helper()
jwkProvider := jwks.NewDynamicJWKSProvider() // empty provider which contains no signing key for this issuer
oauthHelper := oidc.FositeOauth2Helper(store, goodIssuer, []byte(hmacSecret), jwkProvider)
authResponder := simulateAuthEndpointHavingAlreadyRun(t, authRequest, oauthHelper)
return oauthHelper, authResponder.GetCode(), nil
}
func simulateAuthEndpointHavingAlreadyRun(t *testing.T, authRequest *http.Request, oauthHelper fosite.OAuth2Provider) fosite.AuthorizeResponder {
// Simulate the auth endpoint running so Fosite code will fill the store with realistic values.
//
// We only set the fields in the session that Fosite wants us to set.
@ -616,8 +673,7 @@ func makeHappyOauthHelper(
}
authResponder, err := oauthHelper.NewAuthorizeResponse(ctx, authRequester, session)
require.NoError(t, err)
return oauthHelper, authResponder.GetCode(), jwtSigningKey
return authResponder
}
func generateJWTSigningKeyAndJWKSProvider(t *testing.T, issuer string) (*ecdsa.PrivateKey, jwks.DynamicJWKSProvider) {

View File

@ -68,7 +68,7 @@ func TestSupervisorLogin(t *testing.T) {
return proxyURL, nil
},
}}
oidcHttpClientContext := oidc.ClientContext(ctx, httpClient)
oidcHTTPClientContext := oidc.ClientContext(ctx, httpClient)
// Use the CA to issue a TLS server cert.
t.Logf("issuing test certificate")
@ -111,7 +111,7 @@ func TestSupervisorLogin(t *testing.T) {
// Perform OIDC discovery for our downstream.
var discovery *oidc.Provider
assert.Eventually(t, func() bool {
discovery, err = oidc.NewProvider(oidcHttpClientContext, downstream.Spec.Issuer)
discovery, err = oidc.NewProvider(oidcHTTPClientContext, downstream.Spec.Issuer)
return err == nil
}, 30*time.Second, 200*time.Millisecond)
require.NoError(t, err)
@ -164,7 +164,7 @@ func TestSupervisorLogin(t *testing.T) {
require.NotEmpty(t, authcode)
// Call the token endpoint to get tokens.
tokenResponse, err := downstreamOAuth2Config.Exchange(oidcHttpClientContext, authcode, pkceParam.Verifier())
tokenResponse, err := downstreamOAuth2Config.Exchange(oidcHTTPClientContext, authcode, pkceParam.Verifier())
require.NoError(t, err)
// Verify the ID Token.