github.com/operator-framework/operator-lifecycle-manager@v0.30.0/test/e2e/webhook_e2e_test.go (about)

     1  package e2e
     2  
     3  import (
     4  	"context"
     5  	"crypto/ecdsa"
     6  	"crypto/elliptic"
     7  	"crypto/rand"
     8  	"crypto/x509"
     9  	"crypto/x509/pkix"
    10  	"fmt"
    11  	"math"
    12  	"math/big"
    13  	"strings"
    14  	"time"
    15  
    16  	. "github.com/onsi/ginkgo/v2"
    17  	. "github.com/onsi/gomega"
    18  	"github.com/operator-framework/operator-lifecycle-manager/test/e2e/ctx"
    19  	"github.com/stretchr/testify/require"
    20  	admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
    21  	corev1 "k8s.io/api/core/v1"
    22  	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
    23  	"k8s.io/apimachinery/pkg/api/errors"
    24  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    25  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    26  	"k8s.io/apimachinery/pkg/labels"
    27  
    28  	v1 "github.com/operator-framework/api/pkg/operators/v1"
    29  	operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1"
    30  	"github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned"
    31  	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/certs"
    32  	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/install"
    33  	"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorclient"
    34  )
    35  
    36  // Global Variables
    37  const (
    38  	webhookName = "webhook.test.com"
    39  )
    40  
    41  var _ = Describe("CSVs with a Webhook", func() {
    42  	var (
    43  		generatedNamespace corev1.Namespace
    44  		c                  operatorclient.ClientInterface
    45  		crc                versioned.Interface
    46  		nsLabels           map[string]string
    47  	)
    48  
    49  	BeforeEach(func() {
    50  		c = ctx.Ctx().KubeClient()
    51  		crc = ctx.Ctx().OperatorClient()
    52  		nsLabels = map[string]string{
    53  			"foo": "bar",
    54  		}
    55  		generatedNamespace = corev1.Namespace{
    56  			ObjectMeta: metav1.ObjectMeta{
    57  				Name: genName("webhook-e2e-"),
    58  				Labels: map[string]string{
    59  					"foo": "bar",
    60  				},
    61  			},
    62  		}
    63  		Eventually(func() error {
    64  			return ctx.Ctx().Client().Create(context.Background(), &generatedNamespace)
    65  		}).Should(Succeed())
    66  	})
    67  
    68  	AfterEach(func() {
    69  		TeardownNamespace(generatedNamespace.GetName())
    70  	})
    71  
    72  	When("Installed in an OperatorGroup that defines a selector", func() {
    73  		var cleanupCSV cleanupFunc
    74  		var ogSelector *metav1.LabelSelector
    75  
    76  		BeforeEach(func() {
    77  			ogSelector = &metav1.LabelSelector{
    78  				MatchLabels: nsLabels,
    79  			}
    80  
    81  			og := newOperatorGroup(generatedNamespace.GetName(), genName("selector-og-"), nil, ogSelector, nil, false)
    82  			_, err := crc.OperatorsV1().OperatorGroups(generatedNamespace.GetName()).Create(context.TODO(), og, metav1.CreateOptions{})
    83  			Expect(err).Should(BeNil())
    84  		})
    85  
    86  		AfterEach(func() {
    87  			if cleanupCSV != nil {
    88  				cleanupCSV()
    89  			}
    90  		})
    91  
    92  		It("The webhook is scoped to the selector", func() {
    93  			sideEffect := admissionregistrationv1.SideEffectClassNone
    94  			webhook := operatorsv1alpha1.WebhookDescription{
    95  				GenerateName:            webhookName,
    96  				Type:                    operatorsv1alpha1.ValidatingAdmissionWebhook,
    97  				DeploymentName:          genName("webhook-dep-"),
    98  				ContainerPort:           443,
    99  				AdmissionReviewVersions: []string{"v1beta1", "v1"},
   100  				SideEffects:             &sideEffect,
   101  			}
   102  
   103  			csv := createCSVWithWebhook(generatedNamespace.GetName(), webhook)
   104  			var err error
   105  			cleanupCSV, err = createCSV(c, crc, csv, generatedNamespace.GetName(), false, false)
   106  			Expect(err).Should(BeNil())
   107  
   108  			_, err = fetchCSV(crc, generatedNamespace.GetName(), csv.Name, csvSucceededChecker)
   109  			Expect(err).Should(BeNil())
   110  
   111  			actualWebhook, err := getWebhookWithGenerateName(c, webhook.GenerateName)
   112  			Expect(err).Should(BeNil())
   113  
   114  			Expect(actualWebhook.Webhooks[0].NamespaceSelector).Should(Equal(ogSelector))
   115  		})
   116  	})
   117  	When("Installed in a SingleNamespace OperatorGroup", func() {
   118  		var cleanupCSV cleanupFunc
   119  		var og *v1.OperatorGroup
   120  
   121  		BeforeEach(func() {
   122  			og = newOperatorGroup(generatedNamespace.GetName(), genName("single-namespace-og-"), nil, nil, []string{generatedNamespace.GetName()}, false)
   123  			var err error
   124  			og, err = crc.OperatorsV1().OperatorGroups(generatedNamespace.GetName()).Create(context.TODO(), og, metav1.CreateOptions{})
   125  			Expect(err).Should(BeNil())
   126  		})
   127  
   128  		AfterEach(func() {
   129  			if cleanupCSV != nil {
   130  				cleanupCSV()
   131  			}
   132  		})
   133  
   134  		It("Creates Webhooks scoped to a single namespace", func() {
   135  			sideEffect := admissionregistrationv1.SideEffectClassNone
   136  			webhook := operatorsv1alpha1.WebhookDescription{
   137  				GenerateName:            webhookName,
   138  				Type:                    operatorsv1alpha1.ValidatingAdmissionWebhook,
   139  				DeploymentName:          genName("webhook-dep-"),
   140  				ContainerPort:           443,
   141  				AdmissionReviewVersions: []string{"v1beta1", "v1"},
   142  				SideEffects:             &sideEffect,
   143  			}
   144  
   145  			csv := createCSVWithWebhook(generatedNamespace.GetName(), webhook)
   146  			var err error
   147  			cleanupCSV, err = createCSV(c, crc, csv, generatedNamespace.GetName(), false, false)
   148  			Expect(err).Should(BeNil())
   149  
   150  			_, err = fetchCSV(crc, generatedNamespace.GetName(), csv.Name, csvSucceededChecker)
   151  			Expect(err).Should(BeNil())
   152  
   153  			actualWebhook, err := getWebhookWithGenerateName(c, webhook.GenerateName)
   154  			Expect(err).Should(BeNil())
   155  
   156  			ogLabel, err := getOGLabelKey(og)
   157  			require.NoError(GinkgoT(), err)
   158  
   159  			expected := &metav1.LabelSelector{
   160  				MatchLabels:      map[string]string{ogLabel: ""},
   161  				MatchExpressions: []metav1.LabelSelectorRequirement(nil),
   162  			}
   163  			Expect(actualWebhook.Webhooks[0].NamespaceSelector).Should(Equal(expected))
   164  
   165  			By(`Ensure that changes to the WebhookDescription within the CSV trigger an update to on cluster resources`)
   166  			changedGenerateName := webhookName + "-changed"
   167  			Eventually(func() error {
   168  				existingCSV, err := crc.OperatorsV1alpha1().ClusterServiceVersions(generatedNamespace.GetName()).Get(context.TODO(), csv.GetName(), metav1.GetOptions{})
   169  				if err != nil {
   170  					return err
   171  				}
   172  				existingCSV.Spec.WebhookDefinitions[0].GenerateName = changedGenerateName
   173  
   174  				existingCSV, err = crc.OperatorsV1alpha1().ClusterServiceVersions(generatedNamespace.GetName()).Update(context.TODO(), existingCSV, metav1.UpdateOptions{})
   175  				return err
   176  			}, time.Minute, 5*time.Second).Should(Succeed())
   177  			Eventually(func() bool {
   178  				By(`Previous Webhook should be deleted`)
   179  				_, err = getWebhookWithGenerateName(c, webhookName)
   180  				if err != nil && err.Error() != "NotFound" {
   181  					return false
   182  				}
   183  
   184  				By(`Current Webhook should exist`)
   185  				_, err = getWebhookWithGenerateName(c, changedGenerateName)
   186  				return err == nil
   187  			}, time.Minute, 5*time.Second).Should(BeTrue())
   188  		})
   189  		It("Reuses existing valid certs", func() {
   190  			sideEffect := admissionregistrationv1.SideEffectClassNone
   191  			webhook := operatorsv1alpha1.WebhookDescription{
   192  				GenerateName:            webhookName,
   193  				Type:                    operatorsv1alpha1.ValidatingAdmissionWebhook,
   194  				DeploymentName:          genName("webhook-dep-"),
   195  				ContainerPort:           443,
   196  				AdmissionReviewVersions: []string{"v1beta1", "v1"},
   197  				SideEffects:             &sideEffect,
   198  			}
   199  			csv := createCSVWithWebhook(generatedNamespace.GetName(), webhook)
   200  
   201  			var err error
   202  			cleanupCSV, err = createCSV(c, crc, csv, generatedNamespace.GetName(), false, false)
   203  			Expect(err).Should(BeNil())
   204  
   205  			_, err = fetchCSV(crc, generatedNamespace.GetName(), csv.Name, csvSucceededChecker)
   206  			Expect(err).Should(BeNil())
   207  
   208  			By(`Get the existing secret`)
   209  			webhookSecretName := webhook.DeploymentName + "-service-cert"
   210  			existingSecret, err := c.KubernetesInterface().CoreV1().Secrets(generatedNamespace.GetName()).Get(context.TODO(), webhookSecretName, metav1.GetOptions{})
   211  			require.NoError(GinkgoT(), err)
   212  
   213  			By(`Modify the phase`)
   214  			Eventually(func() bool {
   215  				fetchedCSV, err := crc.OperatorsV1alpha1().ClusterServiceVersions(generatedNamespace.GetName()).Get(context.TODO(), csv.GetName(), metav1.GetOptions{})
   216  				if err != nil {
   217  					return false
   218  				}
   219  
   220  				fetchedCSV.Status.Phase = operatorsv1alpha1.CSVPhasePending
   221  
   222  				_, err = crc.OperatorsV1alpha1().ClusterServiceVersions(generatedNamespace.GetName()).UpdateStatus(context.TODO(), fetchedCSV, metav1.UpdateOptions{})
   223  				return err == nil
   224  			}).Should(BeTrue(), "Unable to set CSV phase to Pending")
   225  
   226  			By(`Wait for webhook-operator to succeed`)
   227  			_, err = fetchCSV(crc, generatedNamespace.GetName(), csv.GetName(), csvSucceededChecker)
   228  			require.NoError(GinkgoT(), err)
   229  
   230  			By(`Get the updated secret`)
   231  			updatedSecret, err := c.KubernetesInterface().CoreV1().Secrets(generatedNamespace.GetName()).Get(context.TODO(), webhookSecretName, metav1.GetOptions{})
   232  			require.NoError(GinkgoT(), err)
   233  
   234  			require.Equal(GinkgoT(), existingSecret.GetAnnotations()[install.OLMCAHashAnnotationKey], updatedSecret.GetAnnotations()[install.OLMCAHashAnnotationKey])
   235  			require.Equal(GinkgoT(), existingSecret.Data[install.OLMCAPEMKey], updatedSecret.Data[install.OLMCAPEMKey])
   236  		})
   237  		It("Fails to install a CSV if multiple Webhooks share the same name", func() {
   238  			sideEffect := admissionregistrationv1.SideEffectClassNone
   239  			webhook := operatorsv1alpha1.WebhookDescription{
   240  				GenerateName:            webhookName,
   241  				Type:                    operatorsv1alpha1.ValidatingAdmissionWebhook,
   242  				DeploymentName:          genName("webhook-dep-"),
   243  				ContainerPort:           443,
   244  				AdmissionReviewVersions: []string{"v1beta1", "v1"},
   245  				SideEffects:             &sideEffect,
   246  			}
   247  
   248  			csv := createCSVWithWebhook(generatedNamespace.GetName(), webhook)
   249  			csv.Spec.WebhookDefinitions = append(csv.Spec.WebhookDefinitions, webhook)
   250  			var err error
   251  			cleanupCSV, err = createCSV(c, crc, csv, generatedNamespace.GetName(), false, false)
   252  			Expect(err).Should(BeNil())
   253  
   254  			_, err = fetchCSV(crc, generatedNamespace.GetName(), csv.Name, csvFailedChecker)
   255  			Expect(err).Should(BeNil())
   256  		})
   257  		It("Fails if the webhooks intercepts all resources", func() {
   258  			sideEffect := admissionregistrationv1.SideEffectClassNone
   259  			webhook := operatorsv1alpha1.WebhookDescription{
   260  				GenerateName:            webhookName,
   261  				Type:                    operatorsv1alpha1.ValidatingAdmissionWebhook,
   262  				DeploymentName:          genName("webhook-dep-"),
   263  				ContainerPort:           443,
   264  				AdmissionReviewVersions: []string{"v1beta1", "v1"},
   265  				SideEffects:             &sideEffect,
   266  				Rules: []admissionregistrationv1.RuleWithOperations{
   267  					{
   268  						Operations: []admissionregistrationv1.OperationType{},
   269  						Rule: admissionregistrationv1.Rule{
   270  							APIGroups:   []string{"*"},
   271  							APIVersions: []string{"*"},
   272  							Resources:   []string{"*"},
   273  						},
   274  					},
   275  				},
   276  			}
   277  
   278  			csv := createCSVWithWebhook(generatedNamespace.GetName(), webhook)
   279  
   280  			var err error
   281  			cleanupCSV, err = createCSV(c, crc, csv, generatedNamespace.GetName(), false, false)
   282  			Expect(err).Should(BeNil())
   283  
   284  			failedCSV, err := fetchCSV(crc, generatedNamespace.GetName(), csv.Name, csvFailedChecker)
   285  			Expect(err).Should(BeNil())
   286  			Expect(failedCSV.Status.Message).Should(Equal("webhook rules cannot include all groups"))
   287  		})
   288  		It("Fails if the webhook intercepts OLM resources", func() {
   289  			sideEffect := admissionregistrationv1.SideEffectClassNone
   290  			webhook := operatorsv1alpha1.WebhookDescription{
   291  				GenerateName:            webhookName,
   292  				Type:                    operatorsv1alpha1.ValidatingAdmissionWebhook,
   293  				DeploymentName:          genName("webhook-dep-"),
   294  				ContainerPort:           443,
   295  				AdmissionReviewVersions: []string{"v1beta1", "v1"},
   296  				SideEffects:             &sideEffect,
   297  				Rules: []admissionregistrationv1.RuleWithOperations{
   298  					{
   299  						Operations: []admissionregistrationv1.OperationType{},
   300  						Rule: admissionregistrationv1.Rule{
   301  							APIGroups:   []string{"operators.coreos.com"},
   302  							APIVersions: []string{"*"},
   303  							Resources:   []string{"*"},
   304  						},
   305  					},
   306  				},
   307  			}
   308  
   309  			csv := createCSVWithWebhook(generatedNamespace.GetName(), webhook)
   310  
   311  			var err error
   312  			cleanupCSV, err = createCSV(c, crc, csv, generatedNamespace.GetName(), false, false)
   313  			Expect(err).Should(BeNil())
   314  
   315  			failedCSV, err := fetchCSV(crc, generatedNamespace.GetName(), csv.Name, csvFailedChecker)
   316  			Expect(err).Should(BeNil())
   317  			Expect(failedCSV.Status.Message).Should(Equal("webhook rules cannot include the OLM group"))
   318  		})
   319  		It("Fails if webhook intercepts Admission Webhook resources", func() {
   320  			sideEffect := admissionregistrationv1.SideEffectClassNone
   321  			webhook := operatorsv1alpha1.WebhookDescription{
   322  				GenerateName:            webhookName,
   323  				Type:                    operatorsv1alpha1.ValidatingAdmissionWebhook,
   324  				DeploymentName:          genName("webhook-dep-"),
   325  				ContainerPort:           443,
   326  				AdmissionReviewVersions: []string{"v1beta1", "v1"},
   327  				SideEffects:             &sideEffect,
   328  				Rules: []admissionregistrationv1.RuleWithOperations{
   329  					{
   330  						Operations: []admissionregistrationv1.OperationType{},
   331  						Rule: admissionregistrationv1.Rule{
   332  							APIGroups:   []string{"admissionregistration.k8s.io"},
   333  							APIVersions: []string{"*"},
   334  							Resources:   []string{"*"},
   335  						},
   336  					},
   337  				},
   338  			}
   339  
   340  			csv := createCSVWithWebhook(generatedNamespace.GetName(), webhook)
   341  
   342  			var err error
   343  			cleanupCSV, err = createCSV(c, crc, csv, generatedNamespace.GetName(), false, false)
   344  			Expect(err).Should(BeNil())
   345  
   346  			failedCSV, err := fetchCSV(crc, generatedNamespace.GetName(), csv.Name, csvFailedChecker)
   347  			Expect(err).Should(BeNil())
   348  			Expect(failedCSV.Status.Message).Should(Equal("webhook rules cannot include MutatingWebhookConfiguration or ValidatingWebhookConfiguration resources"))
   349  		})
   350  		It("Succeeds if the webhook intercepts non Admission Webhook resources in admissionregistration group", func() {
   351  			sideEffect := admissionregistrationv1.SideEffectClassNone
   352  			webhook := operatorsv1alpha1.WebhookDescription{
   353  				GenerateName:            webhookName,
   354  				Type:                    operatorsv1alpha1.ValidatingAdmissionWebhook,
   355  				DeploymentName:          genName("webhook-dep-"),
   356  				ContainerPort:           443,
   357  				AdmissionReviewVersions: []string{"v1beta1", "v1"},
   358  				SideEffects:             &sideEffect,
   359  				Rules: []admissionregistrationv1.RuleWithOperations{
   360  					{
   361  						Operations: []admissionregistrationv1.OperationType{
   362  							admissionregistrationv1.OperationAll,
   363  						},
   364  						Rule: admissionregistrationv1.Rule{
   365  							APIGroups:   []string{"admissionregistration.k8s.io"},
   366  							APIVersions: []string{"*"},
   367  							Resources:   []string{"SomeOtherResource"},
   368  						},
   369  					},
   370  				},
   371  			}
   372  
   373  			csv := createCSVWithWebhook(generatedNamespace.GetName(), webhook)
   374  
   375  			var err error
   376  			cleanupCSV, err = createCSV(c, crc, csv, generatedNamespace.GetName(), false, false)
   377  			Expect(err).Should(BeNil())
   378  
   379  			_, err = fetchCSV(crc, generatedNamespace.GetName(), csv.Name, csvSucceededChecker)
   380  			Expect(err).Should(BeNil())
   381  		})
   382  		It("Can be installed and upgraded successfully", func() {
   383  			sideEffect := admissionregistrationv1.SideEffectClassNone
   384  			webhook := operatorsv1alpha1.WebhookDescription{
   385  				GenerateName:            "webhook.test.com",
   386  				Type:                    operatorsv1alpha1.ValidatingAdmissionWebhook,
   387  				DeploymentName:          genName("webhook-dep-"),
   388  				ContainerPort:           443,
   389  				AdmissionReviewVersions: []string{"v1beta1", "v1"},
   390  				SideEffects:             &sideEffect,
   391  				Rules: []admissionregistrationv1.RuleWithOperations{
   392  					{
   393  						Operations: []admissionregistrationv1.OperationType{
   394  							admissionregistrationv1.OperationAll,
   395  						},
   396  						Rule: admissionregistrationv1.Rule{
   397  							APIGroups:   []string{"admissionregistration.k8s.io"},
   398  							APIVersions: []string{"*"},
   399  							Resources:   []string{"SomeOtherResource"},
   400  						},
   401  					},
   402  				},
   403  			}
   404  
   405  			csv := createCSVWithWebhook(generatedNamespace.GetName(), webhook)
   406  
   407  			_, err := createCSV(c, crc, csv, generatedNamespace.GetName(), false, false)
   408  			Expect(err).Should(BeNil())
   409  			By(`cleanup by upgrade`)
   410  
   411  			_, err = fetchCSV(crc, generatedNamespace.GetName(), csv.Name, csvSucceededChecker)
   412  			Expect(err).Should(BeNil())
   413  
   414  			_, err = getWebhookWithGenerateName(c, webhook.GenerateName)
   415  			Expect(err).Should(BeNil())
   416  
   417  			By(`Update the CSV so it it replaces the existing CSV`)
   418  			csv.Spec.Replaces = csv.GetName()
   419  			csv.Name = genName("csv-")
   420  			previousWebhookName := webhook.GenerateName
   421  			webhook.GenerateName = "webhook2.test.com"
   422  			csv.Spec.WebhookDefinitions[0] = webhook
   423  			cleanupCSV, err = createCSV(c, crc, csv, generatedNamespace.GetName(), false, false)
   424  			Expect(err).Should(BeNil())
   425  
   426  			_, err = fetchCSV(crc, generatedNamespace.GetName(), csv.GetName(), csvSucceededChecker)
   427  			Expect(err).Should(BeNil())
   428  
   429  			_, err = getWebhookWithGenerateName(c, webhook.GenerateName)
   430  			Expect(err).Should(BeNil())
   431  
   432  			By(`Make sure old resources are cleaned up.`)
   433  			err = waitForCsvToDelete(generatedNamespace.GetName(), csv.Spec.Replaces, crc)
   434  			Expect(err).ShouldNot(HaveOccurred())
   435  
   436  			By(`Wait until previous webhook is cleaned up`)
   437  			Eventually(func() (bool, error) {
   438  				_, err := c.KubernetesInterface().AdmissionregistrationV1().ValidatingWebhookConfigurations().Get(context.TODO(), previousWebhookName, metav1.GetOptions{})
   439  				if errors.IsNotFound(err) {
   440  					return true, nil
   441  				}
   442  				if err != nil {
   443  					return false, err
   444  				}
   445  				return false, nil
   446  			}).Should(BeTrue())
   447  		})
   448  		It("[FLAKE] Is updated when the CAs expire", func() {
   449  			By(`issue: https://github.com/operator-framework/operator-lifecycle-manager/issues/2629`)
   450  			sideEffect := admissionregistrationv1.SideEffectClassNone
   451  			webhook := operatorsv1alpha1.WebhookDescription{
   452  				GenerateName:            webhookName,
   453  				Type:                    operatorsv1alpha1.ValidatingAdmissionWebhook,
   454  				DeploymentName:          genName("webhook-dep-"),
   455  				ContainerPort:           443,
   456  				AdmissionReviewVersions: []string{"v1beta1", "v1"},
   457  				SideEffects:             &sideEffect,
   458  			}
   459  
   460  			csv := createCSVWithWebhook(generatedNamespace.GetName(), webhook)
   461  
   462  			var err error
   463  			cleanupCSV, err = createCSV(c, crc, csv, generatedNamespace.GetName(), false, false)
   464  			Expect(err).Should(BeNil())
   465  
   466  			_, err = fetchCSV(crc, generatedNamespace.GetName(), csv.Name, csvSucceededChecker)
   467  			Expect(err).Should(BeNil())
   468  
   469  			actualWebhook, err := getWebhookWithGenerateName(c, webhook.GenerateName)
   470  			Expect(err).Should(BeNil())
   471  
   472  			oldWebhookCABundle := actualWebhook.Webhooks[0].ClientConfig.CABundle
   473  
   474  			By(`Get the deployment`)
   475  			dep, err := c.KubernetesInterface().AppsV1().Deployments(generatedNamespace.GetName()).Get(context.TODO(), csv.Spec.WebhookDefinitions[0].DeploymentName, metav1.GetOptions{})
   476  			Expect(err).Should(BeNil())
   477  
   478  			//Store the ca sha annotation
   479  			oldCAAnnotation, ok := dep.Spec.Template.GetAnnotations()[install.OLMCAHashAnnotationKey]
   480  			Expect(ok).Should(BeTrue())
   481  
   482  			caSecret, err := c.KubernetesInterface().CoreV1().Secrets(generatedNamespace.GetName()).Get(context.TODO(), install.SecretName(install.ServiceName(dep.Name)), metav1.GetOptions{})
   483  			Expect(err).Should(BeNil())
   484  
   485  			caPEM, certPEM, privPEM := generateExpiredCerts(install.HostnamesForService(install.ServiceName(dep.Name), generatedNamespace.GetName()))
   486  			By(`Induce a cert rotation`)
   487  			Eventually(Apply(caSecret, func(caSecret *corev1.Secret) error {
   488  				caSecret.Data[install.OLMCAPEMKey] = caPEM
   489  				caSecret.Data["tls.crt"] = certPEM
   490  				caSecret.Data["tls.key"] = privPEM
   491  				return nil
   492  			})).Should(Succeed())
   493  
   494  			_, err = fetchCSV(crc, generatedNamespace.GetName(), csv.Name, func(csv *operatorsv1alpha1.ClusterServiceVersion) bool {
   495  				By(`Should create deployment`)
   496  				dep, err = c.GetDeployment(generatedNamespace.GetName(), csv.Spec.WebhookDefinitions[0].DeploymentName)
   497  				if err != nil {
   498  					return false
   499  				}
   500  
   501  				By(`Should have a new ca hash annotation`)
   502  				newCAAnnotation, ok := dep.Spec.Template.GetAnnotations()[install.OLMCAHashAnnotationKey]
   503  				if !ok {
   504  					return false
   505  				}
   506  
   507  				if newCAAnnotation != oldCAAnnotation {
   508  					By(`Check for success`)
   509  					return csvSucceededChecker(csv)
   510  				}
   511  
   512  				return false
   513  			})
   514  			Expect(err).Should(BeNil())
   515  
   516  			By(`get new webhook`)
   517  			actualWebhook, err = getWebhookWithGenerateName(c, webhook.GenerateName)
   518  			Expect(err).Should(BeNil())
   519  
   520  			newWebhookCABundle := actualWebhook.Webhooks[0].ClientConfig.CABundle
   521  			Expect(newWebhookCABundle).ShouldNot(Equal(oldWebhookCABundle))
   522  		})
   523  	})
   524  	When("Installed in a Global OperatorGroup", func() {
   525  		var cleanupCSV cleanupFunc
   526  
   527  		BeforeEach(func() {
   528  			og := newOperatorGroup(generatedNamespace.GetName(), genName("global-og-"), nil, nil, []string{}, false)
   529  			og, err := crc.OperatorsV1().OperatorGroups(generatedNamespace.GetName()).Create(context.TODO(), og, metav1.CreateOptions{})
   530  			Expect(err).Should(BeNil())
   531  		})
   532  
   533  		AfterEach(func() {
   534  			if cleanupCSV != nil {
   535  				cleanupCSV()
   536  			}
   537  		})
   538  
   539  		It("The webhook is scoped to all namespaces", func() {
   540  			sideEffect := admissionregistrationv1.SideEffectClassNone
   541  			webhook := operatorsv1alpha1.WebhookDescription{
   542  				GenerateName:            webhookName,
   543  				Type:                    operatorsv1alpha1.ValidatingAdmissionWebhook,
   544  				DeploymentName:          genName("webhook-dep-"),
   545  				ContainerPort:           443,
   546  				AdmissionReviewVersions: []string{"v1beta1", "v1"},
   547  				SideEffects:             &sideEffect,
   548  			}
   549  
   550  			csv := createCSVWithWebhook(generatedNamespace.GetName(), webhook)
   551  
   552  			var err error
   553  			cleanupCSV, err = createCSV(c, crc, csv, generatedNamespace.GetName(), false, false)
   554  			Expect(err).Should(BeNil())
   555  
   556  			_, err = fetchCSV(crc, generatedNamespace.GetName(), csv.Name, csvSucceededChecker)
   557  			Expect(err).Should(BeNil())
   558  			actualWebhook, err := getWebhookWithGenerateName(c, webhook.GenerateName)
   559  			Expect(err).Should(BeNil())
   560  
   561  			expected := &metav1.LabelSelector{
   562  				MatchLabels:      map[string]string(nil),
   563  				MatchExpressions: []metav1.LabelSelectorRequirement(nil),
   564  			}
   565  			Expect(actualWebhook.Webhooks[0].NamespaceSelector).Should(Equal(expected))
   566  		})
   567  	})
   568  	It("Allows multiple installs of the same webhook", func() {
   569  		namespace1, cleanupNS1 := newNamespace(c, genName("webhook-test-"))
   570  		defer cleanupNS1()
   571  
   572  		namespace2, cleanupNS2 := newNamespace(c, genName("webhook-test-"))
   573  		defer cleanupNS2()
   574  
   575  		og1 := newOperatorGroup(namespace1.Name, genName("test-og-"), nil, nil, []string{"test-go-"}, false)
   576  		Eventually(func() error {
   577  			og, err := crc.OperatorsV1().OperatorGroups(namespace1.Name).Create(context.TODO(), og1, metav1.CreateOptions{})
   578  			if err != nil {
   579  				return err
   580  			}
   581  
   582  			og1 = og
   583  
   584  			return nil
   585  		}).Should(Succeed())
   586  
   587  		og2 := newOperatorGroup(namespace2.Name, genName("test-og-"), nil, nil, []string{"test-go-"}, false)
   588  		Eventually(func() error {
   589  			og, err := crc.OperatorsV1().OperatorGroups(namespace2.Name).Create(context.TODO(), og2, metav1.CreateOptions{})
   590  			if err != nil {
   591  				return err
   592  			}
   593  
   594  			og2 = og
   595  
   596  			return nil
   597  		}).Should(Succeed())
   598  
   599  		sideEffect := admissionregistrationv1.SideEffectClassNone
   600  		webhook := operatorsv1alpha1.WebhookDescription{
   601  			GenerateName:            webhookName,
   602  			Type:                    operatorsv1alpha1.ValidatingAdmissionWebhook,
   603  			DeploymentName:          genName("webhook-dep-"),
   604  			ContainerPort:           443,
   605  			AdmissionReviewVersions: []string{"v1beta1", "v1"},
   606  			SideEffects:             &sideEffect,
   607  		}
   608  
   609  		csv := createCSVWithWebhook(generatedNamespace.GetName(), webhook)
   610  
   611  		csv.Namespace = namespace1.GetName()
   612  		var cleanupCSV cleanupFunc
   613  		Eventually(func() (err error) {
   614  			cleanupCSV, err = createCSV(c, crc, csv, namespace1.Name, false, false)
   615  			return
   616  		}).Should(Succeed())
   617  		defer cleanupCSV()
   618  
   619  		Eventually(func() (err error) {
   620  			_, err = fetchCSV(crc, namespace1.Name, csv.Name, csvSucceededChecker)
   621  			return
   622  		}).Should(Succeed())
   623  
   624  		csv.Namespace = namespace2.Name
   625  		Eventually(func() (err error) {
   626  			cleanupCSV, err = createCSV(c, crc, csv, namespace2.Name, false, false)
   627  			return
   628  		}).Should(Succeed())
   629  		defer cleanupCSV()
   630  
   631  		Eventually(func() (err error) {
   632  			_, err = fetchCSV(crc, namespace2.Name, csv.Name, csvSucceededChecker)
   633  			return
   634  		}).Should(Succeed())
   635  
   636  		Eventually(func() (count int, err error) {
   637  			var webhooks *admissionregistrationv1.ValidatingWebhookConfigurationList
   638  			webhooks, err = c.KubernetesInterface().AdmissionregistrationV1().ValidatingWebhookConfigurations().List(context.TODO(), metav1.ListOptions{})
   639  			if err != nil {
   640  				return
   641  			}
   642  
   643  			for _, w := range webhooks.Items {
   644  				if strings.HasPrefix(w.GetName(), webhook.GenerateName) {
   645  					count++
   646  				}
   647  			}
   648  
   649  			return
   650  		}).Should(Equal(2))
   651  	})
   652  	When("Installed from a catalog Source", func() {
   653  		const csvName = "webhook-operator.v0.0.1"
   654  		var cleanupCSV cleanupFunc
   655  		var cleanupCatSrc cleanupFunc
   656  		var cleanupSubscription cleanupFunc
   657  
   658  		BeforeEach(func() {
   659  			og := newOperatorGroup(generatedNamespace.GetName(), genName("og-"), nil, nil, []string{}, false)
   660  			_, err := crc.OperatorsV1().OperatorGroups(generatedNamespace.GetName()).Create(context.TODO(), og, metav1.CreateOptions{})
   661  			Expect(err).Should(BeNil())
   662  
   663  			By(`Create a catalogSource which has the webhook-operator`)
   664  			sourceName := genName("catalog-")
   665  			packageName := "webhook-operator"
   666  			channelName := "alpha"
   667  
   668  			catSrcImage := "quay.io/operator-framework/webhook-operator-index"
   669  
   670  			By(`Create gRPC CatalogSource`)
   671  			source := &operatorsv1alpha1.CatalogSource{
   672  				TypeMeta: metav1.TypeMeta{
   673  					Kind:       operatorsv1alpha1.CatalogSourceKind,
   674  					APIVersion: operatorsv1alpha1.CatalogSourceCRDAPIVersion,
   675  				},
   676  				ObjectMeta: metav1.ObjectMeta{
   677  					Name:      sourceName,
   678  					Namespace: generatedNamespace.GetName(),
   679  				},
   680  				Spec: operatorsv1alpha1.CatalogSourceSpec{
   681  					SourceType: operatorsv1alpha1.SourceTypeGrpc,
   682  					Image:      catSrcImage + ":0.0.3",
   683  					GrpcPodConfig: &operatorsv1alpha1.GrpcPodConfig{
   684  						SecurityContextConfig: operatorsv1alpha1.Restricted,
   685  					},
   686  				},
   687  			}
   688  
   689  			source, err = crc.OperatorsV1alpha1().CatalogSources(source.GetNamespace()).Create(context.TODO(), source, metav1.CreateOptions{})
   690  			require.NoError(GinkgoT(), err)
   691  			cleanupCatSrc = func() {
   692  				require.NoError(GinkgoT(), crc.OperatorsV1alpha1().CatalogSources(source.GetNamespace()).Delete(context.TODO(), source.GetName(), metav1.DeleteOptions{}))
   693  			}
   694  
   695  			By(`Wait for the CatalogSource to be ready`)
   696  			_, err = fetchCatalogSourceOnStatus(crc, source.GetName(), source.GetNamespace(), catalogSourceRegistryPodSynced())
   697  			require.NoError(GinkgoT(), err)
   698  
   699  			By(`Create a Subscription for the webhook-operator`)
   700  			subscriptionName := genName("sub-")
   701  			cleanupSubscription := createSubscriptionForCatalog(crc, source.GetNamespace(), subscriptionName, source.GetName(), packageName, channelName, "", operatorsv1alpha1.ApprovalAutomatic)
   702  			defer cleanupSubscription()
   703  
   704  			_, err = fetchSubscription(crc, source.GetNamespace(), subscriptionName, subscriptionHasInstallPlanChecker())
   705  			require.NoError(GinkgoT(), err)
   706  
   707  			By(`Wait for webhook-operator v2 csv to succeed`)
   708  			csv, err := fetchCSV(crc, source.GetNamespace(), csvName, csvSucceededChecker)
   709  			require.NoError(GinkgoT(), err)
   710  
   711  			cleanupCSV = buildCSVCleanupFunc(c, crc, *csv, source.GetNamespace(), true, true)
   712  		})
   713  
   714  		AfterEach(func() {
   715  			if cleanupCSV != nil {
   716  				cleanupCSV()
   717  			}
   718  			if cleanupCatSrc != nil {
   719  				cleanupCatSrc()
   720  			}
   721  			if cleanupSubscription != nil {
   722  				cleanupSubscription()
   723  			}
   724  		})
   725  
   726  		It("Validating, Mutating and Conversion webhooks work as intended", func() {
   727  			By(`An invalid custom resource is rejected by the validating webhook`)
   728  			invalidCR := &unstructured.Unstructured{
   729  				Object: map[string]interface{}{
   730  					"apiVersion": "webhook.operators.coreos.io/v1",
   731  					"kind":       "webhooktests",
   732  					"metadata": map[string]interface{}{
   733  						"namespace": generatedNamespace.GetName(),
   734  						"name":      "my-cr-1",
   735  					},
   736  					"spec": map[string]interface{}{
   737  						"valid": false,
   738  					},
   739  				},
   740  			}
   741  			expectedErrorMessage := "admission webhook \"vwebhooktest.kb.io\" denied the request: WebhookTest.test.operators.coreos.com \"my-cr-1\" is invalid: spec.schedule: Invalid value: false: Spec.Valid must be true"
   742  			Eventually(func() bool {
   743  				err := c.CreateCustomResource(invalidCR)
   744  				if err == nil || expectedErrorMessage != err.Error() {
   745  					return false
   746  				}
   747  				return true
   748  			}).Should(BeTrue(), "The admission webhook should have rejected the invalid resource")
   749  
   750  			By(`An valid custom resource is acceoted by the validating webhook and the mutating webhook sets the CR's spec.mutate field to true.`)
   751  			validCR := &unstructured.Unstructured{
   752  				Object: map[string]interface{}{
   753  					"apiVersion": "webhook.operators.coreos.io/v1",
   754  					"kind":       "webhooktests",
   755  					"metadata": map[string]interface{}{
   756  						"namespace": generatedNamespace.GetName(),
   757  						"name":      "my-cr-1",
   758  					},
   759  					"spec": map[string]interface{}{
   760  						"valid": true,
   761  					},
   762  				},
   763  			}
   764  			crCleanupFunc, err := createCR(c, validCR, "webhook.operators.coreos.io", "v1", generatedNamespace.GetName(), "webhooktests", "my-cr-1")
   765  			defer crCleanupFunc()
   766  			require.NoError(GinkgoT(), err, "The valid CR should have been approved by the validating webhook")
   767  
   768  			By(`Check that you can get v1 of the webhooktest cr`)
   769  			v1UnstructuredObject, err := c.GetCustomResource("webhook.operators.coreos.io", "v1", generatedNamespace.GetName(), "webhooktests", "my-cr-1")
   770  			require.NoError(GinkgoT(), err, "Unable to get the v1 of the valid CR")
   771  			v1Object := v1UnstructuredObject.Object
   772  			v1Spec, ok := v1Object["spec"].(map[string]interface{})
   773  			require.True(GinkgoT(), ok, "Unable to get spec of v1 object")
   774  			v1SpecMutate, ok := v1Spec["mutate"].(bool)
   775  			require.True(GinkgoT(), ok, "Unable to get spec.mutate of v1 object")
   776  			v1SpecValid, ok := v1Spec["valid"].(bool)
   777  			require.True(GinkgoT(), ok, "Unable to get spec.valid of v1 object")
   778  
   779  			require.True(GinkgoT(), v1SpecMutate, "The mutating webhook should have set the valid CR's spec.mutate field to true")
   780  			require.True(GinkgoT(), v1SpecValid, "The validating webhook should have required that the CR's spec.valid field is true")
   781  
   782  			By(`Check that you can get v2 of the webhooktest cr`)
   783  			v2UnstructuredObject, err := c.GetCustomResource("webhook.operators.coreos.io", "v2", generatedNamespace.GetName(), "webhooktests", "my-cr-1")
   784  			require.NoError(GinkgoT(), err, "Unable to get the v2 of the valid CR")
   785  			v2Object := v2UnstructuredObject.Object
   786  			v2Spec := v2Object["spec"].(map[string]interface{})
   787  			require.True(GinkgoT(), ok, "Unable to get spec of v2 object")
   788  			v2SpecConversion, ok := v2Spec["conversion"].(map[string]interface{})
   789  			require.True(GinkgoT(), ok, "Unable to get spec.conversion of v2 object")
   790  			v2SpecConversionMutate := v2SpecConversion["mutate"].(bool)
   791  			require.True(GinkgoT(), ok, "Unable to get spec.conversion.mutate of v2 object")
   792  			v2SpecConversionValid := v2SpecConversion["valid"].(bool)
   793  			require.True(GinkgoT(), ok, "Unable to get spec.conversion.valid of v2 object")
   794  			require.True(GinkgoT(), v2SpecConversionMutate)
   795  			require.True(GinkgoT(), v2SpecConversionValid)
   796  
   797  			By(`Check that conversion strategies are disabled after uninstalling the operator.`)
   798  			err = crc.OperatorsV1alpha1().ClusterServiceVersions(generatedNamespace.GetName()).Delete(context.TODO(), csvName, metav1.DeleteOptions{})
   799  			require.NoError(GinkgoT(), err)
   800  
   801  			Eventually(func() error {
   802  				crd, err := c.ApiextensionsInterface().ApiextensionsV1().CustomResourceDefinitions().Get(context.TODO(), "webhooktests.webhook.operators.coreos.io", metav1.GetOptions{})
   803  				if err != nil {
   804  					return err
   805  				}
   806  
   807  				if crd.Spec.Conversion.Strategy != apiextensionsv1.NoneConverter {
   808  					return fmt.Errorf("conversion strategy is not NoneConverter")
   809  				}
   810  				if crd.Spec.Conversion.Webhook != nil {
   811  					return fmt.Errorf("webhook is not nil")
   812  				}
   813  				return nil
   814  			}).Should(Succeed())
   815  		})
   816  	})
   817  	When("WebhookDescription has conversionCRDs field", func() {
   818  		var cleanupCSV cleanupFunc
   819  
   820  		BeforeEach(func() {
   821  			By(`global operator group`)
   822  			og := newOperatorGroup(generatedNamespace.GetName(), genName("global-og-"), nil, nil, []string{}, false)
   823  			og, err := crc.OperatorsV1().OperatorGroups(generatedNamespace.GetName()).Create(context.TODO(), og, metav1.CreateOptions{})
   824  			Expect(err).Should(BeNil())
   825  		})
   826  
   827  		AfterEach(func() {
   828  			if cleanupCSV != nil {
   829  				cleanupCSV()
   830  			}
   831  		})
   832  
   833  		It("The conversion CRD is not updated via webhook when CSV does not own this CRD", func() {
   834  			By(`create CRD (crdA)`)
   835  			crdAPlural := genName("mockcrda")
   836  			crdA := newV1CRD(crdAPlural)
   837  			cleanupCRD, er := createCRD(c, crdA)
   838  			require.NoError(GinkgoT(), er)
   839  			defer cleanupCRD()
   840  
   841  			By(`create another CRD (crdB)`)
   842  			crdBPlural := genName("mockcrdb")
   843  			crdB := newV1CRD(crdBPlural)
   844  			cleanupCRD2, er := createCRD(c, crdB)
   845  			require.NoError(GinkgoT(), er)
   846  			defer cleanupCRD2()
   847  
   848  			By(`describe webhook`)
   849  			sideEffect := admissionregistrationv1.SideEffectClassNone
   850  			webhook := operatorsv1alpha1.WebhookDescription{
   851  				GenerateName:            webhookName,
   852  				Type:                    operatorsv1alpha1.ValidatingAdmissionWebhook,
   853  				DeploymentName:          genName("webhook-dep-"),
   854  				ContainerPort:           443,
   855  				AdmissionReviewVersions: []string{"v1beta1", "v1"},
   856  				SideEffects:             &sideEffect,
   857  				ConversionCRDs:          []string{crdA.GetName(), crdB.GetName()},
   858  			}
   859  
   860  			ownedCRDDescs := make([]operatorsv1alpha1.CRDDescription, 0)
   861  
   862  			By(`create CSV`)
   863  			csv := createCSVWithWebhookAndCrds(generatedNamespace.GetName(), webhook, ownedCRDDescs)
   864  
   865  			var err error
   866  			cleanupCSV, err = createCSV(c, crc, csv, generatedNamespace.GetName(), false, false)
   867  			Expect(err).Should(BeNil())
   868  
   869  			_, err = fetchCSV(crc, generatedNamespace.GetName(), csv.Name, csvSucceededChecker)
   870  			Expect(err).Should(BeNil())
   871  			actualWebhook, err := getWebhookWithGenerateName(c, webhook.GenerateName)
   872  			Expect(err).Should(BeNil())
   873  
   874  			expected := &metav1.LabelSelector{
   875  				MatchLabels:      map[string]string(nil),
   876  				MatchExpressions: []metav1.LabelSelectorRequirement(nil),
   877  			}
   878  			Expect(actualWebhook.Webhooks[0].NamespaceSelector).Should(Equal(expected))
   879  
   880  			expectedUpdatedCrdFields := &apiextensionsv1.CustomResourceConversion{
   881  				Strategy: "Webhook",
   882  			}
   883  
   884  			By(`Read the updated crdA on cluster into the following crd`)
   885  			tempCrdA, err := c.ApiextensionsInterface().ApiextensionsV1().CustomResourceDefinitions().Get(context.TODO(), crdA.GetName(), metav1.GetOptions{})
   886  
   887  			By(`Read the updated crdB on cluster into the following crd`)
   888  			tempCrdB, err := c.ApiextensionsInterface().ApiextensionsV1().CustomResourceDefinitions().Get(context.TODO(), crdB.GetName(), metav1.GetOptions{})
   889  
   890  			Expect(tempCrdA.Spec.Conversion.Strategy).Should(Equal(expectedUpdatedCrdFields.Strategy))
   891  			Expect(tempCrdB.Spec.Conversion.Strategy).Should(Equal(expectedUpdatedCrdFields.Strategy))
   892  
   893  			var expectedTempPort int32 = 443
   894  			expectedConvertPath := "/convert"
   895  			expectedConvertNamespace := "system"
   896  
   897  			Expect(tempCrdA.Spec.Conversion.Webhook.ClientConfig.Service.Port).Should(Equal(&expectedTempPort))
   898  			Expect(tempCrdA.Spec.Conversion.Webhook.ClientConfig.Service.Path).Should(Equal(&expectedConvertPath))
   899  			Expect(tempCrdA.Spec.Conversion.Webhook.ClientConfig.Service.Name).Should(Equal("webhook-service"))
   900  			Expect(tempCrdA.Spec.Conversion.Webhook.ClientConfig.Service.Namespace).Should(Equal(expectedConvertNamespace))
   901  		})
   902  		It("The CSV is not created when dealing with conversionCRD and multiple installModes support exists", func() {
   903  			By(`create CRD (crdA)`)
   904  			crdAPlural := genName("mockcrda")
   905  			crdA := newV1CRD(crdAPlural)
   906  			cleanupCRD, er := createCRD(c, crdA)
   907  			require.NoError(GinkgoT(), er)
   908  			defer cleanupCRD()
   909  
   910  			By(`describe webhook`)
   911  			sideEffect := admissionregistrationv1.SideEffectClassNone
   912  			webhook := operatorsv1alpha1.WebhookDescription{
   913  				GenerateName:            webhookName,
   914  				Type:                    operatorsv1alpha1.ValidatingAdmissionWebhook,
   915  				DeploymentName:          genName("webhook-dep-"),
   916  				ContainerPort:           443,
   917  				AdmissionReviewVersions: []string{"v1beta1", "v1"},
   918  				SideEffects:             &sideEffect,
   919  				ConversionCRDs:          []string{crdA.GetName()},
   920  			}
   921  
   922  			ownedCRDDescs := make([]operatorsv1alpha1.CRDDescription, 0)
   923  			ownedCRDDescs = append(ownedCRDDescs, operatorsv1alpha1.CRDDescription{Name: crdA.GetName(), Version: crdA.Spec.Versions[0].Name, Kind: crdA.Spec.Names.Kind})
   924  
   925  			By(`create CSV`)
   926  			csv := createCSVWithWebhookAndCrdsAndInvalidInstallModes(generatedNamespace.GetName(), webhook, ownedCRDDescs)
   927  
   928  			var err error
   929  			cleanupCSV, err = createCSV(c, crc, csv, generatedNamespace.GetName(), false, false)
   930  			Expect(err).Should(BeNil())
   931  
   932  			_, err = fetchCSV(crc, generatedNamespace.GetName(), csv.Name, csvSucceededChecker)
   933  			Expect(err).Should(BeNil())
   934  			actualWebhook, err := getWebhookWithGenerateName(c, webhook.GenerateName)
   935  			Expect(err).Should(BeNil())
   936  
   937  			expected := &metav1.LabelSelector{
   938  				MatchLabels:      map[string]string(nil),
   939  				MatchExpressions: []metav1.LabelSelectorRequirement(nil),
   940  			}
   941  			Expect(actualWebhook.Webhooks[0].NamespaceSelector).Should(Equal(expected))
   942  
   943  			expectedUpdatedCrdFields := &apiextensionsv1.CustomResourceConversion{
   944  				Strategy: "Webhook",
   945  			}
   946  
   947  			By(`Read the updated crdA on cluster into the following crd`)
   948  			tempCrdA, err := c.ApiextensionsInterface().ApiextensionsV1().CustomResourceDefinitions().Get(context.TODO(), crdA.GetName(), metav1.GetOptions{})
   949  
   950  			Expect(tempCrdA.Spec.Conversion.Strategy).Should(Equal(expectedUpdatedCrdFields.Strategy))
   951  
   952  			var expectedTempPort int32 = 443
   953  			expectedConvertPath := "/convert"
   954  
   955  			Expect(tempCrdA.Spec.Conversion.Webhook.ClientConfig.Service.Port).Should(Equal(&expectedTempPort))
   956  			Expect(tempCrdA.Spec.Conversion.Webhook.ClientConfig.Service.Path).Should(Equal(&expectedConvertPath))
   957  			By(`CRD namespace would not be updated, hence conversion webhook won't work for objects of this CRD's Kind`)
   958  			Expect(tempCrdA.Spec.Conversion.Webhook.ClientConfig.Service.Namespace).ShouldNot(Equal(csv.GetNamespace()))
   959  		})
   960  	})
   961  })
   962  
   963  func getWebhookWithGenerateName(c operatorclient.ClientInterface, generateName string) (*admissionregistrationv1.ValidatingWebhookConfiguration, error) {
   964  	webhookSelector := labels.SelectorFromSet(map[string]string{install.WebhookDescKey: generateName}).String()
   965  	existingWebhooks, err := c.KubernetesInterface().AdmissionregistrationV1().ValidatingWebhookConfigurations().List(context.TODO(), metav1.ListOptions{LabelSelector: webhookSelector})
   966  	if err != nil {
   967  		return nil, err
   968  	}
   969  
   970  	if len(existingWebhooks.Items) > 0 {
   971  		return &existingWebhooks.Items[0], nil
   972  	}
   973  	return nil, fmt.Errorf("NotFound")
   974  }
   975  
   976  func createCSVWithWebhook(namespace string, webhookDesc operatorsv1alpha1.WebhookDescription) operatorsv1alpha1.ClusterServiceVersion {
   977  	return operatorsv1alpha1.ClusterServiceVersion{
   978  		TypeMeta: metav1.TypeMeta{
   979  			Kind:       operatorsv1alpha1.ClusterServiceVersionKind,
   980  			APIVersion: operatorsv1alpha1.ClusterServiceVersionAPIVersion,
   981  		},
   982  		ObjectMeta: metav1.ObjectMeta{
   983  			Name:      genName("webhook-csv-"),
   984  			Namespace: namespace,
   985  		},
   986  		Spec: operatorsv1alpha1.ClusterServiceVersionSpec{
   987  			WebhookDefinitions: []operatorsv1alpha1.WebhookDescription{
   988  				webhookDesc,
   989  			},
   990  			InstallModes: []operatorsv1alpha1.InstallMode{
   991  				{
   992  					Type:      operatorsv1alpha1.InstallModeTypeOwnNamespace,
   993  					Supported: true,
   994  				},
   995  				{
   996  					Type:      operatorsv1alpha1.InstallModeTypeSingleNamespace,
   997  					Supported: true,
   998  				},
   999  				{
  1000  					Type:      operatorsv1alpha1.InstallModeTypeMultiNamespace,
  1001  					Supported: true,
  1002  				},
  1003  				{
  1004  					Type:      operatorsv1alpha1.InstallModeTypeAllNamespaces,
  1005  					Supported: true,
  1006  				},
  1007  			},
  1008  			InstallStrategy: newNginxInstallStrategy(webhookDesc.DeploymentName, nil, nil),
  1009  		},
  1010  	}
  1011  }
  1012  
  1013  func createCSVWithWebhookAndCrds(namespace string, webhookDesc operatorsv1alpha1.WebhookDescription, ownedCRDDescs []operatorsv1alpha1.CRDDescription) operatorsv1alpha1.ClusterServiceVersion {
  1014  	return operatorsv1alpha1.ClusterServiceVersion{
  1015  		TypeMeta: metav1.TypeMeta{
  1016  			Kind:       operatorsv1alpha1.ClusterServiceVersionKind,
  1017  			APIVersion: operatorsv1alpha1.ClusterServiceVersionAPIVersion,
  1018  		},
  1019  		ObjectMeta: metav1.ObjectMeta{
  1020  			Name:      genName("webhook-csv-"),
  1021  			Namespace: namespace,
  1022  		},
  1023  		Spec: operatorsv1alpha1.ClusterServiceVersionSpec{
  1024  			WebhookDefinitions: []operatorsv1alpha1.WebhookDescription{
  1025  				webhookDesc,
  1026  			},
  1027  			CustomResourceDefinitions: operatorsv1alpha1.CustomResourceDefinitions{
  1028  				Owned: ownedCRDDescs,
  1029  			},
  1030  			InstallModes: []operatorsv1alpha1.InstallMode{
  1031  				{
  1032  					Type:      operatorsv1alpha1.InstallModeTypeOwnNamespace,
  1033  					Supported: false,
  1034  				},
  1035  				{
  1036  					Type:      operatorsv1alpha1.InstallModeTypeSingleNamespace,
  1037  					Supported: false,
  1038  				},
  1039  				{
  1040  					Type:      operatorsv1alpha1.InstallModeTypeMultiNamespace,
  1041  					Supported: false,
  1042  				},
  1043  				{
  1044  					Type:      operatorsv1alpha1.InstallModeTypeAllNamespaces,
  1045  					Supported: true,
  1046  				},
  1047  			},
  1048  			InstallStrategy: newNginxInstallStrategy(webhookDesc.DeploymentName, nil, nil),
  1049  		},
  1050  	}
  1051  }
  1052  
  1053  func createCSVWithWebhookAndCrdsAndInvalidInstallModes(namespace string, webhookDesc operatorsv1alpha1.WebhookDescription, ownedCRDDescs []operatorsv1alpha1.CRDDescription) operatorsv1alpha1.ClusterServiceVersion {
  1054  	return operatorsv1alpha1.ClusterServiceVersion{
  1055  		TypeMeta: metav1.TypeMeta{
  1056  			Kind:       operatorsv1alpha1.ClusterServiceVersionKind,
  1057  			APIVersion: operatorsv1alpha1.ClusterServiceVersionAPIVersion,
  1058  		},
  1059  		ObjectMeta: metav1.ObjectMeta{
  1060  			Name:      genName("webhook-csv-"),
  1061  			Namespace: namespace,
  1062  		},
  1063  		Spec: operatorsv1alpha1.ClusterServiceVersionSpec{
  1064  			WebhookDefinitions: []operatorsv1alpha1.WebhookDescription{
  1065  				webhookDesc,
  1066  			},
  1067  			CustomResourceDefinitions: operatorsv1alpha1.CustomResourceDefinitions{
  1068  				Owned: ownedCRDDescs,
  1069  			},
  1070  			InstallModes: []operatorsv1alpha1.InstallMode{
  1071  				{
  1072  					Type:      operatorsv1alpha1.InstallModeTypeOwnNamespace,
  1073  					Supported: true,
  1074  				},
  1075  				{
  1076  					Type:      operatorsv1alpha1.InstallModeTypeSingleNamespace,
  1077  					Supported: false,
  1078  				},
  1079  				{
  1080  					Type:      operatorsv1alpha1.InstallModeTypeMultiNamespace,
  1081  					Supported: false,
  1082  				},
  1083  				{
  1084  					Type:      operatorsv1alpha1.InstallModeTypeAllNamespaces,
  1085  					Supported: true,
  1086  				},
  1087  			},
  1088  			InstallStrategy: newNginxInstallStrategy(webhookDesc.DeploymentName, nil, nil),
  1089  		},
  1090  	}
  1091  }
  1092  
  1093  func newV1CRD(plural string) apiextensionsv1.CustomResourceDefinition {
  1094  	path := "/convert"
  1095  	var port int32 = 443
  1096  	var min float64 = 2
  1097  	var max float64 = 256
  1098  	crd := apiextensionsv1.CustomResourceDefinition{
  1099  		ObjectMeta: metav1.ObjectMeta{
  1100  			Name: plural + ".cluster.com",
  1101  		},
  1102  		Spec: apiextensionsv1.CustomResourceDefinitionSpec{
  1103  			Group: "cluster.com",
  1104  			Scope: apiextensionsv1.NamespaceScoped,
  1105  			Versions: []apiextensionsv1.CustomResourceDefinitionVersion{
  1106  				{
  1107  					Name:    "v1alpha1",
  1108  					Served:  true,
  1109  					Storage: true,
  1110  					Schema: &apiextensionsv1.CustomResourceValidation{
  1111  						OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{
  1112  							Type: "object",
  1113  							Properties: map[string]apiextensionsv1.JSONSchemaProps{
  1114  								"spec": {
  1115  									Type:        "object",
  1116  									Description: "Spec of a test object.",
  1117  									Properties: map[string]apiextensionsv1.JSONSchemaProps{
  1118  										"scalar": {
  1119  											Type:        "number",
  1120  											Description: "Scalar value that should have a min and max.",
  1121  											Minimum:     &min,
  1122  											Maximum:     &max,
  1123  										},
  1124  									},
  1125  								},
  1126  							},
  1127  						},
  1128  					},
  1129  				},
  1130  				{
  1131  					Name:    "v1alpha2",
  1132  					Served:  true,
  1133  					Storage: false,
  1134  					Schema: &apiextensionsv1.CustomResourceValidation{
  1135  						OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{
  1136  							Type: "object",
  1137  							Properties: map[string]apiextensionsv1.JSONSchemaProps{
  1138  								"spec": {
  1139  									Type:        "object",
  1140  									Description: "Spec of a test object.",
  1141  									Properties: map[string]apiextensionsv1.JSONSchemaProps{
  1142  										"scalar": {
  1143  											Type:        "number",
  1144  											Description: "Scalar value that should have a min and max.",
  1145  											Minimum:     &min,
  1146  											Maximum:     &max,
  1147  										},
  1148  									},
  1149  								},
  1150  							},
  1151  						},
  1152  					},
  1153  				},
  1154  			},
  1155  			Names: apiextensionsv1.CustomResourceDefinitionNames{
  1156  				Plural:   plural,
  1157  				Singular: plural,
  1158  				Kind:     plural,
  1159  				ListKind: plural + "list",
  1160  			},
  1161  			PreserveUnknownFields: false,
  1162  			Conversion: &apiextensionsv1.CustomResourceConversion{
  1163  				Strategy: "Webhook",
  1164  				Webhook: &apiextensionsv1.WebhookConversion{
  1165  					ClientConfig: &apiextensionsv1.WebhookClientConfig{
  1166  						Service: &apiextensionsv1.ServiceReference{
  1167  							Namespace: "system",
  1168  							Name:      "webhook-service",
  1169  							Path:      &path,
  1170  							Port:      &port,
  1171  						},
  1172  					},
  1173  					ConversionReviewVersions: []string{"v1", "v1beta1"},
  1174  				},
  1175  			},
  1176  		},
  1177  		Status: apiextensionsv1.CustomResourceDefinitionStatus{
  1178  			StoredVersions: []string{"v1alpha1", "v1alpha2"},
  1179  		},
  1180  	}
  1181  
  1182  	return crd
  1183  }
  1184  
  1185  func generateExpiredCerts(hosts []string) ([]byte, []byte, []byte) {
  1186  	caSerial, err := rand.Int(rand.Reader, new(big.Int).SetInt64(math.MaxInt64))
  1187  	Expect(err).Should(BeNil())
  1188  
  1189  	caDetails := &x509.Certificate{
  1190  		SerialNumber: caSerial,
  1191  		Subject: pkix.Name{
  1192  			CommonName:   fmt.Sprintf("olm-selfsigned-%x", caSerial),
  1193  			Organization: []string{install.Organization},
  1194  		},
  1195  		NotBefore:             time.Now().Add(-5 * time.Minute),
  1196  		NotAfter:              time.Now().Add(-5 * time.Minute),
  1197  		IsCA:                  true,
  1198  		KeyUsage:              x509.KeyUsageCertSign,
  1199  		BasicConstraintsValid: true,
  1200  	}
  1201  
  1202  	caKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
  1203  	Expect(err).Should(BeNil())
  1204  
  1205  	caRaw, err := x509.CreateCertificate(rand.Reader, caDetails, caDetails, &caKey.PublicKey, caKey)
  1206  	Expect(err).Should(BeNil())
  1207  
  1208  	caCert, err := x509.ParseCertificate(caRaw)
  1209  	Expect(err).Should(BeNil())
  1210  
  1211  	caPEM, _, err := (&certs.KeyPair{
  1212  		Cert: caCert,
  1213  		Priv: caKey,
  1214  	}).ToPEM()
  1215  	Expect(err).Should(BeNil())
  1216  
  1217  	serial, err := rand.Int(rand.Reader, new(big.Int).SetInt64(math.MaxInt64))
  1218  	Expect(err).Should(BeNil())
  1219  
  1220  	certDetails := &x509.Certificate{
  1221  		SerialNumber: serial,
  1222  		Subject: pkix.Name{
  1223  			CommonName:   hosts[0],
  1224  			Organization: []string{install.Organization},
  1225  		},
  1226  		NotBefore:             time.Now(),
  1227  		NotAfter:              time.Now(),
  1228  		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
  1229  		BasicConstraintsValid: true,
  1230  		DNSNames:              hosts,
  1231  	}
  1232  
  1233  	privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
  1234  	Expect(err).Should(BeNil())
  1235  
  1236  	publicKey := &privateKey.PublicKey
  1237  	certRaw, err := x509.CreateCertificate(rand.Reader, certDetails, caCert, publicKey, caKey)
  1238  	Expect(err).Should(BeNil())
  1239  
  1240  	cert, err := x509.ParseCertificate(certRaw)
  1241  	Expect(err).Should(BeNil())
  1242  
  1243  	certPEM, privPEM, err := (&certs.KeyPair{
  1244  		Cert: cert,
  1245  		Priv: privateKey,
  1246  	}).ToPEM()
  1247  	Expect(err).Should(BeNil())
  1248  
  1249  	return caPEM, certPEM, privPEM
  1250  }