Tests use CertificatesV1 when available, otherwise use CertificatesV1beta1
CertificatesV1beta1 was removed in Kube 1.22, so the tests cannot blindly rely on it anymore. Use CertificatesV1 whenever the server reports that is available, and otherwise use the old CertificatesV1beta1. Note that CertificatesV1 was introduced in Kube 1.19.
This commit is contained in:
parent
43ba6ba686
commit
4e98c1bbdb
30
internal/testutil/kube_server_compatibility.go
Normal file
30
internal/testutil/kube_server_compatibility.go
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
// Copyright 2021 the Pinniped contributors. All Rights Reserved.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package testutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
certificatesv1 "k8s.io/api/certificates/v1"
|
||||||
|
"k8s.io/client-go/discovery"
|
||||||
|
)
|
||||||
|
|
||||||
|
func KubeServerSupportsCertificatesV1API(t *testing.T, discoveryClient discovery.DiscoveryInterface) bool {
|
||||||
|
t.Helper()
|
||||||
|
groupList, err := discoveryClient.ServerGroups()
|
||||||
|
require.NoError(t, err)
|
||||||
|
for _, group := range groupList.Groups {
|
||||||
|
if group.Name == certificatesv1.GroupName {
|
||||||
|
for _, version := range group.Versions {
|
||||||
|
if version.Version == "v1" {
|
||||||
|
// Note: v1 should exist in Kubernetes 1.19 and above
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
@ -938,6 +938,9 @@ func TestImpersonationProxy(t *testing.T) { //nolint:gocyclo // yeah, it's compl
|
|||||||
namespaceName := testlib.CreateNamespace(ctx, t, "impersonation").Name
|
namespaceName := testlib.CreateNamespace(ctx, t, "impersonation").Name
|
||||||
kubeClient := adminClient.CoreV1()
|
kubeClient := adminClient.CoreV1()
|
||||||
saName, _, saUID := createServiceAccountToken(ctx, t, adminClient, namespaceName)
|
saName, _, saUID := createServiceAccountToken(ctx, t, adminClient, namespaceName)
|
||||||
|
expectedUsername := serviceaccount.MakeUsername(namespaceName, saName)
|
||||||
|
expectedUID := string(saUID)
|
||||||
|
expectedGroups := []string{"system:serviceaccounts", "system:serviceaccounts:" + namespaceName, "system:authenticated"}
|
||||||
|
|
||||||
_, tokenRequestProbeErr := kubeClient.ServiceAccounts(namespaceName).CreateToken(ctx, saName, &authenticationv1.TokenRequest{}, metav1.CreateOptions{})
|
_, tokenRequestProbeErr := kubeClient.ServiceAccounts(namespaceName).CreateToken(ctx, saName, &authenticationv1.TokenRequest{}, metav1.CreateOptions{})
|
||||||
if k8serrors.IsNotFound(tokenRequestProbeErr) && tokenRequestProbeErr.Error() == "the server could not find the requested resource" {
|
if k8serrors.IsNotFound(tokenRequestProbeErr) && tokenRequestProbeErr.Error() == "the server could not find the requested resource" {
|
||||||
@ -1002,8 +1005,8 @@ func TestImpersonationProxy(t *testing.T) { //nolint:gocyclo // yeah, it's compl
|
|||||||
// new service account tokens include the pod info in the extra fields
|
// new service account tokens include the pod info in the extra fields
|
||||||
require.Equal(t,
|
require.Equal(t,
|
||||||
expectedWhoAmIRequestResponse(
|
expectedWhoAmIRequestResponse(
|
||||||
serviceaccount.MakeUsername(namespaceName, saName),
|
expectedUsername,
|
||||||
[]string{"system:serviceaccounts", "system:serviceaccounts:" + namespaceName, "system:authenticated"},
|
expectedGroups,
|
||||||
map[string]identityv1alpha1.ExtraValue{
|
map[string]identityv1alpha1.ExtraValue{
|
||||||
"authentication.kubernetes.io/pod-name": {pod.Name},
|
"authentication.kubernetes.io/pod-name": {pod.Name},
|
||||||
"authentication.kubernetes.io/pod-uid": {string(pod.UID)},
|
"authentication.kubernetes.io/pod-uid": {string(pod.UID)},
|
||||||
@ -1017,7 +1020,7 @@ func TestImpersonationProxy(t *testing.T) { //nolint:gocyclo // yeah, it's compl
|
|||||||
rbacv1.Subject{Kind: rbacv1.ServiceAccountKind, Name: saName, Namespace: namespaceName},
|
rbacv1.Subject{Kind: rbacv1.ServiceAccountKind, Name: saName, Namespace: namespaceName},
|
||||||
rbacv1.RoleRef{Kind: "ClusterRole", APIGroup: rbacv1.GroupName, Name: "system:node-bootstrapper"},
|
rbacv1.RoleRef{Kind: "ClusterRole", APIGroup: rbacv1.GroupName, Name: "system:node-bootstrapper"},
|
||||||
)
|
)
|
||||||
testlib.WaitForUserToHaveAccess(t, serviceaccount.MakeUsername(namespaceName, saName), []string{}, &authorizationv1.ResourceAttributes{
|
testlib.WaitForUserToHaveAccess(t, expectedUsername, []string{}, &authorizationv1.ResourceAttributes{
|
||||||
Verb: "create", Group: certificatesv1.GroupName, Version: "*", Resource: "certificatesigningrequests",
|
Verb: "create", Group: certificatesv1.GroupName, Version: "*", Resource: "certificatesigningrequests",
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -1041,20 +1044,34 @@ func TestImpersonationProxy(t *testing.T) { //nolint:gocyclo // yeah, it's compl
|
|||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
if testutil.KubeServerSupportsCertificatesV1API(t, adminClient.Discovery()) {
|
||||||
|
saCSR, err := impersonationProxySAClient.Kubernetes.CertificatesV1().CertificateSigningRequests().Get(ctx, csrName, metav1.GetOptions{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
err = adminClient.CertificatesV1().CertificateSigningRequests().Delete(ctx, csrName, metav1.DeleteOptions{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
// make sure the user info that the CSR captured matches the SA, including the UID
|
||||||
|
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)
|
||||||
|
} else {
|
||||||
|
// On old Kubernetes clusters use CertificatesV1beta1
|
||||||
saCSR, err := impersonationProxySAClient.Kubernetes.CertificatesV1beta1().CertificateSigningRequests().Get(ctx, csrName, metav1.GetOptions{})
|
saCSR, err := impersonationProxySAClient.Kubernetes.CertificatesV1beta1().CertificateSigningRequests().Get(ctx, csrName, metav1.GetOptions{})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
err = adminClient.CertificatesV1beta1().CertificateSigningRequests().Delete(ctx, csrName, metav1.DeleteOptions{})
|
err = adminClient.CertificatesV1beta1().CertificateSigningRequests().Delete(ctx, csrName, metav1.DeleteOptions{})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// make sure the user info that the CSR captured matches the SA, including the UID
|
// make sure the user info that the CSR captured matches the SA, including the UID
|
||||||
require.Equal(t, serviceaccount.MakeUsername(namespaceName, saName), saCSR.Spec.Username)
|
require.Equal(t, expectedUsername, saCSR.Spec.Username)
|
||||||
require.Equal(t, string(saUID), saCSR.Spec.UID)
|
require.Equal(t, expectedUID, saCSR.Spec.UID)
|
||||||
require.Equal(t, []string{"system:serviceaccounts", "system:serviceaccounts:" + namespaceName, "system:authenticated"}, saCSR.Spec.Groups)
|
require.Equal(t, expectedGroups, saCSR.Spec.Groups)
|
||||||
require.Equal(t, map[string]certificatesv1beta1.ExtraValue{
|
require.Equal(t, map[string]certificatesv1beta1.ExtraValue{
|
||||||
"authentication.kubernetes.io/pod-name": {pod.Name},
|
"authentication.kubernetes.io/pod-name": {pod.Name},
|
||||||
"authentication.kubernetes.io/pod-uid": {string(pod.UID)},
|
"authentication.kubernetes.io/pod-uid": {string(pod.UID)},
|
||||||
}, saCSR.Spec.Extra)
|
}, saCSR.Spec.Extra)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("kubectl as a client", func(t *testing.T) {
|
t.Run("kubectl as a client", func(t *testing.T) {
|
||||||
@ -2416,7 +2433,7 @@ func getCredForConfig(t *testing.T, config *rest.Config) *loginv1alpha1.ClusterC
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
func getUIDAndExtraViaCSR(ctx context.Context, t *testing.T, uid string, client kubernetes.Interface) (string, map[string]certificatesv1beta1.ExtraValue) {
|
func getUIDAndExtraViaCSR(ctx context.Context, t *testing.T, uid string, client kubernetes.Interface) (string, map[string][]string) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||||
@ -2439,18 +2456,43 @@ func getUIDAndExtraViaCSR(ctx context.Context, t *testing.T, uid string, client
|
|||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
outUID := uid // in the future this may not be empty on some clusters
|
||||||
|
extrasAsStrings := map[string][]string{}
|
||||||
|
|
||||||
|
if testutil.KubeServerSupportsCertificatesV1API(t, client.Discovery()) {
|
||||||
|
csReq, err := client.CertificatesV1().CertificateSigningRequests().Get(ctx, csrName, metav1.GetOptions{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = client.CertificatesV1().CertificateSigningRequests().Delete(ctx, csrName, metav1.DeleteOptions{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
if len(outUID) == 0 {
|
||||||
|
outUID = csReq.Spec.UID
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert each `ExtraValue` to `[]string` to return, so we don't have to deal with v1beta1 types versus v1 types
|
||||||
|
for k, v := range csReq.Spec.Extra {
|
||||||
|
extrasAsStrings[k] = v
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// On old Kubernetes clusters use CertificatesV1beta1
|
||||||
csReq, err := client.CertificatesV1beta1().CertificateSigningRequests().Get(ctx, csrName, metav1.GetOptions{})
|
csReq, err := client.CertificatesV1beta1().CertificateSigningRequests().Get(ctx, csrName, metav1.GetOptions{})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
err = client.CertificatesV1beta1().CertificateSigningRequests().Delete(ctx, csrName, metav1.DeleteOptions{})
|
err = client.CertificatesV1beta1().CertificateSigningRequests().Delete(ctx, csrName, metav1.DeleteOptions{})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
outUID := uid // in the future this may not be empty on some clusters
|
|
||||||
if len(outUID) == 0 {
|
if len(outUID) == 0 {
|
||||||
outUID = csReq.Spec.UID
|
outUID = csReq.Spec.UID
|
||||||
}
|
}
|
||||||
|
|
||||||
return outUID, csReq.Spec.Extra
|
// Convert each `ExtraValue` to `[]string` to return, so we don't have to deal with v1beta1 types versus v1 types
|
||||||
|
for k, v := range csReq.Spec.Extra {
|
||||||
|
extrasAsStrings[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return outUID, extrasAsStrings
|
||||||
}
|
}
|
||||||
|
|
||||||
func parallelIfNotEKS(t *testing.T) {
|
func parallelIfNotEKS(t *testing.T) {
|
||||||
|
@ -26,6 +26,7 @@ import (
|
|||||||
"k8s.io/client-go/util/keyutil"
|
"k8s.io/client-go/util/keyutil"
|
||||||
|
|
||||||
identityv1alpha1 "go.pinniped.dev/generated/latest/apis/concierge/identity/v1alpha1"
|
identityv1alpha1 "go.pinniped.dev/generated/latest/apis/concierge/identity/v1alpha1"
|
||||||
|
"go.pinniped.dev/internal/testutil"
|
||||||
"go.pinniped.dev/test/testlib"
|
"go.pinniped.dev/test/testlib"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -281,13 +282,37 @@ func TestWhoAmI_CSR_Parallel(t *testing.T) {
|
|||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
useCertificatesV1API := testutil.KubeServerSupportsCertificatesV1API(t, kubeClient.Discovery())
|
||||||
|
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
|
if useCertificatesV1API {
|
||||||
|
require.NoError(t, kubeClient.CertificatesV1().CertificateSigningRequests().
|
||||||
|
Delete(context.Background(), csrName, metav1.DeleteOptions{}))
|
||||||
|
} else {
|
||||||
|
// On old clusters use v1beta1
|
||||||
require.NoError(t, kubeClient.CertificatesV1beta1().CertificateSigningRequests().
|
require.NoError(t, kubeClient.CertificatesV1beta1().CertificateSigningRequests().
|
||||||
Delete(context.Background(), csrName, metav1.DeleteOptions{}))
|
Delete(context.Background(), csrName, metav1.DeleteOptions{}))
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// this is a blind update with no resource version checks, which is only safe during tests
|
if useCertificatesV1API {
|
||||||
// use the beta CSR API to support older clusters
|
_, err = kubeClient.CertificatesV1().CertificateSigningRequests().UpdateApproval(ctx, csrName, &certificatesv1.CertificateSigningRequest{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: csrName,
|
||||||
|
},
|
||||||
|
Status: certificatesv1.CertificateSigningRequestStatus{
|
||||||
|
Conditions: []certificatesv1.CertificateSigningRequestCondition{
|
||||||
|
{
|
||||||
|
Type: certificatesv1.CertificateApproved,
|
||||||
|
Status: corev1.ConditionTrue,
|
||||||
|
Reason: "WhoAmICSRTest",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, metav1.UpdateOptions{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
} else {
|
||||||
|
// On old Kubernetes clusters use CertificatesV1beta1
|
||||||
_, err = kubeClient.CertificatesV1beta1().CertificateSigningRequests().UpdateApproval(ctx, &certificatesv1beta1.CertificateSigningRequest{
|
_, err = kubeClient.CertificatesV1beta1().CertificateSigningRequests().UpdateApproval(ctx, &certificatesv1beta1.CertificateSigningRequest{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: csrName,
|
Name: csrName,
|
||||||
@ -303,6 +328,7 @@ func TestWhoAmI_CSR_Parallel(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}, metav1.UpdateOptions{})
|
}, metav1.UpdateOptions{})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
crtPEM, err := csr.WaitForCertificate(ctx, kubeClient, csrName, csrUID)
|
crtPEM, err := csr.WaitForCertificate(ctx, kubeClient, csrName, csrUID)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
Loading…
Reference in New Issue
Block a user