github.com/verrazzano/verrazzano@v1.7.0/platform-operator/internal/operatorinit/update_webhooks_test.go (about)

     1  // Copyright (c) 2020, 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 operatorinit
     5  
     6  import (
     7  	"bytes"
     8  	"context"
     9  	"errors"
    10  	"testing"
    11  
    12  	"github.com/stretchr/testify/assert"
    13  	"github.com/verrazzano/verrazzano/pkg/constants"
    14  	"github.com/verrazzano/verrazzano/platform-operator/internal/k8s/certificate"
    15  	adminv1 "k8s.io/api/admissionregistration/v1"
    16  	v1 "k8s.io/api/core/v1"
    17  	v12 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
    18  	fakeapiExt "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/fake"
    19  	errors2 "k8s.io/apimachinery/pkg/api/errors"
    20  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    21  	"k8s.io/apimachinery/pkg/runtime"
    22  	"k8s.io/client-go/kubernetes/fake"
    23  	fake2 "k8s.io/client-go/kubernetes/typed/admissionregistration/v1/fake"
    24  	testing2 "k8s.io/client-go/testing"
    25  	controllerruntime "sigs.k8s.io/controller-runtime"
    26  )
    27  
    28  // TestUpdateValidatingnWebhookConfiguration tests that the CA Bundle is updated in the verrazzano-platform-operator
    29  // validatingWebhookConfiguration resource.
    30  // GIVEN a validatingWebhookConfiguration resource with the CA Bundle set
    31  //
    32  //	WHEN I call updateValidatingWebhookConfiguration
    33  //	THEN the validatingWebhookConfiguration resource set the CA Bundle as expected
    34  func TestUpdateValidatingnWebhookConfiguration(t *testing.T) {
    35  	asserts := assert.New(t)
    36  
    37  	kubeClient := fake.NewSimpleClientset()
    38  
    39  	_, caCert, err := createExpectedCASecret(kubeClient)
    40  	asserts.Nilf(err, "Unexpected error creating expected CA secret", err)
    41  
    42  	wh, err := createExpectedValidatingWebhook(kubeClient, certificate.OperatorName)
    43  	asserts.Nilf(err, "error should not be returned creating validation webhook configuration: %v", err)
    44  	asserts.NotEmpty(wh)
    45  
    46  	err = updateValidatingWebhookConfiguration(kubeClient, certificate.OperatorName)
    47  	asserts.Nilf(err, "error should not be returned updating validation webhook configuration: %v", err)
    48  
    49  	updatedWebhook, _ := kubeClient.AdmissionregistrationV1().ValidatingWebhookConfigurations().Get(context.TODO(), "verrazzano-platform-operator-webhook", metav1.GetOptions{})
    50  	asserts.Equal(caCert.Bytes(), updatedWebhook.Webhooks[0].ClientConfig.CABundle, "Expected CA bundle name did not match")
    51  }
    52  
    53  // TestUpdateValidatingnWebhookConfigurationFail tests that the CA Bundle is not updated in the
    54  // verrazzano-platform-operator validatingWebhookConfiguration resource.
    55  // GIVEN an invalid validatingWebhookConfiguration resource with the CA Bundle set
    56  //
    57  //	WHEN I call updateValidatingWebhookConfiguration
    58  //	THEN the validatingWebhookConfiguration resource will fail to be updated
    59  func TestUpdateValidatingnWebhookConfigurationFail(t *testing.T) {
    60  	asserts := assert.New(t)
    61  
    62  	kubeClient := fake.NewSimpleClientset()
    63  
    64  	_, _, err := createExpectedCASecret(kubeClient)
    65  	asserts.Nilf(err, "Unexpected error creating expected CA secret", err)
    66  
    67  	_, err = createInvalidExpectedValidatingWebhook(kubeClient, certificate.OperatorName)
    68  	asserts.Nil(err, "error should not be returned creating validation webhook configuration")
    69  
    70  	err = updateValidatingWebhookConfiguration(kubeClient, certificate.OperatorName)
    71  	asserts.Error(err, "error should be returned updating validation webhook configuration")
    72  }
    73  
    74  // TestUpdateConversionWebhookConfiguration tests that the CA Bundle is updated in the verrazzano-platform-operator
    75  // ConversionWebhookConfiguration resource.
    76  // GIVEN a call to updateConversionWebhookConfiguration
    77  //
    78  //	WHEN the webhook CA bundle is present
    79  //	THEN the CRD is updated with a Webhook converter configuration for the v1beta1 review versions with the correct CA Bundle
    80  func TestUpdateConversionWebhookConfiguration(t *testing.T) {
    81  	asserts := assert.New(t)
    82  
    83  	kubeClient := fake.NewSimpleClientset()
    84  
    85  	_, caCert, err := createExpectedCASecret(kubeClient)
    86  	asserts.Nilf(err, "Unexpected error creating expected CA secret", err)
    87  
    88  	apiExtClient := fakeapiExt.NewSimpleClientset().ApiextensionsV1()
    89  	_, err = apiExtClient.CustomResourceDefinitions().Create(context.TODO(), &v12.CustomResourceDefinition{
    90  		ObjectMeta: controllerruntime.ObjectMeta{Name: certificate.CRDName},
    91  	}, metav1.CreateOptions{})
    92  	asserts.Nilf(err, "Unexpected error creating mock CRD: %v", err)
    93  
    94  	err = updateConversionWebhookConfiguration(apiExtClient, kubeClient)
    95  	asserts.Nilf(err, "Unexpected error returned updating validation webhook configuration: %v", err)
    96  
    97  	updatedCRD, err := apiExtClient.CustomResourceDefinitions().Get(context.TODO(), certificate.CRDName, metav1.GetOptions{})
    98  	asserts.Nilf(err, "Unexpected error getting updated CRD: %v", err)
    99  
   100  	asserts.Equal(caCert.Bytes(), updatedCRD.Spec.Conversion.Webhook.ClientConfig.CABundle, "Expected CA bundle name did not match")
   101  	asserts.Equal(certificate.OperatorName, updatedCRD.Spec.Conversion.Webhook.ClientConfig.Service.Name)
   102  	asserts.Equal(certificate.OperatorNamespace, updatedCRD.Spec.Conversion.Webhook.ClientConfig.Service.Namespace)
   103  	asserts.Equal("/convert", *updatedCRD.Spec.Conversion.Webhook.ClientConfig.Service.Path)
   104  	asserts.Equal(int32(443), *updatedCRD.Spec.Conversion.Webhook.ClientConfig.Service.Port)
   105  	asserts.Equal(v12.WebhookConverter, updatedCRD.Spec.Conversion.Strategy)
   106  	asserts.Equal([]string{"v1beta1"}, updatedCRD.Spec.Conversion.Webhook.ConversionReviewVersions)
   107  }
   108  
   109  // TestUpdateMutatingWebhookConfiguration tests that the CA Bundle is updated the specified MutatingWebhook configuration
   110  // GIVEN a call to updateMutatingWebhookConfiguration
   111  //
   112  //	WHEN with the webhook CA bundle secret exists
   113  //	THEN the MutatingWebhook configuration sets the CA bundle on all webhook client configurations
   114  func TestUpdateMutatingWebhookConfiguration(t *testing.T) {
   115  	asserts := assert.New(t)
   116  
   117  	kubeClient := fake.NewSimpleClientset()
   118  
   119  	_, caCert, err := createExpectedCASecret(kubeClient)
   120  	asserts.Nilf(err, "Unexpected error creating expected CA secret", err)
   121  
   122  	_, err = createExpectedMutatingWebhook(kubeClient)
   123  	asserts.Nilf(err, "Unexpected error creating expected webhook configuration")
   124  
   125  	err = updateMutatingWebhookConfiguration(kubeClient, constants.MysqlBackupMutatingWebhookName)
   126  	asserts.Nilf(err, "Unexpected error returned from updateMutatingWebhookConfiguration: %v", err)
   127  
   128  	updatedWebhook, _ := kubeClient.AdmissionregistrationV1().MutatingWebhookConfigurations().Get(context.TODO(), constants.MysqlBackupMutatingWebhookName, metav1.GetOptions{})
   129  	asserts.Equal(caCert.Bytes(), updatedWebhook.Webhooks[0].ClientConfig.CABundle, "Expected CA bundle name did not match")
   130  	asserts.Equal(caCert.Bytes(), updatedWebhook.Webhooks[1].ClientConfig.CABundle, "Expected CA bundle name did not match")
   131  }
   132  
   133  // TestUpdateMutatingWebhookConfigurationNoCASecret tests that
   134  // GIVEN a call to updateMutatingWebhookConfiguration
   135  //
   136  //	WHEN with the webhook CA bundle secret does not exist but the webhook does
   137  //	THEN an error is returned
   138  func TestUpdateMutatingWebhookConfigurationNoCASecret(t *testing.T) {
   139  	asserts := assert.New(t)
   140  
   141  	kubeClient := fake.NewSimpleClientset()
   142  
   143  	_, err := createExpectedMutatingWebhook(kubeClient)
   144  	asserts.Nilf(err, "Unexpected error creating expected webhook configuration", err)
   145  
   146  	err = updateMutatingWebhookConfiguration(kubeClient, constants.MysqlBackupMutatingWebhookName)
   147  	asserts.NotNil(err, "No error returned when webhook doesn't exist")
   148  }
   149  
   150  // TestUpdateMutatingWebhookConfigurationNoWebhook tests that
   151  // GIVEN a call to updateMutatingWebhookConfiguration
   152  //
   153  //	WHEN with the webhook CA bundle secret exists but the webhook does not
   154  //	THEN an error is returned
   155  func TestUpdateMutatingWebhookConfigurationNoWebhook(t *testing.T) {
   156  	asserts := assert.New(t)
   157  
   158  	kubeClient := fake.NewSimpleClientset()
   159  
   160  	_, _, err := createExpectedCASecret(kubeClient)
   161  	asserts.Nilf(err, "Unexpected error creating expected CA secret", err)
   162  
   163  	err = updateMutatingWebhookConfiguration(kubeClient, constants.MysqlBackupMutatingWebhookName)
   164  	asserts.NotNil(err, "No error returned when webhook doesn't exist")
   165  }
   166  
   167  // TestDeleteValidatingWebhookConfiguration tests that
   168  // GIVEN a call to deleteValidatingWebhookConfiguration
   169  //
   170  //	WHEN the webhook does exist
   171  //	THEN no error is returned
   172  func TestDeleteValidatingWebhookConfiguration(t *testing.T) {
   173  	asserts := assert.New(t)
   174  
   175  	kubeClient := fake.NewSimpleClientset()
   176  
   177  	const webhookName = "foo"
   178  	_, err := createExpectedValidatingWebhook(kubeClient, webhookName)
   179  	asserts.Nilf(err, "Unexpected error creating expected webhook configuration")
   180  
   181  	err = deleteValidatingWebhookConfiguration(kubeClient, webhookName)
   182  	asserts.Nilf(err, "Unexpected error when deleting webhook", err)
   183  
   184  	wh, err := kubeClient.AdmissionregistrationV1().ValidatingWebhookConfigurations().Get(context.TODO(), webhookName, metav1.GetOptions{})
   185  	asserts.NotNil(err, "Did not get expected error after delete")
   186  	asserts.True(errors2.IsNotFound(err), "Did not get IsNotFound error after delete")
   187  	asserts.Nilf(wh, "Webhook reference should be nil after delete")
   188  }
   189  
   190  // TestDeleteValidatingWebhookConfigurationDoesNotExist tests that
   191  // GIVEN a call to deleteValidatingWebhookConfiguration
   192  //
   193  //	WHEN the webhook does NOT exist
   194  //	THEN no error is returned
   195  func TestDeleteValidatingWebhookConfigurationDoesNotExist(t *testing.T) {
   196  	asserts := assert.New(t)
   197  	kubeClient := fake.NewSimpleClientset()
   198  	err := deleteValidatingWebhookConfiguration(kubeClient, "foo")
   199  	asserts.Nilf(err, "Unexpected error when deleting webhook that does not exist", err)
   200  }
   201  
   202  // TestDeleteValidatingWebhookConfigurationErrorOnGet tests that
   203  // GIVEN a call to deleteValidatingWebhookConfiguration
   204  //
   205  //	WHEN the webhook Get() operation returns an unexpected error
   206  //	THEN that error is returned
   207  func TestDeleteValidatingWebhookConfigurationErrorOnGet(t *testing.T) {
   208  	asserts := assert.New(t)
   209  
   210  	kubeClient := fake.NewSimpleClientset()
   211  	kubeClient.AdmissionregistrationV1().(*fake2.FakeAdmissionregistrationV1).
   212  		PrependReactor("get", "validatingwebhookconfigurations",
   213  			func(action testing2.Action) (handled bool, ret runtime.Object, err error) {
   214  				return true, nil, errors.New("error deleting validating webhook")
   215  			})
   216  
   217  	err := deleteValidatingWebhookConfiguration(kubeClient, "foo")
   218  	asserts.NotNilf(err, "No expected error returned when deleting webhook", err)
   219  }
   220  
   221  func createExpectedCASecret(kubeClient *fake.Clientset) (*v1.Secret, bytes.Buffer, error) {
   222  	var caCert bytes.Buffer
   223  	caCert.WriteString("Fake CABundle")
   224  
   225  	caSecret := v1.Secret{}
   226  	caSecret.Name = certificate.OperatorCA
   227  	caSecret.Type = v1.SecretTypeTLS
   228  	caSecret.Namespace = certificate.OperatorNamespace
   229  	caSecret.Data = make(map[string][]byte)
   230  	caSecret.Data[certificate.CertKey] = caCert.Bytes()
   231  	caSecret.Data[certificate.PrivKey] = caCert.Bytes()
   232  
   233  	newSecret, err := kubeClient.CoreV1().Secrets(certificate.OperatorNamespace).Create(context.TODO(), &caSecret, metav1.CreateOptions{})
   234  	return newSecret, caCert, err
   235  }
   236  
   237  func createExpectedMutatingWebhook(kubeClient *fake.Clientset) (*adminv1.MutatingWebhookConfiguration, error) {
   238  	webhook := adminv1.MutatingWebhookConfiguration{
   239  		TypeMeta: metav1.TypeMeta{},
   240  		ObjectMeta: metav1.ObjectMeta{
   241  			Name: constants.MysqlBackupMutatingWebhookName,
   242  		},
   243  		Webhooks: []adminv1.MutatingWebhook{
   244  			{Name: "webhook1"},
   245  			{Name: "webhook2"},
   246  		},
   247  	}
   248  	return kubeClient.AdmissionregistrationV1().MutatingWebhookConfigurations().Create(context.TODO(), &webhook, metav1.CreateOptions{})
   249  }
   250  
   251  func createExpectedValidatingWebhook(kubeClient *fake.Clientset, whName string) (*adminv1.ValidatingWebhookConfiguration, error) {
   252  	pathInstall := "/validate-install-verrazzano-io-v1alpha1-verrazzano"
   253  	serviceInstall := adminv1.ServiceReference{
   254  		Name:      whName,
   255  		Namespace: certificate.OperatorNamespace,
   256  		Path:      &pathInstall,
   257  	}
   258  
   259  	webhook := adminv1.ValidatingWebhookConfiguration{
   260  		TypeMeta: metav1.TypeMeta{},
   261  		ObjectMeta: metav1.ObjectMeta{
   262  			Name: whName,
   263  		},
   264  		Webhooks: []adminv1.ValidatingWebhook{
   265  			{
   266  				Name: "install.verrazzano.io",
   267  				ClientConfig: adminv1.WebhookClientConfig{
   268  					Service: &serviceInstall,
   269  				},
   270  			},
   271  			{
   272  				Name: "install.verrazzano.io.v1beta",
   273  				ClientConfig: adminv1.WebhookClientConfig{
   274  					Service: &serviceInstall,
   275  				},
   276  			},
   277  		},
   278  	}
   279  	return kubeClient.AdmissionregistrationV1().ValidatingWebhookConfigurations().Create(context.TODO(), &webhook, metav1.CreateOptions{})
   280  }
   281  
   282  func createInvalidExpectedValidatingWebhook(kubeClient *fake.Clientset, whName string) (*adminv1.ValidatingWebhookConfiguration, error) {
   283  	path := "/validate-install-verrazzano-io-v1alpha1-verrazzano"
   284  	service := adminv1.ServiceReference{
   285  		Name:      whName,
   286  		Namespace: certificate.OperatorNamespace,
   287  		Path:      &path,
   288  	}
   289  	webhook := adminv1.ValidatingWebhookConfiguration{
   290  		TypeMeta: metav1.TypeMeta{},
   291  		ObjectMeta: metav1.ObjectMeta{
   292  			Name: "InvalidName",
   293  		},
   294  		Webhooks: []adminv1.ValidatingWebhook{
   295  			{
   296  				Name: "install.verrazzano.io",
   297  				ClientConfig: adminv1.WebhookClientConfig{
   298  					Service: &service,
   299  				},
   300  			},
   301  		},
   302  	}
   303  	return kubeClient.AdmissionregistrationV1().ValidatingWebhookConfigurations().Create(context.TODO(), &webhook, metav1.CreateOptions{})
   304  }