From efbe18d0292297b440f315ead0970963927dd666 Mon Sep 17 00:00:00 2001 From: Martin Weindel Date: Tue, 18 Aug 2020 16:28:07 +0200 Subject: [PATCH] introducing CA spec, refactoring --- examples/20-issuer-ca.yaml | 19 ++++ examples/30-cert-ca.yaml | 19 ++++ pkg/apis/cert/v1alpha1/issuer.go | 12 +++ pkg/controller/issuer/acme/handler.go | 7 +- pkg/controller/issuer/ca/handler.go | 88 +++++++++++++++++++ .../issuer/certificate/reconciler.go | 36 +++++++- pkg/controller/issuer/core/const.go | 7 ++ pkg/controller/issuer/core/support.go | 40 ++++++--- pkg/controller/issuer/reconciler.go | 3 +- 9 files changed, 208 insertions(+), 23 deletions(-) create mode 100644 examples/20-issuer-ca.yaml create mode 100644 examples/30-cert-ca.yaml create mode 100644 pkg/controller/issuer/ca/handler.go diff --git a/examples/20-issuer-ca.yaml b/examples/20-issuer-ca.yaml new file mode 100644 index 00000000..abf72e78 --- /dev/null +++ b/examples/20-issuer-ca.yaml @@ -0,0 +1,19 @@ +apiVersion: v1 +kind: Secret +metadata: + name: issuer-ca-secret + namespace: default +type: Opaque +data: + foo: Vk13YXJlMSFWTXdhcmUxIQ== +--- +apiVersion: cert.gardener.cloud/v1alpha1 +kind: Issuer +metadata: + name: issuer-ca + namespace: default +spec: + ca: + privateKeySecretRef: + name: issuer-ca-secret + namespace: default \ No newline at end of file diff --git a/examples/30-cert-ca.yaml b/examples/30-cert-ca.yaml new file mode 100644 index 00000000..b46b468f --- /dev/null +++ b/examples/30-cert-ca.yaml @@ -0,0 +1,19 @@ +apiVersion: cert.gardener.cloud/v1alpha1 +kind: Certificate +metadata: + annotations: + # class annotation only needed if cert-controller-manager is started with --cert-class=myclass + #cert.gardener.cloud/class: myclass + name: cert-ca + namespace: default +spec: + commonName: cert1.mydomain.com + dnsNames: + - cert1.my-other-domain.com + # if issuer is not specified, the default issuer is used + issuerRef: + name: issuer-ca + # optionally specify secret to store certificate + secretRef: + name: cert-ca-secret + namespace: default diff --git a/pkg/apis/cert/v1alpha1/issuer.go b/pkg/apis/cert/v1alpha1/issuer.go index 09b51894..1c7fd2c0 100644 --- a/pkg/apis/cert/v1alpha1/issuer.go +++ b/pkg/apis/cert/v1alpha1/issuer.go @@ -37,6 +37,9 @@ type IssuerSpec struct { // ACME is the ACME protocol specific spec. // +optional ACME *ACMESpec `json:"acme,omitempty"` + // CA is the CA specific spec. + // +optional + CA *CASpec `json:"ca,omitempty"` // RequestsPerDayQuota is the maximum number of certificate requests per days allowed for this issuer // +optional RequestsPerDayQuota *int `json:"requestsPerDayQuota,omitempty"` @@ -58,6 +61,13 @@ type ACMESpec struct { PrivateKeySecretRef *corev1.SecretReference `json:"privateKeySecretRef,omitempty"` } +// CASpec is the CA specific part of the spec. +type CASpec struct { + // PrivateKeySecretRef is the secret ref to the CA secret. + // +optional + PrivateKeySecretRef *corev1.SecretReference `json:"privateKeySecretRef,omitempty"` +} + // IssuerStatus is the status of the issuer. type IssuerStatus struct { // ObservedGeneration is the observed generation of the spec. @@ -70,6 +80,8 @@ type IssuerStatus struct { Type *string `json:"type"` // ACME is the ACME specific status. ACME *runtime.RawExtension `json:"acme,omitempty"` + // CA is the CA specific status. + CA *runtime.RawExtension `json:"ca,omitempty"` // RequestsPerDayQuota is the actual maximum number of certificate requests per days allowed for this issuer RequestsPerDayQuota int `json:"requestsPerDayQuota,omitempty"` } diff --git a/pkg/controller/issuer/acme/handler.go b/pkg/controller/issuer/acme/handler.go index f8ba7c23..2d030236 100644 --- a/pkg/controller/issuer/acme/handler.go +++ b/pkg/controller/issuer/acme/handler.go @@ -20,10 +20,7 @@ import ( "github.com/gardener/cert-management/pkg/controller/issuer/core" ) -// ACMEType is the type name for ACME. -const ACMEType = "acme" - -var acmeType = ACMEType +var acmeType = core.ACMEType // NewACMEIssuerHandler creates an ACME IssuerHandler. func NewACMEIssuerHandler(support *core.Support) (core.IssuerHandler, error) { @@ -37,7 +34,7 @@ type acmeIssuerHandler struct { } func (r *acmeIssuerHandler) Type() string { - return ACMEType + return core.ACMEType } func (r *acmeIssuerHandler) CanReconcile(issuer *api.Issuer) bool { diff --git a/pkg/controller/issuer/ca/handler.go b/pkg/controller/issuer/ca/handler.go new file mode 100644 index 00000000..baf6c18e --- /dev/null +++ b/pkg/controller/issuer/ca/handler.go @@ -0,0 +1,88 @@ +/* + * Copyright 2019 SAP SE or an SAP affiliate company. All rights reserved. ur file is licensed under the Apache Software License, v. 2 except as noted otherwise in the LICENSE file + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use ur 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 + * + */ + +package ca + +import ( + "fmt" + + corev1 "k8s.io/api/core/v1" + + "github.com/gardener/controller-manager-library/pkg/controllermanager/controller/reconcile" + "github.com/gardener/controller-manager-library/pkg/logger" + "github.com/gardener/controller-manager-library/pkg/resources" + + api "github.com/gardener/cert-management/pkg/apis/cert/v1alpha1" + "github.com/gardener/cert-management/pkg/controller/issuer/core" +) + +var caType = core.CAType + +// NewCAIssuerHandler creates an ACME IssuerHandler. +func NewCAIssuerHandler(support *core.Support) (core.IssuerHandler, error) { + return &caIssuerHandler{ + support: support, + }, nil +} + +type caIssuerHandler struct { + support *core.Support +} + +func (r *caIssuerHandler) Type() string { + return core.CAType +} + +func (r *caIssuerHandler) CanReconcile(issuer *api.Issuer) bool { + return issuer != nil && issuer.Spec.CA != nil +} + +func (r *caIssuerHandler) Reconcile(logger logger.LogContext, obj resources.Object, issuer *api.Issuer) reconcile.Status { + logger.Infof("reconciling") + + ca := issuer.Spec.CA + if ca == nil { + return r.failedCA(logger, obj, api.StateError, fmt.Errorf("missing CA spec")) + } + + r.support.RememberIssuerSecret(obj.ObjectName(), ca.PrivateKeySecretRef, "") + + var secret *corev1.Secret + var err error + if ca.PrivateKeySecretRef != nil { + secret, err = r.support.ReadIssuerSecret(ca.PrivateKeySecretRef) + if err != nil { + return r.failedCA(logger, obj, api.StateError, fmt.Errorf("loading issuer secret failed with %s", err.Error())) + } + hash := r.support.CalcSecretHash(secret) + r.support.RememberIssuerSecret(obj.ObjectName(), ca.PrivateKeySecretRef, hash) + } + if secret != nil && issuer.Status.CA != nil && issuer.Status.CA.Raw != nil { + // TODO check secret? + return r.support.SucceededAndTriggerCertificates(logger, obj, &caType, issuer.Status.CA.Raw) + } else if secret != nil { + // TODO check secret + regRaw := []byte("{\"foo\":\"bar\"}") + + return r.support.SucceededAndTriggerCertificates(logger, obj, &caType, regRaw) + } else { + return r.failedCA(logger, obj, api.StateError, fmt.Errorf("`SecretRef` not provided")) + } +} + +func (r *caIssuerHandler) failedCA(logger logger.LogContext, obj resources.Object, state string, err error) reconcile.Status { + return r.support.Failed(logger, obj, state, &caType, err) +} diff --git a/pkg/controller/issuer/certificate/reconciler.go b/pkg/controller/issuer/certificate/reconciler.go index d400f011..536823b6 100644 --- a/pkg/controller/issuer/certificate/reconciler.go +++ b/pkg/controller/issuer/certificate/reconciler.go @@ -297,7 +297,26 @@ func (r *certReconciler) obtainCertificateAndPending(logger logger.LogContext, o cert := obj.Data().(*api.Certificate) logger.Infof("obtain certificate") - reguser, server, err := r.restoreRegUser(cert) + issuer, err := r.loadIssuer(cert) + if err != nil { + return r.failed(logger, obj, api.StateError, err) + } + + if issuer.Spec.ACME != nil && issuer.Spec.CA != nil { + return r.failed(logger, obj, api.StateError, fmt.Errorf("invalid issuer spec: only ACME or CA can be set, but not both")) + } + if issuer.Spec.ACME != nil { + return r.obtainCertificateAndPendingACME(logger, obj, renewSecret, cert, issuer) + } + if issuer.Spec.CA != nil { + return r.obtainCertificateCA(logger, obj, renewSecret, cert, issuer) + } + return r.failed(logger, obj, api.StateError, fmt.Errorf("incomplete issuer spec (ACME or CA section must be provided)")) +} + +func (r *certReconciler) obtainCertificateAndPendingACME(logger logger.LogContext, obj resources.Object, + renewSecret *corev1.Secret, cert *api.Certificate, issuer *api.Issuer) reconcile.Status { + reguser, server, err := r.restoreRegUser(issuer) if err != nil { return r.failed(logger, obj, api.StateError, err) } @@ -376,15 +395,24 @@ func (r *certReconciler) obtainCertificateAndPending(logger logger.LogContext, o return r.pending(logger, obj) } -func (r *certReconciler) restoreRegUser(crt *api.Certificate) (*legobridge.RegistrationUser, string, error) { +func (r *certReconciler) obtainCertificateCA(logger logger.LogContext, obj resources.Object, + renewSecret *corev1.Secret, cert *api.Certificate, issuer *api.Issuer) reconcile.Status { + // TODO create certificate + return r.succeeded(logger, obj) +} + +func (r *certReconciler) loadIssuer(crt *api.Certificate) (*api.Issuer, error) { // fetch issuer issuerObjectName := r.issuerObjectName(&crt.Spec) issuer := &api.Issuer{} _, err := r.support.GetIssuerResources().GetInto(issuerObjectName, issuer) if err != nil { - return nil, "", errors.Wrap(err, "fetching issuer failed") + return nil, errors.Wrap(err, "fetching issuer failed") } + return issuer, nil +} +func (r *certReconciler) restoreRegUser(issuer *api.Issuer) (*legobridge.RegistrationUser, string, error) { // fetch issuer secret secretRef := issuer.Spec.ACME.PrivateKeySecretRef if secretRef == nil { @@ -401,7 +429,7 @@ func (r *certReconciler) restoreRegUser(crt *api.Certificate) (*legobridge.Regis } issuerSecretObjectName := resources.NewObjectName(secretRef.Namespace, secretRef.Name) issuerSecret := &corev1.Secret{} - _, err = r.support.GetIssuerSecretResources().GetInto(issuerSecretObjectName, issuerSecret) + _, err := r.support.GetIssuerSecretResources().GetInto(issuerSecretObjectName, issuerSecret) if err != nil { return nil, "", errors.Wrap(err, "fetching issuer secret failed") } diff --git a/pkg/controller/issuer/core/const.go b/pkg/controller/issuer/core/const.go index 5808c172..cbfae557 100644 --- a/pkg/controller/issuer/core/const.go +++ b/pkg/controller/issuer/core/const.go @@ -6,6 +6,13 @@ package core +const ( + // ACMEType is the type name for ACME. + ACMEType = "acme" + // CAType is the type name for CA. + CAType = "ca" +) + const ( // OptDefaultIssuer is the default-issuer command line option. OptDefaultIssuer = "default-issuer" diff --git a/pkg/controller/issuer/core/support.go b/pkg/controller/issuer/core/support.go index a7ec2a1a..86573adf 100644 --- a/pkg/controller/issuer/core/support.go +++ b/pkg/controller/issuer/core/support.go @@ -10,21 +10,24 @@ import ( "bytes" "crypto/sha256" "fmt" - "github.com/gardener/cert-management/pkg/cert/metrics" + "sort" + "strings" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" - "sort" - "strings" - api "github.com/gardener/cert-management/pkg/apis/cert/v1alpha1" - "github.com/gardener/cert-management/pkg/cert/legobridge" - "github.com/gardener/cert-management/pkg/cert/utils" - ctrl "github.com/gardener/cert-management/pkg/controller" + "github.com/gardener/cert-management/pkg/cert/metrics" + "github.com/gardener/controller-manager-library/pkg/controllermanager/controller" "github.com/gardener/controller-manager-library/pkg/controllermanager/controller/reconcile" "github.com/gardener/controller-manager-library/pkg/logger" "github.com/gardener/controller-manager-library/pkg/resources" + + api "github.com/gardener/cert-management/pkg/apis/cert/v1alpha1" + "github.com/gardener/cert-management/pkg/cert/legobridge" + "github.com/gardener/cert-management/pkg/cert/utils" + ctrl "github.com/gardener/cert-management/pkg/controller" ) // Enqueuer is an interface to allow enqueue a key @@ -290,19 +293,30 @@ func (s *Support) SucceededAndTriggerCertificates(logger logger.LogContext, obj s.triggerCertificates(logger, obj.ObjectName()) mod, status := s.prepareUpdateStatus(obj, api.StateReady, itype, nil) + if itype != nil { + switch *itype { + case ACMEType: + updateTypeStatus(mod, &status.ACME, regRaw) + case CAType: + updateTypeStatus(mod, &status.CA, regRaw) + } + } + s.updateStatus(mod) + + return reconcile.Succeeded(logger) +} + +func updateTypeStatus(mod *resources.ModificationState, status **runtime.RawExtension, regRaw []byte) { changedRegistration := false - if status.ACME == nil || status.ACME.Raw == nil { + if *status == nil || (*status).Raw == nil { changedRegistration = regRaw != nil } else { - changedRegistration = !bytes.Equal(status.ACME.Raw, regRaw) + changedRegistration = !bytes.Equal((*status).Raw, regRaw) } if changedRegistration { - status.ACME = &runtime.RawExtension{Raw: regRaw} + *status = &runtime.RawExtension{Raw: regRaw} mod.Modify(true) } - s.updateStatus(mod) - - return reconcile.Succeeded(logger) } // AddCertificate adds a certificate diff --git a/pkg/controller/issuer/reconciler.go b/pkg/controller/issuer/reconciler.go index 54fadc8c..89c6cc47 100644 --- a/pkg/controller/issuer/reconciler.go +++ b/pkg/controller/issuer/reconciler.go @@ -8,6 +8,7 @@ package issuer import ( "github.com/gardener/cert-management/pkg/controller/issuer/acme" + "github.com/gardener/cert-management/pkg/controller/issuer/ca" "github.com/gardener/cert-management/pkg/controller/issuer/certificate" "github.com/gardener/cert-management/pkg/controller/issuer/core" corev1 "k8s.io/api/core/v1" @@ -21,7 +22,7 @@ import ( ) func newCompoundReconciler(c controller.Interface) (reconcile.Interface, error) { - handler, support, err := core.NewHandlerSupport(c, acme.NewACMEIssuerHandler) + handler, support, err := core.NewHandlerSupport(c, acme.NewACMEIssuerHandler, ca.NewCAIssuerHandler) if err != nil { return nil, err }