diff --git a/go.mod b/go.mod index 2218a0f3c..6c5feedb4 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ toolchain go1.22.0 // This version taken from https://github.com/kubernetes/apiserver/blob/v0.29.2/go.mod#L14 to avoid compile failures. replace github.com/google/cel-go => github.com/google/cel-go v0.17.7 -// Fostite depends on ory/x which depends on opentelemetry. kubernetes/apiserver also depends on opentelemetry. +// ory/fosite depends on ory/x which depends on opentelemetry. kubernetes/apiserver also depends on opentelemetry. // Where they clash and cause "go mod tidy" to fail, use replace directives to make it work. // Copied from https://github.com/kubernetes/apiserver/blob/v0.29.2/go.mod#L28-L33. replace ( @@ -23,10 +23,12 @@ replace ( // to resolve the clashes with ory/x, so use the same version that kubernetes/apiserver chooses for opentelemetry. replace go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp => go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 -// This is an indirect dep which is currently at v0.42.0 (see below), but scanners report that version -// has CVE-2023-45142, so replace it with the fixed version. +// This is an indirect dep which has CVE-2023-45142, so replace it with the fixed version. replace go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace => go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.44.0 +// This is an indirect dep which has CVE-2024-24786, so replace it with a fixed version +replace google.golang.org/protobuf => google.golang.org/protobuf v1.33.0 + require ( github.com/MakeNowJust/heredoc/v2 v2.0.1 github.com/chromedp/cdproto v0.0.0-20240304214822-eeb3d13057c9 diff --git a/go.sum b/go.sum index fe9fcb115..bdc80e4c0 100644 --- a/go.sum +++ b/go.sum @@ -227,10 +227,6 @@ github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= @@ -1025,20 +1021,8 @@ google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA5 google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 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..f4a5275e0 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 @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/require" certificatesv1 "k8s.io/api/certificates/v1" + v1 "k8s.io/api/core/v1" "k8s.io/client-go/discovery" ) @@ -31,6 +32,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() @@ -44,3 +49,54 @@ func KubeServerMinorVersionInBetweenInclusive(t *testing.T, discoveryClient disc return minor >= min && minor <= max } + +func convertMap[K1, K2 comparable, V1, V2 any](m1 map[K1]V1, fT func(K1) K2, fU func(V1) V2) map[K2]V2 { + m2 := make(map[K2]V2) + for k, v := range m1 { + m2[fT(k)] = fU(v) + } + return m2 +} + +func identity[T any](t T) T { + return t +} + +func CheckServiceAccountExtraFieldsAccountingForChangesInK8s1_30[M ~map[string]V, V ~[]string]( + t *testing.T, + discoveryClient discovery.DiscoveryInterface, + actualExtras M, + expectedPodValues *v1.Pod, +) { + t.Helper() + + extra := convertMap( + actualExtras, + identity[string], + func(v V) []string { + return v + }, + ) + + require.Equal(t, extra["authentication.kubernetes.io/pod-name"], []string{expectedPodValues.Name}) + require.Equal(t, extra["authentication.kubernetes.io/pod-uid"], []string{string(expectedPodValues.UID)}) + + if KubeServerMinorVersionAtLeastInclusive(t, discoveryClient, 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(extra)) + require.NotEmpty(t, extra["authentication.kubernetes.io/credential-id"]) + require.NotEmpty(t, extra["authentication.kubernetes.io/node-name"]) + require.NotEmpty(t, extra["authentication.kubernetes.io/node-uid"]) + } else { + require.Equal(t, 2, len(extra)) + } +} diff --git a/test/integration/concierge_impersonation_proxy_test.go b/test/integration/concierge_impersonation_proxy_test.go index 7f6ae5086..8df6ad238 100644 --- a/test/integration/concierge_impersonation_proxy_test.go +++ b/test/integration/concierge_impersonation_proxy_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 @@ -258,7 +258,7 @@ func TestImpersonationProxy(t *testing.T) { //nolint:gocyclo // yeah, it's compl // Check that no load balancer has been created by the impersonator's "auto" mode. testlib.RequireNeverWithoutError(t, func() (bool, error) { return hasImpersonationProxyLoadBalancerService(ctx, env, adminClient) - }, 10*time.Second, 500*time.Millisecond) + }, 10*time.Second, 500*time.Millisecond, "there should not be a service for the impersonation proxy") // Check that we can't use the impersonation proxy to execute kubectl commands yet. _, err = impersonationProxyViaSquidKubeClientWithoutCredential(t, proxyServiceEndpoint).CoreV1().Namespaces().List(ctx, metav1.ListOptions{}) @@ -270,6 +270,9 @@ func TestImpersonationProxy(t *testing.T) { //nolint:gocyclo // yeah, it's compl ImpersonationProxy: &conciergev1alpha.ImpersonationProxySpec{ Mode: conciergev1alpha.ImpersonationProxyModeEnabled, ExternalEndpoint: proxyServiceEndpoint, + Service: conciergev1alpha.ImpersonationProxyServiceSpec{ + Type: conciergev1alpha.ImpersonationProxyServiceTypeClusterIP, + }, }, }) } @@ -277,8 +280,8 @@ func TestImpersonationProxy(t *testing.T) { //nolint:gocyclo // yeah, it's compl // At this point the impersonator should be starting/running. When it is ready, the CredentialIssuer's // strategies array should be updated to include a successful impersonation strategy which can be used // to discover the impersonator's URL and CA certificate. Until it has finished starting, it may not be included - // in the strategies array or it may be included in an error state. It can be in an error state for - // awhile when it is waiting for the load balancer to be assigned an ip/hostname. + // in the strategies array, or it may be included in an error state. It can be in an error state for + // a while when it is waiting for the load balancer to be assigned an ip/hostname. impersonationProxyURL, impersonationProxyCACertPEM := performImpersonatorDiscovery(ctx, t, env, adminClient, adminConciergeClient, refreshCredential) if !clusterSupportsLoadBalancers { // In this case, we specified the endpoint in the configmap, so check that it was reported correctly in the CredentialIssuer. @@ -1004,14 +1007,18 @@ func TestImpersonationProxy(t *testing.T) { //nolint:gocyclo // yeah, it's compl expectedWhoAmIRequestResponse( expectedUsername, expectedGroups, - map[string]identityv1alpha1.ExtraValue{ - "authentication.kubernetes.io/pod-name": {pod.Name}, - "authentication.kubernetes.io/pod-uid": {string(pod.UID)}, - }, + whoAmITokenReq.Status.KubernetesUserInfo.User.Extra, // This will be a dynamic assertion below based on the version of K8s ), whoAmITokenReq, ) + testutil.CheckServiceAccountExtraFieldsAccountingForChangesInK8s1_30[map[string]identityv1alpha1.ExtraValue]( + t, + adminClient.Discovery(), + whoAmITokenReq.Status.KubernetesUserInfo.User.Extra, + pod, + ) + // allow the test SA to create CSRs testlib.CreateTestClusterRoleBinding(t, rbacv1.Subject{Kind: rbacv1.ServiceAccountKind, Name: saName, Namespace: namespaceName}, @@ -1050,10 +1057,12 @@ func TestImpersonationProxy(t *testing.T) { //nolint:gocyclo // yeah, it's compl require.Equal(t, expectedUsername, saCSR.Spec.Username) require.Equal(t, expectedUID, saCSR.Spec.UID) require.Equal(t, expectedGroups, saCSR.Spec.Groups) - require.Equal(t, map[string]certificatesv1.ExtraValue{ - "authentication.kubernetes.io/pod-name": {pod.Name}, - "authentication.kubernetes.io/pod-uid": {string(pod.UID)}, - }, saCSR.Spec.Extra) + testutil.CheckServiceAccountExtraFieldsAccountingForChangesInK8s1_30[map[string]certificatesv1.ExtraValue]( + t, + adminClient.Discovery(), + saCSR.Spec.Extra, + pod, + ) } else { // On old Kubernetes clusters use CertificatesV1beta1 saCSR, err := impersonationProxySAClient.Kubernetes.CertificatesV1beta1().CertificateSigningRequests().Get(ctx, csrName, metav1.GetOptions{}) @@ -1064,10 +1073,12 @@ func TestImpersonationProxy(t *testing.T) { //nolint:gocyclo // yeah, it's compl require.Equal(t, expectedUsername, saCSR.Spec.Username) require.Equal(t, expectedUID, saCSR.Spec.UID) require.Equal(t, expectedGroups, saCSR.Spec.Groups) - require.Equal(t, map[string]certificatesv1beta1.ExtraValue{ - "authentication.kubernetes.io/pod-name": {pod.Name}, - "authentication.kubernetes.io/pod-uid": {string(pod.UID)}, - }, saCSR.Spec.Extra) + testutil.CheckServiceAccountExtraFieldsAccountingForChangesInK8s1_30[map[string]certificatesv1beta1.ExtraValue]( + t, + adminClient.Discovery(), + saCSR.Spec.Extra, + pod, + ) } }) @@ -2187,7 +2198,7 @@ func performImpersonatorDiscoveryURL(ctx context.Context, t *testing.T, env *tes } } } - t.Log("Did not find any impersonation proxy strategy on CredentialIssuer") + t.Log("Did not find any successful impersonation proxy strategy on CredentialIssuer") return false, nil // didn't find it, but keep trying }, 10*time.Minute, 10*time.Second) @@ -2282,6 +2293,7 @@ func updateCredentialIssuer(ctx context.Context, t *testing.T, env *testlib.Test if err != nil { return err } + spec.DeepCopyInto(&newCredentialIssuer.Spec) _, err = adminConciergeClient.ConfigV1alpha1().CredentialIssuers().Update(ctx, newCredentialIssuer, metav1.UpdateOptions{}) return err diff --git a/test/integration/concierge_whoami_test.go b/test/integration/concierge_whoami_test.go index a9e18d8c9..2b2aab212 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,20 @@ 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, ) + + testutil.CheckServiceAccountExtraFieldsAccountingForChangesInK8s1_30[map[string]identityv1alpha1.ExtraValue]( + t, + kubeClient.Discovery(), + whoAmIUser.Extra, + pod, + ) } // whoami requests are non-mutating and safe to run in parallel with serial tests, see main_test.go.