diff --git a/Makefile b/Makefile index c599daa..1c39d8e 100644 --- a/Makefile +++ b/Makefile @@ -157,13 +157,28 @@ 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 .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. - $(GOTESTSUM) ./internal/testsetups/simple/e2e/... -coverprofile cover.out -timeout 1m +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 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 ##@ 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 new file mode 100644 index 0000000..34ff204 --- /dev/null +++ b/conformance/certificates/suite.go @@ -0,0 +1,116 @@ +/* +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" + + cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" + "k8s.io/client-go/rest" + + "conformance/framework" + "conformance/framework/helper/featureset" + + . "github.com/onsi/ginkgo/v2" +) + +// 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 + + // 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 + + // 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 + // 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.IssuerRef != (cmmeta.ObjectReference{}) && s.IssuerRef.Name == "" { + Fail("IssuerRef must be set") + } + + if s.Namespace == "" { + Fail("Namespace 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, cmmeta.ObjectReference), requiredFeatures ...featureset.Feature) { + if !s.checkFeatures(requiredFeatures...) { + return + } + It(name, func(ctx context.Context) { + fn(ctx, s.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 { + for _, f := range fs { + if s.UnsupportedFeatures.Contains(f) { + return false + } + } + + return true +} diff --git a/conformance/certificates/tests.go b/conformance/certificates/tests.go new file mode 100644 index 0000000..2effbab --- /dev/null +++ b/conformance/certificates/tests.go @@ -0,0 +1,563 @@ +/* +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 certificates + +import ( + "context" + "crypto/x509/pkix" + "encoding/asn1" + "encoding/base64" + "fmt" + "reflect" + "time" + + 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" + "sigs.k8s.io/controller-runtime/pkg/client" + + "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" +) + +// 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() { + f := framework.NewFramework( + "certificates", + s.KubeClientConfig, + s.Namespace, + []client.Object{ + &cmapi.Certificate{}, + &cmapi.CertificateRequest{}, + &corev1.Secret{}, + }, + ) + + 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) + }) + + 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, + } + }, + 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, + } + }, + 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, + } + }, + // 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, + } + }, + // 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 + }, + }, + requiredFeatures: []featureset.Feature{featureset.LiteralSubjectFeature}, + }, + } + + 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: "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: "e2e-conformance-tls-" + randomTestID, + IssuerRef: issuerRef, + SecretTemplate: &cmapi.CertificateSecretTemplate{ + Labels: map[string]string{ + f.CleanupLabel: "true", + }, + }, + }, + } + + certificate = gen.CertificateFrom( + certificate, + test.certModifiers..., + ) + + By("Creating a Certificate") + err := f.CRClient.Create(ctx, certificate) + Expect(err).NotTo(HaveOccurred()) + + By("Waiting for the Certificate to be issued...") + certificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(ctx, certificate.Name, certificate.Namespace, certificate.Generation, time.Minute*8) + Expect(err).NotTo(HaveOccurred()) + + By("Validating the issued Certificate...") + validations := append(test.extraValidations, validation.CertificateSetForUnsupportedFeatureSet(s.UnsupportedFeatures)...) + err = f.Helper().ValidateCertificate(ctx, 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(ctx context.Context, issuerRef cmmeta.ObjectReference) { + randomTestID := e2eutil.RandStringRunes(10) + testCertificate := &cmapi.Certificate{ + ObjectMeta: metav1.ObjectMeta{ + 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: "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") + err := f.CRClient.Create(ctx, testCertificate) + Expect(err).NotTo(HaveOccurred()) + + By("Waiting for the Certificate to be issued...") + testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(ctx, testCertificate.Name, testCertificate.Namespace, testCertificate.Generation, time.Minute*8) + Expect(err).NotTo(HaveOccurred()) + + By("Validating the issued Certificate...") + err = f.Helper().ValidateCertificate(ctx, testCertificate, validation.CertificateSetForUnsupportedFeatureSet(s.UnsupportedFeatures)...) + Expect(err).NotTo(HaveOccurred()) + + By("Deleting existing certificate data in Secret") + 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") + + 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).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, 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 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: "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: "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) + + 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(ctx, testCertificate.Name, testCertificate.Namespace, testCertificate.Generation, time.Minute*8) + Expect(err).NotTo(HaveOccurred()) + + By("Sanity-check the issued Certificate") + 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) + 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 + } + + 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(ctx, testCertificate.Name, testCertificate.Namespace, testCertificate.Generation, time.Minute*8) + Expect(err).NotTo(HaveOccurred()) + + By("Sanity-check the issued Certificate") + 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 new file mode 100644 index 0000000..3da20e8 --- /dev/null +++ b/conformance/certificatesigningrequests/suite.go @@ -0,0 +1,107 @@ +/* +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" + + "k8s.io/client-go/rest" + + "conformance/framework" + "conformance/framework/helper/featureset" + + . "github.com/onsi/ginkgo/v2" +) + +// 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 + + // 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 + // 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.SignerName == "" { + Fail("SignerName 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) { + fn(ctx, s.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 { + for _, f := range fs { + if s.UnsupportedFeatures.Contains(f) { + return false + } + } + + return true +} diff --git a/conformance/certificatesigningrequests/tests.go b/conformance/certificatesigningrequests/tests.go new file mode 100644 index 0000000..17f4dd5 --- /dev/null +++ b/conformance/certificatesigningrequests/tests.go @@ -0,0 +1,517 @@ +/* +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" + + 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" + "sigs.k8s.io/controller-runtime/pkg/client" + + "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" +) + +// 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, + "", + []client.Object{ + &certificatesv1.CertificateSigningRequest{}, + }, + ) + + 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 + // 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) + }) + + 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 []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: []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: []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: []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: []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: []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: []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: []gen.CSRModifier{ + gen.SetCSRCommonName("test-common-name-" + e2eutil.RandStringRunes(10)), + gen.SetCSRIPAddresses(net.ParseIP(sharedIPAddress)), + }, + kubeCSRUsages: []certificatesv1.KeyUsage{ + certificatesv1.UsageDigitalSignature, + certificatesv1.UsageKeyEncipherment, + }, + 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: []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: []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 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(commonName), + gen.SetCSRDNSNames(commonName, e2eutil.RandomSubdomain(s.DomainSuffix)), + } + }(), + kubeCSRUsages: []certificatesv1.KeyUsage{ + certificatesv1.UsageDigitalSignature, + certificatesv1.UsageKeyEncipherment, + }, + 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: []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 { + commonName := e2eutil.RandomSubdomain(s.DomainSuffix) + + return []gen.CSRModifier{ + gen.SetCSRCommonName(commonName), + gen.SetCSRDNSNames(commonName), + } + }(), + 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 { + commonName := e2eutil.RandomSubdomain(s.DomainSuffix) + + return []gen.CSRModifier{ + gen.SetCSRCommonName(commonName), + gen.SetCSRDNSNames(commonName), + } + }(), + 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: []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: []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 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.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, + certificatesv1.UsageKeyEncipherment, + }, + requiredFeatures: []featureset.Feature{featureset.URISANsFeature, featureset.OnlySAN}, + }, + { + name: "should issue a certificate that includes arbitrary key usages with common name", + keyAlgo: x509.RSA, + csrModifiers: []gen.CSRModifier{ + gen.SetCSRCommonName(e2eutil.RandomSubdomain(s.DomainSuffix)), + }, + kubeCSRUsages: []certificatesv1.KeyUsage{ + certificatesv1.UsageServerAuth, + certificatesv1.UsageClientAuth, + 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: []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}, + }, + { + 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}, + }, + } + + 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 + csr, key, err := gen.CSR(test.keyAlgo, test.csrModifiers...) + Expect(err).NotTo(HaveOccurred()) + + // Create CertificateSigningRequest + randomTestID := e2eutil.RandStringRunes(10) + kubeCSR := &certificatesv1.CertificateSigningRequest{ + ObjectMeta: metav1.ObjectMeta{ + 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, + SignerName: signerName, + Usages: test.kubeCSRUsages, + ExpirationSeconds: test.kubeCSRExpirationSeconds, + }, + } + + // Create the request, and delete at the end of the test + By("Creating a CertificateSigningRequest") + Expect(f.CRClient.Create(ctx, kubeCSR)).NotTo(HaveOccurred()) + + // 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(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(ctx, kubeCSR.Name, key, validations...) + Expect(err).NotTo(HaveOccurred()) + }, test.requiredFeatures...) + } + + for _, tc := range tests { + defineTest(tc) + } + }) +} diff --git a/conformance/conformance_test.go b/conformance/conformance_test.go new file mode 100644 index 0000000..35ac232 --- /dev/null +++ b/conformance/conformance_test.go @@ -0,0 +1,92 @@ +package conformance + +import ( + "flag" + "fmt" + "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 fmt.Sprintf("%v", *i) +} + +func (i *arrayFlags) Set(value string) error { + *i = append(*i, value) + 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") +} + +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, + Namespace: namespace, + 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/framework.go b/conformance/framework/framework.go new file mode 100644 index 0000000..aa202c9 --- /dev/null +++ b/conformance/framework/framework.go @@ -0,0 +1,154 @@ +/* +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" + + clientset "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned" + certmgrscheme "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned/scheme" + 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" + "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 + + // KubeClientConfig which was used to create the connection. + KubeClientConfig *rest.Config + + // Kubernetes API clientsets + KubeClientSet kubernetes.Interface + CertManagerClientSet clientset.Interface + APIExtensionsClientSet apiextcs.Interface + + // controller-runtime client for newer controllers + CRClient crclient.Client + + // 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 +} + +// 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, + namespace string, + cleanupResourceTypes []crclient.Object, +) *Framework { + f := &Framework{ + BaseName: baseName, + KubeClientConfig: kubeClientConfig, + Namespace: namespace, + cleanupResourceTypes: cleanupResourceTypes, + } + + f.helper = helper.NewHelper() + BeforeEach(f.BeforeEach) + AfterEach(f.AfterEach) + + return f +} + +// 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) + + f.KubeClientConfig = kubeConfig + + f.KubeClientSet, err = kubernetes.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") + 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()) + + 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 +} + +func (f *Framework) AfterEach(ctx context.Context) { + 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 { + return f.helper +} + +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 new file mode 100644 index 0000000..096c214 --- /dev/null +++ b/conformance/framework/helper/certificates.go @@ -0,0 +1,87 @@ +/* +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" + + 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" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/wait" + + "conformance/framework/log" +) + +// 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(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: minGeneration, + } + issuing_true_condition := v1.CertificateCondition{ + Type: v1.CertificateConditionIssuing, + Status: cmmeta.ConditionTrue, + } + + var certificate *v1.Certificate + logf, done := log.LogBackoff() + defer done() + + 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", + certificate.Name, + ready_true_condition.Type, + ready_true_condition.Status, + ready_true_condition.ObservedGeneration, + certificate.Status.Conditions, + ) + 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, 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, nil + } + + 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 new file mode 100644 index 0000000..25ddafb --- /dev/null +++ b/conformance/framework/helper/certificatesigningrequests.go @@ -0,0 +1,63 @@ +/* +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" + + "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" + + "conformance/framework/log" +) + +// WaitForCertificateSigningRequestSigned waits for the +// CertificateSigningRequest resource to be signed. +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(pollCtx, 500*time.Millisecond, timeout, true, func(ctx context.Context) (bool, error) { + var err error + csr, err = h.KubeClient.CertificatesV1().CertificateSigningRequests().Get(ctx, name, metav1.GetOptions{}) + if err != nil { + return false, fmt.Errorf("error getting CertificateSigningRequest %s: %w", name, err) + } + + if util.CertificateSigningRequestIsFailed(csr) { + return false, fmt.Errorf("CertificateSigningRequest has failed: %v", csr.Status) + } + + 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 csr, 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..eda636b --- /dev/null +++ b/conformance/framework/helper/featureset/featureset.go @@ -0,0 +1,189 @@ +/* +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 ( + "fmt" + "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 { + newSet := make(FeatureSet, len(fs)) + for k, v := range fs { + newSet[k] = v + } + return newSet +} + +// List returns a slice of all features in the set. +func (fs FeatureSet) List() []Feature { + ret := make([]Feature, 0, len(fs)) + 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, 0, len(fs)) + for f := range fs { + featsSlice = append(featsSlice, string(f)) + } + 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 +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..9f2f899 --- /dev/null +++ b/conformance/framework/helper/helper.go @@ -0,0 +1,32 @@ +/* +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 ( + cmclient "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned" + "k8s.io/client-go/kubernetes" +) + +// Helper provides methods for common operations needed during tests. +type Helper struct { + KubeClient kubernetes.Interface + CMClient cmclient.Interface +} + +func NewHelper() *Helper { + return &Helper{} +} diff --git a/conformance/framework/helper/secret.go b/conformance/framework/helper/secret.go new file mode 100644 index 0000000..8cd0f92 --- /dev/null +++ b/conformance/framework/helper/secret.go @@ -0,0 +1,58 @@ +/* +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" + + "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, name string, namespace string, timeout time.Duration) (*corev1.Secret, error) { + var secret *corev1.Secret + logf, done := log.LogBackoff() + defer done() + + err := wait.PollUntilContextTimeout(pollCtx, 500*time.Millisecond, timeout, true, func(ctx context.Context) (bool, error) { + var err error + secret, err = h.KubeClient.CoreV1().Secrets(namespace).Get(ctx, name, metav1.GetOptions{}) + if err != nil { + return false, fmt.Errorf("error getting Secret %s: %w", name, err) + } + + if len(secret.Data[corev1.TLSCertKey]) == 0 { + logf("Secret still does not contain certificate data %s/%s", secret.Namespace, secret.Name) + return false, nil + } + + return true, nil + }) + + if err != nil { + return secret, err + } + + return secret, nil +} diff --git a/conformance/framework/helper/validate.go b/conformance/framework/helper/validate.go new file mode 100644 index 0000000..b90c43c --- /dev/null +++ b/conformance/framework/helper/validate.go @@ -0,0 +1,71 @@ +/* +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" + "fmt" + + cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "conformance/framework/helper/validation/certificates" + "conformance/framework/helper/validation/certificatesigningrequests" +) + +// ValidateCertificate retrieves the issued certificate and runs all validation functions +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(ctx, 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(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(ctx, 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..e97c658 --- /dev/null +++ b/conformance/framework/helper/validation/certificates/certificates.go @@ -0,0 +1,451 @@ +/* +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/x509" + "encoding/pem" + "fmt" + "strings" + "time" + + 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 +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 + } + + 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, ", ")) + } + + 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 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) + } + + 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, 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 +} + +// 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 + } + + expectedDuration := apiutil.DefaultCertDuration(certificate.Spec.Duration) + 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+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 +} + +// 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) + } + + 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..0153cd2 --- /dev/null +++ b/conformance/framework/helper/validation/certificatesigningrequests/certificatesigningrequests.go @@ -0,0 +1,364 @@ +/* +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" + + 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 +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 +} + +// 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 + } + + 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) + 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+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 +} + +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 +} + +// 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 + } + + 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) + } + + 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..7b78fa7 --- /dev/null +++ b/conformance/framework/helper/validation/validation.go @@ -0,0 +1,103 @@ +/* +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 ( + "conformance/framework/helper/featureset" + "conformance/framework/helper/validation/certificates" + "conformance/framework/helper/validation/certificatesigningrequests" +) + +func CertificateSetForUnsupportedFeatureSet(fs featureset.FeatureSet) []certificates.ValidationFunc { + // basics + out := []certificates.ValidationFunc{ + certificates.ExpectCertificateDNSNamesToMatch, + certificates.ExpectCertificateOrganizationToMatch, + certificates.ExpectValidCertificate, + certificates.ExpectValidPrivateKeyData, + certificates.ExpectValidCommonName, + certificates.ExpectValidBasicConstraints, + + certificates.ExpectValidNotAfterDate, + certificates.ExpectValidKeysInSecret, + certificates.ExpectValidAnnotations, + + certificates.ExpectConditionReadyObservedGeneration, + } + + if !fs.Contains(featureset.URISANsFeature) { + out = append(out, certificates.ExpectCertificateURIsToMatch) + } + + if !fs.Contains(featureset.EmailSANsFeature) { + 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) + + if !fs.Contains(featureset.SaveRootCAToSecret) { + out = append(out, certificates.ExpectCARootCertificate) + } + } + + if !fs.Contains(featureset.DurationFeature) { + out = append(out, certificates.ExpectDurationToMatch) + } + + return out +} + +func CertificateSigningRequestSetForUnsupportedFeatureSet(fs featureset.FeatureSet) []certificatesigningrequests.ValidationFunc { + // 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) { + out = append(out, certificatesigningrequests.ExpectDurationToMatch) + } + + return out +} diff --git a/conformance/framework/log/log.go b/conformance/framework/log/log.go new file mode 100644 index 0000000..dab4330 --- /dev/null +++ b/conformance/framework/log/log.go @@ -0,0 +1,79 @@ +/* +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 Logf(format string, args ...interface{}) { + fmt.Fprintf(Writer, nowStamp()+": INFO: "+format+"\n", 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/go.mod b/conformance/go.mod new file mode 100644 index 0000000..5edb00a --- /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 +) + +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/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/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/util/random.go b/conformance/util/random.go new file mode 100644 index 0000000..1407a90 --- /dev/null +++ b/conformance/util/random.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/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/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 f93ca5e..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 @@ -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/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/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) 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/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 \ diff --git a/make/tools.mk b/make/tools.mk index 222c248..7d19a61 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}' ./conformance/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