mirror of
https://github.com/wahyd4/cert-manager.git
synced 2026-08-23 03:56:34 +10:00
Remove old certificates controller
Signed-off-by: James Munnelly <james@munnelly.eu>
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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(["**"]),
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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()
|
||||
})
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user