Refactor common functions into util. Add renewals.
This commit is contained in:
parent
6ce234f7f6
commit
268ae4ee89
@ -17,6 +17,8 @@ limitations under the License.
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
@ -74,6 +76,15 @@ type ACMEIssuerDNS01Config struct {
|
||||
Providers []ACMEIssuerDNS01Provider `json:"providers"`
|
||||
}
|
||||
|
||||
func (a *ACMEIssuerDNS01Config) Provider(name string) (*ACMEIssuerDNS01Provider, error) {
|
||||
for _, p := range a.Providers {
|
||||
if p.Name == name {
|
||||
return &(*&p), nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("provider '%s' not found", name)
|
||||
}
|
||||
|
||||
type ACMEIssuerDNS01Provider struct {
|
||||
Name string `json:"name"`
|
||||
|
||||
@ -126,6 +137,17 @@ type ACMECertificateConfig struct {
|
||||
Config []ACMECertificateDomainConfig `json:"config"`
|
||||
}
|
||||
|
||||
func (a *ACMECertificateConfig) ConfigForDomain(domain string) ACMECertificateDomainConfig {
|
||||
for _, cfg := range a.Config {
|
||||
for _, d := range cfg.Domains {
|
||||
if d == domain {
|
||||
return cfg
|
||||
}
|
||||
}
|
||||
}
|
||||
return ACMECertificateDomainConfig{}
|
||||
}
|
||||
|
||||
type ACMECertificateDomainConfig struct {
|
||||
Domains []string `json:"domains"`
|
||||
HTTP01 *ACMECertificateHTTP01Config `json:"http-01"`
|
||||
|
||||
@ -4,7 +4,6 @@ import (
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
api "k8s.io/api/core/v1"
|
||||
k8sErrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
@ -13,6 +12,7 @@ import (
|
||||
"github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha1"
|
||||
"github.com/jetstack/cert-manager/pkg/controller"
|
||||
"github.com/jetstack/cert-manager/pkg/issuer"
|
||||
"github.com/jetstack/cert-manager/pkg/util"
|
||||
)
|
||||
|
||||
func sync(ctx *controller.Context, crt *v1alpha1.Certificate) error {
|
||||
@ -33,6 +33,7 @@ func sync(ctx *controller.Context, crt *v1alpha1.Certificate) error {
|
||||
return fmt.Errorf("error getting issuer implementation for issuer '%s': %s", issuerObj.Name, err.Error())
|
||||
}
|
||||
|
||||
// TODO: move this to after the certificate check to avoid unneeded authorization checks
|
||||
err = i.Prepare(crt)
|
||||
|
||||
if err != nil {
|
||||
@ -86,7 +87,7 @@ func sync(ctx *controller.Context, crt *v1alpha1.Certificate) error {
|
||||
return issue(ctx, i, crt)
|
||||
}
|
||||
// step two: check if referenced secret is valid for listed domains. if not, return failure
|
||||
if !equalUnsorted(crt.Spec.Domains, cert.DNSNames) {
|
||||
if !util.EqualUnsorted(crt.Spec.Domains, cert.DNSNames) {
|
||||
ctx.Logger.Printf("list of domains on certificate do not match domains in spec")
|
||||
return issue(ctx, i, crt)
|
||||
}
|
||||
@ -102,13 +103,12 @@ func sync(ctx *controller.Context, crt *v1alpha1.Certificate) error {
|
||||
// return an error on failure. If retrieval is succesful, the certificate data
|
||||
// and private key will be stored in the named secret
|
||||
func issue(ctx *controller.Context, issuer issuer.Interface, crt *v1alpha1.Certificate) error {
|
||||
cert, key, err := issuer.Issue(crt)
|
||||
key, cert, err := issuer.Issue(crt)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error issuing certificate: %s", err.Error())
|
||||
}
|
||||
|
||||
// TODO: support updating resources
|
||||
_, err = ctx.Client.Secrets(crt.Namespace).Create(&api.Secret{
|
||||
_, err = util.EnsureSecret(ctx.Client, &api.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: crt.Spec.SecretName,
|
||||
Namespace: crt.Namespace,
|
||||
@ -126,49 +126,29 @@ func issue(ctx *controller.Context, issuer issuer.Interface, crt *v1alpha1.Certi
|
||||
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(crt *v1alpha1.Certificate) error {
|
||||
// i, err := issuer.IssuerFor(crt)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
|
||||
// cert, key, err := i.Renew(&ctx, crt)
|
||||
// if err != nil {
|
||||
// return fmt.Errorf("error renewing certificate: %s", err.Error())
|
||||
// }
|
||||
|
||||
// _, err = ctx.Client.Secrets(crt.Namespace).Update(&api.Secret{
|
||||
// ObjectMeta: metav1.ObjectMeta{
|
||||
// Name: crt.Spec.SecretName,
|
||||
// Namespace: crt.Namespace,
|
||||
// },
|
||||
// Data: map[string][]byte{
|
||||
// api.TLSCertKey: cert,
|
||||
// api.TLSPrivateKeyKey: key,
|
||||
// },
|
||||
// })
|
||||
|
||||
// if err != nil {
|
||||
// return fmt.Errorf("error saving certificate: %s", err.Error())
|
||||
// }
|
||||
|
||||
// return nil
|
||||
// }
|
||||
|
||||
func equalUnsorted(s1 []string, s2 []string) bool {
|
||||
if len(s1) != len(s2) {
|
||||
return false
|
||||
// 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 renew(ctx *controller.Context, issuer issuer.Interface, crt *v1alpha1.Certificate) error {
|
||||
key, cert, err := issuer.Renew(crt)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error renewing certificate: %s", err.Error())
|
||||
}
|
||||
s1_2, s2_2 := make([]string, len(s1)), make([]string, len(s2))
|
||||
sort.Strings(s1)
|
||||
sort.Strings(s2)
|
||||
for i, s := range s1_2 {
|
||||
if s != s2_2[i] {
|
||||
return false
|
||||
}
|
||||
|
||||
_, err = util.EnsureSecret(ctx.Client, &api.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: crt.Spec.SecretName,
|
||||
Namespace: crt.Namespace,
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
api.TLSCertKey: cert,
|
||||
api.TLSPrivateKeyKey: key,
|
||||
},
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("error saving certificate: %s", err.Error())
|
||||
}
|
||||
return true
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -2,7 +2,6 @@ package acme
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
@ -178,14 +177,3 @@ func (a *account) register() error {
|
||||
a.issuer.Spec.ACME.URI = account.URI
|
||||
return nil
|
||||
}
|
||||
|
||||
func generatePrivateKey(keySize int) ([]byte, *rsa.PrivateKey, error) {
|
||||
privateKey, err := rsa.GenerateKey(rand.Reader, keySize)
|
||||
if err != nil {
|
||||
return []byte{}, nil, err
|
||||
}
|
||||
|
||||
block := &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(privateKey)}
|
||||
|
||||
return pem.EncodeToMemory(block), privateKey, nil
|
||||
}
|
||||
|
||||
@ -75,5 +75,5 @@ func (a *Acme) ensureSetup() error {
|
||||
}
|
||||
|
||||
func (a *Acme) Renew(crt *v1alpha1.Certificate) ([]byte, []byte, error) {
|
||||
return nil, nil, nil
|
||||
return a.obtainCertificate(crt)
|
||||
}
|
||||
|
||||
@ -10,11 +10,40 @@ import (
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/crypto/acme"
|
||||
api "k8s.io/api/core/v1"
|
||||
k8sErrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
|
||||
"crypto"
|
||||
|
||||
"github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha1"
|
||||
)
|
||||
|
||||
func (a *Acme) Issue(crt *v1alpha1.Certificate) ([]byte, []byte, error) {
|
||||
func (a *Acme) getCertificatePrivateKey(crt *v1alpha1.Certificate) ([]byte, crypto.Signer, error) {
|
||||
crtSecret, err := a.ctx.InformerFactory.Core().V1().Secrets().Lister().Secrets(crt.Namespace).Get(crt.Spec.SecretName)
|
||||
if err != nil {
|
||||
if !k8sErrors.IsNotFound(err) {
|
||||
return nil, nil, fmt.Errorf("error reading certificate private key for certificate '%s': %s", crt.Name, err.Error())
|
||||
}
|
||||
return generatePrivateKey(2048)
|
||||
}
|
||||
var keyBytes []byte
|
||||
var ok bool
|
||||
if keyBytes, ok = crtSecret.Data[api.TLSPrivateKeyKey]; !ok {
|
||||
return generatePrivateKey(2048)
|
||||
}
|
||||
block, _ := pem.Decode(keyBytes)
|
||||
der, err := x509.DecryptPEMBlock(block, nil)
|
||||
if err != nil {
|
||||
return generatePrivateKey(2048)
|
||||
}
|
||||
privKey, err := x509.ParsePKCS1PrivateKey(der)
|
||||
if err != nil {
|
||||
return generatePrivateKey(2048)
|
||||
}
|
||||
return keyBytes, privKey, nil
|
||||
}
|
||||
|
||||
func (a *Acme) obtainCertificate(crt *v1alpha1.Certificate) (privateKeyPem []byte, certPem []byte, err error) {
|
||||
if crt.Spec.ACME == nil {
|
||||
return nil, nil, fmt.Errorf("acme config must be specified")
|
||||
}
|
||||
@ -45,7 +74,7 @@ func (a *Acme) Issue(crt *v1alpha1.Certificate) ([]byte, []byte, error) {
|
||||
template.DNSNames = domains
|
||||
}
|
||||
|
||||
privateKeyPem, privateKey, err := generatePrivateKey(2048)
|
||||
privateKeyPem, privateKey, err := a.getCertificatePrivateKey(crt)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("error generating private key for certificiate '%s': %s", crt.Name, err)
|
||||
}
|
||||
@ -74,3 +103,7 @@ func (a *Acme) Issue(crt *v1alpha1.Certificate) ([]byte, []byte, error) {
|
||||
|
||||
return privateKeyPem, certBuffer.Bytes(), nil
|
||||
}
|
||||
|
||||
func (a *Acme) Issue(crt *v1alpha1.Certificate) ([]byte, []byte, error) {
|
||||
return a.obtainCertificate(crt)
|
||||
}
|
||||
|
||||
@ -174,7 +174,7 @@ func (a *Acme) prepare(crt *v1alpha1.Certificate) error {
|
||||
crt, err = a.ctx.CertManagerClient.Certificates(crt.Namespace).Update(crt)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("error updating certificate resource with authorization details")
|
||||
return fmt.Errorf("error updating certificate resource with authorization details: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
19
pkg/issuer/acme/util.go
Normal file
19
pkg/issuer/acme/util.go
Normal file
@ -0,0 +1,19 @@
|
||||
package acme
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
)
|
||||
|
||||
func generatePrivateKey(keySize int) ([]byte, *rsa.PrivateKey, error) {
|
||||
privateKey, err := rsa.GenerateKey(rand.Reader, keySize)
|
||||
if err != nil {
|
||||
return []byte{}, nil, err
|
||||
}
|
||||
|
||||
block := &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(privateKey)}
|
||||
|
||||
return pem.EncodeToMemory(block), privateKey, nil
|
||||
}
|
||||
30
pkg/util/kubernetes.go
Normal file
30
pkg/util/kubernetes.go
Normal file
@ -0,0 +1,30 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
api "k8s.io/api/core/v1"
|
||||
extensions "k8s.io/api/extensions/v1beta1"
|
||||
k8sErrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
)
|
||||
|
||||
func EnsureSecret(cl *kubernetes.Clientset, secret *api.Secret) (*api.Secret, error) {
|
||||
s, err := cl.CoreV1().Secrets(secret.Namespace).Create(secret)
|
||||
if err != nil {
|
||||
if k8sErrors.IsAlreadyExists(err) {
|
||||
return cl.CoreV1().Secrets(secret.Namespace).Update(secret)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func EnsureIngress(cl *kubernetes.Clientset, ingress *extensions.Ingress) (*extensions.Ingress, error) {
|
||||
s, err := cl.ExtensionsV1beta1().Ingresses(ingress.Namespace).Create(ingress)
|
||||
if err != nil {
|
||||
if k8sErrors.IsAlreadyExists(err) {
|
||||
return cl.ExtensionsV1beta1().Ingresses(ingress.Namespace).Update(ingress)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
@ -1,5 +1,7 @@
|
||||
package util
|
||||
|
||||
import "sort"
|
||||
|
||||
func OnlyOneNotNil(items ...interface{}) (any bool, one bool) {
|
||||
oneNotNil := false
|
||||
for _, i := range items {
|
||||
@ -12,3 +14,18 @@ func OnlyOneNotNil(items ...interface{}) (any bool, one bool) {
|
||||
}
|
||||
return oneNotNil, oneNotNil
|
||||
}
|
||||
|
||||
func EqualUnsorted(s1 []string, s2 []string) bool {
|
||||
if len(s1) != len(s2) {
|
||||
return false
|
||||
}
|
||||
s1_2, s2_2 := make([]string, len(s1)), make([]string, len(s2))
|
||||
sort.Strings(s1)
|
||||
sort.Strings(s2)
|
||||
for i, s := range s1_2 {
|
||||
if s != s2_2[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user