diff --git a/cmd/controller/app/options/BUILD.bazel b/cmd/controller/app/options/BUILD.bazel index b2ef44e15..47d2205e5 100644 --- a/cmd/controller/app/options/BUILD.bazel +++ b/cmd/controller/app/options/BUILD.bazel @@ -27,6 +27,7 @@ go_library( "//pkg/controller/certificatesigningrequests/ca:go_default_library", "//pkg/controller/certificatesigningrequests/selfsigned:go_default_library", "//pkg/controller/certificatesigningrequests/vault:go_default_library", + "//pkg/controller/certificatesigningrequests/venafi:go_default_library", "//pkg/controller/clusterissuers:go_default_library", "//pkg/controller/issuers:go_default_library", "//pkg/feature:go_default_library", diff --git a/cmd/controller/app/options/options.go b/cmd/controller/app/options/options.go index 75297f4f0..e923098b3 100644 --- a/cmd/controller/app/options/options.go +++ b/cmd/controller/app/options/options.go @@ -46,6 +46,7 @@ import ( csrcacontroller "github.com/jetstack/cert-manager/pkg/controller/certificatesigningrequests/ca" csrselfsignedcontroller "github.com/jetstack/cert-manager/pkg/controller/certificatesigningrequests/selfsigned" csrvaultcontroller "github.com/jetstack/cert-manager/pkg/controller/certificatesigningrequests/vault" + csrvenaficontroller "github.com/jetstack/cert-manager/pkg/controller/certificatesigningrequests/venafi" clusterissuerscontroller "github.com/jetstack/cert-manager/pkg/controller/clusterissuers" issuerscontroller "github.com/jetstack/cert-manager/pkg/controller/issuers" "github.com/jetstack/cert-manager/pkg/feature" @@ -195,6 +196,7 @@ var ( experimentalCertificateSigningRequestControllers = []string{ csrcacontroller.CSRControllerName, csrselfsignedcontroller.CSRControllerName, + csrvenaficontroller.CSRControllerName, csrvaultcontroller.CSRControllerName, } ) diff --git a/pkg/apis/experimental/v1alpha1/types.go b/pkg/apis/experimental/v1alpha1/types.go index 59b8aff0e..bd2b0e47d 100644 --- a/pkg/apis/experimental/v1alpha1/types.go +++ b/pkg/apis/experimental/v1alpha1/types.go @@ -26,7 +26,10 @@ const ( // CertificateSigningRequestIsCAAnnotationKey is the annotation key used to // request whether the certificate should be marked as CA. CertificateSigningRequestIsCAAnnotationKey = "experimental.cert-manager.io/request-is-ca" +) +// SelfSigned Issuer specific Annotations +const ( // CertificateSigningRequestPrivateKeyAnnotationKey is the annotation key // used to reference a Secret resource containing the private key used to // sign the request. @@ -34,3 +37,18 @@ const ( // issuer type to self-sign certificates. CertificateSigningRequestPrivateKeyAnnotationKey = "experimental.cert-manager.io/private-key-secret-name" ) + +// Venafi Issuer specific Annotations +const ( + // CertificateSigningRequestVenafiCustomFieldsAnnotationKey is the annotation + // that passes on JSON encoded custom fields to the Venafi issuer. + // This will only work with Venafi TPP v19.3 and higher. + // The value is an array with objects containing the name and value keys for + // example: `[{"name": "custom-field", "value": "custom-value"}]` + CertificateSigningRequestVenafiCustomFieldsAnnotationKey = "venafi.experimental.cert-manager.io/custom-fields" + + // CertificateSigningRequestVenafiPickupIDAnnotationKey is the annotation key + // used to record the Venafi Pickup ID of a certificate signing request that + // has been submitted to the Venafi API for collection later. + CertificateSigningRequestVenafiPickupIDAnnotationKey = "venafi.experimental.cert-manager.io/pickup-id" +) diff --git a/pkg/controller/certificatesigningrequests/BUILD.bazel b/pkg/controller/certificatesigningrequests/BUILD.bazel index 203d6519f..2b1eec407 100644 --- a/pkg/controller/certificatesigningrequests/BUILD.bazel +++ b/pkg/controller/certificatesigningrequests/BUILD.bazel @@ -74,6 +74,7 @@ filegroup( "//pkg/controller/certificatesigningrequests/selfsigned:all-srcs", "//pkg/controller/certificatesigningrequests/util:all-srcs", "//pkg/controller/certificatesigningrequests/vault:all-srcs", + "//pkg/controller/certificatesigningrequests/venafi:all-srcs", ], tags = ["automanaged"], visibility = ["//visibility:public"], diff --git a/pkg/controller/certificatesigningrequests/sync.go b/pkg/controller/certificatesigningrequests/sync.go index 292dfe0e4..b5ee1d3f4 100644 --- a/pkg/controller/certificatesigningrequests/sync.go +++ b/pkg/controller/certificatesigningrequests/sync.go @@ -38,6 +38,11 @@ func (c *Controller) Sync(ctx context.Context, csr *certificatesv1.CertificateSi log := logf.WithResource(logf.FromContext(ctx), csr).WithValues("signerName", csr.Spec.SignerName) dbg := log.V(logf.DebugLevel) + // Deep copy CertificateSigningRequest to prevent writing to the shared local + // cache making it invalid. Done early in the sync to avoid accidental + // invalidation by future contributions. + csr = csr.DeepCopy() + ref, ok := util.SignerIssuerRefFromSignerName(csr.Spec.SignerName) if !ok { dbg.Info("certificate signing request has malformed signer name,", "signerName", csr.Spec.SignerName) diff --git a/pkg/controller/certificatesigningrequests/venafi/BUILD.bazel b/pkg/controller/certificatesigningrequests/venafi/BUILD.bazel new file mode 100644 index 000000000..b034ebd94 --- /dev/null +++ b/pkg/controller/certificatesigningrequests/venafi/BUILD.bazel @@ -0,0 +1,73 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = ["venafi.go"], + importpath = "github.com/jetstack/cert-manager/pkg/controller/certificatesigningrequests/venafi", + visibility = ["//visibility:public"], + deps = [ + "//pkg/api/util:go_default_library", + "//pkg/apis/certmanager/v1:go_default_library", + "//pkg/apis/experimental/v1alpha1:go_default_library", + "//pkg/controller:go_default_library", + "//pkg/controller/certificatesigningrequests:go_default_library", + "//pkg/controller/certificatesigningrequests/util:go_default_library", + "//pkg/issuer/venafi/client:go_default_library", + "//pkg/issuer/venafi/client/api:go_default_library", + "//pkg/logs:go_default_library", + "//pkg/util/pki:go_default_library", + "@com_github_venafi_vcert_v4//pkg/endpoint:go_default_library", + "@io_k8s_api//certificates/v1:go_default_library", + "@io_k8s_api//core/v1:go_default_library", + "@io_k8s_apimachinery//pkg/api/errors:go_default_library", + "@io_k8s_apimachinery//pkg/apis/meta/v1:go_default_library", + "@io_k8s_client_go//kubernetes/typed/certificates/v1:go_default_library", + "@io_k8s_client_go//listers/core/v1:go_default_library", + "@io_k8s_client_go//tools/record:go_default_library", + ], +) + +go_test( + name = "go_default_test", + srcs = ["venafi_test.go"], + embed = [":go_default_library"], + deps = [ + "//pkg/api/util:go_default_library", + "//pkg/apis/certmanager:go_default_library", + "//pkg/apis/certmanager/v1:go_default_library", + "//pkg/apis/meta/v1:go_default_library", + "//pkg/controller/certificatesigningrequests:go_default_library", + "//pkg/controller/certificatesigningrequests/util:go_default_library", + "//pkg/controller/test:go_default_library", + "//pkg/issuer/venafi/client:go_default_library", + "//pkg/issuer/venafi/client/api:go_default_library", + "//pkg/issuer/venafi/client/fake:go_default_library", + "//pkg/util/pki:go_default_library", + "//test/unit/gen:go_default_library", + "@com_github_venafi_vcert_v4//pkg/endpoint:go_default_library", + "@io_k8s_api//authorization/v1:go_default_library", + "@io_k8s_api//certificates/v1:go_default_library", + "@io_k8s_api//core/v1:go_default_library", + "@io_k8s_apimachinery//pkg/api/errors:go_default_library", + "@io_k8s_apimachinery//pkg/apis/meta/v1:go_default_library", + "@io_k8s_apimachinery//pkg/runtime:go_default_library", + "@io_k8s_apimachinery//pkg/runtime/schema:go_default_library", + "@io_k8s_client_go//listers/core/v1: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(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/pkg/controller/certificatesigningrequests/venafi/venafi.go b/pkg/controller/certificatesigningrequests/venafi/venafi.go new file mode 100644 index 000000000..60abe179f --- /dev/null +++ b/pkg/controller/certificatesigningrequests/venafi/venafi.go @@ -0,0 +1,213 @@ +/* +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 venafi + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/Venafi/vcert/v4/pkg/endpoint" + certificatesv1 "k8s.io/api/certificates/v1" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + certificatesclient "k8s.io/client-go/kubernetes/typed/certificates/v1" + corelisters "k8s.io/client-go/listers/core/v1" + "k8s.io/client-go/tools/record" + + apiutil "github.com/jetstack/cert-manager/pkg/api/util" + cmapi "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1" + experimentalapi "github.com/jetstack/cert-manager/pkg/apis/experimental/v1alpha1" + controllerpkg "github.com/jetstack/cert-manager/pkg/controller" + "github.com/jetstack/cert-manager/pkg/controller/certificatesigningrequests" + "github.com/jetstack/cert-manager/pkg/controller/certificatesigningrequests/util" + venaficlient "github.com/jetstack/cert-manager/pkg/issuer/venafi/client" + venafiapi "github.com/jetstack/cert-manager/pkg/issuer/venafi/client/api" + logf "github.com/jetstack/cert-manager/pkg/logs" + "github.com/jetstack/cert-manager/pkg/util/pki" + utilpki "github.com/jetstack/cert-manager/pkg/util/pki" +) + +const ( + CSRControllerName = "certificatesigningrequests-issuer-venafi" +) + +// Venafi is a Kubernetes CertificateSigningRequest controller, responsible for +// signing CertificateSigningRequests that reference a cert-manager Venafi +// Issuer or ClusterIssuer +type Venafi struct { + issuerOptions controllerpkg.IssuerOptions + secretsLister corelisters.SecretLister + certClient certificatesclient.CertificateSigningRequestInterface + recorder record.EventRecorder + + clientBuilder venaficlient.VenafiClientBuilder +} + +func init() { + controllerpkg.Register(CSRControllerName, func(ctx *controllerpkg.Context) (controllerpkg.Interface, error) { + return controllerpkg.NewBuilder(ctx, CSRControllerName). + For(certificatesigningrequests.New(apiutil.IssuerVenafi, NewVenafi(ctx))). + Complete() + }) +} + +func NewVenafi(ctx *controllerpkg.Context) *Venafi { + return &Venafi{ + issuerOptions: ctx.IssuerOptions, + secretsLister: ctx.KubeSharedInformerFactory.Core().V1().Secrets().Lister(), + certClient: ctx.Client.CertificatesV1().CertificateSigningRequests(), + recorder: ctx.Recorder, + clientBuilder: venaficlient.New, + } +} + +// Sign attempts to sign the given CertificateSigningRequest based on the +// provided Venafi Issuer or ClusterIssuer. This function will update the resource +// if signing was successful. Returns an error which, if not nil, should +// trigger a retry. +// Since this signer takes some time to sign the request, this controller will +// set a "pick ID" annotation value that is used to fetch the latest state of +// the request in subsequent re-syncs. The re-syncs are triggered by using the +// workqueue's back-off mechanism. +func (v *Venafi) Sign(ctx context.Context, csr *certificatesv1.CertificateSigningRequest, issuerObj cmapi.GenericIssuer) error { + log := logf.FromContext(ctx, "sign") + log = logf.WithRelatedResource(log, issuerObj) + + resourceNamespace := v.issuerOptions.ResourceNamespace(issuerObj) + + client, err := v.clientBuilder(resourceNamespace, v.secretsLister, issuerObj) + if apierrors.IsNotFound(err) { + message := "Required secret resource not found" + v.recorder.Event(csr, corev1.EventTypeWarning, "SecretNotFound", message) + log.Error(err, message) + return nil + } + + if err != nil { + message := fmt.Sprintf("Failed to initialise venafi client for signing: %s", err) + v.recorder.Event(csr, corev1.EventTypeWarning, "ErrorVenafiInit", message) + log.Error(err, message) + return err + } + + var customFields []venafiapi.CustomField + if annotation, exists := csr.GetAnnotations()[experimentalapi.CertificateSigningRequestVenafiCustomFieldsAnnotationKey]; exists && annotation != "" { + err := json.Unmarshal([]byte(annotation), &customFields) + if err != nil { + message := fmt.Sprintf("Failed to parse %q annotation: %s", experimentalapi.CertificateSigningRequestVenafiCustomFieldsAnnotationKey, err) + v.recorder.Event(csr, corev1.EventTypeWarning, "ErrorCustomFields", message) + util.CertificateSigningRequestSetFailed(csr, "ErrorCustomFields", message) + _, userr := v.certClient.UpdateStatus(ctx, csr, metav1.UpdateOptions{}) + return userr + } + } + + duration, err := pki.DurationFromCertificateSigningRequest(csr) + if err != nil { + message := fmt.Sprintf("Failed to parse requested duration: %s", err) + log.Error(err, message) + v.recorder.Event(csr, corev1.EventTypeWarning, "ErrorParseDuration", message) + util.CertificateSigningRequestSetFailed(csr, "ErrorParseDuration", message) + _, userr := v.certClient.UpdateStatus(ctx, csr, metav1.UpdateOptions{}) + return userr + } + + // The signing process with Venafi is slow. The "pickupID" allows us to track + // the progress of the certificate signing. It is set as an annotation the + // first time the Certificate is reconciled. + pickupID := csr.GetAnnotations()[experimentalapi.CertificateSigningRequestVenafiPickupIDAnnotationKey] + + // check if the pickup ID annotation is there, if not set it up. + if len(pickupID) == 0 { + pickupID, err := client.RequestCertificate(csr.Spec.Request, duration, customFields) + // Check some known error types + if err != nil { + switch err.(type) { + + case venaficlient.ErrCustomFieldsType: + log.Error(err, "") + v.recorder.Event(csr, corev1.EventTypeWarning, "ErrorCustomFields", err.Error()) + util.CertificateSigningRequestSetFailed(csr, "ErrorCustomFields", err.Error()) + _, userr := v.certClient.UpdateStatus(ctx, csr, metav1.UpdateOptions{}) + return userr + + default: + message := fmt.Sprintf("Failed to request venafi certificate: %s", err) + log.Error(err, message) + v.recorder.Event(csr, corev1.EventTypeWarning, "ErrorRequest", message) + util.CertificateSigningRequestSetFailed(csr, "ErrorRequest", message) + _, userr := v.certClient.UpdateStatus(ctx, csr, metav1.UpdateOptions{}) + return userr + } + } + + if csr.Annotations == nil { + csr.Annotations = make(map[string]string) + } + csr.Annotations[experimentalapi.CertificateSigningRequestVenafiPickupIDAnnotationKey] = pickupID + _, uerr := v.certClient.Update(ctx, csr, metav1.UpdateOptions{}) + return uerr + } + + certPem, err := client.RetrieveCertificate(pickupID, csr.Spec.Request, duration, customFields) + if err != nil { + switch err.(type) { + case endpoint.ErrCertificatePending: + message := "Venafi certificate still in a pending state, waiting" + log.V(2).Info(message, "error", err.Error()) + v.recorder.Event(csr, corev1.EventTypeNormal, "IssuancePending", message) + return err + + case endpoint.ErrRetrieveCertificateTimeout: + message := "Venafi retrieve certificate timeout, retrying" + log.Error(err, message) + v.recorder.Event(csr, corev1.EventTypeWarning, "RetrieveCertificateTimeout", message) + return err + + default: + message := fmt.Sprintf("Failed to obtain venafi certificate: %s", err) + log.Error(err, message) + v.recorder.Event(csr, corev1.EventTypeWarning, "ErrorRetrieve", message) + return err + } + } + + bundle, err := utilpki.ParseSingleCertificateChainPEM(certPem) + if err != nil { + message := fmt.Sprintf("Failed to parse returned certificate bundle: %s", err) + log.Error(err, message) + v.recorder.Event(csr, corev1.EventTypeWarning, "ErrorParse", message) + util.CertificateSigningRequestSetFailed(csr, "ErrorParse", message) + _, userr := v.certClient.UpdateStatus(ctx, csr, metav1.UpdateOptions{}) + return userr + } + + csr.Status.Certificate = bundle.ChainPEM + csr, err = v.certClient.UpdateStatus(ctx, csr, metav1.UpdateOptions{}) + if err != nil { + message := "Error updating certificate" + v.recorder.Eventf(csr, corev1.EventTypeWarning, "SigningError", "%s: %s", message, err) + return err + } + + log.V(logf.DebugLevel).Info("certificate issued") + v.recorder.Event(csr, corev1.EventTypeNormal, "CertificateIssued", "Certificate fetched from venafi issuer successfully") + + return nil +} diff --git a/pkg/controller/certificatesigningrequests/venafi/venafi_test.go b/pkg/controller/certificatesigningrequests/venafi/venafi_test.go new file mode 100644 index 000000000..7a04fc5f3 --- /dev/null +++ b/pkg/controller/certificatesigningrequests/venafi/venafi_test.go @@ -0,0 +1,905 @@ +/* +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 venafi + +import ( + "context" + "crypto/x509" + "errors" + "fmt" + "testing" + "time" + + "github.com/Venafi/vcert/v4/pkg/endpoint" + authzv1 "k8s.io/api/authorization/v1" + certificatesv1 "k8s.io/api/certificates/v1" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + corelisters "k8s.io/client-go/listers/core/v1" + coretesting "k8s.io/client-go/testing" + fakeclock "k8s.io/utils/clock/testing" + + apiutil "github.com/jetstack/cert-manager/pkg/api/util" + "github.com/jetstack/cert-manager/pkg/apis/certmanager" + cmapi "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1" + cmmeta "github.com/jetstack/cert-manager/pkg/apis/meta/v1" + "github.com/jetstack/cert-manager/pkg/controller/certificatesigningrequests" + "github.com/jetstack/cert-manager/pkg/controller/certificatesigningrequests/util" + testpkg "github.com/jetstack/cert-manager/pkg/controller/test" + venaficlient "github.com/jetstack/cert-manager/pkg/issuer/venafi/client" + venafiapi "github.com/jetstack/cert-manager/pkg/issuer/venafi/client/api" + fakevenaficlient "github.com/jetstack/cert-manager/pkg/issuer/venafi/client/fake" + "github.com/jetstack/cert-manager/pkg/util/pki" + "github.com/jetstack/cert-manager/test/unit/gen" +) + +var ( + fixedClockStart = time.Now() + fixedClock = fakeclock.NewFakeClock(fixedClockStart) +) + +func TestProcessItem(t *testing.T) { + metaFixedClockStart := metav1.NewTime(fixedClockStart) + util.Clock = fixedClock + + rootCSRPEM, rootPK, err := gen.CSR(x509.RSA, + gen.SetCSRCommonName("root"), + ) + if err != nil { + t.Fatal(err) + } + + rootTmpl, err := pki.GenerateTemplateFromCSRPEM(rootCSRPEM, time.Hour, true) + if err != nil { + t.Fatal(err) + } + _, rootCert, err := pki.SignCertificate(rootTmpl, rootTmpl, rootPK.Public(), rootPK) + if err != nil { + t.Fatal(err) + } + + leafCSRPEM, leafPK, err := gen.CSR(x509.RSA, + gen.SetCSRCommonName("leaf"), + ) + if err != nil { + t.Fatal(err) + } + leafTmpl, err := pki.GenerateTemplateFromCSRPEM(leafCSRPEM, time.Hour, false) + if err != nil { + t.Fatal(err) + } + _, leafCert, err := pki.SignCertificate(leafTmpl, rootCert, leafPK.Public(), rootPK) + if err != nil { + t.Fatal(err) + } + certBundle, err := pki.ParseSingleCertificateChain([]*x509.Certificate{leafCert, rootCert}) + if err != nil { + t.Fatal(err) + } + + baseIssuer := gen.Issuer("test-issuer", + gen.SetIssuerVenafi(cmapi.VenafiIssuer{ + Cloud: &cmapi.VenafiCloud{}, + }), + gen.AddIssuerCondition(cmapi.IssuerCondition{ + Type: cmapi.IssuerConditionReady, + Status: cmmeta.ConditionTrue, + }), + ) + + baseCSR := gen.CertificateSigningRequest("test-cr", + gen.SetCertificateSigningRequestRequest(leafCSRPEM), + gen.SetCertificateSigningRequestSignerName("issuers.cert-manager.io/default-unit-test-ns.test-issuer"), + gen.SetCertificateSigningRequestDuration("1440h"), + gen.SetCertificateSigningRequestUsername("user-1"), + gen.SetCertificateSigningRequestGroups([]string{"group-1", "group-2"}), + gen.SetCertificateSigningRequestUID("uid-1"), + gen.SetCertificateSigningRequestExtra(map[string]certificatesv1.ExtraValue{ + "extra": []string{"1", "2"}, + }), + ) + + tests := map[string]struct { + builder *testpkg.Builder + csr *certificatesv1.CertificateSigningRequest + clientBuilder venaficlient.VenafiClientBuilder + expectedErr bool + }{ + "a CertificateSigningRequest without an approved condition should do nothing": { + csr: gen.CertificateSigningRequestFrom(baseCSR), + builder: &testpkg.Builder{ + CertManagerObjects: []runtime.Object{baseIssuer.DeepCopy()}, + }, + }, + "a CertificateSigningRequest with a denied condition should do nothing": { + csr: gen.CertificateSigningRequestFrom(baseCSR, + gen.SetCertificateSigningRequestStatusCondition(certificatesv1.CertificateSigningRequestCondition{ + Type: certificatesv1.CertificateDenied, + Status: corev1.ConditionTrue, + }), + ), + builder: &testpkg.Builder{ + CertManagerObjects: []runtime.Object{baseIssuer.DeepCopy()}, + ExpectedEvents: []string{}, + ExpectedActions: nil, + }, + }, + "an approved CSR where the venafi client builder returns a not found error should fire event and do nothing": { + csr: gen.CertificateSigningRequestFrom(baseCSR, + gen.SetCertificateSigningRequestStatusCondition(certificatesv1.CertificateSigningRequestCondition{ + Type: certificatesv1.CertificateApproved, + Status: corev1.ConditionTrue, + }), + ), + clientBuilder: func(_ string, _ corelisters.SecretLister, _ cmapi.GenericIssuer) (venaficlient.Interface, error) { + return nil, apierrors.NewNotFound(schema.GroupResource{}, "test-secret") + }, + builder: &testpkg.Builder{ + CertManagerObjects: []runtime.Object{baseIssuer.DeepCopy()}, + ExpectedEvents: []string{ + "Warning SecretNotFound Required secret resource not found", + }, + ExpectedActions: []testpkg.Action{ + testpkg.NewAction(coretesting.NewCreateAction( + authzv1.SchemeGroupVersion.WithResource("subjectaccessreviews"), + "", + &authzv1.SubjectAccessReview{ + Spec: authzv1.SubjectAccessReviewSpec{ + User: "user-1", + Groups: []string{"group-1", "group-2"}, + Extra: map[string]authzv1.ExtraValue{ + "extra": []string{"1", "2"}, + }, + UID: "uid-1", + + ResourceAttributes: &authzv1.ResourceAttributes{ + Group: certmanager.GroupName, + Resource: "signers", + Verb: "reference", + Namespace: baseIssuer.Namespace, + Name: baseIssuer.Name, + Version: "*", + }, + }, + }, + )), + }, + }, + }, + "an approved CSR where the venafi client builder returns a generic error should mark as Failed": { + csr: gen.CertificateSigningRequestFrom(baseCSR, + gen.SetCertificateSigningRequestStatusCondition(certificatesv1.CertificateSigningRequestCondition{ + Type: certificatesv1.CertificateApproved, + Status: corev1.ConditionTrue, + }), + ), + clientBuilder: func(_ string, _ corelisters.SecretLister, _ cmapi.GenericIssuer) (venaficlient.Interface, error) { + return nil, errors.New("generic error") + }, + expectedErr: true, + builder: &testpkg.Builder{ + CertManagerObjects: []runtime.Object{baseIssuer.DeepCopy()}, + ExpectedEvents: []string{ + "Warning ErrorVenafiInit Failed to initialise venafi client for signing: generic error", + }, + ExpectedActions: []testpkg.Action{ + testpkg.NewAction(coretesting.NewCreateAction( + authzv1.SchemeGroupVersion.WithResource("subjectaccessreviews"), + "", + &authzv1.SubjectAccessReview{ + Spec: authzv1.SubjectAccessReviewSpec{ + User: "user-1", + Groups: []string{"group-1", "group-2"}, + Extra: map[string]authzv1.ExtraValue{ + "extra": []string{"1", "2"}, + }, + UID: "uid-1", + + ResourceAttributes: &authzv1.ResourceAttributes{ + Group: certmanager.GroupName, + Resource: "signers", + Verb: "reference", + Namespace: baseIssuer.Namespace, + Name: baseIssuer.Name, + Version: "*", + }, + }, + }, + )), + }, + }, + }, + "an approved CSR where the custom fields annotations contain garbage data should mark as Failed": { + csr: gen.CertificateSigningRequestFrom(baseCSR, + gen.AddCertificateSigningRequestAnnotations(map[string]string{ + "venafi.experimental.cert-manager.io/custom-fields": "garbage-data", + }), + gen.SetCertificateSigningRequestStatusCondition(certificatesv1.CertificateSigningRequestCondition{ + Type: certificatesv1.CertificateApproved, + Status: corev1.ConditionTrue, + }), + ), + clientBuilder: func(_ string, _ corelisters.SecretLister, _ cmapi.GenericIssuer) (venaficlient.Interface, error) { + return &fakevenaficlient.Venafi{}, nil + }, + builder: &testpkg.Builder{ + CertManagerObjects: []runtime.Object{baseIssuer.DeepCopy()}, + ExpectedEvents: []string{ + `Warning ErrorCustomFields Failed to parse "venafi.experimental.cert-manager.io/custom-fields" annotation: invalid character 'g' looking for beginning of value`, + }, + ExpectedActions: []testpkg.Action{ + testpkg.NewAction(coretesting.NewCreateAction( + authzv1.SchemeGroupVersion.WithResource("subjectaccessreviews"), + "", + &authzv1.SubjectAccessReview{ + Spec: authzv1.SubjectAccessReviewSpec{ + User: "user-1", + Groups: []string{"group-1", "group-2"}, + Extra: map[string]authzv1.ExtraValue{ + "extra": []string{"1", "2"}, + }, + UID: "uid-1", + + ResourceAttributes: &authzv1.ResourceAttributes{ + Group: certmanager.GroupName, + Resource: "signers", + Verb: "reference", + Namespace: baseIssuer.Namespace, + Name: baseIssuer.Name, + Version: "*", + }, + }, + }, + )), + testpkg.NewAction(coretesting.NewUpdateSubresourceAction( + certificatesv1.SchemeGroupVersion.WithResource("certificatesigningrequests"), + "status", + "", + gen.CertificateSigningRequestFrom(baseCSR.DeepCopy(), + gen.AddCertificateSigningRequestAnnotations(map[string]string{ + "venafi.experimental.cert-manager.io/custom-fields": "garbage-data", + }), + gen.SetCertificateSigningRequestStatusCondition(certificatesv1.CertificateSigningRequestCondition{ + Type: certificatesv1.CertificateApproved, + Status: corev1.ConditionTrue, + }), + gen.SetCertificateSigningRequestStatusCondition(certificatesv1.CertificateSigningRequestCondition{ + Type: certificatesv1.CertificateFailed, + Status: corev1.ConditionTrue, + Reason: "ErrorCustomFields", + Message: `Failed to parse "venafi.experimental.cert-manager.io/custom-fields" annotation: invalid character 'g' looking for beginning of value`, + LastTransitionTime: metaFixedClockStart, + LastUpdateTime: metaFixedClockStart, + }), + ), + )), + }, + }, + }, + "an approved CSR where the requested duration annotations contains garbage data should mark as Failed": { + csr: gen.CertificateSigningRequestFrom(baseCSR, + gen.AddCertificateSigningRequestAnnotations(map[string]string{ + "venafi.experimental.cert-manager.io/custom-fields": `[ {"name": "field-name", "value": "vield value"}]`, + }), + gen.SetCertificateSigningRequestDuration("garbage-duration"), + gen.SetCertificateSigningRequestStatusCondition(certificatesv1.CertificateSigningRequestCondition{ + Type: certificatesv1.CertificateApproved, + Status: corev1.ConditionTrue, + }), + ), + clientBuilder: func(_ string, _ corelisters.SecretLister, _ cmapi.GenericIssuer) (venaficlient.Interface, error) { + return &fakevenaficlient.Venafi{}, nil + }, + builder: &testpkg.Builder{ + CertManagerObjects: []runtime.Object{baseIssuer.DeepCopy()}, + ExpectedEvents: []string{ + `Warning ErrorParseDuration Failed to parse requested duration: failed to parse requested duration on annotation "experimental.cert-manager.io/request-duration": time: invalid duration "garbage-duration"`, + }, + ExpectedActions: []testpkg.Action{ + testpkg.NewAction(coretesting.NewCreateAction( + authzv1.SchemeGroupVersion.WithResource("subjectaccessreviews"), + "", + &authzv1.SubjectAccessReview{ + Spec: authzv1.SubjectAccessReviewSpec{ + User: "user-1", + Groups: []string{"group-1", "group-2"}, + Extra: map[string]authzv1.ExtraValue{ + "extra": []string{"1", "2"}, + }, + UID: "uid-1", + + ResourceAttributes: &authzv1.ResourceAttributes{ + Group: certmanager.GroupName, + Resource: "signers", + Verb: "reference", + Namespace: baseIssuer.Namespace, + Name: baseIssuer.Name, + Version: "*", + }, + }, + }, + )), + testpkg.NewAction(coretesting.NewUpdateSubresourceAction( + certificatesv1.SchemeGroupVersion.WithResource("certificatesigningrequests"), + "status", + "", + gen.CertificateSigningRequestFrom(baseCSR.DeepCopy(), + gen.SetCertificateSigningRequestDuration("garbage-duration"), + gen.AddCertificateSigningRequestAnnotations(map[string]string{ + "venafi.experimental.cert-manager.io/custom-fields": `[ {"name": "field-name", "value": "vield value"}]`, + }), + gen.SetCertificateSigningRequestStatusCondition(certificatesv1.CertificateSigningRequestCondition{ + Type: certificatesv1.CertificateApproved, + Status: corev1.ConditionTrue, + }), + gen.SetCertificateSigningRequestStatusCondition(certificatesv1.CertificateSigningRequestCondition{ + Type: certificatesv1.CertificateFailed, + Status: corev1.ConditionTrue, + Reason: "ErrorParseDuration", + Message: `Failed to parse requested duration: failed to parse requested duration on annotation "experimental.cert-manager.io/request-duration": time: invalid duration "garbage-duration"`, + LastTransitionTime: metaFixedClockStart, + LastUpdateTime: metaFixedClockStart, + }), + ), + )), + }, + }, + }, + "an approved CSR which does not yet have a pickup ID, but the client responds with fields type error, should mark as Failed": { + csr: gen.CertificateSigningRequestFrom(baseCSR, + gen.AddCertificateSigningRequestAnnotations(map[string]string{ + "venafi.experimental.cert-manager.io/custom-fields": `[ {"name": "field-name", "value": "vield value"}]`, + }), + gen.SetCertificateSigningRequestStatusCondition(certificatesv1.CertificateSigningRequestCondition{ + Type: certificatesv1.CertificateApproved, + Status: corev1.ConditionTrue, + }), + ), + clientBuilder: func(_ string, _ corelisters.SecretLister, _ cmapi.GenericIssuer) (venaficlient.Interface, error) { + return &fakevenaficlient.Venafi{ + RequestCertificateFn: func(_ []byte, _ time.Duration, _ []venafiapi.CustomField) (string, error) { + return "", venaficlient.ErrCustomFieldsType{Type: "test-type"} + }, + }, nil + }, + builder: &testpkg.Builder{ + CertManagerObjects: []runtime.Object{baseIssuer.DeepCopy()}, + ExpectedEvents: []string{ + `Warning ErrorCustomFields certificate request contains an invalid Venafi custom fields type: "test-type"`, + }, + ExpectedActions: []testpkg.Action{ + testpkg.NewAction(coretesting.NewCreateAction( + authzv1.SchemeGroupVersion.WithResource("subjectaccessreviews"), + "", + &authzv1.SubjectAccessReview{ + Spec: authzv1.SubjectAccessReviewSpec{ + User: "user-1", + Groups: []string{"group-1", "group-2"}, + Extra: map[string]authzv1.ExtraValue{ + "extra": []string{"1", "2"}, + }, + UID: "uid-1", + + ResourceAttributes: &authzv1.ResourceAttributes{ + Group: certmanager.GroupName, + Resource: "signers", + Verb: "reference", + Namespace: baseIssuer.Namespace, + Name: baseIssuer.Name, + Version: "*", + }, + }, + }, + )), + testpkg.NewAction(coretesting.NewUpdateSubresourceAction( + certificatesv1.SchemeGroupVersion.WithResource("certificatesigningrequests"), + "status", + "", + gen.CertificateSigningRequestFrom(baseCSR.DeepCopy(), + gen.AddCertificateSigningRequestAnnotations(map[string]string{ + "venafi.experimental.cert-manager.io/custom-fields": `[ {"name": "field-name", "value": "vield value"}]`, + }), + gen.SetCertificateSigningRequestStatusCondition(certificatesv1.CertificateSigningRequestCondition{ + Type: certificatesv1.CertificateApproved, + Status: corev1.ConditionTrue, + }), + gen.SetCertificateSigningRequestStatusCondition(certificatesv1.CertificateSigningRequestCondition{ + Type: certificatesv1.CertificateFailed, + Status: corev1.ConditionTrue, + Reason: "ErrorCustomFields", + Message: `certificate request contains an invalid Venafi custom fields type: "test-type"`, + LastTransitionTime: metaFixedClockStart, + LastUpdateTime: metaFixedClockStart, + }), + ), + )), + }, + }, + }, + "an approved CSR which does not yet have a pickup ID, but the client responds a generic error, should mark as Failed": { + csr: gen.CertificateSigningRequestFrom(baseCSR, + gen.AddCertificateSigningRequestAnnotations(map[string]string{ + "venafi.experimental.cert-manager.io/custom-fields": `[ {"name": "field-name", "value": "vield value"}]`, + }), + gen.SetCertificateSigningRequestStatusCondition(certificatesv1.CertificateSigningRequestCondition{ + Type: certificatesv1.CertificateApproved, + Status: corev1.ConditionTrue, + }), + ), + clientBuilder: func(_ string, _ corelisters.SecretLister, _ cmapi.GenericIssuer) (venaficlient.Interface, error) { + return &fakevenaficlient.Venafi{ + RequestCertificateFn: func(_ []byte, _ time.Duration, _ []venafiapi.CustomField) (string, error) { + return "", errors.New("generic error") + }, + }, nil + }, + builder: &testpkg.Builder{ + CertManagerObjects: []runtime.Object{baseIssuer.DeepCopy()}, + ExpectedEvents: []string{ + "Warning ErrorRequest Failed to request venafi certificate: generic error", + }, + ExpectedActions: []testpkg.Action{ + testpkg.NewAction(coretesting.NewCreateAction( + authzv1.SchemeGroupVersion.WithResource("subjectaccessreviews"), + "", + &authzv1.SubjectAccessReview{ + Spec: authzv1.SubjectAccessReviewSpec{ + User: "user-1", + Groups: []string{"group-1", "group-2"}, + Extra: map[string]authzv1.ExtraValue{ + "extra": []string{"1", "2"}, + }, + UID: "uid-1", + + ResourceAttributes: &authzv1.ResourceAttributes{ + Group: certmanager.GroupName, + Resource: "signers", + Verb: "reference", + Namespace: baseIssuer.Namespace, + Name: baseIssuer.Name, + Version: "*", + }, + }, + }, + )), + testpkg.NewAction(coretesting.NewUpdateSubresourceAction( + certificatesv1.SchemeGroupVersion.WithResource("certificatesigningrequests"), + "status", + "", + gen.CertificateSigningRequestFrom(baseCSR.DeepCopy(), + gen.AddCertificateSigningRequestAnnotations(map[string]string{ + "venafi.experimental.cert-manager.io/custom-fields": `[ {"name": "field-name", "value": "vield value"}]`, + }), + gen.SetCertificateSigningRequestStatusCondition(certificatesv1.CertificateSigningRequestCondition{ + Type: certificatesv1.CertificateApproved, + Status: corev1.ConditionTrue, + }), + gen.SetCertificateSigningRequestStatusCondition(certificatesv1.CertificateSigningRequestCondition{ + Type: certificatesv1.CertificateFailed, + Status: corev1.ConditionTrue, + Reason: "ErrorRequest", + Message: "Failed to request venafi certificate: generic error", + LastTransitionTime: metaFixedClockStart, + LastUpdateTime: metaFixedClockStart, + }), + ), + )), + }, + }, + }, + "an approved CSR which does not yet have a pickup ID, should update the annotation with one and return": { + csr: gen.CertificateSigningRequestFrom(baseCSR, + gen.AddCertificateSigningRequestAnnotations(map[string]string{ + "venafi.experimental.cert-manager.io/custom-fields": `[ {"name": "field-name", "value": "vield value"}]`, + }), + gen.SetCertificateSigningRequestStatusCondition(certificatesv1.CertificateSigningRequestCondition{ + Type: certificatesv1.CertificateApproved, + Status: corev1.ConditionTrue, + }), + ), + clientBuilder: func(_ string, _ corelisters.SecretLister, _ cmapi.GenericIssuer) (venaficlient.Interface, error) { + return &fakevenaficlient.Venafi{ + RequestCertificateFn: func(_ []byte, _ time.Duration, _ []venafiapi.CustomField) (string, error) { + return "test-pickup-id", nil + }, + }, nil + }, + builder: &testpkg.Builder{ + CertManagerObjects: []runtime.Object{baseIssuer.DeepCopy()}, + ExpectedEvents: []string{}, + ExpectedActions: []testpkg.Action{ + testpkg.NewAction(coretesting.NewCreateAction( + authzv1.SchemeGroupVersion.WithResource("subjectaccessreviews"), + "", + &authzv1.SubjectAccessReview{ + Spec: authzv1.SubjectAccessReviewSpec{ + User: "user-1", + Groups: []string{"group-1", "group-2"}, + Extra: map[string]authzv1.ExtraValue{ + "extra": []string{"1", "2"}, + }, + UID: "uid-1", + + ResourceAttributes: &authzv1.ResourceAttributes{ + Group: certmanager.GroupName, + Resource: "signers", + Verb: "reference", + Namespace: baseIssuer.Namespace, + Name: baseIssuer.Name, + Version: "*", + }, + }, + }, + )), + testpkg.NewAction(coretesting.NewUpdateAction( + certificatesv1.SchemeGroupVersion.WithResource("certificatesigningrequests"), + "", + gen.CertificateSigningRequestFrom(baseCSR.DeepCopy(), + gen.AddCertificateSigningRequestAnnotations(map[string]string{ + "venafi.experimental.cert-manager.io/custom-fields": `[ {"name": "field-name", "value": "vield value"}]`, + "venafi.experimental.cert-manager.io/pickup-id": "test-pickup-id", + }), + gen.SetCertificateSigningRequestStatusCondition(certificatesv1.CertificateSigningRequestCondition{ + Type: certificatesv1.CertificateApproved, + Status: corev1.ConditionTrue, + }), + ), + )), + }, + }, + }, + "an approved CSR which has a pickup ID, retrieve certificate returns a pending error, fire event and return error": { + csr: gen.CertificateSigningRequestFrom(baseCSR, + gen.AddCertificateSigningRequestAnnotations(map[string]string{ + "venafi.experimental.cert-manager.io/custom-fields": `[ {"name": "field-name", "value": "vield value"}]`, + "venafi.experimental.cert-manager.io/pickup-id": "test-pickup-id", + }), + gen.SetCertificateSigningRequestStatusCondition(certificatesv1.CertificateSigningRequestCondition{ + Type: certificatesv1.CertificateApproved, + Status: corev1.ConditionTrue, + }), + ), + clientBuilder: func(_ string, _ corelisters.SecretLister, _ cmapi.GenericIssuer) (venaficlient.Interface, error) { + return &fakevenaficlient.Venafi{ + RetrieveCertificateFn: func(_ string, _ []byte, _ time.Duration, _ []venafiapi.CustomField) ([]byte, error) { + return nil, endpoint.ErrCertificatePending{} + }, + }, nil + }, + expectedErr: true, + builder: &testpkg.Builder{ + CertManagerObjects: []runtime.Object{baseIssuer.DeepCopy()}, + ExpectedEvents: []string{ + "Normal IssuancePending Venafi certificate still in a pending state, waiting", + }, + ExpectedActions: []testpkg.Action{ + testpkg.NewAction(coretesting.NewCreateAction( + authzv1.SchemeGroupVersion.WithResource("subjectaccessreviews"), + "", + &authzv1.SubjectAccessReview{ + Spec: authzv1.SubjectAccessReviewSpec{ + User: "user-1", + Groups: []string{"group-1", "group-2"}, + Extra: map[string]authzv1.ExtraValue{ + "extra": []string{"1", "2"}, + }, + UID: "uid-1", + + ResourceAttributes: &authzv1.ResourceAttributes{ + Group: certmanager.GroupName, + Resource: "signers", + Verb: "reference", + Namespace: baseIssuer.Namespace, + Name: baseIssuer.Name, + Version: "*", + }, + }, + }, + )), + }, + }, + }, + "an approved CSR which has a pickup ID, retrieve certificate returns a timeout error, fire event and return error": { + csr: gen.CertificateSigningRequestFrom(baseCSR, + gen.AddCertificateSigningRequestAnnotations(map[string]string{ + "venafi.experimental.cert-manager.io/custom-fields": `[ {"name": "field-name", "value": "vield value"}]`, + "venafi.experimental.cert-manager.io/pickup-id": "test-pickup-id", + }), + gen.SetCertificateSigningRequestStatusCondition(certificatesv1.CertificateSigningRequestCondition{ + Type: certificatesv1.CertificateApproved, + Status: corev1.ConditionTrue, + }), + ), + clientBuilder: func(_ string, _ corelisters.SecretLister, _ cmapi.GenericIssuer) (venaficlient.Interface, error) { + return &fakevenaficlient.Venafi{ + RetrieveCertificateFn: func(_ string, _ []byte, _ time.Duration, _ []venafiapi.CustomField) ([]byte, error) { + return nil, endpoint.ErrRetrieveCertificateTimeout{} + }, + }, nil + }, + expectedErr: true, + builder: &testpkg.Builder{ + CertManagerObjects: []runtime.Object{baseIssuer.DeepCopy()}, + ExpectedEvents: []string{ + "Warning RetrieveCertificateTimeout Venafi retrieve certificate timeout, retrying", + }, + ExpectedActions: []testpkg.Action{ + testpkg.NewAction(coretesting.NewCreateAction( + authzv1.SchemeGroupVersion.WithResource("subjectaccessreviews"), + "", + &authzv1.SubjectAccessReview{ + Spec: authzv1.SubjectAccessReviewSpec{ + User: "user-1", + Groups: []string{"group-1", "group-2"}, + Extra: map[string]authzv1.ExtraValue{ + "extra": []string{"1", "2"}, + }, + UID: "uid-1", + + ResourceAttributes: &authzv1.ResourceAttributes{ + Group: certmanager.GroupName, + Resource: "signers", + Verb: "reference", + Namespace: baseIssuer.Namespace, + Name: baseIssuer.Name, + Version: "*", + }, + }, + }, + )), + }, + }, + }, + "an approved CSR which has a pickup ID, retrieve certificate returns a generic error, fire event and return error": { + csr: gen.CertificateSigningRequestFrom(baseCSR, + gen.AddCertificateSigningRequestAnnotations(map[string]string{ + "venafi.experimental.cert-manager.io/custom-fields": `[ {"name": "field-name", "value": "vield value"}]`, + "venafi.experimental.cert-manager.io/pickup-id": "test-pickup-id", + }), + gen.SetCertificateSigningRequestStatusCondition(certificatesv1.CertificateSigningRequestCondition{ + Type: certificatesv1.CertificateApproved, + Status: corev1.ConditionTrue, + }), + ), + clientBuilder: func(_ string, _ corelisters.SecretLister, _ cmapi.GenericIssuer) (venaficlient.Interface, error) { + return &fakevenaficlient.Venafi{ + RetrieveCertificateFn: func(_ string, _ []byte, _ time.Duration, _ []venafiapi.CustomField) ([]byte, error) { + return nil, errors.New("generic error") + }, + }, nil + }, + expectedErr: true, + builder: &testpkg.Builder{ + CertManagerObjects: []runtime.Object{baseIssuer.DeepCopy()}, + ExpectedEvents: []string{ + "Warning ErrorRetrieve Failed to obtain venafi certificate: generic error", + }, + ExpectedActions: []testpkg.Action{ + testpkg.NewAction(coretesting.NewCreateAction( + authzv1.SchemeGroupVersion.WithResource("subjectaccessreviews"), + "", + &authzv1.SubjectAccessReview{ + Spec: authzv1.SubjectAccessReviewSpec{ + User: "user-1", + Groups: []string{"group-1", "group-2"}, + Extra: map[string]authzv1.ExtraValue{ + "extra": []string{"1", "2"}, + }, + UID: "uid-1", + + ResourceAttributes: &authzv1.ResourceAttributes{ + Group: certmanager.GroupName, + Resource: "signers", + Verb: "reference", + Namespace: baseIssuer.Namespace, + Name: baseIssuer.Name, + Version: "*", + }, + }, + }, + )), + }, + }, + }, + "an approved CSR which has a pickup ID, retrieve certificate returns garbage certificates, should mark as Failed": { + csr: gen.CertificateSigningRequestFrom(baseCSR, + gen.AddCertificateSigningRequestAnnotations(map[string]string{ + "venafi.experimental.cert-manager.io/custom-fields": `[ {"name": "field-name", "value": "vield value"}]`, + "venafi.experimental.cert-manager.io/pickup-id": "test-pickup-id", + }), + gen.SetCertificateSigningRequestStatusCondition(certificatesv1.CertificateSigningRequestCondition{ + Type: certificatesv1.CertificateApproved, + Status: corev1.ConditionTrue, + }), + ), + clientBuilder: func(_ string, _ corelisters.SecretLister, _ cmapi.GenericIssuer) (venaficlient.Interface, error) { + return &fakevenaficlient.Venafi{ + RetrieveCertificateFn: func(_ string, _ []byte, _ time.Duration, _ []venafiapi.CustomField) ([]byte, error) { + return []byte("garbage"), nil + }, + }, nil + }, + builder: &testpkg.Builder{ + CertManagerObjects: []runtime.Object{baseIssuer.DeepCopy()}, + ExpectedEvents: []string{ + "Warning ErrorParse Failed to parse returned certificate bundle: error decoding certificate PEM block", + }, + ExpectedActions: []testpkg.Action{ + testpkg.NewAction(coretesting.NewCreateAction( + authzv1.SchemeGroupVersion.WithResource("subjectaccessreviews"), + "", + &authzv1.SubjectAccessReview{ + Spec: authzv1.SubjectAccessReviewSpec{ + User: "user-1", + Groups: []string{"group-1", "group-2"}, + Extra: map[string]authzv1.ExtraValue{ + "extra": []string{"1", "2"}, + }, + UID: "uid-1", + + ResourceAttributes: &authzv1.ResourceAttributes{ + Group: certmanager.GroupName, + Resource: "signers", + Verb: "reference", + Namespace: baseIssuer.Namespace, + Name: baseIssuer.Name, + Version: "*", + }, + }, + }, + )), + testpkg.NewAction(coretesting.NewUpdateSubresourceAction( + certificatesv1.SchemeGroupVersion.WithResource("certificatesigningrequests"), + "status", + "", + gen.CertificateSigningRequestFrom(baseCSR.DeepCopy(), + gen.AddCertificateSigningRequestAnnotations(map[string]string{ + "venafi.experimental.cert-manager.io/custom-fields": `[ {"name": "field-name", "value": "vield value"}]`, + "venafi.experimental.cert-manager.io/pickup-id": "test-pickup-id", + }), + gen.SetCertificateSigningRequestStatusCondition(certificatesv1.CertificateSigningRequestCondition{ + Type: certificatesv1.CertificateApproved, + Status: corev1.ConditionTrue, + }), + gen.SetCertificateSigningRequestStatusCondition(certificatesv1.CertificateSigningRequestCondition{ + Type: certificatesv1.CertificateFailed, + Status: corev1.ConditionTrue, + Reason: "ErrorParse", + Message: "Failed to parse returned certificate bundle: error decoding certificate PEM block", + LastTransitionTime: metaFixedClockStart, + LastUpdateTime: metaFixedClockStart, + }), + ), + )), + }, + }, + }, + "an approved CSR which has a pickup ID, retrieve certificate returns a CA and certificate should update with certificate": { + csr: gen.CertificateSigningRequestFrom(baseCSR, + gen.AddCertificateSigningRequestAnnotations(map[string]string{ + "venafi.experimental.cert-manager.io/custom-fields": `[ {"name": "field-name", "value": "vield value"}]`, + "venafi.experimental.cert-manager.io/pickup-id": "test-pickup-id", + }), + gen.SetCertificateSigningRequestStatusCondition(certificatesv1.CertificateSigningRequestCondition{ + Type: certificatesv1.CertificateApproved, + Status: corev1.ConditionTrue, + }), + ), + clientBuilder: func(_ string, _ corelisters.SecretLister, _ cmapi.GenericIssuer) (venaficlient.Interface, error) { + return &fakevenaficlient.Venafi{ + RetrieveCertificateFn: func(_ string, _ []byte, _ time.Duration, _ []venafiapi.CustomField) ([]byte, error) { + return []byte(fmt.Sprintf("%s%s", certBundle.ChainPEM, certBundle.CAPEM)), nil + }, + }, nil + }, + builder: &testpkg.Builder{ + CertManagerObjects: []runtime.Object{baseIssuer.DeepCopy()}, + ExpectedEvents: []string{ + "Normal CertificateIssued Certificate fetched from venafi issuer successfully", + }, + ExpectedActions: []testpkg.Action{ + testpkg.NewAction(coretesting.NewCreateAction( + authzv1.SchemeGroupVersion.WithResource("subjectaccessreviews"), + "", + &authzv1.SubjectAccessReview{ + Spec: authzv1.SubjectAccessReviewSpec{ + User: "user-1", + Groups: []string{"group-1", "group-2"}, + Extra: map[string]authzv1.ExtraValue{ + "extra": []string{"1", "2"}, + }, + UID: "uid-1", + + ResourceAttributes: &authzv1.ResourceAttributes{ + Group: certmanager.GroupName, + Resource: "signers", + Verb: "reference", + Namespace: baseIssuer.Namespace, + Name: baseIssuer.Name, + Version: "*", + }, + }, + }, + )), + testpkg.NewAction(coretesting.NewUpdateSubresourceAction( + certificatesv1.SchemeGroupVersion.WithResource("certificatesigningrequests"), + "status", + "", + gen.CertificateSigningRequestFrom(baseCSR.DeepCopy(), + gen.AddCertificateSigningRequestAnnotations(map[string]string{ + "venafi.experimental.cert-manager.io/custom-fields": `[ {"name": "field-name", "value": "vield value"}]`, + "venafi.experimental.cert-manager.io/pickup-id": "test-pickup-id", + }), + gen.SetCertificateSigningRequestStatusCondition(certificatesv1.CertificateSigningRequestCondition{ + Type: certificatesv1.CertificateApproved, + Status: corev1.ConditionTrue, + }), + gen.SetCertificateSigningRequestCertificate(certBundle.ChainPEM), + ), + )), + }, + }, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + if test.csr != nil { + test.builder.KubeObjects = append(test.builder.KubeObjects, test.csr) + } + + fixedClock.SetTime(fixedClockStart) + test.builder.Clock = fixedClock + test.builder.T = t + test.builder.Init() + + // Always return true for SubjectAccessReviews in tests + test.builder.FakeKubeClient().PrependReactor("create", "*", func(action coretesting.Action) (bool, runtime.Object, error) { + if action.GetResource() != authzv1.SchemeGroupVersion.WithResource("subjectaccessreviews") { + return false, nil, nil + } + return true, &authzv1.SubjectAccessReview{ + Status: authzv1.SubjectAccessReviewStatus{ + Allowed: true, + }, + }, nil + }) + + defer test.builder.Stop() + + venafi := NewVenafi(test.builder.Context) + venafi.clientBuilder = test.clientBuilder + + controller := certificatesigningrequests.New(apiutil.IssuerVenafi, venafi) + controller.Register(test.builder.Context) + test.builder.Start() + + err := controller.ProcessItem(context.Background(), test.csr.Name) + if err != nil && !test.expectedErr { + t.Errorf("expected to not get an error, but got: %v", err) + } + if err == nil && test.expectedErr { + t.Errorf("expected to get an error but did not get one") + } + + test.builder.CheckAndFinish(err) + }) + } +} diff --git a/pkg/issuer/venafi/client/venaficlient.go b/pkg/issuer/venafi/client/venaficlient.go index 1352b4839..a140a6ef0 100644 --- a/pkg/issuer/venafi/client/venaficlient.go +++ b/pkg/issuer/venafi/client/venaficlient.go @@ -70,6 +70,8 @@ type connector interface { RenewCertificate(req *certificate.RenewalRequest) (requestID string, err error) } +// New constructs a Venafi client Interface. Errors may be network errors and +// should be considered for retrying. func New(namespace string, secretsLister corelisters.SecretLister, issuer cmapi.GenericIssuer) (Interface, error) { cfg, err := configForIssuer(issuer, secretsLister, namespace) if err != nil { diff --git a/test/e2e/framework/addon/BUILD.bazel b/test/e2e/framework/addon/BUILD.bazel index cd82eda9e..0f9d047d8 100644 --- a/test/e2e/framework/addon/BUILD.bazel +++ b/test/e2e/framework/addon/BUILD.bazel @@ -28,6 +28,7 @@ filegroup( "//test/e2e/framework/addon/base:all-srcs", "//test/e2e/framework/addon/chart:all-srcs", "//test/e2e/framework/addon/vault:all-srcs", + "//test/e2e/framework/addon/venafi:all-srcs", ], tags = ["automanaged"], visibility = ["//visibility:public"], diff --git a/test/e2e/suite/issuers/venafi/addon/BUILD.bazel b/test/e2e/framework/addon/venafi/BUILD.bazel similarity index 91% rename from test/e2e/suite/issuers/venafi/addon/BUILD.bazel rename to test/e2e/framework/addon/venafi/BUILD.bazel index eea2e15ac..4bd786f18 100644 --- a/test/e2e/suite/issuers/venafi/addon/BUILD.bazel +++ b/test/e2e/framework/addon/venafi/BUILD.bazel @@ -7,7 +7,7 @@ go_library( "doc.go", "tpp.go", ], - importpath = "github.com/jetstack/cert-manager/test/e2e/suite/issuers/venafi/addon", + importpath = "github.com/jetstack/cert-manager/test/e2e/framework/addon/venafi", tags = ["manual"], visibility = ["//visibility:public"], deps = [ diff --git a/test/e2e/suite/issuers/venafi/addon/cloud.go b/test/e2e/framework/addon/venafi/cloud.go similarity index 99% rename from test/e2e/suite/issuers/venafi/addon/cloud.go rename to test/e2e/framework/addon/venafi/cloud.go index b91368775..5121c030e 100644 --- a/test/e2e/suite/issuers/venafi/addon/cloud.go +++ b/test/e2e/framework/addon/venafi/cloud.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package addon +package venafi import ( "context" diff --git a/test/e2e/suite/issuers/venafi/addon/doc.go b/test/e2e/framework/addon/venafi/doc.go similarity index 89% rename from test/e2e/suite/issuers/venafi/addon/doc.go rename to test/e2e/framework/addon/venafi/doc.go index 8732a5e50..b995bf6e1 100644 --- a/test/e2e/suite/issuers/venafi/addon/doc.go +++ b/test/e2e/framework/addon/venafi/doc.go @@ -14,6 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -// Package addon implements an addon for the Venafi platform. +// Package venafi implements an addon for the Venafi platform. // It provides a means for e2e tests to consume credentials for Venafi TPP. -package addon +package venafi diff --git a/test/e2e/suite/issuers/venafi/addon/tpp.go b/test/e2e/framework/addon/venafi/tpp.go similarity index 99% rename from test/e2e/suite/issuers/venafi/addon/tpp.go rename to test/e2e/framework/addon/venafi/tpp.go index 2a8aab804..e2309c084 100644 --- a/test/e2e/suite/issuers/venafi/addon/tpp.go +++ b/test/e2e/framework/addon/venafi/tpp.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package addon +package venafi import ( "context" diff --git a/test/e2e/framework/helper/validation/validation.go b/test/e2e/framework/helper/validation/validation.go index 3a0d2f9d1..c76bd91cb 100644 --- a/test/e2e/framework/helper/validation/validation.go +++ b/test/e2e/framework/helper/validation/validation.go @@ -49,7 +49,6 @@ func DefaultCertificateSigningRequestSet() []certificatesigningrequests.Validati certificatesigningrequests.ExpectCertificateURIsToMatch, certificatesigningrequests.ExpectCertificateIPsToMatch, certificatesigningrequests.ExpectValidCommonName, - certificatesigningrequests.ExpectValidDuration, certificatesigningrequests.ExpectKeyUsageUsageDigitalSignature, certificatesigningrequests.ExpectEmailsToMatch, certificatesigningrequests.ExpectIsCA, @@ -92,5 +91,11 @@ func CertificateSetForUnsupportedFeatureSet(fs featureset.FeatureSet) []certific } func CertificateSigningRequestSetForUnsupportedFeatureSet(fs featureset.FeatureSet) []certificatesigningrequests.ValidationFunc { - return DefaultCertificateSigningRequestSet() + validations := DefaultCertificateSigningRequestSet() + + if !fs.Contains(featureset.DurationFeature) { + validations = append(validations, certificatesigningrequests.ExpectValidDuration) + } + + return validations } diff --git a/test/e2e/suite/conformance/BUILD.bazel b/test/e2e/suite/conformance/BUILD.bazel index c5397ea8a..d86719d37 100644 --- a/test/e2e/suite/conformance/BUILD.bazel +++ b/test/e2e/suite/conformance/BUILD.bazel @@ -17,6 +17,7 @@ go_library( "//test/e2e/suite/conformance/certificatesigningrequests/ca:go_default_library", "//test/e2e/suite/conformance/certificatesigningrequests/selfsigned:go_default_library", "//test/e2e/suite/conformance/certificatesigningrequests/vault:go_default_library", + "//test/e2e/suite/conformance/certificatesigningrequests/venafi:go_default_library", "//test/e2e/suite/conformance/rbac:go_default_library", ], ) diff --git a/test/e2e/suite/conformance/certificates/venafi/BUILD.bazel b/test/e2e/suite/conformance/certificates/venafi/BUILD.bazel index 709ecd240..7b7961877 100644 --- a/test/e2e/suite/conformance/certificates/venafi/BUILD.bazel +++ b/test/e2e/suite/conformance/certificates/venafi/BUILD.bazel @@ -9,10 +9,10 @@ go_library( "//pkg/apis/certmanager/v1:go_default_library", "//pkg/apis/meta/v1:go_default_library", "//test/e2e/framework:go_default_library", + "//test/e2e/framework/addon/venafi:go_default_library", "//test/e2e/framework/helper/featureset: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", "@io_k8s_apimachinery//pkg/apis/meta/v1:go_default_library", diff --git a/test/e2e/suite/conformance/certificates/venafi/venafi.go b/test/e2e/suite/conformance/certificates/venafi/venafi.go index f2142ffd6..5a909c4ba 100644 --- a/test/e2e/suite/conformance/certificates/venafi/venafi.go +++ b/test/e2e/suite/conformance/certificates/venafi/venafi.go @@ -26,10 +26,10 @@ import ( cmapi "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/jetstack/cert-manager/pkg/apis/meta/v1" "github.com/jetstack/cert-manager/test/e2e/framework" + vaddon "github.com/jetstack/cert-manager/test/e2e/framework/addon/venafi" "github.com/jetstack/cert-manager/test/e2e/framework/helper/featureset" "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() { diff --git a/test/e2e/suite/conformance/certificates/venaficloud/BUILD.bazel b/test/e2e/suite/conformance/certificates/venaficloud/BUILD.bazel index 4046d2505..e22142c9b 100644 --- a/test/e2e/suite/conformance/certificates/venaficloud/BUILD.bazel +++ b/test/e2e/suite/conformance/certificates/venaficloud/BUILD.bazel @@ -9,10 +9,10 @@ go_library( "//pkg/apis/certmanager/v1:go_default_library", "//pkg/apis/meta/v1:go_default_library", "//test/e2e/framework:go_default_library", + "//test/e2e/framework/addon/venafi:go_default_library", "//test/e2e/framework/helper/featureset: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", "@io_k8s_apimachinery//pkg/apis/meta/v1:go_default_library", diff --git a/test/e2e/suite/conformance/certificates/venaficloud/cloud.go b/test/e2e/suite/conformance/certificates/venaficloud/cloud.go index 08dd887cc..3b1fde1b6 100644 --- a/test/e2e/suite/conformance/certificates/venaficloud/cloud.go +++ b/test/e2e/suite/conformance/certificates/venaficloud/cloud.go @@ -26,10 +26,10 @@ import ( cmapi "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/jetstack/cert-manager/pkg/apis/meta/v1" "github.com/jetstack/cert-manager/test/e2e/framework" + vaddon "github.com/jetstack/cert-manager/test/e2e/framework/addon/venafi" "github.com/jetstack/cert-manager/test/e2e/framework/helper/featureset" "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("[Feature:Issuers:Venafi:Cloud] Certificates", func() { diff --git a/test/e2e/suite/conformance/certificatesigningrequests/BUILD.bazel b/test/e2e/suite/conformance/certificatesigningrequests/BUILD.bazel index cee603c87..b5311223f 100644 --- a/test/e2e/suite/conformance/certificatesigningrequests/BUILD.bazel +++ b/test/e2e/suite/conformance/certificatesigningrequests/BUILD.bazel @@ -40,6 +40,7 @@ filegroup( "//test/e2e/suite/conformance/certificatesigningrequests/ca:all-srcs", "//test/e2e/suite/conformance/certificatesigningrequests/selfsigned:all-srcs", "//test/e2e/suite/conformance/certificatesigningrequests/vault:all-srcs", + "//test/e2e/suite/conformance/certificatesigningrequests/venafi:all-srcs", ], tags = ["automanaged"], visibility = ["//visibility:public"], diff --git a/test/e2e/suite/conformance/certificatesigningrequests/tests.go b/test/e2e/suite/conformance/certificatesigningrequests/tests.go index 1a9af5c4e..6faf58812 100644 --- a/test/e2e/suite/conformance/certificatesigningrequests/tests.go +++ b/test/e2e/suite/conformance/certificatesigningrequests/tests.go @@ -72,8 +72,11 @@ func (s *Suite) Define() { }) type testCase struct { - keyAlgo x509.PublicKeyAlgorithm - csrModifiers []gen.CSRModifier + keyAlgo x509.PublicKeyAlgorithm + // csrModifers define the shape of the X.509 CSR which is used in the + // test case. We use a function to allow access to variables that are + // initialized at test runtime by complete(). + csrModifiers func() []gen.CSRModifier kubeCSRUsages []certificatesv1.KeyUsage kubeCSRAnnotations map[string]string // The list of features that are required by the Issuer for the test to @@ -86,8 +89,10 @@ func (s *Suite) Define() { tests := map[string]testCase{ "should issue an RSA certificate for a single distinct DNS Name": { - keyAlgo: x509.RSA, - csrModifiers: []gen.CSRModifier{gen.SetCSRDNSNames(e2eutil.RandomSubdomain(s.DomainSuffix))}, + keyAlgo: x509.RSA, + csrModifiers: func() []gen.CSRModifier { + return []gen.CSRModifier{gen.SetCSRDNSNames(e2eutil.RandomSubdomain(s.DomainSuffix))} + }, kubeCSRUsages: []certificatesv1.KeyUsage{ certificatesv1.UsageDigitalSignature, certificatesv1.UsageKeyEncipherment, @@ -96,8 +101,10 @@ func (s *Suite) Define() { }, "should issue an ECDSA certificate for a single distinct DNS Name": { - keyAlgo: x509.ECDSA, - csrModifiers: []gen.CSRModifier{gen.SetCSRDNSNames(e2eutil.RandomSubdomain(s.DomainSuffix))}, + keyAlgo: x509.ECDSA, + csrModifiers: func() []gen.CSRModifier { + return []gen.CSRModifier{gen.SetCSRDNSNames(e2eutil.RandomSubdomain(s.DomainSuffix))} + }, kubeCSRUsages: []certificatesv1.KeyUsage{ certificatesv1.UsageDigitalSignature, certificatesv1.UsageKeyEncipherment, @@ -106,8 +113,10 @@ func (s *Suite) Define() { }, "should issue an Ed25519 certificate for a single distinct DNS Name": { - keyAlgo: x509.Ed25519, - csrModifiers: []gen.CSRModifier{gen.SetCSRDNSNames(e2eutil.RandomSubdomain(s.DomainSuffix))}, + keyAlgo: x509.Ed25519, + csrModifiers: func() []gen.CSRModifier { + return []gen.CSRModifier{gen.SetCSRDNSNames(e2eutil.RandomSubdomain(s.DomainSuffix))} + }, kubeCSRUsages: []certificatesv1.KeyUsage{ certificatesv1.UsageDigitalSignature, certificatesv1.UsageKeyEncipherment, @@ -116,8 +125,10 @@ func (s *Suite) Define() { }, "should issue an RSA certificate for a single Common Name": { - keyAlgo: x509.RSA, - csrModifiers: []gen.CSRModifier{gen.SetCSRCommonName("test-common-name-" + util.RandStringRunes(10))}, + keyAlgo: x509.RSA, + csrModifiers: func() []gen.CSRModifier { + return []gen.CSRModifier{gen.SetCSRCommonName("test-common-name-" + util.RandStringRunes(10))} + }, kubeCSRUsages: []certificatesv1.KeyUsage{ certificatesv1.UsageDigitalSignature, certificatesv1.UsageKeyEncipherment, @@ -126,8 +137,10 @@ func (s *Suite) Define() { }, "should issue an ECDSA certificate for a single Common Name": { - keyAlgo: x509.ECDSA, - csrModifiers: []gen.CSRModifier{gen.SetCSRCommonName("test-common-name-" + util.RandStringRunes(10))}, + keyAlgo: x509.ECDSA, + csrModifiers: func() []gen.CSRModifier { + return []gen.CSRModifier{gen.SetCSRCommonName("test-common-name-" + util.RandStringRunes(10))} + }, kubeCSRUsages: []certificatesv1.KeyUsage{ certificatesv1.UsageDigitalSignature, certificatesv1.UsageKeyEncipherment, @@ -136,8 +149,10 @@ func (s *Suite) Define() { }, "should issue an Ed25519 certificate for a single Common Name": { - keyAlgo: x509.Ed25519, - csrModifiers: []gen.CSRModifier{gen.SetCSRCommonName("test-common-name-" + util.RandStringRunes(10))}, + keyAlgo: x509.Ed25519, + csrModifiers: func() []gen.CSRModifier { + return []gen.CSRModifier{gen.SetCSRCommonName("test-common-name-" + util.RandStringRunes(10))} + }, kubeCSRUsages: []certificatesv1.KeyUsage{ certificatesv1.UsageDigitalSignature, certificatesv1.UsageKeyEncipherment, @@ -147,9 +162,11 @@ func (s *Suite) Define() { "should issue a certificate that defines a Common Name and IP Address": { keyAlgo: x509.RSA, - csrModifiers: []gen.CSRModifier{ - gen.SetCSRCommonName("test-common-name-" + util.RandStringRunes(10)), - gen.SetCSRIPAddresses(net.IPv4(127, 0, 0, 1), net.IPv4(8, 8, 8, 8)), + csrModifiers: func() []gen.CSRModifier { + return []gen.CSRModifier{ + gen.SetCSRCommonName("test-common-name-" + util.RandStringRunes(10)), + gen.SetCSRIPAddresses(net.IPv4(127, 0, 0, 1), net.IPv4(8, 8, 8, 8)), + } }, kubeCSRUsages: []certificatesv1.KeyUsage{ certificatesv1.UsageDigitalSignature, @@ -160,8 +177,10 @@ func (s *Suite) Define() { "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"}), + csrModifiers: func() []gen.CSRModifier { + return []gen.CSRModifier{ + gen.SetCSREmails([]string{"alice@example.com", "bob@cert-manager.io"}), + } }, kubeCSRUsages: []certificatesv1.KeyUsage{ certificatesv1.UsageDigitalSignature, @@ -172,9 +191,11 @@ func (s *Suite) Define() { "should issue a certificate that defines a Common Name and URI SAN": { keyAlgo: x509.RSA, - csrModifiers: []gen.CSRModifier{ - gen.SetCSRCommonName("test-common-name-" + util.RandStringRunes(10)), - gen.SetCSRURIs(sharedURI), + csrModifiers: func() []gen.CSRModifier { + return []gen.CSRModifier{ + gen.SetCSRCommonName("test-common-name-" + util.RandStringRunes(10)), + gen.SetCSRURIs(sharedURI), + } }, kubeCSRUsages: []certificatesv1.KeyUsage{ certificatesv1.UsageDigitalSignature, @@ -185,9 +206,11 @@ func (s *Suite) Define() { "should issue a certificate that defines a 2 distinct DNS Name with one copied to the Common Name": { keyAlgo: x509.RSA, - csrModifiers: []gen.CSRModifier{ - gen.SetCSRCommonName(sharedCommonName), - gen.SetCSRDNSNames(sharedCommonName, e2eutil.RandomSubdomain(s.DomainSuffix)), + csrModifiers: func() []gen.CSRModifier { + return []gen.CSRModifier{ + gen.SetCSRCommonName(sharedCommonName), + gen.SetCSRDNSNames(sharedCommonName, e2eutil.RandomSubdomain(s.DomainSuffix)), + } }, kubeCSRUsages: []certificatesv1.KeyUsage{ certificatesv1.UsageDigitalSignature, @@ -198,9 +221,11 @@ func (s *Suite) Define() { "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)), + csrModifiers: func() []gen.CSRModifier { + return []gen.CSRModifier{ + gen.SetCSRCommonName(e2eutil.RandomSubdomain(s.DomainSuffix)), + gen.SetCSRDNSNames(e2eutil.RandomSubdomain(s.DomainSuffix)), + } }, kubeCSRUsages: []certificatesv1.KeyUsage{ certificatesv1.UsageDigitalSignature, @@ -211,9 +236,11 @@ func (s *Suite) Define() { "should issue a certificate that defines a Common Name, DNS Name, and sets a duration": { keyAlgo: x509.RSA, - csrModifiers: []gen.CSRModifier{ - gen.SetCSRDNSNames(sharedCommonName), - gen.SetCSRDNSNames(sharedCommonName), + csrModifiers: func() []gen.CSRModifier { + return []gen.CSRModifier{ + gen.SetCSRDNSNames(sharedCommonName), + gen.SetCSRDNSNames(sharedCommonName), + } }, kubeCSRUsages: []certificatesv1.KeyUsage{ certificatesv1.UsageDigitalSignature, @@ -227,8 +254,10 @@ func (s *Suite) Define() { "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)), + csrModifiers: func() []gen.CSRModifier { + return []gen.CSRModifier{ + gen.SetCSRDNSNames(e2eutil.RandomSubdomain(s.DomainSuffix)), + } }, kubeCSRUsages: []certificatesv1.KeyUsage{ certificatesv1.UsageDigitalSignature, @@ -242,8 +271,10 @@ func (s *Suite) Define() { "should issue a certificate which has a wildcard DNS Name defined": { keyAlgo: x509.RSA, - csrModifiers: []gen.CSRModifier{ - gen.SetCSRDNSNames("*." + e2eutil.RandomSubdomain(s.DomainSuffix)), + csrModifiers: func() []gen.CSRModifier { + return []gen.CSRModifier{ + gen.SetCSRDNSNames("*." + e2eutil.RandomSubdomain(s.DomainSuffix)), + } }, kubeCSRUsages: []certificatesv1.KeyUsage{ certificatesv1.UsageDigitalSignature, @@ -254,8 +285,10 @@ func (s *Suite) Define() { "should issue a certificate that includes only a URISANs name": { keyAlgo: x509.RSA, - csrModifiers: []gen.CSRModifier{ - gen.SetCSRURIs(sharedURI), + csrModifiers: func() []gen.CSRModifier { + return []gen.CSRModifier{ + gen.SetCSRURIs(sharedURI), + } }, kubeCSRUsages: []certificatesv1.KeyUsage{ certificatesv1.UsageDigitalSignature, @@ -266,9 +299,11 @@ func (s *Suite) Define() { "should issue a certificate that includes arbitrary key usages": { keyAlgo: x509.RSA, - csrModifiers: []gen.CSRModifier{ - gen.SetCSRCommonName(sharedCommonName), - gen.SetCSRDNSNames(sharedCommonName), + csrModifiers: func() []gen.CSRModifier { + return []gen.CSRModifier{ + gen.SetCSRCommonName(sharedCommonName), + gen.SetCSRDNSNames(sharedCommonName), + } }, kubeCSRUsages: []certificatesv1.KeyUsage{ certificatesv1.UsageServerAuth, @@ -287,8 +322,10 @@ func (s *Suite) Define() { "should issue a signing CA certificate that has a large duration": { keyAlgo: x509.RSA, - csrModifiers: []gen.CSRModifier{ - gen.SetCSRCommonName("cert-manager-ca"), + csrModifiers: func() []gen.CSRModifier { + return []gen.CSRModifier{ + gen.SetCSRCommonName("cert-manager-ca"), + } }, kubeCSRUsages: []certificatesv1.KeyUsage{ certificatesv1.UsageDigitalSignature, @@ -306,7 +343,7 @@ func (s *Suite) Define() { defineTest := func(name string, test testCase) { s.it(f, name, func(signerName string) { // Generate request CSR - csr, key, err := gen.CSR(test.keyAlgo, test.csrModifiers...) + csr, key, err := gen.CSR(test.keyAlgo, test.csrModifiers()...) Expect(err).NotTo(HaveOccurred()) // Create CertificateSigningRequest diff --git a/test/e2e/suite/conformance/certificatesigningrequests/venafi/BUILD.bazel b/test/e2e/suite/conformance/certificatesigningrequests/venafi/BUILD.bazel new file mode 100644 index 000000000..fef80de28 --- /dev/null +++ b/test/e2e/suite/conformance/certificatesigningrequests/venafi/BUILD.bazel @@ -0,0 +1,37 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = [ + "cloud.go", + "tpp.go", + ], + importpath = "github.com/jetstack/cert-manager/test/e2e/suite/conformance/certificatesigningrequests/venafi", + visibility = ["//visibility:public"], + deps = [ + "//pkg/controller/certificatesigningrequests/util:go_default_library", + "//pkg/util:go_default_library", + "//test/e2e/framework:go_default_library", + "//test/e2e/framework/addon/venafi:go_default_library", + "//test/e2e/framework/helper/featureset:go_default_library", + "//test/e2e/framework/util/errors:go_default_library", + "//test/e2e/suite/conformance/certificatesigningrequests: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/certificatesigningrequests/venafi/cloud.go b/test/e2e/suite/conformance/certificatesigningrequests/venafi/cloud.go new file mode 100644 index 000000000..c56fc6352 --- /dev/null +++ b/test/e2e/suite/conformance/certificatesigningrequests/venafi/cloud.go @@ -0,0 +1,133 @@ +/* +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 venafi + +import ( + "context" + "fmt" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/jetstack/cert-manager/pkg/controller/certificatesigningrequests/util" + "github.com/jetstack/cert-manager/test/e2e/framework" + "github.com/jetstack/cert-manager/test/e2e/framework/addon/venafi" + "github.com/jetstack/cert-manager/test/e2e/framework/helper/featureset" + "github.com/jetstack/cert-manager/test/e2e/framework/util/errors" + "github.com/jetstack/cert-manager/test/e2e/suite/conformance/certificatesigningrequests" +) + +var _ = framework.ConformanceDescribe("CertificateSigningRequests", func() { + // unsupportedFeatures is a list of features that are not supported by the + // Venafi TPP issuer. + var unsupportedFeatures = featureset.NewFeatureSet( + // Venafi TPP doesn't allow setting a duration + featureset.DurationFeature, + // Due to the current configuration of the test environment, it does not + // support signing certificates that pair with an elliptic curve or + // Ed255119 private keys + featureset.ECDSAFeature, + featureset.Ed25519FeatureSet, + // Our Venafi TPP doesn't allow setting non DNS SANs + // TODO: investigate options to enable these + featureset.EmailSANsFeature, + featureset.URISANsFeature, + featureset.IPAddressFeature, + // Venafi doesn't allow certs with empty CN & DN + featureset.OnlySAN, + // Venafi doesn't setting key usages. + featureset.KeyUsagesFeature, + ) + + venafiIssuer := new(cloud) + (&certificatesigningrequests.Suite{ + Name: "Venafi Cloud Issuer", + CreateIssuerFunc: venafiIssuer.createIssuer, + DeleteIssuerFunc: venafiIssuer.delete, + UnsupportedFeatures: unsupportedFeatures, + }).Define() + + venafiClusterIssuer := new(cloud) + (&certificatesigningrequests.Suite{ + Name: "Venafi Cloud Cluster Issuer", + CreateIssuerFunc: venafiClusterIssuer.createClusterIssuer, + DeleteIssuerFunc: venafiClusterIssuer.delete, + UnsupportedFeatures: unsupportedFeatures, + }).Define() +}) + +type cloud struct { + *venafi.VenafiCloud +} + +func (c *cloud) delete(f *framework.Framework, signerName string) { + Expect(c.Deprovision()).NotTo(HaveOccurred(), "failed to deprovision cloud venafi") + + ref, _ := util.SignerIssuerRefFromSignerName(signerName) + if ref.Type == "clusterissuers" { + err := f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Delete(context.TODO(), ref.Name, metav1.DeleteOptions{}) + Expect(err).NotTo(HaveOccurred()) + } +} + +func (c *cloud) createIssuer(f *framework.Framework) string { + By("Creating a Venafi Cloud Issuer") + + c.VenafiCloud = &venafi.VenafiCloud{ + Namespace: f.Namespace.Name, + } + + err := c.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 provision venafi cloud issuer") + + Expect(c.Provision()).NotTo(HaveOccurred(), "failed to provision tpp venafi") + + issuer := c.Details().BuildIssuer() + issuer, err = f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), issuer, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred(), "failed to create issuer for venafi") + + return fmt.Sprintf("issuers.cert-manager.io/%s.%s", issuer.Namespace, issuer.Name) +} + +// createClusterIssuer creates and returns name of a Venafi Cloud +// ClusterIssuer. The name is of the form +// "clusterissuers.cert-manager.io/issuer-ab3de1". +func (c *cloud) createClusterIssuer(f *framework.Framework) string { + By("Creating a Venafi Cloud ClusterIssuer") + + c.VenafiCloud = &venafi.VenafiCloud{ + Namespace: f.Config.Addons.CertManager.ClusterResourceNamespace, + } + + err := c.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(c.Provision()).NotTo(HaveOccurred(), "failed to provision tpp venafi") + + issuer := c.Details().BuildClusterIssuer() + issuer, err = f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Create(context.TODO(), issuer, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred(), "failed to create issuer for venafi") + + return fmt.Sprintf("clusterissuers.cert-manager.io/%s", issuer.Name) +} diff --git a/test/e2e/suite/conformance/certificatesigningrequests/venafi/tpp.go b/test/e2e/suite/conformance/certificatesigningrequests/venafi/tpp.go new file mode 100644 index 000000000..353b55a3a --- /dev/null +++ b/test/e2e/suite/conformance/certificatesigningrequests/venafi/tpp.go @@ -0,0 +1,133 @@ +/* +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 venafi + +import ( + "context" + "fmt" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/jetstack/cert-manager/pkg/controller/certificatesigningrequests/util" + cmutil "github.com/jetstack/cert-manager/pkg/util" + "github.com/jetstack/cert-manager/test/e2e/framework" + "github.com/jetstack/cert-manager/test/e2e/framework/addon/venafi" + "github.com/jetstack/cert-manager/test/e2e/framework/helper/featureset" + "github.com/jetstack/cert-manager/test/e2e/framework/util/errors" + "github.com/jetstack/cert-manager/test/e2e/suite/conformance/certificatesigningrequests" +) + +var _ = framework.ConformanceDescribe("CertificateSigningRequests", func() { + // unsupportedFeatures is a list of features that are not supported by the + // Venafi TPP issuer. + var unsupportedFeatures = featureset.NewFeatureSet( + // Venafi TPP doesn't allow setting a duration + featureset.DurationFeature, + // Due to the current configuration of the test environment, it does not + // support signing certificates that pair with an elliptic curve or + // Ed255119 private keys + featureset.ECDSAFeature, + featureset.Ed25519FeatureSet, + // Our Venafi TPP doesn't allow setting non DNS SANs + // TODO: investigate options to enable these + featureset.EmailSANsFeature, + featureset.URISANsFeature, + featureset.IPAddressFeature, + // Venafi doesn't allow certs with empty CN & DN + featureset.OnlySAN, + // Venafi doesn't setting key usages. + featureset.KeyUsagesFeature, + ) + + venafiIssuer := new(tpp) + (&certificatesigningrequests.Suite{ + Name: "Venafi TPP Issuer", + CreateIssuerFunc: venafiIssuer.createIssuer, + DeleteIssuerFunc: venafiIssuer.delete, + UnsupportedFeatures: unsupportedFeatures, + DomainSuffix: fmt.Sprintf("%s-venafi-e2e", cmutil.RandStringRunes(5)), + }).Define() + + venafiClusterIssuer := new(tpp) + (&certificatesigningrequests.Suite{ + Name: "Venafi TPP Cluster Issuer", + CreateIssuerFunc: venafiClusterIssuer.createClusterIssuer, + DeleteIssuerFunc: venafiClusterIssuer.delete, + UnsupportedFeatures: unsupportedFeatures, + DomainSuffix: fmt.Sprintf("%s-venafi-e2e", cmutil.RandStringRunes(5)), + }).Define() +}) + +type tpp struct { + *venafi.VenafiTPP +} + +func (t *tpp) delete(f *framework.Framework, signerName string) { + Expect(t.Deprovision()).NotTo(HaveOccurred(), "failed to deprovision tpp venafi") + ref, _ := util.SignerIssuerRefFromSignerName(signerName) + + if ref.Type == "clusterissuers" { + err := f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Delete(context.TODO(), ref.Name, metav1.DeleteOptions{}) + Expect(err).NotTo(HaveOccurred()) + } +} + +func (t *tpp) createIssuer(f *framework.Framework) string { + By("Creating a Venafi Issuer") + + t.VenafiTPP = &venafi.VenafiTPP{ + Namespace: f.Namespace.Name, + } + + err := t.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(t.Provision()).NotTo(HaveOccurred(), "failed to provision tpp venafi") + + issuer := t.Details().BuildIssuer() + issuer, err = f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), issuer, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred(), "failed to create issuer for venafi") + + return fmt.Sprintf("issuers.cert-manager.io/%s.%s", issuer.Namespace, issuer.Name) +} + +func (t *tpp) createClusterIssuer(f *framework.Framework) string { + By("Creating a Venafi ClusterIssuer") + + t.VenafiTPP = &venafi.VenafiTPP{ + Namespace: f.Config.Addons.CertManager.ClusterResourceNamespace, + } + + err := t.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(t.Provision()).NotTo(HaveOccurred(), "failed to provision tpp venafi") + + issuer := t.Details().BuildClusterIssuer() + issuer, err = f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Create(context.TODO(), issuer, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred(), "failed to create issuer for venafi") + + return fmt.Sprintf("clusterissuers.cert-manager.io/%s", issuer.Name) +} diff --git a/test/e2e/suite/conformance/import.go b/test/e2e/suite/conformance/import.go index 61b61b9af..964c572dc 100644 --- a/test/e2e/suite/conformance/import.go +++ b/test/e2e/suite/conformance/import.go @@ -27,5 +27,6 @@ import ( _ "github.com/jetstack/cert-manager/test/e2e/suite/conformance/certificatesigningrequests/ca" _ "github.com/jetstack/cert-manager/test/e2e/suite/conformance/certificatesigningrequests/selfsigned" _ "github.com/jetstack/cert-manager/test/e2e/suite/conformance/certificatesigningrequests/vault" + _ "github.com/jetstack/cert-manager/test/e2e/suite/conformance/certificatesigningrequests/venafi" _ "github.com/jetstack/cert-manager/test/e2e/suite/conformance/rbac" ) diff --git a/test/e2e/suite/issuers/venafi/BUILD.bazel b/test/e2e/suite/issuers/venafi/BUILD.bazel index 55dbd9eee..d6136ba37 100644 --- a/test/e2e/suite/issuers/venafi/BUILD.bazel +++ b/test/e2e/suite/issuers/venafi/BUILD.bazel @@ -19,7 +19,6 @@ filegroup( name = "all-srcs", srcs = [ ":package-srcs", - "//test/e2e/suite/issuers/venafi/addon:all-srcs", "//test/e2e/suite/issuers/venafi/tpp:all-srcs", ], tags = ["automanaged"], diff --git a/test/e2e/suite/issuers/venafi/tpp/BUILD.bazel b/test/e2e/suite/issuers/venafi/tpp/BUILD.bazel index 3900c5983..030b7e18e 100644 --- a/test/e2e/suite/issuers/venafi/tpp/BUILD.bazel +++ b/test/e2e/suite/issuers/venafi/tpp/BUILD.bazel @@ -15,7 +15,7 @@ go_library( "//pkg/apis/meta/v1:go_default_library", "//pkg/util:go_default_library", "//test/e2e/framework:go_default_library", - "//test/e2e/suite/issuers/venafi/addon:go_default_library", + "//test/e2e/framework/addon/venafi:go_default_library", "//test/e2e/util:go_default_library", "@com_github_onsi_ginkgo//:go_default_library", "@com_github_onsi_gomega//:go_default_library", diff --git a/test/e2e/suite/issuers/venafi/tpp/certificate.go b/test/e2e/suite/issuers/venafi/tpp/certificate.go index 55d818fa6..a58f8b1d1 100644 --- a/test/e2e/suite/issuers/venafi/tpp/certificate.go +++ b/test/e2e/suite/issuers/venafi/tpp/certificate.go @@ -28,7 +28,7 @@ import ( cmmeta "github.com/jetstack/cert-manager/pkg/apis/meta/v1" cmutil "github.com/jetstack/cert-manager/pkg/util" "github.com/jetstack/cert-manager/test/e2e/framework" - vaddon "github.com/jetstack/cert-manager/test/e2e/suite/issuers/venafi/addon" + vaddon "github.com/jetstack/cert-manager/test/e2e/framework/addon/venafi" "github.com/jetstack/cert-manager/test/e2e/util" ) diff --git a/test/e2e/suite/issuers/venafi/tpp/certificaterequest.go b/test/e2e/suite/issuers/venafi/tpp/certificaterequest.go index 31c074b0e..bec3a00be 100644 --- a/test/e2e/suite/issuers/venafi/tpp/certificaterequest.go +++ b/test/e2e/suite/issuers/venafi/tpp/certificaterequest.go @@ -29,7 +29,7 @@ import ( cmmeta "github.com/jetstack/cert-manager/pkg/apis/meta/v1" cmutil "github.com/jetstack/cert-manager/pkg/util" "github.com/jetstack/cert-manager/test/e2e/framework" - vaddon "github.com/jetstack/cert-manager/test/e2e/suite/issuers/venafi/addon" + vaddon "github.com/jetstack/cert-manager/test/e2e/framework/addon/venafi" "github.com/jetstack/cert-manager/test/e2e/util" )