github.com/verrazzano/verrazzano@v1.7.0/cluster-operator/internal/certificate/certificate_test.go (about) 1 // Copyright (c) 2022, Oracle and/or its affiliates. 2 // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. 3 4 package certificate 5 6 import ( 7 "context" 8 "fmt" 9 "os" 10 "strings" 11 "testing" 12 13 "github.com/stretchr/testify/assert" 14 "go.uber.org/zap" 15 corev1 "k8s.io/api/core/v1" 16 errors2 "k8s.io/apimachinery/pkg/api/errors" 17 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 18 "k8s.io/apimachinery/pkg/runtime" 19 "k8s.io/client-go/kubernetes/fake" 20 fakecorev1 "k8s.io/client-go/kubernetes/typed/core/v1/fake" 21 testing2 "k8s.io/client-go/testing" 22 ) 23 24 // TestCreateWebhookCertificates tests that the certificates needed for webhooks are created 25 // GIVEN an output directory for certificates 26 // 27 // WHEN I call CreateWebhookCertificates 28 // THEN all the needed certificate artifacts are created 29 func TestCreateWebhookCertificates(t *testing.T) { 30 asserts := assert.New(t) 31 client := fake.NewSimpleClientset() 32 33 // create temp dir for certs 34 tempDir, err := os.MkdirTemp("", "certs") 35 t.Logf("Using temp dir %s", tempDir) 36 defer func() { 37 err := os.RemoveAll(tempDir) 38 if err != nil { 39 t.Logf("Error removing tempdir %s", tempDir) 40 } 41 }() 42 asserts.Nil(err) 43 44 err = CreateWebhookCertificates(zap.S(), client, tempDir) 45 asserts.Nil(err) 46 47 // Verify generated certs 48 cert1 := validateFile(asserts, tempDir+"/tls.crt", "-----BEGIN CERTIFICATE-----") 49 key1 := validateFile(asserts, tempDir+"/tls.key", "-----BEGIN RSA PRIVATE KEY-----") 50 51 // Verify generated secrets 52 var secret *corev1.Secret 53 secret, err = client.CoreV1().Secrets(WebhookNamespace).Get(context.TODO(), OperatorCA, metav1.GetOptions{}) 54 _, err2 := client.CoreV1().Secrets(WebhookNamespace).Get(context.TODO(), OperatorTLS, metav1.GetOptions{}) 55 asserts.Nil(err, "error should not be returned setting up certificates") 56 asserts.Nil(err2, "error should not be returned setting up certificates") 57 asserts.NotEmpty(string(secret.Data[CertKey])) 58 59 // create temp dir for certs 60 tempDir2, err := os.MkdirTemp("", "certs") 61 t.Logf("Using temp dir %s", tempDir2) 62 defer func() { 63 err := os.RemoveAll(tempDir2) 64 if err != nil { 65 t.Logf("Error removing tempdir %s", tempDir2) 66 } 67 }() 68 asserts.Nil(err) 69 70 // Call it again, should create new certs in location with identical contents 71 err = CreateWebhookCertificates(zap.S(), client, tempDir2) 72 asserts.Nil(err) 73 asserts.Nil(err) 74 75 // Verify generated certs 76 cert2 := validateFile(asserts, tempDir+"/tls.crt", "-----BEGIN CERTIFICATE-----") 77 key2 := validateFile(asserts, tempDir+"/tls.key", "-----BEGIN RSA PRIVATE KEY-----") 78 79 asserts.Equalf(cert1, cert2, "Certs did not match") 80 asserts.Equalf(key1, key2, "Keys did not match") 81 } 82 83 // TestCreateWebhookCertificatesRaceCondition the handling of the race condition for the CA and TLS 84 // certificates creation 85 // GIVEN a call to CreateWebhookCertificates 86 // 87 // WHEN the secrets initially do not exist at the beginning of the method invocation, but later do on the Create call 88 // THEN the previously stored secret data is used for the generated certificate/key files 89 func TestCreateWebhookCertificatesRaceCondition(t *testing.T) { 90 asserts := assert.New(t) 91 client := fake.NewSimpleClientset() 92 93 commonName := fmt.Sprintf("%s.%s.svc", WebhookName, WebhookNamespace) 94 95 log := zap.S() 96 97 // Call the internal routines to create the cert data and secrets within the fake client; 98 // later we will simulate the race condition using reactors with the fake 99 100 // Create the CA cert and key, and verify the secret is tracked in the fake client 101 ca, caKey, err := createCACert(log, client, commonName) 102 asserts.Nil(err) 103 _, err = client.CoreV1().Secrets(WebhookNamespace).Get(context.TODO(), OperatorCA, metav1.GetOptions{}) 104 asserts.Nil(err) 105 106 // Create the TLS cert and key, and verify the secret is tracked in the fake client 107 serverPEM, serverKeyPEM, err := createTLSCert(log, client, commonName, ca, caKey) 108 asserts.Nil(err) 109 _, err = client.CoreV1().Secrets(WebhookNamespace).Get(context.TODO(), OperatorTLS, metav1.GetOptions{}) 110 asserts.Nil(err) 111 112 // create temp dir for certs 113 tempDir, err := os.MkdirTemp("", "certs") 114 t.Logf("Using temp dir %s", tempDir) 115 defer func() { 116 err := os.RemoveAll(tempDir) 117 if err != nil { 118 t.Logf("Error removing tempdir %s", tempDir) 119 } 120 }() 121 asserts.Nil(err) 122 123 // Simulate the race condition where the secrets do not exist when CreateWebhookCertificates is called 124 // but do exist when it attempts to create them later in the routine. 125 // 126 // We do this by having the initial secret Get() operations return a NotExists error, 127 // but later the Create() calls return an Already exists error (the code before this causes the 128 // secrets to exist in the fake client). 129 130 getCAInvokes := 0 131 getTLSInvokes := 0 132 client.CoreV1().(*fakecorev1.FakeCoreV1). 133 PrependReactor("get", "secrets", 134 func(action testing2.Action) (handled bool, obj runtime.Object, err error) { 135 getActionImpl := action.(testing2.GetActionImpl) 136 resName := getActionImpl.GetName() 137 if resName == OperatorCA { 138 getCAInvokes++ 139 if getCAInvokes > 1 { 140 // Return not handled so the test secret is returned 141 return false, nil, nil 142 } 143 } else if resName == OperatorTLS { 144 getTLSInvokes++ 145 if getTLSInvokes > 1 { 146 // Return not handled so the test secret is returned 147 return false, nil, nil 148 } 149 } 150 // Return not found on the initial Get call for each secret 151 return true, nil, errors2.NewNotFound(action.GetResource().GroupResource(), getActionImpl.GetName()) 152 }) 153 154 err = CreateWebhookCertificates(log, client, tempDir) 155 asserts.Nil(err) 156 157 // Verify generated certs 158 cert1 := validateFile(asserts, tempDir+"/tls.crt", "-----BEGIN CERTIFICATE-----") 159 key1 := validateFile(asserts, tempDir+"/tls.key", "-----BEGIN RSA PRIVATE KEY-----") 160 161 asserts.Equalf(serverPEM, cert1, "Certs did not match") 162 asserts.Equalf(serverKeyPEM, key1, "Keys did not match") 163 } 164 165 func validateFile(asserts *assert.Assertions, certFile string, certPrefix string) []byte { 166 file, err := os.ReadFile(certFile) 167 asserts.Nilf(err, "Error reading file", certFile) 168 asserts.True(strings.Contains(string(file), certPrefix)) 169 return file 170 }