cert-manager/pkg/controller/certificates/sync.go

320 lines
10 KiB
Go

package certificates
import (
"context"
"crypto/x509"
"fmt"
"time"
api "k8s.io/api/core/v1"
k8sErrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/apimachinery/pkg/util/runtime"
"github.com/golang/glog"
"github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha1"
"github.com/jetstack/cert-manager/pkg/issuer"
"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"
)
const renewBefore = time.Hour * 24 * 30
const (
errorIssuerNotFound = "ErrorIssuerNotFound"
errorIssuerNotReady = "ErrorIssuerNotReady"
errorIssuerInit = "ErrorIssuerInitialization"
errorCheckCertificate = "ErrorCheckCertificate"
errorGetCertificate = "ErrorGetCertificate"
errorPreparingCertificate = "ErrorPrepareCertificate"
errorIssuingCertificate = "ErrorIssueCertificate"
errorRenewingCertificate = "ErrorRenewCertificate"
errorSavingCertificate = "ErrorSaveCertificate"
reasonPreparingCertificate = "PrepareCertificate"
reasonIssuingCertificate = "IssueCertificate"
reasonRenewingCertificate = "RenewCertificate"
successCeritificateIssued = "CeritifcateIssued"
successCeritificateRenewed = "CeritifcateRenewed"
successRenewalScheduled = "RenewalScheduled"
messageIssuerNotFound = "Issuer %s does not exist"
messageIssuerNotReady = "Issuer %s not ready"
messageIssuerErrorInit = "Error initializing issuer: "
messageErrorCheckCertificate = "Error checking existing TLS certificate: "
messageErrorGetCertificate = "Error getting TLS certificate: "
messageErrorPreparingCertificate = "Error preparing issuer for certificate: "
messageErrorIssuingCertificate = "Error issuing certificate: "
messageErrorRenewingCertificate = "Error renewing certificate: "
messageErrorSavingCertificate = "Error saving TLS certificate: "
messagePreparingCertificate = "Preparing certificate with issuer"
messageIssuingCertificate = "Issuing certificate..."
messageRenewingCertificate = "Renewing certificate..."
messageCertificateIssued = "Certificated issued successfully"
messageCertificateRenewed = "Certificated renewed successfully"
messageRenewalScheduled = "Certificate scheduled for renewal in %d hours"
)
func (c *Controller) Sync(ctx context.Context, crt *v1alpha1.Certificate) (err error) {
// step zero: check if the referenced issuer exists and is ready
issuerObj, err := c.getGenericIssuer(crt)
if err != nil {
s := fmt.Sprintf(messageIssuerNotFound, err.Error())
glog.Info(s)
c.recorder.Event(crt, api.EventTypeWarning, errorIssuerNotFound, s)
return err
}
issuerReady := issuerObj.HasCondition(v1alpha1.IssuerCondition{
Type: v1alpha1.IssuerConditionReady,
Status: v1alpha1.ConditionTrue,
})
if !issuerReady {
s := fmt.Sprintf(messageIssuerNotReady, issuerObj.GetObjectMeta().Name)
glog.Info(s)
c.recorder.Event(crt, api.EventTypeWarning, errorIssuerNotReady, s)
return fmt.Errorf(s)
}
i, err := c.issuerFactory.IssuerFor(issuerObj)
if err != nil {
s := messageIssuerErrorInit + err.Error()
glog.Info(s)
c.recorder.Event(crt, api.EventTypeWarning, errorIssuerInit, s)
return err
}
// grab existing certificate and validate private key
cert, err := kube.SecretTLSCert(c.secretLister, crt.Namespace, crt.Spec.SecretName)
if err != nil {
s := messageErrorCheckCertificate + err.Error()
glog.Info(s)
c.recorder.Event(crt, api.EventTypeWarning, errorCheckCertificate, s)
}
// if an error is returned, and that error is something other than
// IsNotFound or invalid data, then we should return the error.
if err != nil && !k8sErrors.IsNotFound(err) && !errors.IsInvalidData(err) {
return err
}
// as there is an existing certificate, or we may create one below, we will
// run scheduleRenewal to schedule a renewal if required at the end of
// execution.
defer c.scheduleRenewal(crt)
crtCopy := crt.DeepCopy()
expectedCN, err := pki.CommonNameForCertificate(crtCopy)
if err != nil {
return err
}
expectedDNSNames, err := pki.DNSNamesForCertificate(crtCopy)
if err != nil {
return err
}
// if the certificate was not found, or the certificate data is invalid, we
// should issue a new certificate.
// if the certificate is valid for a list of domains other than those
// listed in the certificate spec, we should re-issue the certificate.
if k8sErrors.IsNotFound(err) || errors.IsInvalidData(err) ||
expectedCN != cert.Subject.CommonName || !util.EqualUnsorted(cert.DNSNames, expectedDNSNames) {
err := c.issue(ctx, i, crtCopy)
updateErr := c.updateCertificateStatus(crtCopy)
if err != nil || updateErr != nil {
return utilerrors.NewAggregate([]error{err, updateErr})
}
return nil
}
// calculate the amount of time until expiry
durationUntilExpiry := cert.NotAfter.Sub(time.Now())
// calculate how long until we should start attempting to renew the
// certificate
renewIn := durationUntilExpiry - renewBefore
// if we should being attempting to renew now, then trigger a renewal
if renewIn <= 0 {
err := c.renew(ctx, i, crtCopy)
updateErr := c.updateCertificateStatus(crtCopy)
if err != nil || updateErr != nil {
return utilerrors.NewAggregate([]error{err, updateErr})
}
}
return nil
}
func (c *Controller) getGenericIssuer(crt *v1alpha1.Certificate) (v1alpha1.GenericIssuer, error) {
switch crt.Spec.IssuerRef.Kind {
case "", v1alpha1.IssuerKind:
return c.issuerLister.Issuers(crt.Namespace).Get(crt.Spec.IssuerRef.Name)
case v1alpha1.ClusterIssuerKind:
if c.clusterIssuerLister == nil {
return nil, fmt.Errorf("cannot get ClusterIssuer for %q as cert-manager is scoped to a single namespace", crt.Name)
}
return c.clusterIssuerLister.Get(crt.Spec.IssuerRef.Name)
default:
return nil, fmt.Errorf(`invalid value %q for certificate issuer kind. Must be empty, %q or %q`, crt.Spec.IssuerRef.Kind, v1alpha1.IssuerKind, v1alpha1.ClusterIssuerKind)
}
}
func needsRenew(cert *x509.Certificate) bool {
durationUntilExpiry := cert.NotAfter.Sub(time.Now())
renewIn := durationUntilExpiry - renewBefore
// step three: check if referenced secret is valid (after start & before expiry)
if renewIn <= 0 {
return true
}
return false
}
func (c *Controller) scheduleRenewal(crt *v1alpha1.Certificate) {
key, err := keyFunc(crt)
if err != nil {
runtime.HandleError(fmt.Errorf("error getting key for certificate resource: %s", err.Error()))
return
}
cert, err := kube.SecretTLSCert(c.secretLister, crt.Namespace, crt.Spec.SecretName)
if err != nil {
runtime.HandleError(fmt.Errorf("[%s/%s] Error getting certificate '%s': %s", crt.Namespace, crt.Name, crt.Spec.SecretName, err.Error()))
return
}
durationUntilExpiry := cert.NotAfter.Sub(time.Now())
renewIn := durationUntilExpiry - renewBefore
c.scheduledWorkQueue.Add(key, renewIn)
s := fmt.Sprintf(messageRenewalScheduled, renewIn/time.Hour)
glog.Info(s)
c.recorder.Event(crt, api.EventTypeNormal, successRenewalScheduled, s)
}
// return an error on failure. If retrieval is succesful, the certificate data
// and private key will be stored in the named secret
func (c *Controller) issue(ctx context.Context, issuer issuer.Interface, crt *v1alpha1.Certificate) error {
var err error
s := messagePreparingCertificate
glog.Info(s)
c.recorder.Event(crt, api.EventTypeNormal, reasonPreparingCertificate, s)
if err = issuer.Prepare(ctx, crt); err != nil {
s := messageErrorPreparingCertificate + err.Error()
glog.Info(s)
c.recorder.Event(crt, api.EventTypeWarning, errorPreparingCertificate, s)
return err
}
s = messageIssuingCertificate
glog.Info(s)
c.recorder.Event(crt, api.EventTypeNormal, reasonIssuingCertificate, s)
var key, cert []byte
key, cert, err = issuer.Issue(ctx, crt)
if err != nil {
s := messageErrorIssuingCertificate + err.Error()
glog.Info(s)
c.recorder.Event(crt, api.EventTypeWarning, errorIssuingCertificate, s)
return err
}
_, err = kube.EnsureSecret(c.client, &api.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: crt.Spec.SecretName,
Namespace: crt.Namespace,
},
Type: api.SecretTypeTLS,
Data: map[string][]byte{
api.TLSCertKey: cert,
api.TLSPrivateKeyKey: key,
},
})
if err != nil {
s := messageErrorSavingCertificate + err.Error()
glog.Info(s)
c.recorder.Event(crt, api.EventTypeWarning, errorSavingCertificate, s)
return err
}
s = messageCertificateIssued
glog.Info(s)
c.recorder.Event(crt, api.EventTypeNormal, successCeritificateIssued, s)
return nil
}
// renew will attempt to renew a certificate from the specified issuer, or
// return an error on failure. If renewal is succesful, the certificate data
// and private key will be stored in the named secret
func (c *Controller) renew(ctx context.Context, issuer issuer.Interface, crt *v1alpha1.Certificate) error {
var err error
s := messagePreparingCertificate
glog.Info(s)
c.recorder.Event(crt, api.EventTypeNormal, reasonPreparingCertificate, s)
if err = issuer.Prepare(ctx, crt); err != nil {
s := messageErrorPreparingCertificate + err.Error()
glog.Info(s)
c.recorder.Event(crt, api.EventTypeWarning, errorPreparingCertificate, s)
return err
}
s = messageRenewingCertificate
glog.Info(s)
c.recorder.Event(crt, api.EventTypeNormal, reasonRenewingCertificate, s)
var key, cert []byte
key, cert, err = issuer.Renew(ctx, crt)
if err != nil {
s := messageErrorRenewingCertificate + err.Error()
glog.Info(s)
c.recorder.Event(crt, api.EventTypeWarning, errorRenewingCertificate, s)
return err
}
_, err = kube.EnsureSecret(c.client, &api.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: crt.Spec.SecretName,
Namespace: crt.Namespace,
},
Type: api.SecretTypeTLS,
Data: map[string][]byte{
api.TLSCertKey: cert,
api.TLSPrivateKeyKey: key,
},
})
if err != nil {
s := messageErrorSavingCertificate + err.Error()
glog.Info(s)
c.recorder.Event(crt, api.EventTypeWarning, errorSavingCertificate, s)
return err
}
s = messageCertificateRenewed
glog.Info(s)
c.recorder.Event(crt, api.EventTypeNormal, successCeritificateRenewed, s)
return nil
}
func (c *Controller) updateCertificateStatus(crt *v1alpha1.Certificate) error {
// TODO: replace Update call with UpdateStatus. This requires a custom API
// server with the /status subresource enabled and/or subresource support
// for CRDs (https://github.com/kubernetes/kubernetes/issues/38113)
_, err := c.cmClient.CertmanagerV1alpha1().Certificates(crt.Namespace).Update(crt)
return err
}