diff --git a/pkg/issuer/acme/account.go b/pkg/issuer/acme/account.go index faffce7df..6b3549f7b 100644 --- a/pkg/issuer/acme/account.go +++ b/pkg/issuer/acme/account.go @@ -6,17 +6,14 @@ import ( "crypto/x509" "encoding/pem" "fmt" + "log" "strings" "golang.org/x/crypto/acme" api "k8s.io/api/core/v1" k8sErrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes" - corev1listers "k8s.io/client-go/listers/core/v1" - "github.com/jetstack-experimental/cert-manager/pkg/apis/certmanager/v1alpha1" - "github.com/jetstack-experimental/cert-manager/pkg/log" "github.com/jetstack-experimental/cert-manager/pkg/util" ) @@ -24,43 +21,11 @@ const ( acmeAccountPrivateKeyKey = "key.pem" ) -type account struct { - issuer *v1alpha1.Issuer - - client kubernetes.Interface - secretsLister corev1listers.SecretLister -} - -func newAccount(issuer *v1alpha1.Issuer, client kubernetes.Interface, secretsLister corev1listers.SecretLister) *account { - return &account{issuer, client, secretsLister} -} - -func (a *account) uri() string { - if a.issuer.Status.ACME == nil { - return "" - } - return a.issuer.Status.ACME.URI -} - -func (a *account) email() string { - if a.issuer.Spec.ACME == nil { - return "" - } - return a.issuer.Spec.ACME.Email -} - -func (a *account) server() string { - if a.issuer.Spec.ACME == nil { - return "" - } - return a.issuer.Spec.ACME.Server -} - // privateKey returns the private key for this account from the given context, // or an error // TODO (@munnerz): how can we support different types of private keys other // than rsa? -func (a *account) privateKey() (*rsa.PrivateKey, error) { +func (a *Acme) accountPrivateKey() (*rsa.PrivateKey, error) { if a.issuer.Spec.ACME == nil { return nil, fmt.Errorf("acme spec block cannot be empty") } @@ -99,7 +64,7 @@ func (a *account) privateKey() (*rsa.PrivateKey, error) { } // verify verifies an acme account is valid with the acme server -func (a *account) verify() error { +func (a *Acme) verifyAccount() error { if a.issuer.Spec.ACME.Server == "" { return fmt.Errorf("acme server url must be set") } @@ -107,10 +72,9 @@ func (a *account) verify() error { return fmt.Errorf("acme account uri must be set") } - privateKey, err := a.privateKey() + privateKey, err := a.accountPrivateKey() if err != nil { - a.issuer.Status.Ready = false return err } @@ -133,25 +97,25 @@ func (a *account) verify() error { } // register will register an account with the acme server and store the account -// details in the context +// details in the API server. It returns the registered account URI, or an error // TODO: break this function down -func (a *account) register() error { +func (a *Acme) registerAccount() (string, error) { if a.issuer.Spec.ACME.Server == "" { - return fmt.Errorf("acme server url must be set") + return "", fmt.Errorf("acme server url must be set") } - privateKey, err := a.privateKey() + privateKey, err := a.accountPrivateKey() var privateKeyPem []byte if err != nil { if !k8sErrors.IsNotFound(err) { - return fmt.Errorf("error getting private key: %s", err.Error()) + return "", fmt.Errorf("error getting private key: %s", err.Error()) } // TODO (@munnerz): allow changing the keysize privateKeyPem, privateKey, err = generatePrivateKey(2048) if err != nil { - return fmt.Errorf("error generating private key: %s", err.Error()) + return "", fmt.Errorf("error generating private key: %s", err.Error()) } _, err = util.EnsureSecret(a.client, &api.Secret{ @@ -165,7 +129,7 @@ func (a *account) register() error { }) if err != nil { - return fmt.Errorf("error saving private key: %s", err.Error()) + return "", fmt.Errorf("error saving private key: %s", err.Error()) } } @@ -186,19 +150,17 @@ func (a *account) register() error { var acmeErr *acme.Error var ok bool if acmeErr, ok = err.(*acme.Error); !ok || (acmeErr.StatusCode != 409) { - return fmt.Errorf("error registering acme account: %s", err.Error()) + return "", fmt.Errorf("error registering acme account: %s", err.Error()) } if a.issuer.Status.ACME == nil || a.issuer.Status.ACME.URI == "" { - return fmt.Errorf("private key already registered but user URI not found. delete existing private key or set acme account URI") + return "", fmt.Errorf("private key already registered but user URI not found. delete existing private key or set acme account URI") } if account, err = cl.UpdateReg(context.Background(), acc); err != nil { - return fmt.Errorf("error updating acme account registration: %s", err.Error()) + return "", fmt.Errorf("error updating acme account registration: %s", err.Error()) } } - a.issuer.Status.ACMEStatus().URI = account.URI - - return nil + return account.URI, nil } diff --git a/pkg/issuer/acme/acme.go b/pkg/issuer/acme/acme.go index b60da5bf2..05560b0db 100644 --- a/pkg/issuer/acme/acme.go +++ b/pkg/issuer/acme/acme.go @@ -3,25 +3,28 @@ package acme import ( "context" "fmt" + "time" - "k8s.io/client-go/informers" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + coreinformers "k8s.io/client-go/informers/core/v1" "k8s.io/client-go/kubernetes" + corelisters "k8s.io/client-go/listers/core/v1" + "k8s.io/client-go/tools/cache" "github.com/jetstack-experimental/cert-manager/pkg/apis/certmanager/v1alpha1" "github.com/jetstack-experimental/cert-manager/pkg/client" - cminformers "github.com/jetstack-experimental/cert-manager/pkg/informers" "github.com/jetstack-experimental/cert-manager/pkg/issuer" "github.com/jetstack-experimental/cert-manager/pkg/issuer/acme/dns" "github.com/jetstack-experimental/cert-manager/pkg/issuer/acme/http" ) type Acme struct { - account *account + issuer *v1alpha1.Issuer - client kubernetes.Interface - cmClient client.Interface - factory informers.SharedInformerFactory - cmFactory cminformers.SharedInformerFactory + client kubernetes.Interface + cmClient client.Interface + + secretsLister corelisters.SecretLister dnsSolver solver httpSolver solver @@ -30,16 +33,19 @@ type Acme struct { func New(issuer *v1alpha1.Issuer, client kubernetes.Interface, cmClient client.Interface, - factory informers.SharedInformerFactory, - cmFactory cminformers.SharedInformerFactory) (issuer.Interface, error) { + secretsInformer cache.SharedIndexInformer) (issuer.Interface, error) { + if issuer.Spec.ACME == nil { + return nil, fmt.Errorf("acme config may not be empty") + } + + secretsLister := corelisters.NewSecretLister(secretsInformer.GetIndexer()) return &Acme{ - account: newAccount(issuer, client, factory.Core().V1().Secrets().Lister()), - client: client, - cmClient: cmClient, - factory: factory, - cmFactory: cmFactory, - dnsSolver: dns.NewSolver(issuer, client, factory.Core().V1().Secrets().Lister()), - httpSolver: http.NewSolver(issuer, client, factory.Core().V1().Secrets().Lister()), + issuer: issuer, + client: client, + cmClient: cmClient, + secretsLister: secretsLister, + dnsSolver: dns.NewSolver(issuer, client, secretsLister), + httpSolver: http.NewSolver(issuer, client, secretsLister), }, nil } @@ -60,5 +66,15 @@ func (a *Acme) solverFor(challengeType string) (solver, error) { } func init() { - issuer.SharedFactory().Register(issuer.IssuerACME, New) + issuer.Register(issuer.IssuerACME, func(i *v1alpha1.Issuer, ctx *issuer.Context) (issuer.Interface, error) { + return New( + i, + ctx.Client, + ctx.CMClient, + ctx.SharedInformerFactory.InformerFor( + ctx.Namespace, + metav1.GroupVersionKind{Version: "v1", Kind: "Secret"}, + coreinformers.NewSecretInformer(ctx.Client, ctx.Namespace, time.Second*30, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc})), + ) + }) } diff --git a/pkg/issuer/acme/http/http.go b/pkg/issuer/acme/http/http.go index 4c1e660f9..ddc2b0f33 100644 --- a/pkg/issuer/acme/http/http.go +++ b/pkg/issuer/acme/http/http.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "io/ioutil" + "log" "net/http" "net/url" "strings" @@ -23,7 +24,6 @@ import ( "github.com/jetstack-experimental/cert-manager/pkg/apis/certmanager/v1alpha1" "github.com/jetstack-experimental/cert-manager/pkg/issuer/acme/http/solver" - "github.com/jetstack-experimental/cert-manager/pkg/log" "github.com/jetstack-experimental/cert-manager/pkg/util" ) diff --git a/pkg/issuer/acme/issue.go b/pkg/issuer/acme/issue.go index 0079f2f58..10f6bb93a 100644 --- a/pkg/issuer/acme/issue.go +++ b/pkg/issuer/acme/issue.go @@ -9,17 +9,17 @@ import ( "crypto/x509/pkix" "encoding/pem" "fmt" + "log" "golang.org/x/crypto/acme" api "k8s.io/api/core/v1" k8sErrors "k8s.io/apimachinery/pkg/api/errors" "github.com/jetstack-experimental/cert-manager/pkg/apis/certmanager/v1alpha1" - "github.com/jetstack-experimental/cert-manager/pkg/log" ) func (a *Acme) getCertificatePrivateKey(crt *v1alpha1.Certificate) ([]byte, crypto.Signer, error) { - crtSecret, err := a.factory.Core().V1().Secrets().Lister().Secrets(crt.Namespace).Get(crt.Spec.SecretName) + crtSecret, err := a.secretsLister.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()) @@ -53,7 +53,7 @@ func (a *Acme) obtainCertificate(crt *v1alpha1.Certificate) (privateKeyPem []byt return nil, nil, fmt.Errorf("no domains specified") } - privKey, err := a.account.privateKey() + privKey, err := a.accountPrivateKey() if err != nil { return nil, nil, fmt.Errorf("error getting acme account private key: %s", err.Error()) @@ -61,7 +61,7 @@ func (a *Acme) obtainCertificate(crt *v1alpha1.Certificate) (privateKeyPem []byt cl := &acme.Client{ Key: privKey, - DirectoryURL: a.account.server(), + DirectoryURL: a.issuer.Spec.ACME.Server, } template := x509.CertificateRequest{ diff --git a/pkg/issuer/acme/prepare.go b/pkg/issuer/acme/prepare.go index d5e03586c..3558463e9 100644 --- a/pkg/issuer/acme/prepare.go +++ b/pkg/issuer/acme/prepare.go @@ -3,12 +3,12 @@ package acme import ( "context" "fmt" + "log" "reflect" "golang.org/x/crypto/acme" "github.com/jetstack-experimental/cert-manager/pkg/apis/certmanager/v1alpha1" - "github.com/jetstack-experimental/cert-manager/pkg/log" "github.com/jetstack-experimental/cert-manager/pkg/util" ) @@ -71,8 +71,8 @@ func (a *Acme) prepare(crt *v1alpha1.Certificate) error { return fmt.Errorf("acme config must be specified") } - log.Printf("getting private key for acme issuer %s/%s", a.account.issuer.Namespace, a.account.issuer.Name) - privKey, err := a.account.privateKey() + log.Printf("getting private key for acme issuer %s/%s", a.issuer.Namespace, a.issuer.Name) + privKey, err := a.accountPrivateKey() if err != nil { return fmt.Errorf("error getting acme account private key: %s", err.Error()) @@ -80,7 +80,7 @@ func (a *Acme) prepare(crt *v1alpha1.Certificate) error { cl := &acme.Client{ Key: privKey, - DirectoryURL: a.account.server(), + DirectoryURL: a.issuer.Spec.ACME.Server, } // step one: check issuer to see if we already have authorizations diff --git a/pkg/issuer/acme/setup.go b/pkg/issuer/acme/setup.go index 5f5dbe1e9..b80708ce9 100644 --- a/pkg/issuer/acme/setup.go +++ b/pkg/issuer/acme/setup.go @@ -2,49 +2,28 @@ package acme import ( "fmt" - "reflect" + + "github.com/jetstack-experimental/cert-manager/pkg/apis/certmanager/v1alpha1" ) -func (a *Acme) Setup() (err error) { - issuerBefore := a.account.issuer.DeepCopy() +func (a *Acme) Setup() (v1alpha1.IssuerStatus, error) { + updateStatus := a.issuer.Status.DeepCopy() - defer func() { - if !reflect.DeepEqual(issuerBefore, a.account.issuer) { - if err == nil { - _, err = a.cmClient.CertmanagerV1alpha1().Issuers(a.account.issuer.Namespace).Update(a.account.issuer) - } - } - }() - - err = a.ensureSetup() - - if err != nil { - return err - } - - return nil -} - -// ensureSetup will ensure that this issuer is ready to issue certificates. -// it -func (a *Acme) ensureSetup() error { - err := a.account.verify() + err := a.verifyAccount() if err == nil { - a.account.issuer.Status.Ready = true - return nil + updateStatus.Ready = true + return *updateStatus, nil } - a.account.issuer.Status.Ready = false - - err = a.account.register() + uri, err := a.registerAccount() if err != nil { - // don't write updated state as an actual error occurred - return fmt.Errorf("error registering acme account: %s", err.Error()) + updateStatus.Ready = false + return *updateStatus, fmt.Errorf("error registering acme account: %s", err.Error()) } - a.account.issuer.Status.Ready = true + updateStatus.ACMEStatus().URI = uri - return nil + return *updateStatus, nil } diff --git a/pkg/issuer/context.go b/pkg/issuer/context.go new file mode 100644 index 000000000..beeebd8ff --- /dev/null +++ b/pkg/issuer/context.go @@ -0,0 +1,17 @@ +package issuer + +import ( + "k8s.io/client-go/kubernetes" + + "github.com/jetstack-experimental/cert-manager/pkg/client" + "github.com/jetstack-experimental/cert-manager/pkg/kube" +) + +type Context struct { + Client kubernetes.Interface + CMClient client.Interface + + SharedInformerFactory kube.SharedInformerFactory + + Namespace string +} diff --git a/pkg/issuer/factory.go b/pkg/issuer/factory.go index 8a81009c9..934d48395 100644 --- a/pkg/issuer/factory.go +++ b/pkg/issuer/factory.go @@ -2,47 +2,32 @@ package issuer import ( "fmt" - "sync" - - "k8s.io/client-go/informers" - "k8s.io/client-go/kubernetes" "github.com/jetstack-experimental/cert-manager/pkg/apis/certmanager/v1alpha1" - "github.com/jetstack-experimental/cert-manager/pkg/client" - cminformers "github.com/jetstack-experimental/cert-manager/pkg/informers" - "github.com/jetstack-experimental/cert-manager/pkg/log" ) -type Factory struct { - constructors map[string]Constructor - constructorsLock sync.RWMutex - - client kubernetes.Interface - cmClient client.Interface - factory informers.SharedInformerFactory - cmFactory cminformers.SharedInformerFactory +type Factory interface { + IssuerFor(*v1alpha1.Issuer) (Interface, error) } -func (f *Factory) Setup(client kubernetes.Interface, - cmClient client.Interface, - factory informers.SharedInformerFactory, - cmFactory cminformers.SharedInformerFactory) { - f.client = client - f.cmClient = cmClient - f.factory = factory - f.cmFactory = cmFactory +func NewFactory(ctx *Context) Factory { + return &factory{ctx: ctx} } -func (f *Factory) IssuerFor(issuer *v1alpha1.Issuer) (Interface, error) { +type factory struct { + ctx *Context +} + +func (f *factory) IssuerFor(issuer *v1alpha1.Issuer) (Interface, error) { issuerType, err := nameForIssuer(issuer) if err != nil { return nil, fmt.Errorf("could not get issuer type: %s", err.Error()) } - f.constructorsLock.RLock() - defer f.constructorsLock.RUnlock() - if constructor, ok := f.constructors[issuerType]; ok { - return constructor(issuer, f.client, f.cmClient, f.factory, f.cmFactory) + constructorsLock.RLock() + defer constructorsLock.RUnlock() + if constructor, ok := constructors[issuerType]; ok { + return constructor(issuer, f.ctx) } return nil, fmt.Errorf("issuer '%s' not registered", issuerType) } @@ -50,9 +35,8 @@ func (f *Factory) IssuerFor(issuer *v1alpha1.Issuer) (Interface, error) { // Register will register an issuer constructor so it can be used within the // application. 'name' should be unique, and should be used to identify this // issuer. -func (f *Factory) Register(name string, c Constructor) { - f.constructorsLock.Lock() - defer f.constructorsLock.Unlock() - log.Printf("registered issuer '%s'", name) - f.constructors[name] = c +func Register(name string, c Constructor) { + constructorsLock.Lock() + defer constructorsLock.Unlock() + constructors[name] = c } diff --git a/pkg/issuer/interface.go b/pkg/issuer/interface.go deleted file mode 100644 index 89af6da17..000000000 --- a/pkg/issuer/interface.go +++ /dev/null @@ -1,39 +0,0 @@ -package issuer - -import ( - "k8s.io/client-go/informers" - "k8s.io/client-go/kubernetes" - - "github.com/jetstack-experimental/cert-manager/pkg/apis/certmanager/v1alpha1" - "github.com/jetstack-experimental/cert-manager/pkg/client" - cminformers "github.com/jetstack-experimental/cert-manager/pkg/informers" -) - -var sharedFactory = &Factory{ - constructors: make(map[string]Constructor), -} - -func SharedFactory() *Factory { - return sharedFactory -} - -type Constructor func(issuer *v1alpha1.Issuer, - client kubernetes.Interface, - cmClient client.Interface, - factory informers.SharedInformerFactory, - cmFactory cminformers.SharedInformerFactory) (Interface, error) - -type Interface interface { - // Setup initialises the issuer. This may include registering accounts with - // a service, creating a CA and storing it somewhere, or verifying - // credentials and authorization with a remote server. - Setup() error - // Prepare - Prepare(*v1alpha1.Certificate) error - // Issue attempts to issue a certificate as described by the certificate - // resource given - Issue(*v1alpha1.Certificate) ([]byte, []byte, error) - // Renew attempts to renew the certificate describe by the certificate - // resource given. If no certificate exists, an error is returned. - Renew(*v1alpha1.Certificate) ([]byte, []byte, error) -} diff --git a/pkg/issuer/issuer.go b/pkg/issuer/issuer.go new file mode 100644 index 000000000..4660ef64c --- /dev/null +++ b/pkg/issuer/issuer.go @@ -0,0 +1,18 @@ +package issuer + +import "github.com/jetstack-experimental/cert-manager/pkg/apis/certmanager/v1alpha1" + +type Interface interface { + // Setup initialises the issuer. This may include registering accounts with + // a service, creating a CA and storing it somewhere, or verifying + // credentials and authorization with a remote server. + Setup() (v1alpha1.IssuerStatus, error) + // Prepare + Prepare(*v1alpha1.Certificate) error + // Issue attempts to issue a certificate as described by the certificate + // resource given + Issue(*v1alpha1.Certificate) ([]byte, []byte, error) + // Renew attempts to renew the certificate describe by the certificate + // resource given. If no certificate exists, an error is returned. + Renew(*v1alpha1.Certificate) ([]byte, []byte, error) +} diff --git a/pkg/issuer/register.go b/pkg/issuer/register.go new file mode 100644 index 000000000..134963d1e --- /dev/null +++ b/pkg/issuer/register.go @@ -0,0 +1,14 @@ +package issuer + +import ( + "sync" + + "github.com/jetstack-experimental/cert-manager/pkg/apis/certmanager/v1alpha1" +) + +type Constructor func(*v1alpha1.Issuer, *Context) (Interface, error) + +var ( + constructors = make(map[string]Constructor) + constructorsLock sync.RWMutex +)