From 62b6f6bf2b59a5cf614fcaa0fae12d011cb05649 Mon Sep 17 00:00:00 2001 From: Mohamed Awnallah Date: Sun, 29 Sep 2024 12:29:38 +0300 Subject: [PATCH] operator/pkg: unit test cert tasks Signed-off-by: Mohamed Awnallah --- operator/pkg/tasks/init/cert.go | 2 +- operator/pkg/tasks/init/cert_test.go | 427 +++++++++++++++++++++++++++ 2 files changed, 428 insertions(+), 1 deletion(-) create mode 100644 operator/pkg/tasks/init/cert_test.go diff --git a/operator/pkg/tasks/init/cert.go b/operator/pkg/tasks/init/cert.go index 3530df0884e4..f48dfe1da72c 100644 --- a/operator/pkg/tasks/init/cert.go +++ b/operator/pkg/tasks/init/cert.go @@ -127,7 +127,7 @@ func runCertTask(cc, caCert *certs.CertConfig) func(d workflow.RunData) error { } if cc.CAName != caCert.Name { - return fmt.Errorf("expected CAname for %s, but was %s", cc.CAName, cc.Name) + return fmt.Errorf("mismatched CA name: expected %s but got %s", cc.CAName, caCert.Name) } if err := mutateCertConfig(data, cc); err != nil { diff --git a/operator/pkg/tasks/init/cert_test.go b/operator/pkg/tasks/init/cert_test.go new file mode 100644 index 000000000000..b66a01707c47 --- /dev/null +++ b/operator/pkg/tasks/init/cert_test.go @@ -0,0 +1,427 @@ +/* +Copyright 2024 The Karmada Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package tasks + +import ( + "context" + "crypto/x509" + "fmt" + "strings" + "testing" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + fakeclientset "k8s.io/client-go/kubernetes/fake" + + "github.com/karmada-io/karmada/operator/pkg/apis/operator/v1alpha1" + "github.com/karmada-io/karmada/operator/pkg/certs" + "github.com/karmada-io/karmada/operator/pkg/constants" + "github.com/karmada-io/karmada/operator/pkg/util" + "github.com/karmada-io/karmada/operator/pkg/workflow" +) + +func TestNewCertTask(t *testing.T) { + tests := []struct { + name string + wantTask workflow.Task + }{ + { + name: "TestNewCertTask_IsCalled_ExpectedWorkflowTask", + wantTask: workflow.Task{ + Name: "Certs", + Run: runCerts, + Skip: skipCerts, + RunSubTasks: true, + Tasks: newCertSubTasks(), + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + certTask := NewCertTask() + err := util.DeepEqualTasks(certTask, test.wantTask) + if err != nil { + t.Errorf("unexpected error, got %v", err) + } + }) + } +} + +func TestRunCerts(t *testing.T) { + tests := []struct { + name string + runData workflow.RunData + wantErr bool + errMsg string + }{ + { + name: "RunCerts_InvalidTypeAssertion_TypeAssertionFailed", + runData: MyTestData{Data: "test"}, + wantErr: true, + errMsg: "certs task invoked with an invalid data struct", + }, + { + name: "RunCerts_ValidTypeAssertion_TypeAssertionSuceeded", + runData: &TestInitData{}, + wantErr: false, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err := runCerts(test.runData) + if err == nil && test.wantErr { + t.Errorf("expected error, but got none") + } + if err != nil && !test.wantErr { + t.Errorf("unexpected error: %v", err) + } + if err != nil && test.wantErr && !strings.Contains(err.Error(), test.errMsg) { + t.Errorf("expected %s error msg to contain %s", err.Error(), test.errMsg) + } + }) + } +} + +func TestSkipCerts(t *testing.T) { + client := fakeclientset.NewSimpleClientset() + tests := []struct { + name string + runData workflow.RunData + prep func() error + cleanup func() error + wantErr bool + wantSkip bool + errMsg string + }{ + { + name: "SkipCerts_InvalidTypeAssertion_TypeAssertionFailed", + runData: MyTestData{Data: "test"}, + prep: func() error { return nil }, + cleanup: func() error { return nil }, + wantErr: true, + wantSkip: false, + errMsg: "certs task invoked with an invalid data struct", + }, + { + name: "SkipCerts_ValidTypeAssertion_TypeAssertionSuceeded", + runData: &TestInitData{ + Name: "karmada-demo", + Namespace: "test", + RemoteClientConnector: client, + }, + prep: func() error { return nil }, + cleanup: func() error { return nil }, + wantErr: false, + wantSkip: false, + }, + { + name: "SkipCerts_WithEmptySecretData_ErrorReturned", + runData: &TestInitData{ + Name: "karmada-demo", + Namespace: "test", + RemoteClientConnector: client, + }, + prep: func() error { + _, err := client.CoreV1().Secrets("test").Create( + context.TODO(), &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: util.KarmadaCertSecretName("karmada-demo"), + Namespace: "test", + }, + Data: map[string][]byte{}, + }, + metav1.CreateOptions{}, + ) + return err + }, + cleanup: func() error { + err := client.CoreV1().Secrets("test").Delete( + context.TODO(), util.KarmadaCertSecretName("karmada-demo"), + metav1.DeleteOptions{}, + ) + if err != nil { + return fmt.Errorf("failed to delete %s secret", "test") + } + return nil + }, + wantErr: true, + wantSkip: false, + }, + { + name: "SkipCerts_SecertCertDataExist_CertsSkipped", + runData: &TestInitData{ + Name: "karmada-demo", + Namespace: "test", + RemoteClientConnector: client, + }, + prep: func() error { + var err error + _, err = client.CoreV1().Secrets("test").Create( + context.TODO(), &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: util.KarmadaCertSecretName("karmada-demo"), + Namespace: "test", + }, + Data: map[string][]byte{ + "ca.crt": []byte("ca-crt-data"), + "ca.key": []byte("ca-key-data"), + }, + }, + metav1.CreateOptions{}, + ) + return err + }, + cleanup: func() error { + err := client.CoreV1().Secrets("test").Delete( + context.TODO(), util.KarmadaCertSecretName("karmada-demo"), + metav1.DeleteOptions{}, + ) + if err != nil { + return fmt.Errorf("failed to delete %s secret", "test") + } + return nil + }, + wantErr: false, + wantSkip: true, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err := test.prep() + if err != nil { + t.Errorf("failed to prep test: %v", err) + } + + skip, err := skipCerts(test.runData) + if err == nil && test.wantErr { + t.Errorf("expected error, but got none") + } + if err != nil && !test.wantErr { + t.Errorf("unexpected error: %v", err) + } + if err != nil && test.wantErr && !strings.Contains(err.Error(), test.errMsg) { + t.Errorf("expected %s error msg to contain %s", err.Error(), test.errMsg) + } + if skip != test.wantSkip { + t.Errorf("expected %t skip, but got %t", test.wantSkip, skip) + } + err = test.cleanup() + if err != nil { + t.Errorf("failed to clean up test: %v", err) + } + }) + } +} + +func TestRunCATask(t *testing.T) { + tests := []struct { + name string + kc *certs.CertConfig + runData workflow.RunData + prep func(*certs.CertConfig) error + verify func(workflow.RunData) error + wantErr bool + errMsg string + }{ + { + name: "RunCATask_InvalidTypeAssertion_TypeAssertionFailed", + kc: certs.KarmadaCertRootCA(), + runData: MyTestData{Data: "test"}, + prep: func(*certs.CertConfig) error { return nil }, + verify: func(workflow.RunData) error { return nil }, + wantErr: true, + errMsg: "certs task invoked with an invalid data struct", + }, + { + name: "RunCATask_WithNonCACertificate_CACertificateExpected", + kc: certs.KarmadaCertAdmin(), + runData: &TestInitData{ + Name: "karmada-demo", + Namespace: "test", + }, + prep: func(*certs.CertConfig) error { return nil }, + verify: func(workflow.RunData) error { return nil }, + wantErr: true, + errMsg: fmt.Sprintf("this function should only be used for CAs, but cert %s has CA %s", constants.KarmadaCertAndKeyName, constants.CaCertAndKeyName), + }, + { + name: "RunCATask_WithEd25519UnsupportedPublicKeyAlgorithm_UnsupportedKeyType", + kc: certs.KarmadaCertRootCA(), + runData: &TestInitData{ + Name: "karmada-demo", + Namespace: "test", + }, + prep: func(cc *certs.CertConfig) error { + cc.PublicKeyAlgorithm = x509.Ed25519 + return nil + }, + verify: func(workflow.RunData) error { return nil }, + wantErr: true, + errMsg: fmt.Sprintf("unsupported key type: %T", x509.Ed25519), + }, + { + name: "RunCATask_GenerateCACertificate_CACertificateSuccessfullyGenerated", + kc: certs.KarmadaCertRootCA(), + runData: &TestInitData{ + Name: "karmada-demo", + Namespace: "test", + }, + prep: func(*certs.CertConfig) error { return nil }, + verify: func(rd workflow.RunData) error { + certData := rd.(*TestInitData).CertList() + if len(certData) != 1 { + return fmt.Errorf("expected cert store to contain the generated CA certificate but found %d certs", len(certData)) + } + return nil + }, + wantErr: false, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + if err := test.prep(test.kc); err != nil { + t.Errorf("failed to prep cert config data: %v", err) + } + caTask := runCATask(test.kc) + err := caTask(test.runData) + if err == nil && test.wantErr { + t.Errorf("expected error, but got none") + } + if err != nil && !test.wantErr { + t.Errorf("unexpected error: %v", err) + } + if err != nil && test.wantErr && !strings.Contains(err.Error(), test.errMsg) { + t.Errorf("expected %s error msg to contain %s", err.Error(), test.errMsg) + } + if err := test.verify(test.runData); err != nil { + t.Errorf("failed to verify run data: %v", err) + } + }) + } +} + +func TestRunCertTask(t *testing.T) { + tests := []struct { + name string + kc *certs.CertConfig + caCert *certs.CertConfig + runData workflow.RunData + prep func(*certs.CertConfig, *certs.CertConfig, workflow.RunData) error + verify func(workflow.RunData) error + wantErr bool + errMsg string + }{ + { + name: "RunCertTask_InvalidTypeAssertion_TypeAssertionFailed", + kc: certs.KarmadaCertAdmin(), + runData: MyTestData{Data: "test"}, + prep: func(*certs.CertConfig, *certs.CertConfig, workflow.RunData) error { return nil }, + verify: func(workflow.RunData) error { return nil }, + caCert: nil, + wantErr: true, + errMsg: "certs task invoked with an invalid data struct", + }, + { + name: "RunCertTask_NilCACert_CACertIsNil", + kc: certs.KarmadaCertAdmin(), + runData: &TestInitData{ + Name: "karmada-demo", + Namespace: "test", + }, + prep: func(*certs.CertConfig, *certs.CertConfig, workflow.RunData) error { return nil }, + verify: func(workflow.RunData) error { return nil }, + caCert: nil, + wantErr: true, + errMsg: fmt.Sprintf("unexpected empty ca cert for %s", constants.KarmadaCertAndKeyName), + }, + { + name: "RunCertTask_MismatchCAName_CANameIsMismatch", + kc: certs.KarmadaCertAdmin(), + runData: &TestInitData{ + Name: "karmada-demo", + Namespace: "test", + }, + prep: func(*certs.CertConfig, *certs.CertConfig, workflow.RunData) error { return nil }, + verify: func(workflow.RunData) error { return nil }, + caCert: &certs.CertConfig{ + Name: "invalid", + }, + wantErr: true, + errMsg: fmt.Sprintf("mismatched CA name: expected %s but got %s", constants.CaCertAndKeyName, "invalid"), + }, + { + name: "RunCertTask_CreateCertAndKeyFileWithCA_SuccessfullyGeneratedCertificate", + kc: certs.KarmadaCertAdmin(), + caCert: certs.KarmadaCertRootCA(), + runData: &TestInitData{ + Name: "karmada-demo", + Namespace: "test", + ComponentsUnits: &v1alpha1.KarmadaComponents{ + KarmadaAPIServer: &v1alpha1.KarmadaAPIServer{}, + }, + }, + prep: func(ca *certs.CertConfig, _ *certs.CertConfig, rd workflow.RunData) error { + newCA, err := certs.NewCertificateAuthority(ca) + if err != nil { + return fmt.Errorf("failed to create certificate authority: %v", err) + } + rd.(*TestInitData).AddCert(newCA) + return nil + }, + verify: func(rd workflow.RunData) error { + certData := rd.(*TestInitData).CertList() + if len(certData) != 2 { + return fmt.Errorf("expected cert store to contain the Certificate Authority and the associated certificate but found %d certs", len(certData)) + } + if rd.(*TestInitData).GetCert(constants.CaCertAndKeyName) == nil { + return fmt.Errorf("expected %s Karmada Root CA to exist in the certificates store", constants.CaCertAndKeyName) + } + if rd.(*TestInitData).GetCert(constants.KarmadaCertAndKeyName) == nil { + return fmt.Errorf("expected %s karmada admin certificate to exist in the certificate store", constants.KarmadaCertAndKeyName) + } + return nil + }, + wantErr: false, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + if err := test.prep(test.caCert, test.kc, test.runData); err != nil { + t.Errorf("failed to prep cert config data: %v", err) + } + certTask := runCertTask(test.kc, test.caCert) + err := certTask(test.runData) + if err == nil && test.wantErr { + t.Errorf("expected error, but got none") + } + if err != nil && !test.wantErr { + t.Errorf("unexpected error: %v", err) + } + if err != nil && test.wantErr && !strings.Contains(err.Error(), test.errMsg) { + t.Errorf("expected %s error msg to contain %s", err.Error(), test.errMsg) + } + if err := test.verify(test.runData); err != nil { + t.Errorf("failed to verify run data: %v", err) + } + }) + } +}