add start of authorization
This commit is contained in:
parent
ab18029189
commit
afbe0ba9c5
@ -29,6 +29,7 @@ import (
|
|||||||
_ "github.com/jetstack/cert-manager/pkg/apis/certmanager/install"
|
_ "github.com/jetstack/cert-manager/pkg/apis/certmanager/install"
|
||||||
"github.com/jetstack/cert-manager/pkg/client"
|
"github.com/jetstack/cert-manager/pkg/client"
|
||||||
"github.com/jetstack/cert-manager/pkg/controller"
|
"github.com/jetstack/cert-manager/pkg/controller"
|
||||||
|
"github.com/jetstack/cert-manager/pkg/controller/certificates"
|
||||||
"github.com/jetstack/cert-manager/pkg/controller/issuers"
|
"github.com/jetstack/cert-manager/pkg/controller/issuers"
|
||||||
"github.com/jetstack/cert-manager/pkg/informers/externalversions"
|
"github.com/jetstack/cert-manager/pkg/informers/externalversions"
|
||||||
logpkg "github.com/jetstack/cert-manager/pkg/log"
|
logpkg "github.com/jetstack/cert-manager/pkg/log"
|
||||||
@ -75,14 +76,14 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
issuerCtrl := issuers.New(ctx)
|
issuerCtrl := issuers.New(ctx)
|
||||||
// certificatesCtrl := certificates.New(ctx)
|
certificatesCtrl := certificates.New(ctx)
|
||||||
|
|
||||||
stopCh := make(chan struct{})
|
stopCh := make(chan struct{})
|
||||||
factory.Start(stopCh)
|
factory.Start(stopCh)
|
||||||
cmFactory.Start(stopCh)
|
cmFactory.Start(stopCh)
|
||||||
|
|
||||||
go issuerCtrl.Run(5, stopCh)
|
go issuerCtrl.Run(5, stopCh)
|
||||||
// go certificatesCtrl.Run(5, stopCh)
|
go certificatesCtrl.Run(5, stopCh)
|
||||||
|
|
||||||
<-stopCh
|
<-stopCh
|
||||||
}
|
}
|
||||||
|
|||||||
@ -38,7 +38,6 @@ func processNextWorkItem(ctx controller.Context, obj interface{}) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
case *api.Secret:
|
case *api.Secret:
|
||||||
ctx.Logger.Printf("unhandled change to Secret resource: %+v", v)
|
|
||||||
default:
|
default:
|
||||||
ctx.Logger.Errorf("unexpected resource type (%T) in work queue", obj)
|
ctx.Logger.Errorf("unexpected resource type (%T) in work queue", obj)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,17 +1,36 @@
|
|||||||
package certificates
|
package certificates
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha1"
|
"github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha1"
|
||||||
"github.com/jetstack/cert-manager/pkg/controller"
|
"github.com/jetstack/cert-manager/pkg/controller"
|
||||||
|
"github.com/jetstack/cert-manager/pkg/issuer"
|
||||||
)
|
)
|
||||||
|
|
||||||
func sync(ctx *controller.Context, crt *v1alpha1.Certificate) error {
|
func sync(ctx *controller.Context, crt *v1alpha1.Certificate) error {
|
||||||
// // step zero: check if the referenced issuer exists and is ready
|
// step zero: check if the referenced issuer exists and is ready
|
||||||
// issuer, err := ctx.CertManagerInformerFactory.Certmanager().V1alpha1().Issuers().Lister().Issuers(crt.Namespace).Get(crt.Spec.Issuer)
|
issuerObj, err := ctx.CertManagerInformerFactory.Certmanager().V1alpha1().Issuers().Lister().Issuers(crt.Namespace).Get(crt.Spec.Issuer)
|
||||||
|
|
||||||
// if err != nil {
|
if err != nil {
|
||||||
// return fmt.Errorf("issuer '%s' for certificate '%s' does not exist", crt.Spec.Issuer, crt.Name)
|
return fmt.Errorf("issuer '%s' for certificate '%s' does not exist", crt.Spec.Issuer, crt.Name)
|
||||||
// }
|
}
|
||||||
|
|
||||||
|
if !issuerObj.Status.Ready {
|
||||||
|
return fmt.Errorf("issuer '%s/%s' for certificate '%s' not ready", issuerObj.Namespace, issuerObj.Name, crt.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
i, err := issuer.IssuerFor(*ctx, issuerObj)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error getting issuer implementation for issuer '%s': %s", issuerObj.Name, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
err = i.Prepare(crt)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// // step one: check if referenced secret exists, if not, trigger issue event
|
// // step one: check if referenced secret exists, if not, trigger issue event
|
||||||
// secret, err := ctx.InformerFactory.Core().V1().Secrets().Lister().Secrets(crt.Namespace).Get(crt.Spec.SecretName)
|
// secret, err := ctx.InformerFactory.Core().V1().Secrets().Lister().Secrets(crt.Namespace).Get(crt.Spec.SecretName)
|
||||||
|
|||||||
@ -3,7 +3,6 @@ package acme
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha1"
|
"github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha1"
|
||||||
"github.com/jetstack/cert-manager/pkg/client/scheme"
|
"github.com/jetstack/cert-manager/pkg/client/scheme"
|
||||||
@ -75,25 +74,6 @@ func (a *Acme) ensureSetup() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Acme) Issue(crt *v1alpha1.Certificate) ([]byte, []byte, error) {
|
|
||||||
if crt.Spec.ACME == nil {
|
|
||||||
return nil, nil, fmt.Errorf("acme config must be specified")
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO (@munnerz): tidy this weird horrible line up
|
|
||||||
switch v1alpha1.ACMEChallengeType(strings.ToUpper(string(crt.Spec.ACME.Challenge))) {
|
|
||||||
case v1alpha1.ACMEChallengeTypeHTTP01:
|
|
||||||
a.ctx.Logger.Printf("Obtaining certificates for %+v", crt.Spec.Domains)
|
|
||||||
// todo: use acme library to obtain challenge details and pass them to the solver
|
|
||||||
case v1alpha1.ACMEChallengeTypeTLSSNI01:
|
|
||||||
case v1alpha1.ACMEChallengeTypeDNS01:
|
|
||||||
default:
|
|
||||||
return nil, nil, fmt.Errorf("invalid acme challenge type '%s'", crt.Spec.ACME.Challenge)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Acme) Renew(crt *v1alpha1.Certificate) ([]byte, []byte, error) {
|
func (a *Acme) Renew(crt *v1alpha1.Certificate) ([]byte, []byte, error) {
|
||||||
return nil, nil, nil
|
return nil, nil, nil
|
||||||
}
|
}
|
||||||
|
|||||||
17
pkg/issuer/acme/interface.go
Normal file
17
pkg/issuer/acme/interface.go
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package acme
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha1"
|
||||||
|
"github.com/jetstack/cert-manager/pkg/controller"
|
||||||
|
)
|
||||||
|
|
||||||
|
type solver interface {
|
||||||
|
Present(ctx controller.Context, crt *v1alpha1.Certificate, domain, token, key string) error
|
||||||
|
Cleanup(ctx controller.Context, crt *v1alpha1.Certificate, domain, token string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func solverFor(challengeType string) (solver, error) {
|
||||||
|
return nil, fmt.Errorf("no solver implemented")
|
||||||
|
}
|
||||||
11
pkg/issuer/acme/issue.go
Normal file
11
pkg/issuer/acme/issue.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package acme
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha1"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (a *Acme) Issue(crt *v1alpha1.Certificate) ([]byte, []byte, error) {
|
||||||
|
return nil, nil, fmt.Errorf("not implemented")
|
||||||
|
}
|
||||||
232
pkg/issuer/acme/prepare.go
Normal file
232
pkg/issuer/acme/prepare.go
Normal file
@ -0,0 +1,232 @@
|
|||||||
|
package acme
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/acme"
|
||||||
|
|
||||||
|
"github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha1"
|
||||||
|
"github.com/jetstack/cert-manager/pkg/log"
|
||||||
|
"github.com/jetstack/cert-manager/pkg/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
func authorizationsMap(list []v1alpha1.ACMEDomainAuthorization) map[string]v1alpha1.ACMEDomainAuthorization {
|
||||||
|
out := make(map[string]v1alpha1.ACMEDomainAuthorization, len(list))
|
||||||
|
for _, a := range list {
|
||||||
|
out[a.Domain] = a
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func authorizationsToObtain(cl *acme.Client, crt v1alpha1.Certificate) ([]string, error) {
|
||||||
|
authMap := authorizationsMap(crt.Status.ACME.Authorizations)
|
||||||
|
toAuthorize := util.StringFilter(func(domain string) (bool, error) {
|
||||||
|
auth, ok := authMap[domain]
|
||||||
|
if !ok {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return checkAuthorization(cl, auth.URI)
|
||||||
|
}, crt.Spec.Domains...)
|
||||||
|
|
||||||
|
domains := make([]string, len(toAuthorize))
|
||||||
|
for i, v := range toAuthorize {
|
||||||
|
if v.Err != nil {
|
||||||
|
return nil, fmt.Errorf("error checking authorization status for %s: %s", v.String, v.Err)
|
||||||
|
}
|
||||||
|
domains[i] = v.String
|
||||||
|
}
|
||||||
|
|
||||||
|
return domains, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare will ensure the issuer has been initialised and is ready to issue
|
||||||
|
// certificates for the domains listed on the Certificate resource.
|
||||||
|
//
|
||||||
|
// It will send the appropriate Letsencrypt authorizations, and complete
|
||||||
|
// challenge requests if neccessary.
|
||||||
|
func (a *Acme) Prepare(crt *v1alpha1.Certificate) error {
|
||||||
|
if crt.Spec.ACME == nil {
|
||||||
|
return fmt.Errorf("acme config must be specified")
|
||||||
|
}
|
||||||
|
|
||||||
|
privKey, err := a.account.privateKey()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error getting acme account private key: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
cl := &acme.Client{
|
||||||
|
Key: privKey,
|
||||||
|
DirectoryURL: a.account.server(),
|
||||||
|
}
|
||||||
|
|
||||||
|
// step one: check issuer to see if we already have authorizations
|
||||||
|
toAuthorize, err := authorizationsToObtain(cl, *crt)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
a.ctx.Logger.Printf("need to get authorizations for %v", toAuthorize)
|
||||||
|
|
||||||
|
// step two: if there are any domains that we don't have authorization for,
|
||||||
|
// we should attempt to authorize those domains
|
||||||
|
if len(toAuthorize) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
auths, err := getAuthorizations(cl, toAuthorize...)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
a.ctx.Logger.Printf("requested authorizations for %v", toAuthorize)
|
||||||
|
|
||||||
|
// todo: parallelize this
|
||||||
|
// todo: refactor into own function
|
||||||
|
for _, auth := range auths {
|
||||||
|
a.ctx.Logger.Printf("picking challenge type for domain '%s'", auth.domain)
|
||||||
|
challengeType, err := pickChallengeType(a.ctx.Logger, auth.domain, auth.auth, crt.Spec.ACME.Config)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error challenge type to use for domain '%s': %s", auth.domain, err.Error())
|
||||||
|
}
|
||||||
|
a.ctx.Logger.Printf("using challenge type %s for domain '%s'", challengeType, auth.domain)
|
||||||
|
challenge, err := challengeForAuthorization(cl, auth.auth, challengeType)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error getting challenge for domain '%s': %s", auth.domain, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
token := challenge.Token
|
||||||
|
var key string
|
||||||
|
switch challenge.Type {
|
||||||
|
case "http-01":
|
||||||
|
key, err = cl.HTTP01ChallengeResponse(challenge.Token)
|
||||||
|
case "dns-01":
|
||||||
|
key, err = cl.DNS01ChallengeRecord(challenge.Token)
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf("unsupported challenge type %s", challenge.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error getting key for acme challenge for domain '%s': %s", auth.domain, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
solver, err := solverFor(challengeType)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error getting solver for challenge type '%s': %s", challengeType, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
err = solver.Present(*a.ctx, crt, auth.domain, token, key)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error presenting acme authorization for domain '%s': %s", auth.domain, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
authorization, err := cl.WaitAuthorization(context.Background(), challenge.URI)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error waiting for authorization for domain '%s': %s", auth.domain, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if authorization.Status != acme.StatusValid {
|
||||||
|
return fmt.Errorf("expected acme domain authorization status for '%s' to be valid, but it's %s", auth.domain, authorization.Status)
|
||||||
|
}
|
||||||
|
|
||||||
|
crt.Status.ACME.SaveAuthorization(v1alpha1.ACMEDomainAuthorization{
|
||||||
|
Domain: auth.domain,
|
||||||
|
URI: authorization.URI,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkAuthorization(cl *acme.Client, uri string) (bool, error) {
|
||||||
|
a, err := cl.GetAuthorization(context.Background(), uri)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if a.Status == acme.StatusValid {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type authResponses []authResponse
|
||||||
|
type authResponse struct {
|
||||||
|
domain string
|
||||||
|
auth *acme.Authorization
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error returns an error if any one of the authResponses contains an error
|
||||||
|
func (a authResponses) Error() error {
|
||||||
|
var errs []error
|
||||||
|
for _, r := range a {
|
||||||
|
if r.err != nil {
|
||||||
|
errs = append(errs, fmt.Errorf("'%s': %s", r.domain, r.err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(errs) > 0 {
|
||||||
|
return fmt.Errorf("error getting authorization for domains: %v", errs)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAuthorizations(cl *acme.Client, domains ...string) ([]authResponse, error) {
|
||||||
|
respCh := make(chan authResponse)
|
||||||
|
defer close(respCh)
|
||||||
|
for _, d := range domains {
|
||||||
|
go func(domain string) {
|
||||||
|
auth, err := cl.Authorize(context.Background(), domain)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
respCh <- authResponse{"", nil, fmt.Errorf("getting acme authorization failed: %s", err.Error())}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
respCh <- authResponse{domain, auth, nil}
|
||||||
|
}(d)
|
||||||
|
}
|
||||||
|
|
||||||
|
responses := make([]authResponse, len(domains))
|
||||||
|
for i := 0; i < len(domains); i++ {
|
||||||
|
responses[i] = <-respCh
|
||||||
|
}
|
||||||
|
return responses, authResponses(responses).Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func pickChallengeType(log log.Logger, domain string, auth *acme.Authorization, cfg []v1alpha1.ACMECertificateDomainConfig) (string, error) {
|
||||||
|
for _, d := range cfg {
|
||||||
|
log.Printf("checking config %v", d)
|
||||||
|
for _, dom := range d.Domains {
|
||||||
|
log.Printf("checking domain %s", dom)
|
||||||
|
if dom == domain {
|
||||||
|
for _, challenge := range auth.Challenges {
|
||||||
|
switch {
|
||||||
|
case challenge.Type == "http-01" && d.HTTP01 != nil:
|
||||||
|
return challenge.Type, nil
|
||||||
|
case challenge.Type == "dns-01" && d.DNS01 != nil:
|
||||||
|
return challenge.Type, nil
|
||||||
|
}
|
||||||
|
log.Printf("cannot use %s challenge for domain %s", challenge.Type, domain)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("no configured and supported challenge type found")
|
||||||
|
}
|
||||||
|
|
||||||
|
func challengeForAuthorization(cl *acme.Client, auth *acme.Authorization, challengeType string) (*acme.Challenge, error) {
|
||||||
|
for _, challenge := range auth.Challenges {
|
||||||
|
if challenge.Type != challengeType {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return challenge, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("no supported challenges offered")
|
||||||
|
}
|
||||||
@ -13,6 +13,8 @@ type Interface interface {
|
|||||||
// a service, creating a CA and storing it somewhere, or verifying
|
// a service, creating a CA and storing it somewhere, or verifying
|
||||||
// credentials and authorization with a remote server.
|
// credentials and authorization with a remote server.
|
||||||
Setup() error
|
Setup() error
|
||||||
|
// Prepare
|
||||||
|
Prepare(*v1alpha1.Certificate) error
|
||||||
// Issue attempts to issue a certificate as described by the certificate
|
// Issue attempts to issue a certificate as described by the certificate
|
||||||
// resource given
|
// resource given
|
||||||
Issue(*v1alpha1.Certificate) ([]byte, []byte, error)
|
Issue(*v1alpha1.Certificate) ([]byte, []byte, error)
|
||||||
|
|||||||
57
pkg/util/filter.go
Normal file
57
pkg/util/filter.go
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// StringFilterT is a tuple for a value that has not been filtered out
|
||||||
|
type StringFilterT struct {
|
||||||
|
String string
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
type StringFilterWrapper []StringFilterT
|
||||||
|
|
||||||
|
func (f StringFilterWrapper) Error() error {
|
||||||
|
var errs []error
|
||||||
|
for _, r := range f {
|
||||||
|
if r.Err != nil {
|
||||||
|
errs = append(errs, fmt.Errorf("'%s': %s", r.String, r.Err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(errs) > 0 {
|
||||||
|
return fmt.Errorf("%v", errs)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FilterFn is a function used to filter a list of string. If the
|
||||||
|
// function returns false or a non-nil error, it will not be filtered.
|
||||||
|
type FilterFn func(string) (filter bool, err error)
|
||||||
|
|
||||||
|
// StringFilter will run fn with each element of in, filtering out elements.
|
||||||
|
// it will return a slice of results where fn returned ok, or a non-nil error.
|
||||||
|
// it will also call each instance of fn in it's own goroutine.
|
||||||
|
func StringFilter(fn FilterFn, in ...string) StringFilterWrapper {
|
||||||
|
outCh := make(chan StringFilterT, len(in))
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
for i, s := range in {
|
||||||
|
wg.Add(1)
|
||||||
|
go func(i int, s string) {
|
||||||
|
defer wg.Done()
|
||||||
|
if filter, err := fn(s); err != nil || !filter {
|
||||||
|
outCh <- StringFilterT{s, err}
|
||||||
|
}
|
||||||
|
}(i, s)
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
close(outCh)
|
||||||
|
res := make([]StringFilterT, len(outCh))
|
||||||
|
i := 0
|
||||||
|
for o := range outCh {
|
||||||
|
res[i] = o
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user