From c61299e8e8afb51e37540f7e4fd031328d8e87bc Mon Sep 17 00:00:00 2001 From: rambohe Date: Mon, 27 May 2024 20:07:45 +0800 Subject: [PATCH] improve csr approver controller (#2058) --- .../csrapprover/csrapprover_controller.go | 18 +- .../csrapprover_controller_test.go | 611 ++++++++++++++---- 2 files changed, 494 insertions(+), 135 deletions(-) diff --git a/pkg/yurtmanager/controller/csrapprover/csrapprover_controller.go b/pkg/yurtmanager/controller/csrapprover/csrapprover_controller.go index bf83a9a9f54..4561e2911ee 100644 --- a/pkg/yurtmanager/controller/csrapprover/csrapprover_controller.go +++ b/pkg/yurtmanager/controller/csrapprover/csrapprover_controller.go @@ -27,10 +27,8 @@ import ( certificatesv1beta1 "k8s.io/api/certificates/v1beta1" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apiserver/pkg/authentication/user" - "k8s.io/client-go/kubernetes" "k8s.io/klog/v2" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" @@ -120,20 +118,13 @@ var _ reconcile.Reconciler = &ReconcileCsrApprover{} // ReconcileCsrApprover reconciles a CsrApprover object type ReconcileCsrApprover struct { client.Client - csrV1Supported bool - csrApproverClient kubernetes.Interface + csrV1Supported bool } func NewReconcileCsrApprover(mgr manager.Manager) (*ReconcileCsrApprover, error) { r := &ReconcileCsrApprover{ Client: mgr.GetClient(), } - client, err := kubernetes.NewForConfig(mgr.GetConfig()) - if err != nil { - klog.Errorf("could not create kube client, %v", err) - return nil, err - } - r.csrApproverClient = client mapper := mgr.GetRESTMapper() if gvk, err := mapper.KindFor(csrV1Resource); err != nil { @@ -211,14 +202,13 @@ func (r *ReconcileCsrApprover) Reconcile(ctx context.Context, request reconcile. } // updateApproval is used for adding approval info into csr resource -func (r *ReconcileCsrApprover) updateApproval(ctx context.Context, csr *certificatesv1.CertificateSigningRequest) (err error) { +func (r *ReconcileCsrApprover) updateApproval(ctx context.Context, csr *certificatesv1.CertificateSigningRequest) error { if r.csrV1Supported { - _, err = r.csrApproverClient.CertificatesV1().CertificateSigningRequests().UpdateApproval(ctx, csr.Name, csr, metav1.UpdateOptions{}) + return r.SubResource("approval").Update(ctx, csr, &client.SubResourceUpdateOptions{}) } else { v1beta1Csr := v1Csr2v1beta1Csr(csr) - _, err = r.csrApproverClient.CertificatesV1beta1().CertificateSigningRequests().UpdateApproval(ctx, v1beta1Csr, metav1.UpdateOptions{}) + return r.SubResource("approval").Update(ctx, v1beta1Csr, &client.SubResourceUpdateOptions{}) } - return } // isYurtCSR checks if given csr is an openyurt related csr and diff --git a/pkg/yurtmanager/controller/csrapprover/csrapprover_controller_test.go b/pkg/yurtmanager/controller/csrapprover/csrapprover_controller_test.go index 5fd1d5990ad..5c8e14beba7 100644 --- a/pkg/yurtmanager/controller/csrapprover/csrapprover_controller_test.go +++ b/pkg/yurtmanager/controller/csrapprover/csrapprover_controller_test.go @@ -1,5 +1,5 @@ /* -Copyright 2020 The OpenYurt Authors. +Copyright 2024 The OpenYurt Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ limitations under the License. package csrapprover import ( + "context" "crypto/ecdsa" "crypto/elliptic" cryptorand "crypto/rand" @@ -24,51 +25,157 @@ import ( "crypto/x509/pkix" "encoding/pem" "net" + "reflect" "testing" certificatesv1 "k8s.io/api/certificates/v1" + certificatesv1beta1 "k8s.io/api/certificates/v1beta1" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" "k8s.io/apiserver/pkg/authentication/user" + clientgoscheme "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/util/cert" "k8s.io/klog/v2" + "sigs.k8s.io/controller-runtime/pkg/client" + fakeclient "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/reconcile" "github.com/openyurtio/openyurt/pkg/yurthub/certificate/token" "github.com/openyurtio/openyurt/pkg/yurttunnel/constants" ) -func TestIsYurtCSR(t *testing.T) { - tests := []struct { - desc string - csr *certificatesv1.CertificateSigningRequest - exp bool +func TestReconcile(t *testing.T) { + v1beta1SignerName := certificatesv1beta1.KubeAPIServerClientSignerName + csrData := newCSRData("system:node:xxx", []string{token.YurtHubCSROrg, user.NodesGroup, "unknown org"}, []string{}, []net.IP{}) + testcases := map[string]struct { + obj runtime.Object + csrV1Supported bool + skipRequest bool + expectedObj runtime.Object }{ - { - desc: "is not certificate request", - csr: &certificatesv1.CertificateSigningRequest{ + "yurthub server related certificate request": { + obj: &certificatesv1.CertificateSigningRequest{ + ObjectMeta: metav1.ObjectMeta{ + Name: "server-csr", + Namespace: "default", + }, Spec: certificatesv1.CertificateSigningRequestSpec{ - Request: pem.EncodeToMemory( - &pem.Block{ - Type: "PUBLIC KEY", - }), + SignerName: certificatesv1.KubeletServingSignerName, + Usages: []certificatesv1.KeyUsage{ + certificatesv1.UsageDigitalSignature, + certificatesv1.UsageKeyEncipherment, + certificatesv1.UsageServerAuth, + }, + Request: newCSRData("system:node:xxx", []string{user.NodesGroup}, []string{}, []net.IP{net.ParseIP("127.0.0.1")}), }, }, - exp: false, - }, - { - desc: "can not parse certificate request", - csr: &certificatesv1.CertificateSigningRequest{ + csrV1Supported: true, + skipRequest: true, + expectedObj: &certificatesv1.CertificateSigningRequest{ + TypeMeta: metav1.TypeMeta{ + Kind: "CertificateSigningRequest", + APIVersion: "certificates.k8s.io/v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "server-csr", + Namespace: "default", + }, Spec: certificatesv1.CertificateSigningRequestSpec{ - Request: pem.EncodeToMemory( - &pem.Block{ - Type: "CERTIFICATE REQUEST", - Bytes: []byte(`MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAlRuRnThUjU8/prwYxbty`), - }), + SignerName: certificatesv1.KubeletServingSignerName, + Usages: []certificatesv1.KeyUsage{ + certificatesv1.UsageDigitalSignature, + certificatesv1.UsageKeyEncipherment, + certificatesv1.UsageServerAuth, + }, + // don't compare CSR data + Request: []byte{}, + }, + Status: certificatesv1.CertificateSigningRequestStatus{ + Conditions: []certificatesv1.CertificateSigningRequestCondition{ + { + Type: certificatesv1.CertificateApproved, + Status: corev1.ConditionTrue, + Reason: "AutoApproved", + Message: "Auto approving openyurt tls server certificate", + }, + }, + }, + }, + }, + "yurthub node client related certificate request": { + obj: &certificatesv1beta1.CertificateSigningRequest{ + ObjectMeta: metav1.ObjectMeta{ + Name: "node-client-csr", + Namespace: "default", + }, + Spec: certificatesv1beta1.CertificateSigningRequestSpec{ + SignerName: &v1beta1SignerName, + Usages: []certificatesv1beta1.KeyUsage{ + certificatesv1beta1.UsageDigitalSignature, + certificatesv1beta1.UsageKeyEncipherment, + certificatesv1beta1.UsageClientAuth, + }, + Request: newCSRData("system:node:xxx", []string{token.YurtHubCSROrg, user.NodesGroup, "openyurt:tenant:xxx"}, []string{}, []net.IP{}), + }, + Status: certificatesv1beta1.CertificateSigningRequestStatus{ + Conditions: []certificatesv1beta1.CertificateSigningRequestCondition{ + { + Type: certificatesv1beta1.RequestConditionType("test"), + Status: corev1.ConditionTrue, + Reason: "test", + Message: "test", + }, + }, + }, + }, + csrV1Supported: false, + skipRequest: true, + expectedObj: &certificatesv1beta1.CertificateSigningRequest{ + TypeMeta: metav1.TypeMeta{ + Kind: "CertificateSigningRequest", + APIVersion: "certificates.k8s.io/v1beta1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "node-client-csr", + Namespace: "default", + }, + Spec: certificatesv1beta1.CertificateSigningRequestSpec{ + SignerName: &v1beta1SignerName, + Usages: []certificatesv1beta1.KeyUsage{ + certificatesv1beta1.UsageDigitalSignature, + certificatesv1beta1.UsageKeyEncipherment, + certificatesv1beta1.UsageClientAuth, + }, + // don't compare CSR data + Request: []byte{}, + }, + Status: certificatesv1beta1.CertificateSigningRequestStatus{ + Conditions: []certificatesv1beta1.CertificateSigningRequestCondition{ + { + Type: certificatesv1beta1.RequestConditionType("test"), + Status: corev1.ConditionTrue, + Reason: "test", + Message: "test", + }, + { + Type: certificatesv1beta1.CertificateApproved, + Status: corev1.ConditionTrue, + Reason: "AutoApproved", + Message: "Auto approving yurthub node client certificate", + }, + }, }, }, - exp: false, }, - { - desc: "is yurthub server related certificate request", - csr: &certificatesv1.CertificateSigningRequest{ + "yurt-tunnel server related tls server certificate request": { + obj: &certificatesv1.CertificateSigningRequest{ + ObjectMeta: metav1.ObjectMeta{ + Name: "tunnel-server-csr", + Namespace: "default", + }, Spec: certificatesv1.CertificateSigningRequestSpec{ SignerName: certificatesv1.KubeletServingSignerName, Usages: []certificatesv1.KeyUsage{ @@ -76,14 +183,48 @@ func TestIsYurtCSR(t *testing.T) { certificatesv1.UsageKeyEncipherment, certificatesv1.UsageServerAuth, }, - Request: newCSRData("system:node:xxx", []string{user.NodesGroup}, []string{}, []net.IP{net.ParseIP("127.0.0.1")}), + Request: newCSRData("system:node:xxx", []string{user.NodesGroup}, []string{"x-tunnel-server-svc"}, []net.IP{net.ParseIP("127.0.0.1")}), + }, + }, + csrV1Supported: true, + skipRequest: true, + expectedObj: &certificatesv1.CertificateSigningRequest{ + TypeMeta: metav1.TypeMeta{ + Kind: "CertificateSigningRequest", + APIVersion: "certificates.k8s.io/v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "tunnel-server-csr", + Namespace: "default", + }, + Spec: certificatesv1.CertificateSigningRequestSpec{ + SignerName: certificatesv1.KubeletServingSignerName, + Usages: []certificatesv1.KeyUsage{ + certificatesv1.UsageDigitalSignature, + certificatesv1.UsageKeyEncipherment, + certificatesv1.UsageServerAuth, + }, + // don't compare CSR data + Request: []byte{}, + }, + Status: certificatesv1.CertificateSigningRequestStatus{ + Conditions: []certificatesv1.CertificateSigningRequestCondition{ + { + Type: certificatesv1.CertificateApproved, + Status: corev1.ConditionTrue, + Reason: "AutoApproved", + Message: "Auto approving openyurt tls server certificate", + }, + }, }, }, - exp: true, }, - { - desc: "is yurthub node client related certificate request", - csr: &certificatesv1.CertificateSigningRequest{ + "yurt-tunnel-server related proxy client certificate request": { + obj: &certificatesv1.CertificateSigningRequest{ + ObjectMeta: metav1.ObjectMeta{ + Name: "tunnel-server-proxy-client-csr", + Namespace: "default", + }, Spec: certificatesv1.CertificateSigningRequestSpec{ SignerName: certificatesv1.KubeAPIServerClientSignerName, Usages: []certificatesv1.KeyUsage{ @@ -91,14 +232,20 @@ func TestIsYurtCSR(t *testing.T) { certificatesv1.UsageKeyEncipherment, certificatesv1.UsageClientAuth, }, - Request: newCSRData("system:node:xxx", []string{token.YurtHubCSROrg, user.NodesGroup, "openyurt:tenant:xxx"}, []string{}, []net.IP{}), + Request: newCSRData(constants.YurtTunnelProxyClientCSRCN, []string{constants.YurtTunnelCSROrg}, []string{}, []net.IP{}), }, }, - exp: true, - }, - { - desc: "yurthub node client csr with unknown org", - csr: &certificatesv1.CertificateSigningRequest{ + csrV1Supported: true, + skipRequest: true, + expectedObj: &certificatesv1.CertificateSigningRequest{ + TypeMeta: metav1.TypeMeta{ + Kind: "CertificateSigningRequest", + APIVersion: "certificates.k8s.io/v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "tunnel-server-proxy-client-csr", + Namespace: "default", + }, Spec: certificatesv1.CertificateSigningRequestSpec{ SignerName: certificatesv1.KubeAPIServerClientSignerName, Usages: []certificatesv1.KeyUsage{ @@ -106,29 +253,48 @@ func TestIsYurtCSR(t *testing.T) { certificatesv1.UsageKeyEncipherment, certificatesv1.UsageClientAuth, }, - Request: newCSRData("system:node:xxx", []string{token.YurtHubCSROrg, user.NodesGroup, "unknown org"}, []string{}, []net.IP{}), + // don't compare CSR data + Request: []byte{}, + }, + Status: certificatesv1.CertificateSigningRequestStatus{ + Conditions: []certificatesv1.CertificateSigningRequestCondition{ + { + Type: certificatesv1.CertificateApproved, + Status: corev1.ConditionTrue, + Reason: "AutoApproved", + Message: "Auto approving tunnel-server proxy client certificate", + }, + }, }, }, - exp: false, }, - { - desc: "is yurt-tunnel server related tls server certificate request", - csr: &certificatesv1.CertificateSigningRequest{ + "yurt-tunnel-agent related certificate request": { + obj: &certificatesv1.CertificateSigningRequest{ + ObjectMeta: metav1.ObjectMeta{ + Name: "tunnel-agent-client-csr", + Namespace: "default", + }, Spec: certificatesv1.CertificateSigningRequestSpec{ - SignerName: certificatesv1.KubeletServingSignerName, + SignerName: certificatesv1.KubeAPIServerClientSignerName, Usages: []certificatesv1.KeyUsage{ certificatesv1.UsageDigitalSignature, certificatesv1.UsageKeyEncipherment, - certificatesv1.UsageServerAuth, + certificatesv1.UsageClientAuth, }, - Request: newCSRData("system:node:xxx", []string{user.NodesGroup}, []string{"x-tunnel-server-svc"}, []net.IP{net.ParseIP("127.0.0.1")}), + Request: newCSRData(constants.YurtTunnelAgentCSRCN, []string{constants.YurtTunnelCSROrg}, []string{"node-name-xxx"}, []net.IP{net.ParseIP("127.0.0.1")}), }, }, - exp: true, - }, - { - desc: "is yurt-tunnel-server related proxy client certificate request", - csr: &certificatesv1.CertificateSigningRequest{ + csrV1Supported: true, + skipRequest: true, + expectedObj: &certificatesv1.CertificateSigningRequest{ + TypeMeta: metav1.TypeMeta{ + Kind: "CertificateSigningRequest", + APIVersion: "certificates.k8s.io/v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "tunnel-agent-client-csr", + Namespace: "default", + }, Spec: certificatesv1.CertificateSigningRequestSpec{ SignerName: certificatesv1.KubeAPIServerClientSignerName, Usages: []certificatesv1.KeyUsage{ @@ -136,14 +302,91 @@ func TestIsYurtCSR(t *testing.T) { certificatesv1.UsageKeyEncipherment, certificatesv1.UsageClientAuth, }, - Request: newCSRData(constants.YurtTunnelProxyClientCSRCN, []string{constants.YurtTunnelCSROrg}, []string{}, []net.IP{}), + // don't compare CSR data + Request: []byte{}, + }, + Status: certificatesv1.CertificateSigningRequestStatus{ + Conditions: []certificatesv1.CertificateSigningRequestCondition{ + { + Type: certificatesv1.CertificateApproved, + Status: corev1.ConditionTrue, + Reason: "AutoApproved", + Message: "Auto approving tunnel-agent client certificate", + }, + }, }, }, - exp: true, }, - { - desc: "is yurt-tunnel-agent related certificate request", - csr: &certificatesv1.CertificateSigningRequest{ + "it is not a certificate request": { + obj: &certificatesv1.CertificateSigningRequest{ + ObjectMeta: metav1.ObjectMeta{ + Name: "server-csr", + Namespace: "default", + }, + Spec: certificatesv1.CertificateSigningRequestSpec{ + Request: pem.EncodeToMemory( + &pem.Block{ + Type: "PUBLIC KEY", + }), + }, + }, + csrV1Supported: true, + expectedObj: &certificatesv1.CertificateSigningRequest{ + TypeMeta: metav1.TypeMeta{ + Kind: "CertificateSigningRequest", + APIVersion: "certificates.k8s.io/v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "server-csr", + Namespace: "default", + }, + Spec: certificatesv1.CertificateSigningRequestSpec{ + Request: pem.EncodeToMemory( + &pem.Block{ + Type: "PUBLIC KEY", + }), + }, + }, + }, + "can not parse certificate request": { + obj: &certificatesv1.CertificateSigningRequest{ + ObjectMeta: metav1.ObjectMeta{ + Name: "server-csr", + Namespace: "default", + }, + Spec: certificatesv1.CertificateSigningRequestSpec{ + Request: pem.EncodeToMemory( + &pem.Block{ + Type: "CERTIFICATE REQUEST", + Bytes: []byte(`MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAlRuRnThUjU8/prwYxbty`), + }), + }, + }, + csrV1Supported: true, + expectedObj: &certificatesv1.CertificateSigningRequest{ + TypeMeta: metav1.TypeMeta{ + Kind: "CertificateSigningRequest", + APIVersion: "certificates.k8s.io/v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "server-csr", + Namespace: "default", + }, + Spec: certificatesv1.CertificateSigningRequestSpec{ + Request: pem.EncodeToMemory( + &pem.Block{ + Type: "CERTIFICATE REQUEST", + Bytes: []byte(`MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAlRuRnThUjU8/prwYxbty`), + }), + }, + }, + }, + "yurthub node client csr with unknown org": { + obj: &certificatesv1.CertificateSigningRequest{ + ObjectMeta: metav1.ObjectMeta{ + Name: "server-csr", + Namespace: "default", + }, Spec: certificatesv1.CertificateSigningRequestSpec{ SignerName: certificatesv1.KubeAPIServerClientSignerName, Usages: []certificatesv1.KeyUsage{ @@ -151,88 +394,214 @@ func TestIsYurtCSR(t *testing.T) { certificatesv1.UsageKeyEncipherment, certificatesv1.UsageClientAuth, }, - Request: newCSRData(constants.YurtTunnelAgentCSRCN, []string{constants.YurtTunnelCSROrg}, []string{"node-name-xxx"}, []net.IP{net.ParseIP("127.0.0.1")}), + Request: csrData, }, }, - exp: true, - }, - } - - for _, tt := range tests { - t.Run(tt.desc, func(t *testing.T) { - act, msg := isYurtCSR(tt.csr) - if act != tt.exp { - t.Errorf("the value we want is %v, but the actual value is %v, and the message: %s", tt.exp, act, msg) - } - }) - } -} - -func TestCheckCertApprovalCondition(t *testing.T) { - tests := []struct { - desc string - status *certificatesv1.CertificateSigningRequestStatus - exp struct { - approved bool - denied bool - } - }{ - { - desc: "approved", - status: &certificatesv1.CertificateSigningRequestStatus{ - Conditions: []certificatesv1.CertificateSigningRequestCondition{ - { - Type: certificatesv1.CertificateApproved, + csrV1Supported: true, + expectedObj: &certificatesv1.CertificateSigningRequest{ + TypeMeta: metav1.TypeMeta{ + Kind: "CertificateSigningRequest", + APIVersion: "certificates.k8s.io/v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "server-csr", + Namespace: "default", + }, + Spec: certificatesv1.CertificateSigningRequestSpec{ + SignerName: certificatesv1.KubeAPIServerClientSignerName, + Usages: []certificatesv1.KeyUsage{ + certificatesv1.UsageDigitalSignature, + certificatesv1.UsageKeyEncipherment, + certificatesv1.UsageClientAuth, }, + Request: csrData, }, + Status: certificatesv1.CertificateSigningRequestStatus{}, }, - exp: struct { - approved bool - denied bool - }{approved: true, denied: false}, }, - { - desc: "denied", - status: &certificatesv1.CertificateSigningRequestStatus{ - Conditions: []certificatesv1.CertificateSigningRequestCondition{ - { - Type: certificatesv1.CertificateDenied, + "csr has already been approved": { + obj: &certificatesv1.CertificateSigningRequest{ + ObjectMeta: metav1.ObjectMeta{ + Name: "server-csr", + Namespace: "default", + }, + Spec: certificatesv1.CertificateSigningRequestSpec{ + SignerName: certificatesv1.KubeletServingSignerName, + Usages: []certificatesv1.KeyUsage{ + certificatesv1.UsageDigitalSignature, + certificatesv1.UsageKeyEncipherment, + certificatesv1.UsageServerAuth, + }, + Request: newCSRData("system:node:xxx", []string{user.NodesGroup}, []string{}, []net.IP{net.ParseIP("127.0.0.1")}), + }, + Status: certificatesv1.CertificateSigningRequestStatus{ + Conditions: []certificatesv1.CertificateSigningRequestCondition{ + { + Type: certificatesv1.CertificateApproved, + Status: corev1.ConditionTrue, + Reason: "init approved", + Message: "csr has already been approved", + }, + }, + }, + }, + csrV1Supported: true, + skipRequest: true, + expectedObj: &certificatesv1.CertificateSigningRequest{ + TypeMeta: metav1.TypeMeta{ + Kind: "CertificateSigningRequest", + APIVersion: "certificates.k8s.io/v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "server-csr", + Namespace: "default", + }, + Spec: certificatesv1.CertificateSigningRequestSpec{ + SignerName: certificatesv1.KubeletServingSignerName, + Usages: []certificatesv1.KeyUsage{ + certificatesv1.UsageDigitalSignature, + certificatesv1.UsageKeyEncipherment, + certificatesv1.UsageServerAuth, + }, + // don't compare CSR data + Request: []byte{}, + }, + Status: certificatesv1.CertificateSigningRequestStatus{ + Conditions: []certificatesv1.CertificateSigningRequestCondition{ + { + Type: certificatesv1.CertificateApproved, + Status: corev1.ConditionTrue, + Reason: "init approved", + Message: "csr has already been approved", + }, }, }, }, - exp: struct { - approved bool - denied bool - }{approved: false, denied: true}, }, - { - desc: "approved and denied", - status: &certificatesv1.CertificateSigningRequestStatus{ - Conditions: []certificatesv1.CertificateSigningRequestCondition{ - { - Type: certificatesv1.CertificateApproved, + "csr has already been denied": { + obj: &certificatesv1.CertificateSigningRequest{ + ObjectMeta: metav1.ObjectMeta{ + Name: "server-csr", + Namespace: "default", + }, + Spec: certificatesv1.CertificateSigningRequestSpec{ + SignerName: certificatesv1.KubeletServingSignerName, + Usages: []certificatesv1.KeyUsage{ + certificatesv1.UsageDigitalSignature, + certificatesv1.UsageKeyEncipherment, + certificatesv1.UsageServerAuth, }, - { - Type: certificatesv1.CertificateDenied, + Request: newCSRData("system:node:xxx", []string{user.NodesGroup}, []string{}, []net.IP{net.ParseIP("127.0.0.1")}), + }, + Status: certificatesv1.CertificateSigningRequestStatus{ + Conditions: []certificatesv1.CertificateSigningRequestCondition{ + { + Type: certificatesv1.CertificateDenied, + Status: corev1.ConditionTrue, + Reason: "init denied", + Message: "csr has already been denied", + }, + }, + }, + }, + csrV1Supported: true, + skipRequest: true, + expectedObj: &certificatesv1.CertificateSigningRequest{ + TypeMeta: metav1.TypeMeta{ + Kind: "CertificateSigningRequest", + APIVersion: "certificates.k8s.io/v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "server-csr", + Namespace: "default", + }, + Spec: certificatesv1.CertificateSigningRequestSpec{ + SignerName: certificatesv1.KubeletServingSignerName, + Usages: []certificatesv1.KeyUsage{ + certificatesv1.UsageDigitalSignature, + certificatesv1.UsageKeyEncipherment, + certificatesv1.UsageServerAuth, + }, + // don't compare CSR data + Request: []byte{}, + }, + Status: certificatesv1.CertificateSigningRequestStatus{ + Conditions: []certificatesv1.CertificateSigningRequestCondition{ + { + Type: certificatesv1.CertificateDenied, + Status: corev1.ConditionTrue, + Reason: "init denied", + Message: "csr has already been denied", + }, }, }, }, - exp: struct { - approved bool - denied bool - }{approved: true, denied: true}, }, } - for _, tt := range tests { - t.Run(tt.desc, func(t *testing.T) { - actApproved, actDenied := checkCertApprovalCondition(tt.status) - act := struct { - approved bool - denied bool - }{approved: actApproved, denied: actDenied} - if act != tt.exp { - t.Errorf("the value we want is %+v, but the actual value is %+v", tt.exp, act) + scheme := runtime.NewScheme() + if err := clientgoscheme.AddToScheme(scheme); err != nil { + t.Fatal("Fail to add kubernetes clint-go custom resource") + } + + for k, tc := range testcases { + t.Run(k, func(t *testing.T) { + obj := tc.obj.DeepCopyObject() + accessor := meta.NewAccessor() + name, _ := accessor.Name(obj) + ns, _ := accessor.Namespace(obj) + req := reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: name, + Namespace: ns, + }, + } + c := fakeclient.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(tc.obj).WithStatusSubresource([]client.Object{&certificatesv1beta1.CertificateSigningRequest{}}...).Build() + csrApproverController := &ReconcileCsrApprover{ + Client: c, + csrV1Supported: tc.csrV1Supported, + } + + _, err := csrApproverController.Reconcile(context.Background(), req) + if err != nil { + t.Errorf("expect an error, but got no error") + } else { + if err != nil { + t.Errorf("expect no error, but got an error, %v", err) + return + } + + if tc.csrV1Supported { + v1Instance := &certificatesv1.CertificateSigningRequest{} + err = csrApproverController.Get(context.Background(), req.NamespacedName, v1Instance) + if err != nil { + t.Errorf("couldn't get csr v1 instance, %v", err) + return + } + // clear resource version and csr data + accessor.SetResourceVersion(v1Instance, "") + if tc.skipRequest { + v1Instance.Spec.Request = []byte{} + } + if !reflect.DeepEqual(v1Instance, tc.expectedObj) { + t.Errorf("expect object %#+v\n, but got %#+v\n", tc.expectedObj, v1Instance) + } + } else { + v1beta1Instance := &certificatesv1beta1.CertificateSigningRequest{} + err = csrApproverController.Get(context.Background(), req.NamespacedName, v1beta1Instance) + if err != nil { + t.Errorf("couldn't get csr, %v", err) + return + } + // clear resource version and csr data + accessor.SetResourceVersion(v1beta1Instance, "") + if tc.skipRequest { + v1beta1Instance.Spec.Request = []byte{} + } + if !reflect.DeepEqual(v1beta1Instance, tc.expectedObj) { + t.Errorf("expect object %#+v\n, but got %#+v\n", tc.expectedObj, v1beta1Instance) + } + } } }) }