From b5e0068325da7aa5ca42a7d5ea6b0f012a519765 Mon Sep 17 00:00:00 2001 From: Monis Khan Date: Tue, 13 Feb 2024 13:45:53 -0500 Subject: [PATCH] Support all key algs with structured authn config Signed-off-by: Monis Khan --- pkg/kubeapiserver/options/authentication.go | 4 ++ .../options/authentication_test.go | 39 +++++++++++++++++++ .../pkg/authenticator/token/oidc/oidc.go | 8 +++- test/integration/apiserver/oidc/oidc_test.go | 39 +++++++++++++++++++ 4 files changed, 88 insertions(+), 2 deletions(-) diff --git a/pkg/kubeapiserver/options/authentication.go b/pkg/kubeapiserver/options/authentication.go index 11501a2f3cb..1e83d015559 100644 --- a/pkg/kubeapiserver/options/authentication.go +++ b/pkg/kubeapiserver/options/authentication.go @@ -40,6 +40,7 @@ import ( "k8s.io/apiserver/pkg/server/egressselector" genericoptions "k8s.io/apiserver/pkg/server/options" utilfeature "k8s.io/apiserver/pkg/util/feature" + "k8s.io/apiserver/plugin/pkg/authenticator/token/oidc" "k8s.io/client-go/informers" "k8s.io/client-go/kubernetes" v1listers "k8s.io/client-go/listers/core/v1" @@ -461,6 +462,9 @@ func (o *BuiltInAuthenticationOptions) ToAuthenticationConfig() (kubeauthenticat if ret.AuthenticationConfig, err = loadAuthenticationConfig(o.AuthenticationConfigFile); err != nil { return kubeauthenticator.Config{}, err } + // all known signing algs are allowed when using authentication config + // TODO: what we really want to express is 'any alg is fine as long it matches a public key' + ret.OIDCSigningAlgs = oidc.AllValidSigningAlgorithms() } else if o.OIDC != nil && len(o.OIDC.IssuerURL) > 0 && len(o.OIDC.ClientID) > 0 { usernamePrefix := o.OIDC.UsernamePrefix diff --git a/pkg/kubeapiserver/options/authentication_test.go b/pkg/kubeapiserver/options/authentication_test.go index eed32f1cab8..c2b06c3950e 100644 --- a/pkg/kubeapiserver/options/authentication_test.go +++ b/pkg/kubeapiserver/options/authentication_test.go @@ -446,6 +446,8 @@ func TestBuiltInAuthenticationOptionsAddFlags(t *testing.T) { } func TestToAuthenticationConfig_OIDC(t *testing.T) { + defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.StructuredAuthenticationConfiguration, true)() + testCases := []struct { name string args []string @@ -640,6 +642,43 @@ func TestToAuthenticationConfig_OIDC(t *testing.T) { OIDCSigningAlgs: []string{"RS256"}, }, }, + { + name: "basic authentication configuration", + args: []string{ + "--authentication-config=" + writeTempFile(t, ` +apiVersion: apiserver.config.k8s.io/v1alpha1 +kind: AuthenticationConfiguration +jwt: +- issuer: + url: https://test-issuer + audiences: [ "🐼" ] + claimMappings: + username: + claim: sub + prefix: "" +`), + }, + expectConfig: kubeauthenticator.Config{ + TokenSuccessCacheTTL: 10 * time.Second, + AuthenticationConfig: &apiserver.AuthenticationConfiguration{ + JWT: []apiserver.JWTAuthenticator{ + { + Issuer: apiserver.Issuer{ + URL: "https://test-issuer", + Audiences: []string{"🐼"}, + }, + ClaimMappings: apiserver.ClaimMappings{ + Username: apiserver.PrefixedClaimOrExpression{ + Claim: "sub", + Prefix: pointer.String(""), + }, + }, + }, + }, + }, + OIDCSigningAlgs: []string{"ES256", "ES384", "ES512", "PS256", "PS384", "PS512", "RS256", "RS384", "RS512"}, + }, + }, } for _, testcase := range testCases { diff --git a/staging/src/k8s.io/apiserver/plugin/pkg/authenticator/token/oidc/oidc.go b/staging/src/k8s.io/apiserver/plugin/pkg/authenticator/token/oidc/oidc.go index 1aa2f7abff5..3c7dd5b34ae 100644 --- a/staging/src/k8s.io/apiserver/plugin/pkg/authenticator/token/oidc/oidc.go +++ b/staging/src/k8s.io/apiserver/plugin/pkg/authenticator/token/oidc/oidc.go @@ -49,6 +49,7 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/net" + "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/apiserver/pkg/apis/apiserver" apiservervalidation "k8s.io/apiserver/pkg/apis/apiserver/validation" @@ -196,8 +197,11 @@ func (a *Authenticator) Close() { a.cancel() } -// whitelist of signing algorithms to ensure users don't mistakenly pass something -// goofy. +func AllValidSigningAlgorithms() []string { + return sets.List(sets.KeySet(allowedSigningAlgs)) +} + +// allowlist of signing algorithms to ensure users don't mistakenly pass something goofy. var allowedSigningAlgs = map[string]bool{ oidc.RS256: true, oidc.RS384: true, diff --git a/test/integration/apiserver/oidc/oidc_test.go b/test/integration/apiserver/oidc/oidc_test.go index ee7e1544b03..c9b375ec8a9 100644 --- a/test/integration/apiserver/oidc/oidc_test.go +++ b/test/integration/apiserver/oidc/oidc_test.go @@ -18,6 +18,8 @@ package oidc import ( "context" + "crypto/ecdsa" + "crypto/elliptic" "crypto/rand" "crypto/rsa" "crypto/tls" @@ -280,6 +282,34 @@ jwt: for _, tt := range tests { t.Run(tt.name, singleTestRunner(useAuthenticationConfig, rsaGenerateKey, tt)) } + + for _, tt := range []singleTest[*ecdsa.PrivateKey, *ecdsa.PublicKey]{ + { + name: "ID token is ok", + configureInfrastructure: configureTestInfrastructure[*ecdsa.PrivateKey, *ecdsa.PublicKey], + configureOIDCServerBehaviour: func(t *testing.T, oidcServer *utilsoidc.TestServer, signingPrivateKey *ecdsa.PrivateKey) { + idTokenLifetime := time.Second * 1200 + oidcServer.TokenHandler().EXPECT().Token().Times(1).DoAndReturn(utilsoidc.TokenHandlerBehaviorReturningPredefinedJWT( + t, + signingPrivateKey, + map[string]interface{}{ + "iss": oidcServer.URL(), + "sub": defaultOIDCClaimedUsername, + "aud": defaultOIDCClientID, + "exp": time.Now().Add(idTokenLifetime).Unix(), + }, + defaultStubAccessToken, + defaultStubRefreshToken, + )) + }, + configureClient: configureClientFetchingOIDCCredentials, + assertErrFn: func(t *testing.T, errorToCheck error) { + assert.NoError(t, errorToCheck) + }, + }, + } { + t.Run(tt.name, singleTestRunner(useAuthenticationConfig, ecdsaGenerateKey, tt)) + } } type singleTest[K utilsoidc.JosePrivateKey, L utilsoidc.JosePublicKey] struct { @@ -755,6 +785,15 @@ func rsaGenerateKey(t *testing.T) (*rsa.PrivateKey, *rsa.PublicKey) { return privateKey, &privateKey.PublicKey } +func ecdsaGenerateKey(t *testing.T) (*ecdsa.PrivateKey, *ecdsa.PublicKey) { + t.Helper() + + privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + require.NoError(t, err) + + return privateKey, &privateKey.PublicKey +} + func configureTestInfrastructure[K utilsoidc.JosePrivateKey, L utilsoidc.JosePublicKey](t *testing.T, fn authenticationConfigFunc, keyFunc func(t *testing.T) (K, L)) ( oidcServer *utilsoidc.TestServer, apiServer *kubeapiserverapptesting.TestServer,