From ab0682917a5f61b5dd6ae26fde05c63839cd2ad7 Mon Sep 17 00:00:00 2001 From: Joshua Casey Date: Tue, 5 Mar 2024 22:00:17 -0600 Subject: [PATCH] whoami integration test now allows for additional extra fields in K8s 1.30+ --- hack/kind-up.sh | 3 +- .../testutil/kube_server_compatibility.go | 6 ++- test/integration/concierge_whoami_test.go | 47 ++++++++++++++----- 3 files changed, 41 insertions(+), 15 deletions(-) diff --git a/hack/kind-up.sh b/hack/kind-up.sh index f4a92b46c..7fdb90d03 100755 --- a/hack/kind-up.sh +++ b/hack/kind-up.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -# Copyright 2020-2023 the Pinniped contributors. All Rights Reserved. +# Copyright 2020-2024 the Pinniped contributors. All Rights Reserved. # SPDX-License-Identifier: Apache-2.0 set -euo pipefail @@ -41,6 +41,7 @@ fi ytt ${use_kind_registry} ${use_contour_registry} --file="${ROOT}"/hack/lib/kind-config/single-node.yaml >/tmp/kind-config.yaml # To choose a specific version of kube, add this option to the command below: `--image kindest/node:v1.28.0`. +# To use the "latest-main" version of kubernetes builds by the pipeline, use `--image ghcr.io/pinniped-ci-bot/kind-node-image:latest` # To debug the kind config, add this option to the command below: `-v 10` kind create cluster --config /tmp/kind-config.yaml --name pinniped diff --git a/internal/testutil/kube_server_compatibility.go b/internal/testutil/kube_server_compatibility.go index fbf6fbc8c..d538e11bb 100644 --- a/internal/testutil/kube_server_compatibility.go +++ b/internal/testutil/kube_server_compatibility.go @@ -1,4 +1,4 @@ -// Copyright 2021 the Pinniped contributors. All Rights Reserved. +// Copyright 2021-2024 the Pinniped contributors. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 package testutil @@ -31,6 +31,10 @@ func KubeServerSupportsCertificatesV1API(t *testing.T, discoveryClient discovery return false } +func KubeServerMinorVersionAtLeastInclusive(t *testing.T, discoveryClient discovery.DiscoveryInterface, min int) bool { + return !KubeServerMinorVersionInBetweenInclusive(t, discoveryClient, 0, min-1) +} + func KubeServerMinorVersionInBetweenInclusive(t *testing.T, discoveryClient discovery.DiscoveryInterface, min, max int) bool { t.Helper() diff --git a/test/integration/concierge_whoami_test.go b/test/integration/concierge_whoami_test.go index a9e18d8c9..182f5889d 100644 --- a/test/integration/concierge_whoami_test.go +++ b/test/integration/concierge_whoami_test.go @@ -1,4 +1,4 @@ -// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved. +// Copyright 2020-2024 the Pinniped contributors. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 package integration @@ -151,9 +151,10 @@ func TestWhoAmI_ServiceAccount_TokenRequest_Parallel(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() - kubeClient := testlib.NewKubernetesClientset(t).CoreV1() + kubeClient := testlib.NewKubernetesClientset(t) + coreV1client := kubeClient.CoreV1() - ns, err := kubeClient.Namespaces().Create(ctx, &corev1.Namespace{ + ns, err := coreV1client.Namespaces().Create(ctx, &corev1.Namespace{ ObjectMeta: metav1.ObjectMeta{ GenerateName: "test-whoami-", }, @@ -161,17 +162,17 @@ func TestWhoAmI_ServiceAccount_TokenRequest_Parallel(t *testing.T) { require.NoError(t, err) t.Cleanup(func() { - require.NoError(t, kubeClient.Namespaces().Delete(context.Background(), ns.Name, metav1.DeleteOptions{})) + require.NoError(t, coreV1client.Namespaces().Delete(context.Background(), ns.Name, metav1.DeleteOptions{})) }) - sa, err := kubeClient.ServiceAccounts(ns.Name).Create(ctx, &corev1.ServiceAccount{ + sa, err := coreV1client.ServiceAccounts(ns.Name).Create(ctx, &corev1.ServiceAccount{ ObjectMeta: metav1.ObjectMeta{ GenerateName: "test-whoami-", }, }, metav1.CreateOptions{}) require.NoError(t, err) - _, tokenRequestProbeErr := kubeClient.ServiceAccounts(ns.Name).CreateToken(ctx, sa.Name, &authenticationv1.TokenRequest{}, metav1.CreateOptions{}) + _, tokenRequestProbeErr := coreV1client.ServiceAccounts(ns.Name).CreateToken(ctx, sa.Name, &authenticationv1.TokenRequest{}, metav1.CreateOptions{}) if errors.IsNotFound(tokenRequestProbeErr) && tokenRequestProbeErr.Error() == "the server could not find the requested resource" { return // stop test early since the token request API is not enabled on this cluster - other errors are caught below } @@ -191,7 +192,7 @@ func TestWhoAmI_ServiceAccount_TokenRequest_Parallel(t *testing.T) { ServiceAccountName: sa.Name, }) - tokenRequestBadAudience, err := kubeClient.ServiceAccounts(ns.Name).CreateToken(ctx, sa.Name, &authenticationv1.TokenRequest{ + tokenRequestBadAudience, err := coreV1client.ServiceAccounts(ns.Name).CreateToken(ctx, sa.Name, &authenticationv1.TokenRequest{ Spec: authenticationv1.TokenRequestSpec{ Audiences: []string{"should-fail-because-wrong-audience"}, // anything that is not an API server audience BoundObjectRef: &authenticationv1.BoundObjectReference{ @@ -211,7 +212,7 @@ func TestWhoAmI_ServiceAccount_TokenRequest_Parallel(t *testing.T) { Create(ctx, &identityv1alpha1.WhoAmIRequest{}, metav1.CreateOptions{}) require.True(t, errors.IsUnauthorized(badAudErr), testlib.Sdump(badAudErr)) - tokenRequest, err := kubeClient.ServiceAccounts(ns.Name).CreateToken(ctx, sa.Name, &authenticationv1.TokenRequest{ + tokenRequest, err := coreV1client.ServiceAccounts(ns.Name).CreateToken(ctx, sa.Name, &authenticationv1.TokenRequest{ Spec: authenticationv1.TokenRequestSpec{ Audiences: []string{}, BoundObjectRef: &authenticationv1.BoundObjectReference{ @@ -231,7 +232,8 @@ func TestWhoAmI_ServiceAccount_TokenRequest_Parallel(t *testing.T) { Create(ctx, &identityv1alpha1.WhoAmIRequest{}, metav1.CreateOptions{}) require.NoError(t, err, testlib.Sdump(err)) - // new service account tokens include the pod info in the extra fields + whoAmIUser := whoAmITokenReq.Status.KubernetesUserInfo.User + require.Equal(t, &identityv1alpha1.WhoAmIRequest{ Status: identityv1alpha1.WhoAmIRequestStatus{ @@ -244,16 +246,35 @@ func TestWhoAmI_ServiceAccount_TokenRequest_Parallel(t *testing.T) { "system:serviceaccounts:" + ns.Name, "system:authenticated", }, - Extra: map[string]identityv1alpha1.ExtraValue{ - "authentication.kubernetes.io/pod-name": {pod.Name}, - "authentication.kubernetes.io/pod-uid": {string(pod.UID)}, - }, + Extra: whoAmIUser.Extra, // This will be a dynamic assertion below based on the version of K8s }, }, }, }, whoAmITokenReq, ) + + require.Equal(t, whoAmIUser.Extra["authentication.kubernetes.io/pod-name"], identityv1alpha1.ExtraValue{pod.Name}) + require.Equal(t, whoAmIUser.Extra["authentication.kubernetes.io/pod-uid"], identityv1alpha1.ExtraValue{string(pod.UID)}) + + if testutil.KubeServerMinorVersionAtLeastInclusive(t, kubeClient.Discovery(), 30) { + // Starting in K8s 1.30, three additional `Extra` fields were added with unpredictable values. + // This is because the following three feature gates were enabled by default in 1.30. + // https://kubernetes.io/docs/reference/command-line-tools-reference/feature-gates/ + // - ServiceAccountTokenJTI + // - ServiceAccountTokenNodeBindingValidation + // - ServiceAccountTokenPodNodeInfo + // These were added in source code in 1.29 but not enabled by default until 1.30. + // <1.29: https://pkg.go.dev/k8s.io/apiserver@v0.28.7/pkg/authentication/serviceaccount + // 1.29+: https://pkg.go.dev/k8s.io/apiserver@v0.29.0/pkg/authentication/serviceaccount + + require.Equal(t, 5, len(whoAmIUser.Extra)) + require.NotEmpty(t, whoAmIUser.Extra["authentication.kubernetes.io/credential-id"]) + require.NotEmpty(t, whoAmIUser.Extra["authentication.kubernetes.io/node-name"]) + require.NotEmpty(t, whoAmIUser.Extra["authentication.kubernetes.io/node-uid"]) + } else { + require.Equal(t, 2, len(whoAmIUser.Extra)) + } } // whoami requests are non-mutating and safe to run in parallel with serial tests, see main_test.go.