From a2791d7e1233e482258b78f2a2fae46ec47931f5 Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Thu, 8 Jun 2023 18:35:55 +0200 Subject: [PATCH 01/13] copy conformance tests from cert-manager Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- Makefile | 4 +- conformance/certificates/suite.go | 127 +++ conformance/certificates/tests.go | 808 ++++++++++++++++++ .../certificatesigningrequests/suite.go | 142 +++ .../certificatesigningrequests/tests.go | 427 +++++++++ conformance/framework/cleanup.go | 63 ++ conformance/framework/framework.go | 160 ++++ .../framework/helper/certificaterequests.go | 223 +++++ conformance/framework/helper/certificates.go | 329 +++++++ .../helper/certificatesigningrequests.go | 61 ++ .../framework/helper/featureset/featureset.go | 165 ++++ conformance/framework/helper/helper.go | 38 + conformance/framework/helper/secret.go | 59 ++ conformance/framework/helper/validate.go | 70 ++ .../validation/certificates/certificates.go | 437 ++++++++++ .../certificatesigningrequests.go | 369 ++++++++ .../framework/helper/validation/validation.go | 107 +++ conformance/framework/log/log.go | 83 ++ conformance/framework/testenv.go | 89 ++ conformance/framework/util.go | 50 ++ conformance/rbac/certificate.go | 207 +++++ conformance/rbac/certificaterequest.go | 207 +++++ conformance/rbac/doc.go | 96 +++ conformance/rbac/issuer.go | 207 +++++ conformance/rbac/suite.go | 19 + conformance/util/domains.go | 46 + conformance/util/util.go | 427 +++++++++ go.mod | 10 + go.sum | 16 + .../simple/e2e/conformance/conformance.go | 120 +++ .../e2e/conformance/conformance_test.go | 14 + .../simple/e2e/conformance/issuer_builder.go | 103 +++ make/tools.mk | 2 + 33 files changed, 5284 insertions(+), 1 deletion(-) create mode 100644 conformance/certificates/suite.go create mode 100644 conformance/certificates/tests.go create mode 100644 conformance/certificatesigningrequests/suite.go create mode 100644 conformance/certificatesigningrequests/tests.go create mode 100644 conformance/framework/cleanup.go create mode 100644 conformance/framework/framework.go create mode 100644 conformance/framework/helper/certificaterequests.go create mode 100644 conformance/framework/helper/certificates.go create mode 100644 conformance/framework/helper/certificatesigningrequests.go create mode 100644 conformance/framework/helper/featureset/featureset.go create mode 100644 conformance/framework/helper/helper.go create mode 100644 conformance/framework/helper/secret.go create mode 100644 conformance/framework/helper/validate.go create mode 100644 conformance/framework/helper/validation/certificates/certificates.go create mode 100644 conformance/framework/helper/validation/certificatesigningrequests/certificatesigningrequests.go create mode 100644 conformance/framework/helper/validation/validation.go create mode 100644 conformance/framework/log/log.go create mode 100644 conformance/framework/testenv.go create mode 100644 conformance/framework/util.go create mode 100644 conformance/rbac/certificate.go create mode 100644 conformance/rbac/certificaterequest.go create mode 100644 conformance/rbac/doc.go create mode 100644 conformance/rbac/issuer.go create mode 100644 conformance/rbac/suite.go create mode 100644 conformance/util/domains.go create mode 100644 conformance/util/util.go create mode 100644 internal/testsetups/simple/e2e/conformance/conformance.go create mode 100644 internal/testsetups/simple/e2e/conformance/conformance_test.go create mode 100644 internal/testsetups/simple/e2e/conformance/issuer_builder.go diff --git a/Makefile b/Makefile index c599daa..5193c5e 100644 --- a/Makefile +++ b/Makefile @@ -162,9 +162,11 @@ test: test-unit-deps | $(NEEDS_GO) $(NEEDS_GOTESTSUM) ## Run unit tests. $(GOTESTSUM) ./... -coverprofile cover.out .PHONY: test-e2e -test-e2e: test-e2e-deps | $(NEEDS_GOTESTSUM) ## Run e2e tests. This creates a Kind cluster, installs dependencies, deploys the issuer-lib and runs the E2E tests. +test-e2e: test-e2e-deps | $(NEEDS_GOTESTSUM) $(NEEDS_GINKGO) ## Run e2e tests. This creates a Kind cluster, installs dependencies, deploys the issuer-lib and runs the E2E tests. $(GOTESTSUM) ./internal/testsetups/simple/e2e/... -coverprofile cover.out -timeout 1m + $(GINKGO) ./internal/testsetups/simple/e2e/conformance/... + ##@ Build .PHONY: build diff --git a/conformance/certificates/suite.go b/conformance/certificates/suite.go new file mode 100644 index 0000000..d6d0680 --- /dev/null +++ b/conformance/certificates/suite.go @@ -0,0 +1,127 @@ +/* +Copyright 2020 The cert-manager Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package certificates + +import ( + "context" + + . "github.com/onsi/ginkgo/v2" + "k8s.io/client-go/rest" + + cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" + "github.com/cert-manager/issuer-lib/conformance/framework" + "github.com/cert-manager/issuer-lib/conformance/framework/helper/featureset" +) + +// Suite defines a reusable conformance test suite that can be used against any +// Issuer implementation. +type Suite struct { + // KubeClientConfig is the configuration used to connect to the Kubernetes + // API server. + KubeClientConfig *rest.Config + + // Name is the name of the issuer being tested, e.g. SelfSigned, CA, ACME + // This field must be provided. + Name string + + // CreateIssuerFunc is a function that provisions a new issuer resource and + // returns an ObjectReference to that Issuer that will be used as the + // IssuerRef on Certificate resources that this suite creates. + // This field must be provided. + CreateIssuerFunc func(*framework.Framework, context.Context) cmmeta.ObjectReference + + // DeleteIssuerFunc is a function that is run after the test has completed + // in order to clean up resources created for a test (e.g. the resources + // created in CreateIssuerFunc). + // This function will be run regardless whether the test passes or fails. + // If not specified, this function will be skipped. + DeleteIssuerFunc func(*framework.Framework, context.Context, cmmeta.ObjectReference) + + // DomainSuffix is a suffix used on all domain requests. + // This is useful when the issuer being tested requires special + // configuration for a set of domains in order for certificates to be + // issued, such as the ACME issuer. + // If not set, this will be defaulted to the configured 'domain' for the + // nginx-ingress addon. + DomainSuffix string + + // UnsupportedFeatures is a list of features that are not supported by this + // invocation of the test suite. + // This is useful if a particular issuers explicitly does not support + // certain features due to restrictions in their implementation. + UnsupportedFeatures featureset.FeatureSet + + // completed is used internally to track whether Complete() has been called + completed bool +} + +// complete will validate configuration and set default values. +func (s *Suite) complete(f *framework.Framework) { + if s.Name == "" { + Fail("Name must be set") + } + + if s.CreateIssuerFunc == nil { + Fail("CreateIssuerFunc must be set") + } + + if s.DomainSuffix == "" { + s.DomainSuffix = "example.com" + } + + if s.UnsupportedFeatures == nil { + s.UnsupportedFeatures = make(featureset.FeatureSet) + } + + s.completed = true +} + +// it is called by the tests to in Define() to setup and run the test +func (s *Suite) it(f *framework.Framework, name string, fn func(cmmeta.ObjectReference), requiredFeatures ...featureset.Feature) { + if !s.checkFeatures(requiredFeatures...) { + return + } + It(name, func(ctx context.Context) { + By("Creating an issuer resource") + issuerRef := s.CreateIssuerFunc(f, ctx) + defer func() { + if s.DeleteIssuerFunc != nil { + By("Cleaning up the issuer resource") + s.DeleteIssuerFunc(f, ctx, issuerRef) + } + }() + fn(issuerRef) + }) +} + +// checkFeatures is a helper function that is used to ensure that the features +// required for a given test case are supported by the suite. +// It will return 'true' if all features are supported and the test should run, +// or return 'false' if any required feature is not supported. +func (s *Suite) checkFeatures(fs ...featureset.Feature) bool { + unsupported := make(featureset.FeatureSet) + for _, f := range fs { + if s.UnsupportedFeatures.Contains(f) { + unsupported.Add(f) + } + } + // all features supported, return early! + if len(unsupported) == 0 { + return true + } + return false +} diff --git a/conformance/certificates/tests.go b/conformance/certificates/tests.go new file mode 100644 index 0000000..9560eba --- /dev/null +++ b/conformance/certificates/tests.go @@ -0,0 +1,808 @@ +/* +Copyright 2020 The cert-manager Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package certificates + +import ( + "context" + "crypto/x509/pkix" + "encoding/asn1" + "encoding/base64" + "fmt" + "reflect" + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/util/retry" + + cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" + "github.com/cert-manager/cert-manager/pkg/util/pki" + "github.com/cert-manager/issuer-lib/conformance/framework" + "github.com/cert-manager/issuer-lib/conformance/framework/helper/featureset" + "github.com/cert-manager/issuer-lib/conformance/framework/helper/validation" + "github.com/cert-manager/issuer-lib/conformance/framework/helper/validation/certificates" + e2eutil "github.com/cert-manager/issuer-lib/conformance/util" +) + +// Define defines simple conformance tests that can be run against any issuer type. +// If Complete has not been called on this Suite before Define, it will be +// automatically called. +func (s *Suite) Define() { + Describe("with issuer type "+s.Name, func() { + ctx := context.Background() + f := framework.NewFramework("certificates", s.KubeClientConfig) + + sharedIPAddress := "127.0.0.1" + + // Wrap this in a BeforeEach else flags will not have been parsed and + // f.Config will not be populated at the time that this code is run. + BeforeEach(func() { + if s.completed { + return + } + s.complete(f) + }) + + s.it(f, "should issue a basic, defaulted certificate for a single distinct DNS Name", func(issuerRef cmmeta.ObjectReference) { + testCertificate := &cmapi.Certificate{ + ObjectMeta: metav1.ObjectMeta{ + Name: "testcert", + Namespace: f.Namespace.Name, + }, + Spec: cmapi.CertificateSpec{ + SecretName: "testcert-tls", + IssuerRef: issuerRef, + DNSNames: []string{e2eutil.RandomSubdomain(s.DomainSuffix)}, + }, + } + By("Creating a Certificate") + err := f.CRClient.Create(ctx, testCertificate) + Expect(err).NotTo(HaveOccurred()) + + By("Waiting for the Certificate to be issued...") + testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testCertificate, time.Minute*8) + Expect(err).NotTo(HaveOccurred()) + + By("Validating the issued Certificate...") + err = f.Helper().ValidateCertificate(testCertificate, validation.CertificateSetForUnsupportedFeatureSet(s.UnsupportedFeatures)...) + Expect(err).NotTo(HaveOccurred()) + }, featureset.OnlySAN) + + s.it(f, "should issue a CA certificate with the CA basicConstraint set", func(issuerRef cmmeta.ObjectReference) { + testCertificate := &cmapi.Certificate{ + ObjectMeta: metav1.ObjectMeta{ + Name: "testcert", + Namespace: f.Namespace.Name, + }, + Spec: cmapi.CertificateSpec{ + SecretName: "testcert-tls", + IsCA: true, + IssuerRef: issuerRef, + DNSNames: []string{e2eutil.RandomSubdomain(s.DomainSuffix)}, + }, + } + By("Creating a Certificate") + err := f.CRClient.Create(ctx, testCertificate) + Expect(err).NotTo(HaveOccurred()) + + By("Waiting for the Certificate to be issued...") + testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testCertificate, time.Minute*8) + Expect(err).NotTo(HaveOccurred()) + + By("Validating the issued Certificate...") + err = f.Helper().ValidateCertificate(testCertificate, validation.CertificateSetForUnsupportedFeatureSet(s.UnsupportedFeatures)...) + Expect(err).NotTo(HaveOccurred()) + }, featureset.IssueCAFeature) + + s.it(f, "should issue an ECDSA, defaulted certificate for a single distinct DNS Name", func(issuerRef cmmeta.ObjectReference) { + testCertificate := &cmapi.Certificate{ + ObjectMeta: metav1.ObjectMeta{ + Name: "testcert", + Namespace: f.Namespace.Name, + }, + Spec: cmapi.CertificateSpec{ + SecretName: "testcert-tls", + PrivateKey: &cmapi.CertificatePrivateKey{ + Algorithm: cmapi.ECDSAKeyAlgorithm, + }, + DNSNames: []string{e2eutil.RandomSubdomain(s.DomainSuffix)}, + IssuerRef: issuerRef, + }, + } + By("Creating a Certificate") + err := f.CRClient.Create(ctx, testCertificate) + Expect(err).NotTo(HaveOccurred()) + + By("Waiting for the Certificate to be issued...") + testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testCertificate, time.Minute*8) + Expect(err).NotTo(HaveOccurred()) + + By("Validating the issued Certificate...") + err = f.Helper().ValidateCertificate(testCertificate, validation.CertificateSetForUnsupportedFeatureSet(s.UnsupportedFeatures)...) + Expect(err).NotTo(HaveOccurred()) + }, featureset.ECDSAFeature, featureset.OnlySAN) + + s.it(f, "should issue an Ed25519, defaulted certificate for a single distinct DNS Name", func(issuerRef cmmeta.ObjectReference) { + testCertificate := &cmapi.Certificate{ + ObjectMeta: metav1.ObjectMeta{ + Name: "testcert", + Namespace: f.Namespace.Name, + }, + Spec: cmapi.CertificateSpec{ + SecretName: "testcert-tls", + PrivateKey: &cmapi.CertificatePrivateKey{ + Algorithm: cmapi.Ed25519KeyAlgorithm, + }, + DNSNames: []string{e2eutil.RandomSubdomain(s.DomainSuffix)}, + IssuerRef: issuerRef, + }, + } + By("Creating a Certificate") + err := f.CRClient.Create(ctx, testCertificate) + Expect(err).NotTo(HaveOccurred()) + + By("Waiting for the Certificate to be issued...") + testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testCertificate, time.Minute*8) + Expect(err).NotTo(HaveOccurred()) + + By("Validating the issued Certificate...") + err = f.Helper().ValidateCertificate(testCertificate, validation.CertificateSetForUnsupportedFeatureSet(s.UnsupportedFeatures)...) + Expect(err).NotTo(HaveOccurred()) + }, featureset.OnlySAN, featureset.Ed25519FeatureSet) + + s.it(f, "should issue a basic, defaulted certificate for a single Common Name", func(issuerRef cmmeta.ObjectReference) { + // Some issuers use the CN to define the cert's "ID" + // if one cert manages to be in an error state in the issuer it might throw an error + // this makes the CN more unique + cn := "test-common-name-" + e2eutil.RandStringRunes(10) + testCertificate := &cmapi.Certificate{ + ObjectMeta: metav1.ObjectMeta{ + Name: "testcert", + Namespace: f.Namespace.Name, + }, + Spec: cmapi.CertificateSpec{ + SecretName: "testcert-tls", + IssuerRef: issuerRef, + CommonName: cn, + }, + } + By("Creating a Certificate") + err := f.CRClient.Create(ctx, testCertificate) + Expect(err).NotTo(HaveOccurred()) + + By("Waiting for the Certificate to be issued...") + testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testCertificate, time.Minute*8) + Expect(err).NotTo(HaveOccurred()) + + By("Validating the issued Certificate...") + err = f.Helper().ValidateCertificate(testCertificate, validation.CertificateSetForUnsupportedFeatureSet(s.UnsupportedFeatures)...) + Expect(err).NotTo(HaveOccurred()) + }, featureset.CommonNameFeature) + + s.it(f, "should issue a basic, defaulted certificate for a single distinct DNS Name with a literal subject", func(issuerRef cmmeta.ObjectReference) { + // framework.RequireFeatureGate(f, utilfeature.DefaultFeatureGate, feature.LiteralCertificateSubject) + // Some issuers use the CN to define the cert's "ID" + // if one cert manages to be in an error state in the issuer it might throw an error + // this makes the CN more unique + host := fmt.Sprintf("*.%s.foo-long.bar.com", e2eutil.RandStringRunes(10)) + literalSubject := fmt.Sprintf("CN=%s,OU=FooLong,OU=Bar,OU=Baz,OU=Dept.,O=Corp.", host) + testCertificate := &cmapi.Certificate{ + ObjectMeta: metav1.ObjectMeta{ + Name: "testcert", + Namespace: f.Namespace.Name, + }, + Spec: cmapi.CertificateSpec{ + SecretName: "testcert-tls", + IssuerRef: issuerRef, + LiteralSubject: literalSubject, + DNSNames: []string{host}, + }, + } + By("Creating a Certificate") + err := f.CRClient.Create(ctx, testCertificate) + Expect(err).NotTo(HaveOccurred()) + + By("Waiting for the Certificate to be issued...") + + testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testCertificate, time.Minute*5) + Expect(err).NotTo(HaveOccurred()) + + //type ValidationFunc func(certificate *cmapi.Certificate, secret *corev1.Secret) error + valFunc := func(certificate *cmapi.Certificate, secret *corev1.Secret) error { + certBytes, ok := secret.Data[corev1.TLSCertKey] + if !ok { + return fmt.Errorf("no certificate data found for Certificate %q (secret %q)", certificate.Name, certificate.Spec.SecretName) + } + + createdCert, err := pki.DecodeX509CertificateBytes(certBytes) + if err != nil { + return err + } + + var dns pkix.RDNSequence + rest, err := asn1.Unmarshal(createdCert.RawSubject, &dns) + + if err != nil { + return err + } + + rdnSeq, err2 := pki.UnmarshalSubjectStringToRDNSequence(literalSubject) + + if err2 != nil { + return err2 + } + + fmt.Fprintln(GinkgoWriter, "cert", base64.StdEncoding.EncodeToString(createdCert.RawSubject), dns, err, rest) + if !reflect.DeepEqual(rdnSeq, dns) { + return fmt.Errorf("generated certificate's subject [%s] does not match expected subject [%s]", dns.String(), literalSubject) + } + return nil + } + + By("Validating the issued Certificate...") + + err = f.Helper().ValidateCertificate(testCertificate, valFunc) + Expect(err).NotTo(HaveOccurred()) + }, featureset.LiteralSubjectFeature) + + s.it(f, "should issue an ECDSA, defaulted certificate for a single Common Name", func(issuerRef cmmeta.ObjectReference) { + // Some issuers use the CN to define the cert's "ID" + // if one cert manages to be in an error state in the issuer it might throw an error + // this makes the CN more unique + cn := "test-common-name-" + e2eutil.RandStringRunes(10) + testCertificate := &cmapi.Certificate{ + ObjectMeta: metav1.ObjectMeta{ + Name: "testcert", + Namespace: f.Namespace.Name, + }, + Spec: cmapi.CertificateSpec{ + SecretName: "testcert-tls", + PrivateKey: &cmapi.CertificatePrivateKey{ + Algorithm: cmapi.ECDSAKeyAlgorithm, + }, + CommonName: cn, + IssuerRef: issuerRef, + }, + } + By("Creating a Certificate") + err := f.CRClient.Create(ctx, testCertificate) + Expect(err).NotTo(HaveOccurred()) + + By("Waiting for the Certificate to be issued...") + testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testCertificate, time.Minute*8) + Expect(err).NotTo(HaveOccurred()) + + By("Validating the issued Certificate...") + err = f.Helper().ValidateCertificate(testCertificate, validation.CertificateSetForUnsupportedFeatureSet(s.UnsupportedFeatures)...) + Expect(err).NotTo(HaveOccurred()) + }, featureset.ECDSAFeature, featureset.CommonNameFeature) + + s.it(f, "should issue an Ed25519, defaulted certificate for a single Common Name", func(issuerRef cmmeta.ObjectReference) { + // Some issuers use the CN to define the cert's "ID" + // if one cert manages to be in an error state in the issuer it might throw an error + // this makes the CN more unique + cn := "test-common-name-" + e2eutil.RandStringRunes(10) + testCertificate := &cmapi.Certificate{ + ObjectMeta: metav1.ObjectMeta{ + Name: "testcert", + Namespace: f.Namespace.Name, + }, + Spec: cmapi.CertificateSpec{ + SecretName: "testcert-tls", + PrivateKey: &cmapi.CertificatePrivateKey{ + Algorithm: cmapi.Ed25519KeyAlgorithm, + }, + CommonName: cn, + IssuerRef: issuerRef, + }, + } + By("Creating a Certificate") + err := f.CRClient.Create(ctx, testCertificate) + Expect(err).NotTo(HaveOccurred()) + + By("Waiting for the Certificate to be issued...") + testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testCertificate, time.Minute*8) + Expect(err).NotTo(HaveOccurred()) + + By("Validating the issued Certificate...") + err = f.Helper().ValidateCertificate(testCertificate, validation.CertificateSetForUnsupportedFeatureSet(s.UnsupportedFeatures)...) + Expect(err).NotTo(HaveOccurred()) + }, featureset.Ed25519FeatureSet, featureset.CommonNameFeature) + + s.it(f, "should issue a certificate that defines an IP Address", func(issuerRef cmmeta.ObjectReference) { + testCertificate := &cmapi.Certificate{ + ObjectMeta: metav1.ObjectMeta{ + Name: "testcert", + Namespace: f.Namespace.Name, + }, + Spec: cmapi.CertificateSpec{ + SecretName: "testcert-tls", + IPAddresses: []string{sharedIPAddress}, + IssuerRef: issuerRef, + }, + } + By("Creating a Certificate") + err := f.CRClient.Create(ctx, testCertificate) + Expect(err).NotTo(HaveOccurred()) + + By("Waiting for the Certificate to be issued...") + testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testCertificate, time.Minute*8) + Expect(err).NotTo(HaveOccurred()) + + By("Validating the issued Certificate...") + err = f.Helper().ValidateCertificate(testCertificate, validation.CertificateSetForUnsupportedFeatureSet(s.UnsupportedFeatures)...) + Expect(err).NotTo(HaveOccurred()) + }, featureset.IPAddressFeature) + + s.it(f, "should issue a certificate that defines a DNS Name and IP Address", func(issuerRef cmmeta.ObjectReference) { + testCertificate := &cmapi.Certificate{ + ObjectMeta: metav1.ObjectMeta{ + Name: "testcert", + Namespace: f.Namespace.Name, + }, + Spec: cmapi.CertificateSpec{ + SecretName: "testcert-tls", + IPAddresses: []string{sharedIPAddress}, + DNSNames: []string{e2eutil.RandomSubdomain(s.DomainSuffix)}, + IssuerRef: issuerRef, + }, + } + By("Creating a Certificate") + err := f.CRClient.Create(ctx, testCertificate) + Expect(err).NotTo(HaveOccurred()) + + By("Waiting for the Certificate to be issued...") + testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testCertificate, time.Minute*8) + Expect(err).NotTo(HaveOccurred()) + + By("Validating the issued Certificate...") + err = f.Helper().ValidateCertificate(testCertificate, validation.CertificateSetForUnsupportedFeatureSet(s.UnsupportedFeatures)...) + Expect(err).NotTo(HaveOccurred()) + }, featureset.OnlySAN, featureset.IPAddressFeature) + + s.it(f, "should issue a certificate that defines a Common Name and IP Address", func(issuerRef cmmeta.ObjectReference) { + // Some issuers use the CN to define the cert's "ID" + // if one cert manages to be in an error state in the issuer it might throw an error + // this makes the CN more unique + cn := "test-common-name-" + e2eutil.RandStringRunes(10) + testCertificate := &cmapi.Certificate{ + ObjectMeta: metav1.ObjectMeta{ + Name: "testcert", + Namespace: f.Namespace.Name, + }, + Spec: cmapi.CertificateSpec{ + SecretName: "testcert-tls", + CommonName: cn, + IPAddresses: []string{sharedIPAddress}, + IssuerRef: issuerRef, + }, + } + By("Creating a Certificate") + err := f.CRClient.Create(ctx, testCertificate) + Expect(err).NotTo(HaveOccurred()) + + By("Waiting for the Certificate to be issued...") + testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testCertificate, time.Minute*8) + Expect(err).NotTo(HaveOccurred()) + + By("Validating the issued Certificate...") + err = f.Helper().ValidateCertificate(testCertificate, validation.CertificateSetForUnsupportedFeatureSet(s.UnsupportedFeatures)...) + Expect(err).NotTo(HaveOccurred()) + }, featureset.CommonNameFeature, featureset.IPAddressFeature) + + s.it(f, "should issue a certificate that defines an Email Address", func(issuerRef cmmeta.ObjectReference) { + testCertificate := &cmapi.Certificate{ + ObjectMeta: metav1.ObjectMeta{ + Name: "testcert", + Namespace: f.Namespace.Name, + }, + Spec: cmapi.CertificateSpec{ + SecretName: "testcert-tls", + EmailAddresses: []string{"alice@example.com"}, + IssuerRef: issuerRef, + }, + } + By("Creating a Certificate") + err := f.CRClient.Create(ctx, testCertificate) + Expect(err).NotTo(HaveOccurred()) + + By("Waiting for the Certificate to be issued...") + testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testCertificate, time.Minute*8) + Expect(err).NotTo(HaveOccurred()) + + By("Validating the issued Certificate...") + err = f.Helper().ValidateCertificate(testCertificate, validation.CertificateSetForUnsupportedFeatureSet(s.UnsupportedFeatures)...) + Expect(err).NotTo(HaveOccurred()) + }, featureset.EmailSANsFeature, featureset.OnlySAN) + + s.it(f, "should issue a certificate that defines a Common Name and URI SAN", func(issuerRef cmmeta.ObjectReference) { + // Some issuers use the CN to define the cert's "ID" + // if one cert manages to be in an error state in the issuer it might throw an error + // this makes the CN more unique + cn := "test-common-name-" + e2eutil.RandStringRunes(10) + testCertificate := &cmapi.Certificate{ + ObjectMeta: metav1.ObjectMeta{ + Name: "testcert", + Namespace: f.Namespace.Name, + }, + Spec: cmapi.CertificateSpec{ + SecretName: "testcert-tls", + CommonName: cn, + URIs: []string{"spiffe://cluster.local/ns/sandbox/sa/foo"}, + IssuerRef: issuerRef, + }, + } + By("Creating a Certificate") + err := f.CRClient.Create(ctx, testCertificate) + Expect(err).NotTo(HaveOccurred()) + + By("Waiting for the Certificate to be issued...") + testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testCertificate, time.Minute*8) + Expect(err).NotTo(HaveOccurred()) + + By("Validating the issued Certificate...") + err = f.Helper().ValidateCertificate(testCertificate, validation.CertificateSetForUnsupportedFeatureSet(s.UnsupportedFeatures)...) + Expect(err).NotTo(HaveOccurred()) + }, featureset.URISANsFeature, featureset.CommonNameFeature) + + s.it(f, "should issue a certificate that defines a 2 distinct DNS Names with one copied to the Common Name", func(issuerRef cmmeta.ObjectReference) { + testCertificate := &cmapi.Certificate{ + ObjectMeta: metav1.ObjectMeta{ + Name: "testcert", + Namespace: f.Namespace.Name, + }, + Spec: cmapi.CertificateSpec{ + SecretName: "testcert-tls", + CommonName: e2eutil.RandomSubdomain(s.DomainSuffix), + IssuerRef: issuerRef, + }, + } + testCertificate.Spec.DNSNames = []string{ + testCertificate.Spec.CommonName, e2eutil.RandomSubdomain(s.DomainSuffix), + } + + By("Creating a Certificate") + err := f.CRClient.Create(ctx, testCertificate) + Expect(err).NotTo(HaveOccurred()) + + By("Waiting for the Certificate to be issued...") + testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testCertificate, time.Minute*8) + Expect(err).NotTo(HaveOccurred()) + + By("Validating the issued Certificate...") + err = f.Helper().ValidateCertificate(testCertificate, validation.CertificateSetForUnsupportedFeatureSet(s.UnsupportedFeatures)...) + Expect(err).NotTo(HaveOccurred()) + }, featureset.CommonNameFeature) + + s.it(f, "should issue a certificate that defines a distinct DNS Name and another distinct Common Name", func(issuerRef cmmeta.ObjectReference) { + testCertificate := &cmapi.Certificate{ + ObjectMeta: metav1.ObjectMeta{ + Name: "testcert", + Namespace: f.Namespace.Name, + }, + Spec: cmapi.CertificateSpec{ + SecretName: "testcert-tls", + CommonName: e2eutil.RandomSubdomain(s.DomainSuffix), + IssuerRef: issuerRef, + DNSNames: []string{e2eutil.RandomSubdomain(s.DomainSuffix)}, + }, + } + + By("Creating a Certificate") + err := f.CRClient.Create(ctx, testCertificate) + Expect(err).NotTo(HaveOccurred()) + + By("Waiting for the Certificate to be issued...") + testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testCertificate, time.Minute*8) + Expect(err).NotTo(HaveOccurred()) + + By("Validating the issued Certificate...") + err = f.Helper().ValidateCertificate(testCertificate, validation.CertificateSetForUnsupportedFeatureSet(s.UnsupportedFeatures)...) + Expect(err).NotTo(HaveOccurred()) + }, featureset.CommonNameFeature) + + s.it(f, "should issue a certificate that defines a DNS Name and sets a duration", func(issuerRef cmmeta.ObjectReference) { + testCertificate := &cmapi.Certificate{ + ObjectMeta: metav1.ObjectMeta{ + Name: "testcert", + Namespace: f.Namespace.Name, + }, + Spec: cmapi.CertificateSpec{ + SecretName: "testcert-tls", + IssuerRef: issuerRef, + DNSNames: []string{e2eutil.RandomSubdomain(s.DomainSuffix)}, + Duration: &metav1.Duration{ + Duration: time.Hour * 896, + }, + }, + } + By("Creating a Certificate") + err := f.CRClient.Create(ctx, testCertificate) + Expect(err).NotTo(HaveOccurred()) + + By("Waiting for the Certificate to be issued...") + testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testCertificate, time.Minute*8) + Expect(err).NotTo(HaveOccurred()) + + By("Validating the issued Certificate...") + err = f.Helper().ValidateCertificate(testCertificate, validation.CertificateSetForUnsupportedFeatureSet(s.UnsupportedFeatures)...) + Expect(err).NotTo(HaveOccurred()) + }, featureset.DurationFeature, featureset.OnlySAN) + + s.it(f, "should issue a certificate that defines a wildcard DNS Name", func(issuerRef cmmeta.ObjectReference) { + testCertificate := &cmapi.Certificate{ + ObjectMeta: metav1.ObjectMeta{ + Name: "testcert", + Namespace: f.Namespace.Name, + }, + Spec: cmapi.CertificateSpec{ + SecretName: "testcert-tls", + IssuerRef: issuerRef, + DNSNames: []string{"*." + e2eutil.RandomSubdomain(s.DomainSuffix)}, + }, + } + By("Creating a Certificate") + err := f.CRClient.Create(ctx, testCertificate) + Expect(err).NotTo(HaveOccurred()) + + By("Waiting for the Certificate to be issued...") + testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testCertificate, time.Minute*8) + Expect(err).NotTo(HaveOccurred()) + + By("Validating the issued Certificate...") + err = f.Helper().ValidateCertificate(testCertificate, validation.CertificateSetForUnsupportedFeatureSet(s.UnsupportedFeatures)...) + Expect(err).NotTo(HaveOccurred()) + }, featureset.WildcardsFeature, featureset.OnlySAN) + + s.it(f, "should issue a certificate that includes only a URISANs name", func(issuerRef cmmeta.ObjectReference) { + testCertificate := &cmapi.Certificate{ + ObjectMeta: metav1.ObjectMeta{ + Name: "testcert", + Namespace: f.Namespace.Name, + }, + Spec: cmapi.CertificateSpec{ + SecretName: "testcert-tls", + URIs: []string{ + "spiffe://cluster.local/ns/sandbox/sa/foo", + }, + IssuerRef: issuerRef, + }, + } + By("Creating a Certificate") + err := f.CRClient.Create(ctx, testCertificate) + Expect(err).NotTo(HaveOccurred()) + + By("Waiting for the Certificate to be issued...") + testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testCertificate, time.Minute*8) + Expect(err).NotTo(HaveOccurred()) + + By("Validating the issued Certificate...") + err = f.Helper().ValidateCertificate(testCertificate, validation.CertificateSetForUnsupportedFeatureSet(s.UnsupportedFeatures)...) + Expect(err).NotTo(HaveOccurred()) + }, featureset.URISANsFeature, featureset.OnlySAN) + + s.it(f, "should issue a certificate that includes arbitrary key usages", func(issuerRef cmmeta.ObjectReference) { + testCertificate := &cmapi.Certificate{ + ObjectMeta: metav1.ObjectMeta{ + Name: "testcert", + Namespace: f.Namespace.Name, + }, + Spec: cmapi.CertificateSpec{ + SecretName: "testcert-tls", + DNSNames: []string{e2eutil.RandomSubdomain(s.DomainSuffix)}, + IssuerRef: issuerRef, + Usages: []cmapi.KeyUsage{ + cmapi.UsageSigning, + cmapi.UsageDataEncipherment, + cmapi.UsageServerAuth, + cmapi.UsageClientAuth, + }, + }, + } + By("Creating a Certificate") + err := f.CRClient.Create(ctx, testCertificate) + Expect(err).NotTo(HaveOccurred()) + + By("Waiting for the Certificate to be issued...") + testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testCertificate, time.Minute*8) + Expect(err).NotTo(HaveOccurred()) + + By("Validating the issued Certificate...") + + validations := []certificates.ValidationFunc{ + certificates.ExpectKeyUsageExtKeyUsageClientAuth, + certificates.ExpectKeyUsageExtKeyUsageServerAuth, + certificates.ExpectKeyUsageUsageDigitalSignature, + certificates.ExpectKeyUsageUsageDataEncipherment, + } + validations = append(validations, validation.CertificateSetForUnsupportedFeatureSet(s.UnsupportedFeatures)...) + + err = f.Helper().ValidateCertificate(testCertificate, validations...) + Expect(err).NotTo(HaveOccurred()) + }, featureset.KeyUsagesFeature, featureset.OnlySAN) + + s.it(f, "should issue another certificate with the same private key if the existing certificate and CertificateRequest are deleted", func(issuerRef cmmeta.ObjectReference) { + testCertificate := &cmapi.Certificate{ + ObjectMeta: metav1.ObjectMeta{ + Name: "testcert", + Namespace: f.Namespace.Name, + }, + Spec: cmapi.CertificateSpec{ + SecretName: "testcert-tls", + DNSNames: []string{e2eutil.RandomSubdomain(s.DomainSuffix)}, + IssuerRef: issuerRef, + }, + } + By("Creating a Certificate") + err := f.CRClient.Create(ctx, testCertificate) + Expect(err).NotTo(HaveOccurred()) + + By("Waiting for the Certificate to be issued...") + testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testCertificate, time.Minute*8) + Expect(err).NotTo(HaveOccurred()) + + By("Validating the issued Certificate...") + err = f.Helper().ValidateCertificate(testCertificate, validation.CertificateSetForUnsupportedFeatureSet(s.UnsupportedFeatures)...) + Expect(err).NotTo(HaveOccurred()) + + By("Deleting existing certificate data in Secret") + sec, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name). + Get(ctx, testCertificate.Spec.SecretName, metav1.GetOptions{}) + Expect(err).NotTo(HaveOccurred(), "failed to get secret containing signed certificate key pair data") + + sec = sec.DeepCopy() + crtPEM1 := sec.Data[corev1.TLSCertKey] + crt1, err := pki.DecodeX509CertificateBytes(crtPEM1) + Expect(err).NotTo(HaveOccurred(), "failed to get decode first signed certificate data") + + sec.Data[corev1.TLSCertKey] = []byte{} + + _, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Update(ctx, sec, metav1.UpdateOptions{}) + Expect(err).NotTo(HaveOccurred(), "failed to update secret by deleting the signed certificate data") + + By("Waiting for the Certificate to re-issue a certificate") + sec, err = f.Helper().WaitForSecretCertificateData(f.Namespace.Name, sec.Name, time.Minute*8) + Expect(err).NotTo(HaveOccurred(), "failed to wait for secret to have a valid 2nd certificate") + + crtPEM2 := sec.Data[corev1.TLSCertKey] + crt2, err := pki.DecodeX509CertificateBytes(crtPEM2) + Expect(err).NotTo(HaveOccurred(), "failed to get decode second signed certificate data") + + By("Ensuing both certificates are signed by same private key") + match, err := pki.PublicKeysEqual(crt1.PublicKey, crt2.PublicKey) + Expect(err).NotTo(HaveOccurred(), "failed to check public keys of both signed certificates") + + if !match { + Fail("Both signed certificates not signed by same private key") + } + }, featureset.ReusePrivateKeyFeature, featureset.OnlySAN) + + s.it(f, "should issue a certificate that defines a long domain", func(issuerRef cmmeta.ObjectReference) { + // the maximum length of a single segment of the domain being requested + const maxLengthOfDomainSegment = 63 + + testCertificate := &cmapi.Certificate{ + ObjectMeta: metav1.ObjectMeta{ + Name: "testcert", + Namespace: f.Namespace.Name, + }, + Spec: cmapi.CertificateSpec{ + SecretName: "testcert-tls", + DNSNames: []string{e2eutil.RandomSubdomainLength(s.DomainSuffix, maxLengthOfDomainSegment)}, + IssuerRef: issuerRef, + }, + } + validations := validation.CertificateSetForUnsupportedFeatureSet(s.UnsupportedFeatures) + + By("Creating a Certificate") + err := f.CRClient.Create(ctx, testCertificate) + Expect(err).NotTo(HaveOccurred()) + + By("Waiting for the Certificate to be issued...") + testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testCertificate, time.Minute*8) + Expect(err).NotTo(HaveOccurred()) + + By("Sanity-check the issued Certificate") + err = f.Helper().ValidateCertificate(testCertificate, validations...) + Expect(err).NotTo(HaveOccurred()) + }, featureset.OnlySAN, featureset.LongDomainFeatureSet) + + s.it(f, "should allow updating an existing certificate with a new DNS Name", func(issuerRef cmmeta.ObjectReference) { + testCertificate := &cmapi.Certificate{ + ObjectMeta: metav1.ObjectMeta{ + Name: "testcert", + Namespace: f.Namespace.Name, + }, + Spec: cmapi.CertificateSpec{ + SecretName: "testcert-tls", + DNSNames: []string{e2eutil.RandomSubdomain(s.DomainSuffix)}, + IssuerRef: issuerRef, + }, + } + validations := validation.CertificateSetForUnsupportedFeatureSet(s.UnsupportedFeatures) + + By("Creating a Certificate") + err := f.CRClient.Create(ctx, testCertificate) + Expect(err).NotTo(HaveOccurred()) + + By("Waiting for the Certificate to be ready") + testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testCertificate, time.Minute*8) + Expect(err).NotTo(HaveOccurred()) + + By("Sanity-check the issued Certificate") + err = f.Helper().ValidateCertificate(testCertificate, validations...) + Expect(err).NotTo(HaveOccurred()) + + By("Updating the Certificate after having added an additional dnsName") + newDNSName := e2eutil.RandomSubdomain(s.DomainSuffix) + retry.RetryOnConflict(retry.DefaultRetry, func() error { + err = f.CRClient.Get(context.Background(), types.NamespacedName{Name: testCertificate.Name, Namespace: testCertificate.Namespace}, testCertificate) + if err != nil { + return err + } + + testCertificate.Spec.DNSNames = append(testCertificate.Spec.DNSNames, newDNSName) + err = f.CRClient.Update(context.Background(), testCertificate) + if err != nil { + return err + } + return nil + }) + + Expect(err).NotTo(HaveOccurred()) + + By("Waiting for the Certificate Ready condition to be updated") + testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testCertificate, time.Minute*8) + Expect(err).NotTo(HaveOccurred()) + + By("Sanity-check the issued Certificate") + err = f.Helper().ValidateCertificate(testCertificate, validations...) + Expect(err).NotTo(HaveOccurred()) + }, featureset.OnlySAN) + + s.it(f, "should issue a certificate that defines a wildcard DNS Name and its apex DNS Name", func(issuerRef cmmeta.ObjectReference) { + dnsDomain := e2eutil.RandomSubdomain(s.DomainSuffix) + testCertificate := &cmapi.Certificate{ + ObjectMeta: metav1.ObjectMeta{ + Name: "testcert", + Namespace: f.Namespace.Name, + }, + Spec: cmapi.CertificateSpec{ + SecretName: "testcert-tls", + IssuerRef: issuerRef, + DNSNames: []string{"*." + dnsDomain, dnsDomain}, + }, + } + By("Creating a Certificate") + err := f.CRClient.Create(ctx, testCertificate) + Expect(err).NotTo(HaveOccurred()) + + // use a longer timeout for this, as it requires performing 2 dns validations in serial + By("Waiting for the Certificate to be issued...") + testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testCertificate, time.Minute*10) + Expect(err).NotTo(HaveOccurred()) + + By("Validating the issued Certificate...") + err = f.Helper().ValidateCertificate(testCertificate, validation.CertificateSetForUnsupportedFeatureSet(s.UnsupportedFeatures)...) + Expect(err).NotTo(HaveOccurred()) + }, featureset.WildcardsFeature, featureset.OnlySAN) + }) +} diff --git a/conformance/certificatesigningrequests/suite.go b/conformance/certificatesigningrequests/suite.go new file mode 100644 index 0000000..81ed005 --- /dev/null +++ b/conformance/certificatesigningrequests/suite.go @@ -0,0 +1,142 @@ +/* +Copyright 2020 The cert-manager Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package certificatesigningrequests + +import ( + "context" + "crypto" + + . "github.com/onsi/ginkgo/v2" + certificatesv1 "k8s.io/api/certificates/v1" + "k8s.io/client-go/rest" + + "github.com/cert-manager/issuer-lib/conformance/framework" + "github.com/cert-manager/issuer-lib/conformance/framework/helper/featureset" +) + +// Suite defines a reusable conformance test suite that can be used against any +// Issuer implementation. +type Suite struct { + // KubeClientConfig is the configuration used to connect to the Kubernetes + // API server. + KubeClientConfig *rest.Config + + // Name is the name of the issuer being tested, e.g. SelfSigned, CA, ACME + // This field must be provided. + Name string + + // CreateIssuerFunc is a function that provisions a new issuer resource and + // returns an SignerName to that Issuer that will be used as the SignerName + // on CertificateSigningRequest resources that this suite creates. + // This field must be provided. + CreateIssuerFunc func(*framework.Framework, context.Context) string + + // DeleteIssuerFunc is a function that is run after the test has completed + // in order to clean up resources created for a test (e.g. the resources + // created in CreateIssuerFunc). + // This function will be run regardless whether the test passes or fails. + // If not specified, this function will be skipped. + DeleteIssuerFunc func(*framework.Framework, context.Context, string) + + // ProvisionFunc is a function that is run every test just before the + // CertificateSigningRequest is created within a test. This is used to + // provision or create any resources that are required by the Issuer to sign + // the CertificateSigningRequest. This could be for example to annotate the + // CertificateSigningRequest, or create a resource like a Secret needed for + // signing. + // If not specified, this function will be skipped. + ProvisionFunc func(*framework.Framework, context.Context, *certificatesv1.CertificateSigningRequest, crypto.Signer) + + // DeProvisionFunc is run after every test. This is to be used to remove and + // clean-up any resources which may have been created by ProvisionFunc. + // If not specified, this function will be skipped. + DeProvisionFunc func(*framework.Framework, context.Context, *certificatesv1.CertificateSigningRequest) + + // DomainSuffix is a suffix used on all domain requests. + // This is useful when the issuer being tested requires special + // configuration for a set of domains in order for certificates to be + // issued, such as the ACME issuer. + // If not set, this will be defaulted to the configured 'domain' for the + // nginx-ingress addon. + DomainSuffix string + + // UnsupportedFeatures is a list of features that are not supported by this + // invocation of the test suite. + // This is useful if a particular issuers explicitly does not support + // certain features due to restrictions in their implementation. + UnsupportedFeatures featureset.FeatureSet + + // completed is used internally to track whether Complete() has been called + completed bool +} + +// complete will validate configuration and set default values. +func (s *Suite) complete(f *framework.Framework) { + if s.Name == "" { + Fail("Name must be set") + } + + if s.CreateIssuerFunc == nil { + Fail("CreateIssuerFunc must be set") + } + + if s.DomainSuffix == "" { + s.DomainSuffix = "example.com" + } + + if s.UnsupportedFeatures == nil { + s.UnsupportedFeatures = make(featureset.FeatureSet) + } + + s.completed = true +} + +// it is called by the tests to in Define() to setup and run the test +func (s *Suite) it(f *framework.Framework, name string, fn func(context.Context, string), requiredFeatures ...featureset.Feature) { + if !s.checkFeatures(requiredFeatures...) { + return + } + It(name, func(ctx context.Context) { + By("Creating an issuer resource") + signerName := s.CreateIssuerFunc(f, ctx) + defer func() { + if s.DeleteIssuerFunc != nil { + By("Cleaning up the issuer resource") + s.DeleteIssuerFunc(f, ctx, signerName) + } + }() + fn(ctx, signerName) + }) +} + +// checkFeatures is a helper function that is used to ensure that the features +// required for a given test case are supported by the suite. +// It will return 'true' if all features are supported and the test should run, +// or return 'false' if any required feature is not supported. +func (s *Suite) checkFeatures(fs ...featureset.Feature) bool { + unsupported := make(featureset.FeatureSet) + for _, f := range fs { + if s.UnsupportedFeatures.Contains(f) { + unsupported.Add(f) + } + } + // all features supported, return early! + if len(unsupported) == 0 { + return true + } + return false +} diff --git a/conformance/certificatesigningrequests/tests.go b/conformance/certificatesigningrequests/tests.go new file mode 100644 index 0000000..7d0a124 --- /dev/null +++ b/conformance/certificatesigningrequests/tests.go @@ -0,0 +1,427 @@ +/* +Copyright 2021 The cert-manager Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package certificatesigningrequests + +import ( + "context" + "crypto/x509" + "net" + "net/url" + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + certificatesv1 "k8s.io/api/certificates/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/pointer" + + experimentalapi "github.com/cert-manager/cert-manager/pkg/apis/experimental/v1alpha1" + "github.com/cert-manager/cert-manager/test/unit/gen" + "github.com/cert-manager/issuer-lib/conformance/framework" + "github.com/cert-manager/issuer-lib/conformance/framework/helper/featureset" + "github.com/cert-manager/issuer-lib/conformance/framework/helper/validation" + "github.com/cert-manager/issuer-lib/conformance/framework/helper/validation/certificatesigningrequests" + e2eutil "github.com/cert-manager/issuer-lib/conformance/util" +) + +// Defines simple conformance tests that can be run against any issuer type. +// If Complete has not been called on this Suite before Define, it will be +// automatically called. +// The tests in this file require that the CertificateSigningRequest +// controllers are active +// (--feature-gates=ExperimentalCertificateSigningRequestControllers=true). If +// they are not active, these tests will fail. +func (s *Suite) Define() { + Describe("CertificateSigningRequest with issuer type "+s.Name, func() { + f := framework.NewFramework("certificatesigningrequests", s.KubeClientConfig) + + sharedCommonName := "" + sharedURI, err := url.Parse("spiffe://cluster.local/ns/sandbox/sa/foo") + if err != nil { + // This should never happen, and is a bug. Panic to prevent garbage test + // data. + panic(err) + } + + // Wrap this in a BeforeEach else flags will not have been parsed and + // f.Config will not be populated at the time that this code is run. + BeforeEach(func() { + if s.completed { + return + } + + s.complete(f) + + sharedCommonName = e2eutil.RandomSubdomain(s.DomainSuffix) + }) + + type testCase struct { + name string // ginkgo v2 does not support using map[string] to store the test names (#5345) + keyAlgo x509.PublicKeyAlgorithm + // csrModifers define the shape of the X.509 CSR which is used in the + // test case. We use a function to allow access to variables that are + // initialized at test runtime by complete(). + csrModifiers func() []gen.CSRModifier + kubeCSRUsages []certificatesv1.KeyUsage + kubeCSRAnnotations map[string]string + kubeCSRExpirationSeconds *int32 + // The list of features that are required by the Issuer for the test to + // run. + requiredFeatures []featureset.Feature + // Extra validations which may be needed for testing, on a test case by + // case basis. All default validations will be run on every test. + extraValidations []certificatesigningrequests.ValidationFunc + } + + tests := []testCase{ + { + name: "should issue an RSA certificate for a single distinct DNS Name", + keyAlgo: x509.RSA, + csrModifiers: func() []gen.CSRModifier { + return []gen.CSRModifier{gen.SetCSRDNSNames(e2eutil.RandomSubdomain(s.DomainSuffix))} + }, + kubeCSRUsages: []certificatesv1.KeyUsage{ + certificatesv1.UsageDigitalSignature, + certificatesv1.UsageKeyEncipherment, + }, + requiredFeatures: []featureset.Feature{featureset.OnlySAN}, + }, + { + name: "should issue an ECDSA certificate for a single distinct DNS Name", + keyAlgo: x509.ECDSA, + csrModifiers: func() []gen.CSRModifier { + return []gen.CSRModifier{gen.SetCSRDNSNames(e2eutil.RandomSubdomain(s.DomainSuffix))} + }, + kubeCSRUsages: []certificatesv1.KeyUsage{ + certificatesv1.UsageDigitalSignature, + certificatesv1.UsageKeyEncipherment, + }, + requiredFeatures: []featureset.Feature{featureset.ECDSAFeature, featureset.OnlySAN}, + }, + { + name: "should issue an Ed25519 certificate for a single distinct DNS Name", + keyAlgo: x509.Ed25519, + csrModifiers: func() []gen.CSRModifier { + return []gen.CSRModifier{gen.SetCSRDNSNames(e2eutil.RandomSubdomain(s.DomainSuffix))} + }, + kubeCSRUsages: []certificatesv1.KeyUsage{ + certificatesv1.UsageDigitalSignature, + certificatesv1.UsageKeyEncipherment, + }, + requiredFeatures: []featureset.Feature{featureset.Ed25519FeatureSet, featureset.OnlySAN}, + }, + { + name: "should issue an RSA certificate for a single Common Name", + keyAlgo: x509.RSA, + csrModifiers: func() []gen.CSRModifier { + return []gen.CSRModifier{gen.SetCSRCommonName("test-common-name-" + e2eutil.RandStringRunes(10))} + }, + kubeCSRUsages: []certificatesv1.KeyUsage{ + certificatesv1.UsageDigitalSignature, + certificatesv1.UsageKeyEncipherment, + }, + requiredFeatures: []featureset.Feature{featureset.CommonNameFeature}, + }, + { + name: "should issue an ECDSA certificate for a single Common Name", + keyAlgo: x509.ECDSA, + csrModifiers: func() []gen.CSRModifier { + return []gen.CSRModifier{gen.SetCSRCommonName("test-common-name-" + e2eutil.RandStringRunes(10))} + }, + kubeCSRUsages: []certificatesv1.KeyUsage{ + certificatesv1.UsageDigitalSignature, + certificatesv1.UsageKeyEncipherment, + }, + requiredFeatures: []featureset.Feature{featureset.CommonNameFeature, featureset.ECDSAFeature}, + }, + { + name: "should issue an Ed25519 certificate for a single Common Name", + keyAlgo: x509.Ed25519, + csrModifiers: func() []gen.CSRModifier { + return []gen.CSRModifier{gen.SetCSRCommonName("test-common-name-" + e2eutil.RandStringRunes(10))} + }, + kubeCSRUsages: []certificatesv1.KeyUsage{ + certificatesv1.UsageDigitalSignature, + certificatesv1.UsageKeyEncipherment, + }, + requiredFeatures: []featureset.Feature{featureset.CommonNameFeature, featureset.Ed25519FeatureSet}, + }, + { + name: "should issue a certificate that defines a Common Name and IP Address", + keyAlgo: x509.RSA, + csrModifiers: func() []gen.CSRModifier { + return []gen.CSRModifier{ + gen.SetCSRCommonName("test-common-name-" + e2eutil.RandStringRunes(10)), + gen.SetCSRIPAddresses(net.IPv4(127, 0, 0, 1), net.IPv4(8, 8, 8, 8)), + } + }, + kubeCSRUsages: []certificatesv1.KeyUsage{ + certificatesv1.UsageDigitalSignature, + certificatesv1.UsageKeyEncipherment, + }, + requiredFeatures: []featureset.Feature{featureset.CommonNameFeature, featureset.IPAddressFeature}, + }, + { + name: "should issue a certificate that defines an Email Address", + keyAlgo: x509.RSA, + csrModifiers: func() []gen.CSRModifier { + return []gen.CSRModifier{ + gen.SetCSREmails([]string{"alice@example.com", "bob@cert-manager.io"}), + } + }, + kubeCSRUsages: []certificatesv1.KeyUsage{ + certificatesv1.UsageDigitalSignature, + certificatesv1.UsageKeyEncipherment, + }, + requiredFeatures: []featureset.Feature{featureset.OnlySAN, featureset.EmailSANsFeature}, + }, + { + name: "should issue a certificate that defines a Common Name and URI SAN", + keyAlgo: x509.RSA, + csrModifiers: func() []gen.CSRModifier { + return []gen.CSRModifier{ + gen.SetCSRCommonName("test-common-name-" + e2eutil.RandStringRunes(10)), + gen.SetCSRURIs(sharedURI), + } + }, + kubeCSRUsages: []certificatesv1.KeyUsage{ + certificatesv1.UsageDigitalSignature, + certificatesv1.UsageKeyEncipherment, + }, + requiredFeatures: []featureset.Feature{featureset.CommonNameFeature, featureset.URISANsFeature}, + }, + { + name: "should issue a certificate that defines a 2 distinct DNS Name with one copied to the Common Name", + keyAlgo: x509.RSA, + csrModifiers: func() []gen.CSRModifier { + return []gen.CSRModifier{ + gen.SetCSRCommonName(sharedCommonName), + gen.SetCSRDNSNames(sharedCommonName, e2eutil.RandomSubdomain(s.DomainSuffix)), + } + }, + kubeCSRUsages: []certificatesv1.KeyUsage{ + certificatesv1.UsageDigitalSignature, + certificatesv1.UsageKeyEncipherment, + }, + requiredFeatures: []featureset.Feature{}, + }, + { + name: "should issue a certificate that defines a distinct DNS Name and another distinct Common Name", + keyAlgo: x509.RSA, + csrModifiers: func() []gen.CSRModifier { + return []gen.CSRModifier{ + gen.SetCSRCommonName(e2eutil.RandomSubdomain(s.DomainSuffix)), + gen.SetCSRDNSNames(e2eutil.RandomSubdomain(s.DomainSuffix)), + } + }, + kubeCSRUsages: []certificatesv1.KeyUsage{ + certificatesv1.UsageDigitalSignature, + certificatesv1.UsageKeyEncipherment, + }, + requiredFeatures: []featureset.Feature{featureset.CommonNameFeature}, + }, + { + name: "should issue a certificate that defines a Common Name, DNS Name, and sets a duration", + keyAlgo: x509.RSA, + csrModifiers: func() []gen.CSRModifier { + return []gen.CSRModifier{ + gen.SetCSRDNSNames(sharedCommonName), + gen.SetCSRDNSNames(sharedCommonName), + } + }, + kubeCSRUsages: []certificatesv1.KeyUsage{ + certificatesv1.UsageDigitalSignature, + certificatesv1.UsageKeyEncipherment, + }, + kubeCSRAnnotations: map[string]string{ + experimentalapi.CertificateSigningRequestDurationAnnotationKey: "896h", + }, + requiredFeatures: []featureset.Feature{featureset.DurationFeature}, + }, + { + name: "should issue a certificate that defines a Common Name, DNS Name, and sets a duration via expiration seconds", + keyAlgo: x509.RSA, + csrModifiers: func() []gen.CSRModifier { + return []gen.CSRModifier{ + gen.SetCSRDNSNames(sharedCommonName), + gen.SetCSRDNSNames(sharedCommonName), + } + }, + kubeCSRUsages: []certificatesv1.KeyUsage{ + certificatesv1.UsageDigitalSignature, + certificatesv1.UsageKeyEncipherment, + }, + kubeCSRExpirationSeconds: pointer.Int32(3333), + requiredFeatures: []featureset.Feature{featureset.DurationFeature}, + }, + { + name: "should issue a certificate that defines a DNS Name and sets a duration", + keyAlgo: x509.RSA, + csrModifiers: func() []gen.CSRModifier { + return []gen.CSRModifier{ + gen.SetCSRDNSNames(e2eutil.RandomSubdomain(s.DomainSuffix)), + } + }, + kubeCSRUsages: []certificatesv1.KeyUsage{ + certificatesv1.UsageDigitalSignature, + certificatesv1.UsageKeyEncipherment, + }, + kubeCSRAnnotations: map[string]string{ + experimentalapi.CertificateSigningRequestDurationAnnotationKey: "896h", + }, + requiredFeatures: []featureset.Feature{featureset.OnlySAN, featureset.DurationFeature}, + }, + { + name: "should issue a certificate which has a wildcard DNS Name defined", + keyAlgo: x509.RSA, + csrModifiers: func() []gen.CSRModifier { + return []gen.CSRModifier{ + gen.SetCSRDNSNames("*." + e2eutil.RandomSubdomain(s.DomainSuffix)), + } + }, + kubeCSRUsages: []certificatesv1.KeyUsage{ + certificatesv1.UsageDigitalSignature, + certificatesv1.UsageKeyEncipherment, + }, + requiredFeatures: []featureset.Feature{featureset.WildcardsFeature, featureset.OnlySAN}, + }, + { + name: "should issue a certificate that includes only a URISANs name", + keyAlgo: x509.RSA, + csrModifiers: func() []gen.CSRModifier { + return []gen.CSRModifier{ + gen.SetCSRURIs(sharedURI), + } + }, + kubeCSRUsages: []certificatesv1.KeyUsage{ + certificatesv1.UsageDigitalSignature, + certificatesv1.UsageKeyEncipherment, + }, + requiredFeatures: []featureset.Feature{featureset.URISANsFeature, featureset.OnlySAN}, + }, + { + name: "should issue a certificate that includes arbitrary key usages", + keyAlgo: x509.RSA, + csrModifiers: func() []gen.CSRModifier { + return []gen.CSRModifier{ + gen.SetCSRCommonName(sharedCommonName), + gen.SetCSRDNSNames(sharedCommonName), + } + }, + kubeCSRUsages: []certificatesv1.KeyUsage{ + certificatesv1.UsageServerAuth, + certificatesv1.UsageClientAuth, + certificatesv1.UsageDigitalSignature, + certificatesv1.UsageDataEncipherment, + }, + requiredFeatures: []featureset.Feature{featureset.KeyUsagesFeature}, + extraValidations: []certificatesigningrequests.ValidationFunc{ + certificatesigningrequests.ExpectKeyUsageExtKeyUsageClientAuth, + certificatesigningrequests.ExpectKeyUsageExtKeyUsageServerAuth, + certificatesigningrequests.ExpectKeyUsageUsageDigitalSignature, + certificatesigningrequests.ExpectKeyUsageUsageDataEncipherment, + }, + }, + { + name: "should issue a signing CA certificate that has a large duration", + keyAlgo: x509.RSA, + csrModifiers: func() []gen.CSRModifier { + return []gen.CSRModifier{ + gen.SetCSRCommonName("cert-manager-ca"), + } + }, + kubeCSRUsages: []certificatesv1.KeyUsage{ + certificatesv1.UsageDigitalSignature, + certificatesv1.UsageKeyEncipherment, + certificatesv1.UsageCertSign, + }, + kubeCSRAnnotations: map[string]string{ + experimentalapi.CertificateSigningRequestDurationAnnotationKey: "10000h", + experimentalapi.CertificateSigningRequestIsCAAnnotationKey: "true", + }, + requiredFeatures: []featureset.Feature{featureset.KeyUsagesFeature, featureset.DurationFeature, featureset.CommonNameFeature}, + }, + } + + defineTest := func(test testCase) { + s.it(f, test.name, func(ctx context.Context, signerName string) { + // Generate request CSR + csr, key, err := gen.CSR(test.keyAlgo, test.csrModifiers()...) + Expect(err).NotTo(HaveOccurred()) + + // Create CertificateSigningRequest + kubeCSR := &certificatesv1.CertificateSigningRequest{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "e2e-conformance-", + Annotations: test.kubeCSRAnnotations, + }, + Spec: certificatesv1.CertificateSigningRequestSpec{ + Request: csr, + SignerName: signerName, + Usages: test.kubeCSRUsages, + ExpirationSeconds: test.kubeCSRExpirationSeconds, + }, + } + + // Provision any resources needed for the request, or modify the + // request based on Issuer requirements + if s.ProvisionFunc != nil { + s.ProvisionFunc(f, ctx, kubeCSR, key) + } + // Ensure related resources are cleaned up at the end of the test + if s.DeProvisionFunc != nil { + defer s.DeProvisionFunc(f, ctx, kubeCSR) + } + + // Create the request, and delete at the end of the test + By("Creating a CertificateSigningRequest") + Expect(f.CRClient.Create(ctx, kubeCSR)).NotTo(HaveOccurred()) + defer f.CRClient.Delete(context.TODO(), kubeCSR) + + // Approve the request for testing, so that cert-manager may sign the + // request. + By("Approving CertificateSigningRequest") + kubeCSR.Status.Conditions = append(kubeCSR.Status.Conditions, certificatesv1.CertificateSigningRequestCondition{ + Type: certificatesv1.CertificateApproved, + Status: corev1.ConditionTrue, + Reason: "e2e.cert-manager.io", + Message: "Request approved for e2e testing.", + }) + kubeCSR, err = f.KubeClientSet.CertificatesV1().CertificateSigningRequests().UpdateApproval(ctx, kubeCSR.Name, kubeCSR, metav1.UpdateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + // Wait for the status.Certificate and CA annotation to be populated in + // a reasonable amount of time. + By("Waiting for the CertificateSigningRequest to be issued...") + kubeCSR, err = f.Helper().WaitForCertificateSigningRequestSigned(kubeCSR.Name, time.Minute*5) + Expect(err).NotTo(HaveOccurred()) + + // Validate that the request was signed as expected. Add extra + // validations which may be required for this test. + By("Validating the issued CertificateSigningRequest...") + validations := append(test.extraValidations, validation.CertificateSigningRequestSetForUnsupportedFeatureSet(s.UnsupportedFeatures)...) + err = f.Helper().ValidateCertificateSigningRequest(kubeCSR.Name, key, validations...) + Expect(err).NotTo(HaveOccurred()) + }, test.requiredFeatures...) + } + + for _, tc := range tests { + defineTest(tc) + } + }) +} diff --git a/conformance/framework/cleanup.go b/conformance/framework/cleanup.go new file mode 100644 index 0000000..b3063ba --- /dev/null +++ b/conformance/framework/cleanup.go @@ -0,0 +1,63 @@ +// +skip_license_check + +/* +Copyright 2016 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package framework + +import "sync" + +type CleanupActionHandle *int + +var cleanupActionsLock sync.Mutex +var cleanupActions = map[CleanupActionHandle]func(){} + +// AddCleanupAction installs a function that will be called in the event of the +// whole test being terminated. This allows arbitrary pieces of the overall +// test to hook into SynchronizedAfterSuite(). +func AddCleanupAction(fn func()) CleanupActionHandle { + p := CleanupActionHandle(new(int)) + cleanupActionsLock.Lock() + defer cleanupActionsLock.Unlock() + cleanupActions[p] = fn + return p +} + +// RemoveCleanupAction removes a function that was installed by +// AddCleanupAction. +func RemoveCleanupAction(p CleanupActionHandle) { + cleanupActionsLock.Lock() + defer cleanupActionsLock.Unlock() + delete(cleanupActions, p) +} + +// RunCleanupActions runs all functions installed by AddCleanupAction. It does +// not remove them (see RemoveCleanupAction) but it does run unlocked, so they +// may remove themselves. +func RunCleanupActions() { + list := []func(){} + func() { + cleanupActionsLock.Lock() + defer cleanupActionsLock.Unlock() + for _, fn := range cleanupActions { + list = append(list, fn) + } + }() + // Run unlocked. + for _, fn := range list { + fn() + } +} diff --git a/conformance/framework/framework.go b/conformance/framework/framework.go new file mode 100644 index 0000000..436047b --- /dev/null +++ b/conformance/framework/framework.go @@ -0,0 +1,160 @@ +/* +Copyright 2020 The cert-manager Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package framework + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + api "k8s.io/api/core/v1" + apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + apiextcs "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/kubernetes" + kscheme "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" + apireg "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1" + crclient "sigs.k8s.io/controller-runtime/pkg/client" + gwapi "sigs.k8s.io/gateway-api/pkg/client/clientset/versioned" + + clientset "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned" + certmgrscheme "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned/scheme" + "github.com/cert-manager/issuer-lib/conformance/framework/helper" +) + +// TODO: not all this code is required to be externally accessible. Separate the +// bits that do and the bits that don't. Perhaps we should have an external +// testing lib shared across projects? +// TODO: this really should be done somewhere in cert-manager proper +var Scheme = runtime.NewScheme() + +func init() { + kscheme.AddToScheme(Scheme) + certmgrscheme.AddToScheme(Scheme) + apiext.AddToScheme(Scheme) + apireg.AddToScheme(Scheme) +} + +// Framework supports common operations used by e2e tests; it will keep a client & a namespace for you. +type Framework struct { + BaseName string + + // KubeClientConfig which was used to create the connection. + KubeClientConfig *rest.Config + + // Kubernetes API clientsets + KubeClientSet kubernetes.Interface + GWClientSet gwapi.Interface + CertManagerClientSet clientset.Interface + APIExtensionsClientSet apiextcs.Interface + + // controller-runtime client for newer controllers + CRClient crclient.Client + + // Namespace in which all test resources should reside + Namespace *api.Namespace + + // To make sure that this framework cleans up after itself, no matter what, + // we install a Cleanup action before each test and clear it after. If we + // should abort, the AfterSuite hook should run all Cleanup actions. + cleanupHandle CleanupActionHandle + + helper *helper.Helper +} + +// NewFramework makes a new framework and sets up a BeforeEach/AfterEach for +// you (you can write additional before/after each functions). +// It uses the config provided to it for the duration of the tests. +func NewFramework(baseName string, kubeClientConfig *rest.Config) *Framework { + f := &Framework{ + KubeClientConfig: kubeClientConfig, + BaseName: baseName, + } + + f.helper = helper.NewHelper(kubeClientConfig) + BeforeEach(f.BeforeEach) + AfterEach(f.AfterEach) + + return f +} + +// BeforeEach gets a client and makes a namespace. +func (f *Framework) BeforeEach() { + f.cleanupHandle = AddCleanupAction(f.AfterEach) + + var err error + kubeConfig := rest.CopyConfig(f.KubeClientConfig) + + kubeConfig.Burst = 9000 + kubeConfig.QPS = 9000 + + f.KubeClientConfig = kubeConfig + + f.KubeClientSet, err = kubernetes.NewForConfig(kubeConfig) + Expect(err).NotTo(HaveOccurred()) + + By("Creating an API extensions client") + f.APIExtensionsClientSet, err = apiextcs.NewForConfig(kubeConfig) + Expect(err).NotTo(HaveOccurred()) + + By("Creating a cert manager client") + f.CertManagerClientSet, err = clientset.NewForConfig(kubeConfig) + Expect(err).NotTo(HaveOccurred()) + + By("Creating a controller-runtime client") + f.CRClient, err = crclient.New(kubeConfig, crclient.Options{Scheme: Scheme}) + Expect(err).NotTo(HaveOccurred()) + + By("Creating a gateway-api client") + f.GWClientSet, err = gwapi.NewForConfig(kubeConfig) + Expect(err).NotTo(HaveOccurred()) + + By("Building a namespace api object") + f.Namespace, err = f.CreateKubeNamespace(f.BaseName) + Expect(err).NotTo(HaveOccurred()) + + By("Using the namespace " + f.Namespace.Name) + + By("Building a ResourceQuota api object") + _, err = f.CreateKubeResourceQuota() + Expect(err).NotTo(HaveOccurred()) + + f.helper.CMClient = f.CertManagerClientSet + f.helper.KubeClient = f.KubeClientSet +} + +// AfterEach deletes the namespace, after reading its events. +func (f *Framework) AfterEach() { + RemoveCleanupAction(f.cleanupHandle) + + By("Deleting test namespace") + err := f.DeleteKubeNamespace(f.Namespace.Name) + Expect(err).NotTo(HaveOccurred()) +} + +func (f *Framework) Helper() *helper.Helper { + return f.helper +} + +// CertManagerDescribe is a wrapper function for ginkgo describe. Adds namespacing. +func CertManagerDescribe(text string, body func()) bool { + return Describe("[cert-manager] "+text, body) +} + +func ConformanceDescribe(text string, body func()) bool { + return Describe("[Conformance] "+text, body) +} diff --git a/conformance/framework/helper/certificaterequests.go b/conformance/framework/helper/certificaterequests.go new file mode 100644 index 0000000..2ae2397 --- /dev/null +++ b/conformance/framework/helper/certificaterequests.go @@ -0,0 +1,223 @@ +/* +Copyright 2020 The cert-manager Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package helper + +import ( + "context" + "crypto" + "crypto/ecdsa" + "crypto/ed25519" + "crypto/rsa" + "crypto/x509" + "fmt" + "time" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/wait" + + apiutil "github.com/cert-manager/cert-manager/pkg/api/util" + cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" + "github.com/cert-manager/cert-manager/pkg/util" + "github.com/cert-manager/cert-manager/pkg/util/pki" + "github.com/cert-manager/issuer-lib/conformance/framework/log" +) + +// WaitForCertificateRequestReady waits for the CertificateRequest resource to +// enter a Ready state. +func (h *Helper) WaitForCertificateRequestReady(ns, name string, timeout time.Duration) (*cmapi.CertificateRequest, error) { + var cr *cmapi.CertificateRequest + logf, done := log.LogBackoff() + defer done() + err := wait.PollUntilContextTimeout(context.TODO(), time.Second, timeout, true, func(ctx context.Context) (bool, error) { + var err error + logf("Waiting for CertificateRequest %s to be ready", name) + cr, err = h.CMClient.CertmanagerV1().CertificateRequests(ns).Get(context.TODO(), name, metav1.GetOptions{}) + if err != nil { + return false, fmt.Errorf("error getting CertificateRequest %s: %v", name, err) + } + isReady := apiutil.CertificateRequestHasCondition(cr, cmapi.CertificateRequestCondition{ + Type: cmapi.CertificateRequestConditionReady, + Status: cmmeta.ConditionTrue, + }) + if !isReady { + logf("Expected CertificateRequest to have Ready condition 'true' but it has: %v", cr.Status.Conditions) + return false, nil + } + return true, nil + }) + + if err != nil { + return nil, err + } + + return cr, nil +} + +// ValidateIssuedCertificateRequest will ensure that the given +// CertificateRequest has a certificate issued for it, and that the details on +// the x509 certificate are correct as defined by the CertificateRequest's +// spec. +func (h *Helper) ValidateIssuedCertificateRequest(cr *cmapi.CertificateRequest, key crypto.Signer, rootCAPEM []byte) (*x509.Certificate, error) { + csr, err := pki.DecodeX509CertificateRequestBytes(cr.Spec.Request) + if err != nil { + return nil, fmt.Errorf("failed to decode CertificateRequest's Spec.Request: %s", err) + } + + // validate private key is of the correct type (rsa or ecdsa) + switch csr.PublicKeyAlgorithm { + case x509.RSA: + _, ok := key.(*rsa.PrivateKey) + if !ok { + return nil, fmt.Errorf("Expected private key of type RSA, but it was: %T", key) + } + case x509.ECDSA: + _, ok := key.(*ecdsa.PrivateKey) + if !ok { + return nil, fmt.Errorf("Expected private key of type ECDSA, but it was: %T", key) + } + case x509.Ed25519: + _, ok := key.(ed25519.PrivateKey) + if !ok { + return nil, fmt.Errorf("Expected private key of type Ed25519, but it was: %T", key) + } + default: + return nil, fmt.Errorf("unrecognised requested private key algorithm %q", csr.PublicKeyAlgorithm) + } + + // TODO: validate private key KeySize + + // check the provided certificate is valid + expectedOrganization := csr.Subject.Organization + expectedDNSNames := csr.DNSNames + expectedIPAddresses := csr.IPAddresses + expectedURIs := csr.URIs + + cert, err := pki.DecodeX509CertificateBytes(cr.Status.Certificate) + if err != nil { + return nil, err + } + + commonNameCorrect := true + expectedCN := csr.Subject.CommonName + if len(expectedCN) == 0 && len(cert.Subject.CommonName) > 0 { + if !util.Contains(cert.DNSNames, cert.Subject.CommonName) { + commonNameCorrect = false + } + } else if expectedCN != cert.Subject.CommonName { + commonNameCorrect = false + } + + if !commonNameCorrect || + !util.EqualUnsorted(cert.DNSNames, expectedDNSNames) || + !util.EqualUnsorted(cert.Subject.Organization, expectedOrganization) || + !util.EqualIPsUnsorted(cert.IPAddresses, expectedIPAddresses) || + !util.EqualURLsUnsorted(cert.URIs, expectedURIs) { + return nil, fmt.Errorf("Expected certificate valid for CN %q, O %v, dnsNames %v, IPs %v, URIs %v but got a certificate valid for CN %q, O %v, dnsNames %v, IPs %v URIs %v", + expectedCN, expectedOrganization, expectedDNSNames, expectedIPAddresses, expectedURIs, + cert.Subject.CommonName, cert.Subject.Organization, cert.DNSNames, cert.IPAddresses, cert.URIs) + } + + var expectedDNSName string + if len(expectedDNSNames) > 0 { + expectedDNSName = expectedDNSNames[0] + } + + certificateKeyUsages, certificateExtKeyUsages, err := pki.KeyUsagesForCertificateOrCertificateRequest(cr.Spec.Usages, cr.Spec.IsCA) + if err != nil { + return nil, fmt.Errorf("failed to build key usages from certificate: %s", err) + } + + var keyAlg cmapi.PrivateKeyAlgorithm + switch csr.PublicKeyAlgorithm { + case x509.RSA: + keyAlg = cmapi.RSAKeyAlgorithm + case x509.ECDSA: + keyAlg = cmapi.ECDSAKeyAlgorithm + case x509.Ed25519: + keyAlg = cmapi.Ed25519KeyAlgorithm + default: + return nil, fmt.Errorf("unsupported key algorithm type: %s", csr.PublicKeyAlgorithm) + } + + defaultCertKeyUsages, defaultCertExtKeyUsages, err := h.defaultKeyUsagesToAdd(cr.Namespace, &cr.Spec.IssuerRef) + if err != nil { + return nil, err + } + + certificateKeyUsages |= defaultCertKeyUsages + certificateExtKeyUsages = append(certificateExtKeyUsages, defaultCertExtKeyUsages...) + + certificateExtKeyUsages = h.deduplicateExtKeyUsages(certificateExtKeyUsages) + + // If using ECDSA then ignore key encipherment + if keyAlg == cmapi.ECDSAKeyAlgorithm { + certificateKeyUsages &^= x509.KeyUsageKeyEncipherment + cert.KeyUsage &^= x509.KeyUsageKeyEncipherment + } + + if !h.keyUsagesMatch(cert.KeyUsage, cert.ExtKeyUsage, + certificateKeyUsages, certificateExtKeyUsages) { + return nil, fmt.Errorf("key usages and extended key usages do not match: exp=%s got=%s exp=%s got=%s", + apiutil.KeyUsageStrings(certificateKeyUsages), apiutil.KeyUsageStrings(cert.KeyUsage), + apiutil.ExtKeyUsageStrings(certificateExtKeyUsages), apiutil.ExtKeyUsageStrings(cert.ExtKeyUsage)) + } + + // TODO: move this verification step out of this function + if rootCAPEM != nil { + rootCertPool := x509.NewCertPool() + rootCertPool.AppendCertsFromPEM(rootCAPEM) + intermediateCertPool := x509.NewCertPool() + intermediateCertPool.AppendCertsFromPEM(cr.Status.CA) + opts := x509.VerifyOptions{ + DNSName: expectedDNSName, + Intermediates: intermediateCertPool, + Roots: rootCertPool, + } + + if _, err := cert.Verify(opts); err != nil { + return nil, err + } + } + + if !apiutil.CertificateRequestIsApproved(cr) { + return nil, fmt.Errorf("CertificateRequest does not have an Approved condition set to True: %+v", cr.Status.Conditions) + } + if apiutil.CertificateRequestIsDenied(cr) { + return nil, fmt.Errorf("CertificateRequest has a Denied conditon set to True: %+v", cr.Status.Conditions) + } + + return cert, nil +} + +func (h *Helper) WaitCertificateRequestIssuedValid(ns, name string, timeout time.Duration, key crypto.Signer) error { + return h.WaitCertificateRequestIssuedValidTLS(ns, name, timeout, key, nil) +} + +func (h *Helper) WaitCertificateRequestIssuedValidTLS(ns, name string, timeout time.Duration, key crypto.Signer, rootCAPEM []byte) error { + cr, err := h.WaitForCertificateRequestReady(ns, name, timeout) + if err != nil { + return err + } + + _, err = h.ValidateIssuedCertificateRequest(cr, key, rootCAPEM) + if err != nil { + return err + } + + return nil +} diff --git a/conformance/framework/helper/certificates.go b/conformance/framework/helper/certificates.go new file mode 100644 index 0000000..420694f --- /dev/null +++ b/conformance/framework/helper/certificates.go @@ -0,0 +1,329 @@ +/* +Copyright 2020 The cert-manager Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package helper + +import ( + "context" + "crypto/x509" + "fmt" + "sort" + "time" + + errors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/wait" + + apiutil "github.com/cert-manager/cert-manager/pkg/api/util" + v1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" + clientset "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned/typed/certmanager/v1" + "github.com/cert-manager/issuer-lib/conformance/framework/log" +) + +// WaitForCertificateToExist waits for the named certificate to exist and returns the certificate +func (h *Helper) WaitForCertificateToExist(namespace string, name string, timeout time.Duration) (*v1.Certificate, error) { + client := h.CMClient.CertmanagerV1().Certificates(namespace) + var certificate *v1.Certificate + logf, done := log.LogBackoff() + defer done() + + pollErr := wait.PollUntilContextTimeout(context.TODO(), 500*time.Millisecond, timeout, true, func(ctx context.Context) (bool, error) { + logf("Waiting for Certificate %v to exist", name) + var err error + certificate, err = client.Get(context.TODO(), name, metav1.GetOptions{}) + if errors.IsNotFound(err) { + return false, nil + } + if err != nil { + return false, fmt.Errorf("error getting Certificate %v: %v", name, err) + } + + return true, nil + }) + return certificate, pollErr +} + +func (h *Helper) waitForCertificateCondition(client clientset.CertificateInterface, name string, check func(*v1.Certificate) bool, timeout time.Duration) (*v1.Certificate, error) { + var certificate *v1.Certificate + pollErr := wait.PollUntilContextTimeout(context.TODO(), 500*time.Millisecond, timeout, true, func(ctx context.Context) (bool, error) { + var err error + certificate, err = client.Get(context.TODO(), name, metav1.GetOptions{}) + if nil != err { + certificate = nil + return false, fmt.Errorf("error getting Certificate %v: %v", name, err) + } + + return check(certificate), nil + }) + + return certificate, pollErr +} + +// WaitForCertificateReadyAndDoneIssuing waits for the certificate resource to be in a Ready=True state and not be in an Issuing state. +// The Ready=True condition will be checked against the provided certificate to make sure that it is up-to-date (condition gen. >= cert gen.). +func (h *Helper) WaitForCertificateReadyAndDoneIssuing(cert *v1.Certificate, timeout time.Duration) (*v1.Certificate, error) { + ready_true_condition := v1.CertificateCondition{ + Type: v1.CertificateConditionReady, + Status: cmmeta.ConditionTrue, + ObservedGeneration: cert.Generation, + } + issuing_true_condition := v1.CertificateCondition{ + Type: v1.CertificateConditionIssuing, + Status: cmmeta.ConditionTrue, + } + logf, done := log.LogBackoff() + defer done() + return h.waitForCertificateCondition(h.CMClient.CertmanagerV1().Certificates(cert.Namespace), cert.Name, func(certificate *v1.Certificate) bool { + if !apiutil.CertificateHasConditionWithObservedGeneration(certificate, ready_true_condition) { + logf( + "Expected Certificate %v condition %v=%v (generation >= %v) but it has: %v", + certificate.Name, + ready_true_condition.Type, + ready_true_condition.Status, + ready_true_condition.ObservedGeneration, + certificate.Status.Conditions, + ) + return false + } + + if apiutil.CertificateHasCondition(certificate, issuing_true_condition) { + logf("Expected Certificate %v condition %v to be missing but it has: %v", certificate.Name, issuing_true_condition.Type, certificate.Status.Conditions) + return false + } + + if certificate.Status.NextPrivateKeySecretName != nil { + logf("Expected Certificate %v 'next-private-key-secret-name' attribute to be empty but has: %v", certificate.Name, *certificate.Status.NextPrivateKeySecretName) + return false + } + + return true + }, timeout) +} + +// WaitForCertificateNotReadyAndDoneIssuing waits for the certificate resource to be in a Ready=False state and not be in an Issuing state. +// The Ready=False condition will be checked against the provided certificate to make sure that it is up-to-date (condition gen. >= cert gen.). +func (h *Helper) WaitForCertificateNotReadyAndDoneIssuing(cert *v1.Certificate, timeout time.Duration) (*v1.Certificate, error) { + ready_false_condition := v1.CertificateCondition{ + Type: v1.CertificateConditionReady, + Status: cmmeta.ConditionFalse, + ObservedGeneration: cert.Generation, + } + issuing_true_condition := v1.CertificateCondition{ + Type: v1.CertificateConditionIssuing, + Status: cmmeta.ConditionTrue, + } + logf, done := log.LogBackoff() + defer done() + return h.waitForCertificateCondition(h.CMClient.CertmanagerV1().Certificates(cert.Namespace), cert.Name, func(certificate *v1.Certificate) bool { + if !apiutil.CertificateHasConditionWithObservedGeneration(certificate, ready_false_condition) { + logf( + "Expected Certificate %v condition %v=%v (generation >= %v) but it has: %v", + certificate.Name, + ready_false_condition.Type, + ready_false_condition.Status, + ready_false_condition.ObservedGeneration, + certificate.Status.Conditions, + ) + return false + } + + if apiutil.CertificateHasCondition(certificate, issuing_true_condition) { + logf("Expected Certificate %v condition %v to be missing but it has: %v", certificate.Name, issuing_true_condition.Type, certificate.Status.Conditions) + return false + } + + if certificate.Status.NextPrivateKeySecretName != nil { + logf("Expected Certificate %v 'next-private-key-secret-name' attribute to be empty but has: %v", certificate.Name, *certificate.Status.NextPrivateKeySecretName) + return false + } + + return true + }, timeout) +} + +func (h *Helper) waitForIssuerCondition(client clientset.IssuerInterface, name string, check func(issuer *v1.Issuer) bool, timeout time.Duration) (*v1.Issuer, error) { + var issuer *v1.Issuer + pollErr := wait.PollUntilContextTimeout(context.TODO(), 500*time.Millisecond, timeout, true, func(ctx context.Context) (bool, error) { + var err error + issuer, err = client.Get(context.TODO(), name, metav1.GetOptions{}) + if nil != err { + issuer = nil + return false, fmt.Errorf("error getting Issuer %v: %v", name, err) + } + return check(issuer), nil + }) + + if pollErr != nil && issuer != nil { + log.Logf("Failed waiting for issuer %v :%v\n", name, pollErr.Error()) + } + + return issuer, pollErr +} + +// WaitIssuerReady waits for the Issuer resource to be in a Ready=True state +// The Ready=True condition will be checked against the provided issuer to make sure its ready. +func (h *Helper) WaitIssuerReady(issuer *v1.Issuer, timeout time.Duration) (*v1.Issuer, error) { + ready_true_condition := v1.IssuerCondition{ + Type: v1.IssuerConditionReady, + Status: cmmeta.ConditionTrue, + } + + logf, done := log.LogBackoff() + defer done() + return h.waitForIssuerCondition(h.CMClient.CertmanagerV1().Issuers(issuer.Namespace), issuer.Name, func(issuer *v1.Issuer) bool { + if !apiutil.IssuerHasCondition(issuer, ready_true_condition) { + logf( + "Expected Issuer %v condition %v=%v but it has: %v", + issuer.Name, + ready_true_condition.Type, + ready_true_condition.Status, + issuer.Status.Conditions, + ) + return false + } + return true + }, timeout) +} + +func (h *Helper) waitForClusterIssuerCondition(client clientset.ClusterIssuerInterface, name string, check func(issuer *v1.ClusterIssuer) bool, timeout time.Duration) (*v1.ClusterIssuer, error) { + var issuer *v1.ClusterIssuer + pollErr := wait.PollUntilContextTimeout(context.TODO(), 500*time.Millisecond, timeout, true, func(ctx context.Context) (bool, error) { + var err error + issuer, err = client.Get(context.TODO(), name, metav1.GetOptions{}) + if nil != err { + issuer = nil + return false, fmt.Errorf("error getting Issuer %v: %v", name, err) + } + return check(issuer), nil + }) + + if pollErr != nil && issuer != nil { + log.Logf("Failed waiting for issuer %v :%v\n", name, pollErr.Error()) + } + + return issuer, pollErr +} + +// WaitClusterIssuerReady waits for the Cluster Issuer resource to be in a Ready=True state +// The Ready=True condition will be checked against the provided issuer to make sure its ready. +func (h *Helper) WaitClusterIssuerReady(issuer *v1.ClusterIssuer, timeout time.Duration) (*v1.ClusterIssuer, error) { + ready_true_condition := v1.IssuerCondition{ + Type: v1.IssuerConditionReady, + Status: cmmeta.ConditionTrue, + } + logf, done := log.LogBackoff() + defer done() + return h.waitForClusterIssuerCondition(h.CMClient.CertmanagerV1().ClusterIssuers(), issuer.Name, func(issuer *v1.ClusterIssuer) bool { + if !apiutil.IssuerHasCondition(issuer, ready_true_condition) { + logf( + "Expected Cluster Issuer %v condition %v=%v but it has: %v", + issuer.Name, + ready_true_condition.Type, + ready_true_condition.Status, + issuer.Status.Conditions, + ) + return false + } + return true + }, timeout) +} + +func (h *Helper) deduplicateExtKeyUsages(us []x509.ExtKeyUsage) []x509.ExtKeyUsage { + extKeyUsagesMap := make(map[x509.ExtKeyUsage]bool) + for _, e := range us { + extKeyUsagesMap[e] = true + } + + us = make([]x509.ExtKeyUsage, 0) + for e, ok := range extKeyUsagesMap { + if ok { + us = append(us, e) + } + } + + return us +} + +func (h *Helper) defaultKeyUsagesToAdd(ns string, issuerRef *cmmeta.ObjectReference) (x509.KeyUsage, []x509.ExtKeyUsage, error) { + var issuerSpec *v1.IssuerSpec + switch issuerRef.Kind { + case "ClusterIssuer": + issuerObj, err := h.CMClient.CertmanagerV1().ClusterIssuers().Get(context.TODO(), issuerRef.Name, metav1.GetOptions{}) + if err != nil { + return 0, nil, fmt.Errorf("failed to find referenced ClusterIssuer %v: %s", + issuerRef, err) + } + + issuerSpec = &issuerObj.Spec + default: + issuerObj, err := h.CMClient.CertmanagerV1().Issuers(ns).Get(context.TODO(), issuerRef.Name, metav1.GetOptions{}) + if err != nil { + return 0, nil, fmt.Errorf("failed to find referenced Issuer %v: %s", + issuerRef, err) + } + + issuerSpec = &issuerObj.Spec + } + + var keyUsages x509.KeyUsage + var extKeyUsages []x509.ExtKeyUsage + + // Vault and ACME issuers will add server auth and client auth extended key + // usages by default so we need to add them to the list of expected usages + if issuerSpec.ACME != nil || issuerSpec.Vault != nil { + extKeyUsages = append(extKeyUsages, x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth) + } + + // Vault issuers will add key agreement key usage + if issuerSpec.Vault != nil { + keyUsages |= x509.KeyUsageKeyAgreement + } + + // Venafi issue adds server auth key usage + if issuerSpec.Venafi != nil { + extKeyUsages = append(extKeyUsages, x509.ExtKeyUsageServerAuth) + } + + return keyUsages, extKeyUsages, nil +} + +func (h *Helper) keyUsagesMatch(aKU x509.KeyUsage, aEKU []x509.ExtKeyUsage, + bKU x509.KeyUsage, bEKU []x509.ExtKeyUsage) bool { + if aKU != bKU { + return false + } + + if len(aEKU) != len(bEKU) { + return false + } + + sort.SliceStable(aEKU, func(i, j int) bool { + return aEKU[i] < aEKU[j] + }) + + sort.SliceStable(bEKU, func(i, j int) bool { + return bEKU[i] < bEKU[j] + }) + + for i := range aEKU { + if aEKU[i] != bEKU[i] { + return false + } + } + + return true +} diff --git a/conformance/framework/helper/certificatesigningrequests.go b/conformance/framework/helper/certificatesigningrequests.go new file mode 100644 index 0000000..85e8c2c --- /dev/null +++ b/conformance/framework/helper/certificatesigningrequests.go @@ -0,0 +1,61 @@ +/* +Copyright 2021 The cert-manager Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package helper + +import ( + "context" + "fmt" + "time" + + certificatesv1 "k8s.io/api/certificates/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/wait" + + "github.com/cert-manager/cert-manager/pkg/controller/certificatesigningrequests/util" + "github.com/cert-manager/issuer-lib/conformance/framework/log" +) + +// WaitForCertificateSigningRequestSigned waits for the +// CertificateSigningRequest resource to be signed. +func (h *Helper) WaitForCertificateSigningRequestSigned(name string, timeout time.Duration) (*certificatesv1.CertificateSigningRequest, error) { + var csr *certificatesv1.CertificateSigningRequest + logf, done := log.LogBackoff() + defer done() + err := wait.PollUntilContextTimeout(context.TODO(), time.Second, timeout, true, func(ctx context.Context) (bool, error) { + var err error + logf("Waiting for CertificateSigningRequest %s to be ready", name) + csr, err = h.KubeClient.CertificatesV1().CertificateSigningRequests().Get(context.TODO(), name, metav1.GetOptions{}) + if err != nil { + return false, fmt.Errorf("error getting CertificateSigningRequest %s: %v", name, err) + } + + if util.CertificateSigningRequestIsFailed(csr) { + return false, fmt.Errorf("CertificateSigningRequest has failed: %v", csr.Status) + } + + if len(csr.Status.Certificate) == 0 { + return false, nil + } + return true, nil + }) + + if err != nil { + return nil, err + } + + return csr, nil +} diff --git a/conformance/framework/helper/featureset/featureset.go b/conformance/framework/helper/featureset/featureset.go new file mode 100644 index 0000000..00af198 --- /dev/null +++ b/conformance/framework/helper/featureset/featureset.go @@ -0,0 +1,165 @@ +/* +Copyright 2020 The cert-manager Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package featureset + +import "strings" + +// NewFeatureSet constructs a new feature set with the given features. +func NewFeatureSet(feats ...Feature) FeatureSet { + fs := make(FeatureSet) + for _, f := range feats { + fs.Add(f) + } + return fs +} + +// FeatureSet represents a set of features. +// This type does not indicate whether or not features are enabled, rather it +// just defines a grouping of features (i.e. a 'set'). +type FeatureSet map[Feature]struct{} + +// Add adds features to the set +func (fs FeatureSet) Add(f ...Feature) FeatureSet { + for _, feat := range f { + fs[feat] = struct{}{} + } + return fs +} + +// Delete removes a feature from the set +func (fs FeatureSet) Delete(f Feature) { + delete(fs, f) +} + +// Contains returns true if the FeatureSet contains the given feature +func (fs FeatureSet) Contains(f Feature) bool { + _, ok := fs[f] + return ok +} + +// Copy returns a new copy of an existing Feature Set. +// It is not safe to be called by multiple goroutines. +func (fs FeatureSet) Copy() FeatureSet { + new := make(FeatureSet) + for k, v := range fs { + new[k] = v + } + return new +} + +// List returns a slice of all features in the set. +func (fs FeatureSet) List() []Feature { + var ret []Feature + for k := range fs { + ret = append(ret, k) + } + return ret +} + +// String returns this FeatureSet as a comma separated string +func (fs FeatureSet) String() string { + featsSlice := make([]string, len(fs)) + + i := 0 + for f := range fs { + featsSlice[i] = string(f) + i++ + } + + return strings.Join(featsSlice, ", ") +} + +type Feature string + +// String returns the Feature name as a string +func (f Feature) String() string { + return string(f) +} + +const ( + // IPAddressFeature denotes tests that set the IPAddresses field. + // Some issuer's are never going to allow issuing certificates with IP SANs + // set as they are considered bad-practice. + IPAddressFeature Feature = "IPAddresses" + + // DurationFeature denotes tests that set the 'duration' field to some + // custom value. + // Some issuers enforce a particular certificate duration, meaning they + // will never pass tests that validate the duration is as expected. + DurationFeature Feature = "Duration" + + // WildcardsFeature denotes tests that request certificates for wildcard + // domains. Some issuer's disable wildcard certificate issuance, so this + // feature allows runs of the suite to exclude those tests that utilise + // wildcards. + WildcardsFeature Feature = "Wildcards" + + // ECDSAFeature denotes whether the target issuer is able to sign + // certificates with an elliptic curve private key. + ECDSAFeature Feature = "ECDSA" + + // ReusePrivateKey denotes whether the target issuer is able to sign multiple + // certificates for the same private key. + ReusePrivateKeyFeature Feature = "ReusePrivateKey" + + // URISANs denotes whether to the target issuer is able to sign a certificate + // that includes a URISANs. ACME providers do not support this. + URISANsFeature Feature = "URISANs" + + // EmailSANs denotes whether to the target issuer is able to sign a certificate + // that includes a EmailSANs. + EmailSANsFeature Feature = "EmailSANs" + + // CommonName denotes whether the target issuer is able to sign certificates + // with a distinct CommonName. This is useful for issuers such as ACME + // providers that ignore, or otherwise have special requirements for the + // CommonName such as needing to be present in the DNS Name list. + CommonNameFeature = "CommonName" + + // KeyUsages denotes whether the target issuer is able to sign certificates + // with arbitrary key usages. + KeyUsagesFeature = "KeyUsages" + + // OnlySAN denotes whether the target issuer is able to sign certificates + // with only SANs set + OnlySAN = "OnlySAN" + + // SaveCAToSecret denotes whether the target issuer returns a CA + // certificate which can be stored in the ca.crt field of the Secret. + SaveCAToSecret = "SaveCAToSecret" + + // SaveRootCAToSecret denotes whether the CA certificate is expected to + // represent a root CA (sub-feature of SaveCAToSecret) + SaveRootCAToSecret = "SaveRootCAToSecret" + + // Ed25519FeatureSet denotes whether the target issuer is able to sign + // certificates with an Ed25519 private key. + Ed25519FeatureSet Feature = "Ed25519" + + // IssueCAFeature denotes whether the target issuer is able to issue CA + // certificates (i.e., certificates for which the CA basicConstraint is true) + IssueCAFeature Feature = "IssueCA" + + // LongDomainFeatureSet denotes whether the target issuer is able to sign + // a certificate that defines a domain containing a label of 63 characters. + LongDomainFeatureSet Feature = "LongDomain" + + // LiteralSubjectFeature denotes whether the target issuer is able to sign + // a certificate containing an arbitrary Subject in the CSR, without + // imposing requirements on form or structure. + LiteralSubjectFeature Feature = "LiteralCertificateSubject" +) diff --git a/conformance/framework/helper/helper.go b/conformance/framework/helper/helper.go new file mode 100644 index 0000000..87a046b --- /dev/null +++ b/conformance/framework/helper/helper.go @@ -0,0 +1,38 @@ +/* +Copyright 2020 The cert-manager Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package helper + +import ( + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" + + cmclient "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned" +) + +// Helper provides methods for common operations needed during tests. +type Helper struct { + KubeClientConfig *rest.Config + + KubeClient kubernetes.Interface + CMClient cmclient.Interface +} + +func NewHelper(kubeClientConfig *rest.Config) *Helper { + return &Helper{ + KubeClientConfig: kubeClientConfig, + } +} diff --git a/conformance/framework/helper/secret.go b/conformance/framework/helper/secret.go new file mode 100644 index 0000000..9f1239f --- /dev/null +++ b/conformance/framework/helper/secret.go @@ -0,0 +1,59 @@ +/* +Copyright 2020 The cert-manager Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package helper + +import ( + "context" + "fmt" + "time" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/wait" + + "github.com/cert-manager/issuer-lib/conformance/framework/log" +) + +// WaitForSecretCertificateData waits for the certificate data to be ready +// inside a Secret created by cert-manager. +func (h *Helper) WaitForSecretCertificateData(ns, name string, timeout time.Duration) (*corev1.Secret, error) { + var secret *corev1.Secret + logf, done := log.LogBackoff() + defer done() + err := wait.PollUntilContextTimeout(context.TODO(), time.Second, timeout, true, func(ctx context.Context) (bool, error) { + var err error + logf("Waiting for Secret %s:%s to contain a certificate", ns, name) + secret, err = h.KubeClient.CoreV1().Secrets(ns).Get(context.TODO(), name, metav1.GetOptions{}) + if err != nil { + return false, fmt.Errorf("error getting secret %s: %s", name, err) + } + + if len(secret.Data[corev1.TLSCertKey]) > 0 { + return true, nil + } + + logf("Secret still does not contain certificate data %s/%s", + secret.Namespace, secret.Name) + return false, nil + }) + + if err != nil { + return nil, err + } + + return secret, nil +} diff --git a/conformance/framework/helper/validate.go b/conformance/framework/helper/validate.go new file mode 100644 index 0000000..ae50600 --- /dev/null +++ b/conformance/framework/helper/validate.go @@ -0,0 +1,70 @@ +/* +Copyright 2020 The cert-manager Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package helper + +import ( + "context" + "crypto" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + "github.com/cert-manager/issuer-lib/conformance/framework/helper/validation" + "github.com/cert-manager/issuer-lib/conformance/framework/helper/validation/certificates" + "github.com/cert-manager/issuer-lib/conformance/framework/helper/validation/certificatesigningrequests" +) + +// ValidateCertificate retrieves the issued certificate and runs all validation functions +func (h *Helper) ValidateCertificate(certificate *cmapi.Certificate, validations ...certificates.ValidationFunc) error { + if len(validations) == 0 { + validations = validation.DefaultCertificateSet() + } + + secret, err := h.KubeClient.CoreV1().Secrets(certificate.Namespace).Get(context.TODO(), certificate.Spec.SecretName, metav1.GetOptions{}) + if err != nil { + return err + } + + for _, fn := range validations { + err := fn(certificate, secret) + if err != nil { + return err + } + } + + return nil +} + +// ValidateCertificateSigningRequest retrieves the issued certificate and runs all validation functions +func (h *Helper) ValidateCertificateSigningRequest(name string, key crypto.Signer, validations ...certificatesigningrequests.ValidationFunc) error { + if len(validations) == 0 { + validations = validation.DefaultCertificateSigningRequestSet() + } + csr, err := h.KubeClient.CertificatesV1().CertificateSigningRequests().Get(context.TODO(), name, metav1.GetOptions{}) + if err != nil { + return err + } + + for _, fn := range validations { + err := fn(csr, key) + if err != nil { + return err + } + } + + return nil +} diff --git a/conformance/framework/helper/validation/certificates/certificates.go b/conformance/framework/helper/validation/certificates/certificates.go new file mode 100644 index 0000000..18ff91f --- /dev/null +++ b/conformance/framework/helper/validation/certificates/certificates.go @@ -0,0 +1,437 @@ +/* +Copyright 2020 The cert-manager Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package certificates + +import ( + "bytes" + "crypto/ecdsa" + "crypto/ed25519" + "crypto/rsa" + "crypto/x509" + "encoding/pem" + "fmt" + "strings" + "time" + + "github.com/kr/pretty" + corev1 "k8s.io/api/core/v1" + + apiutil "github.com/cert-manager/cert-manager/pkg/api/util" + cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" + "github.com/cert-manager/cert-manager/pkg/util" + "github.com/cert-manager/cert-manager/pkg/util/pki" +) + +// ValidationFunc describes a Certificate validation helper function +type ValidationFunc func(certificate *cmapi.Certificate, secret *corev1.Secret) error + +// ExpectValidKeysInSecret checks that the secret contains valid keys +func ExpectValidKeysInSecret(_ *cmapi.Certificate, secret *corev1.Secret) error { + validKeys := []string{corev1.TLSPrivateKeyKey, corev1.TLSCertKey, cmmeta.TLSCAKey, cmapi.CertificateOutputFormatDERKey, cmapi.CertificateOutputFormatCombinedPEMKey} + nbValidKeys := 0 + for k := range secret.Data { + for _, k2 := range validKeys { + if k == k2 { + nbValidKeys++ + break + } + } + } + if nbValidKeys < 2 { + return fmt.Errorf("Expected at least 2 valid keys in certificate secret, but there was %d", nbValidKeys) + } + return nil +} + +// ExpectValidAnnotations checks if the correct annotations on the secret are present +func ExpectValidAnnotations(certificate *cmapi.Certificate, secret *corev1.Secret) error { + label, ok := secret.Annotations[cmapi.CertificateNameKey] + if !ok { + return fmt.Errorf("Expected secret to have certificate-name label, but had none") + } + + if label != certificate.Name { + return fmt.Errorf("Expected secret to have certificate-name label with a value of %q, but got %q", certificate.Name, label) + } + + return nil +} + +// ExpectValidPrivateKeyData checks of the secret's private key matches the request +func ExpectValidPrivateKeyData(certificate *cmapi.Certificate, secret *corev1.Secret) error { + keyBytes, ok := secret.Data[corev1.TLSPrivateKeyKey] + if !ok { + return fmt.Errorf("No private key data found for Certificate %q (secret %q)", certificate.Name, certificate.Spec.SecretName) + } + key, err := pki.DecodePrivateKeyBytes(keyBytes) + if err != nil { + return err + } + + // validate private key is of the correct type (rsa, ed25519 or ecdsa) + if certificate.Spec.PrivateKey != nil { + switch certificate.Spec.PrivateKey.Algorithm { + case cmapi.PrivateKeyAlgorithm(""), + cmapi.RSAKeyAlgorithm: + _, ok := key.(*rsa.PrivateKey) + if !ok { + return fmt.Errorf("Expected private key of type RSA, but it was: %T", key) + } + case cmapi.ECDSAKeyAlgorithm: + _, ok := key.(*ecdsa.PrivateKey) + if !ok { + return fmt.Errorf("Expected private key of type ECDSA, but it was: %T", key) + } + case cmapi.Ed25519KeyAlgorithm: + _, ok := key.(ed25519.PrivateKey) + if !ok { + return fmt.Errorf("Expected private key of type Ed25519, but it was: %T", key) + } + default: + return fmt.Errorf("unrecognised requested private key algorithm %q", certificate.Spec.PrivateKey.Algorithm) + } + } + + // TODO: validate private key KeySize + return nil +} + +// ExpectValidCertificate checks if the certificate is a valid x509 certificate +func ExpectValidCertificate(certificate *cmapi.Certificate, secret *corev1.Secret) error { + certBytes, ok := secret.Data[corev1.TLSCertKey] + if !ok { + return fmt.Errorf("No certificate data found for Certificate %q (secret %q)", certificate.Name, certificate.Spec.SecretName) + } + + _, err := pki.DecodeX509CertificateBytes(certBytes) + if err != nil { + return err + } + + return nil +} + +// ExpectCertificateOrganizationToMatch checks if the issued certificate has the same Organization as the requested one +func ExpectCertificateOrganizationToMatch(certificate *cmapi.Certificate, secret *corev1.Secret) error { + cert, err := pki.DecodeX509CertificateBytes(secret.Data[corev1.TLSCertKey]) + if err != nil { + return err + } + + expectedOrganization := pki.OrganizationForCertificate(certificate) + if !util.EqualUnsorted(cert.Subject.Organization, expectedOrganization) { + return fmt.Errorf("Expected certificate valid for O %v, but got a certificate valid for O %v", expectedOrganization, cert.Subject.Organization) + } + + return nil +} + +// ExpectCertificateDNSNamesToMatch checks if the issued certificate has all DNS names it requested +func ExpectCertificateDNSNamesToMatch(certificate *cmapi.Certificate, secret *corev1.Secret) error { + cert, err := pki.DecodeX509CertificateBytes(secret.Data[corev1.TLSCertKey]) + if err != nil { + return err + } + + expectedDNSNames := certificate.Spec.DNSNames + if !util.Subset(cert.DNSNames, expectedDNSNames) { + return fmt.Errorf("Expected certificate valid for DNSNames %v, but got a certificate valid for DNSNames %v", expectedDNSNames, cert.DNSNames) + } + + return nil +} + +// ExpectCertificateURIsToMatch checks if the issued certificate has all URI SANs names it requested +func ExpectCertificateURIsToMatch(certificate *cmapi.Certificate, secret *corev1.Secret) error { + cert, err := pki.DecodeX509CertificateBytes(secret.Data[corev1.TLSCertKey]) + if err != nil { + return err + } + + uris, err := pki.URIsForCertificate(certificate) + if err != nil { + return fmt.Errorf("failed to parse URIs: %s", err) + } + actualURIs := pki.URLsToString(cert.URIs) + expectedURIs := pki.URLsToString(uris) + if !util.EqualUnsorted(actualURIs, expectedURIs) { + return fmt.Errorf("Expected certificate valid for URIs %v, but got a certificate valid for URIs %v", expectedURIs, pki.URLsToString(cert.URIs)) + } + + return nil +} + +// ExpectValidCommonName checks if the issued certificate has the requested CN or one of the DNS SANs +func ExpectValidCommonName(certificate *cmapi.Certificate, secret *corev1.Secret) error { + cert, err := pki.DecodeX509CertificateBytes(secret.Data[corev1.TLSCertKey]) + if err != nil { + return err + } + + expectedCN := certificate.Spec.CommonName + + if len(expectedCN) == 0 && len(cert.Subject.CommonName) > 0 { + // no CN is specified but our CA set one, checking if it is one of our DNS names or IP Addresses + if !util.Contains(cert.DNSNames, cert.Subject.CommonName) && !util.Contains(pki.IPAddressesToString(cert.IPAddresses), cert.Subject.CommonName) { + return fmt.Errorf("Expected a common name for one of our DNSNames %v or IP Addresses %v, but got a CN of %v", cert.DNSNames, pki.IPAddressesToString(cert.IPAddresses), cert.Subject.CommonName) + } + } else if expectedCN != cert.Subject.CommonName { + return fmt.Errorf("Expected a common name of %v, but got a CN of %v", expectedCN, cert.Subject.CommonName) + } + + return nil +} + +// ExpectValidNotAfterDate checks if the issued certificate matches the requested duration +func ExpectValidNotAfterDate(certificate *cmapi.Certificate, secret *corev1.Secret) error { + cert, err := pki.DecodeX509CertificateBytes(secret.Data[corev1.TLSCertKey]) + if err != nil { + return err + } + if certificate.Status.NotAfter == nil { + return fmt.Errorf("No certificate expiration found for Certificate %q", certificate.Name) + } + + if !cert.NotAfter.Equal(certificate.Status.NotAfter.Time) { + return fmt.Errorf("Expected certificate expiry date to be %v, but got %v", certificate.Status.NotAfter, cert.NotAfter) + } + + return nil +} + +func containsExtKeyUsage(s []x509.ExtKeyUsage, e x509.ExtKeyUsage) bool { + for _, a := range s { + if a == e { + return true + } + } + return false +} + +// ExpectDurationToMatch checks if the issued certificate matches the requested duration +func ExpectDurationToMatch(certificate *cmapi.Certificate, secret *corev1.Secret) error { + cert, err := pki.DecodeX509CertificateBytes(secret.Data[corev1.TLSCertKey]) + if err != nil { + return err + } + + duration := apiutil.DefaultCertDuration(certificate.Spec.Duration) + fuzz := 30 * time.Second + + certDuration := cert.NotAfter.Sub(cert.NotBefore) + if certDuration > (duration+fuzz) || certDuration < (duration-fuzz) { + return fmt.Errorf( + "Expected duration of %s, got %s (fuzz: %s) [NotBefore: %s, NotAfter: %s]", + duration, certDuration, fuzz, + cert.NotBefore.Format(time.RFC3339), cert.NotAfter.Format(time.RFC3339), + ) + } + + return nil +} + +// ExpectKeyUsageExtKeyUsageServerAuth checks if the issued certificate has the extended key usage of server auth +func ExpectKeyUsageExtKeyUsageServerAuth(certificate *cmapi.Certificate, secret *corev1.Secret) error { + cert, err := pki.DecodeX509CertificateBytes(secret.Data[corev1.TLSCertKey]) + if err != nil { + return err + } + + if !containsExtKeyUsage(cert.ExtKeyUsage, x509.ExtKeyUsageServerAuth) { + return fmt.Errorf("Expected certificate to have ExtKeyUsageServerAuth, but got %v", cert.ExtKeyUsage) + } + return nil +} + +// ExpectKeyUsageExtKeyUsageClientAuth checks if the issued certificate has the extended key usage of client auth +func ExpectKeyUsageExtKeyUsageClientAuth(certificate *cmapi.Certificate, secret *corev1.Secret) error { + cert, err := pki.DecodeX509CertificateBytes(secret.Data[corev1.TLSCertKey]) + if err != nil { + return err + } + + if !containsExtKeyUsage(cert.ExtKeyUsage, x509.ExtKeyUsageClientAuth) { + return fmt.Errorf("Expected certificate to have ExtKeyUsageClientAuth, but got %v", cert.ExtKeyUsage) + } + return nil +} + +// UsageDigitalSignature checks if a cert has the KeyUsageDigitalSignature key usage set +func ExpectKeyUsageUsageDigitalSignature(certificate *cmapi.Certificate, secret *corev1.Secret) error { + cert, err := pki.DecodeX509CertificateBytes(secret.Data[corev1.TLSCertKey]) + if err != nil { + return err + } + + // taking the key usage here and use a binary OR to flip all non KeyUsageCertSign bits to 0 + // so if KeyUsageCertSign the value will be exactly x509.KeyUsageCertSign + usage := cert.KeyUsage + usage &= x509.KeyUsageDigitalSignature + if usage != x509.KeyUsageDigitalSignature { + return fmt.Errorf("Expected certificate to have KeyUsageDigitalSignature %#b, but got %v %#b", x509.KeyUsageDigitalSignature, usage, usage) + } + + return nil +} + +// ExpectKeyUsageUsageDataEncipherment checks if a cert has the KeyUsageDataEncipherment key usage set +func ExpectKeyUsageUsageDataEncipherment(certificate *cmapi.Certificate, secret *corev1.Secret) error { + cert, err := pki.DecodeX509CertificateBytes(secret.Data[corev1.TLSCertKey]) + if err != nil { + return err + } + + // taking the key usage here and use a binary OR to flip all non KeyUsageDataEncipherment bits to 0 + // so if KeyUsageDataEncipherment the value will be exactly x509.KeyUsageDataEncipherment + usage := cert.KeyUsage + usage &= x509.KeyUsageDataEncipherment + if usage != x509.KeyUsageDataEncipherment { + return fmt.Errorf("Expected certificate to have KeyUsageDataEncipherment %#b, but got %v %#b", x509.KeyUsageDataEncipherment, usage, usage) + } + + return nil +} + +// ExpectEmailsToMatch check if the issued certificate has all requested email SANs +func ExpectEmailsToMatch(certificate *cmapi.Certificate, secret *corev1.Secret) error { + cert, err := pki.DecodeX509CertificateBytes(secret.Data[corev1.TLSCertKey]) + if err != nil { + return err + } + + if !util.EqualUnsorted(cert.EmailAddresses, certificate.Spec.EmailAddresses) { + return fmt.Errorf("certificate doesn't contain Email SANs: exp=%v got=%v", certificate.Spec.EmailAddresses, cert.EmailAddresses) + } + + return nil +} + +// ExpectCorrectTrustChain checks if the cert is signed by the root CA if one is provided +func ExpectCorrectTrustChain(certificate *cmapi.Certificate, secret *corev1.Secret) error { + cert, err := pki.DecodeX509CertificateBytes(secret.Data[corev1.TLSCertKey]) + if err != nil { + return err + } + + var dnsName string + if len(certificate.Spec.DNSNames) > 0 { + dnsName = certificate.Spec.DNSNames[0] + } + + rootCertPool := x509.NewCertPool() + rootCertPool.AppendCertsFromPEM(secret.Data[cmmeta.TLSCAKey]) + intermediateCertPool := x509.NewCertPool() + intermediateCertPool.AppendCertsFromPEM(secret.Data[corev1.TLSCertKey]) + opts := x509.VerifyOptions{ + DNSName: dnsName, + Intermediates: intermediateCertPool, + Roots: rootCertPool, + } + + if _, err := cert.Verify(opts); err != nil { + return fmt.Errorf( + "verify error. CERT:\n%s\nROOTS\n%s\nINTERMEDIATES\n%v\nERROR\n%s\n", + pretty.Sprint(cert), + pretty.Sprint(rootCertPool), + pretty.Sprint(intermediateCertPool), + err, + ) + } + + return nil +} + +// ExpectCARootCertificate checks if the CA cert is root CA if one is provided +func ExpectCARootCertificate(certificate *cmapi.Certificate, secret *corev1.Secret) error { + caCert, err := pki.DecodeX509CertificateBytes(secret.Data[cmmeta.TLSCAKey]) + if err != nil { + return err + } + if !bytes.Equal(caCert.RawSubject, caCert.RawIssuer) { + return fmt.Errorf("expected CA certificate to be root CA; want Issuer %v, but got %v", caCert.Subject, caCert.Issuer) + } + + return nil +} + +// ExpectConditionReadyObservedGeneration checks that the ObservedGeneration +// field on the Ready condition which must be true, is set to the Generation of +// the Certificate. +func ExpectConditionReadyObservedGeneration(certificate *cmapi.Certificate, secret *corev1.Secret) error { + cond := apiutil.GetCertificateCondition(certificate, cmapi.CertificateConditionReady) + + if cond.Status != cmmeta.ConditionTrue || cond.ObservedGeneration != certificate.Generation { + return fmt.Errorf("expected Certificate to have ready condition true, observedGeneration matching the Certificate generation, got=%+v", + cond) + } + + return nil +} + +// ExpectValidBasicConstraints asserts that basicConstraints are set correctly on issued certificates +func ExpectValidBasicConstraints(certificate *cmapi.Certificate, secret *corev1.Secret) error { + cert, err := pki.DecodeX509CertificateBytes(secret.Data[corev1.TLSCertKey]) + if err != nil { + return err + } + + if certificate.Spec.IsCA != cert.IsCA { + return fmt.Errorf("Expected CA basicConstraint to be %v, but got %v", certificate.Spec.IsCA, cert.IsCA) + } + + // TODO: also validate pathLen + + return nil +} + +// ExpectValidAdditionalOutputFormats assert that if additional output formats are requested +// It contains the additional output format keys in the secret and the content are valid. +func ExpectValidAdditionalOutputFormats(certificate *cmapi.Certificate, secret *corev1.Secret) error { + if len(certificate.Spec.AdditionalOutputFormats) > 0 { + for _, f := range certificate.Spec.AdditionalOutputFormats { + switch f.Type { + case cmapi.CertificateOutputFormatDER: + if derKey, ok := secret.Data[cmapi.CertificateOutputFormatDERKey]; ok { + privateKey := secret.Data[corev1.TLSPrivateKeyKey] + block, _ := pem.Decode(privateKey) + if !bytes.Equal(derKey, block.Bytes) { + return fmt.Errorf("expected additional output Format DER %s to contain the binary formated private Key", cmapi.CertificateOutputFormatDERKey) + } + } else { + return fmt.Errorf("expected additional output format DER key %s to be present in secret", cmapi.CertificateOutputFormatDERKey) + } + case cmapi.CertificateOutputFormatCombinedPEM: + if combinedPem, ok := secret.Data[cmapi.CertificateOutputFormatCombinedPEMKey]; ok { + privateKey := secret.Data[corev1.TLSPrivateKeyKey] + certificate := secret.Data[corev1.TLSCertKey] + expectedCombinedPem := []byte(strings.Join([]string{string(privateKey), string(certificate)}, "\n")) + if !bytes.Equal(combinedPem, expectedCombinedPem) { + return fmt.Errorf("expected additional output format CombinedPEM %s to contain the combination of privateKey and certificate", cmapi.CertificateOutputFormatCombinedPEMKey) + } + } else { + return fmt.Errorf("expected additional output format CombinedPEM key %s to be present in secret", cmapi.CertificateOutputFormatCombinedPEMKey) + } + + default: + return fmt.Errorf("unknown additional output format %s", f.Type) + } + } + } + + return nil +} diff --git a/conformance/framework/helper/validation/certificatesigningrequests/certificatesigningrequests.go b/conformance/framework/helper/validation/certificatesigningrequests/certificatesigningrequests.go new file mode 100644 index 0000000..84cc524 --- /dev/null +++ b/conformance/framework/helper/validation/certificatesigningrequests/certificatesigningrequests.go @@ -0,0 +1,369 @@ +/* +Copyright 2021 The cert-manager Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package certificatesigningrequests + +import ( + "crypto" + "crypto/ecdsa" + "crypto/ed25519" + "crypto/rsa" + "crypto/x509" + "errors" + "fmt" + "time" + + certificatesv1 "k8s.io/api/certificates/v1" + + cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + experimentalapi "github.com/cert-manager/cert-manager/pkg/apis/experimental/v1alpha1" + ctrlutil "github.com/cert-manager/cert-manager/pkg/controller/certificatesigningrequests/util" + "github.com/cert-manager/cert-manager/pkg/util" + "github.com/cert-manager/cert-manager/pkg/util/pki" +) + +// ValidationFunc describes a CertificateSigningRequest validation helper function +type ValidationFunc func(csr *certificatesv1.CertificateSigningRequest, key crypto.Signer) error + +// ExpectValidCertificateCertificate checks if the certificate is a valid x509 certificate +func ExpectValidCertificate(csr *certificatesv1.CertificateSigningRequest, _ crypto.Signer) error { + _, err := pki.DecodeX509CertificateBytes(csr.Status.Certificate) + return err +} + +// ExpectCertificateOrganizationToMatch checks if the issued +// certificate has the same Organization as the requested one +func ExpectCertificateOrganizationToMatch(csr *certificatesv1.CertificateSigningRequest, _ crypto.Signer) error { + cert, err := pki.DecodeX509CertificateBytes(csr.Status.Certificate) + if err != nil { + return err + } + req, err := pki.DecodeX509CertificateRequestBytes(csr.Spec.Request) + if err != nil { + return err + } + + expectedOrganization := req.Subject.Organization + if !util.EqualUnsorted(cert.Subject.Organization, expectedOrganization) { + return fmt.Errorf("Expected certificate valid for O %v, but got a certificate valid for O %v", expectedOrganization, cert.Subject.Organization) + } + + return nil +} + +// ExpectValidPrivateKeyData checks the requesting private key matches the +// signed certificate +func ExpectValidPrivateKeyData(csr *certificatesv1.CertificateSigningRequest, key crypto.Signer) error { + cert, err := pki.DecodeX509CertificateBytes(csr.Status.Certificate) + if err != nil { + return err + } + + equal := func() (bool, error) { + switch pub := key.Public().(type) { + case *rsa.PublicKey: + return pub.Equal(cert.PublicKey), nil + case *ecdsa.PublicKey: + return pub.Equal(cert.PublicKey), nil + case ed25519.PublicKey: + return pub.Equal(cert.PublicKey), nil + default: + return false, fmt.Errorf("Unrecognised public key type: %T", key) + } + } + + ok, err := equal() + if err != nil { + return err + } + if !ok { + return errors.New("Expected signed certificate's public key to match requester's private key") + } + + return nil +} + +// ExpectCertificateDNSNamesToMatch checks if the issued certificate has all +// DNS names it requested, accounting for the CommonName being optionally +// copied to the DNS Names +func ExpectCertificateDNSNamesToMatch(csr *certificatesv1.CertificateSigningRequest, _ crypto.Signer) error { + cert, err := pki.DecodeX509CertificateBytes(csr.Status.Certificate) + if err != nil { + return err + } + req, err := pki.DecodeX509CertificateRequestBytes(csr.Spec.Request) + if err != nil { + return err + } + + if !util.EqualUnsorted(cert.DNSNames, req.DNSNames) && + !util.EqualUnsorted(cert.DNSNames, append(req.DNSNames, req.Subject.CommonName)) { + return fmt.Errorf("Expected certificate valid for DNSNames %v, but got a certificate valid for DNSNames %v", req.DNSNames, cert.DNSNames) + } + + return nil +} + +// ExpectCertificateURIsToMatch checks if the issued certificate +// has all URI SANs names it requested +func ExpectCertificateURIsToMatch(csr *certificatesv1.CertificateSigningRequest, _ crypto.Signer) error { + cert, err := pki.DecodeX509CertificateBytes(csr.Status.Certificate) + if err != nil { + return err + } + req, err := pki.DecodeX509CertificateRequestBytes(csr.Spec.Request) + if err != nil { + return err + } + + actualURIs := pki.URLsToString(cert.URIs) + expectedURIs := pki.URLsToString(req.URIs) + if !util.EqualUnsorted(actualURIs, expectedURIs) { + return fmt.Errorf("Expected certificate valid for URIs %v, but got a certificate valid for URIs %v", expectedURIs, actualURIs) + } + + return nil +} + +// ExpectCertificateIPsToMatch checks if the issued certificate +// has all IP SANs names it requested +func ExpectCertificateIPsToMatch(csr *certificatesv1.CertificateSigningRequest, _ crypto.Signer) error { + cert, err := pki.DecodeX509CertificateBytes(csr.Status.Certificate) + if err != nil { + return err + } + req, err := pki.DecodeX509CertificateRequestBytes(csr.Spec.Request) + if err != nil { + return err + } + + actualIPs := pki.IPAddressesToString(cert.IPAddresses) + expectedIPs := pki.IPAddressesToString(req.IPAddresses) + if !util.EqualUnsorted(actualIPs, expectedIPs) { + return fmt.Errorf("Expected certificate valid for IPs %v, but got a certificate valid for IPs %v", expectedIPs, actualIPs) + } + + return nil +} + +// ExpectValidCommonName checks if the issued certificate has the requested CN or one of the DNS SANs +func ExpectValidCommonName(csr *certificatesv1.CertificateSigningRequest, _ crypto.Signer) error { + cert, err := pki.DecodeX509CertificateBytes(csr.Status.Certificate) + if err != nil { + return err + } + req, err := pki.DecodeX509CertificateRequestBytes(csr.Spec.Request) + if err != nil { + return err + } + + expectedCN := req.Subject.CommonName + + if len(expectedCN) == 0 && len(cert.Subject.CommonName) > 0 { + // no CN is specified but our CA set one, checking if it is one of our DNS names or IP Addresses + if !util.Contains(cert.DNSNames, cert.Subject.CommonName) && !util.Contains(pki.IPAddressesToString(cert.IPAddresses), cert.Subject.CommonName) { + return fmt.Errorf("Expected a common name for one of our DNSNames %v or IP Addresses %v, but got a CN of %v", cert.DNSNames, pki.IPAddressesToString(cert.IPAddresses), cert.Subject.CommonName) + } + } else if expectedCN != cert.Subject.CommonName { + return fmt.Errorf("Expected a common name of %v, but got a CN of %v", expectedCN, cert.Subject.CommonName) + } + + return nil +} + +// ExpectValidDuration checks if the issued certificate matches the requested duration +func ExpectValidDuration(csr *certificatesv1.CertificateSigningRequest, _ crypto.Signer) error { + cert, err := pki.DecodeX509CertificateBytes(csr.Status.Certificate) + if err != nil { + return err + } + + var expectedDuration time.Duration + durationString, ok := csr.Annotations[experimentalapi.CertificateSigningRequestDurationAnnotationKey] + if !ok { + if csr.Spec.ExpirationSeconds != nil { + expectedDuration = time.Duration(*csr.Spec.ExpirationSeconds) * time.Second + } else { + // If duration wasn't requested, then we match against the default. + expectedDuration = cmapi.DefaultCertificateDuration + } + } else { + expectedDuration, err = time.ParseDuration(durationString) + if err != nil { + return err + } + } + + actualDuration := cert.NotAfter.Sub(cert.NotBefore) + + // Here we ensure that the requested duration is what is signed on the + // certificate. We tolerate a 30 second fuzz either way. + if actualDuration > expectedDuration+time.Second*30 || actualDuration < expectedDuration-time.Second*30 { + return fmt.Errorf("Expected certificate expiry date to be %v, but got %v", expectedDuration, actualDuration) + } + + return nil +} + +func containsExtKeyUsage(s []x509.ExtKeyUsage, e x509.ExtKeyUsage) bool { + for _, a := range s { + if a == e { + return true + } + } + return false +} + +// ExpectKeyUsageExtKeyUsageServerAuth checks if the issued certificate has the +// extended key usage of server auth +func ExpectKeyUsageExtKeyUsageServerAuth(csr *certificatesv1.CertificateSigningRequest, _ crypto.Signer) error { + cert, err := pki.DecodeX509CertificateBytes(csr.Status.Certificate) + if err != nil { + return err + } + + if !containsExtKeyUsage(cert.ExtKeyUsage, x509.ExtKeyUsageServerAuth) { + return fmt.Errorf("Expected certificate to have ExtKeyUsageServerAuth, but got %v", cert.ExtKeyUsage) + } + return nil +} + +// ExpectKeyUsageExtKeyUsageClientAuth checks if the issued certificate has the +// extended key usage of client auth +func ExpectKeyUsageExtKeyUsageClientAuth(csr *certificatesv1.CertificateSigningRequest, _ crypto.Signer) error { + cert, err := pki.DecodeX509CertificateBytes(csr.Status.Certificate) + if err != nil { + return err + } + + if !containsExtKeyUsage(cert.ExtKeyUsage, x509.ExtKeyUsageClientAuth) { + return fmt.Errorf("Expected certificate to have ExtKeyUsageClientAuth, but got %v", cert.ExtKeyUsage) + } + return nil +} + +// UsageDigitalSignature checks if a cert has the KeyUsageDigitalSignature key +// usage set +func ExpectKeyUsageUsageDigitalSignature(csr *certificatesv1.CertificateSigningRequest, _ crypto.Signer) error { + cert, err := pki.DecodeX509CertificateBytes(csr.Status.Certificate) + if err != nil { + return err + } + + // taking the key usage here and use a binary OR to flip all non + // KeyUsageDigitalSignature bits to 0 so if KeyUsageDigitalSignature the + // value will be exactly x509.KeyUsageDigitalSignature + usage := cert.KeyUsage + usage &= x509.KeyUsageDigitalSignature + if usage != x509.KeyUsageDigitalSignature { + return fmt.Errorf("Expected certificate to have KeyUsageDigitalSignature %#b, but got %v %#b", x509.KeyUsageDigitalSignature, cert.KeyUsage, cert.KeyUsage) + } + + return nil +} + +// ExpectKeyUsageUsageDataEncipherment checks if a cert has the +// KeyUsageDataEncipherment key usage set +func ExpectKeyUsageUsageDataEncipherment(csr *certificatesv1.CertificateSigningRequest, _ crypto.Signer) error { + cert, err := pki.DecodeX509CertificateBytes(csr.Status.Certificate) + if err != nil { + return err + } + + // taking the key usage here and use a binary OR to flip all non + // KeyUsageDataEncipherment bits to 0 so if KeyUsageDataEncipherment the + // value will be exactly x509.KeyUsageDataEncipherment + usage := cert.KeyUsage + usage &= x509.KeyUsageDataEncipherment + if usage != x509.KeyUsageDataEncipherment { + return fmt.Errorf("Expected certificate to have KeyUsageDataEncipherment %#b, but got %v %#b", x509.KeyUsageDataEncipherment, usage, usage) + } + + return nil +} + +// ExpectEmailsToMatch check if the issued certificate has all requested email SANs +func ExpectEmailsToMatch(csr *certificatesv1.CertificateSigningRequest, _ crypto.Signer) error { + cert, err := pki.DecodeX509CertificateBytes(csr.Status.Certificate) + if err != nil { + return err + } + req, err := pki.DecodeX509CertificateRequestBytes(csr.Spec.Request) + if err != nil { + return err + } + + if !util.EqualUnsorted(cert.EmailAddresses, req.EmailAddresses) { + return fmt.Errorf("certificate doesn't contain Email SANs: exp=%v got=%v", req.EmailAddresses, cert.EmailAddresses) + } + + return nil +} + +// ExpectIsCA checks the certificate is a CA if requested +func ExpectIsCA(csr *certificatesv1.CertificateSigningRequest, _ crypto.Signer) error { + cert, err := pki.DecodeX509CertificateBytes(csr.Status.Certificate) + if err != nil { + return err + } + + markedIsCA := false + if csr.Annotations[experimentalapi.CertificateSigningRequestIsCAAnnotationKey] == "true" { + markedIsCA = true + } + + if cert.IsCA != markedIsCA { + return fmt.Errorf("requested certificate does not match expected IsCA, exp=%t got=%t", + markedIsCA, cert.IsCA) + } + + hasCertSign := (cert.KeyUsage & x509.KeyUsageCertSign) == x509.KeyUsageCertSign + if hasCertSign != markedIsCA { + return fmt.Errorf("Expected certificate to have KeyUsageCertSign=%t, but got=%t", markedIsCA, hasCertSign) + } + + return nil +} + +// ExpectConditionApproved checks that the CertificateSigningRequest has been +// Approved +func ExpectConditionApproved(csr *certificatesv1.CertificateSigningRequest, _ crypto.Signer) error { + if !ctrlutil.CertificateSigningRequestIsApproved(csr) { + return fmt.Errorf("CertificateSigningRequest does not have an Approved condition: %v", csr.Status.Conditions) + } + + return nil +} + +// ExpectConditionNotDenied checks that the CertificateSigningRequest has not +// been Denied +func ExpectConditiotNotDenied(csr *certificatesv1.CertificateSigningRequest, _ crypto.Signer) error { + if ctrlutil.CertificateSigningRequestIsDenied(csr) { + return fmt.Errorf("CertificateSigningRequest has a Denied condition: %v", csr.Status.Conditions) + } + + return nil +} + +// ExpectConditionNotFailed checks that the CertificateSigningRequest is not +// Failed +func ExpectConditionNotFailed(csr *certificatesv1.CertificateSigningRequest, _ crypto.Signer) error { + if ctrlutil.CertificateSigningRequestIsFailed(csr) { + return fmt.Errorf("CertificateSigningRequest has a Failed condition: %v", csr.Status.Conditions) + } + + return nil +} diff --git a/conformance/framework/helper/validation/validation.go b/conformance/framework/helper/validation/validation.go new file mode 100644 index 0000000..704bbb9 --- /dev/null +++ b/conformance/framework/helper/validation/validation.go @@ -0,0 +1,107 @@ +/* +Copyright 2021 The cert-manager Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package validation + +import ( + "github.com/cert-manager/issuer-lib/conformance/framework/helper/featureset" + "github.com/cert-manager/issuer-lib/conformance/framework/helper/validation/certificates" + "github.com/cert-manager/issuer-lib/conformance/framework/helper/validation/certificatesigningrequests" +) + +func DefaultCertificateSet() []certificates.ValidationFunc { + return []certificates.ValidationFunc{ + certificates.ExpectValidKeysInSecret, + certificates.ExpectCertificateDNSNamesToMatch, + certificates.ExpectCertificateOrganizationToMatch, + certificates.ExpectCertificateURIsToMatch, + certificates.ExpectCorrectTrustChain, + certificates.ExpectCARootCertificate, + certificates.ExpectEmailsToMatch, + certificates.ExpectValidAnnotations, + certificates.ExpectValidCertificate, + certificates.ExpectValidCommonName, + certificates.ExpectValidNotAfterDate, + certificates.ExpectDurationToMatch, + certificates.ExpectValidPrivateKeyData, + certificates.ExpectConditionReadyObservedGeneration, + certificates.ExpectValidBasicConstraints, + certificates.ExpectValidAdditionalOutputFormats, + } +} + +func DefaultCertificateSigningRequestSet() []certificatesigningrequests.ValidationFunc { + return []certificatesigningrequests.ValidationFunc{ + certificatesigningrequests.ExpectValidCertificate, + certificatesigningrequests.ExpectCertificateOrganizationToMatch, + certificatesigningrequests.ExpectValidPrivateKeyData, + certificatesigningrequests.ExpectCertificateDNSNamesToMatch, + certificatesigningrequests.ExpectCertificateURIsToMatch, + certificatesigningrequests.ExpectCertificateIPsToMatch, + certificatesigningrequests.ExpectValidCommonName, + certificatesigningrequests.ExpectKeyUsageUsageDigitalSignature, + certificatesigningrequests.ExpectEmailsToMatch, + certificatesigningrequests.ExpectIsCA, + certificatesigningrequests.ExpectConditionApproved, + certificatesigningrequests.ExpectConditiotNotDenied, + certificatesigningrequests.ExpectConditionNotFailed, + } +} + +func CertificateSetForUnsupportedFeatureSet(fs featureset.FeatureSet) []certificates.ValidationFunc { + // basics + out := []certificates.ValidationFunc{ + certificates.ExpectValidKeysInSecret, + certificates.ExpectCertificateDNSNamesToMatch, + certificates.ExpectCertificateOrganizationToMatch, + certificates.ExpectValidAnnotations, + certificates.ExpectValidCertificate, + certificates.ExpectValidCommonName, + certificates.ExpectValidNotAfterDate, + certificates.ExpectDurationToMatch, + certificates.ExpectValidPrivateKeyData, + certificates.ExpectConditionReadyObservedGeneration, + certificates.ExpectValidBasicConstraints, + } + + if !fs.Contains(featureset.URISANsFeature) { + out = append(out, certificates.ExpectCertificateURIsToMatch) + } + + if !fs.Contains(featureset.EmailSANsFeature) { + out = append(out, certificates.ExpectEmailsToMatch) + } + + if !fs.Contains(featureset.SaveCAToSecret) { + out = append(out, certificates.ExpectCorrectTrustChain) + + if !fs.Contains(featureset.SaveRootCAToSecret) { + out = append(out, certificates.ExpectCARootCertificate) + } + } + + return out +} + +func CertificateSigningRequestSetForUnsupportedFeatureSet(fs featureset.FeatureSet) []certificatesigningrequests.ValidationFunc { + validations := DefaultCertificateSigningRequestSet() + + if !fs.Contains(featureset.DurationFeature) { + validations = append(validations, certificatesigningrequests.ExpectValidDuration) + } + + return validations +} diff --git a/conformance/framework/log/log.go b/conformance/framework/log/log.go new file mode 100644 index 0000000..e570b88 --- /dev/null +++ b/conformance/framework/log/log.go @@ -0,0 +1,83 @@ +/* +Copyright 2020 The cert-manager Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package log + +import ( + "fmt" + "sync" + "time" + + "github.com/onsi/ginkgo/v2" + "k8s.io/apimachinery/pkg/util/wait" +) + +var Writer = ginkgo.GinkgoWriter + +func nowStamp() string { + return time.Now().Format(time.StampMilli) +} + +func log(level string, format string, args ...interface{}) { + fmt.Fprintf(Writer, nowStamp()+": "+level+": "+format+"\n", args...) +} + +func Logf(format string, args ...interface{}) { + log("INFO", format, args...) +} + +// LogBackoff gives you a logger with an exponential backoff. If the +// returned 'logf' func is called too often, the logf calls get ignored +// until the backoff expires. +// +// The reason we use this backoff mechanism is that we have many "waiting +// loops" that poll every 0.5 seconds. We don't want to use a higher +// polling interval since it would slow the test. +// +// The first log line is immediately printed, and the last message is +// always printed even if the backoff isn't done. That's because the first +// and last messages are often helpful to understand how things went. +func LogBackoff() (logf func(format string, args ...interface{}), done func()) { + backoff := wait.Backoff{ + Duration: 5 * time.Second, + Factor: 1.2, + Steps: 10, + Cap: 1 * time.Minute, + } + + start := time.Now() + var msg string + done = func() { + Logf(msg + fmt.Sprintf(" (took %v)", time.Since(start).Truncate(time.Second))) + } + + once := sync.Once{} + step := time.Now() + return func(format string, args ...interface{}) { + msg = fmt.Sprintf(format, args...) + once.Do(func() { + Logf(msg) + }) + + if time.Since(step) < backoff.Duration { + return + } + + step = time.Now() + _ = backoff.Step() + Logf(msg) + }, done +} diff --git a/conformance/framework/testenv.go b/conformance/framework/testenv.go new file mode 100644 index 0000000..9ad1fee --- /dev/null +++ b/conformance/framework/testenv.go @@ -0,0 +1,89 @@ +/* +Copyright 2020 The cert-manager Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package framework + +import ( + "context" + "fmt" + "time" + + v1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/wait" +) + +// Defines methods that help provision test environments + +const ( + // Poll defines how often to poll for conditions. + Poll = 2 * time.Second +) + +// CreateKubeNamespace creates a new Kubernetes Namespace for a test. +func (f *Framework) CreateKubeNamespace(baseName string) (*v1.Namespace, error) { + ns := &v1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: fmt.Sprintf("e2e-tests-%v-", baseName), + }, + } + + return f.KubeClientSet.CoreV1().Namespaces().Create(context.TODO(), ns, metav1.CreateOptions{}) +} + +// CreateKubeResourceQuota provisions a ResourceQuota resource in the target +// namespace. +func (f *Framework) CreateKubeResourceQuota() (*v1.ResourceQuota, error) { + quota := &v1.ResourceQuota{ + ObjectMeta: metav1.ObjectMeta{ + Name: "default-e2e-quota", + Namespace: f.Namespace.Name, + }, + Spec: v1.ResourceQuotaSpec{ + Hard: v1.ResourceList{ + "cpu": resource.MustParse("16"), + "limits.cpu": resource.MustParse("16"), + "requests.cpu": resource.MustParse("16"), + "memory": resource.MustParse("32G"), + "limits.memory": resource.MustParse("32G"), + "requests.memory": resource.MustParse("32G"), + }, + }, + } + return f.KubeClientSet.CoreV1().ResourceQuotas(f.Namespace.Name).Create(context.TODO(), quota, metav1.CreateOptions{}) +} + +// DeleteKubeNamespace will delete a namespace resource +func (f *Framework) DeleteKubeNamespace(namespace string) error { + return f.KubeClientSet.CoreV1().Namespaces().Delete(context.TODO(), namespace, metav1.DeleteOptions{}) +} + +// WaitForKubeNamespaceNotExist will wait for the namespace with the given name +// to not exist for up to 2 minutes. +func (f *Framework) WaitForKubeNamespaceNotExist(namespace string) error { + return wait.PollUntilContextTimeout(context.TODO(), Poll, time.Minute*2, true, func(ctx context.Context) (bool, error) { + _, err := f.KubeClientSet.CoreV1().Namespaces().Get(ctx, namespace, metav1.GetOptions{}) + if apierrors.IsNotFound(err) { + return true, nil + } + if err != nil { + return false, err + } + return false, nil + }) +} diff --git a/conformance/framework/util.go b/conformance/framework/util.go new file mode 100644 index 0000000..7788ca9 --- /dev/null +++ b/conformance/framework/util.go @@ -0,0 +1,50 @@ +/* +Copyright 2020 The cert-manager Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package framework + +import ( + "fmt" + "time" + + . "github.com/onsi/ginkgo/v2" + + "k8s.io/component-base/featuregate" + + . "github.com/cert-manager/issuer-lib/conformance/framework/log" +) + +func nowStamp() string { + return time.Now().Format(time.StampMilli) +} + +func Failf(format string, args ...interface{}) { + msg := fmt.Sprintf(format, args...) + Logf(msg) + Fail(nowStamp()+": "+msg, 1) +} + +func Skipf(format string, args ...interface{}) { + msg := fmt.Sprintf(format, args...) + Logf("INFO", msg) + Skip(nowStamp() + ": " + msg) +} + +func RequireFeatureGate(f *Framework, featureSet featuregate.FeatureGate, gate featuregate.Feature) { + if !featureSet.Enabled(gate) { + Skipf("feature gate %q is not enabled, skipping test", gate) + } +} diff --git a/conformance/rbac/certificate.go b/conformance/rbac/certificate.go new file mode 100644 index 0000000..ae148d5 --- /dev/null +++ b/conformance/rbac/certificate.go @@ -0,0 +1,207 @@ +/* +Copyright 2020 The cert-manager Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package rbac + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/cert-manager/issuer-lib/conformance/framework" +) + +func (s *Suite) defineCertificates() { + RBACDescribe("Certificates", func() { + f := framework.NewFramework("rbac-certificates", s.KubeClientConfig) + resource := "certificates" // this file is related to certificates + + Context("with namespace view access", func() { + clusterRole := "view" + It("shouldn't be able to create certificates", func() { + verb := "create" + + hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + Expect(hasAccess).Should(BeFalse()) + }) + + It("shouldn't be able to delete certificates", func() { + verb := "delete" + + hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + Expect(hasAccess).Should(BeFalse()) + }) + + It("shouldn't be able to delete collections of certificates", func() { + verb := "deletecollection" + + hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + Expect(hasAccess).Should(BeFalse()) + }) + + It("shouldn't be able to patch certificates", func() { + verb := "patch" + + hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + Expect(hasAccess).Should(BeFalse()) + }) + + It("shouldn't be able to update certificates", func() { + verb := "update" + + hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + Expect(hasAccess).Should(BeFalse()) + }) + + It("should be able to get certificates", func() { + verb := "get" + + hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + Expect(hasAccess).Should(BeTrue()) + }) + + It("should be able to list certificates", func() { + verb := "list" + + hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + Expect(hasAccess).Should(BeTrue()) + }) + + It("should be able to watch certificates", func() { + verb := "watch" + + hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + Expect(hasAccess).Should(BeTrue()) + }) + }) + Context("with namespace edit access", func() { + clusterRole := "edit" + It("should be able to create certificates", func() { + verb := "create" + + hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + Expect(hasAccess).Should(BeTrue()) + }) + + It("should be able to delete certificates", func() { + verb := "delete" + + hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + Expect(hasAccess).Should(BeTrue()) + }) + + It("should be able to delete collections of certificates", func() { + verb := "deletecollection" + + hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + Expect(hasAccess).Should(BeTrue()) + }) + + It("should be able to patch certificates", func() { + verb := "patch" + + hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + Expect(hasAccess).Should(BeTrue()) + }) + + It("should be able to update certificates", func() { + verb := "update" + + hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + Expect(hasAccess).Should(BeTrue()) + }) + + It("should be able to get certificates", func() { + verb := "get" + + hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + Expect(hasAccess).Should(BeTrue()) + }) + + It("should be able to list certificates", func() { + verb := "list" + + hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + Expect(hasAccess).Should(BeTrue()) + }) + + It("should be able to watch certificates", func() { + verb := "watch" + + hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + Expect(hasAccess).Should(BeTrue()) + }) + }) + + Context("with namespace admin access", func() { + clusterRole := "admin" + It("should be able to create certificates", func() { + verb := "create" + + hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + Expect(hasAccess).Should(BeTrue()) + }) + + It("should be able to delete certificates", func() { + verb := "delete" + + hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + Expect(hasAccess).Should(BeTrue()) + }) + + It("should be able to delete collections of certificates", func() { + verb := "deletecollection" + + hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + Expect(hasAccess).Should(BeTrue()) + }) + + It("should be able to patch certificates", func() { + verb := "patch" + + hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + Expect(hasAccess).Should(BeTrue()) + }) + + It("should be able to update certificates", func() { + verb := "update" + + hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + Expect(hasAccess).Should(BeTrue()) + }) + + It("should be able to get certificates", func() { + verb := "get" + + hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + Expect(hasAccess).Should(BeTrue()) + }) + + It("should be able to list certificates", func() { + verb := "list" + + hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + Expect(hasAccess).Should(BeTrue()) + }) + + It("should be able to watch certificates", func() { + verb := "watch" + + hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + Expect(hasAccess).Should(BeTrue()) + }) + }) + }) +} diff --git a/conformance/rbac/certificaterequest.go b/conformance/rbac/certificaterequest.go new file mode 100644 index 0000000..d2a7fe3 --- /dev/null +++ b/conformance/rbac/certificaterequest.go @@ -0,0 +1,207 @@ +/* +Copyright 2020 The cert-manager Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package rbac + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/cert-manager/issuer-lib/conformance/framework" +) + +func (s *Suite) defineCertificateRequests() { + RBACDescribe("CertificateRequests", func() { + f := framework.NewFramework("rbac-certificaterequests", s.KubeClientConfig) + resource := "certificaterequests" // this file is related to certificaterequests + + Context("with namespace view access", func() { + clusterRole := "view" + It("shouldn't be able to create certificaterequests", func() { + verb := "create" + + hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + Expect(hasAccess).Should(BeFalse()) + }) + + It("shouldn't be able to delete certificaterequests", func() { + verb := "delete" + + hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + Expect(hasAccess).Should(BeFalse()) + }) + + It("shouldn't be able to delete collections of certificaterequests", func() { + verb := "deletecollection" + + hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + Expect(hasAccess).Should(BeFalse()) + }) + + It("shouldn't be able to patch certificaterequests", func() { + verb := "patch" + + hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + Expect(hasAccess).Should(BeFalse()) + }) + + It("shouldn't be able to update certificaterequests", func() { + verb := "update" + + hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + Expect(hasAccess).Should(BeFalse()) + }) + + It("should be able to get certificaterequests", func() { + verb := "get" + + hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + Expect(hasAccess).Should(BeTrue()) + }) + + It("should be able to list certificaterequests", func() { + verb := "list" + + hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + Expect(hasAccess).Should(BeTrue()) + }) + + It("should be able to watch certificaterequests", func() { + verb := "watch" + + hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + Expect(hasAccess).Should(BeTrue()) + }) + }) + Context("with namespace edit access", func() { + clusterRole := "edit" + It("should be able to create certificaterequests", func() { + verb := "create" + + hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + Expect(hasAccess).Should(BeTrue()) + }) + + It("should be able to delete certificaterequests", func() { + verb := "delete" + + hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + Expect(hasAccess).Should(BeTrue()) + }) + + It("should be able to delete collections of certificaterequests", func() { + verb := "deletecollection" + + hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + Expect(hasAccess).Should(BeTrue()) + }) + + It("should be able to patch certificaterequests", func() { + verb := "patch" + + hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + Expect(hasAccess).Should(BeTrue()) + }) + + It("should be able to update certificaterequests", func() { + verb := "update" + + hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + Expect(hasAccess).Should(BeTrue()) + }) + + It("should be able to get certificaterequests", func() { + verb := "get" + + hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + Expect(hasAccess).Should(BeTrue()) + }) + + It("should be able to list certificaterequests", func() { + verb := "list" + + hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + Expect(hasAccess).Should(BeTrue()) + }) + + It("should be able to watch certificaterequests", func() { + verb := "watch" + + hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + Expect(hasAccess).Should(BeTrue()) + }) + }) + + Context("with namespace admin access", func() { + clusterRole := "admin" + It("should be able to create certificaterequests", func() { + verb := "create" + + hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + Expect(hasAccess).Should(BeTrue()) + }) + + It("should be able to delete certificaterequests", func() { + verb := "delete" + + hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + Expect(hasAccess).Should(BeTrue()) + }) + + It("should be able to delete collections of certificaterequests", func() { + verb := "deletecollection" + + hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + Expect(hasAccess).Should(BeTrue()) + }) + + It("should be able to patch certificaterequests", func() { + verb := "patch" + + hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + Expect(hasAccess).Should(BeTrue()) + }) + + It("should be able to update certificaterequests", func() { + verb := "update" + + hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + Expect(hasAccess).Should(BeTrue()) + }) + + It("should be able to get certificaterequests", func() { + verb := "get" + + hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + Expect(hasAccess).Should(BeTrue()) + }) + + It("should be able to list certificaterequests", func() { + verb := "list" + + hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + Expect(hasAccess).Should(BeTrue()) + }) + + It("should be able to watch certificaterequests", func() { + verb := "watch" + + hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + Expect(hasAccess).Should(BeTrue()) + }) + }) + }) +} diff --git a/conformance/rbac/doc.go b/conformance/rbac/doc.go new file mode 100644 index 0000000..dfa19e2 --- /dev/null +++ b/conformance/rbac/doc.go @@ -0,0 +1,96 @@ +/* +Copyright 2020 The cert-manager Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package rbac + +import ( + "context" + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/cert-manager/issuer-lib/conformance/framework" + authorizationv1 "k8s.io/api/authorization/v1" + v1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" +) + +// RBACDescribe wraps ConformanceDescribe with namespacing for RBAC tests +func RBACDescribe(text string, body func()) bool { + return framework.ConformanceDescribe("[RBAC] "+text, body) +} + +func RbacClusterRoleHasAccessToResource(f *framework.Framework, clusterRole string, verb string, resource string) bool { + By("Creating a service account") + viewServiceAccount := &v1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "rbac-test-", + }, + } + serviceAccountClient := f.KubeClientSet.CoreV1().ServiceAccounts(f.Namespace.Name) + serviceAccount, err := serviceAccountClient.Create(context.TODO(), viewServiceAccount, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + viewServiceAccountName := serviceAccount.Name + + By("Creating ClusterRoleBinding to view " + clusterRole + " clusterRole") + viewRoleBinding := &rbacv1.ClusterRoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: viewServiceAccountName + "-rb-", + }, + Subjects: []rbacv1.Subject{ + {Kind: "ServiceAccount", Name: viewServiceAccountName, Namespace: f.Namespace.Name}, + }, + RoleRef: rbacv1.RoleRef{ + APIGroup: "rbac.authorization.k8s.io", + Kind: "ClusterRole", + Name: clusterRole, + }, + } + roleBindingClient := f.KubeClientSet.RbacV1().ClusterRoleBindings() + _, err = roleBindingClient.Create(context.TODO(), viewRoleBinding, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + By("Sleeping for a second.") + // to allow RBAC to propagate + time.Sleep(time.Second) + + By("Impersonating the Service Account") + var impersonateConfig *rest.Config + impersonateConfig = f.KubeClientConfig + impersonateConfig.Impersonate.UserName = "system:serviceaccount:" + f.Namespace.Name + ":" + viewServiceAccountName + impersonateClient, err := kubernetes.NewForConfig(impersonateConfig) + Expect(err).NotTo(HaveOccurred()) + + By("Submitting a self subject access review") + sarClient := impersonateClient.AuthorizationV1().SelfSubjectAccessReviews() + sar := &authorizationv1.SelfSubjectAccessReview{ + Spec: authorizationv1.SelfSubjectAccessReviewSpec{ + ResourceAttributes: &authorizationv1.ResourceAttributes{ + Namespace: f.Namespace.Name, + Verb: verb, + Group: "cert-manager.io", + Resource: resource, + }, + }, + } + response, err := sarClient.Create(context.TODO(), sar, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + return response.Status.Allowed +} diff --git a/conformance/rbac/issuer.go b/conformance/rbac/issuer.go new file mode 100644 index 0000000..efe66d8 --- /dev/null +++ b/conformance/rbac/issuer.go @@ -0,0 +1,207 @@ +/* +Copyright 2020 The cert-manager Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package rbac + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/cert-manager/issuer-lib/conformance/framework" +) + +func (s *Suite) defineIssuers() { + RBACDescribe("Issuers", func() { + f := framework.NewFramework("rbac-issuers", s.KubeClientConfig) + resource := "issuers" // this file is related to issuers + + Context("with namespace view access", func() { + clusterRole := "view" + It("shouldn't be able to create issuers", func() { + verb := "create" + + hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + Expect(hasAccess).Should(BeFalse()) + }) + + It("shouldn't be able to delete issuers", func() { + verb := "delete" + + hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + Expect(hasAccess).Should(BeFalse()) + }) + + It("shouldn't be able to delete collections of issuers", func() { + verb := "deletecollection" + + hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + Expect(hasAccess).Should(BeFalse()) + }) + + It("shouldn't be able to patch issuers", func() { + verb := "patch" + + hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + Expect(hasAccess).Should(BeFalse()) + }) + + It("shouldn't be able to update issuers", func() { + verb := "update" + + hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + Expect(hasAccess).Should(BeFalse()) + }) + + It("should be able to get issuers", func() { + verb := "get" + + hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + Expect(hasAccess).Should(BeTrue()) + }) + + It("should be able to list issuers", func() { + verb := "list" + + hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + Expect(hasAccess).Should(BeTrue()) + }) + + It("should be able to watch issuers", func() { + verb := "watch" + + hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + Expect(hasAccess).Should(BeTrue()) + }) + }) + Context("with namespace edit access", func() { + clusterRole := "edit" + It("should be able to create issuers", func() { + verb := "create" + + hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + Expect(hasAccess).Should(BeTrue()) + }) + + It("should be able to delete issuers", func() { + verb := "delete" + + hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + Expect(hasAccess).Should(BeTrue()) + }) + + It("should be able to delete collections of issuers", func() { + verb := "deletecollection" + + hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + Expect(hasAccess).Should(BeTrue()) + }) + + It("should be able to patch issuers", func() { + verb := "patch" + + hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + Expect(hasAccess).Should(BeTrue()) + }) + + It("should be able to update issuers", func() { + verb := "update" + + hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + Expect(hasAccess).Should(BeTrue()) + }) + + It("should be able to get issuers", func() { + verb := "get" + + hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + Expect(hasAccess).Should(BeTrue()) + }) + + It("should be able to list issuers", func() { + verb := "list" + + hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + Expect(hasAccess).Should(BeTrue()) + }) + + It("should be able to watch issuers", func() { + verb := "watch" + + hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + Expect(hasAccess).Should(BeTrue()) + }) + }) + + Context("with namespace admin access", func() { + clusterRole := "admin" + It("should be able to create issuers", func() { + verb := "create" + + hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + Expect(hasAccess).Should(BeTrue()) + }) + + It("should be able to delete issuers", func() { + verb := "delete" + + hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + Expect(hasAccess).Should(BeTrue()) + }) + + It("should be able to delete collections of issuers", func() { + verb := "deletecollection" + + hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + Expect(hasAccess).Should(BeTrue()) + }) + + It("should be able to patch issuers", func() { + verb := "patch" + + hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + Expect(hasAccess).Should(BeTrue()) + }) + + It("should be able to update issuers", func() { + verb := "update" + + hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + Expect(hasAccess).Should(BeTrue()) + }) + + It("should be able to get issuers", func() { + verb := "get" + + hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + Expect(hasAccess).Should(BeTrue()) + }) + + It("should be able to list issuers", func() { + verb := "list" + + hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + Expect(hasAccess).Should(BeTrue()) + }) + + It("should be able to watch issuers", func() { + verb := "watch" + + hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + Expect(hasAccess).Should(BeTrue()) + }) + }) + }) +} diff --git a/conformance/rbac/suite.go b/conformance/rbac/suite.go new file mode 100644 index 0000000..d07e3cf --- /dev/null +++ b/conformance/rbac/suite.go @@ -0,0 +1,19 @@ +package rbac + +import ( + "k8s.io/client-go/rest" +) + +// Suite defines a reusable conformance test suite that can be used against any +// Issuer implementation. +type Suite struct { + // KubeClientConfig is the configuration used to connect to the Kubernetes + // API server. + KubeClientConfig *rest.Config +} + +func (s *Suite) Define() { + s.defineCertificates() + s.defineCertificateRequests() + s.defineIssuers() +} diff --git a/conformance/util/domains.go b/conformance/util/domains.go new file mode 100644 index 0000000..1407a90 --- /dev/null +++ b/conformance/util/domains.go @@ -0,0 +1,46 @@ +/* +Copyright 2021 The cert-manager Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package util + +import ( + "fmt" + "math/rand" +) + +var letterRunes = []rune("abcdefghijklmnopqrstuvwxyz") + +// RandStringRunes returns a random string of length n. +func RandStringRunes(n int) string { + b := make([]rune, n) + for i := range b { + b[i] = letterRunes[rand.Intn(len(letterRunes))] + } + return string(b) +} + +// RandomSubdomain returns a new subdomain domain of the domain suffix. +// e.g. abcd.example.com. +func RandomSubdomain(domain string) string { + return RandomSubdomainLength(domain, 5) +} + +// RandomSubdomainLength returns a new subdomain domain of the domain suffix, where the +// subdomain has `length` number of characters. +// e.g. abcdefghij.example.com. +func RandomSubdomainLength(domain string, length int) string { + return fmt.Sprintf("%s.%s", RandStringRunes(length), domain) +} diff --git a/conformance/util/util.go b/conformance/util/util.go new file mode 100644 index 0000000..57f066d --- /dev/null +++ b/conformance/util/util.go @@ -0,0 +1,427 @@ +/* +Copyright 2020 The cert-manager Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package util + +// TODO: we should break this file apart into separate more sane/reusable parts + +import ( + "context" + "crypto" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "fmt" + "net" + "net/url" + "time" + + corev1 "k8s.io/api/core/v1" + networkingv1 "k8s.io/api/networking/v1" + networkingv1beta1 "k8s.io/api/networking/v1beta1" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/intstr" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/client-go/discovery" + gwapiv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" + + apiutil "github.com/cert-manager/cert-manager/pkg/api/util" + v1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" + clientset "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned/typed/certmanager/v1" + "github.com/cert-manager/cert-manager/pkg/util" + "github.com/cert-manager/cert-manager/pkg/util/pki" + "github.com/cert-manager/issuer-lib/conformance/framework/log" +) + +func CertificateOnlyValidForDomains(cert *x509.Certificate, commonName string, dnsNames ...string) bool { + if commonName != cert.Subject.CommonName || !util.EqualUnsorted(cert.DNSNames, dnsNames) { + return false + } + return true +} + +func WaitForIssuerStatusFunc(client clientset.IssuerInterface, name string, fn func(*v1.Issuer) (bool, error)) error { + return wait.PollUntilContextTimeout(context.TODO(), 500*time.Millisecond, time.Minute, true, func(ctx context.Context) (bool, error) { + issuer, err := client.Get(ctx, name, metav1.GetOptions{}) + if err != nil { + return false, fmt.Errorf("error getting Issuer %q: %v", name, err) + } + return fn(issuer) + }) +} + +// WaitForIssuerCondition waits for the status of the named issuer to contain +// a condition whose type and status matches the supplied one. +func WaitForIssuerCondition(client clientset.IssuerInterface, name string, condition v1.IssuerCondition) error { + logf, done := log.LogBackoff() + defer done() + pollErr := wait.PollUntilContextTimeout(context.TODO(), 500*time.Millisecond, time.Minute, true, func(ctx context.Context) (bool, error) { + logf("Waiting for issuer %v condition %#v", name, condition) + issuer, err := client.Get(ctx, name, metav1.GetOptions{}) + if nil != err { + return false, fmt.Errorf("error getting Issuer %q: %v", name, err) + } + + return apiutil.IssuerHasCondition(issuer, condition), nil + }) + return wrapErrorWithIssuerStatusCondition(client, pollErr, name, condition.Type) +} + +// try to retrieve last condition to help diagnose tests. +func wrapErrorWithIssuerStatusCondition(client clientset.IssuerInterface, pollErr error, name string, conditionType v1.IssuerConditionType) error { + if pollErr == nil { + return nil + } + + issuer, err := client.Get(context.TODO(), name, metav1.GetOptions{}) + if err != nil { + return pollErr + } + + for _, cond := range issuer.GetStatus().Conditions { + if cond.Type == conditionType { + return fmt.Errorf("%s: Last Status: '%s' Reason: '%s', Message: '%s'", pollErr.Error(), cond.Status, cond.Reason, cond.Message) + } + + } + + return pollErr +} + +// WaitForClusterIssuerCondition waits for the status of the named issuer to contain +// a condition whose type and status matches the supplied one. +func WaitForClusterIssuerCondition(client clientset.ClusterIssuerInterface, name string, condition v1.IssuerCondition) error { + logf, done := log.LogBackoff() + defer done() + pollErr := wait.PollUntilContextTimeout(context.TODO(), 500*time.Millisecond, time.Minute, true, func(ctx context.Context) (bool, error) { + logf("Waiting for clusterissuer %v condition %#v", name, condition) + issuer, err := client.Get(ctx, name, metav1.GetOptions{}) + if nil != err { + return false, fmt.Errorf("error getting ClusterIssuer %v: %v", name, err) + } + + return apiutil.IssuerHasCondition(issuer, condition), nil + }) + return wrapErrorWithClusterIssuerStatusCondition(client, pollErr, name, condition.Type) +} + +// try to retrieve last condition to help diagnose tests. +func wrapErrorWithClusterIssuerStatusCondition(client clientset.ClusterIssuerInterface, pollErr error, name string, conditionType v1.IssuerConditionType) error { + if pollErr == nil { + return nil + } + + issuer, err := client.Get(context.TODO(), name, metav1.GetOptions{}) + if err != nil { + return pollErr + } + + for _, cond := range issuer.GetStatus().Conditions { + if cond.Type == conditionType { + return fmt.Errorf("%s: Last Status: '%s' Reason: '%s', Message: '%s'", pollErr.Error(), cond.Status, cond.Reason, cond.Message) + } + + } + + return pollErr +} + +// WaitForCRDToNotExist waits for the CRD with the given name to no +// longer exist. +func WaitForCRDToNotExist(client apiextensionsv1.CustomResourceDefinitionInterface, name string) error { + logf, done := log.LogBackoff() + defer done() + return wait.PollUntilContextTimeout(context.TODO(), 500*time.Millisecond, time.Minute, true, func(ctx context.Context) (bool, error) { + logf("Waiting for CRD %v to not exist", name) + _, err := client.Get(ctx, name, metav1.GetOptions{}) + if nil == err { + return false, nil + } + + if errors.IsNotFound(err) { + return true, nil + } + + return false, nil + }) +} + +// Deprecated: use test/unit/gen/Certificate in future +func NewCertManagerBasicCertificate(name, secretName, issuerName string, issuerKind string, duration, renewBefore *metav1.Duration, dnsNames ...string) *v1.Certificate { + cn := "test.domain.com" + if len(dnsNames) > 0 { + cn = dnsNames[0] + } + return &v1.Certificate{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Spec: v1.CertificateSpec{ + CommonName: cn, + DNSNames: dnsNames, + Subject: &v1.X509Subject{ + Organizations: []string{"test-org"}, + }, + SecretName: secretName, + Duration: duration, + RenewBefore: renewBefore, + PrivateKey: &v1.CertificatePrivateKey{}, + IssuerRef: cmmeta.ObjectReference{ + Name: issuerName, + Kind: issuerKind, + }, + }, + } +} + +// Deprecated: use test/unit/gen/CertificateRequest in future +func NewCertManagerBasicCertificateRequest(name, issuerName string, issuerKind string, duration *metav1.Duration, + dnsNames []string, ips []net.IP, uris []string, keyAlgorithm x509.PublicKeyAlgorithm) (*v1.CertificateRequest, crypto.Signer, error) { + cn := "test.domain.com" + if len(dnsNames) > 0 { + cn = dnsNames[0] + } + + var parsedURIs []*url.URL + for _, uri := range uris { + parsed, err := url.Parse(uri) + if err != nil { + return nil, nil, err + } + parsedURIs = append(parsedURIs, parsed) + } + + var sk crypto.Signer + var signatureAlgorithm x509.SignatureAlgorithm + var err error + + switch keyAlgorithm { + case x509.RSA: + sk, err = pki.GenerateRSAPrivateKey(2048) + if err != nil { + return nil, nil, err + } + signatureAlgorithm = x509.SHA256WithRSA + case x509.ECDSA: + sk, err = pki.GenerateECPrivateKey(pki.ECCurve256) + if err != nil { + return nil, nil, err + } + signatureAlgorithm = x509.ECDSAWithSHA256 + case x509.Ed25519: + sk, err = pki.GenerateEd25519PrivateKey() + if err != nil { + return nil, nil, err + } + signatureAlgorithm = x509.PureEd25519 + default: + return nil, nil, fmt.Errorf("unrecognised key algorithm: %s", err) + } + + csr := &x509.CertificateRequest{ + Version: 0, + SignatureAlgorithm: signatureAlgorithm, + PublicKeyAlgorithm: keyAlgorithm, + PublicKey: sk.Public(), + Subject: pkix.Name{ + CommonName: cn, + }, + DNSNames: dnsNames, + IPAddresses: ips, + URIs: parsedURIs, + } + + csrBytes, err := pki.EncodeCSR(csr, sk) + if err != nil { + return nil, nil, err + } + + csrPEM := pem.EncodeToMemory(&pem.Block{ + Type: "CERTIFICATE REQUEST", Bytes: csrBytes, + }) + + return &v1.CertificateRequest{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Spec: v1.CertificateRequestSpec{ + Duration: duration, + Request: csrPEM, + IssuerRef: cmmeta.ObjectReference{ + Name: issuerName, + Kind: issuerKind, + }, + }, + }, sk, nil +} + +func NewCertManagerVaultCertificate(name, secretName, issuerName string, issuerKind string, duration, renewBefore *metav1.Duration) *v1.Certificate { + return &v1.Certificate{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Spec: v1.CertificateSpec{ + CommonName: "test.domain.com", + SecretName: secretName, + Duration: duration, + RenewBefore: renewBefore, + IssuerRef: cmmeta.ObjectReference{ + Name: issuerName, + Kind: issuerKind, + }, + }, + } +} + +func NewIngress(name, secretName string, annotations map[string]string, dnsNames ...string) *networkingv1.Ingress { + return &networkingv1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Annotations: annotations, + }, + Spec: networkingv1.IngressSpec{ + TLS: []networkingv1.IngressTLS{ + { + Hosts: dnsNames, + SecretName: secretName, + }, + }, + Rules: []networkingv1.IngressRule{ + { + Host: dnsNames[0], + IngressRuleValue: networkingv1.IngressRuleValue{ + HTTP: &networkingv1.HTTPIngressRuleValue{ + Paths: []networkingv1.HTTPIngressPath{ + { + Path: "/", + PathType: pathTypePrefix(), + Backend: networkingv1.IngressBackend{ + Service: &networkingv1.IngressServiceBackend{ + Name: "somesvc", + Port: networkingv1.ServiceBackendPort{ + Number: 80, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } +} + +func NewV1Beta1Ingress(name, secretName string, annotations map[string]string, dnsNames ...string) *networkingv1beta1.Ingress { + return &networkingv1beta1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Annotations: annotations, + }, + Spec: networkingv1beta1.IngressSpec{ + TLS: []networkingv1beta1.IngressTLS{ + { + Hosts: dnsNames, + SecretName: secretName, + }, + }, + Rules: []networkingv1beta1.IngressRule{ + { + Host: dnsNames[0], + IngressRuleValue: networkingv1beta1.IngressRuleValue{ + HTTP: &networkingv1beta1.HTTPIngressRuleValue{ + Paths: []networkingv1beta1.HTTPIngressPath{ + { + Path: "/", + Backend: networkingv1beta1.IngressBackend{ + ServiceName: "somesvc", + ServicePort: intstr.FromInt(80), + }, + }, + }, + }, + }, + }, + }, + }, + } +} + +func pathTypePrefix() *networkingv1.PathType { + p := networkingv1.PathTypePrefix + return &p +} + +// NewGateway creates a new test Gateway. There is no Gateway controller +// watching the 'foo' gateway class, so this Gateway will not be used to +// actually route traffic, but can be used to test cert-manager controllers that +// sync Gateways, such as gateway-shim. +func NewGateway(gatewayName, ns, secretName string, annotations map[string]string, dnsNames ...string) *gwapiv1beta1.Gateway { + + return &gwapiv1beta1.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: gatewayName, + Annotations: annotations, + }, + Spec: gwapiv1beta1.GatewaySpec{ + GatewayClassName: "foo", + Listeners: []gwapiv1beta1.Listener{{ + AllowedRoutes: &gwapiv1beta1.AllowedRoutes{ + Namespaces: &gwapiv1beta1.RouteNamespaces{ + From: func() *gwapiv1beta1.FromNamespaces { f := gwapiv1beta1.NamespacesFromSame; return &f }(), + Selector: &metav1.LabelSelector{MatchLabels: map[string]string{ + "gw": gatewayName, + }}, + }, + Kinds: nil, + }, + Name: "acme-solver", + Protocol: gwapiv1beta1.TLSProtocolType, + Port: gwapiv1beta1.PortNumber(443), + Hostname: (*gwapiv1beta1.Hostname)(&dnsNames[0]), + TLS: &gwapiv1beta1.GatewayTLSConfig{ + CertificateRefs: []gwapiv1beta1.SecretObjectReference{ + { + Kind: func() *gwapiv1beta1.Kind { k := gwapiv1beta1.Kind("Secret"); return &k }(), + Name: gwapiv1beta1.ObjectName(secretName), + Group: func() *gwapiv1beta1.Group { g := gwapiv1beta1.Group(corev1.GroupName); return &g }(), + Namespace: (*gwapiv1beta1.Namespace)(&ns), + }, + }, + }, + }}, + }, + } +} + +// HasIngresses lets you know if an API exists in the discovery API +// calling this function always performs a request to the API server. +func HasIngresses(d discovery.DiscoveryInterface, GroupVersion string) bool { + resourceList, err := d.ServerResourcesForGroupVersion(GroupVersion) + if err != nil { + return false + } + for _, r := range resourceList.APIResources { + if r.Kind == "Ingress" { + return true + } + } + return false +} diff --git a/go.mod b/go.mod index eb429fc..d33f1d9 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,9 @@ go 1.19 require ( github.com/cert-manager/cert-manager v1.12.2 github.com/go-logr/logr v1.2.4 + github.com/kr/pretty v0.3.1 + github.com/onsi/ginkgo/v2 v2.9.5 + github.com/onsi/gomega v1.27.7 github.com/stretchr/testify v1.8.4 go.uber.org/zap v1.24.0 golang.org/x/sync v0.3.0 @@ -13,8 +16,10 @@ require ( k8s.io/apimachinery v0.27.3 k8s.io/client-go v0.27.3 k8s.io/klog/v2 v2.100.1 + k8s.io/kube-aggregator v0.27.1 k8s.io/utils v0.0.0-20230505201702-9f6742963106 sigs.k8s.io/controller-runtime v0.15.0 + sigs.k8s.io/gateway-api v0.6.2 ) require ( @@ -33,17 +38,20 @@ require ( github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-openapi/jsonreference v0.20.1 // indirect github.com/go-openapi/swag v0.22.3 // indirect + github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/google/gnostic v0.6.9 // indirect github.com/google/go-cmp v0.5.9 // indirect github.com/google/gofuzz v1.2.0 // indirect + github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect github.com/google/uuid v1.3.0 // indirect github.com/imdario/mergo v0.3.13 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect + github.com/kr/text v0.2.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect @@ -55,6 +63,7 @@ require ( github.com/prometheus/client_model v0.4.0 // indirect github.com/prometheus/common v0.42.0 // indirect github.com/prometheus/procfs v0.9.0 // indirect + github.com/rogpeppe/go-internal v1.10.0 // indirect github.com/spf13/cobra v1.7.0 // indirect github.com/spf13/pflag v1.0.5 // indirect go.uber.org/atomic v1.10.0 // indirect @@ -66,6 +75,7 @@ require ( golang.org/x/term v0.8.0 // indirect golang.org/x/text v0.9.0 // indirect golang.org/x/time v0.3.0 // indirect + golang.org/x/tools v0.9.1 // indirect gomodules.xyz/jsonpatch/v2 v2.3.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/protobuf v1.30.0 // indirect diff --git a/go.sum b/go.sum index 5b402a4..3087632 100644 --- a/go.sum +++ b/go.sum @@ -18,6 +18,9 @@ github.com/cert-manager/cert-manager v1.12.2/go.mod h1:ql0msU88JCcQSceN+PFjEY8U+ github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= @@ -60,6 +63,7 @@ github.com/go-openapi/jsonreference v0.20.1/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -96,10 +100,12 @@ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/ github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= @@ -115,6 +121,7 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -131,7 +138,10 @@ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjY github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q= +github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k= github.com/onsi/gomega v1.27.7 h1:fVih9JD6ogIiHUN6ePK7HJidyEDpWGVB5mzM7cWNXoU= +github.com/onsi/gomega v1.27.7/go.mod h1:1p8OOlwo2iUUDsHnOrjE5UKYJ+e3W8eQ3qSlRahPmr4= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -147,7 +157,9 @@ github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= @@ -160,6 +172,7 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= @@ -198,6 +211,7 @@ golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -230,6 +244,7 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -262,6 +277,7 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo= +golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/internal/testsetups/simple/e2e/conformance/conformance.go b/internal/testsetups/simple/e2e/conformance/conformance.go new file mode 100644 index 0000000..69a4806 --- /dev/null +++ b/internal/testsetups/simple/e2e/conformance/conformance.go @@ -0,0 +1,120 @@ +package conformance + +import ( + "context" + "fmt" + "testing" + + cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" + "github.com/cert-manager/issuer-lib/conformance/certificates" + "github.com/cert-manager/issuer-lib/conformance/certificatesigningrequests" + "github.com/cert-manager/issuer-lib/conformance/framework" + "github.com/cert-manager/issuer-lib/conformance/framework/helper/featureset" + "github.com/cert-manager/issuer-lib/internal/tests/testresource" +) + +type mockTest struct { + testing.TB +} + +func (m *mockTest) Helper() {} + +var _ = framework.ConformanceDescribe("Certificates", func() { + t := &mockTest{} + ctx := testresource.EnsureTestDependencies(t, context.TODO(), testresource.EndToEndTest) + kubeClients := testresource.KubeClients(t, ctx) + + unsupportedFeatures := featureset.NewFeatureSet( + featureset.DurationFeature, + featureset.KeyUsagesFeature, + featureset.SaveCAToSecret, + featureset.Ed25519FeatureSet, + featureset.IssueCAFeature, + featureset.LiteralSubjectFeature, + ) + + issuerBuilder := newIssuerBuilder("SimpleIssuer") + (&certificates.Suite{ + KubeClientConfig: kubeClients.Rest, + Name: "External Issuer", + CreateIssuerFunc: issuerBuilder.create, + DeleteIssuerFunc: issuerBuilder.delete, + UnsupportedFeatures: unsupportedFeatures, + }).Define() + + clusterIssuerBuilder := newIssuerBuilder("SimpleClusterIssuer") + (&certificates.Suite{ + KubeClientConfig: kubeClients.Rest, + Name: "External ClusterIssuer", + CreateIssuerFunc: clusterIssuerBuilder.create, + DeleteIssuerFunc: clusterIssuerBuilder.delete, + UnsupportedFeatures: unsupportedFeatures, + }).Define() +}) + +var _ = framework.ConformanceDescribe("CertificateSigningRequests", func() { + t := &mockTest{} + ctx := testresource.EnsureTestDependencies(t, context.TODO(), testresource.EndToEndTest) + kubeClients := testresource.KubeClients(t, ctx) + + unsupportedFeatures := featureset.NewFeatureSet( + featureset.DurationFeature, + featureset.KeyUsagesFeature, + featureset.SaveCAToSecret, + featureset.Ed25519FeatureSet, + featureset.IssueCAFeature, + featureset.LiteralSubjectFeature, + ) + + clusterIssuerBuilder := newIssuerBuilder("SimpleClusterIssuer") + (&certificatesigningrequests.Suite{ + KubeClientConfig: kubeClients.Rest, + Name: "External ClusterIssuer", + CreateIssuerFunc: func(f *framework.Framework, ctx context.Context) string { + ref := clusterIssuerBuilder.create(f, ctx) + return fmt.Sprintf("simpleclusterissuers.issuer.cert-manager.io/%s", ref.Name) + }, + DeleteIssuerFunc: func(f *framework.Framework, ctx context.Context, s string) { + ref := cmmeta.ObjectReference{ + Group: "testing.cert-manager.io", + Kind: "SimpleClusterIssuer", + Name: s, + } + clusterIssuerBuilder.delete(f, ctx, ref) + }, + UnsupportedFeatures: unsupportedFeatures, + }).Define() +}) + +var _ = framework.ConformanceDescribe("RBAC", func() { + t := &mockTest{} + ctx := testresource.EnsureTestDependencies(t, context.TODO(), testresource.EndToEndTest) + kubeClients := testresource.KubeClients(t, ctx) + + unsupportedFeatures := featureset.NewFeatureSet( + featureset.DurationFeature, + featureset.KeyUsagesFeature, + featureset.SaveCAToSecret, + featureset.Ed25519FeatureSet, + featureset.IssueCAFeature, + featureset.LiteralSubjectFeature, + ) + + issuerBuilder := newIssuerBuilder("SimpleIssuer") + (&certificates.Suite{ + KubeClientConfig: kubeClients.Rest, + Name: "External Issuer", + CreateIssuerFunc: issuerBuilder.create, + DeleteIssuerFunc: issuerBuilder.delete, + UnsupportedFeatures: unsupportedFeatures, + }).Define() + + clusterIssuerBuilder := newIssuerBuilder("SimpleClusterIssuer") + (&certificates.Suite{ + KubeClientConfig: kubeClients.Rest, + Name: "External ClusterIssuer", + CreateIssuerFunc: clusterIssuerBuilder.create, + DeleteIssuerFunc: clusterIssuerBuilder.delete, + UnsupportedFeatures: unsupportedFeatures, + }).Define() +}) diff --git a/internal/testsetups/simple/e2e/conformance/conformance_test.go b/internal/testsetups/simple/e2e/conformance/conformance_test.go new file mode 100644 index 0000000..13a51ea --- /dev/null +++ b/internal/testsetups/simple/e2e/conformance/conformance_test.go @@ -0,0 +1,14 @@ +package conformance + +import ( + "testing" + + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" +) + +func TestConformance(t *testing.T) { + gomega.RegisterFailHandler(ginkgo.Fail) + + ginkgo.RunSpecs(t, "cert-manager conformance suite") +} diff --git a/internal/testsetups/simple/e2e/conformance/issuer_builder.go b/internal/testsetups/simple/e2e/conformance/issuer_builder.go new file mode 100644 index 0000000..ec5e3eb --- /dev/null +++ b/internal/testsetups/simple/e2e/conformance/issuer_builder.go @@ -0,0 +1,103 @@ +package conformance + +import ( + "context" + "fmt" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + crtclient "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/cert-manager/issuer-lib/conformance/framework" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/types" +) + +type issuerBuilder struct { + clusterResourceNamespace string + prototype *unstructured.Unstructured +} + +func newIssuerBuilder(issuerKind string) *issuerBuilder { + return &issuerBuilder{ + clusterResourceNamespace: "test-namespace", + prototype: &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "testing.cert-manager.io/api", + "kind": issuerKind, + "spec": map[string]interface{}{}, + }, + }, + } +} + +func (o *issuerBuilder) nameForTestObject(f *framework.Framework, suffix string) types.NamespacedName { + namespace := f.Namespace.Name + if o.prototype.GetKind() == "ClusterIssuer" { + namespace = o.clusterResourceNamespace + } + return types.NamespacedName{ + Name: fmt.Sprintf("%s-%s", f.Namespace.Name, suffix), + Namespace: namespace, + } +} + +func (o *issuerBuilder) secretAndIssuerForTest(f *framework.Framework) (*corev1.Secret, *unstructured.Unstructured, error) { + secretName := o.nameForTestObject(f, "credentials") + secret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: secretName.Name, + Namespace: secretName.Namespace, + }, + StringData: map[string]string{}, + } + + issuerName := o.nameForTestObject(f, "issuer") + issuer := o.prototype.DeepCopy() + issuer.SetName(issuerName.Name) + issuer.SetNamespace(issuerName.Namespace) + err := unstructured.SetNestedField(issuer.Object, secret.Name, "spec", "authSecretName") + + return secret, issuer, err +} + +func (o *issuerBuilder) create(f *framework.Framework, ctx context.Context) cmmeta.ObjectReference { + By("Creating an Issuer") + secret, issuer, err := o.secretAndIssuerForTest(f) + Expect(err).NotTo(HaveOccurred(), "failed to initialise test objects") + + crt, err := crtclient.New(f.KubeClientConfig, crtclient.Options{}) + Expect(err).NotTo(HaveOccurred(), "failed to create controller-runtime client") + + err = crt.Create(ctx, secret) + Expect(err).NotTo(HaveOccurred(), "failed to create secret") + + err = crt.Create(ctx, issuer) + Expect(err).NotTo(HaveOccurred(), "failed to create issuer") + + return cmmeta.ObjectReference{ + Group: issuer.GroupVersionKind().Group, + Kind: issuer.GroupVersionKind().Kind, + Name: issuer.GetName(), + } +} + +func (o *issuerBuilder) delete(f *framework.Framework, ctx context.Context, _ cmmeta.ObjectReference) { + By("Deleting the issuer") + + crt, err := crtclient.New(f.KubeClientConfig, crtclient.Options{}) + Expect(err).NotTo(HaveOccurred(), "failed to create controller-runtime client") + + secret, issuer, err := o.secretAndIssuerForTest(f) + Expect(err).NotTo(HaveOccurred(), "failed to initialise test objects") + + err = crt.Delete(ctx, issuer) + Expect(err).NotTo(HaveOccurred(), "failed to delete issuer") + + err = crt.Delete(ctx, secret) + Expect(err).NotTo(HaveOccurred(), "failed to delete secret") +} diff --git a/make/tools.mk b/make/tools.mk index 222c248..77225ae 100644 --- a/make/tools.mk +++ b/make/tools.mk @@ -37,6 +37,7 @@ TOOLS += kyverno=v1.10.0 TOOLS += yq=v4.34.1 # https://github.com/ko-build/ko/releases TOOLS += ko=0.13.0 +TOOLS += ginkgo=$(shell awk '/ginkgo\/v2/ {print $$2}' go.mod) ### go packages # https://pkg.go.dev/sigs.k8s.io/controller-tools/cmd/controller-gen?tab=versions @@ -220,6 +221,7 @@ $(BINDIR)/downloaded/tools/go-$(VENDORED_GO_VERSION)-%.tar.gz: | $(BINDIR)/downl ################### GO_DEPENDENCIES := +GO_DEPENDENCIES += ginkgo=github.com/onsi/ginkgo/v2/ginkgo GO_DEPENDENCIES += controller-gen=sigs.k8s.io/controller-tools/cmd/controller-gen GO_DEPENDENCIES += goimports=golang.org/x/tools/cmd/goimports GO_DEPENDENCIES += go-licenses=github.com/google/go-licenses From 85f7a53806b4d8b818104fa3cae1387cc58b689a Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Thu, 8 Jun 2023 21:41:37 +0200 Subject: [PATCH 02/13] transform conformance tests to table-tests and reach parity between CR and CSR tests Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- conformance/certificates/tests.go | 952 ++++++------------ .../certificatesigningrequests/tests.go | 210 ++-- 2 files changed, 474 insertions(+), 688 deletions(-) diff --git a/conformance/certificates/tests.go b/conformance/certificates/tests.go index 9560eba..59f070a 100644 --- a/conformance/certificates/tests.go +++ b/conformance/certificates/tests.go @@ -1,5 +1,5 @@ /* -Copyright 2020 The cert-manager Authors. +Copyright 2021 The cert-manager Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -35,6 +35,7 @@ import ( cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" "github.com/cert-manager/cert-manager/pkg/util/pki" + "github.com/cert-manager/cert-manager/test/unit/gen" "github.com/cert-manager/issuer-lib/conformance/framework" "github.com/cert-manager/issuer-lib/conformance/framework/helper/featureset" "github.com/cert-manager/issuer-lib/conformance/framework/helper/validation" @@ -61,583 +62,349 @@ func (s *Suite) Define() { s.complete(f) }) - s.it(f, "should issue a basic, defaulted certificate for a single distinct DNS Name", func(issuerRef cmmeta.ObjectReference) { - testCertificate := &cmapi.Certificate{ - ObjectMeta: metav1.ObjectMeta{ - Name: "testcert", - Namespace: f.Namespace.Name, - }, - Spec: cmapi.CertificateSpec{ - SecretName: "testcert-tls", - IssuerRef: issuerRef, - DNSNames: []string{e2eutil.RandomSubdomain(s.DomainSuffix)}, - }, - } - By("Creating a Certificate") - err := f.CRClient.Create(ctx, testCertificate) - Expect(err).NotTo(HaveOccurred()) - - By("Waiting for the Certificate to be issued...") - testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testCertificate, time.Minute*8) - Expect(err).NotTo(HaveOccurred()) - - By("Validating the issued Certificate...") - err = f.Helper().ValidateCertificate(testCertificate, validation.CertificateSetForUnsupportedFeatureSet(s.UnsupportedFeatures)...) - Expect(err).NotTo(HaveOccurred()) - }, featureset.OnlySAN) - - s.it(f, "should issue a CA certificate with the CA basicConstraint set", func(issuerRef cmmeta.ObjectReference) { - testCertificate := &cmapi.Certificate{ - ObjectMeta: metav1.ObjectMeta{ - Name: "testcert", - Namespace: f.Namespace.Name, - }, - Spec: cmapi.CertificateSpec{ - SecretName: "testcert-tls", - IsCA: true, - IssuerRef: issuerRef, - DNSNames: []string{e2eutil.RandomSubdomain(s.DomainSuffix)}, - }, - } - By("Creating a Certificate") - err := f.CRClient.Create(ctx, testCertificate) - Expect(err).NotTo(HaveOccurred()) - - By("Waiting for the Certificate to be issued...") - testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testCertificate, time.Minute*8) - Expect(err).NotTo(HaveOccurred()) - - By("Validating the issued Certificate...") - err = f.Helper().ValidateCertificate(testCertificate, validation.CertificateSetForUnsupportedFeatureSet(s.UnsupportedFeatures)...) - Expect(err).NotTo(HaveOccurred()) - }, featureset.IssueCAFeature) - - s.it(f, "should issue an ECDSA, defaulted certificate for a single distinct DNS Name", func(issuerRef cmmeta.ObjectReference) { - testCertificate := &cmapi.Certificate{ - ObjectMeta: metav1.ObjectMeta{ - Name: "testcert", - Namespace: f.Namespace.Name, - }, - Spec: cmapi.CertificateSpec{ - SecretName: "testcert-tls", - PrivateKey: &cmapi.CertificatePrivateKey{ - Algorithm: cmapi.ECDSAKeyAlgorithm, - }, - DNSNames: []string{e2eutil.RandomSubdomain(s.DomainSuffix)}, - IssuerRef: issuerRef, - }, - } - By("Creating a Certificate") - err := f.CRClient.Create(ctx, testCertificate) - Expect(err).NotTo(HaveOccurred()) - - By("Waiting for the Certificate to be issued...") - testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testCertificate, time.Minute*8) - Expect(err).NotTo(HaveOccurred()) - - By("Validating the issued Certificate...") - err = f.Helper().ValidateCertificate(testCertificate, validation.CertificateSetForUnsupportedFeatureSet(s.UnsupportedFeatures)...) - Expect(err).NotTo(HaveOccurred()) - }, featureset.ECDSAFeature, featureset.OnlySAN) - - s.it(f, "should issue an Ed25519, defaulted certificate for a single distinct DNS Name", func(issuerRef cmmeta.ObjectReference) { - testCertificate := &cmapi.Certificate{ - ObjectMeta: metav1.ObjectMeta{ - Name: "testcert", - Namespace: f.Namespace.Name, - }, - Spec: cmapi.CertificateSpec{ - SecretName: "testcert-tls", - PrivateKey: &cmapi.CertificatePrivateKey{ - Algorithm: cmapi.Ed25519KeyAlgorithm, - }, - DNSNames: []string{e2eutil.RandomSubdomain(s.DomainSuffix)}, - IssuerRef: issuerRef, - }, - } - By("Creating a Certificate") - err := f.CRClient.Create(ctx, testCertificate) - Expect(err).NotTo(HaveOccurred()) - - By("Waiting for the Certificate to be issued...") - testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testCertificate, time.Minute*8) - Expect(err).NotTo(HaveOccurred()) - - By("Validating the issued Certificate...") - err = f.Helper().ValidateCertificate(testCertificate, validation.CertificateSetForUnsupportedFeatureSet(s.UnsupportedFeatures)...) - Expect(err).NotTo(HaveOccurred()) - }, featureset.OnlySAN, featureset.Ed25519FeatureSet) - - s.it(f, "should issue a basic, defaulted certificate for a single Common Name", func(issuerRef cmmeta.ObjectReference) { - // Some issuers use the CN to define the cert's "ID" - // if one cert manages to be in an error state in the issuer it might throw an error - // this makes the CN more unique - cn := "test-common-name-" + e2eutil.RandStringRunes(10) - testCertificate := &cmapi.Certificate{ - ObjectMeta: metav1.ObjectMeta{ - Name: "testcert", - Namespace: f.Namespace.Name, - }, - Spec: cmapi.CertificateSpec{ - SecretName: "testcert-tls", - IssuerRef: issuerRef, - CommonName: cn, - }, - } - By("Creating a Certificate") - err := f.CRClient.Create(ctx, testCertificate) - Expect(err).NotTo(HaveOccurred()) - - By("Waiting for the Certificate to be issued...") - testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testCertificate, time.Minute*8) - Expect(err).NotTo(HaveOccurred()) - - By("Validating the issued Certificate...") - err = f.Helper().ValidateCertificate(testCertificate, validation.CertificateSetForUnsupportedFeatureSet(s.UnsupportedFeatures)...) - Expect(err).NotTo(HaveOccurred()) - }, featureset.CommonNameFeature) - - s.it(f, "should issue a basic, defaulted certificate for a single distinct DNS Name with a literal subject", func(issuerRef cmmeta.ObjectReference) { - // framework.RequireFeatureGate(f, utilfeature.DefaultFeatureGate, feature.LiteralCertificateSubject) - // Some issuers use the CN to define the cert's "ID" - // if one cert manages to be in an error state in the issuer it might throw an error - // this makes the CN more unique - host := fmt.Sprintf("*.%s.foo-long.bar.com", e2eutil.RandStringRunes(10)) - literalSubject := fmt.Sprintf("CN=%s,OU=FooLong,OU=Bar,OU=Baz,OU=Dept.,O=Corp.", host) - testCertificate := &cmapi.Certificate{ - ObjectMeta: metav1.ObjectMeta{ - Name: "testcert", - Namespace: f.Namespace.Name, - }, - Spec: cmapi.CertificateSpec{ - SecretName: "testcert-tls", - IssuerRef: issuerRef, - LiteralSubject: literalSubject, - DNSNames: []string{host}, - }, - } - By("Creating a Certificate") - err := f.CRClient.Create(ctx, testCertificate) - Expect(err).NotTo(HaveOccurred()) - - By("Waiting for the Certificate to be issued...") - - testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testCertificate, time.Minute*5) - Expect(err).NotTo(HaveOccurred()) - - //type ValidationFunc func(certificate *cmapi.Certificate, secret *corev1.Secret) error - valFunc := func(certificate *cmapi.Certificate, secret *corev1.Secret) error { - certBytes, ok := secret.Data[corev1.TLSCertKey] - if !ok { - return fmt.Errorf("no certificate data found for Certificate %q (secret %q)", certificate.Name, certificate.Spec.SecretName) - } - - createdCert, err := pki.DecodeX509CertificateBytes(certBytes) - if err != nil { - return err - } - - var dns pkix.RDNSequence - rest, err := asn1.Unmarshal(createdCert.RawSubject, &dns) - - if err != nil { - return err - } - - rdnSeq, err2 := pki.UnmarshalSubjectStringToRDNSequence(literalSubject) - - if err2 != nil { - return err2 - } - - fmt.Fprintln(GinkgoWriter, "cert", base64.StdEncoding.EncodeToString(createdCert.RawSubject), dns, err, rest) - if !reflect.DeepEqual(rdnSeq, dns) { - return fmt.Errorf("generated certificate's subject [%s] does not match expected subject [%s]", dns.String(), literalSubject) - } - return nil - } - - By("Validating the issued Certificate...") - - err = f.Helper().ValidateCertificate(testCertificate, valFunc) - Expect(err).NotTo(HaveOccurred()) - }, featureset.LiteralSubjectFeature) - - s.it(f, "should issue an ECDSA, defaulted certificate for a single Common Name", func(issuerRef cmmeta.ObjectReference) { - // Some issuers use the CN to define the cert's "ID" - // if one cert manages to be in an error state in the issuer it might throw an error - // this makes the CN more unique - cn := "test-common-name-" + e2eutil.RandStringRunes(10) - testCertificate := &cmapi.Certificate{ - ObjectMeta: metav1.ObjectMeta{ - Name: "testcert", - Namespace: f.Namespace.Name, - }, - Spec: cmapi.CertificateSpec{ - SecretName: "testcert-tls", - PrivateKey: &cmapi.CertificatePrivateKey{ - Algorithm: cmapi.ECDSAKeyAlgorithm, + type testCase struct { + name string // ginkgo v2 does not support using map[string] to store the test names (#5345) + certModifiers []gen.CertificateModifier + // The list of features that are required by the Issuer for the test to + // run. + requiredFeatures []featureset.Feature + // Extra validations which may be needed for testing, on a test case by + // case basis. All default validations will be run on every test. + extraValidations []certificates.ValidationFunc + } + + tests := []testCase{ + { + name: "should issue an RSA certificate for a single distinct DNS Name", + certModifiers: []gen.CertificateModifier{ + gen.SetCertificateDNSNames(e2eutil.RandomSubdomain(s.DomainSuffix)), + }, + requiredFeatures: []featureset.Feature{featureset.OnlySAN}, + }, + { + name: "should issue an ECDSA certificate for a single distinct DNS Name", + certModifiers: []gen.CertificateModifier{ + func(c *cmapi.Certificate) { + c.Spec.PrivateKey = &cmapi.CertificatePrivateKey{ + Algorithm: cmapi.ECDSAKeyAlgorithm, + } }, - CommonName: cn, - IssuerRef: issuerRef, - }, - } - By("Creating a Certificate") - err := f.CRClient.Create(ctx, testCertificate) - Expect(err).NotTo(HaveOccurred()) - - By("Waiting for the Certificate to be issued...") - testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testCertificate, time.Minute*8) - Expect(err).NotTo(HaveOccurred()) - - By("Validating the issued Certificate...") - err = f.Helper().ValidateCertificate(testCertificate, validation.CertificateSetForUnsupportedFeatureSet(s.UnsupportedFeatures)...) - Expect(err).NotTo(HaveOccurred()) - }, featureset.ECDSAFeature, featureset.CommonNameFeature) - - s.it(f, "should issue an Ed25519, defaulted certificate for a single Common Name", func(issuerRef cmmeta.ObjectReference) { - // Some issuers use the CN to define the cert's "ID" - // if one cert manages to be in an error state in the issuer it might throw an error - // this makes the CN more unique - cn := "test-common-name-" + e2eutil.RandStringRunes(10) - testCertificate := &cmapi.Certificate{ - ObjectMeta: metav1.ObjectMeta{ - Name: "testcert", - Namespace: f.Namespace.Name, - }, - Spec: cmapi.CertificateSpec{ - SecretName: "testcert-tls", - PrivateKey: &cmapi.CertificatePrivateKey{ - Algorithm: cmapi.Ed25519KeyAlgorithm, + gen.SetCertificateDNSNames(e2eutil.RandomSubdomain(s.DomainSuffix)), + }, + requiredFeatures: []featureset.Feature{featureset.ECDSAFeature, featureset.OnlySAN}, + }, + { + name: "should issue an Ed25519 certificate for a single distinct DNS Name", + certModifiers: []gen.CertificateModifier{ + func(c *cmapi.Certificate) { + c.Spec.PrivateKey = &cmapi.CertificatePrivateKey{ + Algorithm: cmapi.Ed25519KeyAlgorithm, + } }, - CommonName: cn, - IssuerRef: issuerRef, - }, - } - By("Creating a Certificate") - err := f.CRClient.Create(ctx, testCertificate) - Expect(err).NotTo(HaveOccurred()) - - By("Waiting for the Certificate to be issued...") - testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testCertificate, time.Minute*8) - Expect(err).NotTo(HaveOccurred()) - - By("Validating the issued Certificate...") - err = f.Helper().ValidateCertificate(testCertificate, validation.CertificateSetForUnsupportedFeatureSet(s.UnsupportedFeatures)...) - Expect(err).NotTo(HaveOccurred()) - }, featureset.Ed25519FeatureSet, featureset.CommonNameFeature) - - s.it(f, "should issue a certificate that defines an IP Address", func(issuerRef cmmeta.ObjectReference) { - testCertificate := &cmapi.Certificate{ - ObjectMeta: metav1.ObjectMeta{ - Name: "testcert", - Namespace: f.Namespace.Name, - }, - Spec: cmapi.CertificateSpec{ - SecretName: "testcert-tls", - IPAddresses: []string{sharedIPAddress}, - IssuerRef: issuerRef, - }, - } - By("Creating a Certificate") - err := f.CRClient.Create(ctx, testCertificate) - Expect(err).NotTo(HaveOccurred()) - - By("Waiting for the Certificate to be issued...") - testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testCertificate, time.Minute*8) - Expect(err).NotTo(HaveOccurred()) - - By("Validating the issued Certificate...") - err = f.Helper().ValidateCertificate(testCertificate, validation.CertificateSetForUnsupportedFeatureSet(s.UnsupportedFeatures)...) - Expect(err).NotTo(HaveOccurred()) - }, featureset.IPAddressFeature) - - s.it(f, "should issue a certificate that defines a DNS Name and IP Address", func(issuerRef cmmeta.ObjectReference) { - testCertificate := &cmapi.Certificate{ - ObjectMeta: metav1.ObjectMeta{ - Name: "testcert", - Namespace: f.Namespace.Name, - }, - Spec: cmapi.CertificateSpec{ - SecretName: "testcert-tls", - IPAddresses: []string{sharedIPAddress}, - DNSNames: []string{e2eutil.RandomSubdomain(s.DomainSuffix)}, - IssuerRef: issuerRef, - }, - } - By("Creating a Certificate") - err := f.CRClient.Create(ctx, testCertificate) - Expect(err).NotTo(HaveOccurred()) - - By("Waiting for the Certificate to be issued...") - testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testCertificate, time.Minute*8) - Expect(err).NotTo(HaveOccurred()) - - By("Validating the issued Certificate...") - err = f.Helper().ValidateCertificate(testCertificate, validation.CertificateSetForUnsupportedFeatureSet(s.UnsupportedFeatures)...) - Expect(err).NotTo(HaveOccurred()) - }, featureset.OnlySAN, featureset.IPAddressFeature) - - s.it(f, "should issue a certificate that defines a Common Name and IP Address", func(issuerRef cmmeta.ObjectReference) { - // Some issuers use the CN to define the cert's "ID" - // if one cert manages to be in an error state in the issuer it might throw an error - // this makes the CN more unique - cn := "test-common-name-" + e2eutil.RandStringRunes(10) - testCertificate := &cmapi.Certificate{ - ObjectMeta: metav1.ObjectMeta{ - Name: "testcert", - Namespace: f.Namespace.Name, - }, - Spec: cmapi.CertificateSpec{ - SecretName: "testcert-tls", - CommonName: cn, - IPAddresses: []string{sharedIPAddress}, - IssuerRef: issuerRef, - }, - } - By("Creating a Certificate") - err := f.CRClient.Create(ctx, testCertificate) - Expect(err).NotTo(HaveOccurred()) - - By("Waiting for the Certificate to be issued...") - testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testCertificate, time.Minute*8) - Expect(err).NotTo(HaveOccurred()) - - By("Validating the issued Certificate...") - err = f.Helper().ValidateCertificate(testCertificate, validation.CertificateSetForUnsupportedFeatureSet(s.UnsupportedFeatures)...) - Expect(err).NotTo(HaveOccurred()) - }, featureset.CommonNameFeature, featureset.IPAddressFeature) - - s.it(f, "should issue a certificate that defines an Email Address", func(issuerRef cmmeta.ObjectReference) { - testCertificate := &cmapi.Certificate{ - ObjectMeta: metav1.ObjectMeta{ - Name: "testcert", - Namespace: f.Namespace.Name, - }, - Spec: cmapi.CertificateSpec{ - SecretName: "testcert-tls", - EmailAddresses: []string{"alice@example.com"}, - IssuerRef: issuerRef, - }, - } - By("Creating a Certificate") - err := f.CRClient.Create(ctx, testCertificate) - Expect(err).NotTo(HaveOccurred()) - - By("Waiting for the Certificate to be issued...") - testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testCertificate, time.Minute*8) - Expect(err).NotTo(HaveOccurred()) - - By("Validating the issued Certificate...") - err = f.Helper().ValidateCertificate(testCertificate, validation.CertificateSetForUnsupportedFeatureSet(s.UnsupportedFeatures)...) - Expect(err).NotTo(HaveOccurred()) - }, featureset.EmailSANsFeature, featureset.OnlySAN) - - s.it(f, "should issue a certificate that defines a Common Name and URI SAN", func(issuerRef cmmeta.ObjectReference) { - // Some issuers use the CN to define the cert's "ID" - // if one cert manages to be in an error state in the issuer it might throw an error - // this makes the CN more unique - cn := "test-common-name-" + e2eutil.RandStringRunes(10) - testCertificate := &cmapi.Certificate{ - ObjectMeta: metav1.ObjectMeta{ - Name: "testcert", - Namespace: f.Namespace.Name, - }, - Spec: cmapi.CertificateSpec{ - SecretName: "testcert-tls", - CommonName: cn, - URIs: []string{"spiffe://cluster.local/ns/sandbox/sa/foo"}, - IssuerRef: issuerRef, - }, - } - By("Creating a Certificate") - err := f.CRClient.Create(ctx, testCertificate) - Expect(err).NotTo(HaveOccurred()) - - By("Waiting for the Certificate to be issued...") - testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testCertificate, time.Minute*8) - Expect(err).NotTo(HaveOccurred()) - - By("Validating the issued Certificate...") - err = f.Helper().ValidateCertificate(testCertificate, validation.CertificateSetForUnsupportedFeatureSet(s.UnsupportedFeatures)...) - Expect(err).NotTo(HaveOccurred()) - }, featureset.URISANsFeature, featureset.CommonNameFeature) - - s.it(f, "should issue a certificate that defines a 2 distinct DNS Names with one copied to the Common Name", func(issuerRef cmmeta.ObjectReference) { - testCertificate := &cmapi.Certificate{ - ObjectMeta: metav1.ObjectMeta{ - Name: "testcert", - Namespace: f.Namespace.Name, - }, - Spec: cmapi.CertificateSpec{ - SecretName: "testcert-tls", - CommonName: e2eutil.RandomSubdomain(s.DomainSuffix), - IssuerRef: issuerRef, - }, - } - testCertificate.Spec.DNSNames = []string{ - testCertificate.Spec.CommonName, e2eutil.RandomSubdomain(s.DomainSuffix), - } - - By("Creating a Certificate") - err := f.CRClient.Create(ctx, testCertificate) - Expect(err).NotTo(HaveOccurred()) - - By("Waiting for the Certificate to be issued...") - testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testCertificate, time.Minute*8) - Expect(err).NotTo(HaveOccurred()) - - By("Validating the issued Certificate...") - err = f.Helper().ValidateCertificate(testCertificate, validation.CertificateSetForUnsupportedFeatureSet(s.UnsupportedFeatures)...) - Expect(err).NotTo(HaveOccurred()) - }, featureset.CommonNameFeature) - - s.it(f, "should issue a certificate that defines a distinct DNS Name and another distinct Common Name", func(issuerRef cmmeta.ObjectReference) { - testCertificate := &cmapi.Certificate{ - ObjectMeta: metav1.ObjectMeta{ - Name: "testcert", - Namespace: f.Namespace.Name, - }, - Spec: cmapi.CertificateSpec{ - SecretName: "testcert-tls", - CommonName: e2eutil.RandomSubdomain(s.DomainSuffix), - IssuerRef: issuerRef, - DNSNames: []string{e2eutil.RandomSubdomain(s.DomainSuffix)}, - }, - } - - By("Creating a Certificate") - err := f.CRClient.Create(ctx, testCertificate) - Expect(err).NotTo(HaveOccurred()) - - By("Waiting for the Certificate to be issued...") - testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testCertificate, time.Minute*8) - Expect(err).NotTo(HaveOccurred()) - - By("Validating the issued Certificate...") - err = f.Helper().ValidateCertificate(testCertificate, validation.CertificateSetForUnsupportedFeatureSet(s.UnsupportedFeatures)...) - Expect(err).NotTo(HaveOccurred()) - }, featureset.CommonNameFeature) - - s.it(f, "should issue a certificate that defines a DNS Name and sets a duration", func(issuerRef cmmeta.ObjectReference) { - testCertificate := &cmapi.Certificate{ - ObjectMeta: metav1.ObjectMeta{ - Name: "testcert", - Namespace: f.Namespace.Name, - }, - Spec: cmapi.CertificateSpec{ - SecretName: "testcert-tls", - IssuerRef: issuerRef, - DNSNames: []string{e2eutil.RandomSubdomain(s.DomainSuffix)}, - Duration: &metav1.Duration{ - Duration: time.Hour * 896, + gen.SetCertificateDNSNames(e2eutil.RandomSubdomain(s.DomainSuffix)), + }, + requiredFeatures: []featureset.Feature{featureset.Ed25519FeatureSet, featureset.OnlySAN}, + }, + { + name: "should issue an RSA certificate for a single Common Name", + certModifiers: []gen.CertificateModifier{ + // Some issuers use the CN to define the cert's "ID" + // if one cert manages to be in an error state in the issuer it might throw an error + // this makes the CN more unique + gen.SetCertificateCommonName("test-common-name-" + e2eutil.RandStringRunes(10)), + }, + requiredFeatures: []featureset.Feature{featureset.CommonNameFeature}, + }, + { + name: "should issue an ECDSA certificate for a single Common Name", + certModifiers: []gen.CertificateModifier{ + func(c *cmapi.Certificate) { + c.Spec.PrivateKey = &cmapi.CertificatePrivateKey{ + Algorithm: cmapi.ECDSAKeyAlgorithm, + } }, - }, - } - By("Creating a Certificate") - err := f.CRClient.Create(ctx, testCertificate) - Expect(err).NotTo(HaveOccurred()) - - By("Waiting for the Certificate to be issued...") - testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testCertificate, time.Minute*8) - Expect(err).NotTo(HaveOccurred()) - - By("Validating the issued Certificate...") - err = f.Helper().ValidateCertificate(testCertificate, validation.CertificateSetForUnsupportedFeatureSet(s.UnsupportedFeatures)...) - Expect(err).NotTo(HaveOccurred()) - }, featureset.DurationFeature, featureset.OnlySAN) - - s.it(f, "should issue a certificate that defines a wildcard DNS Name", func(issuerRef cmmeta.ObjectReference) { - testCertificate := &cmapi.Certificate{ - ObjectMeta: metav1.ObjectMeta{ - Name: "testcert", - Namespace: f.Namespace.Name, - }, - Spec: cmapi.CertificateSpec{ - SecretName: "testcert-tls", - IssuerRef: issuerRef, - DNSNames: []string{"*." + e2eutil.RandomSubdomain(s.DomainSuffix)}, - }, - } - By("Creating a Certificate") - err := f.CRClient.Create(ctx, testCertificate) - Expect(err).NotTo(HaveOccurred()) - - By("Waiting for the Certificate to be issued...") - testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testCertificate, time.Minute*8) - Expect(err).NotTo(HaveOccurred()) - - By("Validating the issued Certificate...") - err = f.Helper().ValidateCertificate(testCertificate, validation.CertificateSetForUnsupportedFeatureSet(s.UnsupportedFeatures)...) - Expect(err).NotTo(HaveOccurred()) - }, featureset.WildcardsFeature, featureset.OnlySAN) - - s.it(f, "should issue a certificate that includes only a URISANs name", func(issuerRef cmmeta.ObjectReference) { - testCertificate := &cmapi.Certificate{ - ObjectMeta: metav1.ObjectMeta{ - Name: "testcert", - Namespace: f.Namespace.Name, - }, - Spec: cmapi.CertificateSpec{ - SecretName: "testcert-tls", - URIs: []string{ - "spiffe://cluster.local/ns/sandbox/sa/foo", + // Some issuers use the CN to define the cert's "ID" + // if one cert manages to be in an error state in the issuer it might throw an error + // this makes the CN more unique + gen.SetCertificateCommonName("test-common-name-" + e2eutil.RandStringRunes(10)), + }, + requiredFeatures: []featureset.Feature{featureset.CommonNameFeature, featureset.ECDSAFeature}, + }, + { + name: "should issue an Ed25519 certificate for a single Common Name", + certModifiers: []gen.CertificateModifier{ + func(c *cmapi.Certificate) { + c.Spec.PrivateKey = &cmapi.CertificatePrivateKey{ + Algorithm: cmapi.Ed25519KeyAlgorithm, + } }, - IssuerRef: issuerRef, - }, - } - By("Creating a Certificate") - err := f.CRClient.Create(ctx, testCertificate) - Expect(err).NotTo(HaveOccurred()) - - By("Waiting for the Certificate to be issued...") - testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testCertificate, time.Minute*8) - Expect(err).NotTo(HaveOccurred()) - - By("Validating the issued Certificate...") - err = f.Helper().ValidateCertificate(testCertificate, validation.CertificateSetForUnsupportedFeatureSet(s.UnsupportedFeatures)...) - Expect(err).NotTo(HaveOccurred()) - }, featureset.URISANsFeature, featureset.OnlySAN) - - s.it(f, "should issue a certificate that includes arbitrary key usages", func(issuerRef cmmeta.ObjectReference) { - testCertificate := &cmapi.Certificate{ - ObjectMeta: metav1.ObjectMeta{ - Name: "testcert", - Namespace: f.Namespace.Name, - }, - Spec: cmapi.CertificateSpec{ - SecretName: "testcert-tls", - DNSNames: []string{e2eutil.RandomSubdomain(s.DomainSuffix)}, - IssuerRef: issuerRef, - Usages: []cmapi.KeyUsage{ + // Some issuers use the CN to define the cert's "ID" + // if one cert manages to be in an error state in the issuer it might throw an error + // this makes the CN more unique + gen.SetCertificateCommonName("test-common-name-" + e2eutil.RandStringRunes(10)), + }, + requiredFeatures: []featureset.Feature{featureset.CommonNameFeature, featureset.Ed25519FeatureSet}, + }, + { + name: "should issue a certificate that defines a Common Name and IP Address", + certModifiers: []gen.CertificateModifier{ + gen.SetCertificateIPs(sharedIPAddress), + // Some issuers use the CN to define the cert's "ID" + // if one cert manages to be in an error state in the issuer it might throw an error + // this makes the CN more unique + gen.SetCertificateCommonName("test-common-name-" + e2eutil.RandStringRunes(10)), + }, + requiredFeatures: []featureset.Feature{featureset.CommonNameFeature, featureset.IPAddressFeature}, + }, + { + name: "should issue a certificate that defines an IP Address", + certModifiers: []gen.CertificateModifier{ + gen.SetCertificateIPs(sharedIPAddress), + }, + requiredFeatures: []featureset.Feature{featureset.IPAddressFeature}, + }, + { + name: "should issue a certificate that defines a DNS Name and IP Address", + certModifiers: []gen.CertificateModifier{ + gen.SetCertificateIPs(sharedIPAddress), + gen.SetCertificateDNSNames(e2eutil.RandomSubdomain(s.DomainSuffix)), + }, + requiredFeatures: []featureset.Feature{featureset.OnlySAN, featureset.IPAddressFeature}, + }, + { + name: "should issue a CA certificate with the CA basicConstraint set", + certModifiers: []gen.CertificateModifier{ + gen.SetCertificateIsCA(true), + gen.SetCertificateDNSNames(e2eutil.RandomSubdomain(s.DomainSuffix)), + }, + requiredFeatures: []featureset.Feature{featureset.IssueCAFeature}, + }, + { + name: "should issue a certificate that defines an Email Address", + certModifiers: []gen.CertificateModifier{ + gen.SetCertificateEmails("alice@example.com"), + }, + requiredFeatures: []featureset.Feature{featureset.OnlySAN, featureset.EmailSANsFeature}, + }, + { + name: "should issue a certificate that defines a Common Name and URI SAN", + certModifiers: []gen.CertificateModifier{ + gen.SetCertificateURIs("spiffe://cluster.local/ns/sandbox/sa/foo"), + // Some issuers use the CN to define the cert's "ID" + // if one cert manages to be in an error state in the issuer it might throw an error + // this makes the CN more unique + gen.SetCertificateCommonName("test-common-name-" + e2eutil.RandStringRunes(10)), + }, + requiredFeatures: []featureset.Feature{featureset.CommonNameFeature, featureset.URISANsFeature}, + }, + { + name: "should issue a certificate that defines a 2 distinct DNS Names with one copied to the Common Name", + certModifiers: func() []gen.CertificateModifier { + commonName := e2eutil.RandomSubdomain(s.DomainSuffix) + + return []gen.CertificateModifier{ + gen.SetCertificateCommonName(commonName), + gen.SetCertificateDNSNames(commonName, e2eutil.RandomSubdomain(s.DomainSuffix)), + } + }(), + requiredFeatures: []featureset.Feature{featureset.CommonNameFeature}, + }, + { + name: "should issue a certificate that defines a distinct DNS Name and another distinct Common Name", + certModifiers: []gen.CertificateModifier{ + gen.SetCertificateCommonName(e2eutil.RandomSubdomain(s.DomainSuffix)), + gen.SetCertificateDNSNames(e2eutil.RandomSubdomain(s.DomainSuffix)), + }, + requiredFeatures: []featureset.Feature{featureset.CommonNameFeature}, + }, + { + name: "should issue a certificate that defines a Common Name, DNS Name, and sets a duration", + certModifiers: func() []gen.CertificateModifier { + commonName := e2eutil.RandomSubdomain(s.DomainSuffix) + + return []gen.CertificateModifier{ + gen.SetCertificateCommonName(commonName), + gen.SetCertificateDNSNames(commonName), + gen.SetCertificateDuration(time.Hour * 896), + } + }(), + requiredFeatures: []featureset.Feature{featureset.DurationFeature}, + }, + { + name: "should issue a certificate that defines a DNS Name and sets a duration", + certModifiers: []gen.CertificateModifier{ + gen.SetCertificateDNSNames(e2eutil.RandomSubdomain(s.DomainSuffix)), + gen.SetCertificateDuration(time.Hour * 896), + }, + requiredFeatures: []featureset.Feature{featureset.OnlySAN, featureset.DurationFeature}, + }, + { + name: "should issue a certificate which has a wildcard DNS Name defined", + certModifiers: []gen.CertificateModifier{ + gen.SetCertificateDNSNames("*." + e2eutil.RandomSubdomain(s.DomainSuffix)), + }, + requiredFeatures: []featureset.Feature{featureset.WildcardsFeature, featureset.OnlySAN}, + }, + { + name: "should issue a certificate which has a wildcard DNS Name and its apex DNS Name defined", + certModifiers: func() []gen.CertificateModifier { + dnsDomain := e2eutil.RandomSubdomain(s.DomainSuffix) + + return []gen.CertificateModifier{ + gen.SetCertificateDNSNames("*."+dnsDomain, dnsDomain), + } + }(), + requiredFeatures: []featureset.Feature{featureset.WildcardsFeature, featureset.OnlySAN}, + }, + { + name: "should issue a certificate that includes only a URISANs name", + certModifiers: []gen.CertificateModifier{ + gen.SetCertificateURIs("spiffe://cluster.local/ns/sandbox/sa/foo"), + }, + requiredFeatures: []featureset.Feature{featureset.URISANsFeature, featureset.OnlySAN}, + }, + { + name: "should issue a certificate that includes arbitrary key usages with common name", + certModifiers: []gen.CertificateModifier{ + gen.SetCertificateCommonName(e2eutil.RandomSubdomain(s.DomainSuffix)), + gen.SetCertificateKeyUsages( + cmapi.UsageServerAuth, + cmapi.UsageClientAuth, + cmapi.UsageDigitalSignature, + cmapi.UsageDataEncipherment, + ), + }, + extraValidations: []certificates.ValidationFunc{ + certificates.ExpectKeyUsageExtKeyUsageClientAuth, + certificates.ExpectKeyUsageExtKeyUsageServerAuth, + certificates.ExpectKeyUsageUsageDigitalSignature, + certificates.ExpectKeyUsageUsageDataEncipherment, + }, + requiredFeatures: []featureset.Feature{featureset.KeyUsagesFeature}, + }, + { + name: "should issue a certificate that includes arbitrary key usages with SAN only", + certModifiers: []gen.CertificateModifier{ + gen.SetCertificateDNSNames(e2eutil.RandomSubdomain(s.DomainSuffix)), + gen.SetCertificateKeyUsages( cmapi.UsageSigning, cmapi.UsageDataEncipherment, cmapi.UsageServerAuth, cmapi.UsageClientAuth, + ), + }, + extraValidations: []certificates.ValidationFunc{ + certificates.ExpectKeyUsageExtKeyUsageClientAuth, + certificates.ExpectKeyUsageExtKeyUsageServerAuth, + certificates.ExpectKeyUsageUsageDigitalSignature, + certificates.ExpectKeyUsageUsageDataEncipherment, + }, + requiredFeatures: []featureset.Feature{featureset.KeyUsagesFeature, featureset.OnlySAN}, + }, + { + name: "should issue a signing CA certificate that has a large duration", + certModifiers: []gen.CertificateModifier{ + gen.SetCertificateCommonName("cert-manager-ca"), + gen.SetCertificateDuration(10000 * time.Hour), + gen.SetCertificateIsCA(true), + }, + requiredFeatures: []featureset.Feature{featureset.KeyUsagesFeature, featureset.DurationFeature, featureset.CommonNameFeature}, + }, + { + name: "should issue a certificate that defines a long domain", + certModifiers: func() []gen.CertificateModifier { + const maxLengthOfDomainSegment = 63 + return []gen.CertificateModifier{ + gen.SetCertificateDNSNames(e2eutil.RandomSubdomainLength(s.DomainSuffix, maxLengthOfDomainSegment)), + } + }(), + requiredFeatures: []featureset.Feature{featureset.OnlySAN, featureset.LongDomainFeatureSet}, + }, + { + name: "should issue a basic, defaulted certificate for a single distinct DNS Name with a literal subject", + certModifiers: func() []gen.CertificateModifier { + host := fmt.Sprintf("*.%s.foo-long.bar.com", e2eutil.RandStringRunes(10)) + literalSubject := fmt.Sprintf("CN=%s,OU=FooLong,OU=Bar,OU=Baz,OU=Dept.,O=Corp.", host) + + return []gen.CertificateModifier{ + func(c *cmapi.Certificate) { + c.Spec.LiteralSubject = literalSubject + }, + gen.SetCertificateDNSNames(host), + } + }(), + extraValidations: []certificates.ValidationFunc{ + func(certificate *cmapi.Certificate, secret *corev1.Secret) error { + certBytes, ok := secret.Data[corev1.TLSCertKey] + if !ok { + return fmt.Errorf("no certificate data found for Certificate %q (secret %q)", certificate.Name, certificate.Spec.SecretName) + } + + createdCert, err := pki.DecodeX509CertificateBytes(certBytes) + if err != nil { + return err + } + + var dns pkix.RDNSequence + rest, err := asn1.Unmarshal(createdCert.RawSubject, &dns) + + if err != nil { + return err + } + + rdnSeq, err2 := pki.UnmarshalSubjectStringToRDNSequence(certificate.Spec.LiteralSubject) + + if err2 != nil { + return err2 + } + + fmt.Fprintln(GinkgoWriter, "cert", base64.StdEncoding.EncodeToString(createdCert.RawSubject), dns, err, rest) + if !reflect.DeepEqual(rdnSeq, dns) { + return fmt.Errorf("generated certificate's subject [%s] does not match expected subject [%s]", dns.String(), certificate.Spec.LiteralSubject) + } + return nil }, }, - } - By("Creating a Certificate") - err := f.CRClient.Create(ctx, testCertificate) - Expect(err).NotTo(HaveOccurred()) + requiredFeatures: []featureset.Feature{featureset.LiteralSubjectFeature}, + }, + } - By("Waiting for the Certificate to be issued...") - testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testCertificate, time.Minute*8) - Expect(err).NotTo(HaveOccurred()) + defineTest := func(test testCase) { + s.it(f, test.name, func(issuerRef cmmeta.ObjectReference) { + certificate := &cmapi.Certificate{ + ObjectMeta: metav1.ObjectMeta{ + Name: "testcert", + Namespace: f.Namespace.Name, + }, + Spec: cmapi.CertificateSpec{ + SecretName: "testcert-tls", + IssuerRef: issuerRef, + }, + } - By("Validating the issued Certificate...") + certificate = gen.CertificateFrom( + certificate, + test.certModifiers..., + ) - validations := []certificates.ValidationFunc{ - certificates.ExpectKeyUsageExtKeyUsageClientAuth, - certificates.ExpectKeyUsageExtKeyUsageServerAuth, - certificates.ExpectKeyUsageUsageDigitalSignature, - certificates.ExpectKeyUsageUsageDataEncipherment, - } - validations = append(validations, validation.CertificateSetForUnsupportedFeatureSet(s.UnsupportedFeatures)...) + By("Creating a Certificate") + err := f.CRClient.Create(ctx, certificate) + Expect(err).NotTo(HaveOccurred()) - err = f.Helper().ValidateCertificate(testCertificate, validations...) - Expect(err).NotTo(HaveOccurred()) - }, featureset.KeyUsagesFeature, featureset.OnlySAN) + By("Waiting for the Certificate to be issued...") + certificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(certificate, time.Minute*8) + Expect(err).NotTo(HaveOccurred()) + + By("Validating the issued Certificate...") + validations := append(test.extraValidations, validation.CertificateSetForUnsupportedFeatureSet(s.UnsupportedFeatures)...) + err = f.Helper().ValidateCertificate(certificate, validations...) + Expect(err).NotTo(HaveOccurred()) + }, test.requiredFeatures...) + } + + for _, tc := range tests { + defineTest(tc) + } s.it(f, "should issue another certificate with the same private key if the existing certificate and CertificateRequest are deleted", func(issuerRef cmmeta.ObjectReference) { testCertificate := &cmapi.Certificate{ @@ -695,36 +462,6 @@ func (s *Suite) Define() { } }, featureset.ReusePrivateKeyFeature, featureset.OnlySAN) - s.it(f, "should issue a certificate that defines a long domain", func(issuerRef cmmeta.ObjectReference) { - // the maximum length of a single segment of the domain being requested - const maxLengthOfDomainSegment = 63 - - testCertificate := &cmapi.Certificate{ - ObjectMeta: metav1.ObjectMeta{ - Name: "testcert", - Namespace: f.Namespace.Name, - }, - Spec: cmapi.CertificateSpec{ - SecretName: "testcert-tls", - DNSNames: []string{e2eutil.RandomSubdomainLength(s.DomainSuffix, maxLengthOfDomainSegment)}, - IssuerRef: issuerRef, - }, - } - validations := validation.CertificateSetForUnsupportedFeatureSet(s.UnsupportedFeatures) - - By("Creating a Certificate") - err := f.CRClient.Create(ctx, testCertificate) - Expect(err).NotTo(HaveOccurred()) - - By("Waiting for the Certificate to be issued...") - testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testCertificate, time.Minute*8) - Expect(err).NotTo(HaveOccurred()) - - By("Sanity-check the issued Certificate") - err = f.Helper().ValidateCertificate(testCertificate, validations...) - Expect(err).NotTo(HaveOccurred()) - }, featureset.OnlySAN, featureset.LongDomainFeatureSet) - s.it(f, "should allow updating an existing certificate with a new DNS Name", func(issuerRef cmmeta.ObjectReference) { testCertificate := &cmapi.Certificate{ ObjectMeta: metav1.ObjectMeta{ @@ -777,32 +514,5 @@ func (s *Suite) Define() { err = f.Helper().ValidateCertificate(testCertificate, validations...) Expect(err).NotTo(HaveOccurred()) }, featureset.OnlySAN) - - s.it(f, "should issue a certificate that defines a wildcard DNS Name and its apex DNS Name", func(issuerRef cmmeta.ObjectReference) { - dnsDomain := e2eutil.RandomSubdomain(s.DomainSuffix) - testCertificate := &cmapi.Certificate{ - ObjectMeta: metav1.ObjectMeta{ - Name: "testcert", - Namespace: f.Namespace.Name, - }, - Spec: cmapi.CertificateSpec{ - SecretName: "testcert-tls", - IssuerRef: issuerRef, - DNSNames: []string{"*." + dnsDomain, dnsDomain}, - }, - } - By("Creating a Certificate") - err := f.CRClient.Create(ctx, testCertificate) - Expect(err).NotTo(HaveOccurred()) - - // use a longer timeout for this, as it requires performing 2 dns validations in serial - By("Waiting for the Certificate to be issued...") - testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testCertificate, time.Minute*10) - Expect(err).NotTo(HaveOccurred()) - - By("Validating the issued Certificate...") - err = f.Helper().ValidateCertificate(testCertificate, validation.CertificateSetForUnsupportedFeatureSet(s.UnsupportedFeatures)...) - Expect(err).NotTo(HaveOccurred()) - }, featureset.WildcardsFeature, featureset.OnlySAN) }) } diff --git a/conformance/certificatesigningrequests/tests.go b/conformance/certificatesigningrequests/tests.go index 7d0a124..ad329c2 100644 --- a/conformance/certificatesigningrequests/tests.go +++ b/conformance/certificatesigningrequests/tests.go @@ -50,7 +50,7 @@ func (s *Suite) Define() { Describe("CertificateSigningRequest with issuer type "+s.Name, func() { f := framework.NewFramework("certificatesigningrequests", s.KubeClientConfig) - sharedCommonName := "" + sharedIPAddress := "127.0.0.1" sharedURI, err := url.Parse("spiffe://cluster.local/ns/sandbox/sa/foo") if err != nil { // This should never happen, and is a bug. Panic to prevent garbage test @@ -66,8 +66,6 @@ func (s *Suite) Define() { } s.complete(f) - - sharedCommonName = e2eutil.RandomSubdomain(s.DomainSuffix) }) type testCase struct { @@ -76,7 +74,7 @@ func (s *Suite) Define() { // csrModifers define the shape of the X.509 CSR which is used in the // test case. We use a function to allow access to variables that are // initialized at test runtime by complete(). - csrModifiers func() []gen.CSRModifier + csrModifiers []gen.CSRModifier kubeCSRUsages []certificatesv1.KeyUsage kubeCSRAnnotations map[string]string kubeCSRExpirationSeconds *int32 @@ -92,8 +90,8 @@ func (s *Suite) Define() { { name: "should issue an RSA certificate for a single distinct DNS Name", keyAlgo: x509.RSA, - csrModifiers: func() []gen.CSRModifier { - return []gen.CSRModifier{gen.SetCSRDNSNames(e2eutil.RandomSubdomain(s.DomainSuffix))} + csrModifiers: []gen.CSRModifier{ + gen.SetCSRDNSNames(e2eutil.RandomSubdomain(s.DomainSuffix)), }, kubeCSRUsages: []certificatesv1.KeyUsage{ certificatesv1.UsageDigitalSignature, @@ -104,8 +102,8 @@ func (s *Suite) Define() { { name: "should issue an ECDSA certificate for a single distinct DNS Name", keyAlgo: x509.ECDSA, - csrModifiers: func() []gen.CSRModifier { - return []gen.CSRModifier{gen.SetCSRDNSNames(e2eutil.RandomSubdomain(s.DomainSuffix))} + csrModifiers: []gen.CSRModifier{ + gen.SetCSRDNSNames(e2eutil.RandomSubdomain(s.DomainSuffix)), }, kubeCSRUsages: []certificatesv1.KeyUsage{ certificatesv1.UsageDigitalSignature, @@ -116,8 +114,8 @@ func (s *Suite) Define() { { name: "should issue an Ed25519 certificate for a single distinct DNS Name", keyAlgo: x509.Ed25519, - csrModifiers: func() []gen.CSRModifier { - return []gen.CSRModifier{gen.SetCSRDNSNames(e2eutil.RandomSubdomain(s.DomainSuffix))} + csrModifiers: []gen.CSRModifier{ + gen.SetCSRDNSNames(e2eutil.RandomSubdomain(s.DomainSuffix)), }, kubeCSRUsages: []certificatesv1.KeyUsage{ certificatesv1.UsageDigitalSignature, @@ -128,8 +126,8 @@ func (s *Suite) Define() { { name: "should issue an RSA certificate for a single Common Name", keyAlgo: x509.RSA, - csrModifiers: func() []gen.CSRModifier { - return []gen.CSRModifier{gen.SetCSRCommonName("test-common-name-" + e2eutil.RandStringRunes(10))} + csrModifiers: []gen.CSRModifier{ + gen.SetCSRCommonName("test-common-name-" + e2eutil.RandStringRunes(10)), }, kubeCSRUsages: []certificatesv1.KeyUsage{ certificatesv1.UsageDigitalSignature, @@ -140,8 +138,8 @@ func (s *Suite) Define() { { name: "should issue an ECDSA certificate for a single Common Name", keyAlgo: x509.ECDSA, - csrModifiers: func() []gen.CSRModifier { - return []gen.CSRModifier{gen.SetCSRCommonName("test-common-name-" + e2eutil.RandStringRunes(10))} + csrModifiers: []gen.CSRModifier{ + gen.SetCSRCommonName("test-common-name-" + e2eutil.RandStringRunes(10)), }, kubeCSRUsages: []certificatesv1.KeyUsage{ certificatesv1.UsageDigitalSignature, @@ -152,8 +150,8 @@ func (s *Suite) Define() { { name: "should issue an Ed25519 certificate for a single Common Name", keyAlgo: x509.Ed25519, - csrModifiers: func() []gen.CSRModifier { - return []gen.CSRModifier{gen.SetCSRCommonName("test-common-name-" + e2eutil.RandStringRunes(10))} + csrModifiers: []gen.CSRModifier{ + gen.SetCSRCommonName("test-common-name-" + e2eutil.RandStringRunes(10)), }, kubeCSRUsages: []certificatesv1.KeyUsage{ certificatesv1.UsageDigitalSignature, @@ -164,11 +162,9 @@ func (s *Suite) Define() { { name: "should issue a certificate that defines a Common Name and IP Address", keyAlgo: x509.RSA, - csrModifiers: func() []gen.CSRModifier { - return []gen.CSRModifier{ - gen.SetCSRCommonName("test-common-name-" + e2eutil.RandStringRunes(10)), - gen.SetCSRIPAddresses(net.IPv4(127, 0, 0, 1), net.IPv4(8, 8, 8, 8)), - } + csrModifiers: []gen.CSRModifier{ + gen.SetCSRCommonName("test-common-name-" + e2eutil.RandStringRunes(10)), + gen.SetCSRIPAddresses(net.ParseIP(sharedIPAddress)), }, kubeCSRUsages: []certificatesv1.KeyUsage{ certificatesv1.UsageDigitalSignature, @@ -176,13 +172,51 @@ func (s *Suite) Define() { }, requiredFeatures: []featureset.Feature{featureset.CommonNameFeature, featureset.IPAddressFeature}, }, + { + name: "should issue a certificate that defines an IP Address", + keyAlgo: x509.RSA, + csrModifiers: []gen.CSRModifier{ + gen.SetCSRIPAddresses(net.ParseIP(sharedIPAddress)), + }, + kubeCSRUsages: []certificatesv1.KeyUsage{ + certificatesv1.UsageDigitalSignature, + certificatesv1.UsageKeyEncipherment, + }, + requiredFeatures: []featureset.Feature{featureset.IPAddressFeature}, + }, + { + name: "should issue a certificate that defines a DNS Name and IP Address", + keyAlgo: x509.RSA, + csrModifiers: []gen.CSRModifier{ + gen.SetCSRIPAddresses(net.ParseIP(sharedIPAddress)), + gen.SetCSRDNSNames(e2eutil.RandomSubdomain(s.DomainSuffix)), + }, + kubeCSRUsages: []certificatesv1.KeyUsage{ + certificatesv1.UsageDigitalSignature, + certificatesv1.UsageKeyEncipherment, + }, + requiredFeatures: []featureset.Feature{featureset.OnlySAN, featureset.IPAddressFeature}, + }, + { + name: "should issue a CA certificate with the CA basicConstraint set", + keyAlgo: x509.RSA, + csrModifiers: []gen.CSRModifier{ + gen.SetCSRDNSNames(e2eutil.RandomSubdomain(s.DomainSuffix)), + }, + kubeCSRAnnotations: map[string]string{ + experimentalapi.CertificateSigningRequestIsCAAnnotationKey: "true", + }, + kubeCSRUsages: []certificatesv1.KeyUsage{ + certificatesv1.UsageDigitalSignature, + certificatesv1.UsageKeyEncipherment, + }, + requiredFeatures: []featureset.Feature{featureset.IssueCAFeature}, + }, { name: "should issue a certificate that defines an Email Address", keyAlgo: x509.RSA, - csrModifiers: func() []gen.CSRModifier { - return []gen.CSRModifier{ - gen.SetCSREmails([]string{"alice@example.com", "bob@cert-manager.io"}), - } + csrModifiers: []gen.CSRModifier{ + gen.SetCSREmails([]string{"alice@example.com", "bob@cert-manager.io"}), }, kubeCSRUsages: []certificatesv1.KeyUsage{ certificatesv1.UsageDigitalSignature, @@ -193,11 +227,9 @@ func (s *Suite) Define() { { name: "should issue a certificate that defines a Common Name and URI SAN", keyAlgo: x509.RSA, - csrModifiers: func() []gen.CSRModifier { - return []gen.CSRModifier{ - gen.SetCSRCommonName("test-common-name-" + e2eutil.RandStringRunes(10)), - gen.SetCSRURIs(sharedURI), - } + csrModifiers: []gen.CSRModifier{ + gen.SetCSRCommonName("test-common-name-" + e2eutil.RandStringRunes(10)), + gen.SetCSRURIs(sharedURI), }, kubeCSRUsages: []certificatesv1.KeyUsage{ certificatesv1.UsageDigitalSignature, @@ -206,28 +238,28 @@ func (s *Suite) Define() { requiredFeatures: []featureset.Feature{featureset.CommonNameFeature, featureset.URISANsFeature}, }, { - name: "should issue a certificate that defines a 2 distinct DNS Name with one copied to the Common Name", + name: "should issue a certificate that define 2 distinct DNS Names with one copied to the Common Name", keyAlgo: x509.RSA, csrModifiers: func() []gen.CSRModifier { + commonName := e2eutil.RandomSubdomain(s.DomainSuffix) + return []gen.CSRModifier{ - gen.SetCSRCommonName(sharedCommonName), - gen.SetCSRDNSNames(sharedCommonName, e2eutil.RandomSubdomain(s.DomainSuffix)), + gen.SetCSRCommonName(commonName), + gen.SetCSRDNSNames(commonName, e2eutil.RandomSubdomain(s.DomainSuffix)), } - }, + }(), kubeCSRUsages: []certificatesv1.KeyUsage{ certificatesv1.UsageDigitalSignature, certificatesv1.UsageKeyEncipherment, }, - requiredFeatures: []featureset.Feature{}, + requiredFeatures: []featureset.Feature{featureset.CommonNameFeature}, }, { name: "should issue a certificate that defines a distinct DNS Name and another distinct Common Name", keyAlgo: x509.RSA, - csrModifiers: func() []gen.CSRModifier { - return []gen.CSRModifier{ - gen.SetCSRCommonName(e2eutil.RandomSubdomain(s.DomainSuffix)), - gen.SetCSRDNSNames(e2eutil.RandomSubdomain(s.DomainSuffix)), - } + csrModifiers: []gen.CSRModifier{ + gen.SetCSRCommonName(e2eutil.RandomSubdomain(s.DomainSuffix)), + gen.SetCSRDNSNames(e2eutil.RandomSubdomain(s.DomainSuffix)), }, kubeCSRUsages: []certificatesv1.KeyUsage{ certificatesv1.UsageDigitalSignature, @@ -239,11 +271,13 @@ func (s *Suite) Define() { name: "should issue a certificate that defines a Common Name, DNS Name, and sets a duration", keyAlgo: x509.RSA, csrModifiers: func() []gen.CSRModifier { + commonName := e2eutil.RandomSubdomain(s.DomainSuffix) + return []gen.CSRModifier{ - gen.SetCSRDNSNames(sharedCommonName), - gen.SetCSRDNSNames(sharedCommonName), + gen.SetCSRCommonName(commonName), + gen.SetCSRDNSNames(commonName), } - }, + }(), kubeCSRUsages: []certificatesv1.KeyUsage{ certificatesv1.UsageDigitalSignature, certificatesv1.UsageKeyEncipherment, @@ -257,11 +291,13 @@ func (s *Suite) Define() { name: "should issue a certificate that defines a Common Name, DNS Name, and sets a duration via expiration seconds", keyAlgo: x509.RSA, csrModifiers: func() []gen.CSRModifier { + commonName := e2eutil.RandomSubdomain(s.DomainSuffix) + return []gen.CSRModifier{ - gen.SetCSRDNSNames(sharedCommonName), - gen.SetCSRDNSNames(sharedCommonName), + gen.SetCSRCommonName(commonName), + gen.SetCSRDNSNames(commonName), } - }, + }(), kubeCSRUsages: []certificatesv1.KeyUsage{ certificatesv1.UsageDigitalSignature, certificatesv1.UsageKeyEncipherment, @@ -272,10 +308,8 @@ func (s *Suite) Define() { { name: "should issue a certificate that defines a DNS Name and sets a duration", keyAlgo: x509.RSA, - csrModifiers: func() []gen.CSRModifier { - return []gen.CSRModifier{ - gen.SetCSRDNSNames(e2eutil.RandomSubdomain(s.DomainSuffix)), - } + csrModifiers: []gen.CSRModifier{ + gen.SetCSRDNSNames(e2eutil.RandomSubdomain(s.DomainSuffix)), }, kubeCSRUsages: []certificatesv1.KeyUsage{ certificatesv1.UsageDigitalSignature, @@ -289,10 +323,8 @@ func (s *Suite) Define() { { name: "should issue a certificate which has a wildcard DNS Name defined", keyAlgo: x509.RSA, - csrModifiers: func() []gen.CSRModifier { - return []gen.CSRModifier{ - gen.SetCSRDNSNames("*." + e2eutil.RandomSubdomain(s.DomainSuffix)), - } + csrModifiers: []gen.CSRModifier{ + gen.SetCSRDNSNames("*." + e2eutil.RandomSubdomain(s.DomainSuffix)), }, kubeCSRUsages: []certificatesv1.KeyUsage{ certificatesv1.UsageDigitalSignature, @@ -301,12 +333,26 @@ func (s *Suite) Define() { requiredFeatures: []featureset.Feature{featureset.WildcardsFeature, featureset.OnlySAN}, }, { - name: "should issue a certificate that includes only a URISANs name", + name: "should issue a certificate which has a wildcard DNS Name and its apex DNS Name defined", keyAlgo: x509.RSA, csrModifiers: func() []gen.CSRModifier { + dnsDomain := e2eutil.RandomSubdomain(s.DomainSuffix) + return []gen.CSRModifier{ - gen.SetCSRURIs(sharedURI), + gen.SetCSRDNSNames("*."+dnsDomain, dnsDomain), } + }(), + kubeCSRUsages: []certificatesv1.KeyUsage{ + certificatesv1.UsageDigitalSignature, + certificatesv1.UsageKeyEncipherment, + }, + requiredFeatures: []featureset.Feature{featureset.WildcardsFeature, featureset.OnlySAN}, + }, + { + name: "should issue a certificate that includes only a URISANs name", + keyAlgo: x509.RSA, + csrModifiers: []gen.CSRModifier{ + gen.SetCSRURIs(sharedURI), }, kubeCSRUsages: []certificatesv1.KeyUsage{ certificatesv1.UsageDigitalSignature, @@ -315,13 +361,10 @@ func (s *Suite) Define() { requiredFeatures: []featureset.Feature{featureset.URISANsFeature, featureset.OnlySAN}, }, { - name: "should issue a certificate that includes arbitrary key usages", + name: "should issue a certificate that includes arbitrary key usages with common name", keyAlgo: x509.RSA, - csrModifiers: func() []gen.CSRModifier { - return []gen.CSRModifier{ - gen.SetCSRCommonName(sharedCommonName), - gen.SetCSRDNSNames(sharedCommonName), - } + csrModifiers: []gen.CSRModifier{ + gen.SetCSRCommonName(e2eutil.RandomSubdomain(s.DomainSuffix)), }, kubeCSRUsages: []certificatesv1.KeyUsage{ certificatesv1.UsageServerAuth, @@ -329,21 +372,39 @@ func (s *Suite) Define() { certificatesv1.UsageDigitalSignature, certificatesv1.UsageDataEncipherment, }, + extraValidations: []certificatesigningrequests.ValidationFunc{ + certificatesigningrequests.ExpectKeyUsageExtKeyUsageClientAuth, + certificatesigningrequests.ExpectKeyUsageExtKeyUsageServerAuth, + certificatesigningrequests.ExpectKeyUsageUsageDigitalSignature, + certificatesigningrequests.ExpectKeyUsageUsageDataEncipherment, + }, requiredFeatures: []featureset.Feature{featureset.KeyUsagesFeature}, + }, + { + name: "should issue a certificate that includes arbitrary key usages with SAN only", + keyAlgo: x509.RSA, + csrModifiers: []gen.CSRModifier{ + gen.SetCSRDNSNames(e2eutil.RandomSubdomain(s.DomainSuffix)), + }, + kubeCSRUsages: []certificatesv1.KeyUsage{ + certificatesv1.UsageSigning, + certificatesv1.UsageDataEncipherment, + certificatesv1.UsageServerAuth, + certificatesv1.UsageClientAuth, + }, extraValidations: []certificatesigningrequests.ValidationFunc{ certificatesigningrequests.ExpectKeyUsageExtKeyUsageClientAuth, certificatesigningrequests.ExpectKeyUsageExtKeyUsageServerAuth, certificatesigningrequests.ExpectKeyUsageUsageDigitalSignature, certificatesigningrequests.ExpectKeyUsageUsageDataEncipherment, }, + requiredFeatures: []featureset.Feature{featureset.KeyUsagesFeature, featureset.OnlySAN}, }, { name: "should issue a signing CA certificate that has a large duration", keyAlgo: x509.RSA, - csrModifiers: func() []gen.CSRModifier { - return []gen.CSRModifier{ - gen.SetCSRCommonName("cert-manager-ca"), - } + csrModifiers: []gen.CSRModifier{ + gen.SetCSRCommonName("cert-manager-ca"), }, kubeCSRUsages: []certificatesv1.KeyUsage{ certificatesv1.UsageDigitalSignature, @@ -356,12 +417,27 @@ func (s *Suite) Define() { }, requiredFeatures: []featureset.Feature{featureset.KeyUsagesFeature, featureset.DurationFeature, featureset.CommonNameFeature}, }, + { + name: "should issue a certificate that defines a long domain", + keyAlgo: x509.RSA, + csrModifiers: func() []gen.CSRModifier { + const maxLengthOfDomainSegment = 63 + return []gen.CSRModifier{ + gen.SetCSRDNSNames(e2eutil.RandomSubdomainLength(s.DomainSuffix, maxLengthOfDomainSegment)), + } + }(), + kubeCSRUsages: []certificatesv1.KeyUsage{ + certificatesv1.UsageDigitalSignature, + certificatesv1.UsageKeyEncipherment, + }, + requiredFeatures: []featureset.Feature{featureset.OnlySAN, featureset.LongDomainFeatureSet}, + }, } defineTest := func(test testCase) { s.it(f, test.name, func(ctx context.Context, signerName string) { // Generate request CSR - csr, key, err := gen.CSR(test.keyAlgo, test.csrModifiers()...) + csr, key, err := gen.CSR(test.keyAlgo, test.csrModifiers...) Expect(err).NotTo(HaveOccurred()) // Create CertificateSigningRequest From 8d5f0dcf836ee653c48f2e6c2da50c1b60d5b323 Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Fri, 9 Jun 2023 11:26:15 +0200 Subject: [PATCH 03/13] cleanup validator functions Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- conformance/framework/helper/validate.go | 7 +- .../validation/certificates/certificates.go | 29 ++++-- .../certificatesigningrequests.go | 20 +++-- .../framework/helper/validation/validation.go | 90 +++++++++---------- 4 files changed, 84 insertions(+), 62 deletions(-) diff --git a/conformance/framework/helper/validate.go b/conformance/framework/helper/validate.go index ae50600..6f7944e 100644 --- a/conformance/framework/helper/validate.go +++ b/conformance/framework/helper/validate.go @@ -19,11 +19,11 @@ package helper import ( "context" "crypto" + "fmt" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" - "github.com/cert-manager/issuer-lib/conformance/framework/helper/validation" "github.com/cert-manager/issuer-lib/conformance/framework/helper/validation/certificates" "github.com/cert-manager/issuer-lib/conformance/framework/helper/validation/certificatesigningrequests" ) @@ -31,7 +31,7 @@ import ( // ValidateCertificate retrieves the issued certificate and runs all validation functions func (h *Helper) ValidateCertificate(certificate *cmapi.Certificate, validations ...certificates.ValidationFunc) error { if len(validations) == 0 { - validations = validation.DefaultCertificateSet() + return fmt.Errorf("no validation functions provided") } secret, err := h.KubeClient.CoreV1().Secrets(certificate.Namespace).Get(context.TODO(), certificate.Spec.SecretName, metav1.GetOptions{}) @@ -52,8 +52,9 @@ func (h *Helper) ValidateCertificate(certificate *cmapi.Certificate, validations // ValidateCertificateSigningRequest retrieves the issued certificate and runs all validation functions func (h *Helper) ValidateCertificateSigningRequest(name string, key crypto.Signer, validations ...certificatesigningrequests.ValidationFunc) error { if len(validations) == 0 { - validations = validation.DefaultCertificateSigningRequestSet() + return fmt.Errorf("no validation functions provided") } + csr, err := h.KubeClient.CertificatesV1().CertificateSigningRequests().Get(context.TODO(), name, metav1.GetOptions{}) if err != nil { return err diff --git a/conformance/framework/helper/validation/certificates/certificates.go b/conformance/framework/helper/validation/certificates/certificates.go index 18ff91f..f85d8dd 100644 --- a/conformance/framework/helper/validation/certificates/certificates.go +++ b/conformance/framework/helper/validation/certificates/certificates.go @@ -170,7 +170,24 @@ func ExpectCertificateURIsToMatch(certificate *cmapi.Certificate, secret *corev1 actualURIs := pki.URLsToString(cert.URIs) expectedURIs := pki.URLsToString(uris) if !util.EqualUnsorted(actualURIs, expectedURIs) { - return fmt.Errorf("Expected certificate valid for URIs %v, but got a certificate valid for URIs %v", expectedURIs, pki.URLsToString(cert.URIs)) + return fmt.Errorf("Expected certificate valid for URIs %v, but got a certificate valid for URIs %v", expectedURIs, actualURIs) + } + + return nil +} + +// ExpectCertificateIPsToMatch checks if the issued certificate has all IP SANs names it requested +func ExpectCertificateIPsToMatch(certificate *cmapi.Certificate, secret *corev1.Secret) error { + cert, err := pki.DecodeX509CertificateBytes(secret.Data[corev1.TLSCertKey]) + if err != nil { + return err + } + + ips := pki.IPAddressesForCertificate(certificate) + actualIPs := pki.IPAddressesToString(cert.IPAddresses) + expectedIPs := pki.IPAddressesToString(ips) + if !util.EqualUnsorted(actualIPs, expectedIPs) { + return fmt.Errorf("Expected certificate valid for IPs %v, but got a certificate valid for IPs %v", expectedIPs, actualIPs) } return nil @@ -230,14 +247,16 @@ func ExpectDurationToMatch(certificate *cmapi.Certificate, secret *corev1.Secret return err } - duration := apiutil.DefaultCertDuration(certificate.Spec.Duration) + expectedDuration := apiutil.DefaultCertDuration(certificate.Spec.Duration) + actualDuration := cert.NotAfter.Sub(cert.NotBefore) fuzz := 30 * time.Second - certDuration := cert.NotAfter.Sub(cert.NotBefore) - if certDuration > (duration+fuzz) || certDuration < (duration-fuzz) { + // Here we ensure that the requested duration is what is signed on the + // certificate. We tolerate a 30 second fuzz either way. + if actualDuration > (expectedDuration+fuzz) || actualDuration < (expectedDuration-fuzz) { return fmt.Errorf( "Expected duration of %s, got %s (fuzz: %s) [NotBefore: %s, NotAfter: %s]", - duration, certDuration, fuzz, + expectedDuration, actualDuration, fuzz, cert.NotBefore.Format(time.RFC3339), cert.NotAfter.Format(time.RFC3339), ) } diff --git a/conformance/framework/helper/validation/certificatesigningrequests/certificatesigningrequests.go b/conformance/framework/helper/validation/certificatesigningrequests/certificatesigningrequests.go index 84cc524..f13eda0 100644 --- a/conformance/framework/helper/validation/certificatesigningrequests/certificatesigningrequests.go +++ b/conformance/framework/helper/validation/certificatesigningrequests/certificatesigningrequests.go @@ -184,8 +184,8 @@ func ExpectValidCommonName(csr *certificatesv1.CertificateSigningRequest, _ cryp return nil } -// ExpectValidDuration checks if the issued certificate matches the requested duration -func ExpectValidDuration(csr *certificatesv1.CertificateSigningRequest, _ crypto.Signer) error { +// ExpectDurationToMatch checks if the issued certificate matches the requested duration +func ExpectDurationToMatch(csr *certificatesv1.CertificateSigningRequest, _ crypto.Signer) error { cert, err := pki.DecodeX509CertificateBytes(csr.Status.Certificate) if err != nil { return err @@ -206,13 +206,17 @@ func ExpectValidDuration(csr *certificatesv1.CertificateSigningRequest, _ crypto return err } } - actualDuration := cert.NotAfter.Sub(cert.NotBefore) + fuzz := 30 * time.Second // Here we ensure that the requested duration is what is signed on the // certificate. We tolerate a 30 second fuzz either way. - if actualDuration > expectedDuration+time.Second*30 || actualDuration < expectedDuration-time.Second*30 { - return fmt.Errorf("Expected certificate expiry date to be %v, but got %v", expectedDuration, actualDuration) + if actualDuration > (expectedDuration+fuzz) || actualDuration < (expectedDuration-fuzz) { + return fmt.Errorf( + "Expected duration of %s, got %s (fuzz: %s) [NotBefore: %s, NotAfter: %s]", + expectedDuration, actualDuration, fuzz, + cert.NotBefore.Format(time.RFC3339), cert.NotAfter.Format(time.RFC3339), + ) } return nil @@ -313,8 +317,8 @@ func ExpectEmailsToMatch(csr *certificatesv1.CertificateSigningRequest, _ crypto return nil } -// ExpectIsCA checks the certificate is a CA if requested -func ExpectIsCA(csr *certificatesv1.CertificateSigningRequest, _ crypto.Signer) error { +// ExpectValidBasicConstraints checks the certificate is a CA if requested +func ExpectValidBasicConstraints(csr *certificatesv1.CertificateSigningRequest, _ crypto.Signer) error { cert, err := pki.DecodeX509CertificateBytes(csr.Status.Certificate) if err != nil { return err @@ -335,6 +339,8 @@ func ExpectIsCA(csr *certificatesv1.CertificateSigningRequest, _ crypto.Signer) return fmt.Errorf("Expected certificate to have KeyUsageCertSign=%t, but got=%t", markedIsCA, hasCertSign) } + // TODO: also validate pathLen + return nil } diff --git a/conformance/framework/helper/validation/validation.go b/conformance/framework/helper/validation/validation.go index 704bbb9..3d680a4 100644 --- a/conformance/framework/helper/validation/validation.go +++ b/conformance/framework/helper/validation/validation.go @@ -22,59 +22,21 @@ import ( "github.com/cert-manager/issuer-lib/conformance/framework/helper/validation/certificatesigningrequests" ) -func DefaultCertificateSet() []certificates.ValidationFunc { - return []certificates.ValidationFunc{ - certificates.ExpectValidKeysInSecret, - certificates.ExpectCertificateDNSNamesToMatch, - certificates.ExpectCertificateOrganizationToMatch, - certificates.ExpectCertificateURIsToMatch, - certificates.ExpectCorrectTrustChain, - certificates.ExpectCARootCertificate, - certificates.ExpectEmailsToMatch, - certificates.ExpectValidAnnotations, - certificates.ExpectValidCertificate, - certificates.ExpectValidCommonName, - certificates.ExpectValidNotAfterDate, - certificates.ExpectDurationToMatch, - certificates.ExpectValidPrivateKeyData, - certificates.ExpectConditionReadyObservedGeneration, - certificates.ExpectValidBasicConstraints, - certificates.ExpectValidAdditionalOutputFormats, - } -} - -func DefaultCertificateSigningRequestSet() []certificatesigningrequests.ValidationFunc { - return []certificatesigningrequests.ValidationFunc{ - certificatesigningrequests.ExpectValidCertificate, - certificatesigningrequests.ExpectCertificateOrganizationToMatch, - certificatesigningrequests.ExpectValidPrivateKeyData, - certificatesigningrequests.ExpectCertificateDNSNamesToMatch, - certificatesigningrequests.ExpectCertificateURIsToMatch, - certificatesigningrequests.ExpectCertificateIPsToMatch, - certificatesigningrequests.ExpectValidCommonName, - certificatesigningrequests.ExpectKeyUsageUsageDigitalSignature, - certificatesigningrequests.ExpectEmailsToMatch, - certificatesigningrequests.ExpectIsCA, - certificatesigningrequests.ExpectConditionApproved, - certificatesigningrequests.ExpectConditiotNotDenied, - certificatesigningrequests.ExpectConditionNotFailed, - } -} - func CertificateSetForUnsupportedFeatureSet(fs featureset.FeatureSet) []certificates.ValidationFunc { // basics out := []certificates.ValidationFunc{ - certificates.ExpectValidKeysInSecret, certificates.ExpectCertificateDNSNamesToMatch, certificates.ExpectCertificateOrganizationToMatch, - certificates.ExpectValidAnnotations, certificates.ExpectValidCertificate, + certificates.ExpectValidPrivateKeyData, certificates.ExpectValidCommonName, + certificates.ExpectValidBasicConstraints, + certificates.ExpectValidNotAfterDate, - certificates.ExpectDurationToMatch, - certificates.ExpectValidPrivateKeyData, + certificates.ExpectValidKeysInSecret, + certificates.ExpectValidAnnotations, + certificates.ExpectConditionReadyObservedGeneration, - certificates.ExpectValidBasicConstraints, } if !fs.Contains(featureset.URISANsFeature) { @@ -85,6 +47,10 @@ func CertificateSetForUnsupportedFeatureSet(fs featureset.FeatureSet) []certific out = append(out, certificates.ExpectEmailsToMatch) } + if !fs.Contains(featureset.IPAddressFeature) { + out = append(out, certificates.ExpectCertificateIPsToMatch) + } + if !fs.Contains(featureset.SaveCAToSecret) { out = append(out, certificates.ExpectCorrectTrustChain) @@ -93,15 +59,45 @@ func CertificateSetForUnsupportedFeatureSet(fs featureset.FeatureSet) []certific } } + if !fs.Contains(featureset.DurationFeature) { + out = append(out, certificates.ExpectDurationToMatch) + } + return out } func CertificateSigningRequestSetForUnsupportedFeatureSet(fs featureset.FeatureSet) []certificatesigningrequests.ValidationFunc { - validations := DefaultCertificateSigningRequestSet() + // basics + out := []certificatesigningrequests.ValidationFunc{ + certificatesigningrequests.ExpectCertificateDNSNamesToMatch, + certificatesigningrequests.ExpectCertificateOrganizationToMatch, + certificatesigningrequests.ExpectValidCertificate, + certificatesigningrequests.ExpectValidPrivateKeyData, + certificatesigningrequests.ExpectValidCommonName, + certificatesigningrequests.ExpectValidBasicConstraints, + + certificatesigningrequests.ExpectKeyUsageUsageDigitalSignature, + + certificatesigningrequests.ExpectConditionApproved, + certificatesigningrequests.ExpectConditiotNotDenied, + certificatesigningrequests.ExpectConditionNotFailed, + } + + if !fs.Contains(featureset.URISANsFeature) { + out = append(out, certificatesigningrequests.ExpectCertificateURIsToMatch) + } + + if !fs.Contains(featureset.EmailSANsFeature) { + out = append(out, certificatesigningrequests.ExpectEmailsToMatch) + } + + if !fs.Contains(featureset.IPAddressFeature) { + out = append(out, certificatesigningrequests.ExpectCertificateIPsToMatch) + } if !fs.Contains(featureset.DurationFeature) { - validations = append(validations, certificatesigningrequests.ExpectValidDuration) + out = append(out, certificatesigningrequests.ExpectDurationToMatch) } - return validations + return out } From 440bc0dd94201ff0c642ba6b0106b6460456f772 Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Fri, 9 Jun 2023 12:54:43 +0200 Subject: [PATCH 04/13] resolve linter errors Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- conformance/certificates/suite.go | 15 +- conformance/certificates/tests.go | 36 +- .../certificatesigningrequests/suite.go | 13 +- .../certificatesigningrequests/tests.go | 21 +- conformance/framework/framework.go | 19 +- .../framework/helper/certificaterequests.go | 223 --------- conformance/framework/helper/certificates.go | 256 +---------- .../helper/certificatesigningrequests.go | 8 +- .../framework/helper/featureset/featureset.go | 16 +- conformance/framework/helper/helper.go | 3 +- conformance/framework/helper/secret.go | 6 +- conformance/framework/helper/validate.go | 10 +- .../validation/certificates/certificates.go | 5 +- .../certificatesigningrequests.go | 3 +- conformance/framework/log/log.go | 6 +- conformance/framework/util.go | 3 +- conformance/rbac/certificate.go | 4 +- conformance/rbac/certificaterequest.go | 4 +- conformance/rbac/doc.go | 13 +- conformance/rbac/issuer.go | 4 +- conformance/util/{domains.go => random.go} | 0 conformance/util/util.go | 427 ------------------ .../simple/e2e/conformance/conformance.go | 1 + .../simple/e2e/conformance/issuer_builder.go | 10 +- 24 files changed, 103 insertions(+), 1003 deletions(-) delete mode 100644 conformance/framework/helper/certificaterequests.go rename conformance/util/{domains.go => random.go} (100%) delete mode 100644 conformance/util/util.go diff --git a/conformance/certificates/suite.go b/conformance/certificates/suite.go index d6d0680..4e74847 100644 --- a/conformance/certificates/suite.go +++ b/conformance/certificates/suite.go @@ -19,12 +19,13 @@ package certificates import ( "context" - . "github.com/onsi/ginkgo/v2" + cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" "k8s.io/client-go/rest" - cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" "github.com/cert-manager/issuer-lib/conformance/framework" "github.com/cert-manager/issuer-lib/conformance/framework/helper/featureset" + + . "github.com/onsi/ginkgo/v2" ) // Suite defines a reusable conformance test suite that can be used against any @@ -113,15 +114,11 @@ func (s *Suite) it(f *framework.Framework, name string, fn func(cmmeta.ObjectRef // It will return 'true' if all features are supported and the test should run, // or return 'false' if any required feature is not supported. func (s *Suite) checkFeatures(fs ...featureset.Feature) bool { - unsupported := make(featureset.FeatureSet) for _, f := range fs { if s.UnsupportedFeatures.Contains(f) { - unsupported.Add(f) + return false } } - // all features supported, return early! - if len(unsupported) == 0 { - return true - } - return false + + return true } diff --git a/conformance/certificates/tests.go b/conformance/certificates/tests.go index 59f070a..084f53d 100644 --- a/conformance/certificates/tests.go +++ b/conformance/certificates/tests.go @@ -25,22 +25,23 @@ import ( "reflect" "time" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" + cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" + "github.com/cert-manager/cert-manager/pkg/util/pki" + "github.com/cert-manager/cert-manager/test/unit/gen" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/util/retry" - cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" - cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" - "github.com/cert-manager/cert-manager/pkg/util/pki" - "github.com/cert-manager/cert-manager/test/unit/gen" "github.com/cert-manager/issuer-lib/conformance/framework" "github.com/cert-manager/issuer-lib/conformance/framework/helper/featureset" "github.com/cert-manager/issuer-lib/conformance/framework/helper/validation" "github.com/cert-manager/issuer-lib/conformance/framework/helper/validation/certificates" e2eutil "github.com/cert-manager/issuer-lib/conformance/util" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" ) // Define defines simple conformance tests that can be run against any issuer type. @@ -392,12 +393,12 @@ func (s *Suite) Define() { Expect(err).NotTo(HaveOccurred()) By("Waiting for the Certificate to be issued...") - certificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(certificate, time.Minute*8) + certificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(ctx, certificate, time.Minute*8) Expect(err).NotTo(HaveOccurred()) By("Validating the issued Certificate...") validations := append(test.extraValidations, validation.CertificateSetForUnsupportedFeatureSet(s.UnsupportedFeatures)...) - err = f.Helper().ValidateCertificate(certificate, validations...) + err = f.Helper().ValidateCertificate(ctx, certificate, validations...) Expect(err).NotTo(HaveOccurred()) }, test.requiredFeatures...) } @@ -423,11 +424,11 @@ func (s *Suite) Define() { Expect(err).NotTo(HaveOccurred()) By("Waiting for the Certificate to be issued...") - testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testCertificate, time.Minute*8) + testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(ctx, testCertificate, time.Minute*8) Expect(err).NotTo(HaveOccurred()) By("Validating the issued Certificate...") - err = f.Helper().ValidateCertificate(testCertificate, validation.CertificateSetForUnsupportedFeatureSet(s.UnsupportedFeatures)...) + err = f.Helper().ValidateCertificate(ctx, testCertificate, validation.CertificateSetForUnsupportedFeatureSet(s.UnsupportedFeatures)...) Expect(err).NotTo(HaveOccurred()) By("Deleting existing certificate data in Secret") @@ -446,7 +447,7 @@ func (s *Suite) Define() { Expect(err).NotTo(HaveOccurred(), "failed to update secret by deleting the signed certificate data") By("Waiting for the Certificate to re-issue a certificate") - sec, err = f.Helper().WaitForSecretCertificateData(f.Namespace.Name, sec.Name, time.Minute*8) + sec, err = f.Helper().WaitForSecretCertificateData(ctx, f.Namespace.Name, sec.Name, time.Minute*8) Expect(err).NotTo(HaveOccurred(), "failed to wait for secret to have a valid 2nd certificate") crtPEM2 := sec.Data[corev1.TLSCertKey] @@ -481,17 +482,17 @@ func (s *Suite) Define() { Expect(err).NotTo(HaveOccurred()) By("Waiting for the Certificate to be ready") - testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testCertificate, time.Minute*8) + testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(ctx, testCertificate, time.Minute*8) Expect(err).NotTo(HaveOccurred()) By("Sanity-check the issued Certificate") - err = f.Helper().ValidateCertificate(testCertificate, validations...) + err = f.Helper().ValidateCertificate(ctx, testCertificate, validations...) Expect(err).NotTo(HaveOccurred()) By("Updating the Certificate after having added an additional dnsName") newDNSName := e2eutil.RandomSubdomain(s.DomainSuffix) - retry.RetryOnConflict(retry.DefaultRetry, func() error { - err = f.CRClient.Get(context.Background(), types.NamespacedName{Name: testCertificate.Name, Namespace: testCertificate.Namespace}, testCertificate) + err = retry.RetryOnConflict(retry.DefaultRetry, func() error { + err := f.CRClient.Get(context.Background(), types.NamespacedName{Name: testCertificate.Name, Namespace: testCertificate.Namespace}, testCertificate) if err != nil { return err } @@ -503,15 +504,14 @@ func (s *Suite) Define() { } return nil }) - Expect(err).NotTo(HaveOccurred()) By("Waiting for the Certificate Ready condition to be updated") - testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testCertificate, time.Minute*8) + testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(ctx, testCertificate, time.Minute*8) Expect(err).NotTo(HaveOccurred()) By("Sanity-check the issued Certificate") - err = f.Helper().ValidateCertificate(testCertificate, validations...) + err = f.Helper().ValidateCertificate(ctx, testCertificate, validations...) Expect(err).NotTo(HaveOccurred()) }, featureset.OnlySAN) }) diff --git a/conformance/certificatesigningrequests/suite.go b/conformance/certificatesigningrequests/suite.go index 81ed005..f03e4a6 100644 --- a/conformance/certificatesigningrequests/suite.go +++ b/conformance/certificatesigningrequests/suite.go @@ -20,12 +20,13 @@ import ( "context" "crypto" - . "github.com/onsi/ginkgo/v2" certificatesv1 "k8s.io/api/certificates/v1" "k8s.io/client-go/rest" "github.com/cert-manager/issuer-lib/conformance/framework" "github.com/cert-manager/issuer-lib/conformance/framework/helper/featureset" + + . "github.com/onsi/ginkgo/v2" ) // Suite defines a reusable conformance test suite that can be used against any @@ -128,15 +129,11 @@ func (s *Suite) it(f *framework.Framework, name string, fn func(context.Context, // It will return 'true' if all features are supported and the test should run, // or return 'false' if any required feature is not supported. func (s *Suite) checkFeatures(fs ...featureset.Feature) bool { - unsupported := make(featureset.FeatureSet) for _, f := range fs { if s.UnsupportedFeatures.Contains(f) { - unsupported.Add(f) + return false } } - // all features supported, return early! - if len(unsupported) == 0 { - return true - } - return false + + return true } diff --git a/conformance/certificatesigningrequests/tests.go b/conformance/certificatesigningrequests/tests.go index ad329c2..d55357b 100644 --- a/conformance/certificatesigningrequests/tests.go +++ b/conformance/certificatesigningrequests/tests.go @@ -23,20 +23,21 @@ import ( "net/url" "time" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" + experimentalapi "github.com/cert-manager/cert-manager/pkg/apis/experimental/v1alpha1" + "github.com/cert-manager/cert-manager/test/unit/gen" certificatesv1 "k8s.io/api/certificates/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/utils/pointer" - experimentalapi "github.com/cert-manager/cert-manager/pkg/apis/experimental/v1alpha1" - "github.com/cert-manager/cert-manager/test/unit/gen" "github.com/cert-manager/issuer-lib/conformance/framework" "github.com/cert-manager/issuer-lib/conformance/framework/helper/featureset" "github.com/cert-manager/issuer-lib/conformance/framework/helper/validation" "github.com/cert-manager/issuer-lib/conformance/framework/helper/validation/certificatesigningrequests" e2eutil "github.com/cert-manager/issuer-lib/conformance/util" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" ) // Defines simple conformance tests that can be run against any issuer type. @@ -467,7 +468,13 @@ func (s *Suite) Define() { // Create the request, and delete at the end of the test By("Creating a CertificateSigningRequest") Expect(f.CRClient.Create(ctx, kubeCSR)).NotTo(HaveOccurred()) - defer f.CRClient.Delete(context.TODO(), kubeCSR) + defer func() { + // Create a new context with a timeout to prevent the deletion of the + // CertificateSigningRequest from blocking test completion. + deleteCtx, cancel := context.WithTimeout(context.Background(), time.Second*30) + defer cancel() + Expect(f.CRClient.Delete(deleteCtx, kubeCSR)).NotTo(HaveOccurred()) + }() // Approve the request for testing, so that cert-manager may sign the // request. @@ -484,14 +491,14 @@ func (s *Suite) Define() { // Wait for the status.Certificate and CA annotation to be populated in // a reasonable amount of time. By("Waiting for the CertificateSigningRequest to be issued...") - kubeCSR, err = f.Helper().WaitForCertificateSigningRequestSigned(kubeCSR.Name, time.Minute*5) + kubeCSR, err = f.Helper().WaitForCertificateSigningRequestSigned(ctx, kubeCSR.Name, time.Minute*5) Expect(err).NotTo(HaveOccurred()) // Validate that the request was signed as expected. Add extra // validations which may be required for this test. By("Validating the issued CertificateSigningRequest...") validations := append(test.extraValidations, validation.CertificateSigningRequestSetForUnsupportedFeatureSet(s.UnsupportedFeatures)...) - err = f.Helper().ValidateCertificateSigningRequest(kubeCSR.Name, key, validations...) + err = f.Helper().ValidateCertificateSigningRequest(ctx, kubeCSR.Name, key, validations...) Expect(err).NotTo(HaveOccurred()) }, test.requiredFeatures...) } diff --git a/conformance/framework/framework.go b/conformance/framework/framework.go index 436047b..bc7b12d 100644 --- a/conformance/framework/framework.go +++ b/conformance/framework/framework.go @@ -17,13 +17,13 @@ limitations under the License. package framework import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - + clientset "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned" + certmgrscheme "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned/scheme" api "k8s.io/api/core/v1" apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" apiextcs "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" "k8s.io/apimachinery/pkg/runtime" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/client-go/kubernetes" kscheme "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" @@ -31,9 +31,10 @@ import ( crclient "sigs.k8s.io/controller-runtime/pkg/client" gwapi "sigs.k8s.io/gateway-api/pkg/client/clientset/versioned" - clientset "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned" - certmgrscheme "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned/scheme" "github.com/cert-manager/issuer-lib/conformance/framework/helper" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" ) // TODO: not all this code is required to be externally accessible. Separate the @@ -43,10 +44,10 @@ import ( var Scheme = runtime.NewScheme() func init() { - kscheme.AddToScheme(Scheme) - certmgrscheme.AddToScheme(Scheme) - apiext.AddToScheme(Scheme) - apireg.AddToScheme(Scheme) + utilruntime.Must(kscheme.AddToScheme(Scheme)) + utilruntime.Must(certmgrscheme.AddToScheme(Scheme)) + utilruntime.Must(apiext.AddToScheme(Scheme)) + utilruntime.Must(apireg.AddToScheme(Scheme)) } // Framework supports common operations used by e2e tests; it will keep a client & a namespace for you. diff --git a/conformance/framework/helper/certificaterequests.go b/conformance/framework/helper/certificaterequests.go deleted file mode 100644 index 2ae2397..0000000 --- a/conformance/framework/helper/certificaterequests.go +++ /dev/null @@ -1,223 +0,0 @@ -/* -Copyright 2020 The cert-manager Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package helper - -import ( - "context" - "crypto" - "crypto/ecdsa" - "crypto/ed25519" - "crypto/rsa" - "crypto/x509" - "fmt" - "time" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/wait" - - apiutil "github.com/cert-manager/cert-manager/pkg/api/util" - cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" - cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" - "github.com/cert-manager/cert-manager/pkg/util" - "github.com/cert-manager/cert-manager/pkg/util/pki" - "github.com/cert-manager/issuer-lib/conformance/framework/log" -) - -// WaitForCertificateRequestReady waits for the CertificateRequest resource to -// enter a Ready state. -func (h *Helper) WaitForCertificateRequestReady(ns, name string, timeout time.Duration) (*cmapi.CertificateRequest, error) { - var cr *cmapi.CertificateRequest - logf, done := log.LogBackoff() - defer done() - err := wait.PollUntilContextTimeout(context.TODO(), time.Second, timeout, true, func(ctx context.Context) (bool, error) { - var err error - logf("Waiting for CertificateRequest %s to be ready", name) - cr, err = h.CMClient.CertmanagerV1().CertificateRequests(ns).Get(context.TODO(), name, metav1.GetOptions{}) - if err != nil { - return false, fmt.Errorf("error getting CertificateRequest %s: %v", name, err) - } - isReady := apiutil.CertificateRequestHasCondition(cr, cmapi.CertificateRequestCondition{ - Type: cmapi.CertificateRequestConditionReady, - Status: cmmeta.ConditionTrue, - }) - if !isReady { - logf("Expected CertificateRequest to have Ready condition 'true' but it has: %v", cr.Status.Conditions) - return false, nil - } - return true, nil - }) - - if err != nil { - return nil, err - } - - return cr, nil -} - -// ValidateIssuedCertificateRequest will ensure that the given -// CertificateRequest has a certificate issued for it, and that the details on -// the x509 certificate are correct as defined by the CertificateRequest's -// spec. -func (h *Helper) ValidateIssuedCertificateRequest(cr *cmapi.CertificateRequest, key crypto.Signer, rootCAPEM []byte) (*x509.Certificate, error) { - csr, err := pki.DecodeX509CertificateRequestBytes(cr.Spec.Request) - if err != nil { - return nil, fmt.Errorf("failed to decode CertificateRequest's Spec.Request: %s", err) - } - - // validate private key is of the correct type (rsa or ecdsa) - switch csr.PublicKeyAlgorithm { - case x509.RSA: - _, ok := key.(*rsa.PrivateKey) - if !ok { - return nil, fmt.Errorf("Expected private key of type RSA, but it was: %T", key) - } - case x509.ECDSA: - _, ok := key.(*ecdsa.PrivateKey) - if !ok { - return nil, fmt.Errorf("Expected private key of type ECDSA, but it was: %T", key) - } - case x509.Ed25519: - _, ok := key.(ed25519.PrivateKey) - if !ok { - return nil, fmt.Errorf("Expected private key of type Ed25519, but it was: %T", key) - } - default: - return nil, fmt.Errorf("unrecognised requested private key algorithm %q", csr.PublicKeyAlgorithm) - } - - // TODO: validate private key KeySize - - // check the provided certificate is valid - expectedOrganization := csr.Subject.Organization - expectedDNSNames := csr.DNSNames - expectedIPAddresses := csr.IPAddresses - expectedURIs := csr.URIs - - cert, err := pki.DecodeX509CertificateBytes(cr.Status.Certificate) - if err != nil { - return nil, err - } - - commonNameCorrect := true - expectedCN := csr.Subject.CommonName - if len(expectedCN) == 0 && len(cert.Subject.CommonName) > 0 { - if !util.Contains(cert.DNSNames, cert.Subject.CommonName) { - commonNameCorrect = false - } - } else if expectedCN != cert.Subject.CommonName { - commonNameCorrect = false - } - - if !commonNameCorrect || - !util.EqualUnsorted(cert.DNSNames, expectedDNSNames) || - !util.EqualUnsorted(cert.Subject.Organization, expectedOrganization) || - !util.EqualIPsUnsorted(cert.IPAddresses, expectedIPAddresses) || - !util.EqualURLsUnsorted(cert.URIs, expectedURIs) { - return nil, fmt.Errorf("Expected certificate valid for CN %q, O %v, dnsNames %v, IPs %v, URIs %v but got a certificate valid for CN %q, O %v, dnsNames %v, IPs %v URIs %v", - expectedCN, expectedOrganization, expectedDNSNames, expectedIPAddresses, expectedURIs, - cert.Subject.CommonName, cert.Subject.Organization, cert.DNSNames, cert.IPAddresses, cert.URIs) - } - - var expectedDNSName string - if len(expectedDNSNames) > 0 { - expectedDNSName = expectedDNSNames[0] - } - - certificateKeyUsages, certificateExtKeyUsages, err := pki.KeyUsagesForCertificateOrCertificateRequest(cr.Spec.Usages, cr.Spec.IsCA) - if err != nil { - return nil, fmt.Errorf("failed to build key usages from certificate: %s", err) - } - - var keyAlg cmapi.PrivateKeyAlgorithm - switch csr.PublicKeyAlgorithm { - case x509.RSA: - keyAlg = cmapi.RSAKeyAlgorithm - case x509.ECDSA: - keyAlg = cmapi.ECDSAKeyAlgorithm - case x509.Ed25519: - keyAlg = cmapi.Ed25519KeyAlgorithm - default: - return nil, fmt.Errorf("unsupported key algorithm type: %s", csr.PublicKeyAlgorithm) - } - - defaultCertKeyUsages, defaultCertExtKeyUsages, err := h.defaultKeyUsagesToAdd(cr.Namespace, &cr.Spec.IssuerRef) - if err != nil { - return nil, err - } - - certificateKeyUsages |= defaultCertKeyUsages - certificateExtKeyUsages = append(certificateExtKeyUsages, defaultCertExtKeyUsages...) - - certificateExtKeyUsages = h.deduplicateExtKeyUsages(certificateExtKeyUsages) - - // If using ECDSA then ignore key encipherment - if keyAlg == cmapi.ECDSAKeyAlgorithm { - certificateKeyUsages &^= x509.KeyUsageKeyEncipherment - cert.KeyUsage &^= x509.KeyUsageKeyEncipherment - } - - if !h.keyUsagesMatch(cert.KeyUsage, cert.ExtKeyUsage, - certificateKeyUsages, certificateExtKeyUsages) { - return nil, fmt.Errorf("key usages and extended key usages do not match: exp=%s got=%s exp=%s got=%s", - apiutil.KeyUsageStrings(certificateKeyUsages), apiutil.KeyUsageStrings(cert.KeyUsage), - apiutil.ExtKeyUsageStrings(certificateExtKeyUsages), apiutil.ExtKeyUsageStrings(cert.ExtKeyUsage)) - } - - // TODO: move this verification step out of this function - if rootCAPEM != nil { - rootCertPool := x509.NewCertPool() - rootCertPool.AppendCertsFromPEM(rootCAPEM) - intermediateCertPool := x509.NewCertPool() - intermediateCertPool.AppendCertsFromPEM(cr.Status.CA) - opts := x509.VerifyOptions{ - DNSName: expectedDNSName, - Intermediates: intermediateCertPool, - Roots: rootCertPool, - } - - if _, err := cert.Verify(opts); err != nil { - return nil, err - } - } - - if !apiutil.CertificateRequestIsApproved(cr) { - return nil, fmt.Errorf("CertificateRequest does not have an Approved condition set to True: %+v", cr.Status.Conditions) - } - if apiutil.CertificateRequestIsDenied(cr) { - return nil, fmt.Errorf("CertificateRequest has a Denied conditon set to True: %+v", cr.Status.Conditions) - } - - return cert, nil -} - -func (h *Helper) WaitCertificateRequestIssuedValid(ns, name string, timeout time.Duration, key crypto.Signer) error { - return h.WaitCertificateRequestIssuedValidTLS(ns, name, timeout, key, nil) -} - -func (h *Helper) WaitCertificateRequestIssuedValidTLS(ns, name string, timeout time.Duration, key crypto.Signer, rootCAPEM []byte) error { - cr, err := h.WaitForCertificateRequestReady(ns, name, timeout) - if err != nil { - return err - } - - _, err = h.ValidateIssuedCertificateRequest(cr, key, rootCAPEM) - if err != nil { - return err - } - - return nil -} diff --git a/conformance/framework/helper/certificates.go b/conformance/framework/helper/certificates.go index 420694f..fff9d2e 100644 --- a/conformance/framework/helper/certificates.go +++ b/conformance/framework/helper/certificates.go @@ -18,50 +18,24 @@ package helper import ( "context" - "crypto/x509" "fmt" - "sort" "time" - errors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/wait" - apiutil "github.com/cert-manager/cert-manager/pkg/api/util" v1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" clientset "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned/typed/certmanager/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/wait" + "github.com/cert-manager/issuer-lib/conformance/framework/log" ) -// WaitForCertificateToExist waits for the named certificate to exist and returns the certificate -func (h *Helper) WaitForCertificateToExist(namespace string, name string, timeout time.Duration) (*v1.Certificate, error) { - client := h.CMClient.CertmanagerV1().Certificates(namespace) - var certificate *v1.Certificate - logf, done := log.LogBackoff() - defer done() - - pollErr := wait.PollUntilContextTimeout(context.TODO(), 500*time.Millisecond, timeout, true, func(ctx context.Context) (bool, error) { - logf("Waiting for Certificate %v to exist", name) - var err error - certificate, err = client.Get(context.TODO(), name, metav1.GetOptions{}) - if errors.IsNotFound(err) { - return false, nil - } - if err != nil { - return false, fmt.Errorf("error getting Certificate %v: %v", name, err) - } - - return true, nil - }) - return certificate, pollErr -} - -func (h *Helper) waitForCertificateCondition(client clientset.CertificateInterface, name string, check func(*v1.Certificate) bool, timeout time.Duration) (*v1.Certificate, error) { +func (h *Helper) waitForCertificateCondition(pollCtx context.Context, client clientset.CertificateInterface, name string, check func(*v1.Certificate) bool, timeout time.Duration) (*v1.Certificate, error) { var certificate *v1.Certificate - pollErr := wait.PollUntilContextTimeout(context.TODO(), 500*time.Millisecond, timeout, true, func(ctx context.Context) (bool, error) { + pollErr := wait.PollUntilContextTimeout(pollCtx, 500*time.Millisecond, timeout, true, func(ctx context.Context) (bool, error) { var err error - certificate, err = client.Get(context.TODO(), name, metav1.GetOptions{}) + certificate, err = client.Get(ctx, name, metav1.GetOptions{}) if nil != err { certificate = nil return false, fmt.Errorf("error getting Certificate %v: %v", name, err) @@ -75,7 +49,7 @@ func (h *Helper) waitForCertificateCondition(client clientset.CertificateInterfa // WaitForCertificateReadyAndDoneIssuing waits for the certificate resource to be in a Ready=True state and not be in an Issuing state. // The Ready=True condition will be checked against the provided certificate to make sure that it is up-to-date (condition gen. >= cert gen.). -func (h *Helper) WaitForCertificateReadyAndDoneIssuing(cert *v1.Certificate, timeout time.Duration) (*v1.Certificate, error) { +func (h *Helper) WaitForCertificateReadyAndDoneIssuing(ctx context.Context, cert *v1.Certificate, timeout time.Duration) (*v1.Certificate, error) { ready_true_condition := v1.CertificateCondition{ Type: v1.CertificateConditionReady, Status: cmmeta.ConditionTrue, @@ -87,7 +61,7 @@ func (h *Helper) WaitForCertificateReadyAndDoneIssuing(cert *v1.Certificate, tim } logf, done := log.LogBackoff() defer done() - return h.waitForCertificateCondition(h.CMClient.CertmanagerV1().Certificates(cert.Namespace), cert.Name, func(certificate *v1.Certificate) bool { + return h.waitForCertificateCondition(ctx, h.CMClient.CertmanagerV1().Certificates(cert.Namespace), cert.Name, func(certificate *v1.Certificate) bool { if !apiutil.CertificateHasConditionWithObservedGeneration(certificate, ready_true_condition) { logf( "Expected Certificate %v condition %v=%v (generation >= %v) but it has: %v", @@ -113,217 +87,3 @@ func (h *Helper) WaitForCertificateReadyAndDoneIssuing(cert *v1.Certificate, tim return true }, timeout) } - -// WaitForCertificateNotReadyAndDoneIssuing waits for the certificate resource to be in a Ready=False state and not be in an Issuing state. -// The Ready=False condition will be checked against the provided certificate to make sure that it is up-to-date (condition gen. >= cert gen.). -func (h *Helper) WaitForCertificateNotReadyAndDoneIssuing(cert *v1.Certificate, timeout time.Duration) (*v1.Certificate, error) { - ready_false_condition := v1.CertificateCondition{ - Type: v1.CertificateConditionReady, - Status: cmmeta.ConditionFalse, - ObservedGeneration: cert.Generation, - } - issuing_true_condition := v1.CertificateCondition{ - Type: v1.CertificateConditionIssuing, - Status: cmmeta.ConditionTrue, - } - logf, done := log.LogBackoff() - defer done() - return h.waitForCertificateCondition(h.CMClient.CertmanagerV1().Certificates(cert.Namespace), cert.Name, func(certificate *v1.Certificate) bool { - if !apiutil.CertificateHasConditionWithObservedGeneration(certificate, ready_false_condition) { - logf( - "Expected Certificate %v condition %v=%v (generation >= %v) but it has: %v", - certificate.Name, - ready_false_condition.Type, - ready_false_condition.Status, - ready_false_condition.ObservedGeneration, - certificate.Status.Conditions, - ) - return false - } - - if apiutil.CertificateHasCondition(certificate, issuing_true_condition) { - logf("Expected Certificate %v condition %v to be missing but it has: %v", certificate.Name, issuing_true_condition.Type, certificate.Status.Conditions) - return false - } - - if certificate.Status.NextPrivateKeySecretName != nil { - logf("Expected Certificate %v 'next-private-key-secret-name' attribute to be empty but has: %v", certificate.Name, *certificate.Status.NextPrivateKeySecretName) - return false - } - - return true - }, timeout) -} - -func (h *Helper) waitForIssuerCondition(client clientset.IssuerInterface, name string, check func(issuer *v1.Issuer) bool, timeout time.Duration) (*v1.Issuer, error) { - var issuer *v1.Issuer - pollErr := wait.PollUntilContextTimeout(context.TODO(), 500*time.Millisecond, timeout, true, func(ctx context.Context) (bool, error) { - var err error - issuer, err = client.Get(context.TODO(), name, metav1.GetOptions{}) - if nil != err { - issuer = nil - return false, fmt.Errorf("error getting Issuer %v: %v", name, err) - } - return check(issuer), nil - }) - - if pollErr != nil && issuer != nil { - log.Logf("Failed waiting for issuer %v :%v\n", name, pollErr.Error()) - } - - return issuer, pollErr -} - -// WaitIssuerReady waits for the Issuer resource to be in a Ready=True state -// The Ready=True condition will be checked against the provided issuer to make sure its ready. -func (h *Helper) WaitIssuerReady(issuer *v1.Issuer, timeout time.Duration) (*v1.Issuer, error) { - ready_true_condition := v1.IssuerCondition{ - Type: v1.IssuerConditionReady, - Status: cmmeta.ConditionTrue, - } - - logf, done := log.LogBackoff() - defer done() - return h.waitForIssuerCondition(h.CMClient.CertmanagerV1().Issuers(issuer.Namespace), issuer.Name, func(issuer *v1.Issuer) bool { - if !apiutil.IssuerHasCondition(issuer, ready_true_condition) { - logf( - "Expected Issuer %v condition %v=%v but it has: %v", - issuer.Name, - ready_true_condition.Type, - ready_true_condition.Status, - issuer.Status.Conditions, - ) - return false - } - return true - }, timeout) -} - -func (h *Helper) waitForClusterIssuerCondition(client clientset.ClusterIssuerInterface, name string, check func(issuer *v1.ClusterIssuer) bool, timeout time.Duration) (*v1.ClusterIssuer, error) { - var issuer *v1.ClusterIssuer - pollErr := wait.PollUntilContextTimeout(context.TODO(), 500*time.Millisecond, timeout, true, func(ctx context.Context) (bool, error) { - var err error - issuer, err = client.Get(context.TODO(), name, metav1.GetOptions{}) - if nil != err { - issuer = nil - return false, fmt.Errorf("error getting Issuer %v: %v", name, err) - } - return check(issuer), nil - }) - - if pollErr != nil && issuer != nil { - log.Logf("Failed waiting for issuer %v :%v\n", name, pollErr.Error()) - } - - return issuer, pollErr -} - -// WaitClusterIssuerReady waits for the Cluster Issuer resource to be in a Ready=True state -// The Ready=True condition will be checked against the provided issuer to make sure its ready. -func (h *Helper) WaitClusterIssuerReady(issuer *v1.ClusterIssuer, timeout time.Duration) (*v1.ClusterIssuer, error) { - ready_true_condition := v1.IssuerCondition{ - Type: v1.IssuerConditionReady, - Status: cmmeta.ConditionTrue, - } - logf, done := log.LogBackoff() - defer done() - return h.waitForClusterIssuerCondition(h.CMClient.CertmanagerV1().ClusterIssuers(), issuer.Name, func(issuer *v1.ClusterIssuer) bool { - if !apiutil.IssuerHasCondition(issuer, ready_true_condition) { - logf( - "Expected Cluster Issuer %v condition %v=%v but it has: %v", - issuer.Name, - ready_true_condition.Type, - ready_true_condition.Status, - issuer.Status.Conditions, - ) - return false - } - return true - }, timeout) -} - -func (h *Helper) deduplicateExtKeyUsages(us []x509.ExtKeyUsage) []x509.ExtKeyUsage { - extKeyUsagesMap := make(map[x509.ExtKeyUsage]bool) - for _, e := range us { - extKeyUsagesMap[e] = true - } - - us = make([]x509.ExtKeyUsage, 0) - for e, ok := range extKeyUsagesMap { - if ok { - us = append(us, e) - } - } - - return us -} - -func (h *Helper) defaultKeyUsagesToAdd(ns string, issuerRef *cmmeta.ObjectReference) (x509.KeyUsage, []x509.ExtKeyUsage, error) { - var issuerSpec *v1.IssuerSpec - switch issuerRef.Kind { - case "ClusterIssuer": - issuerObj, err := h.CMClient.CertmanagerV1().ClusterIssuers().Get(context.TODO(), issuerRef.Name, metav1.GetOptions{}) - if err != nil { - return 0, nil, fmt.Errorf("failed to find referenced ClusterIssuer %v: %s", - issuerRef, err) - } - - issuerSpec = &issuerObj.Spec - default: - issuerObj, err := h.CMClient.CertmanagerV1().Issuers(ns).Get(context.TODO(), issuerRef.Name, metav1.GetOptions{}) - if err != nil { - return 0, nil, fmt.Errorf("failed to find referenced Issuer %v: %s", - issuerRef, err) - } - - issuerSpec = &issuerObj.Spec - } - - var keyUsages x509.KeyUsage - var extKeyUsages []x509.ExtKeyUsage - - // Vault and ACME issuers will add server auth and client auth extended key - // usages by default so we need to add them to the list of expected usages - if issuerSpec.ACME != nil || issuerSpec.Vault != nil { - extKeyUsages = append(extKeyUsages, x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth) - } - - // Vault issuers will add key agreement key usage - if issuerSpec.Vault != nil { - keyUsages |= x509.KeyUsageKeyAgreement - } - - // Venafi issue adds server auth key usage - if issuerSpec.Venafi != nil { - extKeyUsages = append(extKeyUsages, x509.ExtKeyUsageServerAuth) - } - - return keyUsages, extKeyUsages, nil -} - -func (h *Helper) keyUsagesMatch(aKU x509.KeyUsage, aEKU []x509.ExtKeyUsage, - bKU x509.KeyUsage, bEKU []x509.ExtKeyUsage) bool { - if aKU != bKU { - return false - } - - if len(aEKU) != len(bEKU) { - return false - } - - sort.SliceStable(aEKU, func(i, j int) bool { - return aEKU[i] < aEKU[j] - }) - - sort.SliceStable(bEKU, func(i, j int) bool { - return bEKU[i] < bEKU[j] - }) - - for i := range aEKU { - if aEKU[i] != bEKU[i] { - return false - } - } - - return true -} diff --git a/conformance/framework/helper/certificatesigningrequests.go b/conformance/framework/helper/certificatesigningrequests.go index 85e8c2c..e633f89 100644 --- a/conformance/framework/helper/certificatesigningrequests.go +++ b/conformance/framework/helper/certificatesigningrequests.go @@ -21,24 +21,24 @@ import ( "fmt" "time" + "github.com/cert-manager/cert-manager/pkg/controller/certificatesigningrequests/util" certificatesv1 "k8s.io/api/certificates/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/wait" - "github.com/cert-manager/cert-manager/pkg/controller/certificatesigningrequests/util" "github.com/cert-manager/issuer-lib/conformance/framework/log" ) // WaitForCertificateSigningRequestSigned waits for the // CertificateSigningRequest resource to be signed. -func (h *Helper) WaitForCertificateSigningRequestSigned(name string, timeout time.Duration) (*certificatesv1.CertificateSigningRequest, error) { +func (h *Helper) WaitForCertificateSigningRequestSigned(pollCtx context.Context, name string, timeout time.Duration) (*certificatesv1.CertificateSigningRequest, error) { var csr *certificatesv1.CertificateSigningRequest logf, done := log.LogBackoff() defer done() - err := wait.PollUntilContextTimeout(context.TODO(), time.Second, timeout, true, func(ctx context.Context) (bool, error) { + err := wait.PollUntilContextTimeout(pollCtx, time.Second, timeout, true, func(ctx context.Context) (bool, error) { var err error logf("Waiting for CertificateSigningRequest %s to be ready", name) - csr, err = h.KubeClient.CertificatesV1().CertificateSigningRequests().Get(context.TODO(), name, metav1.GetOptions{}) + csr, err = h.KubeClient.CertificatesV1().CertificateSigningRequests().Get(ctx, name, metav1.GetOptions{}) if err != nil { return false, fmt.Errorf("error getting CertificateSigningRequest %s: %v", name, err) } diff --git a/conformance/framework/helper/featureset/featureset.go b/conformance/framework/helper/featureset/featureset.go index 00af198..5e67d26 100644 --- a/conformance/framework/helper/featureset/featureset.go +++ b/conformance/framework/helper/featureset/featureset.go @@ -54,16 +54,16 @@ func (fs FeatureSet) Contains(f Feature) bool { // Copy returns a new copy of an existing Feature Set. // It is not safe to be called by multiple goroutines. func (fs FeatureSet) Copy() FeatureSet { - new := make(FeatureSet) + newSet := make(FeatureSet, len(fs)) for k, v := range fs { - new[k] = v + newSet[k] = v } - return new + return newSet } // List returns a slice of all features in the set. func (fs FeatureSet) List() []Feature { - var ret []Feature + ret := make([]Feature, 0, len(fs)) for k := range fs { ret = append(ret, k) } @@ -72,14 +72,10 @@ func (fs FeatureSet) List() []Feature { // String returns this FeatureSet as a comma separated string func (fs FeatureSet) String() string { - featsSlice := make([]string, len(fs)) - - i := 0 + featsSlice := make([]string, 0, len(fs)) for f := range fs { - featsSlice[i] = string(f) - i++ + featsSlice = append(featsSlice, string(f)) } - return strings.Join(featsSlice, ", ") } diff --git a/conformance/framework/helper/helper.go b/conformance/framework/helper/helper.go index 87a046b..03c9e7c 100644 --- a/conformance/framework/helper/helper.go +++ b/conformance/framework/helper/helper.go @@ -17,10 +17,9 @@ limitations under the License. package helper import ( + cmclient "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" - - cmclient "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned" ) // Helper provides methods for common operations needed during tests. diff --git a/conformance/framework/helper/secret.go b/conformance/framework/helper/secret.go index 9f1239f..d409fbf 100644 --- a/conformance/framework/helper/secret.go +++ b/conformance/framework/helper/secret.go @@ -30,14 +30,14 @@ import ( // WaitForSecretCertificateData waits for the certificate data to be ready // inside a Secret created by cert-manager. -func (h *Helper) WaitForSecretCertificateData(ns, name string, timeout time.Duration) (*corev1.Secret, error) { +func (h *Helper) WaitForSecretCertificateData(pollCtx context.Context, ns, name string, timeout time.Duration) (*corev1.Secret, error) { var secret *corev1.Secret logf, done := log.LogBackoff() defer done() - err := wait.PollUntilContextTimeout(context.TODO(), time.Second, timeout, true, func(ctx context.Context) (bool, error) { + err := wait.PollUntilContextTimeout(pollCtx, time.Second, timeout, true, func(ctx context.Context) (bool, error) { var err error logf("Waiting for Secret %s:%s to contain a certificate", ns, name) - secret, err = h.KubeClient.CoreV1().Secrets(ns).Get(context.TODO(), name, metav1.GetOptions{}) + secret, err = h.KubeClient.CoreV1().Secrets(ns).Get(ctx, name, metav1.GetOptions{}) if err != nil { return false, fmt.Errorf("error getting secret %s: %s", name, err) } diff --git a/conformance/framework/helper/validate.go b/conformance/framework/helper/validate.go index 6f7944e..991c64d 100644 --- a/conformance/framework/helper/validate.go +++ b/conformance/framework/helper/validate.go @@ -21,20 +21,20 @@ import ( "crypto" "fmt" + cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" "github.com/cert-manager/issuer-lib/conformance/framework/helper/validation/certificates" "github.com/cert-manager/issuer-lib/conformance/framework/helper/validation/certificatesigningrequests" ) // ValidateCertificate retrieves the issued certificate and runs all validation functions -func (h *Helper) ValidateCertificate(certificate *cmapi.Certificate, validations ...certificates.ValidationFunc) error { +func (h *Helper) ValidateCertificate(ctx context.Context, certificate *cmapi.Certificate, validations ...certificates.ValidationFunc) error { if len(validations) == 0 { return fmt.Errorf("no validation functions provided") } - secret, err := h.KubeClient.CoreV1().Secrets(certificate.Namespace).Get(context.TODO(), certificate.Spec.SecretName, metav1.GetOptions{}) + secret, err := h.KubeClient.CoreV1().Secrets(certificate.Namespace).Get(ctx, certificate.Spec.SecretName, metav1.GetOptions{}) if err != nil { return err } @@ -50,12 +50,12 @@ func (h *Helper) ValidateCertificate(certificate *cmapi.Certificate, validations } // ValidateCertificateSigningRequest retrieves the issued certificate and runs all validation functions -func (h *Helper) ValidateCertificateSigningRequest(name string, key crypto.Signer, validations ...certificatesigningrequests.ValidationFunc) error { +func (h *Helper) ValidateCertificateSigningRequest(ctx context.Context, name string, key crypto.Signer, validations ...certificatesigningrequests.ValidationFunc) error { if len(validations) == 0 { return fmt.Errorf("no validation functions provided") } - csr, err := h.KubeClient.CertificatesV1().CertificateSigningRequests().Get(context.TODO(), name, metav1.GetOptions{}) + csr, err := h.KubeClient.CertificatesV1().CertificateSigningRequests().Get(ctx, name, metav1.GetOptions{}) if err != nil { return err } diff --git a/conformance/framework/helper/validation/certificates/certificates.go b/conformance/framework/helper/validation/certificates/certificates.go index f85d8dd..ff9175b 100644 --- a/conformance/framework/helper/validation/certificates/certificates.go +++ b/conformance/framework/helper/validation/certificates/certificates.go @@ -27,14 +27,13 @@ import ( "strings" "time" - "github.com/kr/pretty" - corev1 "k8s.io/api/core/v1" - apiutil "github.com/cert-manager/cert-manager/pkg/api/util" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" "github.com/cert-manager/cert-manager/pkg/util" "github.com/cert-manager/cert-manager/pkg/util/pki" + "github.com/kr/pretty" + corev1 "k8s.io/api/core/v1" ) // ValidationFunc describes a Certificate validation helper function diff --git a/conformance/framework/helper/validation/certificatesigningrequests/certificatesigningrequests.go b/conformance/framework/helper/validation/certificatesigningrequests/certificatesigningrequests.go index f13eda0..fba0246 100644 --- a/conformance/framework/helper/validation/certificatesigningrequests/certificatesigningrequests.go +++ b/conformance/framework/helper/validation/certificatesigningrequests/certificatesigningrequests.go @@ -26,13 +26,12 @@ import ( "fmt" "time" - certificatesv1 "k8s.io/api/certificates/v1" - cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" experimentalapi "github.com/cert-manager/cert-manager/pkg/apis/experimental/v1alpha1" ctrlutil "github.com/cert-manager/cert-manager/pkg/controller/certificatesigningrequests/util" "github.com/cert-manager/cert-manager/pkg/util" "github.com/cert-manager/cert-manager/pkg/util/pki" + certificatesv1 "k8s.io/api/certificates/v1" ) // ValidationFunc describes a CertificateSigningRequest validation helper function diff --git a/conformance/framework/log/log.go b/conformance/framework/log/log.go index e570b88..dab4330 100644 --- a/conformance/framework/log/log.go +++ b/conformance/framework/log/log.go @@ -31,12 +31,8 @@ func nowStamp() string { return time.Now().Format(time.StampMilli) } -func log(level string, format string, args ...interface{}) { - fmt.Fprintf(Writer, nowStamp()+": "+level+": "+format+"\n", args...) -} - func Logf(format string, args ...interface{}) { - log("INFO", format, args...) + fmt.Fprintf(Writer, nowStamp()+": INFO: "+format+"\n", args...) } // LogBackoff gives you a logger with an exponential backoff. If the diff --git a/conformance/framework/util.go b/conformance/framework/util.go index 7788ca9..104a402 100644 --- a/conformance/framework/util.go +++ b/conformance/framework/util.go @@ -20,11 +20,10 @@ import ( "fmt" "time" - . "github.com/onsi/ginkgo/v2" - "k8s.io/component-base/featuregate" . "github.com/cert-manager/issuer-lib/conformance/framework/log" + . "github.com/onsi/ginkgo/v2" ) func nowStamp() string { diff --git a/conformance/rbac/certificate.go b/conformance/rbac/certificate.go index ae148d5..c086406 100644 --- a/conformance/rbac/certificate.go +++ b/conformance/rbac/certificate.go @@ -17,10 +17,10 @@ limitations under the License. package rbac import ( + "github.com/cert-manager/issuer-lib/conformance/framework" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - - "github.com/cert-manager/issuer-lib/conformance/framework" ) func (s *Suite) defineCertificates() { diff --git a/conformance/rbac/certificaterequest.go b/conformance/rbac/certificaterequest.go index d2a7fe3..b309424 100644 --- a/conformance/rbac/certificaterequest.go +++ b/conformance/rbac/certificaterequest.go @@ -17,10 +17,10 @@ limitations under the License. package rbac import ( + "github.com/cert-manager/issuer-lib/conformance/framework" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - - "github.com/cert-manager/issuer-lib/conformance/framework" ) func (s *Suite) defineCertificateRequests() { diff --git a/conformance/rbac/doc.go b/conformance/rbac/doc.go index dfa19e2..d9731f6 100644 --- a/conformance/rbac/doc.go +++ b/conformance/rbac/doc.go @@ -20,16 +20,16 @@ import ( "context" "time" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/cert-manager/issuer-lib/conformance/framework" authorizationv1 "k8s.io/api/authorization/v1" v1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" - "k8s.io/client-go/rest" + + "github.com/cert-manager/issuer-lib/conformance/framework" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" ) // RBACDescribe wraps ConformanceDescribe with namespacing for RBAC tests @@ -72,8 +72,7 @@ func RbacClusterRoleHasAccessToResource(f *framework.Framework, clusterRole stri time.Sleep(time.Second) By("Impersonating the Service Account") - var impersonateConfig *rest.Config - impersonateConfig = f.KubeClientConfig + impersonateConfig := f.KubeClientConfig impersonateConfig.Impersonate.UserName = "system:serviceaccount:" + f.Namespace.Name + ":" + viewServiceAccountName impersonateClient, err := kubernetes.NewForConfig(impersonateConfig) Expect(err).NotTo(HaveOccurred()) diff --git a/conformance/rbac/issuer.go b/conformance/rbac/issuer.go index efe66d8..d1e9544 100644 --- a/conformance/rbac/issuer.go +++ b/conformance/rbac/issuer.go @@ -17,10 +17,10 @@ limitations under the License. package rbac import ( + "github.com/cert-manager/issuer-lib/conformance/framework" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - - "github.com/cert-manager/issuer-lib/conformance/framework" ) func (s *Suite) defineIssuers() { diff --git a/conformance/util/domains.go b/conformance/util/random.go similarity index 100% rename from conformance/util/domains.go rename to conformance/util/random.go diff --git a/conformance/util/util.go b/conformance/util/util.go deleted file mode 100644 index 57f066d..0000000 --- a/conformance/util/util.go +++ /dev/null @@ -1,427 +0,0 @@ -/* -Copyright 2020 The cert-manager Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package util - -// TODO: we should break this file apart into separate more sane/reusable parts - -import ( - "context" - "crypto" - "crypto/x509" - "crypto/x509/pkix" - "encoding/pem" - "fmt" - "net" - "net/url" - "time" - - corev1 "k8s.io/api/core/v1" - networkingv1 "k8s.io/api/networking/v1" - networkingv1beta1 "k8s.io/api/networking/v1beta1" - apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1" - "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/intstr" - "k8s.io/apimachinery/pkg/util/wait" - "k8s.io/client-go/discovery" - gwapiv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" - - apiutil "github.com/cert-manager/cert-manager/pkg/api/util" - v1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" - cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" - clientset "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned/typed/certmanager/v1" - "github.com/cert-manager/cert-manager/pkg/util" - "github.com/cert-manager/cert-manager/pkg/util/pki" - "github.com/cert-manager/issuer-lib/conformance/framework/log" -) - -func CertificateOnlyValidForDomains(cert *x509.Certificate, commonName string, dnsNames ...string) bool { - if commonName != cert.Subject.CommonName || !util.EqualUnsorted(cert.DNSNames, dnsNames) { - return false - } - return true -} - -func WaitForIssuerStatusFunc(client clientset.IssuerInterface, name string, fn func(*v1.Issuer) (bool, error)) error { - return wait.PollUntilContextTimeout(context.TODO(), 500*time.Millisecond, time.Minute, true, func(ctx context.Context) (bool, error) { - issuer, err := client.Get(ctx, name, metav1.GetOptions{}) - if err != nil { - return false, fmt.Errorf("error getting Issuer %q: %v", name, err) - } - return fn(issuer) - }) -} - -// WaitForIssuerCondition waits for the status of the named issuer to contain -// a condition whose type and status matches the supplied one. -func WaitForIssuerCondition(client clientset.IssuerInterface, name string, condition v1.IssuerCondition) error { - logf, done := log.LogBackoff() - defer done() - pollErr := wait.PollUntilContextTimeout(context.TODO(), 500*time.Millisecond, time.Minute, true, func(ctx context.Context) (bool, error) { - logf("Waiting for issuer %v condition %#v", name, condition) - issuer, err := client.Get(ctx, name, metav1.GetOptions{}) - if nil != err { - return false, fmt.Errorf("error getting Issuer %q: %v", name, err) - } - - return apiutil.IssuerHasCondition(issuer, condition), nil - }) - return wrapErrorWithIssuerStatusCondition(client, pollErr, name, condition.Type) -} - -// try to retrieve last condition to help diagnose tests. -func wrapErrorWithIssuerStatusCondition(client clientset.IssuerInterface, pollErr error, name string, conditionType v1.IssuerConditionType) error { - if pollErr == nil { - return nil - } - - issuer, err := client.Get(context.TODO(), name, metav1.GetOptions{}) - if err != nil { - return pollErr - } - - for _, cond := range issuer.GetStatus().Conditions { - if cond.Type == conditionType { - return fmt.Errorf("%s: Last Status: '%s' Reason: '%s', Message: '%s'", pollErr.Error(), cond.Status, cond.Reason, cond.Message) - } - - } - - return pollErr -} - -// WaitForClusterIssuerCondition waits for the status of the named issuer to contain -// a condition whose type and status matches the supplied one. -func WaitForClusterIssuerCondition(client clientset.ClusterIssuerInterface, name string, condition v1.IssuerCondition) error { - logf, done := log.LogBackoff() - defer done() - pollErr := wait.PollUntilContextTimeout(context.TODO(), 500*time.Millisecond, time.Minute, true, func(ctx context.Context) (bool, error) { - logf("Waiting for clusterissuer %v condition %#v", name, condition) - issuer, err := client.Get(ctx, name, metav1.GetOptions{}) - if nil != err { - return false, fmt.Errorf("error getting ClusterIssuer %v: %v", name, err) - } - - return apiutil.IssuerHasCondition(issuer, condition), nil - }) - return wrapErrorWithClusterIssuerStatusCondition(client, pollErr, name, condition.Type) -} - -// try to retrieve last condition to help diagnose tests. -func wrapErrorWithClusterIssuerStatusCondition(client clientset.ClusterIssuerInterface, pollErr error, name string, conditionType v1.IssuerConditionType) error { - if pollErr == nil { - return nil - } - - issuer, err := client.Get(context.TODO(), name, metav1.GetOptions{}) - if err != nil { - return pollErr - } - - for _, cond := range issuer.GetStatus().Conditions { - if cond.Type == conditionType { - return fmt.Errorf("%s: Last Status: '%s' Reason: '%s', Message: '%s'", pollErr.Error(), cond.Status, cond.Reason, cond.Message) - } - - } - - return pollErr -} - -// WaitForCRDToNotExist waits for the CRD with the given name to no -// longer exist. -func WaitForCRDToNotExist(client apiextensionsv1.CustomResourceDefinitionInterface, name string) error { - logf, done := log.LogBackoff() - defer done() - return wait.PollUntilContextTimeout(context.TODO(), 500*time.Millisecond, time.Minute, true, func(ctx context.Context) (bool, error) { - logf("Waiting for CRD %v to not exist", name) - _, err := client.Get(ctx, name, metav1.GetOptions{}) - if nil == err { - return false, nil - } - - if errors.IsNotFound(err) { - return true, nil - } - - return false, nil - }) -} - -// Deprecated: use test/unit/gen/Certificate in future -func NewCertManagerBasicCertificate(name, secretName, issuerName string, issuerKind string, duration, renewBefore *metav1.Duration, dnsNames ...string) *v1.Certificate { - cn := "test.domain.com" - if len(dnsNames) > 0 { - cn = dnsNames[0] - } - return &v1.Certificate{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - }, - Spec: v1.CertificateSpec{ - CommonName: cn, - DNSNames: dnsNames, - Subject: &v1.X509Subject{ - Organizations: []string{"test-org"}, - }, - SecretName: secretName, - Duration: duration, - RenewBefore: renewBefore, - PrivateKey: &v1.CertificatePrivateKey{}, - IssuerRef: cmmeta.ObjectReference{ - Name: issuerName, - Kind: issuerKind, - }, - }, - } -} - -// Deprecated: use test/unit/gen/CertificateRequest in future -func NewCertManagerBasicCertificateRequest(name, issuerName string, issuerKind string, duration *metav1.Duration, - dnsNames []string, ips []net.IP, uris []string, keyAlgorithm x509.PublicKeyAlgorithm) (*v1.CertificateRequest, crypto.Signer, error) { - cn := "test.domain.com" - if len(dnsNames) > 0 { - cn = dnsNames[0] - } - - var parsedURIs []*url.URL - for _, uri := range uris { - parsed, err := url.Parse(uri) - if err != nil { - return nil, nil, err - } - parsedURIs = append(parsedURIs, parsed) - } - - var sk crypto.Signer - var signatureAlgorithm x509.SignatureAlgorithm - var err error - - switch keyAlgorithm { - case x509.RSA: - sk, err = pki.GenerateRSAPrivateKey(2048) - if err != nil { - return nil, nil, err - } - signatureAlgorithm = x509.SHA256WithRSA - case x509.ECDSA: - sk, err = pki.GenerateECPrivateKey(pki.ECCurve256) - if err != nil { - return nil, nil, err - } - signatureAlgorithm = x509.ECDSAWithSHA256 - case x509.Ed25519: - sk, err = pki.GenerateEd25519PrivateKey() - if err != nil { - return nil, nil, err - } - signatureAlgorithm = x509.PureEd25519 - default: - return nil, nil, fmt.Errorf("unrecognised key algorithm: %s", err) - } - - csr := &x509.CertificateRequest{ - Version: 0, - SignatureAlgorithm: signatureAlgorithm, - PublicKeyAlgorithm: keyAlgorithm, - PublicKey: sk.Public(), - Subject: pkix.Name{ - CommonName: cn, - }, - DNSNames: dnsNames, - IPAddresses: ips, - URIs: parsedURIs, - } - - csrBytes, err := pki.EncodeCSR(csr, sk) - if err != nil { - return nil, nil, err - } - - csrPEM := pem.EncodeToMemory(&pem.Block{ - Type: "CERTIFICATE REQUEST", Bytes: csrBytes, - }) - - return &v1.CertificateRequest{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - }, - Spec: v1.CertificateRequestSpec{ - Duration: duration, - Request: csrPEM, - IssuerRef: cmmeta.ObjectReference{ - Name: issuerName, - Kind: issuerKind, - }, - }, - }, sk, nil -} - -func NewCertManagerVaultCertificate(name, secretName, issuerName string, issuerKind string, duration, renewBefore *metav1.Duration) *v1.Certificate { - return &v1.Certificate{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - }, - Spec: v1.CertificateSpec{ - CommonName: "test.domain.com", - SecretName: secretName, - Duration: duration, - RenewBefore: renewBefore, - IssuerRef: cmmeta.ObjectReference{ - Name: issuerName, - Kind: issuerKind, - }, - }, - } -} - -func NewIngress(name, secretName string, annotations map[string]string, dnsNames ...string) *networkingv1.Ingress { - return &networkingv1.Ingress{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Annotations: annotations, - }, - Spec: networkingv1.IngressSpec{ - TLS: []networkingv1.IngressTLS{ - { - Hosts: dnsNames, - SecretName: secretName, - }, - }, - Rules: []networkingv1.IngressRule{ - { - Host: dnsNames[0], - IngressRuleValue: networkingv1.IngressRuleValue{ - HTTP: &networkingv1.HTTPIngressRuleValue{ - Paths: []networkingv1.HTTPIngressPath{ - { - Path: "/", - PathType: pathTypePrefix(), - Backend: networkingv1.IngressBackend{ - Service: &networkingv1.IngressServiceBackend{ - Name: "somesvc", - Port: networkingv1.ServiceBackendPort{ - Number: 80, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - } -} - -func NewV1Beta1Ingress(name, secretName string, annotations map[string]string, dnsNames ...string) *networkingv1beta1.Ingress { - return &networkingv1beta1.Ingress{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Annotations: annotations, - }, - Spec: networkingv1beta1.IngressSpec{ - TLS: []networkingv1beta1.IngressTLS{ - { - Hosts: dnsNames, - SecretName: secretName, - }, - }, - Rules: []networkingv1beta1.IngressRule{ - { - Host: dnsNames[0], - IngressRuleValue: networkingv1beta1.IngressRuleValue{ - HTTP: &networkingv1beta1.HTTPIngressRuleValue{ - Paths: []networkingv1beta1.HTTPIngressPath{ - { - Path: "/", - Backend: networkingv1beta1.IngressBackend{ - ServiceName: "somesvc", - ServicePort: intstr.FromInt(80), - }, - }, - }, - }, - }, - }, - }, - }, - } -} - -func pathTypePrefix() *networkingv1.PathType { - p := networkingv1.PathTypePrefix - return &p -} - -// NewGateway creates a new test Gateway. There is no Gateway controller -// watching the 'foo' gateway class, so this Gateway will not be used to -// actually route traffic, but can be used to test cert-manager controllers that -// sync Gateways, such as gateway-shim. -func NewGateway(gatewayName, ns, secretName string, annotations map[string]string, dnsNames ...string) *gwapiv1beta1.Gateway { - - return &gwapiv1beta1.Gateway{ - ObjectMeta: metav1.ObjectMeta{ - Name: gatewayName, - Annotations: annotations, - }, - Spec: gwapiv1beta1.GatewaySpec{ - GatewayClassName: "foo", - Listeners: []gwapiv1beta1.Listener{{ - AllowedRoutes: &gwapiv1beta1.AllowedRoutes{ - Namespaces: &gwapiv1beta1.RouteNamespaces{ - From: func() *gwapiv1beta1.FromNamespaces { f := gwapiv1beta1.NamespacesFromSame; return &f }(), - Selector: &metav1.LabelSelector{MatchLabels: map[string]string{ - "gw": gatewayName, - }}, - }, - Kinds: nil, - }, - Name: "acme-solver", - Protocol: gwapiv1beta1.TLSProtocolType, - Port: gwapiv1beta1.PortNumber(443), - Hostname: (*gwapiv1beta1.Hostname)(&dnsNames[0]), - TLS: &gwapiv1beta1.GatewayTLSConfig{ - CertificateRefs: []gwapiv1beta1.SecretObjectReference{ - { - Kind: func() *gwapiv1beta1.Kind { k := gwapiv1beta1.Kind("Secret"); return &k }(), - Name: gwapiv1beta1.ObjectName(secretName), - Group: func() *gwapiv1beta1.Group { g := gwapiv1beta1.Group(corev1.GroupName); return &g }(), - Namespace: (*gwapiv1beta1.Namespace)(&ns), - }, - }, - }, - }}, - }, - } -} - -// HasIngresses lets you know if an API exists in the discovery API -// calling this function always performs a request to the API server. -func HasIngresses(d discovery.DiscoveryInterface, GroupVersion string) bool { - resourceList, err := d.ServerResourcesForGroupVersion(GroupVersion) - if err != nil { - return false - } - for _, r := range resourceList.APIResources { - if r.Kind == "Ingress" { - return true - } - } - return false -} diff --git a/internal/testsetups/simple/e2e/conformance/conformance.go b/internal/testsetups/simple/e2e/conformance/conformance.go index 69a4806..9cfe031 100644 --- a/internal/testsetups/simple/e2e/conformance/conformance.go +++ b/internal/testsetups/simple/e2e/conformance/conformance.go @@ -6,6 +6,7 @@ import ( "testing" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" + "github.com/cert-manager/issuer-lib/conformance/certificates" "github.com/cert-manager/issuer-lib/conformance/certificatesigningrequests" "github.com/cert-manager/issuer-lib/conformance/framework" diff --git a/internal/testsetups/simple/e2e/conformance/issuer_builder.go b/internal/testsetups/simple/e2e/conformance/issuer_builder.go index ec5e3eb..cde735d 100644 --- a/internal/testsetups/simple/e2e/conformance/issuer_builder.go +++ b/internal/testsetups/simple/e2e/conformance/issuer_builder.go @@ -4,17 +4,17 @@ import ( "context" "fmt" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/types" crtclient "sigs.k8s.io/controller-runtime/pkg/client" "github.com/cert-manager/issuer-lib/conformance/framework" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/types" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" ) type issuerBuilder struct { From bddf10e7823b631cee9c6561150e3c7ba064d3d2 Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Fri, 9 Jun 2023 13:20:17 +0200 Subject: [PATCH 05/13] resolved remaining linter issues Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- conformance/framework/framework.go | 21 ++++------- .../validation/certificates/certificates.go | 35 ++++--------------- .../certificatesigningrequests.go | 2 -- 3 files changed, 13 insertions(+), 45 deletions(-) diff --git a/conformance/framework/framework.go b/conformance/framework/framework.go index bc7b12d..29562e3 100644 --- a/conformance/framework/framework.go +++ b/conformance/framework/framework.go @@ -23,7 +23,6 @@ import ( apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" apiextcs "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" "k8s.io/apimachinery/pkg/runtime" - utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/client-go/kubernetes" kscheme "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" @@ -37,19 +36,6 @@ import ( . "github.com/onsi/gomega" ) -// TODO: not all this code is required to be externally accessible. Separate the -// bits that do and the bits that don't. Perhaps we should have an external -// testing lib shared across projects? -// TODO: this really should be done somewhere in cert-manager proper -var Scheme = runtime.NewScheme() - -func init() { - utilruntime.Must(kscheme.AddToScheme(Scheme)) - utilruntime.Must(certmgrscheme.AddToScheme(Scheme)) - utilruntime.Must(apiext.AddToScheme(Scheme)) - utilruntime.Must(apireg.AddToScheme(Scheme)) -} - // Framework supports common operations used by e2e tests; it will keep a client & a namespace for you. type Framework struct { BaseName string @@ -117,7 +103,12 @@ func (f *Framework) BeforeEach() { Expect(err).NotTo(HaveOccurred()) By("Creating a controller-runtime client") - f.CRClient, err = crclient.New(kubeConfig, crclient.Options{Scheme: Scheme}) + scheme := runtime.NewScheme() + Expect(kscheme.AddToScheme(scheme)).NotTo(HaveOccurred()) + Expect(certmgrscheme.AddToScheme(scheme)).NotTo(HaveOccurred()) + Expect(apiext.AddToScheme(scheme)).NotTo(HaveOccurred()) + Expect(apireg.AddToScheme(scheme)).NotTo(HaveOccurred()) + f.CRClient, err = crclient.New(kubeConfig, crclient.Options{Scheme: scheme}) Expect(err).NotTo(HaveOccurred()) By("Creating a gateway-api client") diff --git a/conformance/framework/helper/validation/certificates/certificates.go b/conformance/framework/helper/validation/certificates/certificates.go index ff9175b..d039fac 100644 --- a/conformance/framework/helper/validation/certificates/certificates.go +++ b/conformance/framework/helper/validation/certificates/certificates.go @@ -18,9 +18,6 @@ package certificates import ( "bytes" - "crypto/ecdsa" - "crypto/ed25519" - "crypto/rsa" "crypto/x509" "encoding/pem" "fmt" @@ -82,31 +79,15 @@ func ExpectValidPrivateKeyData(certificate *cmapi.Certificate, secret *corev1.Se return err } - // validate private key is of the correct type (rsa, ed25519 or ecdsa) - if certificate.Spec.PrivateKey != nil { - switch certificate.Spec.PrivateKey.Algorithm { - case cmapi.PrivateKeyAlgorithm(""), - cmapi.RSAKeyAlgorithm: - _, ok := key.(*rsa.PrivateKey) - if !ok { - return fmt.Errorf("Expected private key of type RSA, but it was: %T", key) - } - case cmapi.ECDSAKeyAlgorithm: - _, ok := key.(*ecdsa.PrivateKey) - if !ok { - return fmt.Errorf("Expected private key of type ECDSA, but it was: %T", key) - } - case cmapi.Ed25519KeyAlgorithm: - _, ok := key.(ed25519.PrivateKey) - if !ok { - return fmt.Errorf("Expected private key of type Ed25519, but it was: %T", key) - } - default: - return fmt.Errorf("unrecognised requested private key algorithm %q", certificate.Spec.PrivateKey.Algorithm) - } + violations, err := pki.PrivateKeyMatchesSpec(key, certificate.Spec) + if err != nil { + return err + } + + if len(violations) > 0 { + return fmt.Errorf("private key does not match CertificateRequest: %s", strings.Join(violations, ", ")) } - // TODO: validate private key KeySize return nil } @@ -412,8 +393,6 @@ func ExpectValidBasicConstraints(certificate *cmapi.Certificate, secret *corev1. return fmt.Errorf("Expected CA basicConstraint to be %v, but got %v", certificate.Spec.IsCA, cert.IsCA) } - // TODO: also validate pathLen - return nil } diff --git a/conformance/framework/helper/validation/certificatesigningrequests/certificatesigningrequests.go b/conformance/framework/helper/validation/certificatesigningrequests/certificatesigningrequests.go index fba0246..a49ce0d 100644 --- a/conformance/framework/helper/validation/certificatesigningrequests/certificatesigningrequests.go +++ b/conformance/framework/helper/validation/certificatesigningrequests/certificatesigningrequests.go @@ -338,8 +338,6 @@ func ExpectValidBasicConstraints(csr *certificatesv1.CertificateSigningRequest, return fmt.Errorf("Expected certificate to have KeyUsageCertSign=%t, but got=%t", markedIsCA, hasCertSign) } - // TODO: also validate pathLen - return nil } From e421edde8323ed6e0912e16d37652f40693ea2fd Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Fri, 9 Jun 2023 13:25:45 +0200 Subject: [PATCH 06/13] resolve CI error Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- .../testsetups/simple/e2e/conformance/conformance_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/internal/testsetups/simple/e2e/conformance/conformance_test.go b/internal/testsetups/simple/e2e/conformance/conformance_test.go index 13a51ea..aec3275 100644 --- a/internal/testsetups/simple/e2e/conformance/conformance_test.go +++ b/internal/testsetups/simple/e2e/conformance/conformance_test.go @@ -3,11 +3,15 @@ package conformance import ( "testing" + "github.com/cert-manager/issuer-lib/internal/tests/testcontext" + "github.com/cert-manager/issuer-lib/internal/tests/testresource" "github.com/onsi/ginkgo/v2" "github.com/onsi/gomega" ) func TestConformance(t *testing.T) { + _ = testresource.EnsureTestDependencies(t, testcontext.ForTest(t), testresource.EndToEndTest) + gomega.RegisterFailHandler(ginkgo.Fail) ginkgo.RunSpecs(t, "cert-manager conformance suite") From 675230fd9555b423febd0949c872a1897442440e Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Fri, 9 Jun 2023 13:32:58 +0200 Subject: [PATCH 07/13] increase e2e timeout Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 5193c5e..e439ce8 100644 --- a/Makefile +++ b/Makefile @@ -163,7 +163,7 @@ test: test-unit-deps | $(NEEDS_GO) $(NEEDS_GOTESTSUM) ## Run unit tests. .PHONY: test-e2e test-e2e: test-e2e-deps | $(NEEDS_GOTESTSUM) $(NEEDS_GINKGO) ## Run e2e tests. This creates a Kind cluster, installs dependencies, deploys the issuer-lib and runs the E2E tests. - $(GOTESTSUM) ./internal/testsetups/simple/e2e/... -coverprofile cover.out -timeout 1m + $(GOTESTSUM) ./internal/testsetups/simple/e2e/... -coverprofile cover.out -timeout 5m $(GINKGO) ./internal/testsetups/simple/e2e/conformance/... From 1376db3166aef0834db80a82986ac2a363f2dc13 Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Fri, 9 Jun 2023 13:34:41 +0200 Subject: [PATCH 08/13] fix gci linting error Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- .../testsetups/simple/e2e/conformance/conformance_test.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/internal/testsetups/simple/e2e/conformance/conformance_test.go b/internal/testsetups/simple/e2e/conformance/conformance_test.go index aec3275..3aa7393 100644 --- a/internal/testsetups/simple/e2e/conformance/conformance_test.go +++ b/internal/testsetups/simple/e2e/conformance/conformance_test.go @@ -3,10 +3,11 @@ package conformance import ( "testing" - "github.com/cert-manager/issuer-lib/internal/tests/testcontext" - "github.com/cert-manager/issuer-lib/internal/tests/testresource" "github.com/onsi/ginkgo/v2" "github.com/onsi/gomega" + + "github.com/cert-manager/issuer-lib/internal/tests/testcontext" + "github.com/cert-manager/issuer-lib/internal/tests/testresource" ) func TestConformance(t *testing.T) { From a9c55ba3b3dcbc14db3ac8f93882665097166637 Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Mon, 26 Jun 2023 14:52:43 +0200 Subject: [PATCH 09/13] enable more conformance tests Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- .../validation/certificates/certificates.go | 17 ++++++++ .../certificatesigningrequests.go | 10 +---- go.mod | 8 ++-- .../testsetups/simple/controller/signer.go | 3 +- .../simple/e2e/conformance/conformance.go | 41 ++++--------------- make/e2e-setup.mk | 3 +- 6 files changed, 31 insertions(+), 51 deletions(-) diff --git a/conformance/framework/helper/validation/certificates/certificates.go b/conformance/framework/helper/validation/certificates/certificates.go index d039fac..e97c658 100644 --- a/conformance/framework/helper/validation/certificates/certificates.go +++ b/conformance/framework/helper/validation/certificates/certificates.go @@ -114,6 +114,23 @@ func ExpectCertificateOrganizationToMatch(certificate *cmapi.Certificate, secret } expectedOrganization := pki.OrganizationForCertificate(certificate) + if certificate.Spec.LiteralSubject != "" { + sequence, err := pki.UnmarshalSubjectStringToRDNSequence(certificate.Spec.LiteralSubject) + if err != nil { + return err + } + + for _, rdns := range sequence { + for _, atv := range rdns { + if atv.Type.Equal(pki.OIDConstants.Organization) { + if str, ok := atv.Value.(string); ok { + expectedOrganization = append(expectedOrganization, str) + } + } + } + } + } + if !util.EqualUnsorted(cert.Subject.Organization, expectedOrganization) { return fmt.Errorf("Expected certificate valid for O %v, but got a certificate valid for O %v", expectedOrganization, cert.Subject.Organization) } diff --git a/conformance/framework/helper/validation/certificatesigningrequests/certificatesigningrequests.go b/conformance/framework/helper/validation/certificatesigningrequests/certificatesigningrequests.go index a49ce0d..0153cd2 100644 --- a/conformance/framework/helper/validation/certificatesigningrequests/certificatesigningrequests.go +++ b/conformance/framework/helper/validation/certificatesigningrequests/certificatesigningrequests.go @@ -323,21 +323,13 @@ func ExpectValidBasicConstraints(csr *certificatesv1.CertificateSigningRequest, return err } - markedIsCA := false - if csr.Annotations[experimentalapi.CertificateSigningRequestIsCAAnnotationKey] == "true" { - markedIsCA = true - } + markedIsCA := csr.Annotations[experimentalapi.CertificateSigningRequestIsCAAnnotationKey] == "true" if cert.IsCA != markedIsCA { return fmt.Errorf("requested certificate does not match expected IsCA, exp=%t got=%t", markedIsCA, cert.IsCA) } - hasCertSign := (cert.KeyUsage & x509.KeyUsageCertSign) == x509.KeyUsageCertSign - if hasCertSign != markedIsCA { - return fmt.Errorf("Expected certificate to have KeyUsageCertSign=%t, but got=%t", markedIsCA, hasCertSign) - } - return nil } diff --git a/go.mod b/go.mod index d33f1d9..5a81318 100644 --- a/go.mod +++ b/go.mod @@ -15,11 +15,12 @@ require ( k8s.io/apiextensions-apiserver v0.27.3 k8s.io/apimachinery v0.27.3 k8s.io/client-go v0.27.3 + k8s.io/component-base v0.27.3 k8s.io/klog/v2 v2.100.1 - k8s.io/kube-aggregator v0.27.1 + k8s.io/kube-aggregator v0.27.2 k8s.io/utils v0.0.0-20230505201702-9f6742963106 sigs.k8s.io/controller-runtime v0.15.0 - sigs.k8s.io/gateway-api v0.6.2 + sigs.k8s.io/gateway-api v0.7.0 ) require ( @@ -82,10 +83,7 @@ require ( gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/component-base v0.27.3 // indirect - k8s.io/kube-aggregator v0.27.2 // indirect k8s.io/kube-openapi v0.0.0-20230515203736-54b630e78af5 // indirect - sigs.k8s.io/gateway-api v0.7.0 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect sigs.k8s.io/yaml v1.3.0 // indirect diff --git a/internal/testsetups/simple/controller/signer.go b/internal/testsetups/simple/controller/signer.go index f93ca5e..b4d43d8 100644 --- a/internal/testsetups/simple/controller/signer.go +++ b/internal/testsetups/simple/controller/signer.go @@ -82,8 +82,7 @@ func (Signer) Sign(ctx context.Context, cr signer.CertificateRequestObject, issu NotBefore: time.Now(), NotAfter: time.Now().Add(time.Hour * 24 * 180), - KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, BasicConstraintsValid: true, } diff --git a/internal/testsetups/simple/e2e/conformance/conformance.go b/internal/testsetups/simple/e2e/conformance/conformance.go index 9cfe031..bb1d351 100644 --- a/internal/testsetups/simple/e2e/conformance/conformance.go +++ b/internal/testsetups/simple/e2e/conformance/conformance.go @@ -26,12 +26,7 @@ var _ = framework.ConformanceDescribe("Certificates", func() { kubeClients := testresource.KubeClients(t, ctx) unsupportedFeatures := featureset.NewFeatureSet( - featureset.DurationFeature, - featureset.KeyUsagesFeature, featureset.SaveCAToSecret, - featureset.Ed25519FeatureSet, - featureset.IssueCAFeature, - featureset.LiteralSubjectFeature, ) issuerBuilder := newIssuerBuilder("SimpleIssuer") @@ -59,12 +54,7 @@ var _ = framework.ConformanceDescribe("CertificateSigningRequests", func() { kubeClients := testresource.KubeClients(t, ctx) unsupportedFeatures := featureset.NewFeatureSet( - featureset.DurationFeature, - featureset.KeyUsagesFeature, featureset.SaveCAToSecret, - featureset.Ed25519FeatureSet, - featureset.IssueCAFeature, - featureset.LiteralSubjectFeature, ) clusterIssuerBuilder := newIssuerBuilder("SimpleClusterIssuer") @@ -87,35 +77,18 @@ var _ = framework.ConformanceDescribe("CertificateSigningRequests", func() { }).Define() }) +/* var _ = framework.ConformanceDescribe("RBAC", func() { t := &mockTest{} ctx := testresource.EnsureTestDependencies(t, context.TODO(), testresource.EndToEndTest) kubeClients := testresource.KubeClients(t, ctx) - unsupportedFeatures := featureset.NewFeatureSet( - featureset.DurationFeature, - featureset.KeyUsagesFeature, - featureset.SaveCAToSecret, - featureset.Ed25519FeatureSet, - featureset.IssueCAFeature, - featureset.LiteralSubjectFeature, - ) + kubeConfig := rest.CopyConfig(kubeClients.Rest) + kubeConfig.Impersonate.UserName = "system:serviceaccount:my-namespace:simple-issuer-controller-manager" + kubeConfig.Impersonate.Groups = []string{"system:authenticated"} - issuerBuilder := newIssuerBuilder("SimpleIssuer") - (&certificates.Suite{ - KubeClientConfig: kubeClients.Rest, - Name: "External Issuer", - CreateIssuerFunc: issuerBuilder.create, - DeleteIssuerFunc: issuerBuilder.delete, - UnsupportedFeatures: unsupportedFeatures, - }).Define() - - clusterIssuerBuilder := newIssuerBuilder("SimpleClusterIssuer") - (&certificates.Suite{ - KubeClientConfig: kubeClients.Rest, - Name: "External ClusterIssuer", - CreateIssuerFunc: clusterIssuerBuilder.create, - DeleteIssuerFunc: clusterIssuerBuilder.delete, - UnsupportedFeatures: unsupportedFeatures, + (&rbac.Suite{ + KubeClientConfig: kubeConfig, }).Define() }) +*/ diff --git a/make/e2e-setup.mk b/make/e2e-setup.mk index 3089cbd..43cd120 100644 --- a/make/e2e-setup.mk +++ b/make/e2e-setup.mk @@ -78,7 +78,8 @@ e2e-setup-cert-manager: | kind-cluster images $(NEEDS_HELM) $(NEEDS_KUBECTL) --namespace cert-manager \ --repo https://charts.jetstack.io \ --set installCRDs=true \ - --set featureGates=ServerSideApply=true \ + --set featureGates="ServerSideApply=true\,LiteralCertificateSubject=true" \ + --set webhook.featureGates="ServerSideApply=true\,LiteralCertificateSubject=true" \ --set image.repository=$(quay.io/jetstack/cert-manager-controller.REPO) \ --set image.tag=$(quay.io/jetstack/cert-manager-controller.TAG) \ --set image.pullPolicy=Never \ From b52870a75bcf52c8679fda65debe6215fa75e5b7 Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Fri, 30 Jun 2023 00:18:24 +0200 Subject: [PATCH 10/13] create binary for conformance tests Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- Makefile | 17 +- conformance/certificates/suite.go | 36 +- conformance/certificates/tests.go | 27 +- .../certificatesigningrequests/suite.go | 50 +-- .../certificatesigningrequests/tests.go | 30 +- conformance/conformance_test.go | 89 +++++ conformance/framework/cleanup.go | 63 --- conformance/framework/framework.go | 31 +- conformance/framework/helper/certificates.go | 50 ++- .../helper/certificatesigningrequests.go | 12 +- .../framework/helper/featureset/featureset.go | 30 +- conformance/framework/helper/secret.go | 23 +- conformance/framework/helper/validate.go | 4 +- .../framework/helper/validation/validation.go | 6 +- conformance/framework/testenv.go | 16 +- conformance/framework/util.go | 49 --- conformance/go.mod | 86 +++++ conformance/go.sum | 358 ++++++++++++++++++ conformance/rbac/certificate.go | 2 +- conformance/rbac/certificaterequest.go | 2 +- conformance/rbac/doc.go | 2 +- conformance/rbac/issuer.go | 2 +- go.mod | 14 +- go.sum | 16 - .../simple/api/simple_cluster_issuer_types.go | 2 +- .../simple/api/simple_issuer_types.go | 2 +- .../testsetups/simple/controller/signer.go | 2 +- .../testsetups/simple/deploy/rbac/role.yaml | 4 +- .../simple/e2e/conformance/conformance.go | 94 ----- .../e2e/conformance/conformance_test.go | 19 - .../simple/e2e/conformance/issuer_builder.go | 103 ----- .../simple/example/simple-cluster-issuer.yaml | 5 + .../simple/example/simple-issuer.yaml | 5 + make/tools.mk | 2 +- 34 files changed, 702 insertions(+), 551 deletions(-) create mode 100644 conformance/conformance_test.go delete mode 100644 conformance/framework/cleanup.go delete mode 100644 conformance/framework/util.go create mode 100644 conformance/go.mod create mode 100644 conformance/go.sum delete mode 100644 internal/testsetups/simple/e2e/conformance/conformance.go delete mode 100644 internal/testsetups/simple/e2e/conformance/conformance_test.go delete mode 100644 internal/testsetups/simple/e2e/conformance/issuer_builder.go create mode 100644 internal/testsetups/simple/example/simple-cluster-issuer.yaml create mode 100644 internal/testsetups/simple/example/simple-issuer.yaml diff --git a/Makefile b/Makefile index e439ce8..8621602 100644 --- a/Makefile +++ b/Makefile @@ -157,15 +157,26 @@ test-e2e-deps: TEST_MODE := E2E test-e2e-deps: DOCKER_REGISTRY := kind.local test-e2e-deps: e2e-setup docker-build test-e2e-envs install +$(BINDIR)/conformance.test: | $(NEEDS_GINKGO) + $(GINKGO) build ./conformance/ --trimpath --cover --require-suite + mv ./conformance/conformance.test $@ + .PHONY: test test: test-unit-deps | $(NEEDS_GO) $(NEEDS_GOTESTSUM) ## Run unit tests. $(GOTESTSUM) ./... -coverprofile cover.out +# $(GOTESTSUM) ./internal/testsetups/simple/e2e/... -coverprofile cover.out -timeout 5m + .PHONY: test-e2e -test-e2e: test-e2e-deps | $(NEEDS_GOTESTSUM) $(NEEDS_GINKGO) ## Run e2e tests. This creates a Kind cluster, installs dependencies, deploys the issuer-lib and runs the E2E tests. - $(GOTESTSUM) ./internal/testsetups/simple/e2e/... -coverprofile cover.out -timeout 5m +test-e2e: test-e2e-deps | $(NEEDS_GOTESTSUM) $(NEEDS_GINKGO) $(BINDIR)/conformance.test ## Run e2e tests. This creates a Kind cluster, installs dependencies, deploys the issuer-lib and runs the E2E tests. + + + kubectl apply -f internal/testsetups/simple/example/simple-cluster-issuer.yaml - $(GINKGO) ./internal/testsetups/simple/e2e/conformance/... + $(GINKGO) -procs=10 run $(BINDIR)/conformance.test -- \ + --cm-issuers=testing.cert-manager.io/SimpleClusterIssuer/simple-cluster-issuer \ + --k8s-issuers=simpleclusterissuers.testing.cert-manager.io/simple-cluster-issuer \ + --unsupported-features=SaveCAToSecret \ ##@ Build diff --git a/conformance/certificates/suite.go b/conformance/certificates/suite.go index 4e74847..4435cda 100644 --- a/conformance/certificates/suite.go +++ b/conformance/certificates/suite.go @@ -22,8 +22,8 @@ import ( cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" "k8s.io/client-go/rest" - "github.com/cert-manager/issuer-lib/conformance/framework" - "github.com/cert-manager/issuer-lib/conformance/framework/helper/featureset" + "conformance/framework" + "conformance/framework/helper/featureset" . "github.com/onsi/ginkgo/v2" ) @@ -39,18 +39,10 @@ type Suite struct { // This field must be provided. Name string - // CreateIssuerFunc is a function that provisions a new issuer resource and - // returns an ObjectReference to that Issuer that will be used as the - // IssuerRef on Certificate resources that this suite creates. - // This field must be provided. - CreateIssuerFunc func(*framework.Framework, context.Context) cmmeta.ObjectReference - - // DeleteIssuerFunc is a function that is run after the test has completed - // in order to clean up resources created for a test (e.g. the resources - // created in CreateIssuerFunc). - // This function will be run regardless whether the test passes or fails. - // If not specified, this function will be skipped. - DeleteIssuerFunc func(*framework.Framework, context.Context, cmmeta.ObjectReference) + // IssuerRef is reference to the issuer resource that this test suite will + // test against. All Certificate resources created by this suite will be + // created with this issuer reference. + IssuerRef cmmeta.ObjectReference // DomainSuffix is a suffix used on all domain requests. // This is useful when the issuer being tested requires special @@ -76,8 +68,8 @@ func (s *Suite) complete(f *framework.Framework) { Fail("Name must be set") } - if s.CreateIssuerFunc == nil { - Fail("CreateIssuerFunc must be set") + if s.IssuerRef != (cmmeta.ObjectReference{}) && s.IssuerRef.Name == "" { + Fail("IssuerRef must be set") } if s.DomainSuffix == "" { @@ -92,20 +84,12 @@ func (s *Suite) complete(f *framework.Framework) { } // it is called by the tests to in Define() to setup and run the test -func (s *Suite) it(f *framework.Framework, name string, fn func(cmmeta.ObjectReference), requiredFeatures ...featureset.Feature) { +func (s *Suite) it(f *framework.Framework, name string, fn func(context.Context, cmmeta.ObjectReference), requiredFeatures ...featureset.Feature) { if !s.checkFeatures(requiredFeatures...) { return } It(name, func(ctx context.Context) { - By("Creating an issuer resource") - issuerRef := s.CreateIssuerFunc(f, ctx) - defer func() { - if s.DeleteIssuerFunc != nil { - By("Cleaning up the issuer resource") - s.DeleteIssuerFunc(f, ctx, issuerRef) - } - }() - fn(issuerRef) + fn(ctx, s.IssuerRef) }) } diff --git a/conformance/certificates/tests.go b/conformance/certificates/tests.go index 084f53d..059787b 100644 --- a/conformance/certificates/tests.go +++ b/conformance/certificates/tests.go @@ -34,11 +34,11 @@ import ( "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/util/retry" - "github.com/cert-manager/issuer-lib/conformance/framework" - "github.com/cert-manager/issuer-lib/conformance/framework/helper/featureset" - "github.com/cert-manager/issuer-lib/conformance/framework/helper/validation" - "github.com/cert-manager/issuer-lib/conformance/framework/helper/validation/certificates" - e2eutil "github.com/cert-manager/issuer-lib/conformance/util" + "conformance/framework" + "conformance/framework/helper/featureset" + "conformance/framework/helper/validation" + "conformance/framework/helper/validation/certificates" + e2eutil "conformance/util" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -49,7 +49,6 @@ import ( // automatically called. func (s *Suite) Define() { Describe("with issuer type "+s.Name, func() { - ctx := context.Background() f := framework.NewFramework("certificates", s.KubeClientConfig) sharedIPAddress := "127.0.0.1" @@ -371,7 +370,7 @@ func (s *Suite) Define() { } defineTest := func(test testCase) { - s.it(f, test.name, func(issuerRef cmmeta.ObjectReference) { + s.it(f, test.name, func(ctx context.Context, issuerRef cmmeta.ObjectReference) { certificate := &cmapi.Certificate{ ObjectMeta: metav1.ObjectMeta{ Name: "testcert", @@ -393,7 +392,7 @@ func (s *Suite) Define() { Expect(err).NotTo(HaveOccurred()) By("Waiting for the Certificate to be issued...") - certificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(ctx, certificate, time.Minute*8) + certificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(ctx, certificate.Name, certificate.Namespace, certificate.Generation, time.Minute*8) Expect(err).NotTo(HaveOccurred()) By("Validating the issued Certificate...") @@ -407,7 +406,7 @@ func (s *Suite) Define() { defineTest(tc) } - s.it(f, "should issue another certificate with the same private key if the existing certificate and CertificateRequest are deleted", func(issuerRef cmmeta.ObjectReference) { + s.it(f, "should issue another certificate with the same private key if the existing certificate and CertificateRequest are deleted", func(ctx context.Context, issuerRef cmmeta.ObjectReference) { testCertificate := &cmapi.Certificate{ ObjectMeta: metav1.ObjectMeta{ Name: "testcert", @@ -424,7 +423,7 @@ func (s *Suite) Define() { Expect(err).NotTo(HaveOccurred()) By("Waiting for the Certificate to be issued...") - testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(ctx, testCertificate, time.Minute*8) + testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(ctx, testCertificate.Name, testCertificate.Namespace, testCertificate.Generation, time.Minute*8) Expect(err).NotTo(HaveOccurred()) By("Validating the issued Certificate...") @@ -447,7 +446,7 @@ func (s *Suite) Define() { Expect(err).NotTo(HaveOccurred(), "failed to update secret by deleting the signed certificate data") By("Waiting for the Certificate to re-issue a certificate") - sec, err = f.Helper().WaitForSecretCertificateData(ctx, f.Namespace.Name, sec.Name, time.Minute*8) + sec, err = f.Helper().WaitForSecretCertificateData(ctx, sec.Name, f.Namespace.Name, time.Minute*8) Expect(err).NotTo(HaveOccurred(), "failed to wait for secret to have a valid 2nd certificate") crtPEM2 := sec.Data[corev1.TLSCertKey] @@ -463,7 +462,7 @@ func (s *Suite) Define() { } }, featureset.ReusePrivateKeyFeature, featureset.OnlySAN) - s.it(f, "should allow updating an existing certificate with a new DNS Name", func(issuerRef cmmeta.ObjectReference) { + s.it(f, "should allow updating an existing certificate with a new DNS Name", func(ctx context.Context, issuerRef cmmeta.ObjectReference) { testCertificate := &cmapi.Certificate{ ObjectMeta: metav1.ObjectMeta{ Name: "testcert", @@ -482,7 +481,7 @@ func (s *Suite) Define() { Expect(err).NotTo(HaveOccurred()) By("Waiting for the Certificate to be ready") - testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(ctx, testCertificate, time.Minute*8) + testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(ctx, testCertificate.Name, testCertificate.Namespace, testCertificate.Generation, time.Minute*8) Expect(err).NotTo(HaveOccurred()) By("Sanity-check the issued Certificate") @@ -507,7 +506,7 @@ func (s *Suite) Define() { Expect(err).NotTo(HaveOccurred()) By("Waiting for the Certificate Ready condition to be updated") - testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(ctx, testCertificate, time.Minute*8) + testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(ctx, testCertificate.Name, testCertificate.Namespace, testCertificate.Generation, time.Minute*8) Expect(err).NotTo(HaveOccurred()) By("Sanity-check the issued Certificate") diff --git a/conformance/certificatesigningrequests/suite.go b/conformance/certificatesigningrequests/suite.go index f03e4a6..3da20e8 100644 --- a/conformance/certificatesigningrequests/suite.go +++ b/conformance/certificatesigningrequests/suite.go @@ -18,13 +18,11 @@ package certificatesigningrequests import ( "context" - "crypto" - certificatesv1 "k8s.io/api/certificates/v1" "k8s.io/client-go/rest" - "github.com/cert-manager/issuer-lib/conformance/framework" - "github.com/cert-manager/issuer-lib/conformance/framework/helper/featureset" + "conformance/framework" + "conformance/framework/helper/featureset" . "github.com/onsi/ginkgo/v2" ) @@ -40,32 +38,10 @@ type Suite struct { // This field must be provided. Name string - // CreateIssuerFunc is a function that provisions a new issuer resource and - // returns an SignerName to that Issuer that will be used as the SignerName - // on CertificateSigningRequest resources that this suite creates. - // This field must be provided. - CreateIssuerFunc func(*framework.Framework, context.Context) string - - // DeleteIssuerFunc is a function that is run after the test has completed - // in order to clean up resources created for a test (e.g. the resources - // created in CreateIssuerFunc). - // This function will be run regardless whether the test passes or fails. - // If not specified, this function will be skipped. - DeleteIssuerFunc func(*framework.Framework, context.Context, string) - - // ProvisionFunc is a function that is run every test just before the - // CertificateSigningRequest is created within a test. This is used to - // provision or create any resources that are required by the Issuer to sign - // the CertificateSigningRequest. This could be for example to annotate the - // CertificateSigningRequest, or create a resource like a Secret needed for - // signing. - // If not specified, this function will be skipped. - ProvisionFunc func(*framework.Framework, context.Context, *certificatesv1.CertificateSigningRequest, crypto.Signer) - - // DeProvisionFunc is run after every test. This is to be used to remove and - // clean-up any resources which may have been created by ProvisionFunc. - // If not specified, this function will be skipped. - DeProvisionFunc func(*framework.Framework, context.Context, *certificatesv1.CertificateSigningRequest) + // SignerName is the name of the signer that the conformance suite will test + // against. All CertificateSigningRequest resources created by this suite + // will be created with this signer name. + SignerName string // DomainSuffix is a suffix used on all domain requests. // This is useful when the issuer being tested requires special @@ -91,8 +67,8 @@ func (s *Suite) complete(f *framework.Framework) { Fail("Name must be set") } - if s.CreateIssuerFunc == nil { - Fail("CreateIssuerFunc must be set") + if s.SignerName == "" { + Fail("SignerName must be set") } if s.DomainSuffix == "" { @@ -112,15 +88,7 @@ func (s *Suite) it(f *framework.Framework, name string, fn func(context.Context, return } It(name, func(ctx context.Context) { - By("Creating an issuer resource") - signerName := s.CreateIssuerFunc(f, ctx) - defer func() { - if s.DeleteIssuerFunc != nil { - By("Cleaning up the issuer resource") - s.DeleteIssuerFunc(f, ctx, signerName) - } - }() - fn(ctx, signerName) + fn(ctx, s.SignerName) }) } diff --git a/conformance/certificatesigningrequests/tests.go b/conformance/certificatesigningrequests/tests.go index d55357b..163f223 100644 --- a/conformance/certificatesigningrequests/tests.go +++ b/conformance/certificatesigningrequests/tests.go @@ -30,11 +30,11 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/utils/pointer" - "github.com/cert-manager/issuer-lib/conformance/framework" - "github.com/cert-manager/issuer-lib/conformance/framework/helper/featureset" - "github.com/cert-manager/issuer-lib/conformance/framework/helper/validation" - "github.com/cert-manager/issuer-lib/conformance/framework/helper/validation/certificatesigningrequests" - e2eutil "github.com/cert-manager/issuer-lib/conformance/util" + "conformance/framework" + "conformance/framework/helper/featureset" + "conformance/framework/helper/validation" + "conformance/framework/helper/validation/certificatesigningrequests" + e2eutil "conformance/util" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -455,26 +455,12 @@ func (s *Suite) Define() { }, } - // Provision any resources needed for the request, or modify the - // request based on Issuer requirements - if s.ProvisionFunc != nil { - s.ProvisionFunc(f, ctx, kubeCSR, key) - } - // Ensure related resources are cleaned up at the end of the test - if s.DeProvisionFunc != nil { - defer s.DeProvisionFunc(f, ctx, kubeCSR) - } - // Create the request, and delete at the end of the test By("Creating a CertificateSigningRequest") Expect(f.CRClient.Create(ctx, kubeCSR)).NotTo(HaveOccurred()) - defer func() { - // Create a new context with a timeout to prevent the deletion of the - // CertificateSigningRequest from blocking test completion. - deleteCtx, cancel := context.WithTimeout(context.Background(), time.Second*30) - defer cancel() - Expect(f.CRClient.Delete(deleteCtx, kubeCSR)).NotTo(HaveOccurred()) - }() + DeferCleanup(func(ctx context.Context) { + Expect(f.CRClient.Delete(ctx, kubeCSR)).NotTo(HaveOccurred()) + }) // Approve the request for testing, so that cert-manager may sign the // request. diff --git a/conformance/conformance_test.go b/conformance/conformance_test.go new file mode 100644 index 0000000..e44305f --- /dev/null +++ b/conformance/conformance_test.go @@ -0,0 +1,89 @@ +package conformance + +import ( + "flag" + "strings" + "testing" + + v1 "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" + ctrl "sigs.k8s.io/controller-runtime" + + "conformance/certificates" + "conformance/certificatesigningrequests" + "conformance/framework/helper/featureset" +) + +type arrayFlags []string + +func (i *arrayFlags) String() string { + return "my string representation" +} + +func (i *arrayFlags) Set(value string) error { + *i = append(*i, value) + return nil +} + +var unsupportedFeatures arrayFlags + +var cmIssuerReferences arrayFlags +var k8sIssuerReferences arrayFlags + +func init() { + flag.Var(&unsupportedFeatures, "unsupported-features", "list of features that are not supported by this invocation of the test suite") + flag.Var(&cmIssuerReferences, "cm-issuers", "list of issuer references to use for conformance tests") + flag.Var(&k8sIssuerReferences, "k8s-issuers", "list of issuer references to use for conformance tests") +} + +func parseCMReference(g *gomega.WithT, ref string) v1.ObjectReference { + parts := strings.SplitN(ref, "/", 3) + g.Expect(parts).To(gomega.HaveLen(3), "invalid issuer reference %q: expected format //", ref) + + return v1.ObjectReference{ + Group: parts[0], + Kind: parts[1], + Name: parts[2], + } +} + +func TestConformance(t *testing.T) { + gomega.RegisterFailHandler(ginkgo.Fail) + + g := gomega.NewGomegaWithT(t) + + restConfig, err := ctrl.GetConfig() + g.Expect(err).To(gomega.BeNil(), "failed to get rest config", err) + + restConfig.Burst = 9000 + restConfig.QPS = 9000 + + unsupportedFeatureSet := featureset.NewFeatureSet() + + for _, value := range unsupportedFeatures { + feature, err := featureset.ConvertToFeature(value) + g.Expect(err).To(gomega.BeNil(), "failed to convert unsupported feature %q: %v", value, err) + unsupportedFeatureSet.Add(feature) + } + + for _, ref := range cmIssuerReferences { + (&certificates.Suite{ + KubeClientConfig: restConfig, + Name: ref, + IssuerRef: parseCMReference(g, ref), + UnsupportedFeatures: unsupportedFeatureSet, + }).Define() + } + + for _, ref := range k8sIssuerReferences { + (&certificatesigningrequests.Suite{ + KubeClientConfig: restConfig, + Name: ref, + SignerName: ref, + UnsupportedFeatures: unsupportedFeatureSet, + }).Define() + } + + ginkgo.RunSpecs(t, "cert-manager conformance suite") +} diff --git a/conformance/framework/cleanup.go b/conformance/framework/cleanup.go deleted file mode 100644 index b3063ba..0000000 --- a/conformance/framework/cleanup.go +++ /dev/null @@ -1,63 +0,0 @@ -// +skip_license_check - -/* -Copyright 2016 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package framework - -import "sync" - -type CleanupActionHandle *int - -var cleanupActionsLock sync.Mutex -var cleanupActions = map[CleanupActionHandle]func(){} - -// AddCleanupAction installs a function that will be called in the event of the -// whole test being terminated. This allows arbitrary pieces of the overall -// test to hook into SynchronizedAfterSuite(). -func AddCleanupAction(fn func()) CleanupActionHandle { - p := CleanupActionHandle(new(int)) - cleanupActionsLock.Lock() - defer cleanupActionsLock.Unlock() - cleanupActions[p] = fn - return p -} - -// RemoveCleanupAction removes a function that was installed by -// AddCleanupAction. -func RemoveCleanupAction(p CleanupActionHandle) { - cleanupActionsLock.Lock() - defer cleanupActionsLock.Unlock() - delete(cleanupActions, p) -} - -// RunCleanupActions runs all functions installed by AddCleanupAction. It does -// not remove them (see RemoveCleanupAction) but it does run unlocked, so they -// may remove themselves. -func RunCleanupActions() { - list := []func(){} - func() { - cleanupActionsLock.Lock() - defer cleanupActionsLock.Unlock() - for _, fn := range cleanupActions { - list = append(list, fn) - } - }() - // Run unlocked. - for _, fn := range list { - fn() - } -} diff --git a/conformance/framework/framework.go b/conformance/framework/framework.go index 29562e3..939db44 100644 --- a/conformance/framework/framework.go +++ b/conformance/framework/framework.go @@ -17,6 +17,8 @@ limitations under the License. package framework import ( + "context" + clientset "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned" certmgrscheme "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned/scheme" api "k8s.io/api/core/v1" @@ -30,7 +32,7 @@ import ( crclient "sigs.k8s.io/controller-runtime/pkg/client" gwapi "sigs.k8s.io/gateway-api/pkg/client/clientset/versioned" - "github.com/cert-manager/issuer-lib/conformance/framework/helper" + "conformance/framework/helper" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -55,11 +57,6 @@ type Framework struct { // Namespace in which all test resources should reside Namespace *api.Namespace - // To make sure that this framework cleans up after itself, no matter what, - // we install a Cleanup action before each test and clear it after. If we - // should abort, the AfterSuite hook should run all Cleanup actions. - cleanupHandle CleanupActionHandle - helper *helper.Helper } @@ -80,15 +77,10 @@ func NewFramework(baseName string, kubeClientConfig *rest.Config) *Framework { } // BeforeEach gets a client and makes a namespace. -func (f *Framework) BeforeEach() { - f.cleanupHandle = AddCleanupAction(f.AfterEach) - +func (f *Framework) BeforeEach(ctx context.Context) { var err error kubeConfig := rest.CopyConfig(f.KubeClientConfig) - kubeConfig.Burst = 9000 - kubeConfig.QPS = 9000 - f.KubeClientConfig = kubeConfig f.KubeClientSet, err = kubernetes.NewForConfig(kubeConfig) @@ -116,13 +108,13 @@ func (f *Framework) BeforeEach() { Expect(err).NotTo(HaveOccurred()) By("Building a namespace api object") - f.Namespace, err = f.CreateKubeNamespace(f.BaseName) + f.Namespace, err = f.CreateKubeNamespace(ctx, f.BaseName) Expect(err).NotTo(HaveOccurred()) By("Using the namespace " + f.Namespace.Name) By("Building a ResourceQuota api object") - _, err = f.CreateKubeResourceQuota() + _, err = f.CreateKubeResourceQuota(ctx) Expect(err).NotTo(HaveOccurred()) f.helper.CMClient = f.CertManagerClientSet @@ -130,11 +122,9 @@ func (f *Framework) BeforeEach() { } // AfterEach deletes the namespace, after reading its events. -func (f *Framework) AfterEach() { - RemoveCleanupAction(f.cleanupHandle) - +func (f *Framework) AfterEach(ctx context.Context) { By("Deleting test namespace") - err := f.DeleteKubeNamespace(f.Namespace.Name) + err := f.DeleteKubeNamespace(ctx, f.Namespace.Name) Expect(err).NotTo(HaveOccurred()) } @@ -142,11 +132,6 @@ func (f *Framework) Helper() *helper.Helper { return f.helper } -// CertManagerDescribe is a wrapper function for ginkgo describe. Adds namespacing. -func CertManagerDescribe(text string, body func()) bool { - return Describe("[cert-manager] "+text, body) -} - func ConformanceDescribe(text string, body func()) bool { return Describe("[Conformance] "+text, body) } diff --git a/conformance/framework/helper/certificates.go b/conformance/framework/helper/certificates.go index fff9d2e..096c214 100644 --- a/conformance/framework/helper/certificates.go +++ b/conformance/framework/helper/certificates.go @@ -24,44 +24,36 @@ import ( apiutil "github.com/cert-manager/cert-manager/pkg/api/util" v1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" - clientset "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned/typed/certmanager/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/wait" - "github.com/cert-manager/issuer-lib/conformance/framework/log" + "conformance/framework/log" ) -func (h *Helper) waitForCertificateCondition(pollCtx context.Context, client clientset.CertificateInterface, name string, check func(*v1.Certificate) bool, timeout time.Duration) (*v1.Certificate, error) { - var certificate *v1.Certificate - pollErr := wait.PollUntilContextTimeout(pollCtx, 500*time.Millisecond, timeout, true, func(ctx context.Context) (bool, error) { - var err error - certificate, err = client.Get(ctx, name, metav1.GetOptions{}) - if nil != err { - certificate = nil - return false, fmt.Errorf("error getting Certificate %v: %v", name, err) - } - - return check(certificate), nil - }) - - return certificate, pollErr -} - // WaitForCertificateReadyAndDoneIssuing waits for the certificate resource to be in a Ready=True state and not be in an Issuing state. // The Ready=True condition will be checked against the provided certificate to make sure that it is up-to-date (condition gen. >= cert gen.). -func (h *Helper) WaitForCertificateReadyAndDoneIssuing(ctx context.Context, cert *v1.Certificate, timeout time.Duration) (*v1.Certificate, error) { +func (h *Helper) WaitForCertificateReadyAndDoneIssuing(pollCtx context.Context, name string, namespace string, minGeneration int64, timeout time.Duration) (*v1.Certificate, error) { ready_true_condition := v1.CertificateCondition{ Type: v1.CertificateConditionReady, Status: cmmeta.ConditionTrue, - ObservedGeneration: cert.Generation, + ObservedGeneration: minGeneration, } issuing_true_condition := v1.CertificateCondition{ Type: v1.CertificateConditionIssuing, Status: cmmeta.ConditionTrue, } + + var certificate *v1.Certificate logf, done := log.LogBackoff() defer done() - return h.waitForCertificateCondition(ctx, h.CMClient.CertmanagerV1().Certificates(cert.Namespace), cert.Name, func(certificate *v1.Certificate) bool { + + err := wait.PollUntilContextTimeout(pollCtx, 500*time.Millisecond, timeout, true, func(ctx context.Context) (bool, error) { + var err error + certificate, err = h.CMClient.CertmanagerV1().Certificates(namespace).Get(ctx, name, metav1.GetOptions{}) + if err != nil { + return false, fmt.Errorf("error getting Certificate %v: %w", name, err) + } + if !apiutil.CertificateHasConditionWithObservedGeneration(certificate, ready_true_condition) { logf( "Expected Certificate %v condition %v=%v (generation >= %v) but it has: %v", @@ -71,19 +63,25 @@ func (h *Helper) WaitForCertificateReadyAndDoneIssuing(ctx context.Context, cert ready_true_condition.ObservedGeneration, certificate.Status.Conditions, ) - return false + return false, nil } if apiutil.CertificateHasCondition(certificate, issuing_true_condition) { logf("Expected Certificate %v condition %v to be missing but it has: %v", certificate.Name, issuing_true_condition.Type, certificate.Status.Conditions) - return false + return false, nil } if certificate.Status.NextPrivateKeySecretName != nil { logf("Expected Certificate %v 'next-private-key-secret-name' attribute to be empty but has: %v", certificate.Name, *certificate.Status.NextPrivateKeySecretName) - return false + return false, nil } - return true - }, timeout) + return true, nil + }) + + if err != nil { + return certificate, err + } + + return certificate, nil } diff --git a/conformance/framework/helper/certificatesigningrequests.go b/conformance/framework/helper/certificatesigningrequests.go index e633f89..25ddafb 100644 --- a/conformance/framework/helper/certificatesigningrequests.go +++ b/conformance/framework/helper/certificatesigningrequests.go @@ -26,7 +26,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/wait" - "github.com/cert-manager/issuer-lib/conformance/framework/log" + "conformance/framework/log" ) // WaitForCertificateSigningRequestSigned waits for the @@ -35,12 +35,12 @@ func (h *Helper) WaitForCertificateSigningRequestSigned(pollCtx context.Context, var csr *certificatesv1.CertificateSigningRequest logf, done := log.LogBackoff() defer done() - err := wait.PollUntilContextTimeout(pollCtx, time.Second, timeout, true, func(ctx context.Context) (bool, error) { + + err := wait.PollUntilContextTimeout(pollCtx, 500*time.Millisecond, timeout, true, func(ctx context.Context) (bool, error) { var err error - logf("Waiting for CertificateSigningRequest %s to be ready", name) csr, err = h.KubeClient.CertificatesV1().CertificateSigningRequests().Get(ctx, name, metav1.GetOptions{}) if err != nil { - return false, fmt.Errorf("error getting CertificateSigningRequest %s: %v", name, err) + return false, fmt.Errorf("error getting CertificateSigningRequest %s: %w", name, err) } if util.CertificateSigningRequestIsFailed(csr) { @@ -48,13 +48,15 @@ func (h *Helper) WaitForCertificateSigningRequestSigned(pollCtx context.Context, } if len(csr.Status.Certificate) == 0 { + logf("CertificateSigningRequest is not yet signed %s", csr.Name) return false, nil } + return true, nil }) if err != nil { - return nil, err + return csr, err } return csr, nil diff --git a/conformance/framework/helper/featureset/featureset.go b/conformance/framework/helper/featureset/featureset.go index 5e67d26..eda636b 100644 --- a/conformance/framework/helper/featureset/featureset.go +++ b/conformance/framework/helper/featureset/featureset.go @@ -16,7 +16,10 @@ limitations under the License. package featureset -import "strings" +import ( + "fmt" + "strings" +) // NewFeatureSet constructs a new feature set with the given features. func NewFeatureSet(feats ...Feature) FeatureSet { @@ -79,6 +82,31 @@ func (fs FeatureSet) String() string { return strings.Join(featsSlice, ", ") } +func ConvertToFeature(value string) (Feature, error) { + switch Feature(value) { + case + IPAddressFeature, + DurationFeature, + WildcardsFeature, + ECDSAFeature, + ReusePrivateKeyFeature, + URISANsFeature, + EmailSANsFeature, + CommonNameFeature, + KeyUsagesFeature, + OnlySAN, + SaveCAToSecret, + SaveRootCAToSecret, + Ed25519FeatureSet, + IssueCAFeature, + LongDomainFeatureSet, + LiteralSubjectFeature: + return Feature(value), nil + default: + return "", fmt.Errorf("unknown feature %q", value) + } +} + type Feature string // String returns the Feature name as a string diff --git a/conformance/framework/helper/secret.go b/conformance/framework/helper/secret.go index d409fbf..8cd0f92 100644 --- a/conformance/framework/helper/secret.go +++ b/conformance/framework/helper/secret.go @@ -25,34 +25,33 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/wait" - "github.com/cert-manager/issuer-lib/conformance/framework/log" + "conformance/framework/log" ) // WaitForSecretCertificateData waits for the certificate data to be ready // inside a Secret created by cert-manager. -func (h *Helper) WaitForSecretCertificateData(pollCtx context.Context, ns, name string, timeout time.Duration) (*corev1.Secret, error) { +func (h *Helper) WaitForSecretCertificateData(pollCtx context.Context, name string, namespace string, timeout time.Duration) (*corev1.Secret, error) { var secret *corev1.Secret logf, done := log.LogBackoff() defer done() - err := wait.PollUntilContextTimeout(pollCtx, time.Second, timeout, true, func(ctx context.Context) (bool, error) { + + err := wait.PollUntilContextTimeout(pollCtx, 500*time.Millisecond, timeout, true, func(ctx context.Context) (bool, error) { var err error - logf("Waiting for Secret %s:%s to contain a certificate", ns, name) - secret, err = h.KubeClient.CoreV1().Secrets(ns).Get(ctx, name, metav1.GetOptions{}) + secret, err = h.KubeClient.CoreV1().Secrets(namespace).Get(ctx, name, metav1.GetOptions{}) if err != nil { - return false, fmt.Errorf("error getting secret %s: %s", name, err) + return false, fmt.Errorf("error getting Secret %s: %w", name, err) } - if len(secret.Data[corev1.TLSCertKey]) > 0 { - return true, nil + if len(secret.Data[corev1.TLSCertKey]) == 0 { + logf("Secret still does not contain certificate data %s/%s", secret.Namespace, secret.Name) + return false, nil } - logf("Secret still does not contain certificate data %s/%s", - secret.Namespace, secret.Name) - return false, nil + return true, nil }) if err != nil { - return nil, err + return secret, err } return secret, nil diff --git a/conformance/framework/helper/validate.go b/conformance/framework/helper/validate.go index 991c64d..b90c43c 100644 --- a/conformance/framework/helper/validate.go +++ b/conformance/framework/helper/validate.go @@ -24,8 +24,8 @@ import ( cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "github.com/cert-manager/issuer-lib/conformance/framework/helper/validation/certificates" - "github.com/cert-manager/issuer-lib/conformance/framework/helper/validation/certificatesigningrequests" + "conformance/framework/helper/validation/certificates" + "conformance/framework/helper/validation/certificatesigningrequests" ) // ValidateCertificate retrieves the issued certificate and runs all validation functions diff --git a/conformance/framework/helper/validation/validation.go b/conformance/framework/helper/validation/validation.go index 3d680a4..7b78fa7 100644 --- a/conformance/framework/helper/validation/validation.go +++ b/conformance/framework/helper/validation/validation.go @@ -17,9 +17,9 @@ limitations under the License. package validation import ( - "github.com/cert-manager/issuer-lib/conformance/framework/helper/featureset" - "github.com/cert-manager/issuer-lib/conformance/framework/helper/validation/certificates" - "github.com/cert-manager/issuer-lib/conformance/framework/helper/validation/certificatesigningrequests" + "conformance/framework/helper/featureset" + "conformance/framework/helper/validation/certificates" + "conformance/framework/helper/validation/certificatesigningrequests" ) func CertificateSetForUnsupportedFeatureSet(fs featureset.FeatureSet) []certificates.ValidationFunc { diff --git a/conformance/framework/testenv.go b/conformance/framework/testenv.go index 9ad1fee..16cc6f2 100644 --- a/conformance/framework/testenv.go +++ b/conformance/framework/testenv.go @@ -36,19 +36,19 @@ const ( ) // CreateKubeNamespace creates a new Kubernetes Namespace for a test. -func (f *Framework) CreateKubeNamespace(baseName string) (*v1.Namespace, error) { +func (f *Framework) CreateKubeNamespace(ctx context.Context, baseName string) (*v1.Namespace, error) { ns := &v1.Namespace{ ObjectMeta: metav1.ObjectMeta{ GenerateName: fmt.Sprintf("e2e-tests-%v-", baseName), }, } - return f.KubeClientSet.CoreV1().Namespaces().Create(context.TODO(), ns, metav1.CreateOptions{}) + return f.KubeClientSet.CoreV1().Namespaces().Create(ctx, ns, metav1.CreateOptions{}) } // CreateKubeResourceQuota provisions a ResourceQuota resource in the target // namespace. -func (f *Framework) CreateKubeResourceQuota() (*v1.ResourceQuota, error) { +func (f *Framework) CreateKubeResourceQuota(ctx context.Context) (*v1.ResourceQuota, error) { quota := &v1.ResourceQuota{ ObjectMeta: metav1.ObjectMeta{ Name: "default-e2e-quota", @@ -65,18 +65,18 @@ func (f *Framework) CreateKubeResourceQuota() (*v1.ResourceQuota, error) { }, }, } - return f.KubeClientSet.CoreV1().ResourceQuotas(f.Namespace.Name).Create(context.TODO(), quota, metav1.CreateOptions{}) + return f.KubeClientSet.CoreV1().ResourceQuotas(f.Namespace.Name).Create(ctx, quota, metav1.CreateOptions{}) } // DeleteKubeNamespace will delete a namespace resource -func (f *Framework) DeleteKubeNamespace(namespace string) error { - return f.KubeClientSet.CoreV1().Namespaces().Delete(context.TODO(), namespace, metav1.DeleteOptions{}) +func (f *Framework) DeleteKubeNamespace(ctx context.Context, namespace string) error { + return f.KubeClientSet.CoreV1().Namespaces().Delete(ctx, namespace, metav1.DeleteOptions{}) } // WaitForKubeNamespaceNotExist will wait for the namespace with the given name // to not exist for up to 2 minutes. -func (f *Framework) WaitForKubeNamespaceNotExist(namespace string) error { - return wait.PollUntilContextTimeout(context.TODO(), Poll, time.Minute*2, true, func(ctx context.Context) (bool, error) { +func (f *Framework) WaitForKubeNamespaceNotExist(pollCtx context.Context, namespace string) error { + return wait.PollUntilContextTimeout(pollCtx, Poll, time.Minute*2, true, func(ctx context.Context) (bool, error) { _, err := f.KubeClientSet.CoreV1().Namespaces().Get(ctx, namespace, metav1.GetOptions{}) if apierrors.IsNotFound(err) { return true, nil diff --git a/conformance/framework/util.go b/conformance/framework/util.go deleted file mode 100644 index 104a402..0000000 --- a/conformance/framework/util.go +++ /dev/null @@ -1,49 +0,0 @@ -/* -Copyright 2020 The cert-manager Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package framework - -import ( - "fmt" - "time" - - "k8s.io/component-base/featuregate" - - . "github.com/cert-manager/issuer-lib/conformance/framework/log" - . "github.com/onsi/ginkgo/v2" -) - -func nowStamp() string { - return time.Now().Format(time.StampMilli) -} - -func Failf(format string, args ...interface{}) { - msg := fmt.Sprintf(format, args...) - Logf(msg) - Fail(nowStamp()+": "+msg, 1) -} - -func Skipf(format string, args ...interface{}) { - msg := fmt.Sprintf(format, args...) - Logf("INFO", msg) - Skip(nowStamp() + ": " + msg) -} - -func RequireFeatureGate(f *Framework, featureSet featuregate.FeatureGate, gate featuregate.Feature) { - if !featureSet.Enabled(gate) { - Skipf("feature gate %q is not enabled, skipping test", gate) - } -} diff --git a/conformance/go.mod b/conformance/go.mod new file mode 100644 index 0000000..0aa3663 --- /dev/null +++ b/conformance/go.mod @@ -0,0 +1,86 @@ +module conformance + +go 1.20 + +require ( + github.com/cert-manager/cert-manager v1.12.2 + github.com/kr/pretty v0.3.1 + github.com/onsi/ginkgo/v2 v2.11.0 + github.com/onsi/gomega v1.27.8 + k8s.io/api v0.27.3 + k8s.io/apiextensions-apiserver v0.27.3 + k8s.io/apimachinery v0.27.3 + k8s.io/client-go v0.27.3 + k8s.io/kube-aggregator v0.27.3 + k8s.io/utils v0.0.0-20230505201702-9f6742963106 + sigs.k8s.io/controller-runtime v0.15.0 + sigs.k8s.io/gateway-api v0.7.1 +) + +require ( + github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/blang/semver/v4 v4.0.0 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/emicklei/go-restful/v3 v3.9.0 // indirect + github.com/evanphx/json-patch/v5 v5.6.0 // indirect + github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/go-asn1-ber/asn1-ber v1.5.4 // indirect + github.com/go-ldap/ldap/v3 v3.4.4 // indirect + github.com/go-logr/logr v1.2.4 // indirect + github.com/go-logr/zapr v1.2.4 // indirect + github.com/go-openapi/jsonpointer v0.19.6 // indirect + github.com/go-openapi/jsonreference v0.20.1 // indirect + github.com/go-openapi/swag v0.22.3 // indirect + github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/google/gnostic v0.6.9 // indirect + github.com/google/go-cmp v0.5.9 // indirect + github.com/google/gofuzz v1.2.0 // indirect + github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect + github.com/google/uuid v1.3.0 // indirect + github.com/imdario/mergo v0.3.12 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/prometheus/client_golang v1.15.1 // indirect + github.com/prometheus/client_model v0.4.0 // indirect + github.com/prometheus/common v0.42.0 // indirect + github.com/prometheus/procfs v0.9.0 // indirect + github.com/rogpeppe/go-internal v1.10.0 // indirect + github.com/spf13/cobra v1.7.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + go.uber.org/atomic v1.9.0 // indirect + go.uber.org/multierr v1.6.0 // indirect + go.uber.org/zap v1.24.0 // indirect + golang.org/x/crypto v0.6.0 // indirect + golang.org/x/net v0.10.0 // indirect + golang.org/x/oauth2 v0.5.0 // indirect + golang.org/x/sys v0.9.0 // indirect + golang.org/x/term v0.8.0 // indirect + golang.org/x/text v0.9.0 // indirect + golang.org/x/time v0.3.0 // indirect + golang.org/x/tools v0.9.3 // indirect + gomodules.xyz/jsonpatch/v2 v2.3.0 // indirect + google.golang.org/appengine v1.6.7 // indirect + google.golang.org/protobuf v1.30.0 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/component-base v0.27.3 // indirect + k8s.io/klog/v2 v2.100.1 // indirect + k8s.io/kube-openapi v0.0.0-20230515203736-54b630e78af5 // indirect + sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect + sigs.k8s.io/yaml v1.3.0 // indirect +) diff --git a/conformance/go.sum b/conformance/go.sum new file mode 100644 index 0000000..0408b8e --- /dev/null +++ b/conformance/go.sum @@ -0,0 +1,358 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e h1:NeAW1fUYUEWhft7pkxDf6WoUvEZJ/uOKsvtpjLnn8MU= +github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= +github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= +github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cert-manager/cert-manager v1.12.2 h1:lJ7Xn0VhmBA4uOZb5dlSZzepu38ez73okOqgE24x8YM= +github.com/cert-manager/cert-manager v1.12.2/go.mod h1:ql0msU88JCcQSceN+PFjEY8U+AMe13y06vO2klJk8bs= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE= +github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= +github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww= +github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= +github.com/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM0rVwpMwimd3F3N0= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-asn1-ber/asn1-ber v1.5.4 h1:vXT6d/FNDiELJnLb6hGNa309LMsrCoYFvpwHDF0+Y1A= +github.com/go-asn1-ber/asn1-ber v1.5.4/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= +github.com/go-ldap/ldap/v3 v3.4.4 h1:qPjipEpt+qDa6SI/h1fzuGWoRUY+qqQ9sOZq67/PYUs= +github.com/go-ldap/ldap/v3 v3.4.4/go.mod h1:fe1MsuN5eJJ1FeLT/LEBVdWfNWKh459R7aXgXtJC+aI= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= +github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/zapr v1.2.4 h1:QHVo+6stLbfJmYGkQ7uGHUCu5hnAFAj6mDe6Ea0SeOo= +github.com/go-logr/zapr v1.2.4/go.mod h1:FyHWQIzQORZ0QVE1BtVHv3cKtNLuXsbNLtpuhNapBOA= +github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= +github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/jsonreference v0.20.1 h1:FBLnyygC4/IZZr893oiomc9XaghoveYTrLC1F86HID8= +github.com/go-openapi/jsonreference v0.20.1/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= +github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/gnostic v0.6.9 h1:ZK/5VhkoX835RikCHpSUJV9a+S3e1zLh59YnyWeBW+0= +github.com/google/gnostic v0.6.9/go.mod h1:Nm8234We1lq6iB9OmlgNv3nH91XLLVZHCDayfA3xq+E= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= +github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= +github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU= +github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM= +github.com/onsi/gomega v1.27.8 h1:gegWiwZjBsf2DgiSbf5hpokZ98JVDMcWkUiigk6/KXc= +github.com/onsi/gomega v1.27.8/go.mod h1:2J8vzI/s+2shY9XHRApDkdgPo1TKT7P2u6fXeJKFnNQ= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.15.1 h1:8tXpTmJbyH5lydzFPoxSIJ0J46jdh3tylbvM1xCv0LI= +github.com/prometheus/client_golang v1.15.1/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY= +github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= +github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM= +github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= +github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= +github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= +github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= +go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= +go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc= +golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.5.0 h1:HuArIo48skDwlrvM3sEdHXElYslAMsf3KwRkkW4MC4s= +golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s= +golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.9.3 h1:Gn1I8+64MsuTb/HpH+LmQtNas23LhUVr3rYZ0eKuaMM= +golang.org/x/tools v0.9.3/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gomodules.xyz/jsonpatch/v2 v2.3.0 h1:8NFhfS6gzxNqjLIYnZxg319wZ5Qjnx4m/CcX+Klzazc= +gomodules.xyz/jsonpatch/v2 v2.3.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +k8s.io/api v0.27.3 h1:yR6oQXXnUEBWEWcvPWS0jQL575KoAboQPfJAuKNrw5Y= +k8s.io/api v0.27.3/go.mod h1:C4BNvZnQOF7JA/0Xed2S+aUyJSfTGkGFxLXz9MnpIpg= +k8s.io/apiextensions-apiserver v0.27.3 h1:xAwC1iYabi+TDfpRhxh4Eapl14Hs2OftM2DN5MpgKX4= +k8s.io/apiextensions-apiserver v0.27.3/go.mod h1:BH3wJ5NsB9XE1w+R6SSVpKmYNyIiyIz9xAmBl8Mb+84= +k8s.io/apimachinery v0.27.3 h1:Ubye8oBufD04l9QnNtW05idcOe9Z3GQN8+7PqmuVcUM= +k8s.io/apimachinery v0.27.3/go.mod h1:XNfZ6xklnMCOGGFNqXG7bUrQCoR04dh/E7FprV6pb+E= +k8s.io/client-go v0.27.3 h1:7dnEGHZEJld3lYwxvLl7WoehK6lAq7GvgjxpA3nv1E8= +k8s.io/client-go v0.27.3/go.mod h1:2MBEKuTo6V1lbKy3z1euEGnhPfGZLKTS9tiJ2xodM48= +k8s.io/component-base v0.27.3 h1:g078YmdcdTfrCE4fFobt7qmVXwS8J/3cI1XxRi/2+6k= +k8s.io/component-base v0.27.3/go.mod h1:JNiKYcGImpQ44iwSYs6dysxzR9SxIIgQalk4HaCNVUY= +k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg= +k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/kube-aggregator v0.27.3 h1:0o/Q30C84hHvhUef7OOTHMhO2eCySOPHKOUUrhBwpfo= +k8s.io/kube-aggregator v0.27.3/go.mod h1:zbx67NbFee9cqjbXjib89/oOyrXdOq3UYStIBGazv08= +k8s.io/kube-openapi v0.0.0-20230515203736-54b630e78af5 h1:azYPdzztXxPSa8wb+hksEKayiz0o+PPisO/d+QhWnoo= +k8s.io/kube-openapi v0.0.0-20230515203736-54b630e78af5/go.mod h1:kzo02I3kQ4BTtEfVLaPbjvCkX97YqGve33wzlb3fofQ= +k8s.io/utils v0.0.0-20230505201702-9f6742963106 h1:EObNQ3TW2D+WptiYXlApGNLVy0zm/JIBVY9i+M4wpAU= +k8s.io/utils v0.0.0-20230505201702-9f6742963106/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/controller-runtime v0.15.0 h1:ML+5Adt3qZnMSYxZ7gAverBLNPSMQEibtzAgp0UPojU= +sigs.k8s.io/controller-runtime v0.15.0/go.mod h1:7ngYvp1MLT+9GeZ+6lH3LOlcHkp/+tzA/fmHa4iq9kk= +sigs.k8s.io/gateway-api v0.7.1 h1:Tts2jeepVkPA5rVG/iO+S43s9n7Vp7jCDhZDQYtPigQ= +sigs.k8s.io/gateway-api v0.7.1/go.mod h1:Xv0+ZMxX0lu1nSSDIIPEfbVztgNZ+3cfiYrJsa2Ooso= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= +sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= +sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= +sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/conformance/rbac/certificate.go b/conformance/rbac/certificate.go index c086406..1d7ee51 100644 --- a/conformance/rbac/certificate.go +++ b/conformance/rbac/certificate.go @@ -17,7 +17,7 @@ limitations under the License. package rbac import ( - "github.com/cert-manager/issuer-lib/conformance/framework" + "conformance/framework" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" diff --git a/conformance/rbac/certificaterequest.go b/conformance/rbac/certificaterequest.go index b309424..e2984b3 100644 --- a/conformance/rbac/certificaterequest.go +++ b/conformance/rbac/certificaterequest.go @@ -17,7 +17,7 @@ limitations under the License. package rbac import ( - "github.com/cert-manager/issuer-lib/conformance/framework" + "conformance/framework" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" diff --git a/conformance/rbac/doc.go b/conformance/rbac/doc.go index d9731f6..be4c98e 100644 --- a/conformance/rbac/doc.go +++ b/conformance/rbac/doc.go @@ -26,7 +26,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" - "github.com/cert-manager/issuer-lib/conformance/framework" + "conformance/framework" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" diff --git a/conformance/rbac/issuer.go b/conformance/rbac/issuer.go index d1e9544..7447a5a 100644 --- a/conformance/rbac/issuer.go +++ b/conformance/rbac/issuer.go @@ -17,7 +17,7 @@ limitations under the License. package rbac import ( - "github.com/cert-manager/issuer-lib/conformance/framework" + "conformance/framework" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" diff --git a/go.mod b/go.mod index 5a81318..eb429fc 100644 --- a/go.mod +++ b/go.mod @@ -5,9 +5,6 @@ go 1.19 require ( github.com/cert-manager/cert-manager v1.12.2 github.com/go-logr/logr v1.2.4 - github.com/kr/pretty v0.3.1 - github.com/onsi/ginkgo/v2 v2.9.5 - github.com/onsi/gomega v1.27.7 github.com/stretchr/testify v1.8.4 go.uber.org/zap v1.24.0 golang.org/x/sync v0.3.0 @@ -15,12 +12,9 @@ require ( k8s.io/apiextensions-apiserver v0.27.3 k8s.io/apimachinery v0.27.3 k8s.io/client-go v0.27.3 - k8s.io/component-base v0.27.3 k8s.io/klog/v2 v2.100.1 - k8s.io/kube-aggregator v0.27.2 k8s.io/utils v0.0.0-20230505201702-9f6742963106 sigs.k8s.io/controller-runtime v0.15.0 - sigs.k8s.io/gateway-api v0.7.0 ) require ( @@ -39,20 +33,17 @@ require ( github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-openapi/jsonreference v0.20.1 // indirect github.com/go-openapi/swag v0.22.3 // indirect - github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/google/gnostic v0.6.9 // indirect github.com/google/go-cmp v0.5.9 // indirect github.com/google/gofuzz v1.2.0 // indirect - github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect github.com/google/uuid v1.3.0 // indirect github.com/imdario/mergo v0.3.13 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/kr/text v0.2.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect @@ -64,7 +55,6 @@ require ( github.com/prometheus/client_model v0.4.0 // indirect github.com/prometheus/common v0.42.0 // indirect github.com/prometheus/procfs v0.9.0 // indirect - github.com/rogpeppe/go-internal v1.10.0 // indirect github.com/spf13/cobra v1.7.0 // indirect github.com/spf13/pflag v1.0.5 // indirect go.uber.org/atomic v1.10.0 // indirect @@ -76,14 +66,16 @@ require ( golang.org/x/term v0.8.0 // indirect golang.org/x/text v0.9.0 // indirect golang.org/x/time v0.3.0 // indirect - golang.org/x/tools v0.9.1 // indirect gomodules.xyz/jsonpatch/v2 v2.3.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/protobuf v1.30.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/component-base v0.27.3 // indirect + k8s.io/kube-aggregator v0.27.2 // indirect k8s.io/kube-openapi v0.0.0-20230515203736-54b630e78af5 // indirect + sigs.k8s.io/gateway-api v0.7.0 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect sigs.k8s.io/yaml v1.3.0 // indirect diff --git a/go.sum b/go.sum index 3087632..5b402a4 100644 --- a/go.sum +++ b/go.sum @@ -18,9 +18,6 @@ github.com/cert-manager/cert-manager v1.12.2/go.mod h1:ql0msU88JCcQSceN+PFjEY8U+ github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= @@ -63,7 +60,6 @@ github.com/go-openapi/jsonreference v0.20.1/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= -github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -100,12 +96,10 @@ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/ github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= -github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= @@ -121,7 +115,6 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -138,10 +131,7 @@ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjY github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q= -github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k= github.com/onsi/gomega v1.27.7 h1:fVih9JD6ogIiHUN6ePK7HJidyEDpWGVB5mzM7cWNXoU= -github.com/onsi/gomega v1.27.7/go.mod h1:1p8OOlwo2iUUDsHnOrjE5UKYJ+e3W8eQ3qSlRahPmr4= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -157,9 +147,7 @@ github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= -github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= @@ -172,7 +160,6 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= @@ -211,7 +198,6 @@ golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -244,7 +230,6 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -277,7 +262,6 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo= -golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/internal/testsetups/simple/api/simple_cluster_issuer_types.go b/internal/testsetups/simple/api/simple_cluster_issuer_types.go index 010555e..15ef353 100644 --- a/internal/testsetups/simple/api/simple_cluster_issuer_types.go +++ b/internal/testsetups/simple/api/simple_cluster_issuer_types.go @@ -47,7 +47,7 @@ func (vi *SimpleClusterIssuer) GetStatus() *v1alpha1.IssuerStatus { } func (vi *SimpleClusterIssuer) GetIssuerTypeIdentifier() string { - return "simpleclusterissuers.issuer.cert-manager.io" + return "simpleclusterissuers.testing.cert-manager.io" } var _ v1alpha1.Issuer = &SimpleClusterIssuer{} diff --git a/internal/testsetups/simple/api/simple_issuer_types.go b/internal/testsetups/simple/api/simple_issuer_types.go index 72c5d95..3ca1693 100644 --- a/internal/testsetups/simple/api/simple_issuer_types.go +++ b/internal/testsetups/simple/api/simple_issuer_types.go @@ -46,7 +46,7 @@ func (vi *SimpleIssuer) GetStatus() *v1alpha1.IssuerStatus { } func (vi *SimpleIssuer) GetIssuerTypeIdentifier() string { - return "simpleissuers.issuer.cert-manager.io" + return "simpleissuers.testing.cert-manager.io" } var _ v1alpha1.Issuer = &SimpleIssuer{} diff --git a/internal/testsetups/simple/controller/signer.go b/internal/testsetups/simple/controller/signer.go index b4d43d8..eeff47b 100644 --- a/internal/testsetups/simple/controller/signer.go +++ b/internal/testsetups/simple/controller/signer.go @@ -40,7 +40,7 @@ import ( // +kubebuilder:rbac:groups=certificates.k8s.io,resources=certificatesigningrequests,verbs=get;list;watch // +kubebuilder:rbac:groups=certificates.k8s.io,resources=certificatesigningrequests/status,verbs=patch -// +kubebuilder:rbac:groups=certificates.k8s.io,resources=signers,verbs=sign,resourceNames=simpleissuers.issuer.cert-manager.io/*;simpleclusterissuers.issuer.cert-manager.io/* +// +kubebuilder:rbac:groups=certificates.k8s.io,resources=signers,verbs=sign,resourceNames=simpleissuers.testing.cert-manager.io/*;simpleclusterissuers.testing.cert-manager.io/* // +kubebuilder:rbac:groups=testing.cert-manager.io,resources=simpleissuers;simpleclusterissuers,verbs=get;list;watch // +kubebuilder:rbac:groups=testing.cert-manager.io,resources=simpleissuers/status;simpleclusterissuers/status,verbs=patch diff --git a/internal/testsetups/simple/deploy/rbac/role.yaml b/internal/testsetups/simple/deploy/rbac/role.yaml index 789a5f3..5e70ee1 100644 --- a/internal/testsetups/simple/deploy/rbac/role.yaml +++ b/internal/testsetups/simple/deploy/rbac/role.yaml @@ -35,8 +35,8 @@ rules: - apiGroups: - certificates.k8s.io resourceNames: - - simpleclusterissuers.issuer.cert-manager.io/* - - simpleissuers.issuer.cert-manager.io/* + - simpleclusterissuers.testing.cert-manager.io/* + - simpleissuers.testing.cert-manager.io/* resources: - signers verbs: diff --git a/internal/testsetups/simple/e2e/conformance/conformance.go b/internal/testsetups/simple/e2e/conformance/conformance.go deleted file mode 100644 index bb1d351..0000000 --- a/internal/testsetups/simple/e2e/conformance/conformance.go +++ /dev/null @@ -1,94 +0,0 @@ -package conformance - -import ( - "context" - "fmt" - "testing" - - cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" - - "github.com/cert-manager/issuer-lib/conformance/certificates" - "github.com/cert-manager/issuer-lib/conformance/certificatesigningrequests" - "github.com/cert-manager/issuer-lib/conformance/framework" - "github.com/cert-manager/issuer-lib/conformance/framework/helper/featureset" - "github.com/cert-manager/issuer-lib/internal/tests/testresource" -) - -type mockTest struct { - testing.TB -} - -func (m *mockTest) Helper() {} - -var _ = framework.ConformanceDescribe("Certificates", func() { - t := &mockTest{} - ctx := testresource.EnsureTestDependencies(t, context.TODO(), testresource.EndToEndTest) - kubeClients := testresource.KubeClients(t, ctx) - - unsupportedFeatures := featureset.NewFeatureSet( - featureset.SaveCAToSecret, - ) - - issuerBuilder := newIssuerBuilder("SimpleIssuer") - (&certificates.Suite{ - KubeClientConfig: kubeClients.Rest, - Name: "External Issuer", - CreateIssuerFunc: issuerBuilder.create, - DeleteIssuerFunc: issuerBuilder.delete, - UnsupportedFeatures: unsupportedFeatures, - }).Define() - - clusterIssuerBuilder := newIssuerBuilder("SimpleClusterIssuer") - (&certificates.Suite{ - KubeClientConfig: kubeClients.Rest, - Name: "External ClusterIssuer", - CreateIssuerFunc: clusterIssuerBuilder.create, - DeleteIssuerFunc: clusterIssuerBuilder.delete, - UnsupportedFeatures: unsupportedFeatures, - }).Define() -}) - -var _ = framework.ConformanceDescribe("CertificateSigningRequests", func() { - t := &mockTest{} - ctx := testresource.EnsureTestDependencies(t, context.TODO(), testresource.EndToEndTest) - kubeClients := testresource.KubeClients(t, ctx) - - unsupportedFeatures := featureset.NewFeatureSet( - featureset.SaveCAToSecret, - ) - - clusterIssuerBuilder := newIssuerBuilder("SimpleClusterIssuer") - (&certificatesigningrequests.Suite{ - KubeClientConfig: kubeClients.Rest, - Name: "External ClusterIssuer", - CreateIssuerFunc: func(f *framework.Framework, ctx context.Context) string { - ref := clusterIssuerBuilder.create(f, ctx) - return fmt.Sprintf("simpleclusterissuers.issuer.cert-manager.io/%s", ref.Name) - }, - DeleteIssuerFunc: func(f *framework.Framework, ctx context.Context, s string) { - ref := cmmeta.ObjectReference{ - Group: "testing.cert-manager.io", - Kind: "SimpleClusterIssuer", - Name: s, - } - clusterIssuerBuilder.delete(f, ctx, ref) - }, - UnsupportedFeatures: unsupportedFeatures, - }).Define() -}) - -/* -var _ = framework.ConformanceDescribe("RBAC", func() { - t := &mockTest{} - ctx := testresource.EnsureTestDependencies(t, context.TODO(), testresource.EndToEndTest) - kubeClients := testresource.KubeClients(t, ctx) - - kubeConfig := rest.CopyConfig(kubeClients.Rest) - kubeConfig.Impersonate.UserName = "system:serviceaccount:my-namespace:simple-issuer-controller-manager" - kubeConfig.Impersonate.Groups = []string{"system:authenticated"} - - (&rbac.Suite{ - KubeClientConfig: kubeConfig, - }).Define() -}) -*/ diff --git a/internal/testsetups/simple/e2e/conformance/conformance_test.go b/internal/testsetups/simple/e2e/conformance/conformance_test.go deleted file mode 100644 index 3aa7393..0000000 --- a/internal/testsetups/simple/e2e/conformance/conformance_test.go +++ /dev/null @@ -1,19 +0,0 @@ -package conformance - -import ( - "testing" - - "github.com/onsi/ginkgo/v2" - "github.com/onsi/gomega" - - "github.com/cert-manager/issuer-lib/internal/tests/testcontext" - "github.com/cert-manager/issuer-lib/internal/tests/testresource" -) - -func TestConformance(t *testing.T) { - _ = testresource.EnsureTestDependencies(t, testcontext.ForTest(t), testresource.EndToEndTest) - - gomega.RegisterFailHandler(ginkgo.Fail) - - ginkgo.RunSpecs(t, "cert-manager conformance suite") -} diff --git a/internal/testsetups/simple/e2e/conformance/issuer_builder.go b/internal/testsetups/simple/e2e/conformance/issuer_builder.go deleted file mode 100644 index cde735d..0000000 --- a/internal/testsetups/simple/e2e/conformance/issuer_builder.go +++ /dev/null @@ -1,103 +0,0 @@ -package conformance - -import ( - "context" - "fmt" - - cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/types" - crtclient "sigs.k8s.io/controller-runtime/pkg/client" - - "github.com/cert-manager/issuer-lib/conformance/framework" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -type issuerBuilder struct { - clusterResourceNamespace string - prototype *unstructured.Unstructured -} - -func newIssuerBuilder(issuerKind string) *issuerBuilder { - return &issuerBuilder{ - clusterResourceNamespace: "test-namespace", - prototype: &unstructured.Unstructured{ - Object: map[string]interface{}{ - "apiVersion": "testing.cert-manager.io/api", - "kind": issuerKind, - "spec": map[string]interface{}{}, - }, - }, - } -} - -func (o *issuerBuilder) nameForTestObject(f *framework.Framework, suffix string) types.NamespacedName { - namespace := f.Namespace.Name - if o.prototype.GetKind() == "ClusterIssuer" { - namespace = o.clusterResourceNamespace - } - return types.NamespacedName{ - Name: fmt.Sprintf("%s-%s", f.Namespace.Name, suffix), - Namespace: namespace, - } -} - -func (o *issuerBuilder) secretAndIssuerForTest(f *framework.Framework) (*corev1.Secret, *unstructured.Unstructured, error) { - secretName := o.nameForTestObject(f, "credentials") - secret := &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: secretName.Name, - Namespace: secretName.Namespace, - }, - StringData: map[string]string{}, - } - - issuerName := o.nameForTestObject(f, "issuer") - issuer := o.prototype.DeepCopy() - issuer.SetName(issuerName.Name) - issuer.SetNamespace(issuerName.Namespace) - err := unstructured.SetNestedField(issuer.Object, secret.Name, "spec", "authSecretName") - - return secret, issuer, err -} - -func (o *issuerBuilder) create(f *framework.Framework, ctx context.Context) cmmeta.ObjectReference { - By("Creating an Issuer") - secret, issuer, err := o.secretAndIssuerForTest(f) - Expect(err).NotTo(HaveOccurred(), "failed to initialise test objects") - - crt, err := crtclient.New(f.KubeClientConfig, crtclient.Options{}) - Expect(err).NotTo(HaveOccurred(), "failed to create controller-runtime client") - - err = crt.Create(ctx, secret) - Expect(err).NotTo(HaveOccurred(), "failed to create secret") - - err = crt.Create(ctx, issuer) - Expect(err).NotTo(HaveOccurred(), "failed to create issuer") - - return cmmeta.ObjectReference{ - Group: issuer.GroupVersionKind().Group, - Kind: issuer.GroupVersionKind().Kind, - Name: issuer.GetName(), - } -} - -func (o *issuerBuilder) delete(f *framework.Framework, ctx context.Context, _ cmmeta.ObjectReference) { - By("Deleting the issuer") - - crt, err := crtclient.New(f.KubeClientConfig, crtclient.Options{}) - Expect(err).NotTo(HaveOccurred(), "failed to create controller-runtime client") - - secret, issuer, err := o.secretAndIssuerForTest(f) - Expect(err).NotTo(HaveOccurred(), "failed to initialise test objects") - - err = crt.Delete(ctx, issuer) - Expect(err).NotTo(HaveOccurred(), "failed to delete issuer") - - err = crt.Delete(ctx, secret) - Expect(err).NotTo(HaveOccurred(), "failed to delete secret") -} diff --git a/internal/testsetups/simple/example/simple-cluster-issuer.yaml b/internal/testsetups/simple/example/simple-cluster-issuer.yaml new file mode 100644 index 0000000..5ef3c34 --- /dev/null +++ b/internal/testsetups/simple/example/simple-cluster-issuer.yaml @@ -0,0 +1,5 @@ +apiVersion: testing.cert-manager.io/api +kind: SimpleClusterIssuer +metadata: + name: simple-cluster-issuer +spec: {} \ No newline at end of file diff --git a/internal/testsetups/simple/example/simple-issuer.yaml b/internal/testsetups/simple/example/simple-issuer.yaml new file mode 100644 index 0000000..e6e3b6d --- /dev/null +++ b/internal/testsetups/simple/example/simple-issuer.yaml @@ -0,0 +1,5 @@ +apiVersion: testing.cert-manager.io/api +kind: SimpleIssuer +metadata: + name: simple-issuer +spec: {} \ No newline at end of file diff --git a/make/tools.mk b/make/tools.mk index 77225ae..7d19a61 100644 --- a/make/tools.mk +++ b/make/tools.mk @@ -37,7 +37,7 @@ TOOLS += kyverno=v1.10.0 TOOLS += yq=v4.34.1 # https://github.com/ko-build/ko/releases TOOLS += ko=0.13.0 -TOOLS += ginkgo=$(shell awk '/ginkgo\/v2/ {print $$2}' go.mod) +TOOLS += ginkgo=$(shell awk '/ginkgo\/v2/ {print $$2}' ./conformance/go.mod) ### go packages # https://pkg.go.dev/sigs.k8s.io/controller-tools/cmd/controller-gen?tab=versions From 44da812cde263a9ddec529461602bb309c13eebd Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Fri, 30 Jun 2023 09:51:09 +0200 Subject: [PATCH 11/13] further improve conformance test binary Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- Makefile | 12 +-- api/v1alpha1/issuer_interface.go | 4 +- conformance/certificates/suite.go | 8 ++ conformance/certificates/tests.go | 72 ++++++++++++--- .../certificatesigningrequests/tests.go | 33 +++++-- conformance/conformance_test.go | 4 +- conformance/framework/framework.go | 79 +++++++++------- conformance/framework/helper/helper.go | 9 +- conformance/framework/testenv.go | 89 ------------------- conformance/go.mod | 2 +- conformance/rbac/certificate.go | 7 +- conformance/rbac/certificaterequest.go | 7 +- conformance/rbac/doc.go | 8 +- conformance/rbac/issuer.go | 7 +- ...rtificatesigningrequest_controller_test.go | 12 +-- internal/testsetups/simple/e2e/e2e_test.go | 2 +- 16 files changed, 186 insertions(+), 169 deletions(-) delete mode 100644 conformance/framework/testenv.go diff --git a/Makefile b/Makefile index 8621602..1c39d8e 100644 --- a/Makefile +++ b/Makefile @@ -165,18 +165,20 @@ $(BINDIR)/conformance.test: | $(NEEDS_GINKGO) test: test-unit-deps | $(NEEDS_GO) $(NEEDS_GOTESTSUM) ## Run unit tests. $(GOTESTSUM) ./... -coverprofile cover.out -# $(GOTESTSUM) ./internal/testsetups/simple/e2e/... -coverprofile cover.out -timeout 5m - .PHONY: test-e2e test-e2e: test-e2e-deps | $(NEEDS_GOTESTSUM) $(NEEDS_GINKGO) $(BINDIR)/conformance.test ## Run e2e tests. This creates a Kind cluster, installs dependencies, deploys the issuer-lib and runs the E2E tests. - + $(GOTESTSUM) ./internal/testsetups/simple/e2e/... -coverprofile cover.out -timeout 5m - kubectl apply -f internal/testsetups/simple/example/simple-cluster-issuer.yaml + kubectl create ns cm-conformance-test || true + kubectl -n cm-conformance-test apply -f internal/testsetups/simple/example/simple-issuer.yaml + kubectl -n cm-conformance-test apply -f internal/testsetups/simple/example/simple-cluster-issuer.yaml $(GINKGO) -procs=10 run $(BINDIR)/conformance.test -- \ + --namespace=cm-conformance-test \ + --cm-issuers=testing.cert-manager.io/SimpleIssuer/simple-issuer \ --cm-issuers=testing.cert-manager.io/SimpleClusterIssuer/simple-cluster-issuer \ --k8s-issuers=simpleclusterissuers.testing.cert-manager.io/simple-cluster-issuer \ - --unsupported-features=SaveCAToSecret \ + --unsupported-features=SaveCAToSecret ##@ Build diff --git a/api/v1alpha1/issuer_interface.go b/api/v1alpha1/issuer_interface.go index a250518..3208883 100644 --- a/api/v1alpha1/issuer_interface.go +++ b/api/v1alpha1/issuer_interface.go @@ -35,7 +35,7 @@ type Issuer interface { // issuer type for a Kubernetes CertificateSigningRequest resource based // on the issuerName field. The value should be formatted as follows: // ".". For example, the value - // "simpleclusterissuers.issuer.cert-manager.io" will match all CSRs - // with an issuerName set to eg. "simpleclusterissuers.issuer.cert-manager.io/issuer1". + // "simpleclusterissuers.testing.cert-manager.io" will match all CSRs + // with an issuerName set to eg. "simpleclusterissuers.testing.cert-manager.io/issuer1". GetIssuerTypeIdentifier() string } diff --git a/conformance/certificates/suite.go b/conformance/certificates/suite.go index 4435cda..34ff204 100644 --- a/conformance/certificates/suite.go +++ b/conformance/certificates/suite.go @@ -44,6 +44,10 @@ type Suite struct { // created with this issuer reference. IssuerRef cmmeta.ObjectReference + // Namespace is the namespace in which the Certificate resources will be + // created. + Namespace string + // DomainSuffix is a suffix used on all domain requests. // This is useful when the issuer being tested requires special // configuration for a set of domains in order for certificates to be @@ -72,6 +76,10 @@ func (s *Suite) complete(f *framework.Framework) { Fail("IssuerRef must be set") } + if s.Namespace == "" { + Fail("Namespace must be set") + } + if s.DomainSuffix == "" { s.DomainSuffix = "example.com" } diff --git a/conformance/certificates/tests.go b/conformance/certificates/tests.go index 059787b..2effbab 100644 --- a/conformance/certificates/tests.go +++ b/conformance/certificates/tests.go @@ -33,6 +33,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/util/retry" + "sigs.k8s.io/controller-runtime/pkg/client" "conformance/framework" "conformance/framework/helper/featureset" @@ -49,7 +50,16 @@ import ( // automatically called. func (s *Suite) Define() { Describe("with issuer type "+s.Name, func() { - f := framework.NewFramework("certificates", s.KubeClientConfig) + f := framework.NewFramework( + "certificates", + s.KubeClientConfig, + s.Namespace, + []client.Object{ + &cmapi.Certificate{}, + &cmapi.CertificateRequest{}, + &corev1.Secret{}, + }, + ) sharedIPAddress := "127.0.0.1" @@ -371,14 +381,26 @@ func (s *Suite) Define() { defineTest := func(test testCase) { s.it(f, test.name, func(ctx context.Context, issuerRef cmmeta.ObjectReference) { + randomTestID := e2eutil.RandStringRunes(10) certificate := &cmapi.Certificate{ ObjectMeta: metav1.ObjectMeta{ - Name: "testcert", - Namespace: f.Namespace.Name, + Name: "e2e-conformance-" + randomTestID, + Namespace: f.Namespace, + Labels: map[string]string{ + f.CleanupLabel: "true", + }, + Annotations: map[string]string{ + "conformance.cert-manager.io/test-name": s.Name + " " + test.name, + }, }, Spec: cmapi.CertificateSpec{ - SecretName: "testcert-tls", + SecretName: "e2e-conformance-tls-" + randomTestID, IssuerRef: issuerRef, + SecretTemplate: &cmapi.CertificateSecretTemplate{ + Labels: map[string]string{ + f.CleanupLabel: "true", + }, + }, }, } @@ -407,15 +429,27 @@ func (s *Suite) Define() { } s.it(f, "should issue another certificate with the same private key if the existing certificate and CertificateRequest are deleted", func(ctx context.Context, issuerRef cmmeta.ObjectReference) { + randomTestID := e2eutil.RandStringRunes(10) testCertificate := &cmapi.Certificate{ ObjectMeta: metav1.ObjectMeta{ - Name: "testcert", - Namespace: f.Namespace.Name, + Name: "e2e-conformance-" + randomTestID, + Namespace: f.Namespace, + Labels: map[string]string{ + f.CleanupLabel: "true", + }, + Annotations: map[string]string{ + "conformance.cert-manager.io/test-name": s.Name + " should issue another certificate with the same private key if the existing certificate and CertificateRequest are deleted", + }, }, Spec: cmapi.CertificateSpec{ - SecretName: "testcert-tls", + SecretName: "e2e-conformance-tls-" + randomTestID, DNSNames: []string{e2eutil.RandomSubdomain(s.DomainSuffix)}, IssuerRef: issuerRef, + SecretTemplate: &cmapi.CertificateSecretTemplate{ + Labels: map[string]string{ + f.CleanupLabel: "true", + }, + }, }, } By("Creating a Certificate") @@ -431,7 +465,7 @@ func (s *Suite) Define() { Expect(err).NotTo(HaveOccurred()) By("Deleting existing certificate data in Secret") - sec, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name). + sec, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace). Get(ctx, testCertificate.Spec.SecretName, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred(), "failed to get secret containing signed certificate key pair data") @@ -442,11 +476,11 @@ func (s *Suite) Define() { sec.Data[corev1.TLSCertKey] = []byte{} - _, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Update(ctx, sec, metav1.UpdateOptions{}) + _, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace).Update(ctx, sec, metav1.UpdateOptions{}) Expect(err).NotTo(HaveOccurred(), "failed to update secret by deleting the signed certificate data") By("Waiting for the Certificate to re-issue a certificate") - sec, err = f.Helper().WaitForSecretCertificateData(ctx, sec.Name, f.Namespace.Name, time.Minute*8) + sec, err = f.Helper().WaitForSecretCertificateData(ctx, sec.Name, f.Namespace, time.Minute*8) Expect(err).NotTo(HaveOccurred(), "failed to wait for secret to have a valid 2nd certificate") crtPEM2 := sec.Data[corev1.TLSCertKey] @@ -463,15 +497,27 @@ func (s *Suite) Define() { }, featureset.ReusePrivateKeyFeature, featureset.OnlySAN) s.it(f, "should allow updating an existing certificate with a new DNS Name", func(ctx context.Context, issuerRef cmmeta.ObjectReference) { + randomTestID := e2eutil.RandStringRunes(10) testCertificate := &cmapi.Certificate{ ObjectMeta: metav1.ObjectMeta{ - Name: "testcert", - Namespace: f.Namespace.Name, + Name: "e2e-conformance-" + randomTestID, + Namespace: f.Namespace, + Labels: map[string]string{ + f.CleanupLabel: "true", + }, + Annotations: map[string]string{ + "conformance.cert-manager.io/test-name": s.Name + " should allow updating an existing certificate with a new DNS Name", + }, }, Spec: cmapi.CertificateSpec{ - SecretName: "testcert-tls", + SecretName: "e2e-conformance-tls-" + randomTestID, DNSNames: []string{e2eutil.RandomSubdomain(s.DomainSuffix)}, IssuerRef: issuerRef, + SecretTemplate: &cmapi.CertificateSecretTemplate{ + Labels: map[string]string{ + f.CleanupLabel: "true", + }, + }, }, } validations := validation.CertificateSetForUnsupportedFeatureSet(s.UnsupportedFeatures) diff --git a/conformance/certificatesigningrequests/tests.go b/conformance/certificatesigningrequests/tests.go index 163f223..17f4dd5 100644 --- a/conformance/certificatesigningrequests/tests.go +++ b/conformance/certificatesigningrequests/tests.go @@ -29,6 +29,7 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/utils/pointer" + "sigs.k8s.io/controller-runtime/pkg/client" "conformance/framework" "conformance/framework/helper/featureset" @@ -49,7 +50,14 @@ import ( // they are not active, these tests will fail. func (s *Suite) Define() { Describe("CertificateSigningRequest with issuer type "+s.Name, func() { - f := framework.NewFramework("certificatesigningrequests", s.KubeClientConfig) + f := framework.NewFramework( + "certificatesigningrequests", + s.KubeClientConfig, + "", + []client.Object{ + &certificatesv1.CertificateSigningRequest{}, + }, + ) sharedIPAddress := "127.0.0.1" sharedURI, err := url.Parse("spiffe://cluster.local/ns/sandbox/sa/foo") @@ -435,6 +443,14 @@ func (s *Suite) Define() { }, } + addAnnotation := func(annotations map[string]string, key, value string) map[string]string { + if annotations == nil { + annotations = map[string]string{} + } + annotations[key] = value + return annotations + } + defineTest := func(test testCase) { s.it(f, test.name, func(ctx context.Context, signerName string) { // Generate request CSR @@ -442,10 +458,18 @@ func (s *Suite) Define() { Expect(err).NotTo(HaveOccurred()) // Create CertificateSigningRequest + randomTestID := e2eutil.RandStringRunes(10) kubeCSR := &certificatesv1.CertificateSigningRequest{ ObjectMeta: metav1.ObjectMeta{ - GenerateName: "e2e-conformance-", - Annotations: test.kubeCSRAnnotations, + Name: "e2e-conformance-" + randomTestID, + Labels: map[string]string{ + f.CleanupLabel: "true", + }, + Annotations: addAnnotation( + test.kubeCSRAnnotations, + "conformance.cert-manager.io/test-name", + s.Name+" "+test.name, + ), }, Spec: certificatesv1.CertificateSigningRequestSpec{ Request: csr, @@ -458,9 +482,6 @@ func (s *Suite) Define() { // Create the request, and delete at the end of the test By("Creating a CertificateSigningRequest") Expect(f.CRClient.Create(ctx, kubeCSR)).NotTo(HaveOccurred()) - DeferCleanup(func(ctx context.Context) { - Expect(f.CRClient.Delete(ctx, kubeCSR)).NotTo(HaveOccurred()) - }) // Approve the request for testing, so that cert-manager may sign the // request. diff --git a/conformance/conformance_test.go b/conformance/conformance_test.go index e44305f..ae0809d 100644 --- a/conformance/conformance_test.go +++ b/conformance/conformance_test.go @@ -26,12 +26,13 @@ func (i *arrayFlags) Set(value string) error { return nil } +var namespace string var unsupportedFeatures arrayFlags - var cmIssuerReferences arrayFlags var k8sIssuerReferences arrayFlags func init() { + flag.StringVar(&namespace, "namespace", "", "list of issuer references to use for conformance tests") flag.Var(&unsupportedFeatures, "unsupported-features", "list of features that are not supported by this invocation of the test suite") flag.Var(&cmIssuerReferences, "cm-issuers", "list of issuer references to use for conformance tests") flag.Var(&k8sIssuerReferences, "k8s-issuers", "list of issuer references to use for conformance tests") @@ -71,6 +72,7 @@ func TestConformance(t *testing.T) { (&certificates.Suite{ KubeClientConfig: restConfig, Name: ref, + Namespace: namespace, IssuerRef: parseCMReference(g, ref), UnsupportedFeatures: unsupportedFeatureSet, }).Define() diff --git a/conformance/framework/framework.go b/conformance/framework/framework.go index 939db44..aa202c9 100644 --- a/conformance/framework/framework.go +++ b/conformance/framework/framework.go @@ -21,23 +21,28 @@ import ( clientset "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned" certmgrscheme "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned/scheme" - api "k8s.io/api/core/v1" apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" apiextcs "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/kubernetes" kscheme "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" apireg "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1" crclient "sigs.k8s.io/controller-runtime/pkg/client" - gwapi "sigs.k8s.io/gateway-api/pkg/client/clientset/versioned" + "sigs.k8s.io/controller-runtime/pkg/log" "conformance/framework/helper" + e2eutil "conformance/util" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) +func init() { + log.SetLogger(GinkgoLogr) +} + // Framework supports common operations used by e2e tests; it will keep a client & a namespace for you. type Framework struct { BaseName string @@ -47,15 +52,23 @@ type Framework struct { // Kubernetes API clientsets KubeClientSet kubernetes.Interface - GWClientSet gwapi.Interface CertManagerClientSet clientset.Interface APIExtensionsClientSet apiextcs.Interface // controller-runtime client for newer controllers CRClient crclient.Client - // Namespace in which all test resources should reside - Namespace *api.Namespace + // Namespace in which all resources are created for this test suite. + Namespace string + + // CleanupLabel is a label that should be applied to all resources created + // by this test suite. It is used to clean up all resources at the end of + // the test suite. + CleanupLabel string + + // cleanupResourceTypes is a list of all resource types that should be cleaned + // up after each test. + cleanupResourceTypes []crclient.Object helper *helper.Helper } @@ -63,21 +76,30 @@ type Framework struct { // NewFramework makes a new framework and sets up a BeforeEach/AfterEach for // you (you can write additional before/after each functions). // It uses the config provided to it for the duration of the tests. -func NewFramework(baseName string, kubeClientConfig *rest.Config) *Framework { +func NewFramework( + baseName string, + kubeClientConfig *rest.Config, + namespace string, + cleanupResourceTypes []crclient.Object, +) *Framework { f := &Framework{ - KubeClientConfig: kubeClientConfig, - BaseName: baseName, + BaseName: baseName, + KubeClientConfig: kubeClientConfig, + Namespace: namespace, + cleanupResourceTypes: cleanupResourceTypes, } - f.helper = helper.NewHelper(kubeClientConfig) + f.helper = helper.NewHelper() BeforeEach(f.BeforeEach) AfterEach(f.AfterEach) return f } -// BeforeEach gets a client and makes a namespace. +// BeforeEach initializes all clients. func (f *Framework) BeforeEach(ctx context.Context) { + f.CleanupLabel = "cm-conformance-cleanup-" + e2eutil.RandStringRunes(10) + var err error kubeConfig := rest.CopyConfig(f.KubeClientConfig) @@ -86,10 +108,6 @@ func (f *Framework) BeforeEach(ctx context.Context) { f.KubeClientSet, err = kubernetes.NewForConfig(kubeConfig) Expect(err).NotTo(HaveOccurred()) - By("Creating an API extensions client") - f.APIExtensionsClientSet, err = apiextcs.NewForConfig(kubeConfig) - Expect(err).NotTo(HaveOccurred()) - By("Creating a cert manager client") f.CertManagerClientSet, err = clientset.NewForConfig(kubeConfig) Expect(err).NotTo(HaveOccurred()) @@ -100,32 +118,31 @@ func (f *Framework) BeforeEach(ctx context.Context) { Expect(certmgrscheme.AddToScheme(scheme)).NotTo(HaveOccurred()) Expect(apiext.AddToScheme(scheme)).NotTo(HaveOccurred()) Expect(apireg.AddToScheme(scheme)).NotTo(HaveOccurred()) - f.CRClient, err = crclient.New(kubeConfig, crclient.Options{Scheme: scheme}) - Expect(err).NotTo(HaveOccurred()) - By("Creating a gateway-api client") - f.GWClientSet, err = gwapi.NewForConfig(kubeConfig) - Expect(err).NotTo(HaveOccurred()) - - By("Building a namespace api object") - f.Namespace, err = f.CreateKubeNamespace(ctx, f.BaseName) + f.CRClient, err = crclient.New(kubeConfig, crclient.Options{Scheme: scheme}) Expect(err).NotTo(HaveOccurred()) - By("Using the namespace " + f.Namespace.Name) - - By("Building a ResourceQuota api object") - _, err = f.CreateKubeResourceQuota(ctx) - Expect(err).NotTo(HaveOccurred()) + if f.Namespace != "" { + By("Check that the namespace " + f.Namespace + " exists") + _, err = f.KubeClientSet.CoreV1().Namespaces().Get(ctx, f.Namespace, metav1.GetOptions{}) + Expect(err).NotTo(HaveOccurred()) + } f.helper.CMClient = f.CertManagerClientSet f.helper.KubeClient = f.KubeClientSet } -// AfterEach deletes the namespace, after reading its events. func (f *Framework) AfterEach(ctx context.Context) { - By("Deleting test namespace") - err := f.DeleteKubeNamespace(ctx, f.Namespace.Name) - Expect(err).NotTo(HaveOccurred()) + for _, obj := range f.cleanupResourceTypes { + By("Deleting " + obj.GetObjectKind().GroupVersionKind().String() + " resources") + err := f.CRClient.DeleteAllOf( + ctx, + obj, + crclient.InNamespace(f.Namespace), + crclient.MatchingLabels{f.CleanupLabel: "true"}, + ) + Expect(err).NotTo(HaveOccurred()) + } } func (f *Framework) Helper() *helper.Helper { diff --git a/conformance/framework/helper/helper.go b/conformance/framework/helper/helper.go index 03c9e7c..9f2f899 100644 --- a/conformance/framework/helper/helper.go +++ b/conformance/framework/helper/helper.go @@ -19,19 +19,14 @@ package helper import ( cmclient "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned" "k8s.io/client-go/kubernetes" - "k8s.io/client-go/rest" ) // Helper provides methods for common operations needed during tests. type Helper struct { - KubeClientConfig *rest.Config - KubeClient kubernetes.Interface CMClient cmclient.Interface } -func NewHelper(kubeClientConfig *rest.Config) *Helper { - return &Helper{ - KubeClientConfig: kubeClientConfig, - } +func NewHelper() *Helper { + return &Helper{} } diff --git a/conformance/framework/testenv.go b/conformance/framework/testenv.go deleted file mode 100644 index 16cc6f2..0000000 --- a/conformance/framework/testenv.go +++ /dev/null @@ -1,89 +0,0 @@ -/* -Copyright 2020 The cert-manager Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package framework - -import ( - "context" - "fmt" - "time" - - v1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/api/resource" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/wait" -) - -// Defines methods that help provision test environments - -const ( - // Poll defines how often to poll for conditions. - Poll = 2 * time.Second -) - -// CreateKubeNamespace creates a new Kubernetes Namespace for a test. -func (f *Framework) CreateKubeNamespace(ctx context.Context, baseName string) (*v1.Namespace, error) { - ns := &v1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - GenerateName: fmt.Sprintf("e2e-tests-%v-", baseName), - }, - } - - return f.KubeClientSet.CoreV1().Namespaces().Create(ctx, ns, metav1.CreateOptions{}) -} - -// CreateKubeResourceQuota provisions a ResourceQuota resource in the target -// namespace. -func (f *Framework) CreateKubeResourceQuota(ctx context.Context) (*v1.ResourceQuota, error) { - quota := &v1.ResourceQuota{ - ObjectMeta: metav1.ObjectMeta{ - Name: "default-e2e-quota", - Namespace: f.Namespace.Name, - }, - Spec: v1.ResourceQuotaSpec{ - Hard: v1.ResourceList{ - "cpu": resource.MustParse("16"), - "limits.cpu": resource.MustParse("16"), - "requests.cpu": resource.MustParse("16"), - "memory": resource.MustParse("32G"), - "limits.memory": resource.MustParse("32G"), - "requests.memory": resource.MustParse("32G"), - }, - }, - } - return f.KubeClientSet.CoreV1().ResourceQuotas(f.Namespace.Name).Create(ctx, quota, metav1.CreateOptions{}) -} - -// DeleteKubeNamespace will delete a namespace resource -func (f *Framework) DeleteKubeNamespace(ctx context.Context, namespace string) error { - return f.KubeClientSet.CoreV1().Namespaces().Delete(ctx, namespace, metav1.DeleteOptions{}) -} - -// WaitForKubeNamespaceNotExist will wait for the namespace with the given name -// to not exist for up to 2 minutes. -func (f *Framework) WaitForKubeNamespaceNotExist(pollCtx context.Context, namespace string) error { - return wait.PollUntilContextTimeout(pollCtx, Poll, time.Minute*2, true, func(ctx context.Context) (bool, error) { - _, err := f.KubeClientSet.CoreV1().Namespaces().Get(ctx, namespace, metav1.GetOptions{}) - if apierrors.IsNotFound(err) { - return true, nil - } - if err != nil { - return false, err - } - return false, nil - }) -} diff --git a/conformance/go.mod b/conformance/go.mod index 0aa3663..5edb00a 100644 --- a/conformance/go.mod +++ b/conformance/go.mod @@ -14,7 +14,6 @@ require ( k8s.io/kube-aggregator v0.27.3 k8s.io/utils v0.0.0-20230505201702-9f6742963106 sigs.k8s.io/controller-runtime v0.15.0 - sigs.k8s.io/gateway-api v0.7.1 ) require ( @@ -80,6 +79,7 @@ require ( k8s.io/component-base v0.27.3 // indirect k8s.io/klog/v2 v2.100.1 // indirect k8s.io/kube-openapi v0.0.0-20230515203736-54b630e78af5 // indirect + sigs.k8s.io/gateway-api v0.7.1 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect sigs.k8s.io/yaml v1.3.0 // indirect diff --git a/conformance/rbac/certificate.go b/conformance/rbac/certificate.go index 1d7ee51..e3fe99d 100644 --- a/conformance/rbac/certificate.go +++ b/conformance/rbac/certificate.go @@ -25,7 +25,12 @@ import ( func (s *Suite) defineCertificates() { RBACDescribe("Certificates", func() { - f := framework.NewFramework("rbac-certificates", s.KubeClientConfig) + f := framework.NewFramework( + "rbac-certificates", + s.KubeClientConfig, + "", + nil, + ) resource := "certificates" // this file is related to certificates Context("with namespace view access", func() { diff --git a/conformance/rbac/certificaterequest.go b/conformance/rbac/certificaterequest.go index e2984b3..3603b4a 100644 --- a/conformance/rbac/certificaterequest.go +++ b/conformance/rbac/certificaterequest.go @@ -25,7 +25,12 @@ import ( func (s *Suite) defineCertificateRequests() { RBACDescribe("CertificateRequests", func() { - f := framework.NewFramework("rbac-certificaterequests", s.KubeClientConfig) + f := framework.NewFramework( + "rbac-certificaterequests", + s.KubeClientConfig, + "", + nil, + ) resource := "certificaterequests" // this file is related to certificaterequests Context("with namespace view access", func() { diff --git a/conformance/rbac/doc.go b/conformance/rbac/doc.go index be4c98e..a333863 100644 --- a/conformance/rbac/doc.go +++ b/conformance/rbac/doc.go @@ -44,7 +44,7 @@ func RbacClusterRoleHasAccessToResource(f *framework.Framework, clusterRole stri GenerateName: "rbac-test-", }, } - serviceAccountClient := f.KubeClientSet.CoreV1().ServiceAccounts(f.Namespace.Name) + serviceAccountClient := f.KubeClientSet.CoreV1().ServiceAccounts(f.Namespace) serviceAccount, err := serviceAccountClient.Create(context.TODO(), viewServiceAccount, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) viewServiceAccountName := serviceAccount.Name @@ -55,7 +55,7 @@ func RbacClusterRoleHasAccessToResource(f *framework.Framework, clusterRole stri GenerateName: viewServiceAccountName + "-rb-", }, Subjects: []rbacv1.Subject{ - {Kind: "ServiceAccount", Name: viewServiceAccountName, Namespace: f.Namespace.Name}, + {Kind: "ServiceAccount", Name: viewServiceAccountName, Namespace: f.Namespace}, }, RoleRef: rbacv1.RoleRef{ APIGroup: "rbac.authorization.k8s.io", @@ -73,7 +73,7 @@ func RbacClusterRoleHasAccessToResource(f *framework.Framework, clusterRole stri By("Impersonating the Service Account") impersonateConfig := f.KubeClientConfig - impersonateConfig.Impersonate.UserName = "system:serviceaccount:" + f.Namespace.Name + ":" + viewServiceAccountName + impersonateConfig.Impersonate.UserName = "system:serviceaccount:" + f.Namespace + ":" + viewServiceAccountName impersonateClient, err := kubernetes.NewForConfig(impersonateConfig) Expect(err).NotTo(HaveOccurred()) @@ -82,7 +82,7 @@ func RbacClusterRoleHasAccessToResource(f *framework.Framework, clusterRole stri sar := &authorizationv1.SelfSubjectAccessReview{ Spec: authorizationv1.SelfSubjectAccessReviewSpec{ ResourceAttributes: &authorizationv1.ResourceAttributes{ - Namespace: f.Namespace.Name, + Namespace: f.Namespace, Verb: verb, Group: "cert-manager.io", Resource: resource, diff --git a/conformance/rbac/issuer.go b/conformance/rbac/issuer.go index 7447a5a..9fdd27d 100644 --- a/conformance/rbac/issuer.go +++ b/conformance/rbac/issuer.go @@ -25,7 +25,12 @@ import ( func (s *Suite) defineIssuers() { RBACDescribe("Issuers", func() { - f := framework.NewFramework("rbac-issuers", s.KubeClientConfig) + f := framework.NewFramework( + "rbac-issuers", + s.KubeClientConfig, + "", + nil, + ) resource := "issuers" // this file is related to issuers Context("with namespace view access", func() { diff --git a/controllers/certificatesigningrequest_controller_test.go b/controllers/certificatesigningrequest_controller_test.go index 0c07a08..232843b 100644 --- a/controllers/certificatesigningrequest_controller_test.go +++ b/controllers/certificatesigningrequest_controller_test.go @@ -102,7 +102,7 @@ func TestCertificateSigningRequestReconcilerReconcile(t *testing.T) { cr1 := cmgen.CertificateSigningRequest( "cr1", - cmgen.SetCertificateSigningRequestSignerName("simpleissuers.issuer.cert-manager.io/unknown-namespace.unknown-name"), + cmgen.SetCertificateSigningRequestSignerName("simpleissuers.testing.cert-manager.io/unknown-namespace.unknown-name"), func(cr *certificatesv1.CertificateSigningRequest) { conditions.SetCertificateSigningRequestStatusCondition( fakeClock1, @@ -877,17 +877,17 @@ func TestCertificateSigningRequestMatchIssuerType(t *testing.T) { name: "match issuer", issuerTypes: []v1alpha1.Issuer{&api.SimpleIssuer{}}, clusterIssuerTypes: []v1alpha1.Issuer{&api.SimpleClusterIssuer{}}, - csr: createCsr("simpleissuers.issuer.cert-manager.io/namespace.name"), + csr: createCsr("simpleissuers.testing.cert-manager.io/namespace.name"), expectedIssuerType: nil, expectedIssuerName: types.NamespacedName{}, - expectedError: errormatch.ErrorContains("invalid SignerName, \"simpleissuers.issuer.cert-manager.io\" is a namespaced issuer type, namespaced issuers are not supported for Kubernetes CSRs"), + expectedError: errormatch.ErrorContains("invalid SignerName, \"simpleissuers.testing.cert-manager.io\" is a namespaced issuer type, namespaced issuers are not supported for Kubernetes CSRs"), }, { name: "match cluster issuer", issuerTypes: []v1alpha1.Issuer{&api.SimpleIssuer{}}, clusterIssuerTypes: []v1alpha1.Issuer{&api.SimpleClusterIssuer{}}, - csr: createCsr("simpleclusterissuers.issuer.cert-manager.io/name"), + csr: createCsr("simpleclusterissuers.testing.cert-manager.io/name"), expectedIssuerType: &api.SimpleClusterIssuer{}, expectedIssuerName: types.NamespacedName{Name: "name"}, @@ -896,7 +896,7 @@ func TestCertificateSigningRequestMatchIssuerType(t *testing.T) { name: "cluster issuer with dot in name", issuerTypes: []v1alpha1.Issuer{&api.SimpleIssuer{}}, clusterIssuerTypes: []v1alpha1.Issuer{&api.SimpleClusterIssuer{}}, - csr: createCsr("simpleclusterissuers.issuer.cert-manager.io/name.test"), + csr: createCsr("simpleclusterissuers.testing.cert-manager.io/name.test"), expectedIssuerType: &api.SimpleClusterIssuer{}, expectedIssuerName: types.NamespacedName{Name: "name.test"}, @@ -905,7 +905,7 @@ func TestCertificateSigningRequestMatchIssuerType(t *testing.T) { name: "cluster issuer with empty name", issuerTypes: []v1alpha1.Issuer{&api.SimpleIssuer{}}, clusterIssuerTypes: []v1alpha1.Issuer{&api.SimpleClusterIssuer{}}, - csr: createCsr("simpleclusterissuers.issuer.cert-manager.io/"), + csr: createCsr("simpleclusterissuers.testing.cert-manager.io/"), expectedIssuerType: &api.SimpleClusterIssuer{}, expectedIssuerName: types.NamespacedName{Name: ""}, diff --git a/internal/testsetups/simple/e2e/e2e_test.go b/internal/testsetups/simple/e2e/e2e_test.go index b6f94f6..a225b93 100644 --- a/internal/testsetups/simple/e2e/e2e_test.go +++ b/internal/testsetups/simple/e2e/e2e_test.go @@ -110,7 +110,7 @@ func TestSimpleCertificateSigningRequest(t *testing.T) { cmgen.SetCertificateSigningRequestDuration("1h"), cmgen.SetCertificateSigningRequestRequest(csrBlob), cmgen.SetCertificateSigningRequestUsages([]certificatesv1.KeyUsage{certificatesv1.UsageDigitalSignature}), - cmgen.SetCertificateSigningRequestSignerName(fmt.Sprintf("simpleclusterissuers.issuer.cert-manager.io/%s", clusterIssuer.Name)), + cmgen.SetCertificateSigningRequestSignerName(fmt.Sprintf("simpleclusterissuers.testing.cert-manager.io/%s", clusterIssuer.Name)), ) err = kubeClients.Client.Create(ctx, clusterIssuer) From d8102a3e084b21c114a9470fc495b6cdb8791193 Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Fri, 30 Jun 2023 10:02:56 +0200 Subject: [PATCH 12/13] remove RBAC conformance tests Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- conformance/rbac/certificate.go | 212 ------------------------- conformance/rbac/certificaterequest.go | 212 ------------------------- conformance/rbac/doc.go | 95 ----------- conformance/rbac/issuer.go | 212 ------------------------- conformance/rbac/suite.go | 19 --- 5 files changed, 750 deletions(-) delete mode 100644 conformance/rbac/certificate.go delete mode 100644 conformance/rbac/certificaterequest.go delete mode 100644 conformance/rbac/doc.go delete mode 100644 conformance/rbac/issuer.go delete mode 100644 conformance/rbac/suite.go diff --git a/conformance/rbac/certificate.go b/conformance/rbac/certificate.go deleted file mode 100644 index e3fe99d..0000000 --- a/conformance/rbac/certificate.go +++ /dev/null @@ -1,212 +0,0 @@ -/* -Copyright 2020 The cert-manager Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package rbac - -import ( - "conformance/framework" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -func (s *Suite) defineCertificates() { - RBACDescribe("Certificates", func() { - f := framework.NewFramework( - "rbac-certificates", - s.KubeClientConfig, - "", - nil, - ) - resource := "certificates" // this file is related to certificates - - Context("with namespace view access", func() { - clusterRole := "view" - It("shouldn't be able to create certificates", func() { - verb := "create" - - hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) - Expect(hasAccess).Should(BeFalse()) - }) - - It("shouldn't be able to delete certificates", func() { - verb := "delete" - - hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) - Expect(hasAccess).Should(BeFalse()) - }) - - It("shouldn't be able to delete collections of certificates", func() { - verb := "deletecollection" - - hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) - Expect(hasAccess).Should(BeFalse()) - }) - - It("shouldn't be able to patch certificates", func() { - verb := "patch" - - hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) - Expect(hasAccess).Should(BeFalse()) - }) - - It("shouldn't be able to update certificates", func() { - verb := "update" - - hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) - Expect(hasAccess).Should(BeFalse()) - }) - - It("should be able to get certificates", func() { - verb := "get" - - hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) - Expect(hasAccess).Should(BeTrue()) - }) - - It("should be able to list certificates", func() { - verb := "list" - - hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) - Expect(hasAccess).Should(BeTrue()) - }) - - It("should be able to watch certificates", func() { - verb := "watch" - - hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) - Expect(hasAccess).Should(BeTrue()) - }) - }) - Context("with namespace edit access", func() { - clusterRole := "edit" - It("should be able to create certificates", func() { - verb := "create" - - hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) - Expect(hasAccess).Should(BeTrue()) - }) - - It("should be able to delete certificates", func() { - verb := "delete" - - hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) - Expect(hasAccess).Should(BeTrue()) - }) - - It("should be able to delete collections of certificates", func() { - verb := "deletecollection" - - hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) - Expect(hasAccess).Should(BeTrue()) - }) - - It("should be able to patch certificates", func() { - verb := "patch" - - hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) - Expect(hasAccess).Should(BeTrue()) - }) - - It("should be able to update certificates", func() { - verb := "update" - - hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) - Expect(hasAccess).Should(BeTrue()) - }) - - It("should be able to get certificates", func() { - verb := "get" - - hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) - Expect(hasAccess).Should(BeTrue()) - }) - - It("should be able to list certificates", func() { - verb := "list" - - hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) - Expect(hasAccess).Should(BeTrue()) - }) - - It("should be able to watch certificates", func() { - verb := "watch" - - hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) - Expect(hasAccess).Should(BeTrue()) - }) - }) - - Context("with namespace admin access", func() { - clusterRole := "admin" - It("should be able to create certificates", func() { - verb := "create" - - hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) - Expect(hasAccess).Should(BeTrue()) - }) - - It("should be able to delete certificates", func() { - verb := "delete" - - hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) - Expect(hasAccess).Should(BeTrue()) - }) - - It("should be able to delete collections of certificates", func() { - verb := "deletecollection" - - hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) - Expect(hasAccess).Should(BeTrue()) - }) - - It("should be able to patch certificates", func() { - verb := "patch" - - hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) - Expect(hasAccess).Should(BeTrue()) - }) - - It("should be able to update certificates", func() { - verb := "update" - - hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) - Expect(hasAccess).Should(BeTrue()) - }) - - It("should be able to get certificates", func() { - verb := "get" - - hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) - Expect(hasAccess).Should(BeTrue()) - }) - - It("should be able to list certificates", func() { - verb := "list" - - hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) - Expect(hasAccess).Should(BeTrue()) - }) - - It("should be able to watch certificates", func() { - verb := "watch" - - hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) - Expect(hasAccess).Should(BeTrue()) - }) - }) - }) -} diff --git a/conformance/rbac/certificaterequest.go b/conformance/rbac/certificaterequest.go deleted file mode 100644 index 3603b4a..0000000 --- a/conformance/rbac/certificaterequest.go +++ /dev/null @@ -1,212 +0,0 @@ -/* -Copyright 2020 The cert-manager Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package rbac - -import ( - "conformance/framework" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -func (s *Suite) defineCertificateRequests() { - RBACDescribe("CertificateRequests", func() { - f := framework.NewFramework( - "rbac-certificaterequests", - s.KubeClientConfig, - "", - nil, - ) - resource := "certificaterequests" // this file is related to certificaterequests - - Context("with namespace view access", func() { - clusterRole := "view" - It("shouldn't be able to create certificaterequests", func() { - verb := "create" - - hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) - Expect(hasAccess).Should(BeFalse()) - }) - - It("shouldn't be able to delete certificaterequests", func() { - verb := "delete" - - hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) - Expect(hasAccess).Should(BeFalse()) - }) - - It("shouldn't be able to delete collections of certificaterequests", func() { - verb := "deletecollection" - - hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) - Expect(hasAccess).Should(BeFalse()) - }) - - It("shouldn't be able to patch certificaterequests", func() { - verb := "patch" - - hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) - Expect(hasAccess).Should(BeFalse()) - }) - - It("shouldn't be able to update certificaterequests", func() { - verb := "update" - - hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) - Expect(hasAccess).Should(BeFalse()) - }) - - It("should be able to get certificaterequests", func() { - verb := "get" - - hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) - Expect(hasAccess).Should(BeTrue()) - }) - - It("should be able to list certificaterequests", func() { - verb := "list" - - hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) - Expect(hasAccess).Should(BeTrue()) - }) - - It("should be able to watch certificaterequests", func() { - verb := "watch" - - hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) - Expect(hasAccess).Should(BeTrue()) - }) - }) - Context("with namespace edit access", func() { - clusterRole := "edit" - It("should be able to create certificaterequests", func() { - verb := "create" - - hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) - Expect(hasAccess).Should(BeTrue()) - }) - - It("should be able to delete certificaterequests", func() { - verb := "delete" - - hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) - Expect(hasAccess).Should(BeTrue()) - }) - - It("should be able to delete collections of certificaterequests", func() { - verb := "deletecollection" - - hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) - Expect(hasAccess).Should(BeTrue()) - }) - - It("should be able to patch certificaterequests", func() { - verb := "patch" - - hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) - Expect(hasAccess).Should(BeTrue()) - }) - - It("should be able to update certificaterequests", func() { - verb := "update" - - hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) - Expect(hasAccess).Should(BeTrue()) - }) - - It("should be able to get certificaterequests", func() { - verb := "get" - - hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) - Expect(hasAccess).Should(BeTrue()) - }) - - It("should be able to list certificaterequests", func() { - verb := "list" - - hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) - Expect(hasAccess).Should(BeTrue()) - }) - - It("should be able to watch certificaterequests", func() { - verb := "watch" - - hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) - Expect(hasAccess).Should(BeTrue()) - }) - }) - - Context("with namespace admin access", func() { - clusterRole := "admin" - It("should be able to create certificaterequests", func() { - verb := "create" - - hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) - Expect(hasAccess).Should(BeTrue()) - }) - - It("should be able to delete certificaterequests", func() { - verb := "delete" - - hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) - Expect(hasAccess).Should(BeTrue()) - }) - - It("should be able to delete collections of certificaterequests", func() { - verb := "deletecollection" - - hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) - Expect(hasAccess).Should(BeTrue()) - }) - - It("should be able to patch certificaterequests", func() { - verb := "patch" - - hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) - Expect(hasAccess).Should(BeTrue()) - }) - - It("should be able to update certificaterequests", func() { - verb := "update" - - hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) - Expect(hasAccess).Should(BeTrue()) - }) - - It("should be able to get certificaterequests", func() { - verb := "get" - - hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) - Expect(hasAccess).Should(BeTrue()) - }) - - It("should be able to list certificaterequests", func() { - verb := "list" - - hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) - Expect(hasAccess).Should(BeTrue()) - }) - - It("should be able to watch certificaterequests", func() { - verb := "watch" - - hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) - Expect(hasAccess).Should(BeTrue()) - }) - }) - }) -} diff --git a/conformance/rbac/doc.go b/conformance/rbac/doc.go deleted file mode 100644 index a333863..0000000 --- a/conformance/rbac/doc.go +++ /dev/null @@ -1,95 +0,0 @@ -/* -Copyright 2020 The cert-manager Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package rbac - -import ( - "context" - "time" - - authorizationv1 "k8s.io/api/authorization/v1" - v1 "k8s.io/api/core/v1" - rbacv1 "k8s.io/api/rbac/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes" - - "conformance/framework" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -// RBACDescribe wraps ConformanceDescribe with namespacing for RBAC tests -func RBACDescribe(text string, body func()) bool { - return framework.ConformanceDescribe("[RBAC] "+text, body) -} - -func RbacClusterRoleHasAccessToResource(f *framework.Framework, clusterRole string, verb string, resource string) bool { - By("Creating a service account") - viewServiceAccount := &v1.ServiceAccount{ - ObjectMeta: metav1.ObjectMeta{ - GenerateName: "rbac-test-", - }, - } - serviceAccountClient := f.KubeClientSet.CoreV1().ServiceAccounts(f.Namespace) - serviceAccount, err := serviceAccountClient.Create(context.TODO(), viewServiceAccount, metav1.CreateOptions{}) - Expect(err).NotTo(HaveOccurred()) - viewServiceAccountName := serviceAccount.Name - - By("Creating ClusterRoleBinding to view " + clusterRole + " clusterRole") - viewRoleBinding := &rbacv1.ClusterRoleBinding{ - ObjectMeta: metav1.ObjectMeta{ - GenerateName: viewServiceAccountName + "-rb-", - }, - Subjects: []rbacv1.Subject{ - {Kind: "ServiceAccount", Name: viewServiceAccountName, Namespace: f.Namespace}, - }, - RoleRef: rbacv1.RoleRef{ - APIGroup: "rbac.authorization.k8s.io", - Kind: "ClusterRole", - Name: clusterRole, - }, - } - roleBindingClient := f.KubeClientSet.RbacV1().ClusterRoleBindings() - _, err = roleBindingClient.Create(context.TODO(), viewRoleBinding, metav1.CreateOptions{}) - Expect(err).NotTo(HaveOccurred()) - - By("Sleeping for a second.") - // to allow RBAC to propagate - time.Sleep(time.Second) - - By("Impersonating the Service Account") - impersonateConfig := f.KubeClientConfig - impersonateConfig.Impersonate.UserName = "system:serviceaccount:" + f.Namespace + ":" + viewServiceAccountName - impersonateClient, err := kubernetes.NewForConfig(impersonateConfig) - Expect(err).NotTo(HaveOccurred()) - - By("Submitting a self subject access review") - sarClient := impersonateClient.AuthorizationV1().SelfSubjectAccessReviews() - sar := &authorizationv1.SelfSubjectAccessReview{ - Spec: authorizationv1.SelfSubjectAccessReviewSpec{ - ResourceAttributes: &authorizationv1.ResourceAttributes{ - Namespace: f.Namespace, - Verb: verb, - Group: "cert-manager.io", - Resource: resource, - }, - }, - } - response, err := sarClient.Create(context.TODO(), sar, metav1.CreateOptions{}) - Expect(err).NotTo(HaveOccurred()) - return response.Status.Allowed -} diff --git a/conformance/rbac/issuer.go b/conformance/rbac/issuer.go deleted file mode 100644 index 9fdd27d..0000000 --- a/conformance/rbac/issuer.go +++ /dev/null @@ -1,212 +0,0 @@ -/* -Copyright 2020 The cert-manager Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package rbac - -import ( - "conformance/framework" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" -) - -func (s *Suite) defineIssuers() { - RBACDescribe("Issuers", func() { - f := framework.NewFramework( - "rbac-issuers", - s.KubeClientConfig, - "", - nil, - ) - resource := "issuers" // this file is related to issuers - - Context("with namespace view access", func() { - clusterRole := "view" - It("shouldn't be able to create issuers", func() { - verb := "create" - - hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) - Expect(hasAccess).Should(BeFalse()) - }) - - It("shouldn't be able to delete issuers", func() { - verb := "delete" - - hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) - Expect(hasAccess).Should(BeFalse()) - }) - - It("shouldn't be able to delete collections of issuers", func() { - verb := "deletecollection" - - hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) - Expect(hasAccess).Should(BeFalse()) - }) - - It("shouldn't be able to patch issuers", func() { - verb := "patch" - - hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) - Expect(hasAccess).Should(BeFalse()) - }) - - It("shouldn't be able to update issuers", func() { - verb := "update" - - hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) - Expect(hasAccess).Should(BeFalse()) - }) - - It("should be able to get issuers", func() { - verb := "get" - - hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) - Expect(hasAccess).Should(BeTrue()) - }) - - It("should be able to list issuers", func() { - verb := "list" - - hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) - Expect(hasAccess).Should(BeTrue()) - }) - - It("should be able to watch issuers", func() { - verb := "watch" - - hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) - Expect(hasAccess).Should(BeTrue()) - }) - }) - Context("with namespace edit access", func() { - clusterRole := "edit" - It("should be able to create issuers", func() { - verb := "create" - - hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) - Expect(hasAccess).Should(BeTrue()) - }) - - It("should be able to delete issuers", func() { - verb := "delete" - - hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) - Expect(hasAccess).Should(BeTrue()) - }) - - It("should be able to delete collections of issuers", func() { - verb := "deletecollection" - - hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) - Expect(hasAccess).Should(BeTrue()) - }) - - It("should be able to patch issuers", func() { - verb := "patch" - - hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) - Expect(hasAccess).Should(BeTrue()) - }) - - It("should be able to update issuers", func() { - verb := "update" - - hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) - Expect(hasAccess).Should(BeTrue()) - }) - - It("should be able to get issuers", func() { - verb := "get" - - hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) - Expect(hasAccess).Should(BeTrue()) - }) - - It("should be able to list issuers", func() { - verb := "list" - - hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) - Expect(hasAccess).Should(BeTrue()) - }) - - It("should be able to watch issuers", func() { - verb := "watch" - - hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) - Expect(hasAccess).Should(BeTrue()) - }) - }) - - Context("with namespace admin access", func() { - clusterRole := "admin" - It("should be able to create issuers", func() { - verb := "create" - - hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) - Expect(hasAccess).Should(BeTrue()) - }) - - It("should be able to delete issuers", func() { - verb := "delete" - - hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) - Expect(hasAccess).Should(BeTrue()) - }) - - It("should be able to delete collections of issuers", func() { - verb := "deletecollection" - - hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) - Expect(hasAccess).Should(BeTrue()) - }) - - It("should be able to patch issuers", func() { - verb := "patch" - - hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) - Expect(hasAccess).Should(BeTrue()) - }) - - It("should be able to update issuers", func() { - verb := "update" - - hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) - Expect(hasAccess).Should(BeTrue()) - }) - - It("should be able to get issuers", func() { - verb := "get" - - hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) - Expect(hasAccess).Should(BeTrue()) - }) - - It("should be able to list issuers", func() { - verb := "list" - - hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) - Expect(hasAccess).Should(BeTrue()) - }) - - It("should be able to watch issuers", func() { - verb := "watch" - - hasAccess := RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) - Expect(hasAccess).Should(BeTrue()) - }) - }) - }) -} diff --git a/conformance/rbac/suite.go b/conformance/rbac/suite.go deleted file mode 100644 index d07e3cf..0000000 --- a/conformance/rbac/suite.go +++ /dev/null @@ -1,19 +0,0 @@ -package rbac - -import ( - "k8s.io/client-go/rest" -) - -// Suite defines a reusable conformance test suite that can be used against any -// Issuer implementation. -type Suite struct { - // KubeClientConfig is the configuration used to connect to the Kubernetes - // API server. - KubeClientConfig *rest.Config -} - -func (s *Suite) Define() { - s.defineCertificates() - s.defineCertificateRequests() - s.defineIssuers() -} From f76dcef1d258fd7db58699f65270eb9f4717ca75 Mon Sep 17 00:00:00 2001 From: Tim Ramlot <42113979+inteon@users.noreply.github.com> Date: Fri, 30 Jun 2023 10:03:23 +0200 Subject: [PATCH 13/13] update flag string repr Signed-off-by: Tim Ramlot <42113979+inteon@users.noreply.github.com> --- conformance/conformance_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/conformance/conformance_test.go b/conformance/conformance_test.go index ae0809d..35ac232 100644 --- a/conformance/conformance_test.go +++ b/conformance/conformance_test.go @@ -2,6 +2,7 @@ package conformance import ( "flag" + "fmt" "strings" "testing" @@ -18,7 +19,7 @@ import ( type arrayFlags []string func (i *arrayFlags) String() string { - return "my string representation" + return fmt.Sprintf("%v", *i) } func (i *arrayFlags) Set(value string) error {