diff --git a/pkg/api/util/BUILD.bazel b/pkg/api/util/BUILD.bazel index 3322dd028..7dac105fb 100644 --- a/pkg/api/util/BUILD.bazel +++ b/pkg/api/util/BUILD.bazel @@ -6,6 +6,7 @@ go_library( "conditions.go", "duration.go", "issuers.go", + "names.go", "usages.go", ], importpath = "github.com/jetstack/cert-manager/pkg/api/util", diff --git a/pkg/api/util/names.go b/pkg/api/util/names.go new file mode 100644 index 000000000..65de19ea0 --- /dev/null +++ b/pkg/api/util/names.go @@ -0,0 +1,43 @@ +/* +Copyright 2019 The Jetstack cert-manager contributors. + +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 ( + "encoding/json" + "fmt" + "hash/fnv" + + cmapi "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha2" +) + +func ComputeCertificateRequestName(crt *cmapi.Certificate) (string, error) { + crt = crt.DeepCopy() + specBytes, err := json.Marshal(crt.Spec) + if err != nil { + return "", err + } + + hashF := fnv.New32() + _, err = hashF.Write(specBytes) + if err != nil { + return "", err + } + + // shorten the cert name to 52 chars to ensure the total length of the name + // is less than or equal to 64 characters + return fmt.Sprintf("%.52s-%d", crt.Name, hashF.Sum32()), nil +} diff --git a/pkg/controller/certificates/BUILD.bazel b/pkg/controller/certificates/BUILD.bazel index 2ed9ec86f..74d7f61fa 100644 --- a/pkg/controller/certificates/BUILD.bazel +++ b/pkg/controller/certificates/BUILD.bazel @@ -39,6 +39,25 @@ go_library( ], ) +go_test( + name = "go_default_test", + srcs = ["sync_test.go"], + embed = [":go_default_library"], + deps = [ + "//pkg/api/util:go_default_library", + "//pkg/apis/certmanager/v1alpha2:go_default_library", + "//pkg/apis/meta/v1:go_default_library", + "//pkg/controller/test:go_default_library", + "//pkg/util/pki:go_default_library", + "//test/unit/gen:go_default_library", + "@io_k8s_api//core/v1:go_default_library", + "@io_k8s_apimachinery//pkg/apis/meta/v1:go_default_library", + "@io_k8s_apimachinery//pkg/runtime:go_default_library", + "@io_k8s_client_go//testing:go_default_library", + "@io_k8s_utils//clock/testing:go_default_library", + ], +) + filegroup( name = "package-srcs", srcs = glob(["**"]), @@ -52,21 +71,3 @@ filegroup( tags = ["automanaged"], visibility = ["//visibility:public"], ) - -go_test( - name = "go_default_test", - srcs = ["sync_test.go"], - embed = [":go_default_library"], - deps = [ - "//pkg/apis/certmanager/v1alpha2:go_default_library", - "//pkg/apis/meta/v1:go_default_library", - "//pkg/controller/test:go_default_library", - "//pkg/util/pki:go_default_library", - "//test/unit/gen:go_default_library", - "@io_k8s_api//core/v1:go_default_library", - "@io_k8s_apimachinery//pkg/apis/meta/v1:go_default_library", - "@io_k8s_apimachinery//pkg/runtime:go_default_library", - "@io_k8s_client_go//testing:go_default_library", - "@io_k8s_utils//clock/testing:go_default_library", - ], -) diff --git a/pkg/controller/certificates/sync.go b/pkg/controller/certificates/sync.go index 4c20393b1..55de11a06 100644 --- a/pkg/controller/certificates/sync.go +++ b/pkg/controller/certificates/sync.go @@ -21,10 +21,8 @@ import ( "crypto/ecdsa" "crypto/rsa" "crypto/x509" - "encoding/json" "encoding/pem" "fmt" - "hash/fnv" "reflect" "strings" "time" @@ -188,7 +186,7 @@ func (c *certificateRequestManager) processCertificate(ctx context.Context, crt // The certificate request name is a product of the certificate's spec, // which makes it unique and predictable. // First we compute what we expect it to be. - expectedReqName, err := expectedCertificateRequestName(crt) + expectedReqName, err := apiutil.ComputeCertificateRequestName(crt) if err != nil { return fmt.Errorf("internal error hashing certificate spec: %v", err) } @@ -584,24 +582,6 @@ func (c *certificateRequestManager) certificateRequiresIssuance(ctx context.Cont return needsRenew, []string{"Certificate is expiring soon"}, nil } -func expectedCertificateRequestName(crt *cmapi.Certificate) (string, error) { - crt = crt.DeepCopy() - specBytes, err := json.Marshal(crt.Spec) - if err != nil { - return "", err - } - - hashF := fnv.New32() - _, err = hashF.Write(specBytes) - if err != nil { - return "", err - } - - // shorten the cert name to 52 chars to ensure the total length of the name - // is less than or equal to 64 characters - return fmt.Sprintf("%.52s-%d", crt.Name, hashF.Sum32()), nil -} - type generateCSRFn func(*cmapi.Certificate, []byte) ([]byte, error) func generateCSRImpl(crt *cmapi.Certificate, pk []byte) ([]byte, error) { diff --git a/pkg/controller/certificates/sync_test.go b/pkg/controller/certificates/sync_test.go index 260162413..24f9cb66e 100644 --- a/pkg/controller/certificates/sync_test.go +++ b/pkg/controller/certificates/sync_test.go @@ -31,6 +31,7 @@ import ( coretesting "k8s.io/client-go/testing" fakeclock "k8s.io/utils/clock/testing" + apiutil "github.com/jetstack/cert-manager/pkg/api/util" cmapi "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha2" cmmeta "github.com/jetstack/cert-manager/pkg/apis/meta/v1" testpkg "github.com/jetstack/cert-manager/pkg/controller/test" @@ -81,7 +82,7 @@ func mustCreateCryptoBundle(t *testing.T, crt *cmapi.Certificate) cryptoBundle { } func createCryptoBundle(crt *cmapi.Certificate) (*cryptoBundle, error) { - reqName, err := expectedCertificateRequestName(crt) + reqName, err := apiutil.ComputeCertificateRequestName(crt) if err != nil { return nil, err } diff --git a/test/e2e/framework/addon/vault/setup.go b/test/e2e/framework/addon/vault/setup.go index 27521aebb..a8193b7c3 100644 --- a/test/e2e/framework/addon/vault/setup.go +++ b/test/e2e/framework/addon/vault/setup.go @@ -406,6 +406,7 @@ func (v *VaultInitializer) setupRole() error { params := map[string]string{ "allow_any_name": "true", "max_ttl": "2160h", + "key_type": "any", } url := path.Join("/v1", v.IntermediateMount, "roles", v.Role) diff --git a/test/e2e/framework/framework.go b/test/e2e/framework/framework.go index d204fd8b9..72f1a98b4 100644 --- a/test/e2e/framework/framework.go +++ b/test/e2e/framework/framework.go @@ -22,7 +22,6 @@ import ( . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" - "k8s.io/api/core/v1" api "k8s.io/api/core/v1" apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" apiextcs "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" @@ -78,7 +77,7 @@ type Framework struct { CRClient crclient.Client // Namespace in which all test resources should reside - Namespace *v1.Namespace + Namespace *api.Namespace // To make sure that this framework cleans up after itself, no matter what, // we install a Cleanup action before each test and clear it after. If we @@ -232,7 +231,7 @@ func (f *Framework) Helper() *helper.Helper { return f.helper } -func (f *Framework) CertificateDurationValid(c *v1alpha2.Certificate, duration time.Duration) { +func (f *Framework) CertificateDurationValid(c *v1alpha2.Certificate, duration time.Duration, fuzz time.Duration) { By("Verifying TLS certificate exists") secret, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(c.Spec.SecretName, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) @@ -243,8 +242,10 @@ func (f *Framework) CertificateDurationValid(c *v1alpha2.Certificate, duration t cert, err := pki.DecodeX509CertificateBytes(certBytes) Expect(err).NotTo(HaveOccurred()) By("Verifying that the duration is valid") - if cert.NotAfter.Sub(cert.NotBefore) != duration { - Failf("Expected duration of %s, got %s [NotBefore: %s, NotAfter: %s]", duration, cert.NotAfter.Sub(cert.NotBefore), cert.NotBefore.Format(time.RFC3339), cert.NotAfter.Format(time.RFC3339)) + certDuration := cert.NotAfter.Sub(cert.NotBefore) + if certDuration > (duration+fuzz) || certDuration < duration { + Failf("Expected duration of %s, got %s (fuzz: %s) [NotBefore: %s, NotAfter: %s]", duration, certDuration, + fuzz, cert.NotBefore.Format(time.RFC3339), cert.NotAfter.Format(time.RFC3339)) } } diff --git a/test/e2e/framework/helper/BUILD.bazel b/test/e2e/framework/helper/BUILD.bazel index 88e79b55e..4c073016b 100644 --- a/test/e2e/framework/helper/BUILD.bazel +++ b/test/e2e/framework/helper/BUILD.bazel @@ -8,6 +8,7 @@ go_library( "helper.go", "kubectl.go", "pod_start.go", + "secret.go", ], importpath = "github.com/jetstack/cert-manager/test/e2e/framework/helper", tags = ["manual"], diff --git a/test/e2e/framework/helper/secret.go b/test/e2e/framework/helper/secret.go new file mode 100644 index 000000000..aac4e89ec --- /dev/null +++ b/test/e2e/framework/helper/secret.go @@ -0,0 +1,58 @@ +/* +Copyright 2019 The Jetstack cert-manager contributors. + +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 ( + "fmt" + "time" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/wait" + + "github.com/jetstack/cert-manager/test/e2e/framework/log" +) + +// WaitForSecretCertificateData waits for the certificate data to be ready +// inside a Secret created by cert-manager. +func (h *Helper) WaitForSecretCertificateData(ns, name string, timeout time.Duration) (*corev1.Secret, error) { + var secret *corev1.Secret + err := wait.PollImmediate(time.Second, timeout, + func() (bool, error) { + var err error + log.Logf("Waiting for Secret %s:%s to contain a certificate", ns, name) + secret, err = h.KubeClient.CoreV1().Secrets(ns).Get(name, metav1.GetOptions{}) + if err != nil { + return false, fmt.Errorf("error getting secret %s: %s", name, err) + } + + if len(secret.Data[corev1.TLSCertKey]) > 0 { + return true, nil + } + + log.Logf("Secret still does not contain certificate data %s/%s: %v", + secret.Namespace, secret.Name, secret.Data) + return false, nil + }, + ) + + if err != nil { + return nil, err + } + + return secret, nil +} diff --git a/test/e2e/suite/conformance/BUILD.bazel b/test/e2e/suite/conformance/BUILD.bazel index 97d49e697..2d342f18c 100644 --- a/test/e2e/suite/conformance/BUILD.bazel +++ b/test/e2e/suite/conformance/BUILD.bazel @@ -10,6 +10,8 @@ go_library( "//test/e2e/suite/conformance/certificates/acme:go_default_library", "//test/e2e/suite/conformance/certificates/ca:go_default_library", "//test/e2e/suite/conformance/certificates/selfsigned:go_default_library", + "//test/e2e/suite/conformance/certificates/vault:go_default_library", + "//test/e2e/suite/conformance/certificates/venafi:go_default_library", "//test/e2e/suite/conformance/rbac:go_default_library", ], ) diff --git a/test/e2e/suite/conformance/certificates/BUILD.bazel b/test/e2e/suite/conformance/certificates/BUILD.bazel index 10066fe49..610418200 100644 --- a/test/e2e/suite/conformance/certificates/BUILD.bazel +++ b/test/e2e/suite/conformance/certificates/BUILD.bazel @@ -9,12 +9,15 @@ go_library( importpath = "github.com/jetstack/cert-manager/test/e2e/suite/conformance/certificates", visibility = ["//visibility:public"], deps = [ + "//pkg/api/util:go_default_library", "//pkg/apis/certmanager/v1alpha2:go_default_library", "//pkg/apis/meta/v1:go_default_library", "//pkg/util:go_default_library", + "//pkg/util/pki:go_default_library", "//test/e2e/framework:go_default_library", "@com_github_onsi_ginkgo//:go_default_library", "@com_github_onsi_gomega//:go_default_library", + "@io_k8s_api//core/v1:go_default_library", "@io_k8s_apimachinery//pkg/apis/meta/v1:go_default_library", ], ) @@ -33,6 +36,8 @@ filegroup( "//test/e2e/suite/conformance/certificates/acme:all-srcs", "//test/e2e/suite/conformance/certificates/ca:all-srcs", "//test/e2e/suite/conformance/certificates/selfsigned:all-srcs", + "//test/e2e/suite/conformance/certificates/vault:all-srcs", + "//test/e2e/suite/conformance/certificates/venafi:all-srcs", ], tags = ["automanaged"], visibility = ["//visibility:public"], diff --git a/test/e2e/suite/conformance/certificates/acme/BUILD.bazel b/test/e2e/suite/conformance/certificates/acme/BUILD.bazel index 02577b584..b5d48227d 100644 --- a/test/e2e/suite/conformance/certificates/acme/BUILD.bazel +++ b/test/e2e/suite/conformance/certificates/acme/BUILD.bazel @@ -13,6 +13,7 @@ go_library( "//test/e2e/framework/addon/pebble:go_default_library", "//test/e2e/framework/addon/tiller:go_default_library", "//test/e2e/suite/conformance/certificates:go_default_library", + "//test/e2e/suite/issuers/acme/dnsproviders:go_default_library", "@com_github_onsi_ginkgo//:go_default_library", "@com_github_onsi_gomega//:go_default_library", "@io_k8s_apimachinery//pkg/apis/meta/v1:go_default_library", diff --git a/test/e2e/suite/conformance/certificates/acme/acme.go b/test/e2e/suite/conformance/certificates/acme/acme.go index 96e0e4ded..b4d3f19ca 100644 --- a/test/e2e/suite/conformance/certificates/acme/acme.go +++ b/test/e2e/suite/conformance/certificates/acme/acme.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package selfsigned +package acme import ( . "github.com/onsi/ginkgo" @@ -28,76 +28,79 @@ import ( "github.com/jetstack/cert-manager/test/e2e/framework/addon/pebble" "github.com/jetstack/cert-manager/test/e2e/framework/addon/tiller" "github.com/jetstack/cert-manager/test/e2e/suite/conformance/certificates" + "github.com/jetstack/cert-manager/test/e2e/suite/issuers/acme/dnsproviders" ) var _ = framework.ConformanceDescribe("Certificates", func() { - // unsupportedFeatures is a list of features that are not supported by the ACME + // unsupportedHTTP01Features is a list of features that are not supported by the ACME // issuer type using HTTP01 - var unsupportedFeatures = certificates.NewFeatureSet( + var unsupportedHTTP01Features = certificates.NewFeatureSet( certificates.IPAddressFeature, - certificates.Wildcards, + certificates.DurationFeature, + certificates.WildcardsFeature, ) - provisioner := &acmeIssuerProvisioner{setGroupName: false} + // unsupportedDNS01Features is a list of features that are not supported by the ACME + // issuer type using DNS01 + var unsupportedDNS01Features = certificates.NewFeatureSet( + certificates.IPAddressFeature, + certificates.DurationFeature, + ) + + provisionerHTTP01 := new(acmeIssuerProvisioner) (&certificates.Suite{ Name: "ACME HTTP01", - CreateIssuerFunc: provisioner.create, - DeleteIssuerFunc: provisioner.delete, - UnsupportedFeatures: unsupportedFeatures, + CreateIssuerFunc: provisionerHTTP01.createHTTP01, + DeleteIssuerFunc: provisionerHTTP01.delete, + UnsupportedFeatures: unsupportedHTTP01Features, }).Define() - // crProvisioner sets the issuerRef.group field on Certificates it creates - crProvisioner := &acmeIssuerProvisioner{setGroupName: true} + provisionerDNS01 := new(acmeIssuerProvisioner) (&certificates.Suite{ - Name: "ACME HTTP01 (CertificateRequest)", - CreateIssuerFunc: crProvisioner.create, - DeleteIssuerFunc: crProvisioner.delete, - UnsupportedFeatures: unsupportedFeatures, + Name: "ACME DNS01", + CreateIssuerFunc: provisionerDNS01.createDNS01, + DeleteIssuerFunc: provisionerDNS01.delete, + UnsupportedFeatures: unsupportedDNS01Features, }).Define() }) type acmeIssuerProvisioner struct { - tiller *tiller.Tiller - pebble *pebble.Pebble - // if setGroupName is true, the 'group name' field on the IssuerRef will be - // set the 'cert-manager.io'. - // Setting the group name will cause the new 'certificate requests' based - // implementation to be used, however this is not implemented for ACME yet - // See: https://github.com/jetstack/cert-manager/pull/1943 - setGroupName bool + tiller *tiller.Tiller + pebble *pebble.Pebble + cloudflare *dnsproviders.Cloudflare } func (a *acmeIssuerProvisioner) delete(f *framework.Framework, ref cmmeta.ObjectReference) { - Expect(a.pebble.Deprovision()).NotTo(HaveOccurred(), "failed to deprovision pebble") + if a.pebble != nil { + Expect(a.pebble.Deprovision()).NotTo(HaveOccurred(), "failed to deprovision pebble") + } + if a.cloudflare != nil { + Expect(a.cloudflare.Deprovision()).NotTo(HaveOccurred(), "failed to deprovision cloudflare") + } Expect(a.tiller.Deprovision()).NotTo(HaveOccurred(), "failed to deprovision tiller") } -// create will deploy the required components to run an ACME issuer based test. +// createXXX will deploy the required components to run an ACME issuer based test. // This includes: // - tiller // - pebble // - a properly configured Issuer resource -func (a *acmeIssuerProvisioner) create(f *framework.Framework) cmmeta.ObjectReference { - a.tiller = &tiller.Tiller{ - Name: "tiller-deploy", - ClusterPermissions: false, - Namespace: f.Namespace.Name, - } - Expect(a.tiller.Setup(f.Config)).NotTo(HaveOccurred(), "failed to setup tiller") - Expect(a.tiller.Provision()).NotTo(HaveOccurred(), "failed to provision tiller") + +func (a *acmeIssuerProvisioner) createHTTP01(f *framework.Framework) cmmeta.ObjectReference { + a.deployTiller(f, "http01") a.pebble = &pebble.Pebble{ Tiller: a.tiller, - Name: "cm-e2e-create-acme-issuer", + Name: "cm-e2e-create-acme-http01-issuer", Namespace: f.Namespace.Name, } Expect(a.pebble.Setup(f.Config)).NotTo(HaveOccurred(), "failed to setup pebble") Expect(a.pebble.Provision()).NotTo(HaveOccurred(), "failed to provision pebble") - By("Creating an ACME issuer") + By("Creating an ACME HTTP01 issuer") issuer := &cmapi.Issuer{ ObjectMeta: metav1.ObjectMeta{ - Name: "acme-issuer", + Name: "acme-issuer-http01", }, Spec: cmapi.IssuerSpec{ IssuerConfig: cmapi.IssuerConfig{ @@ -106,7 +109,7 @@ func (a *acmeIssuerProvisioner) create(f *framework.Framework) cmmeta.ObjectRefe SkipTLSVerify: true, PrivateKey: cmmeta.SecretKeySelector{ LocalObjectReference: cmmeta.LocalObjectReference{ - Name: "acme-private-key", + Name: "acme-private-key-http01", }, }, Solvers: []cmacme.ACMEChallengeSolver{ @@ -123,21 +126,67 @@ func (a *acmeIssuerProvisioner) create(f *framework.Framework) cmmeta.ObjectRefe }, }, } + issuer, err := f.CertManagerClientSet.CertmanagerV1alpha2().Issuers(f.Namespace.Name).Create(issuer) - Expect(err).NotTo(HaveOccurred(), "failed to create acme issuer") + Expect(err).NotTo(HaveOccurred(), "failed to create acme HTTP01 issuer") return cmmeta.ObjectReference{ - Group: emptyOrString(a.setGroupName, cmapi.SchemeGroupVersion.Group), + Group: cmapi.SchemeGroupVersion.Group, Kind: cmapi.IssuerKind, Name: issuer.Name, } } -// emptyOrString will return the given string 's' if 'set' is true, -// otherwise it will return the empty string. -func emptyOrString(set bool, s string) string { - if set { - return s +func (a *acmeIssuerProvisioner) createDNS01(f *framework.Framework) cmmeta.ObjectReference { + a.deployTiller(f, "dns01") + + a.cloudflare = &dnsproviders.Cloudflare{ + Namespace: f.Namespace.Name, + } + Expect(a.cloudflare.Setup(f.Config)).NotTo(HaveOccurred(), "failed to setup cloudflare") + Expect(a.cloudflare.Provision()).NotTo(HaveOccurred(), "failed to provision cloudflare") + + By("Creating an ACME DNS01 issuer") + issuer := &cmapi.Issuer{ + ObjectMeta: metav1.ObjectMeta{ + Name: "acme-issuer-dns01", + }, + Spec: cmapi.IssuerSpec{ + IssuerConfig: cmapi.IssuerConfig{ + ACME: &cmacme.ACMEIssuer{ + // Hardcode this to the acme staging endpoint now due to issues with pebble dns resolution + Server: "https://acme-staging-v02.api.letsencrypt.org/directory", + SkipTLSVerify: true, + PrivateKey: cmmeta.SecretKeySelector{ + LocalObjectReference: cmmeta.LocalObjectReference{ + Name: "acme-private-key", + }, + }, + Solvers: []cmacme.ACMEChallengeSolver{ + { + DNS01: &a.cloudflare.Details().ProviderConfig, + }, + }, + }, + }, + }, + } + issuer, err := f.CertManagerClientSet.CertmanagerV1alpha2().Issuers(f.Namespace.Name).Create(issuer) + Expect(err).NotTo(HaveOccurred(), "failed to create acme DNS01 issuer") + + return cmmeta.ObjectReference{ + Group: cmapi.SchemeGroupVersion.Group, + Kind: cmapi.IssuerKind, + Name: issuer.Name, } - return "" +} + +func (a *acmeIssuerProvisioner) deployTiller(f *framework.Framework, solverType string) { + a.tiller = &tiller.Tiller{ + Name: "tiller-deploy-" + solverType, + ClusterPermissions: false, + Namespace: f.Namespace.Name, + } + Expect(a.tiller.Setup(f.Config)).NotTo(HaveOccurred(), "failed to setup tiller") + Expect(a.tiller.Provision()).NotTo(HaveOccurred(), "failed to provision tiller") } diff --git a/test/e2e/suite/conformance/certificates/ca/ca.go b/test/e2e/suite/conformance/certificates/ca/ca.go index 049a36a11..8287579d0 100644 --- a/test/e2e/suite/conformance/certificates/ca/ca.go +++ b/test/e2e/suite/conformance/certificates/ca/ca.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package selfsigned +package ca import ( . "github.com/onsi/ginkgo" @@ -36,7 +36,7 @@ var _ = framework.ConformanceDescribe("Certificates", func() { }) func createCAIssuer(f *framework.Framework) cmmeta.ObjectReference { - By("Creating a SelfSigned issuer") + By("Creating a CA issuer") rootCertSecret, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(newSigningKeypairSecret("root-cert")) Expect(err).NotTo(HaveOccurred(), "failed to create root signing keypair secret") diff --git a/test/e2e/suite/conformance/certificates/featureset.go b/test/e2e/suite/conformance/certificates/featureset.go index e6449971e..0731f24a3 100644 --- a/test/e2e/suite/conformance/certificates/featureset.go +++ b/test/e2e/suite/conformance/certificates/featureset.go @@ -83,8 +83,19 @@ const ( // will never pass tests that validate the duration is as expected. DurationFeature Feature = "Duration" - // Wildcards 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. - Wildcards Feature = "Wildcards" + // 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. This is useful for some + // issuers that have trouble being configured to support this feature. + ECDSAFeature Feature = "ECDSA" + + // ReusePrivateKey denotes whether the target issuer is able to sign multiple + // certificates for the same private key. This is useful for some issuers + // that have trouble being configured to support this feature. + ReusePrivateKeyFeature Feature = "ReusePrivateKey" ) diff --git a/test/e2e/suite/conformance/certificates/suite.go b/test/e2e/suite/conformance/certificates/suite.go index cf021cf32..e1d0b6799 100644 --- a/test/e2e/suite/conformance/certificates/suite.go +++ b/test/e2e/suite/conformance/certificates/suite.go @@ -23,11 +23,14 @@ import ( . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + apiutil "github.com/jetstack/cert-manager/pkg/api/util" cmapi "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha2" cmmeta "github.com/jetstack/cert-manager/pkg/apis/meta/v1" "github.com/jetstack/cert-manager/pkg/util" + "github.com/jetstack/cert-manager/pkg/util/pki" "github.com/jetstack/cert-manager/test/e2e/framework" ) @@ -143,6 +146,8 @@ func (s *Suite) Define() { }) It("should issue an ECDSA, defaulted certificate for a single commonName and distinct dnsName", func() { + s.checkFeatures(ECDSAFeature) + testCertificate := &cmapi.Certificate{ ObjectMeta: metav1.ObjectMeta{ Name: "testcert", @@ -188,6 +193,125 @@ func (s *Suite) Define() { err = f.Helper().WaitCertificateIssuedValid(f.Namespace.Name, "testcert", time.Minute*5) Expect(err).NotTo(HaveOccurred()) }) + + It("should issue a certificate that defines a commonName and sets a duration", func() { + s.checkFeatures(DurationFeature) + + testCertificate := &cmapi.Certificate{ + ObjectMeta: metav1.ObjectMeta{ + Name: "testcert", + Namespace: f.Namespace.Name, + }, + Spec: cmapi.CertificateSpec{ + SecretName: "testcert-tls", + CommonName: s.newDomain(), + IssuerRef: issuerRef, + Duration: &metav1.Duration{ + Duration: time.Hour * 896, + }, + }, + } + By("Creating a Certificate") + err := f.CRClient.Create(ctx, testCertificate) + Expect(err).NotTo(HaveOccurred()) + + By("Waiting for the Certificate to be issued...") + err = f.Helper().WaitCertificateIssuedValid(f.Namespace.Name, "testcert", time.Minute*5) + Expect(err).NotTo(HaveOccurred()) + + // We set a weird time here as the duration with should never be used as + // a default by an issuer. This lets us test issuers are using our given + // duration. + // We set a 30 second buffer time here since Vault issues certificates + // with an extra 30 seconds on its duration. + f.CertificateDurationValid(testCertificate, time.Hour*896, 30*time.Second) + }) + + It("should issue a certificate which has a wildcard DNS name defined", func() { + s.checkFeatures(WildcardsFeature) + + testCertificate := &cmapi.Certificate{ + ObjectMeta: metav1.ObjectMeta{ + Name: "testcert", + Namespace: f.Namespace.Name, + }, + Spec: cmapi.CertificateSpec{ + SecretName: "testcert-tls", + CommonName: s.newDomain(), + IssuerRef: issuerRef, + DNSNames: []string{"foo." + s.newDomain()}, + }, + } + By("Creating a Certificate") + err := f.CRClient.Create(ctx, testCertificate) + Expect(err).NotTo(HaveOccurred()) + + By("Waiting for the Certificate to be issued...") + err = f.Helper().WaitCertificateIssuedValid(f.Namespace.Name, "testcert", time.Minute*5) + Expect(err).NotTo(HaveOccurred()) + }) + + It("should issue another certificate with the same private key if the existing certificate and CertificateRequest are deleted", func() { + s.checkFeatures(ReusePrivateKeyFeature) + + testCertificate := &cmapi.Certificate{ + ObjectMeta: metav1.ObjectMeta{ + Name: "testcert", + Namespace: f.Namespace.Name, + }, + Spec: cmapi.CertificateSpec{ + SecretName: "testcert-tls", + CommonName: s.newDomain(), + DNSNames: []string{s.newDomain()}, + IssuerRef: issuerRef, + }, + } + By("Creating a Certificate") + err := f.CRClient.Create(ctx, testCertificate) + Expect(err).NotTo(HaveOccurred()) + + By("Waiting for the Certificate to be issued...") + err = f.Helper().WaitCertificateIssuedValid(f.Namespace.Name, "testcert", time.Minute*5) + Expect(err).NotTo(HaveOccurred()) + + By("Deleting existing certificate data in Secret and owned CertificateRequest") + expectedReqName, err := apiutil.ComputeCertificateRequestName(testCertificate) + Expect(err).NotTo(HaveOccurred(), "failed to generate expected name for created Certificate") + + Expect(f.CertManagerClientSet.CertmanagerV1alpha2(). + CertificateRequests(f.Namespace.Name).Delete(expectedReqName, &metav1.DeleteOptions{})). + NotTo(HaveOccurred(), "failed to delete owned CertificateRequest") + + sec, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name). + Get(testCertificate.Spec.SecretName, metav1.GetOptions{}) + Expect(err).NotTo(HaveOccurred(), "failed to get secret containing signed certificate key pair data") + + sec = sec.DeepCopy() + crtPEM1 := sec.Data[corev1.TLSCertKey] + crt1, err := pki.DecodeX509CertificateBytes(crtPEM1) + Expect(err).NotTo(HaveOccurred(), "failed to get decode first signed certificate data") + + sec.Data[corev1.TLSCertKey] = []byte{} + + _, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Update(sec) + Expect(err).NotTo(HaveOccurred(), "failed to update secret by deleting the signed certificate data") + + By("Waiting for the Certificate to re-issue a certificate") + sec, err = f.Helper().WaitForSecretCertificateData(f.Namespace.Name, sec.Name, time.Minute*5) + 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") + } + }) }) } diff --git a/test/e2e/suite/conformance/certificates/vault/BUILD.bazel b/test/e2e/suite/conformance/certificates/vault/BUILD.bazel new file mode 100644 index 000000000..67ecaf17f --- /dev/null +++ b/test/e2e/suite/conformance/certificates/vault/BUILD.bazel @@ -0,0 +1,33 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = ["vault_approle.go"], + importpath = "github.com/jetstack/cert-manager/test/e2e/suite/conformance/certificates/vault", + visibility = ["//visibility:public"], + deps = [ + "//pkg/apis/certmanager/v1alpha2:go_default_library", + "//pkg/apis/meta/v1:go_default_library", + "//test/e2e/framework:go_default_library", + "//test/e2e/framework/addon/tiller:go_default_library", + "//test/e2e/framework/addon/vault:go_default_library", + "//test/e2e/suite/conformance/certificates:go_default_library", + "@com_github_onsi_ginkgo//:go_default_library", + "@com_github_onsi_gomega//:go_default_library", + "@io_k8s_apimachinery//pkg/apis/meta/v1:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/test/e2e/suite/conformance/certificates/vault/vault_approle.go b/test/e2e/suite/conformance/certificates/vault/vault_approle.go new file mode 100644 index 000000000..bdc3ee1b6 --- /dev/null +++ b/test/e2e/suite/conformance/certificates/vault/vault_approle.go @@ -0,0 +1,129 @@ +/* +Copyright 2019 The Jetstack cert-manager contributors. + +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 vault + +import ( + "path" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + cmapi "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha2" + cmmeta "github.com/jetstack/cert-manager/pkg/apis/meta/v1" + "github.com/jetstack/cert-manager/test/e2e/framework" + "github.com/jetstack/cert-manager/test/e2e/framework/addon/tiller" + vault "github.com/jetstack/cert-manager/test/e2e/framework/addon/vault" + "github.com/jetstack/cert-manager/test/e2e/suite/conformance/certificates" +) + +var _ = framework.ConformanceDescribe("Certificates", func() { + provisioner := new(vaultAppRoleProvisioner) + + (&certificates.Suite{ + Name: "VaultAppRole", + CreateIssuerFunc: provisioner.create, + DeleteIssuerFunc: provisioner.delete, + }).Define() +}) + +type vaultAppRoleProvisioner struct { + tiller *tiller.Tiller + vault *vault.Vault +} + +func (v *vaultAppRoleProvisioner) delete(f *framework.Framework, ref cmmeta.ObjectReference) { + Expect(v.vault.Deprovision()).NotTo(HaveOccurred(), "failed to deprovision vault") + Expect(v.tiller.Deprovision()).NotTo(HaveOccurred(), "failed to deprovision tiller") +} + +func (v *vaultAppRoleProvisioner) create(f *framework.Framework) cmmeta.ObjectReference { + By("Creating a VaultAppRole issuer") + + v.tiller = &tiller.Tiller{ + Name: "tiller-deploy", + Namespace: f.Namespace.Name, + ClusterPermissions: false, + } + Expect(v.tiller.Setup(f.Config)).NotTo(HaveOccurred(), "failed to setup tiller") + Expect(v.tiller.Provision()).NotTo(HaveOccurred(), "failed to provision tiller") + + v.vault = &vault.Vault{ + Tiller: v.tiller, + Namespace: f.Namespace.Name, + Name: "cm-e2e-create-vault-issuer", + } + Expect(v.vault.Setup(f.Config)).NotTo(HaveOccurred(), "failed to setup vault") + Expect(v.vault.Provision()).NotTo(HaveOccurred(), "failed to provision vault") + + intermediateMount := "intermediate-ca" + role := "kubernetes-vault" + vaultSecretAppRoleName := "vault-role" + vaultPath := path.Join(intermediateMount, "sign", role) + authPath := "approle" + + By("Configuring the VaultAppRole server") + vaultInit := &vault.VaultInitializer{ + Details: *v.vault.Details(), + RootMount: "root-ca", + IntermediateMount: intermediateMount, + Role: role, + AppRoleAuthPath: authPath, + } + Expect(vaultInit.Init()).NotTo(HaveOccurred(), "failed to init vault") + Expect(vaultInit.Setup()).NotTo(HaveOccurred(), "fauled to setup vault") + + roleID, secretID, err := vaultInit.CreateAppRole() + Expect(err).NotTo(HaveOccurred(), "vault to create app role from vault") + + _, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(vault.NewVaultAppRoleSecret(vaultSecretAppRoleName, secretID)) + Expect(err).NotTo(HaveOccurred(), "vault to store app role secret from vault") + + issuer, err := f.CertManagerClientSet.CertmanagerV1alpha2().Issuers(f.Namespace.Name).Create(&cmapi.Issuer{ + ObjectMeta: metav1.ObjectMeta{ + Name: "vault-issuer", + }, + Spec: cmapi.IssuerSpec{ + IssuerConfig: cmapi.IssuerConfig{ + Vault: &cmapi.VaultIssuer{ + Server: v.vault.Details().Host, + Path: vaultPath, + CABundle: v.vault.Details().VaultCA, + Auth: cmapi.VaultAuth{ + AppRole: &cmapi.VaultAppRole{ + Path: authPath, + RoleId: roleID, + SecretRef: cmmeta.SecretKeySelector{ + Key: "secretkey", + LocalObjectReference: cmmeta.LocalObjectReference{ + Name: vaultSecretAppRoleName, + }, + }, + }, + }, + }, + }, + }, + }) + Expect(err).NotTo(HaveOccurred(), "failed to create vault issuer") + + return cmmeta.ObjectReference{ + Group: cmapi.SchemeGroupVersion.Group, + Kind: cmapi.IssuerKind, + Name: issuer.Name, + } +} diff --git a/test/e2e/suite/conformance/certificates/venafi/BUILD.bazel b/test/e2e/suite/conformance/certificates/venafi/BUILD.bazel new file mode 100644 index 000000000..8a9c737c4 --- /dev/null +++ b/test/e2e/suite/conformance/certificates/venafi/BUILD.bazel @@ -0,0 +1,32 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = ["venafi.go"], + importpath = "github.com/jetstack/cert-manager/test/e2e/suite/conformance/certificates/venafi", + visibility = ["//visibility:public"], + deps = [ + "//pkg/apis/certmanager/v1alpha2:go_default_library", + "//pkg/apis/meta/v1:go_default_library", + "//test/e2e/framework:go_default_library", + "//test/e2e/framework/util/errors:go_default_library", + "//test/e2e/suite/conformance/certificates:go_default_library", + "//test/e2e/suite/issuers/venafi/addon:go_default_library", + "@com_github_onsi_ginkgo//:go_default_library", + "@com_github_onsi_gomega//:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/test/e2e/suite/conformance/certificates/venafi/venafi.go b/test/e2e/suite/conformance/certificates/venafi/venafi.go new file mode 100644 index 000000000..380b9f340 --- /dev/null +++ b/test/e2e/suite/conformance/certificates/venafi/venafi.go @@ -0,0 +1,84 @@ +/* +Copyright 2019 The Jetstack cert-manager contributors. + +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 venafi + +import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + cmapi "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha2" + cmmeta "github.com/jetstack/cert-manager/pkg/apis/meta/v1" + "github.com/jetstack/cert-manager/test/e2e/framework" + "github.com/jetstack/cert-manager/test/e2e/framework/util/errors" + "github.com/jetstack/cert-manager/test/e2e/suite/conformance/certificates" + vaddon "github.com/jetstack/cert-manager/test/e2e/suite/issuers/venafi/addon" +) + +var _ = framework.ConformanceDescribe("Certificates", func() { + // unsupportedFeatures is a list of features that are not supported by the + // Venafi issuer. + var unsupportedFeatures = certificates.NewFeatureSet( + certificates.DurationFeature, + // Due to the current configuration of the test environment, it does not + // support signing certificates that pair with an elliptic curve private + // key or using the same private key multiple times. + certificates.ECDSAFeature, + certificates.ReusePrivateKeyFeature, + ) + + provisioner := new(venafiProvisioner) + (&certificates.Suite{ + Name: "Venafi", + CreateIssuerFunc: provisioner.create, + DeleteIssuerFunc: provisioner.delete, + UnsupportedFeatures: unsupportedFeatures, + }).Define() +}) + +type venafiProvisioner struct { + tpp *vaddon.VenafiTPP +} + +func (v *venafiProvisioner) delete(f *framework.Framework, ref cmmeta.ObjectReference) { + Expect(v.tpp.Deprovision()).NotTo(HaveOccurred(), "failed to deprovision tpp venafi") +} + +func (v *venafiProvisioner) create(f *framework.Framework) cmmeta.ObjectReference { + By("Creating a Venafi issuer") + + v.tpp = &vaddon.VenafiTPP{ + Namespace: f.Namespace.Name, + } + + err := v.tpp.Setup(f.Config) + if errors.IsSkip(err) { + framework.Skipf("Skipping test as addon could not be setup: %v", err) + } + Expect(err).NotTo(HaveOccurred(), "failed to setup tpp venafi") + + Expect(v.tpp.Provision()).NotTo(HaveOccurred(), "failed to provision tpp venafi") + + issuer := v.tpp.Details().BuildIssuer() + issuer, err = f.CertManagerClientSet.CertmanagerV1alpha2().Issuers(f.Namespace.Name).Create(issuer) + Expect(err).NotTo(HaveOccurred(), "failed to create issuer for venafi") + + return cmmeta.ObjectReference{ + Group: cmapi.SchemeGroupVersion.Group, + Kind: cmapi.IssuerKind, + Name: issuer.Name, + } +} diff --git a/test/e2e/suite/conformance/doc.go b/test/e2e/suite/conformance/doc.go index ec2af0414..a050d7419 100644 --- a/test/e2e/suite/conformance/doc.go +++ b/test/e2e/suite/conformance/doc.go @@ -20,5 +20,7 @@ import ( _ "github.com/jetstack/cert-manager/test/e2e/suite/conformance/certificates/acme" _ "github.com/jetstack/cert-manager/test/e2e/suite/conformance/certificates/ca" _ "github.com/jetstack/cert-manager/test/e2e/suite/conformance/certificates/selfsigned" + _ "github.com/jetstack/cert-manager/test/e2e/suite/conformance/certificates/vault" + _ "github.com/jetstack/cert-manager/test/e2e/suite/conformance/certificates/venafi" _ "github.com/jetstack/cert-manager/test/e2e/suite/conformance/rbac" ) diff --git a/test/e2e/suite/issuers/ca/certificate.go b/test/e2e/suite/issuers/ca/certificate.go index 1f78108b0..ef57f1af7 100644 --- a/test/e2e/suite/issuers/ca/certificate.go +++ b/test/e2e/suite/issuers/ca/certificate.go @@ -123,7 +123,7 @@ var _ = framework.CertManagerDescribe("CA Certificate", func() { By("Verifying the Certificate is valid") err = h.WaitCertificateIssuedValid(f.Namespace.Name, certificateName, time.Second*30) Expect(err).NotTo(HaveOccurred()) - f.CertificateDurationValid(cert, v.expectedDuration) + f.CertificateDurationValid(cert, v.expectedDuration, 0) }) } }) diff --git a/test/e2e/suite/issuers/selfsigned/certificate.go b/test/e2e/suite/issuers/selfsigned/certificate.go index 9ac884a3c..9c02f504a 100644 --- a/test/e2e/suite/issuers/selfsigned/certificate.go +++ b/test/e2e/suite/issuers/selfsigned/certificate.go @@ -102,7 +102,7 @@ var _ = framework.CertManagerDescribe("Self Signed Certificate", func() { Expect(err).NotTo(HaveOccurred()) err = h.WaitCertificateIssuedValid(f.Namespace.Name, certificateName, time.Second*30) Expect(err).NotTo(HaveOccurred()) - f.CertificateDurationValid(cert, v.expectedDuration) + f.CertificateDurationValid(cert, v.expectedDuration, 0) }) } diff --git a/test/e2e/suite/issuers/vault/certificate/approle.go b/test/e2e/suite/issuers/vault/certificate/approle.go index 8e1bb3078..252ee4abd 100644 --- a/test/e2e/suite/issuers/vault/certificate/approle.go +++ b/test/e2e/suite/issuers/vault/certificate/approle.go @@ -178,7 +178,7 @@ var _ = framework.CertManagerDescribe("Vault Certificate (AppRole)", func() { Expect(err).NotTo(HaveOccurred()) // Vault substract 30 seconds to the NotBefore date. - f.CertificateDurationValid(cert, v.expectedDuration+(30*time.Second)) + f.CertificateDurationValid(cert, v.expectedDuration, time.Second*30) }) } }) diff --git a/test/e2e/suite/issuers/venafi/addon/tpp.go b/test/e2e/suite/issuers/venafi/addon/tpp.go index 74ac797aa..27ed74ff3 100644 --- a/test/e2e/suite/issuers/venafi/addon/tpp.go +++ b/test/e2e/suite/issuers/venafi/addon/tpp.go @@ -107,8 +107,7 @@ func (v *VenafiTPP) Details() *TPPDetails { } func (v *VenafiTPP) Deprovision() error { - v.Base.Details().KubeClient.CoreV1().Secrets(v.createdSecret.Namespace).Delete(v.createdSecret.Name, nil) - return nil + return v.Base.Details().KubeClient.CoreV1().Secrets(v.createdSecret.Namespace).Delete(v.createdSecret.Name, nil) } func (v *VenafiTPP) SupportsGlobal() bool {