Adds extensible issuing controller
Signed-off-by: JoshVanL <vleeuwenjoshua@gmail.com>
This commit is contained in:
parent
9556b32e81
commit
ffb5201d95
@ -119,6 +119,15 @@ func GetCertificateCondition(crt *cmapi.Certificate, conditionType cmapi.Certifi
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetCertificateRequestCondition(req *cmapi.CertificateRequest, conditionType cmapi.CertificateRequestConditionType) *cmapi.CertificateRequestCondition {
|
||||
for _, cond := range req.Status.Conditions {
|
||||
if cond.Type == conditionType {
|
||||
return &cond
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetCertificateCondition will set a 'condition' on the given Certificate.
|
||||
// - If no condition of the same type already exists, the condition will be
|
||||
// inserted with the LastTransitionTime set to the current time.
|
||||
@ -164,6 +173,21 @@ func SetCertificateCondition(crt *cmapi.Certificate, conditionType cmapi.Certifi
|
||||
klog.Infof("Setting lastTransitionTime for Certificate %q condition %q to %v", crt.Name, conditionType, nowTime.Time)
|
||||
}
|
||||
|
||||
// RemoteCertificateCondition will remove any condition with this condition type
|
||||
func RemoveCertificateCondition(crt *cmapi.Certificate, conditionType cmapi.CertificateConditionType) {
|
||||
var updatedConditions []cmapi.CertificateCondition
|
||||
|
||||
// Search through existing conditions
|
||||
for _, cond := range crt.Status.Conditions {
|
||||
// Only add unrelated conditions
|
||||
if cond.Type != conditionType {
|
||||
updatedConditions = append(updatedConditions, cond)
|
||||
}
|
||||
}
|
||||
|
||||
crt.Status.Conditions = updatedConditions
|
||||
}
|
||||
|
||||
// SetCertificateRequestCondition will set a 'condition' on the given CertificateRequest.
|
||||
// - If no condition of the same type already exists, the condition will be
|
||||
// inserted with the LastTransitionTime set to the current time.
|
||||
|
||||
@ -55,6 +55,9 @@ const (
|
||||
// Annotation names for CertificateRequests
|
||||
const (
|
||||
CRPrivateKeyAnnotationKey = "cert-manager.io/private-key-secret-name"
|
||||
|
||||
// Annotation to declare the CertificateRequest "revision", beloning to a Certificate Resource
|
||||
CertificateRequestRevisionAnnotationKey = "cert-manager.io/certificate-revision"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@ -55,6 +55,8 @@ const (
|
||||
// Annotation names for CertificateRequests
|
||||
const (
|
||||
CRPrivateKeyAnnotationKey = "cert-manager.io/private-key-secret-name"
|
||||
// Annotation to declare the CertificateRequest "revision", beloning to a Certificate Resource
|
||||
CertificateRequestRevisionAnnotationKey = "cert-manager.io/certificate-revision"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@ -30,6 +30,7 @@ filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [
|
||||
":package-srcs",
|
||||
"//pkg/controller/expcertificates/issuing:all-srcs",
|
||||
"//pkg/controller/expcertificates/trigger:all-srcs",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
|
||||
65
pkg/controller/expcertificates/issuing/BUILD.bazel
Normal file
65
pkg/controller/expcertificates/issuing/BUILD.bazel
Normal file
@ -0,0 +1,65 @@
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"issuing_controller.go",
|
||||
"keystore.go",
|
||||
"secret.go",
|
||||
],
|
||||
importpath = "github.com/jetstack/cert-manager/pkg/controller/expcertificates/issuing",
|
||||
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/informers/externalversions:go_default_library",
|
||||
"//pkg/client/listers/certmanager/v1alpha2:go_default_library",
|
||||
"//pkg/controller:go_default_library",
|
||||
"//pkg/controller/expcertificates:go_default_library",
|
||||
"//pkg/logs: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_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_client_go//informers: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",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["keystore_test.go"],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//pkg/apis/certmanager/v1alpha2:go_default_library",
|
||||
"//pkg/util/pki:go_default_library",
|
||||
"@com_github_pavel_v_chernykh_keystore_go//:go_default_library",
|
||||
"@com_sslmate_software_src_go_pkcs12//:go_default_library",
|
||||
],
|
||||
)
|
||||
368
pkg/controller/expcertificates/issuing/issuing_controller.go
Normal file
368
pkg/controller/expcertificates/issuing/issuing_controller.go
Normal file
@ -0,0 +1,368 @@
|
||||
/*
|
||||
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 issuing
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"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"
|
||||
"k8s.io/client-go/informers"
|
||||
"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"
|
||||
|
||||
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"
|
||||
cmclient "github.com/jetstack/cert-manager/pkg/client/clientset/versioned"
|
||||
cminformers "github.com/jetstack/cert-manager/pkg/client/informers/externalversions"
|
||||
cmlisters "github.com/jetstack/cert-manager/pkg/client/listers/certmanager/v1alpha2"
|
||||
controllerpkg "github.com/jetstack/cert-manager/pkg/controller"
|
||||
certificates "github.com/jetstack/cert-manager/pkg/controller/expcertificates"
|
||||
logf "github.com/jetstack/cert-manager/pkg/logs"
|
||||
utilkube "github.com/jetstack/cert-manager/pkg/util/kube"
|
||||
utilpki "github.com/jetstack/cert-manager/pkg/util/pki"
|
||||
)
|
||||
|
||||
const (
|
||||
ControllerName = "CertificateIssuing"
|
||||
|
||||
ctxTimeout = time.Second * 10
|
||||
)
|
||||
|
||||
var (
|
||||
certificateGvk = cmapi.SchemeGroupVersion.WithKind("Certificate")
|
||||
)
|
||||
|
||||
// This controller observes the state of the certificate's 'Issuing' condition,
|
||||
// which will then copy the singed certificates and private key to the target
|
||||
// Secret resource.
|
||||
type controller struct {
|
||||
certificateLister cmlisters.CertificateLister
|
||||
certificateRequestLister cmlisters.CertificateRequestLister
|
||||
secretLister corelisters.SecretLister
|
||||
recorder record.EventRecorder
|
||||
clock clock.Clock
|
||||
|
||||
client cmclient.Interface
|
||||
kubeClient kubernetes.Interface
|
||||
|
||||
// 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
|
||||
|
||||
// experimentalIssuePKCS12, if true, will make the certificates controller
|
||||
// create a `keystore.p12` in the Secret resource for each Certificate.
|
||||
// This can only be toggled globally, and the keystore will be encrypted
|
||||
// with the supplied ExperimentalPKCS12KeystorePassword.
|
||||
// This flag is likely to be removed in future in favour of native PKCS12
|
||||
// keystore bundle support.
|
||||
experimentalIssuePKCS12 bool
|
||||
// ExperimentalPKCS12KeystorePassword is the password used to encrypt and
|
||||
// decrypt PKCS#12 bundles stored in Secret resources.
|
||||
// This option only has any affect is ExperimentalIssuePKCS12 is true.
|
||||
experimentalPKCS12KeystorePassword string
|
||||
// experimentalIssueJKS, if true, will make the certificates controller
|
||||
// create a `keystore.jks` in the Secret resource for each Certificate.
|
||||
// This can only be toggled globally, and the keystore will be encrypted
|
||||
// with the supplied ExperimentalJKSPassword.
|
||||
// This flag is likely to be removed in future in favour of native JKS
|
||||
// keystore bundle support.
|
||||
experimentalIssueJKS bool
|
||||
// experimentalJKSPassword is the password used to encrypt and
|
||||
// decrypt JKS files stored in Secret resources.
|
||||
// This option only has any affect is experimentalIssueJKS is true.
|
||||
experimentalJKSPassword string
|
||||
}
|
||||
|
||||
func NewController(
|
||||
log logr.Logger,
|
||||
kubeClient kubernetes.Interface,
|
||||
client cmclient.Interface,
|
||||
factory informers.SharedInformerFactory,
|
||||
cmFactory cminformers.SharedInformerFactory,
|
||||
recorder record.EventRecorder,
|
||||
clock clock.Clock,
|
||||
certificateControllerOptions controllerpkg.CertificateOptions,
|
||||
) (*controller, workqueue.RateLimitingInterface, []cache.InformerSynced) {
|
||||
|
||||
// create a queue used to queue up items to be processed
|
||||
queue := workqueue.NewNamedRateLimitingQueue(workqueue.NewItemExponentialFailureRateLimiter(time.Second*1, time.Second*30), ControllerName)
|
||||
|
||||
// obtain references to all the informers used by this controller
|
||||
certificateInformer := cmFactory.Certmanager().V1alpha2().Certificates()
|
||||
certificateRequestInformer := cmFactory.Certmanager().V1alpha2().CertificateRequests()
|
||||
secretsInformer := factory.Core().V1().Secrets()
|
||||
|
||||
certificateInformer.Informer().AddEventHandler(&controllerpkg.QueuingEventHandler{Queue: queue})
|
||||
certificateRequestInformer.Informer().AddEventHandler(&controllerpkg.BlockingEventHandler{
|
||||
WorkFunc: controllerpkg.HandleOwnedResourceNamespacedFunc(log, queue, certificateGvk, certificates.CertificateGetFunc(certificateInformer.Lister())),
|
||||
})
|
||||
secretsInformer.Informer().AddEventHandler(&controllerpkg.BlockingEventHandler{
|
||||
// Issuer reconciles on changes to the Secret named `spec.nextPrivateKeySecretName`
|
||||
WorkFunc: certificates.EnqueueCertificatesForSecretNameFunc(log, certificateInformer.Lister(), labels.Everything(),
|
||||
certificates.WithNextPrivateKeySecretNamePredicateFunc, queue),
|
||||
})
|
||||
|
||||
// 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,
|
||||
}
|
||||
|
||||
return &controller{
|
||||
certificateLister: certificateInformer.Lister(),
|
||||
certificateRequestLister: certificateRequestInformer.Lister(),
|
||||
secretLister: secretsInformer.Lister(),
|
||||
kubeClient: kubeClient,
|
||||
client: client,
|
||||
recorder: recorder,
|
||||
clock: clock,
|
||||
|
||||
enableSecretOwnerReferences: certificateControllerOptions.EnableOwnerRef,
|
||||
experimentalIssuePKCS12: certificateControllerOptions.ExperimentalIssuePKCS12,
|
||||
experimentalPKCS12KeystorePassword: certificateControllerOptions.ExperimentalPKCS12KeystorePassword,
|
||||
experimentalIssueJKS: certificateControllerOptions.ExperimentalIssueJKS,
|
||||
experimentalJKSPassword: certificateControllerOptions.ExperimentalJKSPassword,
|
||||
}, queue, mustSync
|
||||
}
|
||||
|
||||
func (c *controller) ProcessItem(ctx context.Context, key string) error {
|
||||
ctx, cancel := context.WithTimeout(ctx, time.Second*10)
|
||||
defer cancel()
|
||||
|
||||
log := logf.FromContext(ctx).WithValues("key", key)
|
||||
namespace, name, err := cache.SplitMetaNamespaceKey(key)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
crt, err := c.certificateLister.Certificates(namespace).Get(name)
|
||||
if apierrors.IsNotFound(err) {
|
||||
log.Error(err, "certificate not found for key")
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !apiutil.CertificateHasCondition(crt, cmapi.CertificateCondition{
|
||||
Type: cmapi.CertificateConditionIssuing,
|
||||
Status: cmmeta.ConditionTrue,
|
||||
}) {
|
||||
// Do nothing if an issuance is not in progress.
|
||||
return nil
|
||||
}
|
||||
|
||||
if crt.Status.NextPrivateKeySecretName == nil ||
|
||||
len(*crt.Status.NextPrivateKeySecretName) == 0 {
|
||||
// Do nothing if the next private key secret name is not set
|
||||
return nil
|
||||
}
|
||||
|
||||
nextPrivateKeySecretName := *crt.Status.NextPrivateKeySecretName
|
||||
|
||||
// CertificateRequest revisions begin from 1. If no revision is set on the
|
||||
// status then assume no revision yet set.
|
||||
nextRevision := 1
|
||||
if crt.Status.Revision != nil {
|
||||
nextRevision = *crt.Status.Revision + 1
|
||||
}
|
||||
|
||||
reqs, err := certificates.ListCertificateRequestsMatchingPredicates(c.certificateRequestLister.CertificateRequests(crt.Namespace),
|
||||
labels.Everything(),
|
||||
certificates.WithCertificateRevisionPredicateFunc(nextRevision),
|
||||
certificates.WithCertificateRequestOwnerPredicateFunc(crt),
|
||||
)
|
||||
if err != nil || len(reqs) != 1 {
|
||||
// If error return.
|
||||
// if no error but none exist do nothing.
|
||||
// If no error but multiple exist, then leave to requestmanager controller
|
||||
// to clean up.
|
||||
return err
|
||||
}
|
||||
|
||||
req := reqs[0]
|
||||
|
||||
reqReason := apiutil.GetCertificateRequestCondition(req, cmapi.CertificateRequestConditionReady).Reason
|
||||
switch reqReason {
|
||||
|
||||
// If the certificate request has failed, set the last failure time to now,
|
||||
// and set the Issuing status condition to False with reason.
|
||||
case cmapi.CertificateRequestReasonFailed:
|
||||
nowTime := metav1.NewTime(c.clock.Now())
|
||||
crt.Status.LastFailureTime = &nowTime
|
||||
|
||||
var reason, message string
|
||||
condition := apiutil.GetCertificateRequestCondition(req, cmapi.CertificateRequestConditionReady)
|
||||
|
||||
reason = condition.Reason
|
||||
message = fmt.Sprintf("The certificate request has failed to complete and will be retried: %s",
|
||||
condition.Message)
|
||||
|
||||
crt = crt.DeepCopy()
|
||||
apiutil.SetCertificateCondition(crt, cmapi.CertificateConditionIssuing, cmmeta.ConditionFalse, reason, message)
|
||||
|
||||
_, err := c.client.CertmanagerV1alpha2().Certificates(crt.Namespace).UpdateStatus(ctx, crt, metav1.UpdateOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.recorder.Event(crt, corev1.EventTypeWarning, reason, message)
|
||||
|
||||
return nil
|
||||
|
||||
// If the CertificateRequest is valid, verify its status and update
|
||||
// accordingly.
|
||||
case cmapi.CertificateRequestReasonIssued:
|
||||
return c.issueCertificate(ctx, namespace, nextPrivateKeySecretName, nextRevision, crt, req)
|
||||
|
||||
// CertificateRequest is not in a final state so do nothing.
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// issueCertificate will ensure the public key of the CSR matches the signed
|
||||
// certificate, and then store the certificate, CA and private key into the
|
||||
// Secret in the appropriate format type.
|
||||
func (c *controller) issueCertificate(ctx context.Context, namespace, nextPrivateKeySecretName string, nextRevision int, crt *cmapi.Certificate, req *cmapi.CertificateRequest) error {
|
||||
csr, err := utilpki.DecodeX509CertificateRequestBytes(req.Spec.CSRPEM)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
key, err := utilkube.SecretTLSKeyRef(ctx, c.secretLister, namespace, nextPrivateKeySecretName, corev1.TLSPrivateKeyKey)
|
||||
if apierrors.IsNotFound(err) {
|
||||
// If secret does not exist, then the public key does not match. Do
|
||||
// nothing (requestmanager will handle this).
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
publicKeyMatches, err := utilpki.PublicKeyMatchesCSR(key.Public, csr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If public key does not match, do nothing (requestmanager will handle this).
|
||||
if !publicKeyMatches {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Verify the CSR options match what is requested in certificate.spec.
|
||||
violations, err := certificates.RequestMatchesSpec(req, crt.Spec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If there are violations in the spec, then the requestmanager will handle this.
|
||||
if len(violations) > 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
//Encode and issue the key-pair (store it in the Secret)
|
||||
nextPrivateKeySecret, err := c.secretLister.Secrets(namespace).Get(*crt.Status.NextPrivateKeySecretName)
|
||||
if err != nil {
|
||||
// Even if the secret does not exist, we should error here as something
|
||||
// potentially has gone very wrong. We should back off to be safe and try
|
||||
// again once the next Secret has been created with the private key.
|
||||
return err
|
||||
}
|
||||
|
||||
keyData, ok := nextPrivateKeySecret.Data[corev1.TLSPrivateKeyKey]
|
||||
if !ok {
|
||||
return fmt.Errorf("failed to find private key in Secret %s/%s at key %q",
|
||||
namespace, *crt.Status.NextPrivateKeySecretName, corev1.TLSPrivateKeyKey)
|
||||
}
|
||||
|
||||
signedCertificate := req.Status.Certificate
|
||||
ca := req.Status.CA
|
||||
|
||||
err = c.updateSecretData(ctx, namespace, crt, secretData{sk: keyData, cert: signedCertificate, ca: ca})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
//Set status.revision to revision of the CertificateRequest
|
||||
crt.Status.Revision = &nextRevision
|
||||
|
||||
crt = crt.DeepCopy()
|
||||
|
||||
// Remove Issuing status condition
|
||||
apiutil.RemoveCertificateCondition(crt, cmapi.CertificateConditionIssuing)
|
||||
|
||||
//Clear status.lastFailureTime (if set)
|
||||
crt.Status.LastFailureTime = nil
|
||||
|
||||
_, err = c.client.CertmanagerV1alpha2().Certificates(crt.Namespace).UpdateStatus(ctx, crt, metav1.UpdateOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
message := "The certificate has been successfully issued"
|
||||
c.recorder.Event(crt, corev1.EventTypeNormal, "Issuing", message)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// controllerWrapper wraps the `controller` structure to make it implement
|
||||
// the controllerpkg.queueingController interface
|
||||
type controllerWrapper struct {
|
||||
*controller
|
||||
}
|
||||
|
||||
func (c *controllerWrapper) 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)
|
||||
|
||||
ctrl, queue, mustSync := NewController(log,
|
||||
ctx.Client,
|
||||
ctx.CMClient,
|
||||
ctx.KubeSharedInformerFactory,
|
||||
ctx.SharedInformerFactory,
|
||||
ctx.Recorder,
|
||||
ctx.Clock,
|
||||
ctx.CertificateOptions,
|
||||
)
|
||||
c.controller = ctrl
|
||||
|
||||
return queue, mustSync, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
controllerpkg.Register(ControllerName, func(ctx *controllerpkg.Context) (controllerpkg.Interface, error) {
|
||||
return controllerpkg.NewBuilder(ctx, ControllerName).
|
||||
For(&controllerWrapper{}).
|
||||
Complete()
|
||||
})
|
||||
}
|
||||
136
pkg/controller/expcertificates/issuing/keystore.go
Normal file
136
pkg/controller/expcertificates/issuing/keystore.go
Normal file
@ -0,0 +1,136 @@
|
||||
/*
|
||||
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 issuing
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
// 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 string, 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, []byte(password)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
225
pkg/controller/expcertificates/issuing/keystore_test.go
Normal file
225
pkg/controller/expcertificates/issuing/keystore_test.go
Normal file
@ -0,0 +1,225 @@
|
||||
/*
|
||||
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 issuing
|
||||
|
||||
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(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)
|
||||
})
|
||||
}
|
||||
}
|
||||
190
pkg/controller/expcertificates/issuing/secret.go
Normal file
190
pkg/controller/expcertificates/issuing/secret.go
Normal file
@ -0,0 +1,190 @@
|
||||
/*
|
||||
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 issuing
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
apiutil "github.com/jetstack/cert-manager/pkg/api/util"
|
||||
cmapi "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha2"
|
||||
cmmeta "github.com/jetstack/cert-manager/pkg/apis/meta/v1"
|
||||
utilpki "github.com/jetstack/cert-manager/pkg/util/pki"
|
||||
)
|
||||
|
||||
// secretData is a structure wrapping private key, certificate and CA data
|
||||
type secretData struct {
|
||||
sk, cert, ca []byte
|
||||
}
|
||||
|
||||
// updateSecretData will ensure the Secret resource contains the given secret
|
||||
// data as well as appropriate metadata.
|
||||
// If the Secret resource does not exist, it 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 *controller) updateSecretData(ctx context.Context, namespace string, crt *cmapi.Certificate, data secretData) error {
|
||||
// Fetch a copy of the existing Secret resource
|
||||
secret, err := c.secretLister.Secrets(crt.Namespace).Get(crt.Spec.SecretName)
|
||||
if !apierrors.IsNotFound(err) && err != nil {
|
||||
// If secret doesn't exist yet, then don't error
|
||||
return err
|
||||
}
|
||||
|
||||
secretExists := (secret != nil)
|
||||
|
||||
// If the seret does not exist yet, then we need to create one
|
||||
if !secretExists {
|
||||
secret = &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: crt.Spec.SecretName,
|
||||
Namespace: namespace,
|
||||
},
|
||||
Type: corev1.SecretTypeTLS,
|
||||
}
|
||||
}
|
||||
|
||||
// secret will be overwritten by 'existingSecret' if existingSecret is non-nil
|
||||
if c.enableSecretOwnerReferences {
|
||||
secret.OwnerReferences = []metav1.OwnerReference{*metav1.NewControllerRef(crt, certificateGvk)}
|
||||
}
|
||||
|
||||
//newSecret := secret.DeepCopy()
|
||||
|
||||
err = c.setSecretValues(crt, secret, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: P12/JKS values use a random parameter so it's values will always
|
||||
// change. Devise a better solution for checking change.
|
||||
//if reflect.DeepEqual(secret, newSecret) {
|
||||
// return nil
|
||||
//}
|
||||
|
||||
// If secret does not exist then create it
|
||||
if !secretExists {
|
||||
_, err = c.kubeClient.CoreV1().Secrets(secret.Namespace).Create(ctx, secret, metav1.CreateOptions{})
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = c.kubeClient.CoreV1().Secrets(secret.Namespace).Update(ctx, secret, metav1.UpdateOptions{})
|
||||
return err
|
||||
}
|
||||
|
||||
// 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 *controller) setSecretValues(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)
|
||||
}
|
||||
|
||||
// Handle the experimental PKCS12 support
|
||||
if c.experimentalIssuePKCS12 {
|
||||
// Only write a new PKCS12 file if any of the private key/certificate/CA data has
|
||||
// actually changed.
|
||||
if data.sk != nil && data.cert != nil &&
|
||||
(!bytes.Equal(s.Data[corev1.TLSPrivateKeyKey], data.sk) ||
|
||||
!bytes.Equal(s.Data[corev1.TLSCertKey], data.cert) ||
|
||||
!bytes.Equal(s.Data[cmmeta.TLSCAKey], data.ca)) {
|
||||
keystoreData, err := encodePKCS12Keystore(c.experimentalPKCS12KeystorePassword, data.sk, 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
|
||||
}
|
||||
}
|
||||
// Handle the experimental JKS support
|
||||
if c.experimentalIssueJKS {
|
||||
// Only write a new JKS file if any of the private key/certificate/CA data has
|
||||
// actually changed.
|
||||
if data.sk != nil && data.cert != nil &&
|
||||
(!bytes.Equal(s.Data[corev1.TLSPrivateKeyKey], data.sk) ||
|
||||
!bytes.Equal(s.Data[corev1.TLSCertKey], data.cert) ||
|
||||
!bytes.Equal(s.Data[cmmeta.TLSCAKey], data.ca)) {
|
||||
keystoreData, err := encodeJKSKeystore(c.experimentalJKSPassword, data.sk, data.cert, data.ca)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error encoding JKS bundle: %w", err)
|
||||
}
|
||||
// always overwrite the keystore entry for now
|
||||
s.Data[jksSecretKey] = keystoreData
|
||||
}
|
||||
}
|
||||
|
||||
s.Data[corev1.TLSPrivateKeyKey] = data.sk
|
||||
s.Data[corev1.TLSCertKey] = data.cert
|
||||
s.Data[cmmeta.TLSCAKey] = data.ca
|
||||
|
||||
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 := utilpki.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(utilpki.IPAddressesToString(x509Cert.IPAddresses), ",")
|
||||
s.Annotations[cmapi.URISANAnnotationKey] = strings.Join(utilpki.URLsToString(x509Cert.URIs), ",")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -91,7 +91,8 @@ func NewController(
|
||||
})
|
||||
secretsInformer.Informer().AddEventHandler(&controllerpkg.BlockingEventHandler{
|
||||
// Trigger reconciles on changes to the Secret named `spec.secretName`
|
||||
WorkFunc: certificates.EnqueueCertificatesForSecretNameFunc(log, certificateInformer.Lister(), labels.Everything(), queue),
|
||||
WorkFunc: certificates.EnqueueCertificatesForSecretNameFunc(log, certificateInformer.Lister(), labels.Everything(),
|
||||
certificates.WithSecretNamePredicateFunc, queue),
|
||||
})
|
||||
|
||||
// build a list of InformerSynced functions that will be returned by the Register method.
|
||||
@ -181,9 +182,9 @@ func (c *controller) buildPolicyInputForCertificate(ctx context.Context, crt *cm
|
||||
// Attempt to fetch the CertificateRequest resource for the current 'status.revision'.
|
||||
var req *cmapi.CertificateRequest
|
||||
if crt.Status.Revision != nil {
|
||||
reqs, err := certificates.ListCertificateRequestsMatchingPredicate(c.certificateRequestLister.CertificateRequests(crt.Namespace),
|
||||
reqs, err := certificates.ListCertificateRequestsMatchingPredicates(c.certificateRequestLister.CertificateRequests(crt.Namespace),
|
||||
labels.Everything(),
|
||||
certificates.WithOwnerPredicateFunc(crt),
|
||||
certificates.WithCertificateRequestOwnerPredicateFunc(crt),
|
||||
certificates.WithCertificateRevisionPredicateFunc(*crt.Status.Revision),
|
||||
)
|
||||
if err != nil {
|
||||
|
||||
@ -42,11 +42,12 @@ func CertificateGetFunc(lister cmlisters.CertificateLister) GetFunc {
|
||||
}
|
||||
|
||||
// EnqueueCertificatesForSecretNameFunc will enqueue Certificate resources that
|
||||
// specify a `spec.secretName` equal to the name of the Secret resource being
|
||||
// processed.
|
||||
// satisfy a CertificatePredicateFunc based upon the name of the Secret resource
|
||||
// being processed.
|
||||
// This is used to trigger Certificates to reconcile for changes to the Secret
|
||||
// being managed.
|
||||
func EnqueueCertificatesForSecretNameFunc(log logr.Logger, lister cmlisters.CertificateLister, selector labels.Selector, queue workqueue.Interface) func(obj interface{}) {
|
||||
func EnqueueCertificatesForSecretNameFunc(log logr.Logger, lister cmlisters.CertificateLister, selector labels.Selector,
|
||||
secretNamePredicate WithCertificatePredicateFunc, queue workqueue.Interface) func(obj interface{}) {
|
||||
return func(obj interface{}) {
|
||||
s, ok := obj.(*corev1.Secret)
|
||||
if !ok {
|
||||
@ -54,7 +55,7 @@ func EnqueueCertificatesForSecretNameFunc(log logr.Logger, lister cmlisters.Cert
|
||||
return
|
||||
}
|
||||
|
||||
certs, err := ListCertificatesMatchingPredicate(lister.Certificates(s.Namespace), selector, WithSecretNamePredicateFunc(s.Name))
|
||||
certs, err := ListCertificatesMatchingPredicate(lister.Certificates(s.Namespace), selector, secretNamePredicate(s.Name))
|
||||
if err != nil {
|
||||
log.Error(err, "Failed listing Certificate resources")
|
||||
return
|
||||
@ -71,6 +72,8 @@ func EnqueueCertificatesForSecretNameFunc(log logr.Logger, lister cmlisters.Cert
|
||||
}
|
||||
}
|
||||
|
||||
type WithCertificatePredicateFunc func(string) CertificatePredicateFunc
|
||||
|
||||
type CertificatePredicateFunc func(*cmapi.Certificate) bool
|
||||
|
||||
func WithSecretNamePredicateFunc(name string) CertificatePredicateFunc {
|
||||
@ -79,6 +82,15 @@ func WithSecretNamePredicateFunc(name string) CertificatePredicateFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func WithNextPrivateKeySecretNamePredicateFunc(name string) CertificatePredicateFunc {
|
||||
return func(crt *cmapi.Certificate) bool {
|
||||
if crt.Status.NextPrivateKeySecretName == nil {
|
||||
return false
|
||||
}
|
||||
return *crt.Status.NextPrivateKeySecretName == name
|
||||
}
|
||||
}
|
||||
|
||||
func ListCertificatesMatchingPredicate(lister cmlisters.CertificateNamespaceLister, selector labels.Selector, predicate CertificatePredicateFunc) ([]*cmapi.Certificate, error) {
|
||||
crts, err := lister.List(selector)
|
||||
if err != nil {
|
||||
@ -93,10 +105,6 @@ func ListCertificatesMatchingPredicate(lister cmlisters.CertificateNamespaceList
|
||||
return out, nil
|
||||
}
|
||||
|
||||
const (
|
||||
CertificateRevisionAnnotationKey = "cert-manager.io/certificate-revision"
|
||||
)
|
||||
|
||||
type CertificateRequestPredicateFunc func(*cmapi.CertificateRequest) bool
|
||||
|
||||
func WithCertificateRevisionPredicateFunc(revision int) CertificateRequestPredicateFunc {
|
||||
@ -104,17 +112,17 @@ func WithCertificateRevisionPredicateFunc(revision int) CertificateRequestPredic
|
||||
if req.Annotations == nil {
|
||||
return false
|
||||
}
|
||||
return req.Annotations[CertificateRevisionAnnotationKey] == fmt.Sprintf("%d", revision)
|
||||
return req.Annotations[cmapi.CertificateRequestRevisionAnnotationKey] == fmt.Sprintf("%d", revision)
|
||||
}
|
||||
}
|
||||
|
||||
func WithOwnerPredicateFunc(owner metav1.Object) CertificateRequestPredicateFunc {
|
||||
func WithCertificateRequestOwnerPredicateFunc(owner metav1.Object) CertificateRequestPredicateFunc {
|
||||
return func(req *cmapi.CertificateRequest) bool {
|
||||
return metav1.IsControlledBy(req, owner)
|
||||
}
|
||||
}
|
||||
|
||||
func ListCertificateRequestsMatchingPredicate(lister cmlisters.CertificateRequestNamespaceLister, selector labels.Selector, predicates ...CertificateRequestPredicateFunc) ([]*cmapi.CertificateRequest, error) {
|
||||
func ListCertificateRequestsMatchingPredicates(lister cmlisters.CertificateRequestNamespaceLister, selector labels.Selector, predicates ...CertificateRequestPredicateFunc) ([]*cmapi.CertificateRequest, error) {
|
||||
reqs, err := lister.List(selector)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -132,6 +140,7 @@ func ListCertificateRequestsMatchingPredicate(lister cmlisters.CertificateReques
|
||||
out = append(out, req)
|
||||
}
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
|
||||
@ -30,6 +30,9 @@ const (
|
||||
// Annotation names for CertificateRequests
|
||||
const (
|
||||
CRPrivateKeyAnnotationKey = "cert-manager.io/private-key-secret-name"
|
||||
|
||||
// Annotation to declare the CertificateRequest "revision", beloning to a Certificate Resource
|
||||
CertificateRequestRevisionAnnotationKey = "cert-manager.io/certificate-revision"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
Loading…
Reference in New Issue
Block a user