From b57d7d6ad79ed0a2a8359144c07eadeef0ea3fd3 Mon Sep 17 00:00:00 2001 From: Anish Ramasekar Date: Thu, 22 Feb 2024 16:33:24 -0800 Subject: [PATCH] add min valid jwt payload to API docs for structured authn config Signed-off-by: Anish Ramasekar --- .../pkg/apis/apiserver/v1alpha1/types.go | 8 ++ .../pkg/authenticator/token/oidc/oidc_test.go | 32 +++++++ test/integration/apiserver/oidc/oidc_test.go | 87 +++++++++++++++---- 3 files changed, 109 insertions(+), 18 deletions(-) diff --git a/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1alpha1/types.go b/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1alpha1/types.go index 74b1aa002e2..be9a67ef588 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1alpha1/types.go +++ b/staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1alpha1/types.go @@ -176,6 +176,14 @@ type AuthenticationConfiguration struct { // authenticators is neither defined nor stable across releases. Since // each JWT authenticator must have a unique issuer URL, at most one // JWT authenticator will attempt to cryptographically validate the token. + // + // The minimum valid JWT payload must contain the following claims: + // { + // "iss": "https://issuer.example.com", + // "aud": ["audience"], + // "exp": 1234567890, + // "": "username" + // } JWT []JWTAuthenticator `json:"jwt"` } diff --git a/staging/src/k8s.io/apiserver/plugin/pkg/authenticator/token/oidc/oidc_test.go b/staging/src/k8s.io/apiserver/plugin/pkg/authenticator/token/oidc/oidc_test.go index 55184ff99b7..92c84807680 100644 --- a/staging/src/k8s.io/apiserver/plugin/pkg/authenticator/token/oidc/oidc_test.go +++ b/staging/src/k8s.io/apiserver/plugin/pkg/authenticator/token/oidc/oidc_test.go @@ -2953,6 +2953,38 @@ func TestToken(t *testing.T) { }`, valid.Unix()), want: &user.DefaultInfo{}, }, + // test to assert the minimum valid jwt payload + // the required claims are iss, aud, exp and (in this case user). + { + name: "minimum valid jwt payload", + options: Options{ + JWTAuthenticator: apiserver.JWTAuthenticator{ + Issuer: apiserver.Issuer{ + URL: "https://auth.example.com", + Audiences: []string{"my-client"}, + }, + ClaimMappings: apiserver.ClaimMappings{ + Username: apiserver.PrefixedClaimOrExpression{ + Expression: "claims.user", + }, + }, + }, + now: func() time.Time { return now }, + }, + signingKey: loadRSAPrivKey(t, "testdata/rsa_1.pem", jose.RS256), + pubKeys: []*jose.JSONWebKey{ + loadRSAKey(t, "testdata/rsa_1.pem", jose.RS256), + }, + claims: fmt.Sprintf(`{ + "iss": "https://auth.example.com", + "aud": "my-client", + "user": "jane", + "exp": %d + }`, valid.Unix()), + want: &user.DefaultInfo{ + Name: "jane", + }, + }, } for _, test := range tests { t.Run(test.name, test.run) diff --git a/test/integration/apiserver/oidc/oidc_test.go b/test/integration/apiserver/oidc/oidc_test.go index f7725cd68f0..22dd5a239c2 100644 --- a/test/integration/apiserver/oidc/oidc_test.go +++ b/test/integration/apiserver/oidc/oidc_test.go @@ -107,6 +107,15 @@ var ( // authenticationConfigFunc is a function that returns a string representation of an authentication config. type authenticationConfigFunc func(t *testing.T, issuerURL, caCert string) string +type apiServerOIDCConfig struct { + oidcURL string + oidcClientID string + oidcCAFilePath string + oidcUsernamePrefix string + oidcUsernameClaim string + authenticationConfigYAML string +} + func TestOIDC(t *testing.T) { t.Log("Testing OIDC authenticator with --oidc-* flags") runTests(t, false) @@ -122,18 +131,57 @@ func TestStructuredAuthenticationConfig(t *testing.T) { func runTests(t *testing.T, useAuthenticationConfig bool) { var tests = []singleTest[*rsa.PrivateKey, *rsa.PublicKey]{ { - name: "ID token is ok", - configureInfrastructure: configureTestInfrastructure[*rsa.PrivateKey, *rsa.PublicKey], - configureOIDCServerBehaviour: func(t *testing.T, oidcServer *utilsoidc.TestServer, signingPrivateKey *rsa.PrivateKey) { + name: "ID token is ok", + configureInfrastructure: func(t *testing.T, fn authenticationConfigFunc, keyFunc func(t *testing.T) (*rsa.PrivateKey, *rsa.PublicKey)) ( + oidcServer *utilsoidc.TestServer, + apiServer *kubeapiserverapptesting.TestServer, + signingPrivateKey *rsa.PrivateKey, + caCertContent []byte, + caFilePath string, + ) { + caCertContent, _, caFilePath, caKeyFilePath := generateCert(t) + signingPrivateKey, publicKey := keyFunc(t) + oidcServer = utilsoidc.BuildAndRunTestServer(t, caFilePath, caKeyFilePath) + + if useAuthenticationConfig { + authenticationConfig := fmt.Sprintf(` +apiVersion: apiserver.config.k8s.io/v1alpha1 +kind: AuthenticationConfiguration +jwt: +- issuer: + url: %s + audiences: + - %s + certificateAuthority: | + %s + claimMappings: + username: + claim: user + prefix: %s +`, oidcServer.URL(), defaultOIDCClientID, indentCertificateAuthority(string(caCertContent)), defaultOIDCUsernamePrefix) + apiServer = startTestAPIServerForOIDC(t, apiServerOIDCConfig{authenticationConfigYAML: authenticationConfig}, &signingPrivateKey.PublicKey) + } else { + apiServer = startTestAPIServerForOIDC(t, apiServerOIDCConfig{oidcURL: oidcServer.URL(), oidcClientID: defaultOIDCClientID, + oidcCAFilePath: caFilePath, oidcUsernamePrefix: defaultOIDCUsernamePrefix, oidcUsernameClaim: "user"}, &signingPrivateKey.PublicKey) + } + oidcServer.JwksHandler().EXPECT().KeySet().AnyTimes().DoAndReturn(utilsoidc.DefaultJwksHandlerBehavior(t, publicKey)) + + adminClient := kubernetes.NewForConfigOrDie(apiServer.ClientConfig) + configureRBAC(t, adminClient, defaultRole, defaultRoleBinding) + + return oidcServer, apiServer, signingPrivateKey, caCertContent, caFilePath + }, configureOIDCServerBehaviour: func(t *testing.T, oidcServer *utilsoidc.TestServer, signingPrivateKey *rsa.PrivateKey) { idTokenLifetime := time.Second * 1200 oidcServer.TokenHandler().EXPECT().Token().Times(1).DoAndReturn(utilsoidc.TokenHandlerBehaviorReturningPredefinedJWT( t, signingPrivateKey, + // This asserts the minimum valid claims for an ID token required by the authenticator. + // "iss", "aud", "exp" and a claim for the username. map[string]interface{}{ - "iss": oidcServer.URL(), - "sub": defaultOIDCClaimedUsername, - "aud": defaultOIDCClientID, - "exp": time.Now().Add(idTokenLifetime).Unix(), + "iss": oidcServer.URL(), + "user": defaultOIDCClaimedUsername, + "aud": defaultOIDCClientID, + "exp": time.Now().Add(idTokenLifetime).Unix(), }, defaultStubAccessToken, defaultStubRefreshToken, @@ -244,9 +292,9 @@ jwt: claim: sub prefix: %s `, oidcServer.URL(), defaultOIDCClientID, indentCertificateAuthority(string(caCertContent)), defaultOIDCUsernamePrefix) - apiServer = startTestAPIServerForOIDC(t, "", "", "", authenticationConfig, &signingPrivateKey.PublicKey) + apiServer = startTestAPIServerForOIDC(t, apiServerOIDCConfig{authenticationConfigYAML: authenticationConfig}, &signingPrivateKey.PublicKey) } else { - apiServer = startTestAPIServerForOIDC(t, oidcServer.URL(), defaultOIDCClientID, caFilePath, "", &signingPrivateKey.PublicKey) + apiServer = startTestAPIServerForOIDC(t, apiServerOIDCConfig{oidcURL: oidcServer.URL(), oidcClientID: defaultOIDCClientID, oidcCAFilePath: caFilePath, oidcUsernamePrefix: defaultOIDCUsernamePrefix}, &signingPrivateKey.PublicKey) } adminClient := kubernetes.NewForConfigOrDie(apiServer.ClientConfig) @@ -875,9 +923,9 @@ func configureTestInfrastructure[K utilsoidc.JosePrivateKey, L utilsoidc.JosePub authenticationConfig := fn(t, oidcServer.URL(), string(caCertContent)) if len(authenticationConfig) > 0 { - apiServer = startTestAPIServerForOIDC(t, "", "", "", authenticationConfig, publicKey) + apiServer = startTestAPIServerForOIDC(t, apiServerOIDCConfig{authenticationConfigYAML: authenticationConfig}, publicKey) } else { - apiServer = startTestAPIServerForOIDC(t, oidcServer.URL(), defaultOIDCClientID, caFilePath, "", publicKey) + apiServer = startTestAPIServerForOIDC(t, apiServerOIDCConfig{oidcURL: oidcServer.URL(), oidcClientID: defaultOIDCClientID, oidcCAFilePath: caFilePath, oidcUsernamePrefix: defaultOIDCUsernamePrefix}, publicKey) } oidcServer.JwksHandler().EXPECT().KeySet().AnyTimes().DoAndReturn(utilsoidc.DefaultJwksHandlerBehavior(t, publicKey)) @@ -929,18 +977,21 @@ func configureClientConfigForOIDC(t *testing.T, config *rest.Config, clientID, c return cfg } -func startTestAPIServerForOIDC[L utilsoidc.JosePublicKey](t *testing.T, oidcURL, oidcClientID, oidcCAFilePath, authenticationConfigYAML string, publicKey L) *kubeapiserverapptesting.TestServer { +func startTestAPIServerForOIDC[L utilsoidc.JosePublicKey](t *testing.T, c apiServerOIDCConfig, publicKey L) *kubeapiserverapptesting.TestServer { t.Helper() var customFlags []string - if authenticationConfigYAML != "" { - customFlags = []string{fmt.Sprintf("--authentication-config=%s", writeTempFile(t, authenticationConfigYAML))} + if len(c.authenticationConfigYAML) > 0 { + customFlags = []string{fmt.Sprintf("--authentication-config=%s", writeTempFile(t, c.authenticationConfigYAML))} } else { customFlags = []string{ - fmt.Sprintf("--oidc-issuer-url=%s", oidcURL), - fmt.Sprintf("--oidc-client-id=%s", oidcClientID), - fmt.Sprintf("--oidc-ca-file=%s", oidcCAFilePath), - fmt.Sprintf("--oidc-username-prefix=%s", defaultOIDCUsernamePrefix), + fmt.Sprintf("--oidc-issuer-url=%s", c.oidcURL), + fmt.Sprintf("--oidc-client-id=%s", c.oidcClientID), + fmt.Sprintf("--oidc-ca-file=%s", c.oidcCAFilePath), + fmt.Sprintf("--oidc-username-prefix=%s", c.oidcUsernamePrefix), + } + if len(c.oidcUsernameClaim) > 0 { + customFlags = append(customFlags, fmt.Sprintf("--oidc-username-claim=%s", c.oidcUsernameClaim)) } customFlags = append(customFlags, maybeSetSigningAlgs(publicKey)...) }