Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add initial basic CA issuer implementation #79

Merged
merged 15 commits into from Sep 14, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions cmd/controller/start.go
Expand Up @@ -13,6 +13,7 @@ import (
_ "github.com/jetstack-experimental/cert-manager/pkg/controller/certificates"
_ "github.com/jetstack-experimental/cert-manager/pkg/controller/issuers"
_ "github.com/jetstack-experimental/cert-manager/pkg/issuer/acme"
_ "github.com/jetstack-experimental/cert-manager/pkg/issuer/ca"
)

type CertManagerControllerOptions struct {
Expand Down
2 changes: 1 addition & 1 deletion pkg/apis/certmanager/v1alpha1/helpers.go
Expand Up @@ -52,7 +52,7 @@ func (c *CertificateACMEStatus) SaveAuthorization(a ACMEDomainAuthorization) {
c.Authorizations = append(c.Authorizations, a)
}

func IssuerHasCondition(iss *Issuer, condition IssuerCondition) bool {
func (iss *Issuer) HasCondition(condition IssuerCondition) bool {
if len(iss.Status.Conditions) == 0 {
return false
}
Expand Down
107 changes: 56 additions & 51 deletions pkg/apis/certmanager/v1alpha1/types.go
Expand Up @@ -47,6 +47,62 @@ type IssuerList struct {
// configuration required for the issuer.
type IssuerSpec struct {
ACME *ACMEIssuer `json:"acme,omitempty"`
CA *CAIssuer `json:"ca,omitempty"`
}

type CAIssuer struct {
SecretRef LocalObjectReference `json:"secretRef"`
}

// ACMEIssuer contains the specification for an ACME issuer
type ACMEIssuer struct {
// Email is the email for this account
Email string `json:"email"`
// Server is the ACME server URL
Server string `json:"server"`
// PrivateKey is the name of a secret containing the private key for this
// user account.
PrivateKey string `json:"privateKey"`
// DNS-01 config
DNS01 *ACMEIssuerDNS01Config `json:"dns-01"`
}

// ACMEIssuerDNS01Config is a structure containing the ACME DNS configuration
// option. One and only one of the fields within it should be set, when the
// ACME challenge type is set to dns-01
type ACMEIssuerDNS01Config struct {
Providers []ACMEIssuerDNS01Provider `json:"providers"`
}

type ACMEIssuerDNS01Provider struct {
Name string `json:"name"`

CloudDNS *ACMEIssuerDNS01ProviderCloudDNS `json:"clouddns,omitempty"`
Cloudflare *ACMEIssuerDNS01ProviderCloudflare `json:"cloudflare,omitempty"`
Route53 *ACMEIssuerDNS01ProviderRoute53 `json:"route53,omitempty"`
}

// ACMEIssuerDNS01ProviderCloudDNS is a structure containing the DNS
// configuration for Google Cloud DNS
type ACMEIssuerDNS01ProviderCloudDNS struct {
ServiceAccount SecretKeySelector `json:"serviceAccount"`
Project string `json:"project"`
}

// ACMEIssuerDNS01ProviderCloudflare is a structure containing the DNS
// configuration for Cloudflare
type ACMEIssuerDNS01ProviderCloudflare struct {
Email string `json:"email"`
APIKey SecretKeySelector `json:"apiKey"`
}

// ACMEIssuerDNS01ProviderRoute53 is a structure containing the Route 53
// configuration for AWS
type ACMEIssuerDNS01ProviderRoute53 struct {
AccessKeyID string `json:"accessKeyID"`
SecretAccessKey SecretKeySelector `json:"secretAccessKey"`
HostedZoneID string `json:"hostedZoneID"`
Region string `json:"region"`
}

// IssuerStatus contains status information about an Issuer
Expand Down Expand Up @@ -104,57 +160,6 @@ const (
ConditionUnknown ConditionStatus = "Unknown"
)

// ACMEIssuer contains the specification for an ACME issuer
type ACMEIssuer struct {
// Email is the email for this account
Email string `json:"email"`
// Server is the ACME server URL
Server string `json:"server"`
// PrivateKey is the name of a secret containing the private key for this
// user account.
PrivateKey string `json:"privateKey"`
// DNS-01 config
DNS01 *ACMEIssuerDNS01Config `json:"dns-01"`
}

// ACMEIssuerDNS01Config is a structure containing the ACME DNS configuration
// option. One and only one of the fields within it should be set, when the
// ACME challenge type is set to dns-01
type ACMEIssuerDNS01Config struct {
Providers []ACMEIssuerDNS01Provider `json:"providers"`
}

type ACMEIssuerDNS01Provider struct {
Name string `json:"name"`

CloudDNS *ACMEIssuerDNS01ProviderCloudDNS `json:"clouddns,omitempty"`
Cloudflare *ACMEIssuerDNS01ProviderCloudflare `json:"cloudflare,omitempty"`
Route53 *ACMEIssuerDNS01ProviderRoute53 `json:"route53,omitempty"`
}

// ACMEIssuerDNS01ProviderCloudDNS is a structure containing the DNS
// configuration for Google Cloud DNS
type ACMEIssuerDNS01ProviderCloudDNS struct {
ServiceAccount SecretKeySelector `json:"serviceAccount"`
Project string `json:"project"`
}

// ACMEIssuerDNS01ProviderCloudflare is a structure containing the DNS
// configuration for Cloudflare
type ACMEIssuerDNS01ProviderCloudflare struct {
Email string `json:"email"`
APIKey SecretKeySelector `json:"apiKey"`
}

// ACMEIssuerDNS01ProviderRoute53 is a structure containing the Route 53
// configuration for AWS
type ACMEIssuerDNS01ProviderRoute53 struct {
AccessKeyID string `json:"accessKeyID"`
SecretAccessKey SecretKeySelector `json:"secretAccessKey"`
HostedZoneID string `json:"hostedZoneID"`
Region string `json:"region"`
}

type ACMEIssuerStatus struct {
// URI is the unique account identifier, which can also be used to retrieve
// account details from the CA
Expand Down
30 changes: 30 additions & 0 deletions pkg/apis/certmanager/v1alpha1/zz_generated.deepcopy.go
Expand Up @@ -84,6 +84,10 @@ func RegisterDeepCopies(scheme *runtime.Scheme) error {
in.(*ACMEIssuerStatus).DeepCopyInto(out.(*ACMEIssuerStatus))
return nil
}, InType: reflect.TypeOf(&ACMEIssuerStatus{})},
conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error {
in.(*CAIssuer).DeepCopyInto(out.(*CAIssuer))
return nil
}, InType: reflect.TypeOf(&CAIssuer{})},
conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error {
in.(*Certificate).DeepCopyInto(out.(*Certificate))
return nil
Expand Down Expand Up @@ -416,6 +420,23 @@ func (in *ACMEIssuerStatus) DeepCopy() *ACMEIssuerStatus {
return out
}

// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CAIssuer) DeepCopyInto(out *CAIssuer) {
*out = *in
out.SecretRef = in.SecretRef
return
}

// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CAIssuer.
func (in *CAIssuer) DeepCopy() *CAIssuer {
if in == nil {
return nil
}
out := new(CAIssuer)
in.DeepCopyInto(out)
return out
}

// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Certificate) DeepCopyInto(out *Certificate) {
*out = *in
Expand Down Expand Up @@ -664,6 +685,15 @@ func (in *IssuerSpec) DeepCopyInto(out *IssuerSpec) {
(*in).DeepCopyInto(*out)
}
}
if in.CA != nil {
in, out := &in.CA, &out.CA
if *in == nil {
*out = nil
} else {
*out = new(CAIssuer)
**out = **in
}
}
return
}

Expand Down
40 changes: 33 additions & 7 deletions pkg/controller/certificates/sync.go
Expand Up @@ -3,6 +3,7 @@ package certificates
import (
"crypto/x509"
"fmt"
"reflect"
"time"

api "k8s.io/api/core/v1"
Expand Down Expand Up @@ -70,7 +71,7 @@ func (c *Controller) Sync(crt *v1alpha1.Certificate) (err error) {
return err
}

issuerReady := v1alpha1.IssuerHasCondition(issuerObj, v1alpha1.IssuerCondition{
issuerReady := issuerObj.HasCondition(v1alpha1.IssuerCondition{
Type: v1alpha1.IssuerConditionReady,
Status: v1alpha1.ConditionTrue,
})
Expand Down Expand Up @@ -191,7 +192,7 @@ func (c *Controller) prepare(issuer issuer.Interface, crt *v1alpha1.Certificate)

// 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(issuer issuer.Interface, crt *v1alpha1.Certificate) error {
func (c *Controller) issue(issuer issuer.Interface, crt *v1alpha1.Certificate) (err error) {
s := messagePreparingCertificate
glog.Info(s)
c.recorder.Event(crt, api.EventTypeNormal, reasonPreparingCertificate, s)
Expand All @@ -207,7 +208,18 @@ func (c *Controller) issue(issuer issuer.Interface, crt *v1alpha1.Certificate) e
glog.Info(s)
c.recorder.Event(crt, api.EventTypeNormal, reasonIssuingCertificate, s)

key, cert, err := issuer.Issue(crt)
status, key, cert, err := issuer.Issue(crt)

defer func() {
if saveErr := c.updateCertificateStatus(crt, status); saveErr != nil {
errs := []error{saveErr}
if err != nil {
errs = append(errs, err)
}
err = utilerrors.NewAggregate(errs)
}
}()

if err != nil {
s := messageErrorIssuingCertificate + err.Error()
glog.Info(s)
Expand Down Expand Up @@ -259,7 +271,18 @@ func (c *Controller) renew(issuer issuer.Interface, crt *v1alpha1.Certificate) e
glog.Info(s)
c.recorder.Event(crt, api.EventTypeNormal, reasonRenewingCertificate, s)

key, cert, err := issuer.Renew(crt)
status, key, cert, err := issuer.Renew(crt)

defer func() {
if saveErr := c.updateCertificateStatus(crt, status); saveErr != nil {
errs := []error{saveErr}
if err != nil {
errs = append(errs, err)
}
err = utilerrors.NewAggregate(errs)
}
}()

if err != nil {
s := messageErrorRenewingCertificate + err.Error()
glog.Info(s)
Expand Down Expand Up @@ -292,12 +315,15 @@ func (c *Controller) renew(issuer issuer.Interface, crt *v1alpha1.Certificate) e
return nil
}

func (c *Controller) updateCertificateStatus(iss *v1alpha1.Certificate, status v1alpha1.CertificateStatus) error {
updateCertificate := iss.DeepCopy()
func (c *Controller) updateCertificateStatus(crt *v1alpha1.Certificate, status v1alpha1.CertificateStatus) error {
updateCertificate := crt.DeepCopy()
updateCertificate.Status = status
if reflect.DeepEqual(crt.Status, updateCertificate.Status) {
return nil
}
// 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(iss.Namespace).Update(updateCertificate)
_, err := c.cmClient.CertmanagerV1alpha1().Certificates(crt.Namespace).Update(updateCertificate)
return err
}
5 changes: 5 additions & 0 deletions pkg/controller/issuers/sync.go
@@ -1,6 +1,8 @@
package issuers

import (
"reflect"

"github.com/golang/glog"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/errors"
Expand Down Expand Up @@ -47,6 +49,9 @@ func (c *Controller) Sync(iss *v1alpha1.Issuer) (err error) {
func (c *Controller) updateIssuerStatus(iss *v1alpha1.Issuer, status v1alpha1.IssuerStatus) error {
updateIssuer := iss.DeepCopy()
updateIssuer.Status = status
if reflect.DeepEqual(iss.Status, updateIssuer.Status) {
return nil
}
// 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)
Expand Down
24 changes: 22 additions & 2 deletions pkg/issuer/acme/issue.go
Expand Up @@ -17,6 +17,16 @@ import (
"github.com/jetstack-experimental/cert-manager/pkg/util/pki"
)

const (
errorIssueCert = "ErrIssueCert"

successCertIssued = "CertIssueSuccess"

messageErrorIssueCert = "Error issuing TLS certificate: "

messageCertIssued = "Certificate issued successfully"
)

func (a *Acme) obtainCertificate(crt *v1alpha1.Certificate) ([]byte, []byte, error) {
if crt.Spec.ACME == nil {
return nil, nil, fmt.Errorf("acme config must be specified")
Expand Down Expand Up @@ -77,6 +87,16 @@ func (a *Acme) obtainCertificate(crt *v1alpha1.Certificate) ([]byte, []byte, err
return pki.EncodePKCS1PrivateKey(key), certBuffer.Bytes(), nil
}

func (a *Acme) Issue(crt *v1alpha1.Certificate) ([]byte, []byte, error) {
return a.obtainCertificate(crt)
func (a *Acme) Issue(crt *v1alpha1.Certificate) (v1alpha1.CertificateStatus, []byte, []byte, error) {
update := crt.DeepCopy()
key, cert, err := a.obtainCertificate(crt)
if err != nil {
s := messageErrorIssueCert + err.Error()
update.UpdateStatusCondition(v1alpha1.CertificateConditionReady, v1alpha1.ConditionFalse, errorIssueCert, s)
return update.Status, nil, nil, err
}

update.UpdateStatusCondition(v1alpha1.CertificateConditionReady, v1alpha1.ConditionTrue, successCertIssued, messageCertIssued)

return update.Status, key, cert, err
}
26 changes: 23 additions & 3 deletions pkg/issuer/acme/renew.go
@@ -1,7 +1,27 @@
package acme

import "github.com/jetstack-experimental/cert-manager/pkg/apis/certmanager/v1alpha1"
import (
"github.com/jetstack-experimental/cert-manager/pkg/apis/certmanager/v1alpha1"
)

func (a *Acme) Renew(crt *v1alpha1.Certificate) ([]byte, []byte, error) {
return a.obtainCertificate(crt)
const (
errorRenewCert = "ErrRenewCert"
messageErrorRenewCert = "Error renewing TLS certificate: "

successCertRenewed = "CertRenewSuccess"
messageCertRenewed = "Certificate renewed successfully"
)

func (a *Acme) Renew(crt *v1alpha1.Certificate) (v1alpha1.CertificateStatus, []byte, []byte, error) {
update := crt.DeepCopy()
key, cert, err := a.obtainCertificate(crt)
if err != nil {
s := messageErrorIssueCert + err.Error()
update.UpdateStatusCondition(v1alpha1.CertificateConditionReady, v1alpha1.ConditionFalse, errorRenewCert, s)
return update.Status, nil, nil, err
}

update.UpdateStatusCondition(v1alpha1.CertificateConditionReady, v1alpha1.ConditionTrue, successCertRenewed, messageCertRenewed)

return update.Status, key, cert, err
}