Merge pull request #848 from vmware-tanzu/tests_use_certificatesv1

Tests use CertificatesV1 when available, otherwise use CertificatesV1beta1
This commit is contained in:
Mo Khan 2021-09-21 12:13:22 -04:00 committed by GitHub
commit 933697f045
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 142 additions and 44 deletions

View 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
}

View File

@ -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) {

View File

@ -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)