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  }