From 2280480c021cd884681afd2e46fc073249b10232 Mon Sep 17 00:00:00 2001 From: James Munnelly Date: Wed, 1 Jul 2020 11:41:34 +0100 Subject: [PATCH] Remove old certificates controller Signed-off-by: James Munnelly --- cmd/controller/app/BUILD.bazel | 7 - cmd/controller/app/controller.go | 32 - cmd/controller/app/options/BUILD.bazel | 6 +- cmd/controller/app/options/options.go | 14 +- cmd/controller/app/start.go | 1 - pkg/controller/certificates/BUILD.bazel | 71 - pkg/controller/certificates/checks.go | 78 - pkg/controller/certificates/controller.go | 158 -- pkg/controller/certificates/keystore.go | 162 -- pkg/controller/certificates/keystore_test.go | 225 -- pkg/controller/certificates/sync.go | 919 ------- pkg/controller/certificates/sync_test.go | 2333 ------------------ pkg/controller/certificates/util.go | 261 -- pkg/controller/certificates/util_test.go | 285 --- pkg/feature/features.go | 9 +- 15 files changed, 17 insertions(+), 4544 deletions(-) delete mode 100644 pkg/controller/certificates/checks.go delete mode 100644 pkg/controller/certificates/controller.go delete mode 100644 pkg/controller/certificates/keystore.go delete mode 100644 pkg/controller/certificates/keystore_test.go delete mode 100644 pkg/controller/certificates/sync.go delete mode 100644 pkg/controller/certificates/sync_test.go delete mode 100644 pkg/controller/certificates/util.go delete mode 100644 pkg/controller/certificates/util_test.go diff --git a/cmd/controller/app/BUILD.bazel b/cmd/controller/app/BUILD.bazel index 539f18fea..03233cfe7 100644 --- a/cmd/controller/app/BUILD.bazel +++ b/cmd/controller/app/BUILD.bazel @@ -17,16 +17,10 @@ go_library( "//pkg/controller:go_default_library", "//pkg/controller/acmechallenges:go_default_library", "//pkg/controller/acmeorders:go_default_library", - "//pkg/controller/certificates:go_default_library", "//pkg/controller/clusterissuers:go_default_library", - "//pkg/controller/expcertificates/issuing:go_default_library", - "//pkg/controller/expcertificates/keymanager:go_default_library", - "//pkg/controller/expcertificates/readiness:go_default_library", - "//pkg/controller/expcertificates/requestmanager:go_default_library", "//pkg/controller/expcertificates/trigger:go_default_library", "//pkg/controller/ingress-shim:go_default_library", "//pkg/controller/issuers:go_default_library", - "//pkg/feature:go_default_library", "//pkg/issuer/acme:go_default_library", "//pkg/issuer/acme/dns/util:go_default_library", "//pkg/issuer/ca:go_default_library", @@ -42,7 +36,6 @@ go_library( "@io_k8s_apimachinery//pkg/api/resource:go_default_library", "@io_k8s_apimachinery//pkg/apis/meta/v1:go_default_library", "@io_k8s_apimachinery//pkg/util/errors:go_default_library", - "@io_k8s_apimachinery//pkg/util/sets:go_default_library", "@io_k8s_client_go//informers:go_default_library", "@io_k8s_client_go//kubernetes:go_default_library", "@io_k8s_client_go//kubernetes/scheme:go_default_library", diff --git a/cmd/controller/app/controller.go b/cmd/controller/app/controller.go index 8d8d78b24..c10a9eaba 100644 --- a/cmd/controller/app/controller.go +++ b/cmd/controller/app/controller.go @@ -26,7 +26,6 @@ import ( v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/sets" kubeinformers "k8s.io/client-go/informers" "k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes/scheme" @@ -45,19 +44,11 @@ import ( intscheme "github.com/jetstack/cert-manager/pkg/client/clientset/versioned/scheme" informers "github.com/jetstack/cert-manager/pkg/client/informers/externalversions" "github.com/jetstack/cert-manager/pkg/controller" - "github.com/jetstack/cert-manager/pkg/controller/certificates" "github.com/jetstack/cert-manager/pkg/controller/clusterissuers" - "github.com/jetstack/cert-manager/pkg/controller/expcertificates/issuing" - "github.com/jetstack/cert-manager/pkg/controller/expcertificates/keymanager" - "github.com/jetstack/cert-manager/pkg/controller/expcertificates/readiness" - "github.com/jetstack/cert-manager/pkg/controller/expcertificates/requestmanager" - "github.com/jetstack/cert-manager/pkg/controller/expcertificates/trigger" - "github.com/jetstack/cert-manager/pkg/feature" dnsutil "github.com/jetstack/cert-manager/pkg/issuer/acme/dns/util" logf "github.com/jetstack/cert-manager/pkg/logs" "github.com/jetstack/cert-manager/pkg/metrics" "github.com/jetstack/cert-manager/pkg/util" - utilfeature "github.com/jetstack/cert-manager/pkg/util/feature" ) const controllerAgentName = "cert-manager" @@ -81,29 +72,6 @@ func Run(opts *options.ControllerOptions, stopCh <-chan struct{}) { } var wg sync.WaitGroup - var experimentalCertificateControllers = []string{ - trigger.ControllerName, - issuing.ControllerName, - keymanager.ControllerName, - requestmanager.ControllerName, - readiness.ControllerName, - } - enabledSet := sets.NewString(opts.EnabledControllers...) - if utilfeature.DefaultFeatureGate.Enabled(feature.ExperimentalCertificateControllers) { - if enabledSet.Has(certificates.ControllerName) { - log.Info("Disabling old certificates controller") - enabledSet.Delete(certificates.ControllerName) - } - log.Info("Enabling all experimental certificates controllers") - enabledSet.Insert(experimentalCertificateControllers...) - opts.EnabledControllers = enabledSet.List() - } else { - if enabledSet.HasAny(experimentalCertificateControllers...) { - err := fmt.Sprintf("Enable %s feature gate to use these controllers", feature.ExperimentalCertificateControllers) - log.Info(err, "controllers", enabledSet.Intersection(sets.NewString(experimentalCertificateControllers...)).List()) - os.Exit(1) - } - } run := func(_ context.Context) { for n, fn := range controller.Known() { log := log.WithValues("controller", n) diff --git a/cmd/controller/app/options/BUILD.bazel b/cmd/controller/app/options/BUILD.bazel index 4a94788e8..8d35a0425 100644 --- a/cmd/controller/app/options/BUILD.bazel +++ b/cmd/controller/app/options/BUILD.bazel @@ -15,9 +15,13 @@ go_library( "//pkg/controller/certificaterequests/selfsigned:go_default_library", "//pkg/controller/certificaterequests/vault:go_default_library", "//pkg/controller/certificaterequests/venafi:go_default_library", - "//pkg/controller/certificates:go_default_library", "//pkg/controller/certificates/metrics:go_default_library", "//pkg/controller/clusterissuers:go_default_library", + "//pkg/controller/expcertificates/issuing:go_default_library", + "//pkg/controller/expcertificates/keymanager:go_default_library", + "//pkg/controller/expcertificates/readiness:go_default_library", + "//pkg/controller/expcertificates/requestmanager:go_default_library", + "//pkg/controller/expcertificates/trigger:go_default_library", "//pkg/controller/ingress-shim:go_default_library", "//pkg/controller/issuers:go_default_library", "//pkg/util:go_default_library", diff --git a/cmd/controller/app/options/options.go b/cmd/controller/app/options/options.go index 8d61f6d6a..891998280 100644 --- a/cmd/controller/app/options/options.go +++ b/cmd/controller/app/options/options.go @@ -32,9 +32,13 @@ import ( crselfsignedcontroller "github.com/jetstack/cert-manager/pkg/controller/certificaterequests/selfsigned" crvaultcontroller "github.com/jetstack/cert-manager/pkg/controller/certificaterequests/vault" crvenaficontroller "github.com/jetstack/cert-manager/pkg/controller/certificaterequests/venafi" - certificatescontroller "github.com/jetstack/cert-manager/pkg/controller/certificates" certificatesmetricscontroller "github.com/jetstack/cert-manager/pkg/controller/certificates/metrics" clusterissuerscontroller "github.com/jetstack/cert-manager/pkg/controller/clusterissuers" + "github.com/jetstack/cert-manager/pkg/controller/expcertificates/issuing" + "github.com/jetstack/cert-manager/pkg/controller/expcertificates/keymanager" + "github.com/jetstack/cert-manager/pkg/controller/expcertificates/readiness" + "github.com/jetstack/cert-manager/pkg/controller/expcertificates/requestmanager" + "github.com/jetstack/cert-manager/pkg/controller/expcertificates/trigger" ingressshimcontroller "github.com/jetstack/cert-manager/pkg/controller/ingress-shim" issuerscontroller "github.com/jetstack/cert-manager/pkg/controller/issuers" "github.com/jetstack/cert-manager/pkg/util" @@ -125,7 +129,6 @@ var ( defaultEnabledControllers = []string{ issuerscontroller.ControllerName, clusterissuerscontroller.ControllerName, - certificatescontroller.ControllerName, certificatesmetricscontroller.ControllerName, ingressshimcontroller.ControllerName, orderscontroller.ControllerName, @@ -135,7 +138,12 @@ var ( crselfsignedcontroller.CRControllerName, crvaultcontroller.CRControllerName, crvenaficontroller.CRControllerName, - certificatescontroller.ControllerName, + // certificate controllers + trigger.ControllerName, + issuing.ControllerName, + keymanager.ControllerName, + requestmanager.ControllerName, + readiness.ControllerName, } ) diff --git a/cmd/controller/app/start.go b/cmd/controller/app/start.go index f5b05b6be..af1dd5d69 100644 --- a/cmd/controller/app/start.go +++ b/cmd/controller/app/start.go @@ -26,7 +26,6 @@ import ( "github.com/jetstack/cert-manager/cmd/controller/app/options" _ "github.com/jetstack/cert-manager/pkg/controller/acmechallenges" _ "github.com/jetstack/cert-manager/pkg/controller/acmeorders" - _ "github.com/jetstack/cert-manager/pkg/controller/certificates" _ "github.com/jetstack/cert-manager/pkg/controller/clusterissuers" _ "github.com/jetstack/cert-manager/pkg/controller/expcertificates/trigger" _ "github.com/jetstack/cert-manager/pkg/controller/ingress-shim" diff --git a/pkg/controller/certificates/BUILD.bazel b/pkg/controller/certificates/BUILD.bazel index 83b739592..2d47624a1 100644 --- a/pkg/controller/certificates/BUILD.bazel +++ b/pkg/controller/certificates/BUILD.bazel @@ -1,74 +1,3 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") - -go_library( - name = "go_default_library", - srcs = [ - "checks.go", - "controller.go", - "keystore.go", - "sync.go", - "util.go", - ], - importpath = "github.com/jetstack/cert-manager/pkg/controller/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/client/clientset/versioned:go_default_library", - "//pkg/client/listers/certmanager/v1alpha2:go_default_library", - "//pkg/controller:go_default_library", - "//pkg/logs:go_default_library", - "//pkg/scheduler:go_default_library", - "//pkg/util:go_default_library", - "//pkg/util/errors:go_default_library", - "//pkg/util/kube:go_default_library", - "//pkg/util/pki:go_default_library", - "@com_github_go_logr_logr//:go_default_library", - "@com_github_kr_pretty//:go_default_library", - "@com_github_pavel_v_chernykh_keystore_go//:go_default_library", - "@com_sslmate_software_src_go_pkcs12//: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/labels:go_default_library", - "@io_k8s_apimachinery//pkg/util/errors:go_default_library", - "@io_k8s_client_go//kubernetes:go_default_library", - "@io_k8s_client_go//listers/core/v1:go_default_library", - "@io_k8s_client_go//tools/cache:go_default_library", - "@io_k8s_client_go//tools/record:go_default_library", - "@io_k8s_client_go//util/workqueue:go_default_library", - "@io_k8s_utils//clock:go_default_library", - ], -) - -go_test( - name = "go_default_test", - srcs = [ - "keystore_test.go", - "sync_test.go", - "util_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:go_default_library", - "//pkg/controller/test:go_default_library", - "//pkg/util:go_default_library", - "//pkg/util/pki:go_default_library", - "//test/unit/gen:go_default_library", - "@com_github_pavel_v_chernykh_keystore_go//:go_default_library", - "@com_sslmate_software_src_go_pkcs12//: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(["**"]), diff --git a/pkg/controller/certificates/checks.go b/pkg/controller/certificates/checks.go deleted file mode 100644 index 2c96baa2f..000000000 --- a/pkg/controller/certificates/checks.go +++ /dev/null @@ -1,78 +0,0 @@ -/* -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 certificates - -import ( - "fmt" - - "github.com/go-logr/logr" - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/client-go/util/workqueue" - - cmapi "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha2" - cmlisters "github.com/jetstack/cert-manager/pkg/client/listers/certmanager/v1alpha2" - logf "github.com/jetstack/cert-manager/pkg/logs" -) - -func secretResourceHandler(log logr.Logger, certificateLister cmlisters.CertificateLister, queue workqueue.Interface) func(obj interface{}) { - return func(obj interface{}) { - log := log.WithName("handleSecretResource") - - secret, ok := obj.(*corev1.Secret) - if !ok { - log.Error(nil, "object is not a Secret resource") - return - } - log = logf.WithResource(log, secret) - - crts, err := certificatesForSecret(certificateLister, secret) - if err != nil { - log.Error(err, "error looking up Certificates observing Secret") - return - } - for _, crt := range crts { - log := logf.WithRelatedResource(log, crt) - key, err := keyFunc(crt) - if err != nil { - log.Error(err, "error computing key for resource") - continue - } - queue.Add(key) - } - } -} - -func certificatesForSecret(certificateLister cmlisters.CertificateLister, secret *corev1.Secret) ([]*cmapi.Certificate, error) { - crts, err := certificateLister.List(labels.NewSelector()) - - if err != nil { - return nil, fmt.Errorf("error listing certificates: %s", err.Error()) - } - - var affected []*cmapi.Certificate - for _, crt := range crts { - if crt.Namespace != secret.Namespace { - continue - } - if crt.Spec.SecretName == secret.Name { - affected = append(affected, crt) - } - } - - return affected, nil -} diff --git a/pkg/controller/certificates/controller.go b/pkg/controller/certificates/controller.go deleted file mode 100644 index 000de0acf..000000000 --- a/pkg/controller/certificates/controller.go +++ /dev/null @@ -1,158 +0,0 @@ -/* -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 certificates - -import ( - "context" - "crypto/x509" - "time" - - "k8s.io/client-go/kubernetes" - corelisters "k8s.io/client-go/listers/core/v1" - "k8s.io/client-go/tools/cache" - "k8s.io/client-go/tools/record" - "k8s.io/client-go/util/workqueue" - "k8s.io/utils/clock" - - cmapi "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha2" - cmclient "github.com/jetstack/cert-manager/pkg/client/clientset/versioned" - cmlisters "github.com/jetstack/cert-manager/pkg/client/listers/certmanager/v1alpha2" - controllerpkg "github.com/jetstack/cert-manager/pkg/controller" - logf "github.com/jetstack/cert-manager/pkg/logs" - "github.com/jetstack/cert-manager/pkg/scheduler" -) - -// certificateRequestManager manages CertificateRequest resources for a -// Certificate in order to obtain signed certs. -type certificateRequestManager struct { - certificateLister cmlisters.CertificateLister - secretLister corelisters.SecretLister - certificateRequestLister cmlisters.CertificateRequestLister - - kubeClient kubernetes.Interface - cmClient cmclient.Interface - - // maintain a reference to the workqueue for this controller - // so the handleOwnedResource method can enqueue resources - queue workqueue.RateLimitingInterface - scheduledWorkQueue scheduler.ScheduledWorkQueue - - // used to record Events about resources to the API - recorder record.EventRecorder - - // used for testing - clock clock.Clock - - // defined as a field to make it easy to stub out for testing purposes - generatePrivateKeyBytes generatePrivateKeyBytesFn - generateCSR generateCSRFn - - // certificateNeedsRenew is a function that can be used to determine whether - // a certificate currently requires renewal. - // This is a field on the controller struct to avoid having to maintain a reference - // to the controller context, and to make it easier to fake out this call during tests. - certificateNeedsRenew func(ctx context.Context, cert *x509.Certificate, crt *cmapi.Certificate) bool - - // calculateDurationUntilRenew returns the amount of time before the controller should - // begin attempting to renew the certificate, given the provided existing certificate - // and certificate spec. - // This is a field on the controller struct to avoid having to maintain a reference - // to the controller context, and to make it easier to fake out this call during tests. - calculateDurationUntilRenew calculateDurationUntilRenewFn - - // localTemporarySigner signs a certificate that is stored temporarily - localTemporarySigner localTemporarySignerFn - - // if true, Secret resources created by the controller will have an - // 'owner reference' set, meaning when the Certificate is deleted, the - // Secret resource will be automatically deleted. - // This option is disabled by default. - enableSecretOwnerReferences bool -} - -type localTemporarySignerFn func(crt *cmapi.Certificate, pk []byte) ([]byte, error) - -// Register registers and constructs the controller using the provided context. -// It returns the workqueue to be used to enqueue items, a list of -// InformerSynced functions that must be synced, or an error. -func (c *certificateRequestManager) Register(ctx *controllerpkg.Context) (workqueue.RateLimitingInterface, []cache.InformerSynced, error) { - // construct a new named logger to be reused throughout the controller - log := logf.FromContext(ctx.RootContext, ControllerName) - - // create a queue used to queue up items to be processed - c.queue = workqueue.NewNamedRateLimitingQueue(workqueue.NewItemExponentialFailureRateLimiter(time.Second*5, time.Minute*30), ControllerName) - - // obtain references to all the informers used by this controller - certificateInformer := ctx.SharedInformerFactory.Certmanager().V1alpha2().Certificates() - certificateRequestInformer := ctx.SharedInformerFactory.Certmanager().V1alpha2().CertificateRequests() - secretsInformer := ctx.KubeSharedInformerFactory.Core().V1().Secrets() - - // build a list of InformerSynced functions that will be returned by the Register method. - // the controller will only begin processing items once all of these informers have synced. - mustSync := []cache.InformerSynced{ - certificateRequestInformer.Informer().HasSynced, - secretsInformer.Informer().HasSynced, - certificateInformer.Informer().HasSynced, - } - - // set all the references to the listers for used by the Sync function - c.certificateRequestLister = certificateRequestInformer.Lister() - c.secretLister = secretsInformer.Lister() - c.certificateLister = certificateInformer.Lister() - - // register handler functions - certificateInformer.Informer().AddEventHandler(&controllerpkg.QueuingEventHandler{Queue: c.queue}) - certificateRequestInformer.Informer().AddEventHandler(&controllerpkg.BlockingEventHandler{WorkFunc: controllerpkg.HandleOwnedResourceNamespacedFunc(log, c.queue, certificateGvk, certificateGetter(c.certificateLister))}) - secretsInformer.Informer().AddEventHandler(&controllerpkg.BlockingEventHandler{WorkFunc: secretResourceHandler(log, c.certificateLister, c.queue)}) - - // clock is used to determine whether certificates need renewal - c.clock = ctx.Clock - - // Create a scheduled work queue that calls the ctrl.queue.Add method for - // each object in the queue. This is used to schedule re-checks of - // Certificate resources when they get near to expiry - c.scheduledWorkQueue = scheduler.NewScheduledWorkQueue(c.clock, c.queue.Add) - - // recorder records events about resources to the Kubernetes api - c.recorder = ctx.Recorder - - c.certificateNeedsRenew = ctx.IssuerOptions.CertificateNeedsRenew - c.calculateDurationUntilRenew = ctx.IssuerOptions.CalculateDurationUntilRenew - c.generatePrivateKeyBytes = generatePrivateKeyBytesImpl - c.generateCSR = generateCSRImpl - // the localTemporarySigner is used to sign 'temporary certificates' during - // asynchronous certificate issuance flows - c.localTemporarySigner = generateLocallySignedTemporaryCertificate - c.enableSecretOwnerReferences = ctx.CertificateOptions.EnableOwnerRef - - c.cmClient = ctx.CMClient - c.kubeClient = ctx.Client - - return c.queue, mustSync, nil -} - -const ( - ControllerName = "certificates" -) - -func init() { - controllerpkg.Register(ControllerName, func(ctx *controllerpkg.Context) (controllerpkg.Interface, error) { - return controllerpkg.NewBuilder(ctx, ControllerName). - For(&certificateRequestManager{}). - Complete() - }) -} diff --git a/pkg/controller/certificates/keystore.go b/pkg/controller/certificates/keystore.go deleted file mode 100644 index c5621df3a..000000000 --- a/pkg/controller/certificates/keystore.go +++ /dev/null @@ -1,162 +0,0 @@ -/* -Copyright 2020 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. -*/ - -// This file defines methods used for PKCS#12 support. -// This is an experimental feature and the contents of this file are intended -// to be absorbed into a more fully fledged implementing ahead of the v0.15 -// release. -// This should hopefully not exist by the next time you come to read this :) - -package certificates - -import ( - "bytes" - "crypto/rand" - "crypto/x509" - "time" - - jks "github.com/pavel-v-chernykh/keystore-go" - "software.sslmate.com/src/go-pkcs12" - - "github.com/jetstack/cert-manager/pkg/util/pki" -) - -const ( - // pkcs12SecretKey is the name of the data entry in the Secret resource - // used to store the p12 file. - pkcs12SecretKey = "keystore.p12" - - // jksSecretKey is the name of the data entry in the Secret resource - // used to store the jks file. - jksSecretKey = "keystore.jks" - jksTruststoreKey = "truststore.jks" -) - -// encodePKCS12Keystore will encode a PKCS12 keystore using the password provided. -// The key, certificate and CA data must be provided in PKCS1 or PKCS8 PEM format. -// If the certificate data contains multiple certificates, the first will be used -// as the keystores 'certificate' and the remaining certificates will be prepended -// to the list of CAs in the resulting keystore. -func encodePKCS12Keystore(password string, rawKey []byte, certPem []byte, caPem []byte) ([]byte, error) { - key, err := pki.DecodePrivateKeyBytes(rawKey) - if err != nil { - return nil, err - } - certs, err := pki.DecodeX509CertificateChainBytes(certPem) - if err != nil { - return nil, err - } - var cas []*x509.Certificate - if len(caPem) > 0 { - cas, err = pki.DecodeX509CertificateChainBytes(caPem) - if err != nil { - return nil, err - } - // prepend the certificate chain to the list of certificates as the PKCS12 - // library only allows setting a single certificate. - if len(certs) > 1 { - cas = append(certs[1:], cas...) - } - } - keystoreData, err := pkcs12.Encode(rand.Reader, key, certs[0], cas, password) - if err != nil { - return nil, err - } - return keystoreData, nil -} - -func encodeJKSKeystore(password []byte, rawKey []byte, certPem []byte, caPem []byte) ([]byte, error) { - // encode the private key to PKCS8 - key, err := pki.DecodePrivateKeyBytes(rawKey) - if err != nil { - return nil, err - } - keyDER, err := x509.MarshalPKCS8PrivateKey(key) - if err != nil { - return nil, err - } - - // encode the certificate chain - chain, err := pki.DecodeX509CertificateChainBytes(certPem) - if err != nil { - return nil, err - } - certs := make([]jks.Certificate, len(chain)) - for i, cert := range chain { - certs[i] = jks.Certificate{ - Type: "X509", - Content: cert.Raw, - } - } - - ks := jks.KeyStore{ - "certificate": &jks.PrivateKeyEntry{ - Entry: jks.Entry{ - CreationDate: time.Now(), - }, - PrivKey: keyDER, - CertChain: certs, - }, - } - // add the CA certificate, if set - if len(caPem) > 0 { - ca, err := pki.DecodeX509CertificateBytes(caPem) - if err != nil { - return nil, err - } - - ks["ca"] = &jks.TrustedCertificateEntry{ - Entry: jks.Entry{ - CreationDate: time.Now(), - }, - Certificate: jks.Certificate{ - Type: "X509", - Content: ca.Raw, - }, - } - } - - buf := &bytes.Buffer{} - if err := jks.Encode(buf, ks, password); err != nil { - return nil, err - } - return buf.Bytes(), nil -} - -func encodeJKSTruststore(password []byte, caPem []byte) ([]byte, error) { - ca, err := pki.DecodeX509CertificateBytes(caPem) - if err != nil { - return nil, err - } - - ks := jks.KeyStore{ - "ca": &jks.TrustedCertificateEntry{ - Entry: jks.Entry{ - CreationDate: time.Now(), - }, - Certificate: jks.Certificate{ - Type: "X509", - Content: ca.Raw, - }, - }, - } - - buf := &bytes.Buffer{} - if err := jks.Encode(buf, ks, password); err != nil { - return nil, err - } - return buf.Bytes(), nil -} diff --git a/pkg/controller/certificates/keystore_test.go b/pkg/controller/certificates/keystore_test.go deleted file mode 100644 index 2b6c9c591..000000000 --- a/pkg/controller/certificates/keystore_test.go +++ /dev/null @@ -1,225 +0,0 @@ -/* -Copyright 2020 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 certificates - -import ( - "bytes" - "testing" - - jks "github.com/pavel-v-chernykh/keystore-go" - "software.sslmate.com/src/go-pkcs12" - - cmapi "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha2" - "github.com/jetstack/cert-manager/pkg/util/pki" -) - -func mustGeneratePrivateKey(t *testing.T, encoding cmapi.KeyEncoding) []byte { - pk, err := pki.GenerateRSAPrivateKey(2048) - if err != nil { - t.Fatal(err) - } - pkBytes, err := pki.EncodePrivateKey(pk, encoding) - if err != nil { - t.Fatal(err) - } - return pkBytes -} - -func mustSelfSignCertificate(t *testing.T, pkBytes []byte) []byte { - if pkBytes == nil { - pkBytes = mustGeneratePrivateKey(t, cmapi.PKCS8) - } - pk, err := pki.DecodePrivateKeyBytes(pkBytes) - if err != nil { - t.Fatal(err) - } - x509Crt, err := pki.GenerateTemplate(&cmapi.Certificate{ - Spec: cmapi.CertificateSpec{ - DNSNames: []string{"example.com"}, - }, - }) - if err != nil { - t.Fatal(err) - } - certBytes, _, err := pki.SignCertificate(x509Crt, x509Crt, pk.Public(), pk) - if err != nil { - t.Fatal(err) - } - return certBytes -} - -func TestEncodeJKSKeystore(t *testing.T) { - tests := map[string]struct { - password string - rawKey, certPEM, caPEM []byte - verify func(t *testing.T, out []byte, err error) - }{ - "encode a JKS bundle for a PKCS1 key and certificate only": { - password: "password", - rawKey: mustGeneratePrivateKey(t, cmapi.PKCS1), - certPEM: mustSelfSignCertificate(t, nil), - verify: func(t *testing.T, out []byte, err error) { - if err != nil { - t.Errorf("expected no error but got: %v", err) - return - } - buf := bytes.NewBuffer(out) - ks, err := jks.Decode(buf, []byte("password")) - if err != nil { - t.Errorf("error decoding keystore: %v", err) - return - } - if ks["certificate"] == nil { - t.Errorf("no certificate data found in keystore") - } - if ks["ca"] != nil { - t.Errorf("unexpected ca data found in keystore") - } - }, - }, - "encode a JKS bundle for a PKCS8 key and certificate only": { - password: "password", - rawKey: mustGeneratePrivateKey(t, cmapi.PKCS8), - certPEM: mustSelfSignCertificate(t, nil), - verify: func(t *testing.T, out []byte, err error) { - if err != nil { - t.Errorf("expected no error but got: %v", err) - } - buf := bytes.NewBuffer(out) - ks, err := jks.Decode(buf, []byte("password")) - if err != nil { - t.Errorf("error decoding keystore: %v", err) - return - } - if ks["certificate"] == nil { - t.Errorf("no certificate data found in keystore") - } - if ks["ca"] != nil { - t.Errorf("unexpected ca data found in keystore") - } - }, - }, - "encode a JKS bundle for a key, certificate and ca": { - password: "password", - rawKey: mustGeneratePrivateKey(t, cmapi.PKCS8), - certPEM: mustSelfSignCertificate(t, nil), - caPEM: mustSelfSignCertificate(t, nil), - verify: func(t *testing.T, out []byte, err error) { - if err != nil { - t.Errorf("expected no error but got: %v", err) - } - buf := bytes.NewBuffer(out) - ks, err := jks.Decode(buf, []byte("password")) - if err != nil { - t.Errorf("error decoding keystore: %v", err) - return - } - if ks["certificate"] == nil { - t.Errorf("no certificate data found in keystore") - } - if ks["ca"] == nil { - t.Errorf("no ca data found in keystore") - } - }, - }, - } - for name, test := range tests { - t.Run(name, func(t *testing.T) { - out, err := encodeJKSKeystore([]byte(test.password), test.rawKey, test.certPEM, test.caPEM) - test.verify(t, out, err) - }) - } -} - -func TestEncodePKCS12Keystore(t *testing.T) { - tests := map[string]struct { - password string - rawKey, certPEM, caPEM []byte - verify func(t *testing.T, out []byte, err error) - }{ - "encode a JKS bundle for a PKCS1 key and certificate only": { - password: "password", - rawKey: mustGeneratePrivateKey(t, cmapi.PKCS1), - certPEM: mustSelfSignCertificate(t, nil), - verify: func(t *testing.T, out []byte, err error) { - if err != nil { - t.Errorf("expected no error but got: %v", err) - } - pk, cert, err := pkcs12.Decode(out, "password") - if err != nil { - t.Errorf("error decoding keystore: %v", err) - return - } - if cert == nil { - t.Errorf("no certificate data found in keystore") - } - if pk == nil { - t.Errorf("no ca data found in keystore") - } - }, - }, - "encode a JKS bundle for a PKCS8 key and certificate only": { - password: "password", - rawKey: mustGeneratePrivateKey(t, cmapi.PKCS8), - certPEM: mustSelfSignCertificate(t, nil), - verify: func(t *testing.T, out []byte, err error) { - if err != nil { - t.Errorf("expected no error but got: %v", err) - } - pk, cert, err := pkcs12.Decode(out, "password") - if err != nil { - t.Errorf("error decoding keystore: %v", err) - return - } - if cert == nil { - t.Errorf("no certificate data found in keystore") - } - if pk == nil { - t.Errorf("no ca data found in keystore") - } - }, - }, - "encode a JKS bundle for a key, certificate and ca": { - password: "password", - rawKey: mustGeneratePrivateKey(t, cmapi.PKCS8), - certPEM: mustSelfSignCertificate(t, nil), - caPEM: mustSelfSignCertificate(t, nil), - verify: func(t *testing.T, out []byte, err error) { - if err != nil { - t.Errorf("expected no error but got: %v", err) - } - // The pkcs12 package does not expose a way to decode the CA - // data that has been written. - // It will return an error when attempting to decode a file - // with more than one 'certbag', so we just ensure the error - // returned is the expected error and don't inspect the keystore - // contents. - _, _, err = pkcs12.Decode(out, "password") - if err == nil || err.Error() != "pkcs12: expected exactly two safe bags in the PFX PDU" { - t.Errorf("unexpected error string, exp=%q, got=%v", "pkcs12: expected exactly two safe bags in the PFX PDU", err) - return - } - }, - }, - } - for name, test := range tests { - t.Run(name, func(t *testing.T) { - out, err := encodePKCS12Keystore(test.password, test.rawKey, test.certPEM, test.caPEM) - test.verify(t, out, err) - }) - } -} diff --git a/pkg/controller/certificates/sync.go b/pkg/controller/certificates/sync.go deleted file mode 100644 index ea155da65..000000000 --- a/pkg/controller/certificates/sync.go +++ /dev/null @@ -1,919 +0,0 @@ -/* -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 certificates - -import ( - "bytes" - "context" - "crypto/ecdsa" - "crypto/rsa" - "crypto/x509" - "encoding/pem" - "fmt" - "reflect" - "strings" - "time" - - "github.com/go-logr/logr" - 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/labels" - utilerrors "k8s.io/apimachinery/pkg/util/errors" - - 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" - cmlisters "github.com/jetstack/cert-manager/pkg/client/listers/certmanager/v1alpha2" - logf "github.com/jetstack/cert-manager/pkg/logs" - "github.com/jetstack/cert-manager/pkg/util/errors" - "github.com/jetstack/cert-manager/pkg/util/kube" - "github.com/jetstack/cert-manager/pkg/util/pki" -) - -func (c *certificateRequestManager) ProcessItem(ctx context.Context, key string) error { - log := logf.FromContext(ctx) - - crt, err := getCertificateForKey(ctx, key, c.certificateLister) - if apierrors.IsNotFound(err) { - log.Error(err, "certificate resource not found for key", "key", key) - return nil - } - if crt == nil { - log.Info("certificate resource not found for key", "key", key) - return nil - } - if err != nil { - return err - } - - log = logf.WithResource(log, crt) - ctx = logf.NewContext(ctx, log) - updatedCert := crt.DeepCopy() - - err = c.processCertificate(ctx, updatedCert) - log.V(logf.DebugLevel).Info("check if certificate status update is required") - updateStatusErr := c.updateCertificateStatus(ctx, crt, updatedCert) - return utilerrors.NewAggregate([]error{err, updateStatusErr}) -} - -func (c *certificateRequestManager) updateCertificateStatus(ctx context.Context, old, crt *cmapi.Certificate) error { - log := logf.FromContext(ctx) - secretExists := true - certs, key, err := kube.SecretTLSKeyPair(ctx, c.secretLister, crt.Namespace, crt.Spec.SecretName) - if err != nil { - if !apierrors.IsNotFound(err) && !errors.IsInvalidData(err) { - return err - } - - if apierrors.IsNotFound(err) { - secretExists = false - } - } - reqs, err := findCertificateRequestsForCertificate(log, crt, c.certificateRequestLister) - if err != nil { - return err - } - var req *cmapi.CertificateRequest - if len(reqs) == 1 { - req = reqs[0] - } - var cert *x509.Certificate - var certExpired bool - if len(certs) > 0 { - cert = certs[0] - certExpired = cert.NotAfter.Before(c.clock.Now()) - } - - var matches bool - var matchErrs []string - if key != nil && cert != nil { - secret, err := c.secretLister.Secrets(crt.Namespace).Get(crt.Spec.SecretName) - if err != nil { - return err - } - - matches, matchErrs = certificateMatchesSpec(crt, key, cert, secret) - } - - isTempCert := isTemporaryCertificate(cert) - - // begin setting certificate status fields - if !matches || isTempCert { - crt.Status.NotAfter = nil - } else { - metaNotAfter := metav1.NewTime(cert.NotAfter) - crt.Status.NotAfter = &metaNotAfter - } - - // Derive & set 'Ready' condition on Certificate resource - ready := cmmeta.ConditionFalse - reason := "" - message := "" - switch { - case !secretExists || key == nil: - reason = "NotFound" - message = "Certificate does not exist" - case matches && !isTempCert && !certExpired: - ready = cmmeta.ConditionTrue - reason = "Ready" - message = "Certificate is up to date and has not expired" - case apiutil.CertificateRequestHasInvalidRequest(req): - reason = "InvalidRequest" - message = fmt.Sprintf("The certificate request could not be completed due to invalid request options: %s", - apiutil.CertificateRequestInvalidRequestMessage(req)) - case req != nil: - reason = "InProgress" - message = fmt.Sprintf("Waiting for CertificateRequest %q to complete", req.Name) - case cert == nil: - reason = "Pending" - message = "Certificate pending issuance" - case !matches: - reason = "DoesNotMatch" - message = strings.Join(matchErrs, ", ") - case certExpired: - reason = "Expired" - message = fmt.Sprintf("Certificate has expired on %s", cert.NotAfter.Format(time.RFC822)) - case isTempCert: - reason = "TemporaryCertificate" - message = "Certificate issuance in progress. Temporary certificate issued." - default: - // theoretically, it should not be possible to reach this state. - // practically, we may have missed some edge cases above. - // print a dump of the current state as a log message so that users can - // discover, share and attempt to resolve bugs in this area of code easily. - log.Info("unknown certificate state", - "secret_exists", secretExists, - "matches", matches, - "is_temp_cert", isTempCert, - "cert_expired", certExpired, - "key_is_nil", key == nil, - "req_is_nil", req == nil, - "cert_is_nil", cert == nil, - ) - ready = cmmeta.ConditionFalse - reason = "Unknown" - message = "Unknown certificate status. Please open an issue and share your controller logs." - } - apiutil.SetCertificateCondition(crt, cmapi.CertificateConditionReady, ready, reason, message) - - _, err = updateCertificateStatus(ctx, c.cmClient, old, crt) - if err != nil { - return err - } - - return nil -} - -// processCertificate is the core method that is called in the manager. -// It accepts a Certificate resource, and checks to see if the certificate -// requires re-issuance. -func (c *certificateRequestManager) processCertificate(ctx context.Context, crt *cmapi.Certificate) error { - log := logf.FromContext(ctx) - dbg := log.V(logf.DebugLevel) - - // 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 := apiutil.ComputeCertificateRequestName(crt) - if err != nil { - return fmt.Errorf("internal error hashing certificate spec: %v", err) - } - - // Clean up any 'owned' CertificateRequest resources that do not have the - // expected name computed above - err = c.cleanupExistingCertificateRequests(log, crt, expectedReqName) - if err != nil { - return err - } - - // Fetch a copy of the existing Secret resource - existingSecret, err := c.secretLister.Secrets(crt.Namespace).Get(crt.Spec.SecretName) - if apierrors.IsNotFound(err) { - // If the secret does not exist, generate a new private key and store it. - dbg.Info("existing secret not found, generating and storing private key") - return c.generateAndStorePrivateKey(ctx, crt, nil) - } - if err != nil { - return err - } - - log = logf.WithRelatedResource(log, existingSecret) - ctx = logf.NewContext(ctx, log) - - // If the Secret does not contain a private key, generate one and update - // the Secret resource - existingKey := existingSecret.Data[corev1.TLSPrivateKeyKey] - if len(existingKey) == 0 { - log.Info("existing private key not found in Secret, generate a new private key") - return c.generateAndStorePrivateKey(ctx, crt, existingSecret) - } - - // Ensure the the private key has the correct key algorithm and key size. - dbg.Info("validating private key has correct keyAlgorithm/keySize") - validKey, err := validatePrivateKeyUpToDate(log, existingKey, crt) - // If tls.key contains invalid data, we regenerate a new private key - if errors.IsInvalidData(err) { - log.Info("existing private key data is invalid, generating a new private key") - return c.generateAndStorePrivateKey(ctx, crt, existingSecret) - } - if err != nil { - return err - } - // If the private key is not 'up to date', we generate a new private key - if !validKey { - log.Info("existing private key does not match requirements specified on Certificate resource, generating new private key") - return c.generateAndStorePrivateKey(ctx, crt, existingSecret) - } - - // Attempt to fetch the CertificateRequest with the expected name computed above. - dbg.Info("checking for existing CertificateRequest for Certificate") - existingReq, err := c.certificateRequestLister.CertificateRequests(crt.Namespace).Get(expectedReqName) - // Allow IsNotFound errors, later on we check if existingReq == nil and if - // it is, we create a new CertificateRequest resource. - if err != nil && !apierrors.IsNotFound(err) { - return err - } - if existingReq != nil { - dbg.Info("found existing certificate request for Certificate", "request_name", existingReq.Name) - log = logf.WithRelatedResource(log, existingReq) - } - - needsIssue := true - // Parse the existing certificate - existingCert := existingSecret.Data[corev1.TLSCertKey] - if len(existingCert) > 0 { - // Here we check to see if the existing certificate 'matches' the spec - // of the Certificate resource. - // This includes checking if dnsNames, commonName, organization etc. - // are up to date, as well as validating that the stored private key is - // a valid partner to the stored certificate. - var matchErrs []string - dbg.Info("checking if existing certificate stored in Secret resource is not expiring soon and matches certificate spec") - needsIssue, matchErrs, err = c.certificateRequiresIssuance(ctx, crt, existingKey, existingCert, existingSecret) - if err != nil && !errors.IsInvalidData(err) { - return err - } - // If the certificate data is invalid, we require a re-issuance. - // The private key should never be invalid at this point as we already - // check it above. - if errors.IsInvalidData(err) { - dbg.Info("existing secret contains invalid certificate data") - needsIssue = true - } - - if !needsIssue { - dbg.Info("existing certificate does not need re-issuance") - } else { - dbg.Info("will attempt to issue certificate", "reason", matchErrs) - } - } - - // Exit early if the certificate doesn't need issuing to save extra work - if !needsIssue { - if existingReq != nil { - dbg.Info("skipping issuing certificate data into Secret resource as existing issued certificate is still valid") - } - - // Before exiting, ensure that the Secret resource's metadata is up to - // date. If it isn't, it will be updated. - updated, err := c.ensureSecretMetadataUpToDate(ctx, existingSecret, crt) - if err != nil { - return err - } - - if updated { - log.Info("updated Secret resource metadata as it was out of date") - } - - // As the Certificate has been validated as Ready, schedule a renewal - // for near the expiry date. - scheduleRenewal(ctx, c.secretLister, c.calculateDurationUntilRenew, c.scheduledWorkQueue.Add, crt) - - log.Info("certificate does not require re-issuance. certificate renewal scheduled near expiry time.") - - return nil - } - - // Attempt to decode the private key. - // This shouldn't fail as we already validate the private key is valid above. - dbg.Info("decoding existing private key") - privateKey, err := pki.DecodePrivateKeyBytes(existingKey) - if err != nil { - return err - } - - // Attempt to decode the existing certificate. - // We tolerate invalid data errors as we will issue a certificate if the - // data is invalid. - dbg.Info("attempting to decode existing certificate") - existingX509Cert, err := pki.DecodeX509CertificateBytes(existingCert) - if err != nil && !errors.IsInvalidData(err) { - return err - } - if errors.IsInvalidData(err) { - dbg.Info("existing certificate data is invalid, continuing...") - } - - // Handling for 'temporary certificates' - if certificateHasTemporaryCertificateAnnotation(crt) { - // Issue a temporary certificate if the current certificate is empty or the - // private key is not valid for the current certificate. - if existingX509Cert == nil { - log.Info("no existing certificate data found in secret, issuing temporary certificate") - return c.issueTemporaryCertificate(ctx, existingSecret, crt, existingKey) - } - - matches, err := pki.PublicKeyMatchesCertificate(privateKey.Public(), existingX509Cert) - if err != nil || !matches { - log.Info("private key for certificate does not match, issuing temporary certificate") - return c.issueTemporaryCertificate(ctx, existingSecret, crt, existingKey) - } - - log.Info("not issuing temporary certificate as existing certificate is sufficient") - - // Ensure the secret metadata is up to date - updated, err := c.ensureSecretMetadataUpToDate(ctx, existingSecret, crt) - if err != nil { - return err - } - - // Only return early if an update actually occurred, otherwise continue. - if updated { - log.Info("updated Secret resource metadata as it was out of date") - return nil - } - } - - if existingReq == nil { - // If no existing CertificateRequest resource exists, we must create one - log.Info("no existing CertificateRequest resource exists, creating new request...") - req, err := c.buildCertificateRequest(log, crt, expectedReqName, existingKey) - if err != nil { - return err - } - - req, err = c.cmClient.CertmanagerV1alpha2().CertificateRequests(crt.Namespace).Create(context.TODO(), req, metav1.CreateOptions{}) - if err != nil { - return err - } - - c.recorder.Eventf(crt, corev1.EventTypeNormal, "Requested", "Created new CertificateRequest resource %q", req.Name) - log.Info("created certificate request", "request_name", req.Name) - - return nil - } - - // Validate the CertificateRequest's CSR is valid - log.Info("validating existing CSR data") - x509CSR, err := pki.DecodeX509CertificateRequestBytes(existingReq.Spec.CSRPEM) - if errors.IsInvalidData(err) { - log.Info("failed to decode existing CSR on CertificateRequest, deleting resource...") - return c.cmClient.CertmanagerV1alpha2().CertificateRequests(existingReq.Namespace).Delete(context.TODO(), existingReq.Name, metav1.DeleteOptions{}) - } - if err != nil { - return err - } - - // Ensure the stored private key is a 'pair' to the CSR - publicKeyMatches, err := pki.PublicKeyMatchesCSR(privateKey.Public(), x509CSR) - if err != nil { - return err - } - - // if the stored private key does not pair with the CSR on the - // CertificateRequest resource, delete the resource as we won't be able to - // do anything with the certificate if it is issued - if !publicKeyMatches { - log.Info("stored private key is not valid for CSR stored on existing CertificateRequest, recreating CertificateRequest resource") - err := c.cmClient.CertmanagerV1alpha2().CertificateRequests(existingReq.Namespace).Delete(context.TODO(), existingReq.Name, metav1.DeleteOptions{}) - if err != nil { - return err - } - - c.recorder.Eventf(crt, corev1.EventTypeNormal, "PrivateKeyLost", "Lost private key for CertificateRequest %q, deleting old resource", existingReq.Name) - log.Info("deleted existing CertificateRequest as the stored private key does not match the CSR") - return nil - } - - reason := apiutil.CertificateRequestReadyReason(existingReq) - - // If the CertificateRequest condition is present and has the status of - // "True" then do not attempt to retry the CertificateRequest. Else we can - // retry. - if apiutil.CertificateRequestHasInvalidRequest(existingReq) { - log.Info("CertificateRequest is in an InvalidRequest state and will no longer be processed", "state", reason) - - c.recorder.Eventf(crt, corev1.EventTypeWarning, "CertificateRequestInvalidRequest", "The failed CertificateRequest %q is an invalid request and will no longer be processed", existingReq.Name) - return nil - } - - // Determine the status reason of the CertificateRequest and process accordingly - switch reason { - - // If the CertificateRequest exists but has failed then we check the if the - // failure time doesn't exist or is over an hour in the past then delete the - // request so it can be re-created on the next sync. If the failure time is - // less than an hour in the past then schedule this owning Certificate for a - // re-sync in an hour. - case cmapi.CertificateRequestReasonFailed: - if existingReq.Status.FailureTime == nil || c.clock.Since(existingReq.Status.FailureTime.Time) > time.Hour { - log.Info("deleting failed certificate request") - err := c.cmClient.CertmanagerV1alpha2().CertificateRequests(existingReq.Namespace).Delete(context.TODO(), existingReq.Name, metav1.DeleteOptions{}) - if err != nil { - return err - } - - c.recorder.Eventf(crt, corev1.EventTypeNormal, "CertificateRequestRetry", "The failed CertificateRequest %q will be retried now", existingReq.Name) - return nil - } - - log.Info("the failed existing certificate request failed less than an hour ago, will be scheduled for reprocessing in an hour") - - key, err := keyFunc(crt) - if err != nil { - log.Error(err, "error getting key for certificate resource") - return nil - } - - // We don't fire an event here as this could be called multiple times in quick succession - c.scheduledWorkQueue.Add(key, time.Hour) - return nil - - // If the CertificateRequest is in a Ready state then we can decode, - // verify, and check whether it needs renewal - case cmapi.CertificateRequestReasonIssued: - log.Info("CertificateRequest is in a Ready state, issuing certificate...") - - // Decode the certificate bytes so we can ensure the certificate is valid - log.Info("decoding certificate data") - x509Cert, err := pki.DecodeX509CertificateBytes(existingReq.Status.Certificate) - if err != nil { - return err - } - - log.Info("checking stored private key is valid for stored x509 certificate on CertificateRequest") - publicKeyMatches, err := pki.PublicKeyMatchesCertificate(privateKey.Public(), x509Cert) - if err != nil { - return err - } - if !publicKeyMatches { - log.Info("private key stored in Secret does not match public key of issued certificate, deleting the old CertificateRequest resource") - return c.cmClient.CertmanagerV1alpha2().CertificateRequests(existingReq.Namespace).Delete(context.TODO(), existingReq.Name, metav1.DeleteOptions{}) - } - - // Check if the Certificate requires renewal according to the renewBefore - // specified on the Certificate resource. - log.Info("checking if certificate stored on CertificateRequest is up to date") - if c.certificateNeedsRenew(ctx, x509Cert, crt) { - log.Info("certificate stored on CertificateRequest needs renewal, so deleting the old CertificateRequest resource") - err := c.cmClient.CertmanagerV1alpha2().CertificateRequests(existingReq.Namespace).Delete(context.TODO(), existingReq.Name, metav1.DeleteOptions{}) - if err != nil { - return err - } - - return nil - } - - // If certificate stored on CertificateRequest is not expiring soon, copy - // across the status.certificate field into the Secret resource. - log.Info("CertificateRequest contains a valid certificate for issuance. Issuing certificate...") - - _, err = c.updateSecretData(ctx, crt, existingSecret, secretData{pk: existingKey, cert: existingReq.Status.Certificate, ca: existingReq.Status.CA}) - if err != nil { - return err - } - - c.recorder.Eventf(crt, corev1.EventTypeNormal, "Issued", "Certificate issued successfully") - return nil - - // If it is not Ready _OR_ Failed then we return and wait for informer - // updates to re-trigger processing. - default: - log.Info("CertificateRequest is not in a final state, waiting until CertificateRequest is complete", "state", reason) - return nil - } -} - -// updateSecretData will ensure the Secret resource contains the given secret -// data as well as appropriate metadata. -// If the given 'existingSecret' is nil, a new Secret resource will be created. -// Otherwise, the existing resource will be updated. -// The first return argument will be true if the resource was updated/created -// without error. -// updateSecretData will also update deprecated annotations if they exist. -func (c *certificateRequestManager) updateSecretData(ctx context.Context, crt *cmapi.Certificate, existingSecret *corev1.Secret, data secretData) (bool, error) { - s := &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: crt.Spec.SecretName, - Namespace: crt.Namespace, - }, - Type: corev1.SecretTypeTLS, - } - // s will be overwritten by 'existingSecret' if existingSecret is non-nil - if c.enableSecretOwnerReferences { - s.OwnerReferences = []metav1.OwnerReference{*metav1.NewControllerRef(crt, certificateGvk)} - } - if existingSecret != nil { - s = existingSecret - } - - newSecret := s.DeepCopy() - err := c.setSecretValues(ctx, crt, newSecret, secretData{pk: data.pk, cert: data.cert, ca: data.ca}) - if err != nil { - return false, err - } - if reflect.DeepEqual(s, newSecret) { - return false, nil - } - - if existingSecret == nil { - _, err = c.kubeClient.CoreV1().Secrets(newSecret.Namespace).Create(context.TODO(), newSecret, metav1.CreateOptions{}) - if err != nil { - return false, err - } - return true, nil - } - - _, err = c.kubeClient.CoreV1().Secrets(newSecret.Namespace).Update(context.TODO(), newSecret, metav1.UpdateOptions{}) - if err != nil { - return false, err - } - - return true, nil -} - -func (c *certificateRequestManager) ensureSecretMetadataUpToDate(ctx context.Context, s *corev1.Secret, crt *cmapi.Certificate) (bool, error) { - pk := s.Data[corev1.TLSPrivateKeyKey] - cert := s.Data[corev1.TLSCertKey] - ca := s.Data[cmmeta.TLSCAKey] - - updated, err := c.updateSecretData(ctx, crt, s, secretData{pk: pk, cert: cert, ca: ca}) - if err != nil || !updated { - return updated, err - } - - c.recorder.Eventf(crt, corev1.EventTypeNormal, "UpdateMeta", "Updated metadata on Secret resource") - - return true, nil -} - -func (c *certificateRequestManager) issueTemporaryCertificate(ctx context.Context, secret *corev1.Secret, crt *cmapi.Certificate, key []byte) error { - tempCertData, err := c.localTemporarySigner(crt, key) - if err != nil { - return err - } - - newSecret := secret.DeepCopy() - err = c.setSecretValues(ctx, crt, newSecret, secretData{pk: key, cert: tempCertData}) - if err != nil { - return err - } - - _, err = c.kubeClient.CoreV1().Secrets(newSecret.Namespace).Update(context.TODO(), newSecret, metav1.UpdateOptions{}) - if err != nil { - return err - } - - c.recorder.Eventf(crt, corev1.EventTypeNormal, "TempCert", "Issued temporary certificate") - - return nil -} - -func (c *certificateRequestManager) certificateRequiresIssuance(ctx context.Context, crt *cmapi.Certificate, keyBytes, certBytes []byte, secret *corev1.Secret) (bool, []string, error) { - key, err := pki.DecodePrivateKeyBytes(keyBytes) - if err != nil { - return false, nil, err - } - cert, err := pki.DecodeX509CertificateBytes(certBytes) - if err != nil { - return false, nil, err - } - if isTemporaryCertificate(cert) { - return true, nil, nil - } - matches, matchErrs := certificateMatchesSpec(crt, key, cert, secret) - if !matches { - return true, matchErrs, nil - } - needsRenew := c.certificateNeedsRenew(ctx, cert, crt) - return needsRenew, []string{"Certificate is expiring soon"}, nil -} - -type generateCSRFn func(*cmapi.Certificate, []byte) ([]byte, error) - -func generateCSRImpl(crt *cmapi.Certificate, pk []byte) ([]byte, error) { - csr, err := pki.GenerateCSR(crt) - if err != nil { - return nil, err - } - - signer, err := pki.DecodePrivateKeyBytes(pk) - if err != nil { - return nil, err - } - - csrDER, err := pki.EncodeCSR(csr, signer) - if err != nil { - return nil, err - } - - csrPEM := pem.EncodeToMemory(&pem.Block{ - Type: "CERTIFICATE REQUEST", Bytes: csrDER, - }) - - return csrPEM, nil -} - -func (c *certificateRequestManager) buildCertificateRequest(log logr.Logger, crt *cmapi.Certificate, name string, pk []byte) (*cmapi.CertificateRequest, error) { - csrPEM, err := c.generateCSR(crt, pk) - if err != nil { - return nil, err - } - - annotations := make(map[string]string, len(crt.Annotations)+2) - for k, v := range crt.Annotations { - annotations[k] = v - } - annotations[cmapi.CertificateRequestPrivateKeyAnnotationKey] = crt.Spec.SecretName - annotations[cmapi.CertificateNameKey] = crt.Name - - cr := &cmapi.CertificateRequest{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: crt.Namespace, - OwnerReferences: []metav1.OwnerReference{*metav1.NewControllerRef(crt, certificateGvk)}, - Annotations: annotations, - Labels: crt.Labels, - }, - Spec: cmapi.CertificateRequestSpec{ - CSRPEM: csrPEM, - Duration: crt.Spec.Duration, - IssuerRef: crt.Spec.IssuerRef, - IsCA: crt.Spec.IsCA, - Usages: crt.Spec.Usages, - }, - } - - return cr, nil -} - -func (c *certificateRequestManager) cleanupExistingCertificateRequests(log logr.Logger, crt *cmapi.Certificate, retain string) error { - reqs, err := findCertificateRequestsForCertificate(log, crt, c.certificateRequestLister) - if err != nil { - return err - } - - for _, req := range reqs { - log := logf.WithRelatedResource(log, req) - if req.Name == retain { - log.V(logf.DebugLevel).Info("skipping deleting CertificateRequest as it is up to date for the certificate spec") - continue - } - - err = c.cmClient.CertmanagerV1alpha2().CertificateRequests(req.Namespace).Delete(context.TODO(), req.Name, metav1.DeleteOptions{}) - if err != nil { - return err - } - - log.Info("deleted no longer required CertificateRequest") - } - - return nil -} - -func findCertificateRequestsForCertificate(log logr.Logger, crt *cmapi.Certificate, lister cmlisters.CertificateRequestLister) ([]*cmapi.CertificateRequest, error) { - log.V(logf.DebugLevel).Info("finding existing CertificateRequest resources for Certificate") - reqs, err := lister.CertificateRequests(crt.Namespace).List(labels.Everything()) - if err != nil { - return nil, err - } - - var candidates []*cmapi.CertificateRequest - for _, req := range reqs { - log := logf.WithRelatedResource(log, req) - if metav1.IsControlledBy(req, crt) { - log.V(logf.DebugLevel).Info("found CertificateRequest resource for Certificate") - candidates = append(candidates, req) - } - } - - return candidates, nil -} - -// validatePrivateKeyUpToDate will evaluate the private key data in pk and -// ensure it is 'up to date' and matches the specification of the key as -// required by the given Certificate resource. -// It returns false if the private key isn't up to date, e.g. the Certificate -// resource specifies a different keyEncoding, keyAlgorithm or keySize. -func validatePrivateKeyUpToDate(log logr.Logger, pk []byte, crt *cmapi.Certificate) (bool, error) { - signer, err := pki.DecodePrivateKeyBytes(pk) - if err != nil { - return false, err - } - - // TODO: check keyEncoding - - wantedAlgorithm := crt.Spec.KeyAlgorithm - if wantedAlgorithm == "" { - // in-memory defaulting of the key algorithm to RSA - // TODO: remove this in favour of actual defaulting in a mutating webhook - wantedAlgorithm = cmapi.RSAKeyAlgorithm - } - - switch wantedAlgorithm { - case cmapi.RSAKeyAlgorithm: - _, ok := signer.(*rsa.PrivateKey) - if !ok { - log.Info("expected private key's algorithm to be RSA but it is not") - return false, nil - } - // TODO: check keySize - case cmapi.ECDSAKeyAlgorithm: - _, ok := signer.(*ecdsa.PrivateKey) - if !ok { - log.Info("expected private key's algorithm to be ECDSA but it is not") - return false, nil - } - // TODO: check keySize - } - - return true, nil -} - -func (c *certificateRequestManager) generateAndStorePrivateKey(ctx context.Context, crt *cmapi.Certificate, s *corev1.Secret) error { - keyData, err := c.generatePrivateKeyBytes(ctx, crt) - if err != nil { - // TODO: handle permanent failures caused by invalid spec - return err - } - - updated, err := c.updateSecretData(ctx, crt, s, secretData{pk: keyData}) - if err != nil { - return err - } - if !updated { - return nil - } - - c.recorder.Eventf(crt, corev1.EventTypeNormal, "GeneratedKey", "Generated a new private key") - - return nil -} - -type generatePrivateKeyBytesFn func(context.Context, *cmapi.Certificate) ([]byte, error) - -func generatePrivateKeyBytesImpl(ctx context.Context, crt *cmapi.Certificate) ([]byte, error) { - signer, err := pki.GeneratePrivateKeyForCertificate(crt) - if err != nil { - return nil, err - } - - keyData, err := pki.EncodePrivateKey(signer, crt.Spec.KeyEncoding) - if err != nil { - return nil, err - } - - return keyData, nil -} - -// secretData is a structure wrapping private key, certificate and CA data -type secretData struct { - pk, cert, ca []byte -} - -// setSecretValues will update the Secret resource 's' with the data contained -// in the given secretData. -// It will update labels and annotations on the Secret resource appropriately. -// The Secret resource 's' must be non-nil, although may be a resource that does -// not exist in the Kubernetes apiserver yet. -// setSecretValues will NOT actually update the resource in the apiserver. -// If updating an existing Secret resource returned by an api client 'lister', -// make sure to DeepCopy the object first to avoid modifying data in-cache. -// It will also update depreciated issuer name and kind annotations if they exist. -func (c *certificateRequestManager) setSecretValues(ctx context.Context, crt *cmapi.Certificate, s *corev1.Secret, data secretData) error { - // initialize the `Data` field if it is nil - if s.Data == nil { - s.Data = make(map[string][]byte) - } - - // Only write a new PKCS12/JKS file if any of the private key/certificate/CA - // data has actually changed. - if data.pk != nil && data.cert != nil && - (!bytes.Equal(s.Data[corev1.TLSPrivateKeyKey], data.pk) || - !bytes.Equal(s.Data[corev1.TLSCertKey], data.cert) || - !bytes.Equal(s.Data[cmmeta.TLSCAKey], data.ca)) { - - // Handle the experimental PKCS12 support - if crt.Spec.Keystores != nil && crt.Spec.Keystores.PKCS12 != nil && crt.Spec.Keystores.PKCS12.Create { - ref := crt.Spec.Keystores.PKCS12.PasswordSecretRef - pwSecret, err := c.secretLister.Secrets(crt.Namespace).Get(ref.Name) - if err != nil { - return fmt.Errorf("fetching PKCS12 keystore password from Secret: %v", err) - } - if pwSecret.Data == nil || len(pwSecret.Data[ref.Key]) == 0 { - return fmt.Errorf("PKCS12 keystore password Secret contains no data for key %q", ref.Key) - } - pw := pwSecret.Data[ref.Key] - keystoreData, err := encodePKCS12Keystore(string(pw), data.pk, data.cert, data.ca) - if err != nil { - return fmt.Errorf("error encoding PKCS12 bundle: %w", err) - } - // always overwrite the keystore entry for now - s.Data[pkcs12SecretKey] = keystoreData - } else { - delete(s.Data, pkcs12SecretKey) - } - - // Handle the experimental JKS support - if crt.Spec.Keystores != nil && crt.Spec.Keystores.JKS != nil && crt.Spec.Keystores.JKS.Create { - ref := crt.Spec.Keystores.JKS.PasswordSecretRef - pwSecret, err := c.secretLister.Secrets(crt.Namespace).Get(ref.Name) - if err != nil { - return fmt.Errorf("fetching JKS keystore password from Secret: %v", err) - } - if pwSecret.Data == nil || len(pwSecret.Data[ref.Key]) == 0 { - return fmt.Errorf("JKS keystore password Secret contains no data for key %q", ref.Key) - } - pw := pwSecret.Data[ref.Key] - keystoreData, err := encodeJKSKeystore(pw, data.pk, data.cert, data.ca) - if err != nil { - return fmt.Errorf("error encoding JKS bundle: %w", err) - } - // always overwrite the keystore entry - s.Data[jksSecretKey] = keystoreData - - if len(data.ca) > 0 { - truststoreData, err := encodeJKSTruststore(pw, data.ca) - if err != nil { - return fmt.Errorf("error encoding JKS trust store bundle: %w", err) - } - // always overwrite the keystore entry - s.Data[jksTruststoreKey] = truststoreData - } - } else { - delete(s.Data, jksSecretKey) - delete(s.Data, jksTruststoreKey) - } - } - - s.Data[corev1.TLSPrivateKeyKey] = data.pk - s.Data[corev1.TLSCertKey] = data.cert - if len(data.ca) > 0 { - s.Data[cmmeta.TLSCAKey] = data.ca - } else { - delete(s.Data, cmmeta.TLSCAKey) - } - - if s.Annotations == nil { - s.Annotations = make(map[string]string) - } - - s.Annotations[cmapi.CertificateNameKey] = crt.Name - s.Annotations[cmapi.IssuerNameAnnotationKey] = crt.Spec.IssuerRef.Name - s.Annotations[cmapi.IssuerKindAnnotationKey] = apiutil.IssuerKind(crt.Spec.IssuerRef) - - // If deprecated annotations exist with any value, then they too shall be - // updated - if _, ok := s.Annotations[cmapi.DeprecatedIssuerNameAnnotationKey]; ok { - s.Annotations[cmapi.DeprecatedIssuerNameAnnotationKey] = crt.Spec.IssuerRef.Name - } - if _, ok := s.Annotations[cmapi.DeprecatedIssuerKindAnnotationKey]; ok { - s.Annotations[cmapi.DeprecatedIssuerKindAnnotationKey] = apiutil.IssuerKind(crt.Spec.IssuerRef) - } - - // if the certificate data is empty, clear the subject related annotations - if len(data.cert) == 0 { - delete(s.Annotations, cmapi.CommonNameAnnotationKey) - delete(s.Annotations, cmapi.AltNamesAnnotationKey) - delete(s.Annotations, cmapi.IPSANAnnotationKey) - delete(s.Annotations, cmapi.URISANAnnotationKey) - } else { - x509Cert, err := pki.DecodeX509CertificateBytes(data.cert) - // TODO: handle InvalidData here? - if err != nil { - return err - } - - s.Annotations[cmapi.CommonNameAnnotationKey] = x509Cert.Subject.CommonName - s.Annotations[cmapi.AltNamesAnnotationKey] = strings.Join(x509Cert.DNSNames, ",") - s.Annotations[cmapi.IPSANAnnotationKey] = strings.Join(pki.IPAddressesToString(x509Cert.IPAddresses), ",") - s.Annotations[cmapi.URISANAnnotationKey] = strings.Join(pki.URLsToString(x509Cert.URIs), ",") - } - - return nil -} diff --git a/pkg/controller/certificates/sync_test.go b/pkg/controller/certificates/sync_test.go deleted file mode 100644 index 46046c435..000000000 --- a/pkg/controller/certificates/sync_test.go +++ /dev/null @@ -1,2333 +0,0 @@ -/* -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 certificates - -import ( - "context" - "crypto" - "crypto/x509" - "fmt" - "reflect" - "testing" - "time" - - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - 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" - "github.com/jetstack/cert-manager/pkg/controller" - testpkg "github.com/jetstack/cert-manager/pkg/controller/test" - "github.com/jetstack/cert-manager/pkg/util/pki" - "github.com/jetstack/cert-manager/test/unit/gen" -) - -var ( - fixedClockStart = time.Now() - fixedClock = fakeclock.NewFakeClock(fixedClockStart) -) - -type cryptoBundle struct { - // certificate is the Certificate resource used to create this bundle - certificate *cmapi.Certificate - // expectedRequestName is the name of the CertificateRequest that is - // expected to be created to issue this certificate - expectedRequestName string - - // privateKey is the private key used as the complement to the certificates - // in this bundle - privateKey crypto.Signer - privateKeyBytes []byte - - // csr is the CSR used to obtain the certificate in this bundle - csr *x509.CertificateRequest - csrBytes []byte - - // certificateRequest is the request that is expected to be created to - // obtain a certificate when using this bundle - certificateRequest *cmapi.CertificateRequest - certificateRequestReady *cmapi.CertificateRequest - certificateRequestFailed *cmapi.CertificateRequest - certificateRequestFailedInvalidRequest *cmapi.CertificateRequest - - // cert is a signed certificate - cert *x509.Certificate - certBytes []byte - - localTemporaryCertificateBytes []byte -} - -func mustCreateCryptoBundle(t *testing.T, crt *cmapi.Certificate) cryptoBundle { - c, err := createCryptoBundle(crt) - if err != nil { - t.Fatalf("error generating crypto bundle: %v", err) - } - return *c -} - -func createCryptoBundle(crt *cmapi.Certificate) (*cryptoBundle, error) { - reqName, err := apiutil.ComputeCertificateRequestName(crt) - if err != nil { - return nil, err - } - - privateKey, err := pki.GeneratePrivateKeyForCertificate(crt) - if err != nil { - return nil, err - } - - privateKeyBytes, err := pki.EncodePrivateKey(privateKey, crt.Spec.KeyEncoding) - if err != nil { - return nil, err - } - - csrPEM, err := generateCSRImpl(crt, privateKeyBytes) - if err != nil { - return nil, err - } - - csr, err := pki.DecodeX509CertificateRequestBytes(csrPEM) - if err != nil { - return nil, err - } - - annotations := make(map[string]string) - for k, v := range crt.Annotations { - annotations[k] = v - } - annotations[cmapi.CertificateRequestPrivateKeyAnnotationKey] = crt.Spec.SecretName - annotations[cmapi.CertificateNameKey] = crt.Name - certificateRequest := &cmapi.CertificateRequest{ - ObjectMeta: metav1.ObjectMeta{ - Name: reqName, - Namespace: crt.Namespace, - OwnerReferences: []metav1.OwnerReference{*metav1.NewControllerRef(crt, certificateGvk)}, - Annotations: annotations, - }, - Spec: cmapi.CertificateRequestSpec{ - CSRPEM: csrPEM, - Duration: crt.Spec.Duration, - IssuerRef: crt.Spec.IssuerRef, - IsCA: crt.Spec.IsCA, - }, - } - - unsignedCert, err := pki.GenerateTemplateFromCertificateRequest(certificateRequest) - if err != nil { - return nil, err - } - - certBytes, cert, err := pki.SignCertificate(unsignedCert, unsignedCert, privateKey.Public(), privateKey) - if err != nil { - return nil, err - } - - certificateRequestReady := gen.CertificateRequestFrom(certificateRequest, - gen.SetCertificateRequestCertificate(certBytes), - gen.SetCertificateRequestStatusCondition(cmapi.CertificateRequestCondition{ - Type: cmapi.CertificateRequestConditionReady, - Status: cmmeta.ConditionTrue, - Reason: cmapi.CertificateRequestReasonIssued, - }), - ) - - certificateRequestFailed := gen.CertificateRequestFrom(certificateRequest, - gen.SetCertificateRequestStatusCondition(cmapi.CertificateRequestCondition{ - Type: cmapi.CertificateRequestConditionReady, - Status: cmmeta.ConditionFalse, - Reason: cmapi.CertificateRequestReasonFailed, - }), - ) - - certificateRequestFailedInvalidRequest := gen.CertificateRequestFrom(certificateRequestFailed, - gen.SetCertificateRequestStatusCondition(cmapi.CertificateRequestCondition{ - Type: cmapi.CertificateRequestConditionInvalidRequest, - Status: cmmeta.ConditionTrue, - Reason: cmapi.CertificateRequestReasonFailed, - }), - ) - - tempCertBytes, err := generateLocallySignedTemporaryCertificate(crt, privateKeyBytes) - if err != nil { - panic("failed to generate test fixture: " + err.Error()) - } - - return &cryptoBundle{ - certificate: crt, - expectedRequestName: reqName, - privateKey: privateKey, - privateKeyBytes: privateKeyBytes, - csr: csr, - csrBytes: csrPEM, - certificateRequest: certificateRequest, - certificateRequestReady: certificateRequestReady, - certificateRequestFailed: certificateRequestFailed, - certificateRequestFailedInvalidRequest: certificateRequestFailedInvalidRequest, - cert: cert, - certBytes: certBytes, - localTemporaryCertificateBytes: tempCertBytes, - }, nil -} - -func (c *cryptoBundle) generateTestCSR(crt *cmapi.Certificate) []byte { - csrPEM, err := generateCSRImpl(crt, c.privateKeyBytes) - if err != nil { - panic("failed to generate test fixture: " + err.Error()) - } - - return csrPEM -} - -func (c *cryptoBundle) generateTestCertificate(crt *cmapi.Certificate, notBefore *time.Time) []byte { - csr := c.generateTestCSR(crt) - certificateRequest := &cmapi.CertificateRequest{ - Spec: cmapi.CertificateRequestSpec{ - CSRPEM: csr, - Duration: crt.Spec.Duration, - IssuerRef: crt.Spec.IssuerRef, - IsCA: crt.Spec.IsCA, - }, - } - - unsignedCert, err := pki.GenerateTemplateFromCertificateRequest(certificateRequest) - if err != nil { - panic("failed to generate test fixture: " + err.Error()) - } - - if notBefore != nil { - unsignedCert.NotBefore = *notBefore - } - - certBytes, _, err := pki.SignCertificate(unsignedCert, unsignedCert, c.privateKey.Public(), c.privateKey) - if err != nil { - panic("failed to generate test fixture: " + err.Error()) - } - - return certBytes -} - -func (c *cryptoBundle) generateCertificateExpiring1H(crt *cmapi.Certificate) []byte { - csr := c.generateTestCSR(crt) - certificateRequest := &cmapi.CertificateRequest{ - Spec: cmapi.CertificateRequestSpec{ - CSRPEM: csr, - Duration: crt.Spec.Duration, - IssuerRef: crt.Spec.IssuerRef, - IsCA: crt.Spec.IsCA, - }, - } - - unsignedCert, err := pki.GenerateTemplateFromCertificateRequest(certificateRequest) - if err != nil { - panic("failed to generate test fixture: " + err.Error()) - } - - nowTime := fixedClock.Now() - duration := unsignedCert.NotAfter.Sub(unsignedCert.NotBefore) - unsignedCert.NotBefore = nowTime.Add(time.Hour).Add(-1 * duration) - unsignedCert.NotAfter = nowTime.Add(time.Hour) - - certBytes, _, err := pki.SignCertificate(unsignedCert, unsignedCert, c.privateKey.Public(), c.privateKey) - if err != nil { - panic("failed to generate test fixture: " + err.Error()) - } - - return certBytes -} - -func (c *cryptoBundle) generateCertificateExpired(crt *cmapi.Certificate) []byte { - csr := c.generateTestCSR(crt) - certificateRequest := &cmapi.CertificateRequest{ - Spec: cmapi.CertificateRequestSpec{ - CSRPEM: csr, - Duration: crt.Spec.Duration, - IssuerRef: crt.Spec.IssuerRef, - IsCA: crt.Spec.IsCA, - }, - } - - unsignedCert, err := pki.GenerateTemplateFromCertificateRequest(certificateRequest) - if err != nil { - panic("failed to generate test fixture: " + err.Error()) - } - - nowTime := fixedClock.Now() - duration := unsignedCert.NotAfter.Sub(unsignedCert.NotBefore) - unsignedCert.NotBefore = nowTime.Add(-1 * time.Hour).Add(-1 * duration) - unsignedCert.NotAfter = nowTime.Add(-1 * time.Hour) - - certBytes, _, err := pki.SignCertificate(unsignedCert, unsignedCert, c.privateKey.Public(), c.privateKey) - if err != nil { - panic("failed to generate test fixture: " + err.Error()) - } - - return certBytes -} - -func (c *cryptoBundle) generateCertificateTemporary(crt *cmapi.Certificate) []byte { - d, err := generateLocallySignedTemporaryCertificate(crt, c.privateKeyBytes) - if err != nil { - panic("failed to generate test fixture: " + err.Error()) - } - return d -} - -func certificateNotAfter(b []byte) time.Time { - cert, err := pki.DecodeX509CertificateBytes(b) - if err != nil { - panic("failed to decode certificate: " + err.Error()) - } - return cert.NotAfter -} - -func testGeneratePrivateKeyBytesFn(b []byte) generatePrivateKeyBytesFn { - return func(context.Context, *cmapi.Certificate) ([]byte, error) { - return b, nil - } -} - -func testGenerateCSRFn(b []byte) generateCSRFn { - return func(_ *cmapi.Certificate, _ []byte) ([]byte, error) { - return b, nil - } -} - -func testLocalTemporarySignerFn(b []byte) localTemporarySignerFn { - return func(crt *cmapi.Certificate, pk []byte) ([]byte, error) { - return b, nil - } -} - -func TestBuildCertificateRequest(t *testing.T) { - baseCert := gen.Certificate("test", - gen.SetCertificateIssuer(cmmeta.ObjectReference{Name: "ca-issuer", Kind: "Issuer", Group: "not-empty"}), - gen.SetCertificateSecretName("output"), - gen.SetCertificateRenewBefore(time.Hour*36), - gen.SetCertificateDNSNames("example.com"), - ) - exampleBundle := mustCreateCryptoBundle(t, gen.CertificateFrom(baseCert, - gen.SetCertificateDNSNames("example.com"), - )) - - tests := map[string]struct { - crt *cmapi.Certificate - name string - pk []byte - expectedErr bool - - expectedCertificateRequestAnnotations map[string]string - }{ - "a bad private key should error": { - crt: baseCert, - pk: []byte("bad key"), - name: "test", - expectedErr: true, - - expectedCertificateRequestAnnotations: nil, - }, - "a good certificate should always have annotations set": { - crt: baseCert, - pk: exampleBundle.privateKeyBytes, - name: "test", - expectedErr: false, - - expectedCertificateRequestAnnotations: map[string]string{ - cmapi.CertificateRequestPrivateKeyAnnotationKey: baseCert.Spec.SecretName, - cmapi.CertificateNameKey: baseCert.Name, - }, - }, - } - - for name, test := range tests { - c := &certificateRequestManager{ - generateCSR: generateCSRImpl, - } - - cr, err := c.buildCertificateRequest(nil, test.crt, test.name, test.pk) - if err != nil && !test.expectedErr { - t.Errorf("expected no error but got: %s", err) - } - - if err == nil && test.expectedErr { - t.Error("expected an error but got 'nil'") - } - - if cr == nil { - continue - } - - // check for annotations - if !reflect.DeepEqual(cr.Annotations, test.expectedCertificateRequestAnnotations) { - t.Errorf("%s: got unexpected resulting certificate request annotations, exp=%+v got=%+v", - name, test.expectedCertificateRequestAnnotations, cr.Annotations) - } - } -} - -func TestProcessCertificate(t *testing.T) { - baseCert := gen.Certificate("test", - gen.SetCertificateIssuer(cmmeta.ObjectReference{Name: "test", Kind: "something", Group: "not-empty"}), - gen.SetCertificateSecretName("output"), - gen.SetCertificateRenewBefore(time.Hour*36), - ) - exampleBundle1 := mustCreateCryptoBundle(t, gen.CertificateFrom(baseCert, - gen.SetCertificateDNSNames("example.com"), - )) - exampleECBundle := mustCreateCryptoBundle(t, gen.CertificateFrom(baseCert, - gen.SetCertificateDNSNames("example.com"), - gen.SetCertificateKeyAlgorithm(cmapi.ECDSAKeyAlgorithm), - )) - - tests := map[string]testT{ - "generate a private key and create a new secret if one does not exist": { - certificate: exampleBundle1.certificate, - generatePrivateKeyBytes: testGeneratePrivateKeyBytesFn(exampleBundle1.privateKeyBytes), - builder: &testpkg.Builder{ - CertManagerObjects: []runtime.Object{ - exampleBundle1.certificate, - }, - ExpectedActions: []testpkg.Action{ - testpkg.NewAction(coretesting.NewCreateAction( - corev1.SchemeGroupVersion.WithResource("secrets"), - gen.DefaultTestNamespace, - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: gen.DefaultTestNamespace, - Name: "output", - Annotations: map[string]string{ - cmapi.CertificateNameKey: "test", - cmapi.IssuerKindAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Kind, - cmapi.IssuerNameAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Name, - }, - }, - Data: map[string][]byte{ - corev1.TLSCertKey: nil, - corev1.TLSPrivateKeyKey: exampleBundle1.privateKeyBytes, - }, - Type: corev1.SecretTypeTLS, - }, - )), - }, - ExpectedEvents: []string{"Normal GeneratedKey Generated a new private key"}, - }, - }, - "generate a private key and update an existing secret if one already exists": { - certificate: exampleBundle1.certificate, - generatePrivateKeyBytes: testGeneratePrivateKeyBytesFn(exampleBundle1.privateKeyBytes), - builder: &testpkg.Builder{ - KubeObjects: []runtime.Object{ - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: gen.DefaultTestNamespace, - Name: "output", - Annotations: map[string]string{ - "custom-annotation": "value", - }, - }, - Type: corev1.SecretTypeTLS, - }, - }, - CertManagerObjects: []runtime.Object{ - exampleBundle1.certificate, - }, - ExpectedActions: []testpkg.Action{ - testpkg.NewAction(coretesting.NewUpdateAction( - corev1.SchemeGroupVersion.WithResource("secrets"), - gen.DefaultTestNamespace, - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: gen.DefaultTestNamespace, - Name: "output", - Annotations: map[string]string{ - "custom-annotation": "value", - cmapi.CertificateNameKey: "test", - cmapi.IssuerKindAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Kind, - cmapi.IssuerNameAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Name, - }, - }, - Data: map[string][]byte{ - corev1.TLSCertKey: nil, - corev1.TLSPrivateKeyKey: exampleBundle1.privateKeyBytes, - }, - Type: corev1.SecretTypeTLS, - }, - )), - }, - ExpectedEvents: []string{"Normal GeneratedKey Generated a new private key"}, - }, - }, - "generate a new private key and update the Secret if the existing private key data is garbage": { - certificate: exampleBundle1.certificate, - generatePrivateKeyBytes: testGeneratePrivateKeyBytesFn(exampleBundle1.privateKeyBytes), - builder: &testpkg.Builder{ - KubeObjects: []runtime.Object{ - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: gen.DefaultTestNamespace, - Name: "output", - Annotations: map[string]string{ - "custom-annotation": "value", - }, - }, - Type: corev1.SecretTypeTLS, - Data: map[string][]byte{ - corev1.TLSPrivateKeyKey: []byte("invalid"), - }, - }, - }, - CertManagerObjects: []runtime.Object{ - exampleBundle1.certificate, - }, - ExpectedActions: []testpkg.Action{ - testpkg.NewAction(coretesting.NewUpdateAction( - corev1.SchemeGroupVersion.WithResource("secrets"), - gen.DefaultTestNamespace, - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: gen.DefaultTestNamespace, - Name: "output", - Annotations: map[string]string{ - "custom-annotation": "value", - cmapi.CertificateNameKey: "test", - cmapi.IssuerKindAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Kind, - cmapi.IssuerNameAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Name, - }, - }, - Data: map[string][]byte{ - corev1.TLSCertKey: nil, - corev1.TLSPrivateKeyKey: exampleBundle1.privateKeyBytes, - }, - Type: corev1.SecretTypeTLS, - }, - )), - }, - ExpectedEvents: []string{`Normal GeneratedKey Generated a new private key`}, - }, - }, - "generate a new private key and update the Secret if the existing private key data has a differing keyAlgorithm": { - certificate: exampleBundle1.certificate, - generatePrivateKeyBytes: testGeneratePrivateKeyBytesFn(exampleBundle1.privateKeyBytes), - builder: &testpkg.Builder{ - KubeObjects: []runtime.Object{ - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: gen.DefaultTestNamespace, - Name: "output", - Annotations: map[string]string{ - "custom-annotation": "value", - }, - }, - Type: corev1.SecretTypeTLS, - Data: map[string][]byte{ - corev1.TLSPrivateKeyKey: exampleECBundle.privateKeyBytes, - }, - }, - }, - CertManagerObjects: []runtime.Object{ - exampleBundle1.certificate, - }, - ExpectedActions: []testpkg.Action{ - testpkg.NewAction(coretesting.NewUpdateAction( - corev1.SchemeGroupVersion.WithResource("secrets"), - gen.DefaultTestNamespace, - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: gen.DefaultTestNamespace, - Name: "output", - Annotations: map[string]string{ - "custom-annotation": "value", - cmapi.CertificateNameKey: "test", - cmapi.IssuerKindAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Kind, - cmapi.IssuerNameAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Name, - }, - }, - Data: map[string][]byte{ - corev1.TLSCertKey: nil, - corev1.TLSPrivateKeyKey: exampleBundle1.privateKeyBytes, - }, - Type: corev1.SecretTypeTLS, - }, - )), - }, - ExpectedEvents: []string{"Normal GeneratedKey Generated a new private key"}, - }, - }, - "create a new certificatesigningrequest resource if the secret contains a private key but no certificate": { - certificate: exampleBundle1.certificate, - generateCSR: testGenerateCSRFn(exampleBundle1.csrBytes), - builder: &testpkg.Builder{ - KubeObjects: []runtime.Object{ - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: gen.DefaultTestNamespace, - Name: "output", - Annotations: map[string]string{ - "custom-annotation": "value", - cmapi.CertificateNameKey: "test", - cmapi.IssuerKindAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Kind, - cmapi.IssuerNameAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Name, - }, - }, - Data: map[string][]byte{ - corev1.TLSCertKey: nil, - corev1.TLSPrivateKeyKey: exampleBundle1.privateKeyBytes, - cmmeta.TLSCAKey: nil, - }, - Type: corev1.SecretTypeTLS, - }, - }, - CertManagerObjects: []runtime.Object{ - exampleBundle1.certificate, - }, - ExpectedActions: []testpkg.Action{ - testpkg.NewAction(coretesting.NewCreateAction( - cmapi.SchemeGroupVersion.WithResource("certificaterequests"), - gen.DefaultTestNamespace, - exampleBundle1.certificateRequest, - )), - }, - ExpectedEvents: []string{`Normal Requested Created new CertificateRequest resource "test-850937773"`}, - }, - }, - "delete an existing certificaterequest that does not have matching dnsnames": { - certificate: exampleBundle1.certificate, - generateCSR: testGenerateCSRFn(exampleBundle1.csrBytes), - builder: &testpkg.Builder{ - KubeObjects: []runtime.Object{ - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: gen.DefaultTestNamespace, - Name: "output", - Annotations: map[string]string{ - "custom-annotation": "value", - cmapi.CertificateNameKey: "test", - cmapi.IssuerKindAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Kind, - cmapi.IssuerNameAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Name, - }, - }, - Data: map[string][]byte{ - corev1.TLSCertKey: nil, - corev1.TLSPrivateKeyKey: exampleBundle1.privateKeyBytes, - cmmeta.TLSCAKey: nil, - }, - Type: corev1.SecretTypeTLS, - }, - }, - CertManagerObjects: []runtime.Object{ - exampleBundle1.certificate, - gen.CertificateRequestFrom(exampleBundle1.certificateRequest, - gen.SetCertificateRequestName("not-expected-name"), - gen.SetCertificateRequestCSR( - exampleBundle1.generateTestCSR(gen.CertificateFrom(exampleBundle1.certificate, - gen.SetCertificateDNSNames("notexample.com"), - )), - ), - ), - }, - ExpectedActions: []testpkg.Action{ - testpkg.NewAction(coretesting.NewDeleteAction( - cmapi.SchemeGroupVersion.WithResource("certificaterequests"), - gen.DefaultTestNamespace, - "not-expected-name", - )), - testpkg.NewAction(coretesting.NewCreateAction( - cmapi.SchemeGroupVersion.WithResource("certificaterequests"), - gen.DefaultTestNamespace, - exampleBundle1.certificateRequest, - )), - }, - ExpectedEvents: []string{`Normal Requested Created new CertificateRequest resource "test-850937773"`}, - }, - }, - "do nothing and wait if an up to date certificaterequest resource exists and is not Ready": { - certificate: exampleBundle1.certificate, - builder: &testpkg.Builder{ - KubeObjects: []runtime.Object{ - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: gen.DefaultTestNamespace, - Name: "output", - Annotations: map[string]string{ - "custom-annotation": "value", - cmapi.CertificateNameKey: "test", - cmapi.IssuerKindAnnotationKey: "Issuer", - cmapi.IssuerNameAnnotationKey: "test", - }, - }, - Data: map[string][]byte{ - corev1.TLSCertKey: nil, - corev1.TLSPrivateKeyKey: exampleBundle1.privateKeyBytes, - cmmeta.TLSCAKey: nil, - }, - Type: corev1.SecretTypeTLS, - }, - }, - CertManagerObjects: []runtime.Object{ - exampleBundle1.certificate, - exampleBundle1.certificateRequest, - }, - }, - }, - "create a new CertificateRequest if existing Certificate expires soon": { - certificate: exampleBundle1.certificate, - generateCSR: testGenerateCSRFn(exampleBundle1.csrBytes), - builder: &testpkg.Builder{ - KubeObjects: []runtime.Object{ - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: gen.DefaultTestNamespace, - Name: "output", - Annotations: map[string]string{ - "custom-annotation": "value", - cmapi.CertificateNameKey: "test", - cmapi.IssuerKindAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Kind, - cmapi.IssuerNameAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Name, - cmapi.IPSANAnnotationKey: "", - cmapi.AltNamesAnnotationKey: "example.com", - cmapi.CommonNameAnnotationKey: "", - cmapi.URISANAnnotationKey: "", - }, - }, - Data: map[string][]byte{ - corev1.TLSCertKey: exampleBundle1.generateCertificateExpiring1H(exampleBundle1.certificate), - corev1.TLSPrivateKeyKey: exampleBundle1.privateKeyBytes, - cmmeta.TLSCAKey: nil, - }, - Type: corev1.SecretTypeTLS, - }, - }, - CertManagerObjects: []runtime.Object{ - exampleBundle1.certificate, - }, - ExpectedActions: []testpkg.Action{ - testpkg.NewAction(coretesting.NewCreateAction( - cmapi.SchemeGroupVersion.WithResource("certificaterequests"), - gen.DefaultTestNamespace, - exampleBundle1.certificateRequest, - )), - }, - ExpectedEvents: []string{`Normal Requested Created new CertificateRequest resource "test-850937773"`}, - }, - }, - "do nothing if existing x509 certificate is up to date and valid for the cert and no other CertificateRequest exists": { - certificate: exampleBundle1.certificate, - builder: &testpkg.Builder{ - KubeObjects: []runtime.Object{ - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: gen.DefaultTestNamespace, - Name: "output", - Annotations: map[string]string{ - "custom-annotation": "value", - cmapi.CertificateNameKey: "test", - cmapi.IssuerKindAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Kind, - cmapi.IssuerNameAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Name, - cmapi.IPSANAnnotationKey: "", - cmapi.AltNamesAnnotationKey: "example.com", - cmapi.CommonNameAnnotationKey: "", - cmapi.URISANAnnotationKey: "", - }, - }, - Data: map[string][]byte{ - corev1.TLSCertKey: exampleBundle1.certBytes, - corev1.TLSPrivateKeyKey: exampleBundle1.privateKeyBytes, - }, - Type: corev1.SecretTypeTLS, - }, - }, - CertManagerObjects: []runtime.Object{ - exampleBundle1.certificate, - }, - }, - }, - "update secret resource metadata if existing certificate is valid but missing annotations": { - certificate: exampleBundle1.certificate, - builder: &testpkg.Builder{ - KubeObjects: []runtime.Object{ - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: gen.DefaultTestNamespace, - Name: "output", - Annotations: map[string]string{ - "custom-annotation": "value", - cmapi.IssuerKindAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Kind, - cmapi.IssuerNameAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Name, - }, - }, - Data: map[string][]byte{ - corev1.TLSCertKey: exampleBundle1.certBytes, - corev1.TLSPrivateKeyKey: exampleBundle1.privateKeyBytes, - cmmeta.TLSCAKey: nil, - }, - Type: corev1.SecretTypeTLS, - }, - }, - CertManagerObjects: []runtime.Object{ - exampleBundle1.certificate, - }, - ExpectedActions: []testpkg.Action{ - testpkg.NewAction(coretesting.NewUpdateAction( - corev1.SchemeGroupVersion.WithResource("secrets"), - gen.DefaultTestNamespace, - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: gen.DefaultTestNamespace, - Name: "output", - Annotations: map[string]string{ - "custom-annotation": "value", - cmapi.CertificateNameKey: "test", - cmapi.IssuerKindAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Kind, - cmapi.IssuerNameAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Name, - cmapi.IPSANAnnotationKey: "", - cmapi.AltNamesAnnotationKey: "example.com", - cmapi.CommonNameAnnotationKey: "", - cmapi.URISANAnnotationKey: "", - }, - }, - Data: map[string][]byte{ - corev1.TLSCertKey: exampleBundle1.certBytes, - corev1.TLSPrivateKeyKey: exampleBundle1.privateKeyBytes, - }, - Type: corev1.SecretTypeTLS, - }, - )), - }, - ExpectedEvents: []string{"Normal UpdateMeta Updated metadata on Secret resource"}, - }, - }, - "update the Secret resource with the signed certificate if the CertificateRequest is ready": { - certificate: exampleBundle1.certificate, - builder: &testpkg.Builder{ - KubeObjects: []runtime.Object{ - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: gen.DefaultTestNamespace, - Name: "output", - Annotations: map[string]string{ - "custom-annotation": "value", - cmapi.CertificateNameKey: "test", - cmapi.IssuerKindAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Kind, - cmapi.IssuerNameAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Name, - }, - }, - Data: map[string][]byte{ - corev1.TLSCertKey: nil, - corev1.TLSPrivateKeyKey: exampleBundle1.privateKeyBytes, - cmmeta.TLSCAKey: nil, - }, - Type: corev1.SecretTypeTLS, - }, - }, - CertManagerObjects: []runtime.Object{ - exampleBundle1.certificate, - exampleBundle1.certificateRequestReady, - }, - ExpectedActions: []testpkg.Action{ - testpkg.NewAction(coretesting.NewUpdateAction( - corev1.SchemeGroupVersion.WithResource("secrets"), - gen.DefaultTestNamespace, - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: gen.DefaultTestNamespace, - Name: "output", - Annotations: map[string]string{ - "custom-annotation": "value", - cmapi.CertificateNameKey: "test", - cmapi.IssuerKindAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Kind, - cmapi.IssuerNameAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Name, - cmapi.IPSANAnnotationKey: "", - cmapi.AltNamesAnnotationKey: "example.com", - cmapi.CommonNameAnnotationKey: "", - cmapi.URISANAnnotationKey: "", - }, - }, - Data: map[string][]byte{ - corev1.TLSCertKey: exampleBundle1.certBytes, - corev1.TLSPrivateKeyKey: exampleBundle1.privateKeyBytes, - }, - Type: corev1.SecretTypeTLS, - }, - )), - }, - ExpectedEvents: []string{"Normal Issued Certificate issued successfully"}, - }, - }, - "do nothing if the Secret resource is not in need of issuance even if a Ready CertificateRequest exists and contains different data": { - certificate: exampleBundle1.certificate, - builder: &testpkg.Builder{ - KubeObjects: []runtime.Object{ - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: gen.DefaultTestNamespace, - Name: "output", - Annotations: map[string]string{ - "custom-annotation": "value", - cmapi.CertificateNameKey: exampleBundle1.certificate.Name, - cmapi.IssuerKindAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Kind, - cmapi.IssuerNameAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Name, - cmapi.IPSANAnnotationKey: "", - cmapi.AltNamesAnnotationKey: "example.com", - cmapi.CommonNameAnnotationKey: "", - cmapi.URISANAnnotationKey: "", - }, - }, - Data: map[string][]byte{ - corev1.TLSCertKey: exampleBundle1.generateTestCertificate(exampleBundle1.certificate, nil), - corev1.TLSPrivateKeyKey: exampleBundle1.privateKeyBytes, - }, - Type: corev1.SecretTypeTLS, - }, - }, - CertManagerObjects: []runtime.Object{ - exampleBundle1.certificate, - exampleBundle1.certificateRequestReady, - }, - }, - }, - "issue new certificate if existing certificate data is garbage": { - certificate: exampleBundle1.certificate, - builder: &testpkg.Builder{ - KubeObjects: []runtime.Object{ - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: gen.DefaultTestNamespace, - Name: "output", - Annotations: map[string]string{ - "custom-annotation": "value", - cmapi.CertificateNameKey: "test", - cmapi.IssuerKindAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Kind, - cmapi.IssuerNameAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Name, - cmapi.IPSANAnnotationKey: "", - cmapi.AltNamesAnnotationKey: "example.com", - cmapi.CommonNameAnnotationKey: "", - cmapi.URISANAnnotationKey: "", - }, - }, - Data: map[string][]byte{ - corev1.TLSCertKey: []byte("invalid"), - corev1.TLSPrivateKeyKey: exampleBundle1.privateKeyBytes, - cmmeta.TLSCAKey: nil, - }, - Type: corev1.SecretTypeTLS, - }, - }, - CertManagerObjects: []runtime.Object{ - exampleBundle1.certificate, - exampleBundle1.certificateRequestReady, - }, - ExpectedActions: []testpkg.Action{ - testpkg.NewAction(coretesting.NewUpdateAction( - corev1.SchemeGroupVersion.WithResource("secrets"), - gen.DefaultTestNamespace, - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: gen.DefaultTestNamespace, - Name: "output", - Annotations: map[string]string{ - "custom-annotation": "value", - cmapi.CertificateNameKey: "test", - cmapi.IssuerKindAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Kind, - cmapi.IssuerNameAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Name, - cmapi.IPSANAnnotationKey: "", - cmapi.AltNamesAnnotationKey: "example.com", - cmapi.CommonNameAnnotationKey: "", - cmapi.URISANAnnotationKey: "", - }, - }, - Data: map[string][]byte{ - corev1.TLSCertKey: exampleBundle1.certBytes, - corev1.TLSPrivateKeyKey: exampleBundle1.privateKeyBytes, - }, - Type: corev1.SecretTypeTLS, - }, - )), - }, - ExpectedEvents: []string{"Normal Issued Certificate issued successfully"}, - }, - }, - "delete existing certificate request if existing one contains a certificate nearing expiry": { - certificate: exampleBundle1.certificate, - builder: &testpkg.Builder{ - KubeObjects: []runtime.Object{ - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: gen.DefaultTestNamespace, - Name: "output", - Annotations: map[string]string{ - "custom-annotation": "value", - cmapi.CertificateNameKey: "test", - cmapi.IssuerKindAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Kind, - cmapi.IssuerNameAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Name, - cmapi.IPSANAnnotationKey: "", - cmapi.AltNamesAnnotationKey: "example.com", - cmapi.CommonNameAnnotationKey: "", - cmapi.URISANAnnotationKey: "", - }, - }, - Data: map[string][]byte{ - corev1.TLSCertKey: exampleBundle1.generateCertificateExpiring1H(exampleBundle1.certificate), - corev1.TLSPrivateKeyKey: exampleBundle1.privateKeyBytes, - cmmeta.TLSCAKey: nil, - }, - Type: corev1.SecretTypeTLS, - }, - }, - CertManagerObjects: []runtime.Object{ - exampleBundle1.certificate, - gen.CertificateRequestFrom(exampleBundle1.certificateRequestReady, - gen.SetCertificateRequestCertificate( - exampleBundle1.generateCertificateExpiring1H(exampleBundle1.certificate), - ), - ), - }, - ExpectedActions: []testpkg.Action{ - testpkg.NewAction(coretesting.NewDeleteAction( - cmapi.SchemeGroupVersion.WithResource("certificaterequests"), - gen.DefaultTestNamespace, - exampleBundle1.certificateRequestReady.Name, - )), - }, - }, - }, - "delete existing certificate request if existing one contains a csr not valid for stored private key": { - certificate: exampleBundle1.certificate, - builder: &testpkg.Builder{ - KubeObjects: []runtime.Object{ - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: gen.DefaultTestNamespace, - Name: "output", - Annotations: map[string]string{ - "custom-annotation": "value", - cmapi.CertificateNameKey: "test", - cmapi.IssuerKindAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Kind, - cmapi.IssuerNameAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Name, - cmapi.IPSANAnnotationKey: "", - cmapi.AltNamesAnnotationKey: "example.com", - cmapi.CommonNameAnnotationKey: "", - cmapi.URISANAnnotationKey: "", - }, - }, - Data: map[string][]byte{ - corev1.TLSCertKey: exampleBundle1.generateCertificateExpiring1H(exampleBundle1.certificate), - corev1.TLSPrivateKeyKey: exampleBundle1.privateKeyBytes, - cmmeta.TLSCAKey: nil, - }, - Type: corev1.SecretTypeTLS, - }, - }, - CertManagerObjects: []runtime.Object{ - exampleBundle1.certificate, - gen.CertificateRequestFrom(exampleBundle1.certificateRequestReady, - gen.SetCertificateRequestCSR(exampleECBundle.csrBytes), - ), - }, - ExpectedActions: []testpkg.Action{ - testpkg.NewAction(coretesting.NewDeleteAction( - cmapi.SchemeGroupVersion.WithResource("certificaterequests"), - gen.DefaultTestNamespace, - exampleBundle1.certificateRequestReady.Name, - )), - }, - ExpectedEvents: []string{`Normal PrivateKeyLost Lost private key for CertificateRequest "test-850937773", deleting old resource`}, - }, - }, - "if a temporary certificate exists but the request has failed and contains no FailureTime, delete the request to cause a re-sync and retry": { - certificate: exampleBundle1.certificate, - builder: &testpkg.Builder{ - KubeObjects: []runtime.Object{ - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: exampleBundle1.certificate.Spec.SecretName, - Namespace: exampleBundle1.certificate.Namespace, - Annotations: map[string]string{ - cmapi.IssuerNameAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Name, - cmapi.IssuerKindAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Kind, - }, - }, - Data: map[string][]byte{ - corev1.TLSPrivateKeyKey: exampleBundle1.privateKeyBytes, - corev1.TLSCertKey: exampleBundle1.localTemporaryCertificateBytes, - }, - }, - }, - CertManagerObjects: []runtime.Object{ - exampleBundle1.certificate, - exampleBundle1.certificateRequestFailed, - }, - ExpectedActions: []testpkg.Action{ - testpkg.NewAction(coretesting.NewDeleteAction( - cmapi.SchemeGroupVersion.WithResource("certificaterequests"), - gen.DefaultTestNamespace, - exampleBundle1.certificateRequestFailed.Name, - )), - }, - ExpectedEvents: []string{`Normal CertificateRequestRetry The failed CertificateRequest "test-850937773" will be retried now`}, - }, - }, - "if a temporary certificate exists but the request has failed and contains no FailureTime, but does contain a InvalidRequest condition then don't retry": { - certificate: exampleBundle1.certificate, - builder: &testpkg.Builder{ - KubeObjects: []runtime.Object{ - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: exampleBundle1.certificate.Spec.SecretName, - Namespace: exampleBundle1.certificate.Namespace, - Annotations: map[string]string{ - cmapi.IssuerNameAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Name, - cmapi.IssuerKindAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Kind, - }, - }, - Data: map[string][]byte{ - corev1.TLSPrivateKeyKey: exampleBundle1.privateKeyBytes, - corev1.TLSCertKey: exampleBundle1.localTemporaryCertificateBytes, - }, - }, - }, - CertManagerObjects: []runtime.Object{ - exampleBundle1.certificate, - exampleBundle1.certificateRequestFailedInvalidRequest, - }, - ExpectedActions: []testpkg.Action{}, - ExpectedEvents: []string{`Warning CertificateRequestInvalidRequest The failed CertificateRequest "test-850937773" is an invalid request and will no longer be processed`}, - }, - }, - "if a temporary certificate exists but the request has failed and contains a FailureTime over an hour in the past, delete the request to cause a re-sync and retry": { - certificate: exampleBundle1.certificate, - builder: &testpkg.Builder{ - KubeObjects: []runtime.Object{ - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: exampleBundle1.certificate.Spec.SecretName, - Namespace: exampleBundle1.certificate.Namespace, - Annotations: map[string]string{ - cmapi.IssuerNameAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Name, - cmapi.IssuerKindAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Kind, - }, - }, - Data: map[string][]byte{ - corev1.TLSPrivateKeyKey: exampleBundle1.privateKeyBytes, - corev1.TLSCertKey: exampleBundle1.localTemporaryCertificateBytes, - }, - }, - }, - CertManagerObjects: []runtime.Object{ - exampleBundle1.certificate, - gen.CertificateRequestFrom(exampleBundle1.certificateRequestFailed, - gen.SetCertificateRequestFailureTime(metav1.Time{ - Time: fixedClockStart.Add(-time.Minute * 61), - })), - }, - ExpectedActions: []testpkg.Action{ - testpkg.NewAction(coretesting.NewDeleteAction( - cmapi.SchemeGroupVersion.WithResource("certificaterequests"), - gen.DefaultTestNamespace, - exampleBundle1.certificateRequestFailed.Name, - )), - }, - ExpectedEvents: []string{`Normal CertificateRequestRetry The failed CertificateRequest "test-850937773" will be retried now`}, - }, - }, - "if a temporary certificate exists but the request has failed and contains a FailureTime over an hour in the past but has an InvalidRequest condition, then don't retry": { - certificate: exampleBundle1.certificate, - builder: &testpkg.Builder{ - KubeObjects: []runtime.Object{ - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: exampleBundle1.certificate.Spec.SecretName, - Namespace: exampleBundle1.certificate.Namespace, - Annotations: map[string]string{ - cmapi.IssuerNameAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Name, - cmapi.IssuerKindAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Kind, - }, - }, - Data: map[string][]byte{ - corev1.TLSPrivateKeyKey: exampleBundle1.privateKeyBytes, - corev1.TLSCertKey: exampleBundle1.localTemporaryCertificateBytes, - }, - }, - }, - CertManagerObjects: []runtime.Object{ - exampleBundle1.certificate, - gen.CertificateRequestFrom(exampleBundle1.certificateRequestFailedInvalidRequest, - gen.SetCertificateRequestFailureTime(metav1.Time{ - Time: fixedClockStart.Add(-time.Minute * 61), - })), - }, - ExpectedActions: []testpkg.Action{}, - ExpectedEvents: []string{`Warning CertificateRequestInvalidRequest The failed CertificateRequest "test-850937773" is an invalid request and will no longer be processed`}, - }, - }, - "if a temporary certificate exists but the request has failed and contains a FailureTime less than an hour in the past, reschedule a re-sync in an hour": { - certificate: exampleBundle1.certificate, - builder: &testpkg.Builder{ - KubeObjects: []runtime.Object{ - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: exampleBundle1.certificate.Spec.SecretName, - Namespace: exampleBundle1.certificate.Namespace, - Annotations: map[string]string{ - cmapi.IssuerNameAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Name, - cmapi.IssuerKindAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Kind, - }, - }, - Data: map[string][]byte{ - corev1.TLSPrivateKeyKey: exampleBundle1.privateKeyBytes, - corev1.TLSCertKey: exampleBundle1.localTemporaryCertificateBytes, - }, - }, - }, - CertManagerObjects: []runtime.Object{ - exampleBundle1.certificate, - gen.CertificateRequestFrom(exampleBundle1.certificateRequestFailed, - gen.SetCertificateRequestFailureTime(metav1.Time{ - Time: fixedClockStart.Add(-time.Minute * 59), - })), - }, - ExpectedActions: []testpkg.Action{}, - // We don't fire an event here as this could be called multiple times in quick succession - ExpectedEvents: []string{}, - }, - }, - "if a temporary certificate exists but the request has failed and contains a FailureTime less than an hour in the past but has an InvalidRequest condition time, don't re-schedule sync": { - certificate: exampleBundle1.certificate, - builder: &testpkg.Builder{ - KubeObjects: []runtime.Object{ - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: exampleBundle1.certificate.Spec.SecretName, - Namespace: exampleBundle1.certificate.Namespace, - Annotations: map[string]string{ - cmapi.IssuerNameAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Name, - cmapi.IssuerKindAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Kind, - }, - }, - Data: map[string][]byte{ - corev1.TLSPrivateKeyKey: exampleBundle1.privateKeyBytes, - corev1.TLSCertKey: exampleBundle1.localTemporaryCertificateBytes, - }, - }, - }, - CertManagerObjects: []runtime.Object{ - exampleBundle1.certificate, - gen.CertificateRequestFrom(exampleBundle1.certificateRequestFailedInvalidRequest, - gen.SetCertificateRequestFailureTime(metav1.Time{ - Time: fixedClockStart.Add(-time.Minute * 59), - })), - }, - ExpectedActions: []testpkg.Action{}, - ExpectedEvents: []string{`Warning CertificateRequestInvalidRequest The failed CertificateRequest "test-850937773" is an invalid request and will no longer be processed`}, - }, - }, - "with secret owner references enabled, should set the ownerReference field when generating a new private key Secret": { - certificate: exampleBundle1.certificate, - generatePrivateKeyBytes: testGeneratePrivateKeyBytesFn(exampleBundle1.privateKeyBytes), - builder: &testpkg.Builder{ - Context: &controller.Context{ - RootContext: context.Background(), - CertificateOptions: controller.CertificateOptions{ - EnableOwnerRef: true, - }, - }, - KubeObjects: []runtime.Object{}, - CertManagerObjects: []runtime.Object{}, - ExpectedActions: []testpkg.Action{ - testpkg.NewAction(coretesting.NewCreateAction( - corev1.SchemeGroupVersion.WithResource("secrets"), - gen.DefaultTestNamespace, - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: gen.DefaultTestNamespace, - Name: "output", - Annotations: map[string]string{ - cmapi.CertificateNameKey: "test", - cmapi.IssuerKindAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Kind, - cmapi.IssuerNameAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Name, - }, - OwnerReferences: []metav1.OwnerReference{*metav1.NewControllerRef(exampleBundle1.certificate, certificateGvk)}, - }, - Data: map[string][]byte{ - corev1.TLSCertKey: nil, - corev1.TLSPrivateKeyKey: exampleBundle1.privateKeyBytes, - }, - Type: corev1.SecretTypeTLS, - }, - )), - }, - // We don't fire an event here as this could be called multiple times in quick succession - ExpectedEvents: []string{ - "Normal GeneratedKey Generated a new private key", - }, - }, - }, - "with secret owner references enabled, should NOT set the ownerReference field when generating a new private key Secret if one already exists": { - certificate: exampleBundle1.certificate, - generatePrivateKeyBytes: testGeneratePrivateKeyBytesFn(exampleBundle1.privateKeyBytes), - builder: &testpkg.Builder{ - Context: &controller.Context{ - RootContext: context.Background(), - CertificateOptions: controller.CertificateOptions{ - EnableOwnerRef: true, - }, - }, - KubeObjects: []runtime.Object{ - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: gen.DefaultTestNamespace, - Name: "output", - }, - Type: corev1.SecretTypeTLS, - }, - }, - CertManagerObjects: []runtime.Object{}, - ExpectedActions: []testpkg.Action{ - testpkg.NewAction(coretesting.NewUpdateAction( - corev1.SchemeGroupVersion.WithResource("secrets"), - gen.DefaultTestNamespace, - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: gen.DefaultTestNamespace, - Name: "output", - Annotations: map[string]string{ - cmapi.CertificateNameKey: "test", - cmapi.IssuerKindAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Kind, - cmapi.IssuerNameAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Name, - }, - }, - Data: map[string][]byte{ - corev1.TLSCertKey: nil, - corev1.TLSPrivateKeyKey: exampleBundle1.privateKeyBytes, - }, - Type: corev1.SecretTypeTLS, - }, - )), - }, - // We don't fire an event here as this could be called multiple times in quick succession - ExpectedEvents: []string{ - "Normal GeneratedKey Generated a new private key", - }, - }, - }, - } - - for name, test := range tests { - t.Run(name, func(t *testing.T) { - fixedClock.SetTime(fixedClockStart) - test.builder.Clock = fixedClock - - runTest(t, test) - }) - } -} - -func TestTemporaryCertificateEnabled(t *testing.T) { - baseCert := gen.Certificate("test", - gen.SetCertificateIssuer(cmmeta.ObjectReference{Name: "test", Kind: "something", Group: "not-empty"}), - gen.SetCertificateSecretName("output"), - gen.SetCertificateRenewBefore(time.Hour*36), - ) - if baseCert.Annotations == nil { - baseCert.Annotations = make(map[string]string) - } - baseCert.Annotations[cmapi.IssueTemporaryCertificateAnnotation] = "true" - exampleBundle1 := mustCreateCryptoBundle(t, gen.CertificateFrom(baseCert, - gen.SetCertificateDNSNames("example.com"), - )) - - tests := map[string]testT{ - "issue a temporary certificate if no existing request exists and secret does not contain a cert": { - certificate: exampleBundle1.certificate, - builder: &testpkg.Builder{ - KubeObjects: []runtime.Object{ - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: gen.DefaultTestNamespace, - Name: "output", - Annotations: map[string]string{ - "custom-annotation": "value", - cmapi.CertificateNameKey: "test", - cmapi.IssuerKindAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Kind, - cmapi.IssuerNameAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Name, - }, - }, - Data: map[string][]byte{ - corev1.TLSCertKey: nil, - corev1.TLSPrivateKeyKey: exampleBundle1.privateKeyBytes, - cmmeta.TLSCAKey: nil, - }, - Type: corev1.SecretTypeTLS, - }, - }, - CertManagerObjects: []runtime.Object{ - exampleBundle1.certificate, - }, - ExpectedActions: []testpkg.Action{ - testpkg.NewAction(coretesting.NewUpdateAction( - corev1.SchemeGroupVersion.WithResource("secrets"), - gen.DefaultTestNamespace, - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: gen.DefaultTestNamespace, - Name: "output", - Annotations: map[string]string{ - "custom-annotation": "value", - cmapi.CertificateNameKey: "test", - cmapi.IssuerKindAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Kind, - cmapi.IssuerNameAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Name, - cmapi.IPSANAnnotationKey: "", - cmapi.AltNamesAnnotationKey: "example.com", - cmapi.CommonNameAnnotationKey: "", - cmapi.URISANAnnotationKey: "", - }, - }, - Data: map[string][]byte{ - corev1.TLSCertKey: exampleBundle1.localTemporaryCertificateBytes, - corev1.TLSPrivateKeyKey: exampleBundle1.privateKeyBytes, - }, - Type: corev1.SecretTypeTLS, - }, - )), - }, - ExpectedEvents: []string{`Normal TempCert Issued temporary certificate`}, - }, - }, - "issue a temporary certificate if existing request is pending and secret does not contain a cert": { - certificate: exampleBundle1.certificate, - builder: &testpkg.Builder{ - KubeObjects: []runtime.Object{ - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: gen.DefaultTestNamespace, - Name: "output", - Annotations: map[string]string{ - "custom-annotation": "value", - cmapi.CertificateNameKey: "test", - cmapi.IssuerKindAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Kind, - cmapi.IssuerNameAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Name, - }, - }, - Data: map[string][]byte{ - corev1.TLSCertKey: nil, - corev1.TLSPrivateKeyKey: exampleBundle1.privateKeyBytes, - cmmeta.TLSCAKey: nil, - }, - Type: corev1.SecretTypeTLS, - }, - }, - CertManagerObjects: []runtime.Object{ - exampleBundle1.certificate, - exampleBundle1.certificateRequest, - }, - ExpectedActions: []testpkg.Action{ - testpkg.NewAction(coretesting.NewUpdateAction( - corev1.SchemeGroupVersion.WithResource("secrets"), - gen.DefaultTestNamespace, - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: gen.DefaultTestNamespace, - Name: "output", - Annotations: map[string]string{ - "custom-annotation": "value", - cmapi.CertificateNameKey: "test", - cmapi.IssuerKindAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Kind, - cmapi.IssuerNameAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Name, - cmapi.IPSANAnnotationKey: "", - cmapi.AltNamesAnnotationKey: "example.com", - cmapi.CommonNameAnnotationKey: "", - cmapi.URISANAnnotationKey: "", - }, - }, - Data: map[string][]byte{ - corev1.TLSCertKey: exampleBundle1.localTemporaryCertificateBytes, - corev1.TLSPrivateKeyKey: exampleBundle1.privateKeyBytes, - }, - Type: corev1.SecretTypeTLS, - }, - )), - }, - ExpectedEvents: []string{`Normal TempCert Issued temporary certificate`}, - }, - }, - "issue a temporary certificate if existing request is Ready and secret does not contain a cert": { - certificate: exampleBundle1.certificate, - builder: &testpkg.Builder{ - KubeObjects: []runtime.Object{ - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: gen.DefaultTestNamespace, - Name: "output", - Annotations: map[string]string{ - "custom-annotation": "value", - cmapi.CertificateNameKey: "test", - cmapi.IssuerKindAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Kind, - cmapi.IssuerNameAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Name, - }, - }, - Data: map[string][]byte{ - corev1.TLSCertKey: nil, - corev1.TLSPrivateKeyKey: exampleBundle1.privateKeyBytes, - cmmeta.TLSCAKey: nil, - }, - Type: corev1.SecretTypeTLS, - }, - }, - CertManagerObjects: []runtime.Object{ - exampleBundle1.certificate, - exampleBundle1.certificateRequest, - }, - ExpectedActions: []testpkg.Action{ - testpkg.NewAction(coretesting.NewUpdateAction( - corev1.SchemeGroupVersion.WithResource("secrets"), - gen.DefaultTestNamespace, - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: gen.DefaultTestNamespace, - Name: "output", - Annotations: map[string]string{ - "custom-annotation": "value", - cmapi.CertificateNameKey: "test", - cmapi.IssuerKindAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Kind, - cmapi.IssuerNameAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Name, - cmapi.IPSANAnnotationKey: "", - cmapi.AltNamesAnnotationKey: "example.com", - cmapi.CommonNameAnnotationKey: "", - cmapi.URISANAnnotationKey: "", - }, - }, - Data: map[string][]byte{ - corev1.TLSCertKey: exampleBundle1.localTemporaryCertificateBytes, - corev1.TLSPrivateKeyKey: exampleBundle1.privateKeyBytes, - }, - Type: corev1.SecretTypeTLS, - }, - )), - }, - ExpectedEvents: []string{`Normal TempCert Issued temporary certificate`}, - }, - }, - "update the Secret resource with the signed certificate if the CertificateRequest is ready and contains temporary signed certificate": { - certificate: exampleBundle1.certificate, - builder: &testpkg.Builder{ - KubeObjects: []runtime.Object{ - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: gen.DefaultTestNamespace, - Name: "output", - Annotations: map[string]string{ - "custom-annotation": "value", - cmapi.CertificateNameKey: "test", - cmapi.IssuerKindAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Kind, - cmapi.IssuerNameAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Name, - cmapi.IPSANAnnotationKey: "", - cmapi.AltNamesAnnotationKey: "example.com", - cmapi.CommonNameAnnotationKey: "", - cmapi.URISANAnnotationKey: "", - }, - }, - Data: map[string][]byte{ - corev1.TLSCertKey: exampleBundle1.localTemporaryCertificateBytes, - corev1.TLSPrivateKeyKey: exampleBundle1.privateKeyBytes, - }, - Type: corev1.SecretTypeTLS, - }, - }, - CertManagerObjects: []runtime.Object{ - exampleBundle1.certificate, - exampleBundle1.certificateRequestReady, - }, - ExpectedActions: []testpkg.Action{ - testpkg.NewAction(coretesting.NewUpdateAction( - corev1.SchemeGroupVersion.WithResource("secrets"), - gen.DefaultTestNamespace, - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: gen.DefaultTestNamespace, - Name: "output", - Annotations: map[string]string{ - "custom-annotation": "value", - cmapi.CertificateNameKey: "test", - cmapi.IssuerKindAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Kind, - cmapi.IssuerNameAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Name, - cmapi.IPSANAnnotationKey: "", - cmapi.AltNamesAnnotationKey: "example.com", - cmapi.CommonNameAnnotationKey: "", - cmapi.URISANAnnotationKey: "", - }, - }, - Data: map[string][]byte{ - corev1.TLSCertKey: exampleBundle1.certBytes, - corev1.TLSPrivateKeyKey: exampleBundle1.privateKeyBytes, - }, - Type: corev1.SecretTypeTLS, - }, - )), - }, - ExpectedEvents: []string{`Normal Issued Certificate issued successfully`}, - }, - }, - "issue new certificate if existing certificate data is garbage, even if existing CertificateRequest is Ready": { - certificate: exampleBundle1.certificate, - builder: &testpkg.Builder{ - KubeObjects: []runtime.Object{ - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: gen.DefaultTestNamespace, - Name: "output", - Annotations: map[string]string{ - "custom-annotation": "value", - cmapi.CertificateNameKey: "test", - cmapi.IssuerKindAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Kind, - cmapi.IssuerNameAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Name, - cmapi.IPSANAnnotationKey: "", - cmapi.AltNamesAnnotationKey: "example.com", - cmapi.CommonNameAnnotationKey: "", - cmapi.URISANAnnotationKey: "", - }, - }, - Data: map[string][]byte{ - corev1.TLSCertKey: []byte("invalid"), - corev1.TLSPrivateKeyKey: exampleBundle1.privateKeyBytes, - cmmeta.TLSCAKey: nil, - }, - Type: corev1.SecretTypeTLS, - }, - }, - CertManagerObjects: []runtime.Object{ - exampleBundle1.certificate, - exampleBundle1.certificateRequestReady, - }, - ExpectedActions: []testpkg.Action{ - testpkg.NewAction(coretesting.NewUpdateAction( - corev1.SchemeGroupVersion.WithResource("secrets"), - gen.DefaultTestNamespace, - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: gen.DefaultTestNamespace, - Name: "output", - Annotations: map[string]string{ - "custom-annotation": "value", - cmapi.CertificateNameKey: "test", - cmapi.IssuerKindAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Kind, - cmapi.IssuerNameAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Name, - cmapi.IPSANAnnotationKey: "", - cmapi.AltNamesAnnotationKey: "example.com", - cmapi.CommonNameAnnotationKey: "", - cmapi.URISANAnnotationKey: "", - }, - }, - Data: map[string][]byte{ - corev1.TLSCertKey: exampleBundle1.localTemporaryCertificateBytes, - corev1.TLSPrivateKeyKey: exampleBundle1.privateKeyBytes, - }, - Type: corev1.SecretTypeTLS, - }, - )), - }, - ExpectedEvents: []string{`Normal TempCert Issued temporary certificate`}, - }, - }, - "should not generate a new temporary certificate if existing certificate is valid for different dnsNames": { - certificate: exampleBundle1.certificate, - generateCSR: testGenerateCSRFn(exampleBundle1.csrBytes), - builder: &testpkg.Builder{ - KubeObjects: []runtime.Object{ - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: gen.DefaultTestNamespace, - Name: "output", - Annotations: map[string]string{ - "custom-annotation": "value", - cmapi.CertificateNameKey: "test", - cmapi.IssuerKindAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Kind, - cmapi.IssuerNameAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Name, - cmapi.IPSANAnnotationKey: "", - cmapi.AltNamesAnnotationKey: "notexample.com", - cmapi.CommonNameAnnotationKey: "", - cmapi.URISANAnnotationKey: "", - }, - }, - Data: map[string][]byte{ - corev1.TLSCertKey: exampleBundle1.generateCertificateTemporary( - gen.CertificateFrom(exampleBundle1.certificate, - gen.SetCertificateDNSNames("notexample.com"), - ), - ), - corev1.TLSPrivateKeyKey: exampleBundle1.privateKeyBytes, - }, - Type: corev1.SecretTypeTLS, - }, - }, - CertManagerObjects: []runtime.Object{ - exampleBundle1.certificate, - }, - ExpectedActions: []testpkg.Action{ - testpkg.NewAction(coretesting.NewCreateAction( - cmapi.SchemeGroupVersion.WithResource("certificaterequests"), - gen.DefaultTestNamespace, - exampleBundle1.certificateRequest, - )), - }, - ExpectedEvents: []string{`Normal Requested Created new CertificateRequest resource "test-850937773"`}, - }, - }, - "update the secret metadata if existing temporary certificate does not have annotations": { - certificate: exampleBundle1.certificate, - builder: &testpkg.Builder{ - KubeObjects: []runtime.Object{ - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: gen.DefaultTestNamespace, - Name: "output", - Annotations: map[string]string{ - "custom-annotation": "value", - cmapi.IssuerKindAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Kind, - cmapi.IssuerNameAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Name, - }, - }, - Data: map[string][]byte{ - corev1.TLSCertKey: exampleBundle1.localTemporaryCertificateBytes, - corev1.TLSPrivateKeyKey: exampleBundle1.privateKeyBytes, - cmmeta.TLSCAKey: nil, - }, - Type: corev1.SecretTypeTLS, - }, - }, - CertManagerObjects: []runtime.Object{ - exampleBundle1.certificate, - }, - ExpectedActions: []testpkg.Action{ - testpkg.NewAction(coretesting.NewUpdateAction( - corev1.SchemeGroupVersion.WithResource("secrets"), - gen.DefaultTestNamespace, - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: gen.DefaultTestNamespace, - Name: "output", - Annotations: map[string]string{ - "custom-annotation": "value", - cmapi.CertificateNameKey: "test", - cmapi.IssuerKindAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Kind, - cmapi.IssuerNameAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Name, - cmapi.IPSANAnnotationKey: "", - cmapi.AltNamesAnnotationKey: "example.com", - cmapi.CommonNameAnnotationKey: "", - cmapi.URISANAnnotationKey: "", - }, - }, - Data: map[string][]byte{ - corev1.TLSCertKey: exampleBundle1.localTemporaryCertificateBytes, - corev1.TLSPrivateKeyKey: exampleBundle1.privateKeyBytes, - }, - Type: corev1.SecretTypeTLS, - }, - )), - }, - ExpectedEvents: []string{`Normal UpdateMeta Updated metadata on Secret resource`}, - }, - }, - } - - for name, test := range tests { - t.Run(name, func(t *testing.T) { - fixedClock.SetTime(fixedClockStart) - test.builder.Clock = fixedClock - - test.localTemporarySigner = testLocalTemporarySignerFn(exampleBundle1.localTemporaryCertificateBytes) - runTest(t, test) - }) - } -} - -func TestUpdateStatus(t *testing.T) { - baseCert := gen.Certificate("test", - gen.SetCertificateIssuer(cmmeta.ObjectReference{Name: "test", Kind: "something", Group: "not-empty"}), - gen.SetCertificateSecretName("output"), - gen.SetCertificateRenewBefore(time.Hour*36), - ) - exampleBundle1 := mustCreateCryptoBundle(t, gen.CertificateFrom(baseCert, - gen.SetCertificateDNSNames("example.com"), - )) - - metaFixedClockStart := metav1.NewTime(fixedClockStart) - tests := map[string]testT{ - "mark status as NotFound if Secret does not exist for Certificate": { - certificate: exampleBundle1.certificate, - builder: &testpkg.Builder{ - CertManagerObjects: []runtime.Object{ - exampleBundle1.certificate, - }, - ExpectedActions: []testpkg.Action{ - testpkg.NewAction(coretesting.NewUpdateSubresourceAction( - cmapi.SchemeGroupVersion.WithResource("certificates"), - "status", - gen.DefaultTestNamespace, - gen.CertificateFrom(exampleBundle1.certificate, - gen.SetCertificateStatusCondition(cmapi.CertificateCondition{ - Type: cmapi.CertificateConditionReady, - Status: cmmeta.ConditionFalse, - Reason: "NotFound", - Message: "Certificate does not exist", - LastTransitionTime: &metaFixedClockStart, - }), - ), - )), - }, - }, - }, - "mark status as NotFound if Secret does not contain any data": { - certificate: exampleBundle1.certificate, - builder: &testpkg.Builder{ - KubeObjects: []runtime.Object{ - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: exampleBundle1.certificate.Spec.SecretName, - Namespace: exampleBundle1.certificate.Namespace, - }, - Data: map[string][]byte{}, - }, - }, - CertManagerObjects: []runtime.Object{ - exampleBundle1.certificate, - }, - ExpectedActions: []testpkg.Action{ - testpkg.NewAction(coretesting.NewUpdateSubresourceAction( - cmapi.SchemeGroupVersion.WithResource("certificates"), - "status", - gen.DefaultTestNamespace, - gen.CertificateFrom(exampleBundle1.certificate, - gen.SetCertificateStatusCondition(cmapi.CertificateCondition{ - Type: cmapi.CertificateConditionReady, - Status: cmmeta.ConditionFalse, - Reason: "NotFound", - Message: "Certificate does not exist", - LastTransitionTime: &metaFixedClockStart, - }), - ), - )), - }, - }, - }, - "mark certificate as pending issuance if a secret exists with only a private key and no request exists": { - certificate: exampleBundle1.certificate, - builder: &testpkg.Builder{ - KubeObjects: []runtime.Object{ - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: exampleBundle1.certificate.Spec.SecretName, - Namespace: exampleBundle1.certificate.Namespace, - }, - Data: map[string][]byte{ - corev1.TLSPrivateKeyKey: exampleBundle1.privateKeyBytes, - }, - }, - }, - CertManagerObjects: []runtime.Object{ - exampleBundle1.certificate, - }, - ExpectedActions: []testpkg.Action{ - testpkg.NewAction(coretesting.NewUpdateSubresourceAction( - cmapi.SchemeGroupVersion.WithResource("certificates"), - "status", - gen.DefaultTestNamespace, - gen.CertificateFrom(exampleBundle1.certificate, - gen.SetCertificateStatusCondition(cmapi.CertificateCondition{ - Type: cmapi.CertificateConditionReady, - Status: cmmeta.ConditionFalse, - Reason: "Pending", - Message: "Certificate pending issuance", - LastTransitionTime: &metaFixedClockStart, - }), - ), - )), - }, - }, - }, - "mark certificate as in progress if existing Secret contains only private key and request exists & is up to date": { - certificate: exampleBundle1.certificate, - builder: &testpkg.Builder{ - KubeObjects: []runtime.Object{ - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: exampleBundle1.certificate.Spec.SecretName, - Namespace: exampleBundle1.certificate.Namespace, - }, - Data: map[string][]byte{ - corev1.TLSPrivateKeyKey: exampleBundle1.privateKeyBytes, - }, - }, - }, - CertManagerObjects: []runtime.Object{ - exampleBundle1.certificate, - exampleBundle1.certificateRequest, - }, - ExpectedActions: []testpkg.Action{ - testpkg.NewAction(coretesting.NewUpdateSubresourceAction( - cmapi.SchemeGroupVersion.WithResource("certificates"), - "status", - gen.DefaultTestNamespace, - gen.CertificateFrom(exampleBundle1.certificate, - gen.SetCertificateStatusCondition(cmapi.CertificateCondition{ - Type: cmapi.CertificateConditionReady, - Status: cmmeta.ConditionFalse, - Reason: "InProgress", - Message: fmt.Sprintf("Waiting for CertificateRequest %q to complete", exampleBundle1.certificateRequest.Name), - LastTransitionTime: &metaFixedClockStart, - }), - ), - )), - }, - }, - }, - "mark certificate Ready if existing certificate is valid and up to date": { - certificate: exampleBundle1.certificate, - builder: &testpkg.Builder{ - KubeObjects: []runtime.Object{ - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: exampleBundle1.certificate.Spec.SecretName, - Namespace: exampleBundle1.certificate.Namespace, - Annotations: map[string]string{ - cmapi.IssuerNameAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Name, - cmapi.IssuerKindAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Kind, - }, - }, - Data: map[string][]byte{ - corev1.TLSPrivateKeyKey: exampleBundle1.privateKeyBytes, - corev1.TLSCertKey: exampleBundle1.certBytes, - }, - }, - }, - CertManagerObjects: []runtime.Object{ - exampleBundle1.certificate, - }, - ExpectedActions: []testpkg.Action{ - testpkg.NewAction(coretesting.NewUpdateSubresourceAction( - cmapi.SchemeGroupVersion.WithResource("certificates"), - "status", - gen.DefaultTestNamespace, - gen.CertificateFrom(exampleBundle1.certificate, - gen.SetCertificateStatusCondition(cmapi.CertificateCondition{ - Type: cmapi.CertificateConditionReady, - Status: cmmeta.ConditionTrue, - Reason: "Ready", - Message: "Certificate is up to date and has not expired", - LastTransitionTime: &metaFixedClockStart, - }), - gen.SetCertificateNotAfter(metav1.NewTime(exampleBundle1.cert.NotAfter)), - ), - )), - }, - }, - }, - "mark certificate Ready if existing certificate is expiring soon and a pending CertificateRequest exists": { - certificate: exampleBundle1.certificate, - builder: &testpkg.Builder{ - KubeObjects: []runtime.Object{ - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: exampleBundle1.certificate.Spec.SecretName, - Namespace: exampleBundle1.certificate.Namespace, - Annotations: map[string]string{ - cmapi.IssuerNameAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Name, - cmapi.IssuerKindAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Kind, - }, - }, - Data: map[string][]byte{ - corev1.TLSPrivateKeyKey: exampleBundle1.privateKeyBytes, - corev1.TLSCertKey: exampleBundle1.generateCertificateExpiring1H(exampleBundle1.certificate), - }, - }, - }, - CertManagerObjects: []runtime.Object{ - exampleBundle1.certificate, - exampleBundle1.certificateRequest, - }, - ExpectedActions: []testpkg.Action{ - testpkg.NewAction(coretesting.NewUpdateSubresourceAction( - cmapi.SchemeGroupVersion.WithResource("certificates"), - "status", - gen.DefaultTestNamespace, - gen.CertificateFrom(exampleBundle1.certificate, - gen.SetCertificateStatusCondition(cmapi.CertificateCondition{ - Type: cmapi.CertificateConditionReady, - Status: cmmeta.ConditionTrue, - Reason: "Ready", - Message: "Certificate is up to date and has not expired", - LastTransitionTime: &metaFixedClockStart, - }), - gen.SetCertificateNotAfter(metav1.NewTime(certificateNotAfter(exampleBundle1.generateCertificateExpiring1H(exampleBundle1.certificate)))), - ), - )), - }, - }, - }, - "mark certificate Expired if existing certificate is expired": { - certificate: exampleBundle1.certificate, - builder: &testpkg.Builder{ - KubeObjects: []runtime.Object{ - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: exampleBundle1.certificate.Spec.SecretName, - Namespace: exampleBundle1.certificate.Namespace, - Annotations: map[string]string{ - cmapi.IssuerNameAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Name, - cmapi.IssuerKindAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Kind, - }, - }, - Data: map[string][]byte{ - corev1.TLSPrivateKeyKey: exampleBundle1.privateKeyBytes, - corev1.TLSCertKey: exampleBundle1.generateCertificateExpired(exampleBundle1.certificate), - }, - }, - }, - CertManagerObjects: []runtime.Object{ - exampleBundle1.certificate, - }, - ExpectedActions: []testpkg.Action{ - testpkg.NewAction(coretesting.NewUpdateSubresourceAction( - cmapi.SchemeGroupVersion.WithResource("certificates"), - "status", - gen.DefaultTestNamespace, - gen.CertificateFrom(exampleBundle1.certificate, - gen.SetCertificateStatusCondition(cmapi.CertificateCondition{ - Type: cmapi.CertificateConditionReady, - Status: cmmeta.ConditionFalse, - Reason: "Expired", - Message: fmt.Sprintf("Certificate has expired on %s", certificateNotAfter(exampleBundle1.generateCertificateExpired(exampleBundle1.certificate)).Format(time.RFC822)), - LastTransitionTime: &metaFixedClockStart, - }), - gen.SetCertificateNotAfter(metav1.NewTime(certificateNotAfter(exampleBundle1.generateCertificateExpired(exampleBundle1.certificate)))), - ), - )), - }, - }, - }, - "mark certificate InProgress if existing certificate is expired and CertificateRequest is in progress": { - certificate: exampleBundle1.certificate, - builder: &testpkg.Builder{ - KubeObjects: []runtime.Object{ - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: exampleBundle1.certificate.Spec.SecretName, - Namespace: exampleBundle1.certificate.Namespace, - Annotations: map[string]string{ - cmapi.IssuerNameAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Name, - cmapi.IssuerKindAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Kind, - }, - }, - Data: map[string][]byte{ - corev1.TLSPrivateKeyKey: exampleBundle1.privateKeyBytes, - corev1.TLSCertKey: exampleBundle1.generateCertificateExpired(exampleBundle1.certificate), - }, - }, - }, - CertManagerObjects: []runtime.Object{ - exampleBundle1.certificate, - exampleBundle1.certificateRequest, - }, - ExpectedActions: []testpkg.Action{ - testpkg.NewAction(coretesting.NewUpdateSubresourceAction( - cmapi.SchemeGroupVersion.WithResource("certificates"), - "status", - gen.DefaultTestNamespace, - gen.CertificateFrom(exampleBundle1.certificate, - gen.SetCertificateStatusCondition(cmapi.CertificateCondition{ - Type: cmapi.CertificateConditionReady, - Status: cmmeta.ConditionFalse, - Reason: "InProgress", - Message: fmt.Sprintf("Waiting for CertificateRequest %q to complete", exampleBundle1.certificateRequest.Name), - LastTransitionTime: &metaFixedClockStart, - }), - gen.SetCertificateNotAfter(metav1.NewTime(certificateNotAfter(exampleBundle1.generateCertificateExpired(exampleBundle1.certificate)))), - ), - )), - }, - }, - }, - "mark certificate InProgress if existing certificate is expired and CertificateRequest is ready but not stored yet": { - certificate: exampleBundle1.certificate, - builder: &testpkg.Builder{ - KubeObjects: []runtime.Object{ - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: exampleBundle1.certificate.Spec.SecretName, - Namespace: exampleBundle1.certificate.Namespace, - Annotations: map[string]string{ - cmapi.IssuerNameAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Name, - cmapi.IssuerKindAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Kind, - }, - }, - Data: map[string][]byte{ - corev1.TLSPrivateKeyKey: exampleBundle1.privateKeyBytes, - corev1.TLSCertKey: exampleBundle1.generateCertificateExpired(exampleBundle1.certificate), - }, - }, - }, - CertManagerObjects: []runtime.Object{ - exampleBundle1.certificate, - exampleBundle1.certificateRequestReady, - }, - ExpectedActions: []testpkg.Action{ - testpkg.NewAction(coretesting.NewUpdateSubresourceAction( - cmapi.SchemeGroupVersion.WithResource("certificates"), - "status", - gen.DefaultTestNamespace, - gen.CertificateFrom(exampleBundle1.certificate, - gen.SetCertificateStatusCondition(cmapi.CertificateCondition{ - Type: cmapi.CertificateConditionReady, - Status: cmmeta.ConditionFalse, - Reason: "InProgress", - Message: fmt.Sprintf("Waiting for CertificateRequest %q to complete", exampleBundle1.certificateRequest.Name), - LastTransitionTime: &metaFixedClockStart, - }), - gen.SetCertificateNotAfter(metav1.NewTime(certificateNotAfter(exampleBundle1.generateCertificateExpired(exampleBundle1.certificate)))), - ), - )), - }, - }, - }, - "mark certificate DoesNotMatch if existing Certificate does not match spec and no request is in progress": { - certificate: exampleBundle1.certificate, - builder: &testpkg.Builder{ - KubeObjects: []runtime.Object{ - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: exampleBundle1.certificate.Spec.SecretName, - Namespace: exampleBundle1.certificate.Namespace, - Annotations: map[string]string{ - cmapi.IssuerNameAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Name, - cmapi.IssuerKindAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Kind, - }, - }, - Data: map[string][]byte{ - corev1.TLSPrivateKeyKey: exampleBundle1.privateKeyBytes, - corev1.TLSCertKey: exampleBundle1.generateTestCertificate( - gen.CertificateFrom(exampleBundle1.certificate, - gen.SetCertificateDNSNames("notexample.com"), - ), nil, - ), - }, - }, - }, - CertManagerObjects: []runtime.Object{ - exampleBundle1.certificate, - }, - ExpectedActions: []testpkg.Action{ - testpkg.NewAction(coretesting.NewUpdateSubresourceAction( - cmapi.SchemeGroupVersion.WithResource("certificates"), - "status", - gen.DefaultTestNamespace, - gen.CertificateFrom(exampleBundle1.certificate, - gen.SetCertificateStatusCondition(cmapi.CertificateCondition{ - Type: cmapi.CertificateConditionReady, - Status: cmmeta.ConditionFalse, - Reason: "DoesNotMatch", - Message: "DNS names on TLS certificate not up to date: [\"notexample.com\"]", - LastTransitionTime: &metaFixedClockStart, - }), - ), - )), - }, - }, - }, - "mark certificate InProgress if existing Certificate does not match spec and a request is in progress": { - certificate: exampleBundle1.certificate, - builder: &testpkg.Builder{ - KubeObjects: []runtime.Object{ - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: exampleBundle1.certificate.Spec.SecretName, - Namespace: exampleBundle1.certificate.Namespace, - Annotations: map[string]string{ - cmapi.IssuerNameAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Name, - cmapi.IssuerKindAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Kind, - }, - }, - Data: map[string][]byte{ - corev1.TLSPrivateKeyKey: exampleBundle1.privateKeyBytes, - corev1.TLSCertKey: exampleBundle1.generateTestCertificate( - gen.CertificateFrom(exampleBundle1.certificate, - gen.SetCertificateDNSNames("notexample.com"), - ), nil, - ), - }, - }, - }, - CertManagerObjects: []runtime.Object{ - exampleBundle1.certificate, - exampleBundle1.certificateRequest, - }, - ExpectedActions: []testpkg.Action{ - testpkg.NewAction(coretesting.NewUpdateSubresourceAction( - cmapi.SchemeGroupVersion.WithResource("certificates"), - "status", - gen.DefaultTestNamespace, - gen.CertificateFrom(exampleBundle1.certificate, - gen.SetCertificateStatusCondition(cmapi.CertificateCondition{ - Type: cmapi.CertificateConditionReady, - Status: cmmeta.ConditionFalse, - Reason: "InProgress", - Message: fmt.Sprintf("Waiting for CertificateRequest %q to complete", exampleBundle1.certificateRequest.Name), - LastTransitionTime: &metaFixedClockStart, - }), - ), - )), - }, - }, - }, - "mark certificate TemporaryCertificate if secret contains a valid temporary certificate and no request exists": { - certificate: exampleBundle1.certificate, - builder: &testpkg.Builder{ - KubeObjects: []runtime.Object{ - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: exampleBundle1.certificate.Spec.SecretName, - Namespace: exampleBundle1.certificate.Namespace, - Annotations: map[string]string{ - cmapi.IssuerNameAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Name, - cmapi.IssuerKindAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Kind, - }, - }, - Data: map[string][]byte{ - corev1.TLSPrivateKeyKey: exampleBundle1.privateKeyBytes, - corev1.TLSCertKey: exampleBundle1.localTemporaryCertificateBytes, - }, - }, - }, - CertManagerObjects: []runtime.Object{ - exampleBundle1.certificate, - }, - ExpectedActions: []testpkg.Action{ - testpkg.NewAction(coretesting.NewUpdateSubresourceAction( - cmapi.SchemeGroupVersion.WithResource("certificates"), - "status", - gen.DefaultTestNamespace, - gen.CertificateFrom(exampleBundle1.certificate, - gen.SetCertificateStatusCondition(cmapi.CertificateCondition{ - Type: cmapi.CertificateConditionReady, - Status: cmmeta.ConditionFalse, - Reason: "TemporaryCertificate", - Message: "Certificate issuance in progress. Temporary certificate issued.", - LastTransitionTime: &metaFixedClockStart, - }), - ), - )), - }, - }, - }, - "mark certificate InProgress if secret contains a valid temporary certificate and a request exists": { - certificate: exampleBundle1.certificate, - builder: &testpkg.Builder{ - KubeObjects: []runtime.Object{ - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: exampleBundle1.certificate.Spec.SecretName, - Namespace: exampleBundle1.certificate.Namespace, - Annotations: map[string]string{ - cmapi.IssuerNameAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Name, - cmapi.IssuerKindAnnotationKey: exampleBundle1.certificate.Spec.IssuerRef.Kind, - }, - }, - Data: map[string][]byte{ - corev1.TLSPrivateKeyKey: exampleBundle1.privateKeyBytes, - corev1.TLSCertKey: exampleBundle1.localTemporaryCertificateBytes, - }, - }, - }, - CertManagerObjects: []runtime.Object{ - exampleBundle1.certificate, - exampleBundle1.certificateRequest, - }, - ExpectedActions: []testpkg.Action{ - testpkg.NewAction(coretesting.NewUpdateSubresourceAction( - cmapi.SchemeGroupVersion.WithResource("certificates"), - "status", - gen.DefaultTestNamespace, - gen.CertificateFrom(exampleBundle1.certificate, - gen.SetCertificateStatusCondition(cmapi.CertificateCondition{ - Type: cmapi.CertificateConditionReady, - Status: cmmeta.ConditionFalse, - Reason: "InProgress", - Message: fmt.Sprintf("Waiting for CertificateRequest %q to complete", exampleBundle1.certificateRequest.Name), - LastTransitionTime: &metaFixedClockStart, - }), - ), - )), - }, - }, - }, - } - for name, test := range tests { - t.Run(name, func(t *testing.T) { - fixedClock.SetTime(fixedClockStart) - test.builder.Clock = fixedClock - test.builder.T = t - test.builder.Init() - defer test.builder.Stop() - - testManager := &certificateRequestManager{} - testManager.Register(test.builder.Context) - testManager.clock = fixedClock - test.builder.Start() - - err := testManager.updateCertificateStatus(context.Background(), test.certificate, test.certificate.DeepCopy()) - 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) - }) - } -} - -type testT struct { - builder *testpkg.Builder - generatePrivateKeyBytes generatePrivateKeyBytesFn - generateCSR generateCSRFn - localTemporarySigner localTemporarySignerFn - certificate *cmapi.Certificate - expectedErr bool -} - -func runTest(t *testing.T, test testT) { - test.builder.T = t - test.builder.Init() - defer test.builder.Stop() - - testManager := &certificateRequestManager{} - testManager.Register(test.builder.Context) - testManager.generatePrivateKeyBytes = test.generatePrivateKeyBytes - testManager.generateCSR = test.generateCSR - testManager.localTemporarySigner = test.localTemporarySigner - test.builder.Start() - - err := testManager.processCertificate(context.Background(), test.certificate) - 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/controller/certificates/util.go b/pkg/controller/certificates/util.go deleted file mode 100644 index 086c2cd1e..000000000 --- a/pkg/controller/certificates/util.go +++ /dev/null @@ -1,261 +0,0 @@ -/* -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 certificates - -import ( - "context" - "crypto" - "crypto/x509" - "encoding/json" - "fmt" - "math/big" - "reflect" - "time" - - "github.com/kr/pretty" - corev1 "k8s.io/api/core/v1" - k8sErrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - corelisters "k8s.io/client-go/listers/core/v1" - "k8s.io/client-go/tools/cache" - - apiutil "github.com/jetstack/cert-manager/pkg/api/util" - "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha2" - cmclient "github.com/jetstack/cert-manager/pkg/client/clientset/versioned" - cmlisters "github.com/jetstack/cert-manager/pkg/client/listers/certmanager/v1alpha2" - controllerpkg "github.com/jetstack/cert-manager/pkg/controller" - logf "github.com/jetstack/cert-manager/pkg/logs" - "github.com/jetstack/cert-manager/pkg/util" - "github.com/jetstack/cert-manager/pkg/util/errors" - "github.com/jetstack/cert-manager/pkg/util/kube" - "github.com/jetstack/cert-manager/pkg/util/pki" -) - -var ( - certificateGvk = v1alpha2.SchemeGroupVersion.WithKind("Certificate") -) - -type calculateDurationUntilRenewFn func(context.Context, *x509.Certificate, *v1alpha2.Certificate) time.Duration - -func getCertificateForKey(ctx context.Context, key string, lister cmlisters.CertificateLister) (*v1alpha2.Certificate, error) { - namespace, name, err := cache.SplitMetaNamespaceKey(key) - if err != nil { - return nil, nil - } - - crt, err := lister.Certificates(namespace).Get(name) - if k8sErrors.IsNotFound(err) { - return nil, nil - } - if err != nil { - return nil, err - } - - return crt, nil -} - -func certificateGetter(lister cmlisters.CertificateLister) func(namespace, name string) (interface{}, error) { - return func(namespace, name string) (interface{}, error) { - return lister.Certificates(namespace).Get(name) - } -} - -var keyFunc = controllerpkg.KeyFunc - -func certificateMatchesSpec(crt *v1alpha2.Certificate, key crypto.Signer, cert *x509.Certificate, secret *corev1.Secret) (bool, []string) { - var errs []string - - // TODO: add checks for KeySize, KeyAlgorithm fields - // TODO: add checks for Organization field - // TODO: add checks for IsCA field - - // check if the private key is the corresponding pair to the certificate - - matches, err := pki.PublicKeyMatchesCertificate(key.Public(), cert) - if err != nil { - errs = append(errs, err.Error()) - } else if !matches { - errs = append(errs, fmt.Sprintf("Certificate private key does not match certificate")) - } - - // If CN is set on the resource then it should exist on the certificate as - // the Common Name or a DNS Name - expectedCN := crt.Spec.CommonName - gotCN := append(cert.DNSNames, cert.Subject.CommonName) - if len(expectedCN) > 0 && !util.Contains(gotCN, expectedCN) { - errs = append(errs, fmt.Sprintf("Common Name on TLS certificate not up to date (%q): %s", - expectedCN, gotCN)) - } - - // validate the dns names are correct - expectedDNSNames := crt.Spec.DNSNames - if !util.Subset(cert.DNSNames, expectedDNSNames) { - errs = append(errs, fmt.Sprintf("DNS names on TLS certificate not up to date: %q", cert.DNSNames)) - } - - expectedURIs := crt.Spec.URISANs - if !util.EqualUnsorted(pki.URLsToString(cert.URIs), expectedURIs) { - errs = append(errs, fmt.Sprintf("URI SANs on TLS certificate not up to date: %q", cert.URIs)) - } - - // validate the ip addresses are correct - if !util.EqualUnsorted(pki.IPAddressesToString(cert.IPAddresses), crt.Spec.IPAddresses) { - errs = append(errs, fmt.Sprintf("IP addresses on TLS certificate not up to date: %q", pki.IPAddressesToString(cert.IPAddresses))) - } - - if secret.Annotations == nil { - secret.Annotations = make(map[string]string) - } - - // Validate that the issuer name and kind is correct - // If the new annotation exists and doesn't match then error - // If the new annotation doesn't exist and the old annotation doesn't match then error - - annotationError := func(k, v string) { - errs = append(errs, fmt.Sprintf("Issuer %q of the certificate is not up to date: %q", k, v)) - } - - name, ok := secret.Annotations[v1alpha2.IssuerNameAnnotationKey] - if !ok { - if secret.Annotations[v1alpha2.DeprecatedIssuerNameAnnotationKey] != crt.Spec.IssuerRef.Name { - annotationError(v1alpha2.DeprecatedIssuerNameAnnotationKey, secret.Annotations[v1alpha2.DeprecatedIssuerNameAnnotationKey]) - } - } else if name != crt.Spec.IssuerRef.Name { - annotationError(v1alpha2.IssuerNameAnnotationKey, secret.Annotations[v1alpha2.IssuerNameAnnotationKey]) - } - - kind, ok := secret.Annotations[v1alpha2.IssuerKindAnnotationKey] - if !ok { - if secret.Annotations[v1alpha2.DeprecatedIssuerKindAnnotationKey] != apiutil.IssuerKind(crt.Spec.IssuerRef) { - annotationError(v1alpha2.DeprecatedIssuerKindAnnotationKey, secret.Annotations[v1alpha2.DeprecatedIssuerKindAnnotationKey]) - } - } else if kind != apiutil.IssuerKind(crt.Spec.IssuerRef) { - annotationError(v1alpha2.IssuerKindAnnotationKey, secret.Annotations[v1alpha2.IssuerKindAnnotationKey]) - } - - return len(errs) == 0, errs -} - -func scheduleRenewal(ctx context.Context, lister corelisters.SecretLister, calc calculateDurationUntilRenewFn, queueFn func(interface{}, time.Duration), crt *v1alpha2.Certificate) { - log := logf.FromContext(ctx) - log = log.WithValues( - logf.RelatedResourceNameKey, crt.Spec.SecretName, - logf.RelatedResourceNamespaceKey, crt.Namespace, - logf.RelatedResourceKindKey, "Secret", - ) - - key, err := keyFunc(crt) - if err != nil { - log.Error(err, "error getting key for certificate resource") - return - } - - cert, err := kube.SecretTLSCert(ctx, lister, crt.Namespace, crt.Spec.SecretName) - if err != nil { - if !errors.IsInvalidData(err) { - log.Error(err, "error getting secret for certificate resource") - } - return - } - - renewIn := calc(ctx, cert, crt) - queueFn(key, renewIn) - - log.WithValues("duration_until_renewal", renewIn.String()).Info("certificate scheduled for renewal") -} - -// staticTemporarySerialNumber is a fixed serial number we check for when -// updating the status of a certificate. -// It is used to identify temporarily generated certificates, so that friendly -// status messages can be displayed to users. -const staticTemporarySerialNumber = 0x1234567890 - -func isTemporaryCertificate(cert *x509.Certificate) bool { - if cert == nil { - return false - } - return cert.SerialNumber.Int64() == staticTemporarySerialNumber -} - -// generateLocallySignedTemporaryCertificate signs a temporary certificate for -// the given certificate resource using a one-use temporary CA that is then -// discarded afterwards. -// This is to mitigate a potential attack against x509 certificates that use a -// predictable serial number and weak MD5 hashing algorithms. -// In practice, this shouldn't really be a concern anyway. -func generateLocallySignedTemporaryCertificate(crt *v1alpha2.Certificate, pk []byte) ([]byte, error) { - // generate a throwaway self-signed root CA - caPk, err := pki.GenerateECPrivateKey(pki.ECCurve521) - if err != nil { - return nil, err - } - caCertTemplate, err := pki.GenerateTemplate(&v1alpha2.Certificate{ - Spec: v1alpha2.CertificateSpec{ - CommonName: "cert-manager.local", - IsCA: true, - }, - }) - if err != nil { - return nil, err - } - _, caCert, err := pki.SignCertificate(caCertTemplate, caCertTemplate, caPk.Public(), caPk) - if err != nil { - return nil, err - } - - // sign a temporary certificate using the root CA - template, err := pki.GenerateTemplate(crt) - if err != nil { - return nil, err - } - template.SerialNumber = big.NewInt(staticTemporarySerialNumber) - - signeeKey, err := pki.DecodePrivateKeyBytes(pk) - if err != nil { - return nil, err - } - - b, _, err := pki.SignCertificate(template, caCert, signeeKey.Public(), caPk) - if err != nil { - return nil, err - } - - return b, nil -} - -func updateCertificateStatus(ctx context.Context, cmClient cmclient.Interface, old, new *v1alpha2.Certificate) (*v1alpha2.Certificate, error) { - log := logf.FromContext(ctx, "updateStatus") - oldBytes, _ := json.Marshal(old.Status) - newBytes, _ := json.Marshal(new.Status) - if reflect.DeepEqual(oldBytes, newBytes) { - return nil, nil - } - log.V(logf.DebugLevel).Info("updating resource due to change in status", "diff", pretty.Diff(string(oldBytes), string(newBytes))) - return cmClient.CertmanagerV1alpha2().Certificates(new.Namespace).UpdateStatus(context.TODO(), new, metav1.UpdateOptions{}) -} - -func certificateHasTemporaryCertificateAnnotation(crt *v1alpha2.Certificate) bool { - if crt.Annotations == nil { - return false - } - - if val, ok := crt.Annotations[v1alpha2.IssueTemporaryCertificateAnnotation]; ok && val == "true" { - return true - } - - return false -} diff --git a/pkg/controller/certificates/util_test.go b/pkg/controller/certificates/util_test.go deleted file mode 100644 index 52a986b67..000000000 --- a/pkg/controller/certificates/util_test.go +++ /dev/null @@ -1,285 +0,0 @@ -/* -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 certificates - -import ( - "testing" - "time" - - corev1 "k8s.io/api/core/v1" - 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/pkg/util" - "github.com/jetstack/cert-manager/test/unit/gen" -) - -func TestCertificateMatchesSpec(t *testing.T) { - baseCert := gen.Certificate("test", - gen.SetCertificateIssuer(cmmeta.ObjectReference{Name: "ca-issuer", Kind: "Issuer", Group: "not-empty"}), - gen.SetCertificateSecretName("output"), - gen.SetCertificateRenewBefore(time.Hour*36), - ) - - exampleBundle := mustCreateCryptoBundle(t, gen.CertificateFrom(baseCert, - gen.SetCertificateDNSNames("a.example.com"), - gen.SetCertificateCommonName("common.name.example.com"), - gen.SetCertificateURIs("spiffe://cluster.local/ns/sandbox/sa/foo"), - )) - - secret := &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - cmapi.IssuerNameAnnotationKey: "ca-issuer", - cmapi.IssuerKindAnnotationKey: "Issuer", - }, - }, - } - - type testT struct { - cb cryptoBundle - certificate *cmapi.Certificate - secret *corev1.Secret - expMatch bool - expErrors []string - } - - for name, test := range map[string]testT{ - "if all match then return matched": { - cb: exampleBundle, - certificate: exampleBundle.certificate, - secret: gen.SecretFrom(secret), - expMatch: true, - expErrors: nil, - }, - - "if no common name but DNS and all match then return matched": { - cb: mustCreateCryptoBundle(t, gen.CertificateFrom(exampleBundle.certificate, - gen.SetCertificateCommonName(""), - )), - certificate: gen.CertificateFrom(exampleBundle.certificate, - gen.SetCertificateCommonName(""), - ), - secret: gen.SecretFrom(secret), - expMatch: true, - expErrors: nil, - }, - - "if common name empty but requested common name in DNS names then match": { - cb: mustCreateCryptoBundle(t, gen.CertificateFrom(exampleBundle.certificate, - gen.SetCertificateDNSNames("a.example.com", "common.name.example.com"), - gen.SetCertificateCommonName(""), - )), - certificate: gen.CertificateFrom(exampleBundle.certificate), - secret: gen.SecretFrom(secret), - expMatch: true, - expErrors: nil, - }, - - "if common name random string but requested common name in DNS names then match": { - cb: mustCreateCryptoBundle(t, gen.CertificateFrom(exampleBundle.certificate, - gen.SetCertificateDNSNames("a.example.com", "common.name.example.com"), - gen.SetCertificateCommonName("foobar"), - )), - certificate: gen.CertificateFrom(exampleBundle.certificate), - secret: gen.SecretFrom(secret), - expMatch: true, - expErrors: nil, - }, - - "if common name random string and no request DNS names but request common name then error missing common name": { - cb: mustCreateCryptoBundle(t, gen.CertificateFrom(exampleBundle.certificate, - gen.SetCertificateDNSNames(), - gen.SetCertificateCommonName("foobar"), - )), - certificate: gen.CertificateFrom(exampleBundle.certificate), - secret: gen.SecretFrom(secret), - expMatch: false, - expErrors: []string{ - `Common Name on TLS certificate not up to date ("common.name.example.com"): [foobar]`, - "DNS names on TLS certificate not up to date: []", - }, - }, - - "if the issuer name and kind uses v1alpha2 annotation then it should still match the spec": { - cb: mustCreateCryptoBundle(t, gen.CertificateFrom(exampleBundle.certificate)), - certificate: gen.CertificateFrom(exampleBundle.certificate), - secret: gen.SecretFrom(secret, - gen.SetSecretAnnotations(map[string]string{ - cmapi.IssuerNameAnnotationKey: "ca-issuer", - cmapi.IssuerKindAnnotationKey: "Issuer", - })), - expMatch: true, - expErrors: nil, - }, - - "if the issuer name uses v1alpha2 annotation but kind uses deprecated then it should still match the spec": { - cb: mustCreateCryptoBundle(t, gen.CertificateFrom(exampleBundle.certificate)), - certificate: gen.CertificateFrom(exampleBundle.certificate), - secret: gen.SecretFrom(secret, - gen.SetSecretAnnotations(map[string]string{ - cmapi.IssuerNameAnnotationKey: "ca-issuer", - cmapi.DeprecatedIssuerKindAnnotationKey: "Issuer", - })), - expMatch: true, - expErrors: nil, - }, - - "if the issuer name uses deprecated annotation but kind uses v1alpha2 then it should still match the spec": { - cb: mustCreateCryptoBundle(t, gen.CertificateFrom(exampleBundle.certificate)), - certificate: gen.CertificateFrom(exampleBundle.certificate), - secret: gen.SecretFrom(secret, - gen.SetSecretAnnotations(map[string]string{ - cmapi.DeprecatedIssuerNameAnnotationKey: "ca-issuer", - cmapi.IssuerKindAnnotationKey: "Issuer", - })), - expMatch: true, - expErrors: nil, - }, - - "if the issuer name and kind uses the deprecated annotation then it should still match the spec": { - cb: mustCreateCryptoBundle(t, gen.CertificateFrom(exampleBundle.certificate)), - certificate: gen.CertificateFrom(exampleBundle.certificate), - secret: gen.SecretFrom(secret, - gen.SetSecretAnnotations(map[string]string{ - cmapi.DeprecatedIssuerNameAnnotationKey: "ca-issuer", - cmapi.DeprecatedIssuerKindAnnotationKey: "Issuer", - })), - expMatch: true, - expErrors: nil, - }, - - "if the issuer name uses v1alpha2 and kind uses both the deprecated and v1alpha2 annotation then it should still match the spec": { - cb: mustCreateCryptoBundle(t, gen.CertificateFrom(exampleBundle.certificate)), - certificate: gen.CertificateFrom(exampleBundle.certificate), - secret: gen.SecretFrom(secret, - gen.SetSecretAnnotations(map[string]string{ - cmapi.DeprecatedIssuerKindAnnotationKey: "Issuer", - cmapi.IssuerNameAnnotationKey: "ca-issuer", - cmapi.IssuerKindAnnotationKey: "Issuer", - })), - expMatch: true, - expErrors: nil, - }, - - "if the issuer name both the deprecated and v1alpha2 annotation and kind uses deprecated then it should still match the spec": { - cb: mustCreateCryptoBundle(t, gen.CertificateFrom(exampleBundle.certificate)), - certificate: gen.CertificateFrom(exampleBundle.certificate), - secret: gen.SecretFrom(secret, - gen.SetSecretAnnotations(map[string]string{ - cmapi.DeprecatedIssuerNameAnnotationKey: "Issuer", - cmapi.IssuerNameAnnotationKey: "ca-issuer", - cmapi.IssuerKindAnnotationKey: "Issuer", - })), - expMatch: true, - expErrors: nil, - }, - - "if the issuer name and kind uses both the deprecated and v1alpha2 annotation then it should still match the spec": { - cb: mustCreateCryptoBundle(t, gen.CertificateFrom(exampleBundle.certificate)), - certificate: gen.CertificateFrom(exampleBundle.certificate), - secret: gen.SecretFrom(secret, - gen.SetSecretAnnotations(map[string]string{ - cmapi.DeprecatedIssuerNameAnnotationKey: "ca-issuer", - cmapi.DeprecatedIssuerKindAnnotationKey: "Issuer", - cmapi.IssuerNameAnnotationKey: "ca-issuer", - cmapi.IssuerKindAnnotationKey: "Issuer", - })), - expMatch: true, - expErrors: nil, - }, - - "if the issuer name and kind uses both the deprecated and v1alpha2 annotation but no values in deprecated annotations then should match spec": { - cb: mustCreateCryptoBundle(t, gen.CertificateFrom(exampleBundle.certificate)), - certificate: gen.CertificateFrom(exampleBundle.certificate), - secret: gen.SecretFrom(secret, - gen.SetSecretAnnotations(map[string]string{ - cmapi.DeprecatedIssuerNameAnnotationKey: "foo", - cmapi.DeprecatedIssuerKindAnnotationKey: "bar", - cmapi.IssuerNameAnnotationKey: "ca-issuer", - cmapi.IssuerKindAnnotationKey: "Issuer", - })), - expMatch: true, - expErrors: nil, - }, - - "if the issuer name and kind deprecated annotations are correct but v1alpha2 values are wrong then should not match spec": { - cb: mustCreateCryptoBundle(t, gen.CertificateFrom(exampleBundle.certificate)), - certificate: gen.CertificateFrom(exampleBundle.certificate), - secret: gen.SecretFrom(secret, - gen.SetSecretAnnotations(map[string]string{ - cmapi.DeprecatedIssuerNameAnnotationKey: "ca-issuer", - cmapi.DeprecatedIssuerKindAnnotationKey: "Issuer", - cmapi.IssuerNameAnnotationKey: "foo", - cmapi.IssuerKindAnnotationKey: "bar", - })), - expMatch: false, - expErrors: []string{ - `Issuer "cert-manager.io/issuer-name" of the certificate is not up to date: "foo"`, - `Issuer "cert-manager.io/issuer-kind" of the certificate is not up to date: "bar"`, - }, - }, - - "if the issuer name and kind deprecated annotations are correct but v1alpha2 values are empty but exist then should not match spec": { - cb: mustCreateCryptoBundle(t, gen.CertificateFrom(exampleBundle.certificate)), - certificate: gen.CertificateFrom(exampleBundle.certificate), - secret: gen.SecretFrom(secret, - gen.SetSecretAnnotations(map[string]string{ - cmapi.DeprecatedIssuerNameAnnotationKey: "ca-issuer", - cmapi.DeprecatedIssuerKindAnnotationKey: "Issuer", - cmapi.IssuerNameAnnotationKey: "", - cmapi.IssuerKindAnnotationKey: "", - })), - expMatch: false, - expErrors: []string{ - `Issuer "cert-manager.io/issuer-name" of the certificate is not up to date: ""`, - `Issuer "cert-manager.io/issuer-kind" of the certificate is not up to date: ""`, - }, - }, - "if the issuer name and kind deprecated annotations are wrong and no v1alpha2 values then should not match spec": { - cb: mustCreateCryptoBundle(t, gen.CertificateFrom(exampleBundle.certificate)), - certificate: gen.CertificateFrom(exampleBundle.certificate), - secret: gen.SecretFrom(secret, - gen.SetSecretAnnotations(map[string]string{ - cmapi.DeprecatedIssuerNameAnnotationKey: "foo", - cmapi.DeprecatedIssuerKindAnnotationKey: "bar", - })), - expMatch: false, - expErrors: []string{ - `Issuer "certmanager.k8s.io/issuer-name" of the certificate is not up to date: "foo"`, - `Issuer "certmanager.k8s.io/issuer-kind" of the certificate is not up to date: "bar"`, - }, - }, - } { - t.Run(name, func(t *testing.T) { - match, errs := certificateMatchesSpec( - test.certificate, test.cb.privateKey, test.cb.cert, test.secret) - - if match != test.expMatch { - t.Errorf("got unexpected match bool, exp=%t got=%t", - test.expMatch, match) - } - - if !util.EqualSorted(test.expErrors, errs) { - t.Errorf("got unexpected errors, exp=%s got=%s", - test.expErrors, errs) - } - }) - } - -} diff --git a/pkg/feature/features.go b/pkg/feature/features.go index 689bd65ef..f5975825b 100644 --- a/pkg/feature/features.go +++ b/pkg/feature/features.go @@ -28,12 +28,6 @@ const ( // // ValidateCAA enables CAA checking when issuing certificates ValidateCAA featuregate.Feature = "ValidateCAA" - - // alpha: v0.15.0 - // - // ExperimentalCertificateControllers enables all experimental certificate - // controllers and disables the default certificates controller. - ExperimentalCertificateControllers featuregate.Feature = "ExperimentalCertificateControllers" ) func init() { @@ -44,6 +38,5 @@ func init() { // To add a new feature, define a key for it above and add it here. The features will be // available throughout Kubernetes binaries. var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureSpec{ - ValidateCAA: {Default: false, PreRelease: featuregate.Alpha}, - ExperimentalCertificateControllers: {Default: false, PreRelease: featuregate.Alpha}, + ValidateCAA: {Default: false, PreRelease: featuregate.Alpha}, }