From 1f5943153e2d11db526f5165b58df1503b094fa3 Mon Sep 17 00:00:00 2001 From: Ankita Thomas Date: Mon, 22 Apr 2024 10:40:31 -0400 Subject: [PATCH] perform operator apiService certificate validity checks directly Signed-off-by: Ankita Thomas --- pkg/controller/install/certresources.go | 79 ++++++++++++++-- pkg/controller/install/resolver.go | 5 ++ pkg/controller/operators/olm/apiservices.go | 26 +----- pkg/controller/operators/olm/operator.go | 10 ++- pkg/controller/operators/olm/operator_test.go | 40 +++++---- pkg/fakes/fake_strategy_installer.go | 79 ++++++++++++++++ test/e2e/csv_e2e_test.go | 18 ++-- test/e2e/installplan_e2e_test.go | 13 +-- test/e2e/webhook_e2e_test.go | 89 +++++++++++++++++-- 9 files changed, 289 insertions(+), 70 deletions(-) diff --git a/pkg/controller/install/certresources.go b/pkg/controller/install/certresources.go index 68ab1106828..9ff5fcebb1e 100644 --- a/pkg/controller/install/certresources.go +++ b/pkg/controller/install/certresources.go @@ -157,6 +157,13 @@ func ServiceName(deploymentName string) string { return deploymentName + "-service" } +func HostnamesForService(serviceName, namespace string) []string { + return []string{ + fmt.Sprintf("%s.%s", serviceName, namespace), + fmt.Sprintf("%s.%s.svc", serviceName, namespace), + } +} + func (i *StrategyDeploymentInstaller) getCertResources() []certResource { return append(i.apiServiceDescriptions, i.webhookDescriptions...) } @@ -223,15 +230,74 @@ func (i *StrategyDeploymentInstaller) CertsRotated() bool { return i.certificatesRotated } -func ShouldRotateCerts(csv *v1alpha1.ClusterServiceVersion) bool { +// shouldRotateCerts indicates whether an apiService cert should be rotated due to being +// malformed, invalid, expired, inactive or within a specific freshness interval (DefaultCertMinFresh) before expiry. +func shouldRotateCerts(certSecret *corev1.Secret, hosts []string) bool { now := metav1.Now() - if !csv.Status.CertsRotateAt.IsZero() && csv.Status.CertsRotateAt.Before(&now) { + caPEM, ok := certSecret.Data[OLMCAPEMKey] + if !ok { + // missing CA cert in secret + return true + } + certPEM, ok := certSecret.Data["tls.crt"] + if !ok { + // missing cert in secret + return true + } + + ca, err := certs.PEMToCert(caPEM) + if err != nil { + // malformed CA cert + return true + } + cert, err := certs.PEMToCert(certPEM) + if err != nil { + // malformed cert return true } + // check for cert freshness + if !certs.Active(ca) || now.Time.After(CalculateCertRotatesAt(ca.NotAfter)) || + !certs.Active(cert) || now.Time.After(CalculateCertRotatesAt(cert.NotAfter)) { + return true + } + + // Check validity of serving cert and if serving cert is trusted by the CA + for _, host := range hosts { + if err := certs.VerifyCert(ca, cert, host); err != nil { + return true + } + } return false } +func (i *StrategyDeploymentInstaller) ShouldRotateCerts(s Strategy) (bool, error) { + strategy, ok := s.(*v1alpha1.StrategyDetailsDeployment) + if !ok { + return false, fmt.Errorf("attempted to install %s strategy with deployment installer", strategy.GetStrategyName()) + } + + hasCerts := map[string]struct{}{} + for _, c := range i.getCertResources() { + hasCerts[c.getDeploymentName()] = struct{}{} + } + for _, sddSpec := range strategy.DeploymentSpecs { + if _, ok := hasCerts[sddSpec.Name]; ok { + certSecret, err := i.strategyClient.GetOpLister().CoreV1().SecretLister().Secrets(i.owner.GetNamespace()).Get(SecretName(ServiceName(sddSpec.Name))) + if err == nil { + if shouldRotateCerts(certSecret, HostnamesForService(ServiceName(sddSpec.Name), i.owner.GetNamespace())) { + return true, nil + } + } else if apierrors.IsNotFound(err) { + return true, nil + } else { + return false, err + } + } + } + return false, nil +} + func CalculateCertExpiration(startingFrom time.Time) time.Time { return startingFrom.Add(DefaultCertValidFor) } @@ -267,10 +333,7 @@ func (i *StrategyDeploymentInstaller) installCertRequirementsForDeployment(deplo } // Create signed serving cert - hosts := []string{ - fmt.Sprintf("%s.%s", serviceName, i.owner.GetNamespace()), - fmt.Sprintf("%s.%s.svc", serviceName, i.owner.GetNamespace()), - } + hosts := HostnamesForService(serviceName, i.owner.GetNamespace()) servingPair, err := certGenerator.Generate(expiration, Organization, ca, hosts) if err != nil { logger.Warnf("could not generate signed certs for hosts %v", hosts) @@ -314,10 +377,10 @@ func (i *StrategyDeploymentInstaller) installCertRequirementsForDeployment(deplo // Attempt an update // TODO: Check that the secret was not modified - if existingCAPEM, ok := existingSecret.Data[OLMCAPEMKey]; ok && !ShouldRotateCerts(i.owner.(*v1alpha1.ClusterServiceVersion)) { + if !shouldRotateCerts(existingSecret, HostnamesForService(serviceName, i.owner.GetNamespace())) { logger.Warnf("reusing existing cert %s", secret.GetName()) secret = existingSecret - caPEM = existingCAPEM + caPEM = existingSecret.Data[OLMCAPEMKey] caHash = certs.PEMSHA256(caPEM) } else { if _, err := i.strategyClient.GetOpClient().UpdateSecret(secret); err != nil { diff --git a/pkg/controller/install/resolver.go b/pkg/controller/install/resolver.go index 206ce9e3849..3e762fefe4f 100644 --- a/pkg/controller/install/resolver.go +++ b/pkg/controller/install/resolver.go @@ -21,6 +21,7 @@ type Strategy interface { type StrategyInstaller interface { Install(strategy Strategy) error CheckInstalled(strategy Strategy) (bool, error) + ShouldRotateCerts(strategy Strategy) (bool, error) CertsRotateAt() time.Time CertsRotated() bool } @@ -79,3 +80,7 @@ func (i *NullStrategyInstaller) CertsRotateAt() time.Time { func (i *NullStrategyInstaller) CertsRotated() bool { return false } + +func (i *NullStrategyInstaller) ShouldRotateCerts(s Strategy) (bool, error) { + return false, nil +} diff --git a/pkg/controller/operators/olm/apiservices.go b/pkg/controller/operators/olm/apiservices.go index 3faa32a5e4b..240fb2cb20a 100644 --- a/pkg/controller/operators/olm/apiservices.go +++ b/pkg/controller/operators/olm/apiservices.go @@ -93,17 +93,12 @@ func (a *Operator) checkAPIServiceResources(csv *v1alpha1.ClusterServiceVersion, // Check if CA is Active caBundle := apiService.Spec.CABundle - ca, err := certs.PEMToCert(caBundle) + _, err = certs.PEMToCert(caBundle) if err != nil { logger.Warnf("could not convert APIService CA bundle to x509 cert") errs = append(errs, err) continue } - if !certs.Active(ca) { - logger.Warnf("CA cert not active") - errs = append(errs, fmt.Errorf("found the CA cert is not active")) - continue - } // Check if serving cert is active secretName := install.SecretName(serviceName) @@ -113,17 +108,12 @@ func (a *Operator) checkAPIServiceResources(csv *v1alpha1.ClusterServiceVersion, errs = append(errs, err) continue } - cert, err := certs.PEMToCert(secret.Data["tls.crt"]) + _, err = certs.PEMToCert(secret.Data["tls.crt"]) if err != nil { logger.Warnf("could not convert serving cert to x509 cert") errs = append(errs, err) continue } - if !certs.Active(cert) { - logger.Warnf("serving cert not active") - errs = append(errs, fmt.Errorf("found the serving cert not active")) - continue - } // Check if CA hash matches expected caHash := hashFunc(caBundle) @@ -133,18 +123,6 @@ func (a *Operator) checkAPIServiceResources(csv *v1alpha1.ClusterServiceVersion, continue } - // Check if serving cert is trusted by the CA - hosts := []string{ - fmt.Sprintf("%s.%s", service.GetName(), csv.GetNamespace()), - fmt.Sprintf("%s.%s.svc", service.GetName(), csv.GetNamespace()), - } - for _, host := range hosts { - if err := certs.VerifyCert(ca, cert, host); err != nil { - errs = append(errs, fmt.Errorf("could not verify cert: %s", err.Error())) - continue - } - } - // Ensure the existing Deployment has a matching CA hash annotation deployment, err := a.lister.AppsV1().DeploymentLister().Deployments(csv.GetNamespace()).Get(desc.DeploymentName) if apierrors.IsNotFound(err) || err != nil { diff --git a/pkg/controller/operators/olm/operator.go b/pkg/controller/operators/olm/operator.go index b7f9c26ffab..70902b84372 100644 --- a/pkg/controller/operators/olm/operator.go +++ b/pkg/controller/operators/olm/operator.go @@ -2242,7 +2242,10 @@ func (a *Operator) transitionCSVState(in v1alpha1.ClusterServiceVersion) (out *v } // Check if it's time to refresh owned APIService certs - if install.ShouldRotateCerts(out) { + if shouldRotate, err := installer.ShouldRotateCerts(strategy); err != nil { + logger.WithError(err).Info("cert validity check") + return + } else if shouldRotate { logger.Debug("CSV owns resources that require a cert refresh") out.SetPhaseWithEvent(v1alpha1.CSVPhasePending, v1alpha1.CSVReasonNeedsCertRotation, "CSV owns resources that require a cert refresh", now, a.recorder) return @@ -2352,7 +2355,10 @@ func (a *Operator) transitionCSVState(in v1alpha1.ClusterServiceVersion) (out *v } // Check if it's time to refresh owned APIService certs - if install.ShouldRotateCerts(out) { + if shouldRotate, err := installer.ShouldRotateCerts(strategy); err != nil { + logger.WithError(err).Info("cert validity check") + return + } else if shouldRotate { logger.Debug("CSV owns resources that require a cert refresh") out.SetPhaseWithEvent(v1alpha1.CSVPhasePending, v1alpha1.CSVReasonNeedsCertRotation, "owned APIServices need cert refresh", now, a.recorder) return diff --git a/pkg/controller/operators/olm/operator_test.go b/pkg/controller/operators/olm/operator_test.go index 492cc020ce2..ee9bbb893e2 100644 --- a/pkg/controller/operators/olm/operator_test.go +++ b/pkg/controller/operators/olm/operator_test.go @@ -101,6 +101,10 @@ func (i *TestInstaller) CheckInstalled(s install.Strategy) (bool, error) { return true, nil } +func (i *TestInstaller) ShouldRotateCerts(s install.Strategy) (bool, error) { + return false, nil +} + func (i *TestInstaller) CertsRotateAt() time.Time { return time.Time{} } @@ -1956,26 +1960,26 @@ func TestTransitionCSV(t *testing.T) { }, clientObjs: []runtime.Object{defaultOperatorGroup}, apis: []runtime.Object{ - apiService("a1", "v1", "v1-a1", namespace, "a1", expiredCAPEM, apiregistrationv1.ConditionTrue, ownerLabelFromCSV("csv1", namespace)), + apiService("a1", "v1", install.ServiceName("a1"), namespace, "a1", expiredCAPEM, apiregistrationv1.ConditionTrue, ownerLabelFromCSV("csv1", namespace)), }, objs: []runtime.Object{ deployment("a1", namespace, "sa", addAnnotations(defaultTemplateAnnotations, map[string]string{ install.OLMCAHashAnnotationKey: expiredCAHash, })), - withAnnotations(keyPairToTLSSecret("v1.a1-cert", namespace, signedServingPair(time.Now().Add(24*time.Hour), expiredCA, []string{"v1-a1.ns", "v1-a1.ns.svc"})), map[string]string{ + withAnnotations(keyPairToTLSSecret(install.SecretName(install.ServiceName("a1")), namespace, signedServingPair(time.Now().Add(24*time.Hour), expiredCA, install.HostnamesForService(install.ServiceName("a1"), "ns"))), map[string]string{ install.OLMCAHashAnnotationKey: expiredCAHash, }), - service("v1-a1", namespace, "a1", 80), + service(install.ServiceName("a1"), namespace, "a1", 80), serviceAccount("sa", namespace), - role("v1.a1-cert", namespace, []rbacv1.PolicyRule{ + role(install.SecretName(install.ServiceName("a1")), namespace, []rbacv1.PolicyRule{ { Verbs: []string{"get"}, APIGroups: []string{""}, Resources: []string{"secrets"}, - ResourceNames: []string{"v1.a1-cert"}, + ResourceNames: []string{install.SecretName(install.ServiceName("a1"))}, }, }), - roleBinding("v1.a1-cert", namespace, "v1.a1-cert", "sa", namespace), + roleBinding(install.SecretName(install.ServiceName("a1")), namespace, install.SecretName(install.ServiceName("a1")), "sa", namespace), role("extension-apiserver-authentication-reader", "kube-system", []rbacv1.PolicyRule{ { Verbs: []string{"get"}, @@ -1984,7 +1988,7 @@ func TestTransitionCSV(t *testing.T) { ResourceNames: []string{"extension-apiserver-authentication"}, }, }), - roleBinding("v1.a1-auth-reader", "kube-system", "extension-apiserver-authentication-reader", "sa", namespace), + roleBinding(install.AuthReaderRoleBindingName(install.ServiceName("a1")), "kube-system", "extension-apiserver-authentication-reader", "sa", namespace), clusterRole("system:auth-delegator", []rbacv1.PolicyRule{ { Verbs: []string{"create"}, @@ -1997,7 +2001,7 @@ func TestTransitionCSV(t *testing.T) { Resources: []string{"subjectaccessreviews"}, }, }), - clusterRoleBinding("v1.a1-system:auth-delegator", "system:auth-delegator", "sa", namespace), + clusterRoleBinding(install.AuthDelegatorClusterRoleBindingName(install.ServiceName("a1")), "system:auth-delegator", "sa", namespace), }, crds: []runtime.Object{ crd("c1", "v1", "g1"), @@ -2005,7 +2009,7 @@ func TestTransitionCSV(t *testing.T) { }, expected: expected{ csvStates: map[string]csvState{ - "csv1": {exists: true, phase: v1alpha1.CSVPhaseFailed, reason: v1alpha1.CSVReasonAPIServiceResourceIssue}, + "csv1": {exists: true, phase: v1alpha1.CSVPhaseFailed, reason: v1alpha1.CSVReasonNeedsCertRotation}, }, }, }, @@ -2025,26 +2029,26 @@ func TestTransitionCSV(t *testing.T) { }, clientObjs: []runtime.Object{defaultOperatorGroup}, apis: []runtime.Object{ - apiService("a1", "v1", "v1-a1", namespace, "a1", expiredCAPEM, apiregistrationv1.ConditionTrue, ownerLabelFromCSV("csv1", namespace)), + apiService("a1", "v1", install.ServiceName("a1"), namespace, "a1", expiredCAPEM, apiregistrationv1.ConditionTrue, ownerLabelFromCSV("csv1", namespace)), }, objs: []runtime.Object{ deployment("a1", namespace, "sa", addAnnotations(defaultTemplateAnnotations, map[string]string{ install.OLMCAHashAnnotationKey: expiredCAHash, })), - withAnnotations(keyPairToTLSSecret("v1.a1-cert", namespace, signedServingPair(time.Now().Add(24*time.Hour), expiredCA, []string{"v1-a1.ns", "v1-a1.ns.svc"})), map[string]string{ + withAnnotations(keyPairToTLSSecret(install.SecretName(install.ServiceName("a1")), namespace, signedServingPair(time.Now().Add(24*time.Hour), expiredCA, install.HostnamesForService(install.ServiceName("a1"), "ns"))), map[string]string{ install.OLMCAHashAnnotationKey: expiredCAHash, }), - service("v1-a1", namespace, "a1", 80), + service(install.ServiceName("a1"), namespace, "a1", 80), serviceAccount("sa", namespace), - role("v1.a1-cert", namespace, []rbacv1.PolicyRule{ + role(install.SecretName(install.ServiceName("a1")), namespace, []rbacv1.PolicyRule{ { Verbs: []string{"get"}, APIGroups: []string{""}, Resources: []string{"secrets"}, - ResourceNames: []string{"v1.a1-cert"}, + ResourceNames: []string{install.SecretName(install.ServiceName("a1"))}, }, }), - roleBinding("v1.a1-cert", namespace, "v1.a1-cert", "sa", namespace), + roleBinding(install.SecretName(install.ServiceName("a1")), namespace, install.SecretName(install.ServiceName("a1")), "sa", namespace), role("extension-apiserver-authentication-reader", "kube-system", []rbacv1.PolicyRule{ { Verbs: []string{"get"}, @@ -2053,7 +2057,7 @@ func TestTransitionCSV(t *testing.T) { ResourceNames: []string{"extension-apiserver-authentication"}, }, }), - roleBinding("v1.a1-auth-reader", "kube-system", "extension-apiserver-authentication-reader", "sa", namespace), + roleBinding(install.AuthReaderRoleBindingName(install.ServiceName("a1")), "kube-system", "extension-apiserver-authentication-reader", "sa", namespace), clusterRole("system:auth-delegator", []rbacv1.PolicyRule{ { Verbs: []string{"create"}, @@ -2066,7 +2070,7 @@ func TestTransitionCSV(t *testing.T) { Resources: []string{"subjectaccessreviews"}, }, }), - clusterRoleBinding("v1.a1-system:auth-delegator", "system:auth-delegator", "sa", namespace), + clusterRoleBinding(install.AuthDelegatorClusterRoleBindingName(install.ServiceName("a1")), "system:auth-delegator", "sa", namespace), }, crds: []runtime.Object{ crd("c1", "v1", "g1"), @@ -2074,7 +2078,7 @@ func TestTransitionCSV(t *testing.T) { }, expected: expected{ csvStates: map[string]csvState{ - "csv1": {exists: true, phase: v1alpha1.CSVPhasePending, reason: v1alpha1.CSVReasonAPIServiceResourcesNeedReinstall}, + "csv1": {exists: true, phase: v1alpha1.CSVPhasePending, reason: v1alpha1.CSVReasonNeedsCertRotation}, }, }, }, diff --git a/pkg/fakes/fake_strategy_installer.go b/pkg/fakes/fake_strategy_installer.go index a61b0c9c6e9..f3bad53359f 100644 --- a/pkg/fakes/fake_strategy_installer.go +++ b/pkg/fakes/fake_strategy_installer.go @@ -53,6 +53,19 @@ type FakeStrategyInstaller struct { installReturnsOnCall map[int]struct { result1 error } + ShouldRotateCertsStub func(install.Strategy) (bool, error) + shouldRotateCertsMutex sync.RWMutex + shouldRotateCertsArgsForCall []struct { + arg1 install.Strategy + } + shouldRotateCertsReturns struct { + result1 bool + result2 error + } + shouldRotateCertsReturnsOnCall map[int]struct { + result1 bool + result2 error + } invocations map[string][][]interface{} invocationsMutex sync.RWMutex } @@ -288,6 +301,70 @@ func (fake *FakeStrategyInstaller) InstallReturnsOnCall(i int, result1 error) { }{result1} } +func (fake *FakeStrategyInstaller) ShouldRotateCerts(arg1 install.Strategy) (bool, error) { + fake.shouldRotateCertsMutex.Lock() + ret, specificReturn := fake.shouldRotateCertsReturnsOnCall[len(fake.shouldRotateCertsArgsForCall)] + fake.shouldRotateCertsArgsForCall = append(fake.shouldRotateCertsArgsForCall, struct { + arg1 install.Strategy + }{arg1}) + stub := fake.ShouldRotateCertsStub + fakeReturns := fake.shouldRotateCertsReturns + fake.recordInvocation("ShouldRotateCerts", []interface{}{arg1}) + fake.shouldRotateCertsMutex.Unlock() + if stub != nil { + return stub(arg1) + } + if specificReturn { + return ret.result1, ret.result2 + } + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *FakeStrategyInstaller) ShouldRotateCertsCallCount() int { + fake.shouldRotateCertsMutex.RLock() + defer fake.shouldRotateCertsMutex.RUnlock() + return len(fake.shouldRotateCertsArgsForCall) +} + +func (fake *FakeStrategyInstaller) ShouldRotateCertsCalls(stub func(install.Strategy) (bool, error)) { + fake.shouldRotateCertsMutex.Lock() + defer fake.shouldRotateCertsMutex.Unlock() + fake.ShouldRotateCertsStub = stub +} + +func (fake *FakeStrategyInstaller) ShouldRotateCertsArgsForCall(i int) install.Strategy { + fake.shouldRotateCertsMutex.RLock() + defer fake.shouldRotateCertsMutex.RUnlock() + argsForCall := fake.shouldRotateCertsArgsForCall[i] + return argsForCall.arg1 +} + +func (fake *FakeStrategyInstaller) ShouldRotateCertsReturns(result1 bool, result2 error) { + fake.shouldRotateCertsMutex.Lock() + defer fake.shouldRotateCertsMutex.Unlock() + fake.ShouldRotateCertsStub = nil + fake.shouldRotateCertsReturns = struct { + result1 bool + result2 error + }{result1, result2} +} + +func (fake *FakeStrategyInstaller) ShouldRotateCertsReturnsOnCall(i int, result1 bool, result2 error) { + fake.shouldRotateCertsMutex.Lock() + defer fake.shouldRotateCertsMutex.Unlock() + fake.ShouldRotateCertsStub = nil + if fake.shouldRotateCertsReturnsOnCall == nil { + fake.shouldRotateCertsReturnsOnCall = make(map[int]struct { + result1 bool + result2 error + }) + } + fake.shouldRotateCertsReturnsOnCall[i] = struct { + result1 bool + result2 error + }{result1, result2} +} + func (fake *FakeStrategyInstaller) Invocations() map[string][][]interface{} { fake.invocationsMutex.RLock() defer fake.invocationsMutex.RUnlock() @@ -299,6 +376,8 @@ func (fake *FakeStrategyInstaller) Invocations() map[string][][]interface{} { defer fake.checkInstalledMutex.RUnlock() fake.installMutex.RLock() defer fake.installMutex.RUnlock() + fake.shouldRotateCertsMutex.RLock() + defer fake.shouldRotateCertsMutex.RUnlock() copiedInvocations := map[string][][]interface{}{} for key, value := range fake.invocations { copiedInvocations[key] = value diff --git a/test/e2e/csv_e2e_test.go b/test/e2e/csv_e2e_test.go index 132571910e9..7934c339c15 100644 --- a/test/e2e/csv_e2e_test.go +++ b/test/e2e/csv_e2e_test.go @@ -1657,7 +1657,7 @@ var _ = Describe("ClusterServiceVersion", func() { <-deleted }() - fetchedCSV, err := fetchCSV(crc, generatedNamespace.GetName(), csv.Name, csvSucceededChecker) + _, err = fetchCSV(crc, generatedNamespace.GetName(), csv.Name, csvSucceededChecker) Expect(err).ShouldNot(HaveOccurred()) By("Should create Deployment") @@ -1698,11 +1698,15 @@ var _ = Describe("ClusterServiceVersion", func() { oldCAAnnotation, ok := dep.Spec.Template.GetAnnotations()[install.OLMCAHashAnnotationKey] Expect(ok).Should(BeTrue(), "expected olm sha annotation not present on existing pod template") - By("Induce a cert rotation") - Eventually(Apply(fetchedCSV, func(csv *operatorsv1alpha1.ClusterServiceVersion) error { - now := metav1.Now() - csv.Status.CertsLastUpdated = &now - csv.Status.CertsRotateAt = &now + caSecret, err := c.KubernetesInterface().CoreV1().Secrets(generatedNamespace.GetName()).Get(context.TODO(), secretName, metav1.GetOptions{}) + Expect(err).Should(BeNil()) + + caPEM, certPEM, privPEM := generateExpiredCerts(install.HostnamesForService(serviceName, generatedNamespace.GetName())) + By(`Induce a cert rotation`) + Eventually(Apply(caSecret, func(caSecret *corev1.Secret) error { + caSecret.Data[install.OLMCAPEMKey] = caPEM + caSecret.Data["tls.crt"] = certPEM + caSecret.Data["tls.key"] = privPEM return nil })).Should(Succeed()) @@ -1737,7 +1741,7 @@ var _ = Describe("ClusterServiceVersion", func() { Expect(err).ShouldNot(HaveOccurred()) By("Wait for CSV success") - fetchedCSV, err = fetchCSV(crc, generatedNamespace.GetName(), csv.GetName(), func(csv *operatorsv1alpha1.ClusterServiceVersion) bool { + _, err = fetchCSV(crc, generatedNamespace.GetName(), csv.GetName(), func(csv *operatorsv1alpha1.ClusterServiceVersion) bool { By("Should create an APIService") apiService, err := c.GetAPIService(apiServiceName) if err != nil { diff --git a/test/e2e/installplan_e2e_test.go b/test/e2e/installplan_e2e_test.go index da683845e3e..b6739251631 100644 --- a/test/e2e/installplan_e2e_test.go +++ b/test/e2e/installplan_e2e_test.go @@ -2263,12 +2263,13 @@ var _ = Describe("Install Plan", func() { Permissions: permissions, ClusterPermissions: clusterPermissions, } - csv.Spec.InstallStrategy = operatorsv1alpha1.NamedInstallStrategy{ - StrategyName: operatorsv1alpha1.InstallStrategyNameDeployment, - StrategySpec: modifiedDetails, - } - _, err = crc.OperatorsV1alpha1().ClusterServiceVersions(generatedNamespace.GetName()).Update(context.Background(), csv, metav1.UpdateOptions{}) - require.NoError(GinkgoT(), err) + Eventually(Apply(csv, func(csv *operatorsv1alpha1.ClusterServiceVersion) error { + csv.Spec.InstallStrategy = operatorsv1alpha1.NamedInstallStrategy{ + StrategyName: operatorsv1alpha1.InstallStrategyNameDeployment, + StrategySpec: modifiedDetails, + } + return nil + })).Should(Succeed()) By(`Wait for csv to update`) _, err = fetchCSV(crc, generatedNamespace.GetName(), csv.GetName(), csvSucceededChecker) diff --git a/test/e2e/webhook_e2e_test.go b/test/e2e/webhook_e2e_test.go index dcd601e04d3..5468b11ef5d 100644 --- a/test/e2e/webhook_e2e_test.go +++ b/test/e2e/webhook_e2e_test.go @@ -2,7 +2,14 @@ package e2e import ( "context" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/x509" + "crypto/x509/pkix" "fmt" + "math" + "math/big" "strings" "time" @@ -21,6 +28,7 @@ import ( v1 "github.com/operator-framework/api/pkg/operators/v1" operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned" + "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/certs" "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/install" "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorclient" ) @@ -455,7 +463,7 @@ var _ = Describe("CSVs with a Webhook", func() { cleanupCSV, err = createCSV(c, crc, csv, generatedNamespace.GetName(), false, false) Expect(err).Should(BeNil()) - fetchedCSV, err := fetchCSV(crc, generatedNamespace.GetName(), csv.Name, csvSucceededChecker) + _, err = fetchCSV(crc, generatedNamespace.GetName(), csv.Name, csvSucceededChecker) Expect(err).Should(BeNil()) actualWebhook, err := getWebhookWithGenerateName(c, webhook.GenerateName) @@ -471,11 +479,15 @@ var _ = Describe("CSVs with a Webhook", func() { oldCAAnnotation, ok := dep.Spec.Template.GetAnnotations()[install.OLMCAHashAnnotationKey] Expect(ok).Should(BeTrue()) + caSecret, err := c.KubernetesInterface().CoreV1().Secrets(generatedNamespace.GetName()).Get(context.TODO(), install.SecretName(install.ServiceName(dep.Name)), metav1.GetOptions{}) + Expect(err).Should(BeNil()) + + caPEM, certPEM, privPEM := generateExpiredCerts(install.HostnamesForService(install.ServiceName(dep.Name), generatedNamespace.GetName())) By(`Induce a cert rotation`) - Eventually(Apply(fetchedCSV, func(csv *operatorsv1alpha1.ClusterServiceVersion) error { - now := metav1.Now() - csv.Status.CertsLastUpdated = &now - csv.Status.CertsRotateAt = &now + Eventually(Apply(caSecret, func(caSecret *corev1.Secret) error { + caSecret.Data[install.OLMCAPEMKey] = caPEM + caSecret.Data["tls.crt"] = certPEM + caSecret.Data["tls.key"] = privPEM return nil })).Should(Succeed()) @@ -1169,3 +1181,70 @@ func newV1CRD(plural string) apiextensionsv1.CustomResourceDefinition { return crd } + +func generateExpiredCerts(hosts []string) ([]byte, []byte, []byte) { + caSerial, err := rand.Int(rand.Reader, new(big.Int).SetInt64(math.MaxInt64)) + Expect(err).Should(BeNil()) + + caDetails := &x509.Certificate{ + SerialNumber: caSerial, + Subject: pkix.Name{ + CommonName: fmt.Sprintf("olm-selfsigned-%x", caSerial), + Organization: []string{install.Organization}, + }, + NotBefore: time.Now(), + NotAfter: time.Now(), + IsCA: true, + KeyUsage: x509.KeyUsageCertSign, + BasicConstraintsValid: true, + } + + caKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + Expect(err).Should(BeNil()) + + caRaw, err := x509.CreateCertificate(rand.Reader, caDetails, caDetails, &caKey.PublicKey, caKey) + Expect(err).Should(BeNil()) + + caCert, err := x509.ParseCertificate(caRaw) + Expect(err).Should(BeNil()) + + caPEM, _, err := (&certs.KeyPair{ + Cert: caCert, + Priv: caKey, + }).ToPEM() + Expect(err).Should(BeNil()) + + serial, err := rand.Int(rand.Reader, new(big.Int).SetInt64(math.MaxInt64)) + Expect(err).Should(BeNil()) + + certDetails := &x509.Certificate{ + SerialNumber: serial, + Subject: pkix.Name{ + CommonName: hosts[0], + Organization: []string{install.Organization}, + }, + NotBefore: time.Now(), + NotAfter: time.Now(), + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + BasicConstraintsValid: true, + DNSNames: hosts, + } + + privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + Expect(err).Should(BeNil()) + + publicKey := &privateKey.PublicKey + certRaw, err := x509.CreateCertificate(rand.Reader, certDetails, caCert, publicKey, caKey) + Expect(err).Should(BeNil()) + + cert, err := x509.ParseCertificate(certRaw) + Expect(err).Should(BeNil()) + + certPEM, privPEM, err := (&certs.KeyPair{ + Cert: cert, + Priv: privateKey, + }).ToPEM() + Expect(err).Should(BeNil()) + + return caPEM, certPEM, privPEM +}