Rewrite ACME issuer to use new ACMEOrderChallenge struct
This commit is contained in:
parent
d3706ae33c
commit
4b361348ef
@ -141,7 +141,7 @@ func (crt *Certificate) HasCondition(condition CertificateCondition) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (crt *Certificate) UpdateStatusCondition(conditionType CertificateConditionType, status ConditionStatus, reason, message string) {
|
||||
func (crt *Certificate) UpdateStatusCondition(conditionType CertificateConditionType, status ConditionStatus, reason, message string, forceTime bool) {
|
||||
newCondition := CertificateCondition{
|
||||
Type: conditionType,
|
||||
Status: status,
|
||||
@ -158,7 +158,7 @@ func (crt *Certificate) UpdateStatusCondition(conditionType CertificateCondition
|
||||
} else {
|
||||
for i, cond := range crt.Status.Conditions {
|
||||
if cond.Type == conditionType {
|
||||
if cond.Status != newCondition.Status {
|
||||
if cond.Status != newCondition.Status || forceTime {
|
||||
glog.Infof("Found status change for Certificate %q condition %q: %q -> %q; setting lastTransitionTime to %v", crt.Name, conditionType, cond.Status, status, t)
|
||||
newCondition.LastTransitionTime = metav1.NewTime(t)
|
||||
} else {
|
||||
@ -166,9 +166,11 @@ func (crt *Certificate) UpdateStatusCondition(conditionType CertificateCondition
|
||||
}
|
||||
|
||||
crt.Status.Conditions[i] = newCondition
|
||||
break
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
crt.Status.Conditions = append(crt.Status.Conditions, newCondition)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -66,9 +66,12 @@ type Acme struct {
|
||||
// solver solves ACME challenges by presenting the given token and key in an
|
||||
// appropriate way given the config in the Issuer and Certificate.
|
||||
type solver interface {
|
||||
Present(ctx context.Context, crt *v1alpha1.Certificate, domain, token, key string) error
|
||||
Check(domain, token, key string) (bool, error)
|
||||
CleanUp(ctx context.Context, crt *v1alpha1.Certificate, domain, token, key string) error
|
||||
// we pass the certificate to the Present function so that if the solver
|
||||
// needs to create any new resources, it can set the appropriate owner
|
||||
// reference
|
||||
Present(ctx context.Context, crt *v1alpha1.Certificate, ch v1alpha1.ACMEOrderChallenge) error
|
||||
Check(ch v1alpha1.ACMEOrderChallenge) (bool, error)
|
||||
CleanUp(ctx context.Context, crt *v1alpha1.Certificate, ch v1alpha1.ACMEOrderChallenge) error
|
||||
}
|
||||
|
||||
// New returns a new ACME issuer interface for the given issuer.
|
||||
@ -195,7 +198,7 @@ func (a *Acme) solverFor(challengeType string) (solver, error) {
|
||||
case "dns-01":
|
||||
return a.dnsSolver, nil
|
||||
}
|
||||
return nil, fmt.Errorf("no solver implemented")
|
||||
return nil, fmt.Errorf("no solver for %q implemented", challengeType)
|
||||
}
|
||||
|
||||
// Register this Issuer with the issuer factory
|
||||
|
||||
@ -85,11 +85,11 @@ func (a *Acme) Issue(ctx context.Context, crt *v1alpha1.Certificate) ([]byte, []
|
||||
key, cert, err := a.obtainCertificate(ctx, crt)
|
||||
if err != nil {
|
||||
s := messageErrorIssueCert + err.Error()
|
||||
crt.UpdateStatusCondition(v1alpha1.CertificateConditionReady, v1alpha1.ConditionFalse, errorIssueCert, s)
|
||||
crt.UpdateStatusCondition(v1alpha1.CertificateConditionReady, v1alpha1.ConditionFalse, errorIssueCert, s, false)
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
crt.UpdateStatusCondition(v1alpha1.CertificateConditionReady, v1alpha1.ConditionTrue, successCertIssued, messageCertIssued)
|
||||
crt.UpdateStatusCondition(v1alpha1.CertificateConditionReady, v1alpha1.ConditionTrue, successCertIssued, messageCertIssued, false)
|
||||
|
||||
return key, cert, err
|
||||
}
|
||||
|
||||
@ -2,13 +2,15 @@ package acme
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha1"
|
||||
"github.com/jetstack/cert-manager/pkg/issuer/acme/client"
|
||||
"github.com/jetstack/cert-manager/third_party/crypto/acme"
|
||||
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -27,6 +29,10 @@ const (
|
||||
messageErrorCheckAuthorization = "Error checking ACME domain validation: "
|
||||
messageErrorObtainAuthorization = "Error obtaining ACME domain authorization: "
|
||||
messageErrorMissingConfig = "certificate.spec.acme must be specified"
|
||||
|
||||
// the amount of time to wait before attempting to create a new order after
|
||||
// an order has failed.s
|
||||
prepareAttemptWaitPeriod = time.Minute * 5
|
||||
)
|
||||
|
||||
// Prepare will ensure the issuer has been initialised and is ready to issue
|
||||
@ -36,7 +42,7 @@ const (
|
||||
// challenge requests if neccessary.
|
||||
func (a *Acme) Prepare(ctx context.Context, crt *v1alpha1.Certificate) error {
|
||||
if crt.Spec.ACME == nil {
|
||||
crt.UpdateStatusCondition(v1alpha1.CertificateConditionReady, v1alpha1.ConditionFalse, errorInvalidConfig, messageErrorMissingConfig)
|
||||
crt.UpdateStatusCondition(v1alpha1.CertificateConditionReady, v1alpha1.ConditionFalse, errorInvalidConfig, messageErrorMissingConfig, false)
|
||||
return fmt.Errorf(messageErrorMissingConfig)
|
||||
}
|
||||
|
||||
@ -47,231 +53,250 @@ func (a *Acme) Prepare(ctx context.Context, crt *v1alpha1.Certificate) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: clean up old authorization attempts.
|
||||
// This is complex because the Certificate resource may have been updated
|
||||
// to no longer include information required to clean up the challenge
|
||||
// (for example, if a domain is removed from a certificate while an authz
|
||||
// is in progress). This means we need to store a list of challenges we are
|
||||
// currently presenting on the certificates status field, which contains
|
||||
// enough information to clean up the resource:
|
||||
//
|
||||
// http01: domain and the fact it is http01
|
||||
// dns01: domain, token, key and dns01 provider name
|
||||
|
||||
order, err := a.getOrCreateOrder(ctx, cl, crt)
|
||||
// Determine how long until we should attempt validation again.
|
||||
// We perform this near the start of the function to reduce calls to the
|
||||
// acme server.
|
||||
nextPresentIn, order, err := a.shouldAttemptValidation(ctx, cl, crt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If the order here is nil, the last order must have failed or there was
|
||||
// not one previously. Either way, we should clean up the ACME status block
|
||||
if order == nil {
|
||||
err := a.cleanupLastOrder(ctx, crt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// if we should not attempt validation yet, return an error so the item
|
||||
// will be requeued.
|
||||
if nextPresentIn > 0 {
|
||||
nextPresentTimeStr := time.Now().Add(nextPresentIn).Format(time.RFC822Z)
|
||||
return fmt.Errorf("not attempting acme validation until %s", nextPresentTimeStr)
|
||||
}
|
||||
|
||||
// if the current order is nil and it is time to attempt validation, we
|
||||
// need to create a new order.
|
||||
if order == nil {
|
||||
order, err = a.createOrder(ctx, cl, crt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// attempt to present/validate the order
|
||||
return a.presentOrder(ctx, cl, crt, order)
|
||||
}
|
||||
|
||||
func (a *Acme) presentOrder(ctx context.Context, cl client.Interface, crt *v1alpha1.Certificate, order *acme.Order) error {
|
||||
allAuthorizations, err := getAuthorizations(ctx, cl, order.Authorizations...)
|
||||
if err != nil {
|
||||
s := messageErrorCheckAuthorization + err.Error()
|
||||
crt.UpdateStatusCondition(v1alpha1.CertificateConditionReady, v1alpha1.ConditionFalse, errorCheckAuthorization, s)
|
||||
return errors.New(s)
|
||||
// TODO: update status condition?
|
||||
// TODO: log an event?
|
||||
return err
|
||||
}
|
||||
|
||||
failed, pending, valid := partitionAuthorizations(allAuthorizations...)
|
||||
glog.Infof("Authorizations for Certificate %q: %d failed, %d pending, %d valid", crt.Name, len(failed), len(pending), len(valid))
|
||||
toCleanup := append(failed, valid...)
|
||||
for _, auth := range toCleanup {
|
||||
err := a.cleanupAuthorization(ctx, cl, crt, auth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// this may return challenges even if an error occured. we use the partial
|
||||
// list of challenges in order to cleanup challenges that are no longer
|
||||
// required.
|
||||
chs, err := a.selectChallengesForAuthorizations(ctx, cl, crt, allAuthorizations...)
|
||||
errCleanup := a.cleanupIrrelevantChallenges(ctx, crt, chs)
|
||||
if errCleanup != nil {
|
||||
// TODO: update status condition?
|
||||
// TODO: log an event?
|
||||
// perhaps we should just throw a warning here instead of erroring.
|
||||
// for now, return an error to pick up bugs in this codepath
|
||||
return err
|
||||
}
|
||||
if err != nil {
|
||||
// TODO: update status condition?
|
||||
// TODO: log an event?
|
||||
return err
|
||||
}
|
||||
|
||||
if len(failed) > 0 {
|
||||
glog.Infof("Found %d failed authorizations. Cleaning up pending authorizations and clearing order URL")
|
||||
// clear the order url to trigger a new order to be created
|
||||
crt.Status.ACMEStatus().Order.URL = ""
|
||||
// clean up pending authorizations
|
||||
for _, auth := range pending {
|
||||
err := a.cleanupAuthorization(ctx, cl, crt, auth)
|
||||
if err != nil {
|
||||
// TODO: clean up remaining authorizations if one fails
|
||||
return err
|
||||
}
|
||||
}
|
||||
// TODO: pretty-print the list of failed authorizations
|
||||
s := fmt.Sprintf("Error obtaining validations for domains %v", failed)
|
||||
crt.UpdateStatusCondition(v1alpha1.CertificateConditionReady, v1alpha1.ConditionFalse, errorCheckAuthorization, s)
|
||||
return errors.New(s)
|
||||
// set the challenges field of the status block
|
||||
crt.Status.ACMEStatus().Order.Challenges = chs
|
||||
|
||||
var errs []error
|
||||
// TODO: run this in parallel
|
||||
for _, ch := range chs {
|
||||
errs = append(errs, a.presentChallenge(ctx, cl, crt, ch))
|
||||
}
|
||||
// we aggregate the errors here before beginning to accept challenges.
|
||||
// This will mean we only accept challenges once all self checks are
|
||||
// passing, to save the number of 'accept' operations sent to the acme server.
|
||||
err = utilerrors.NewAggregate(errs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// all validations have been obtained
|
||||
if len(pending) == 0 {
|
||||
glog.Infof("No more pending authorizations remaining - challenge verification complete")
|
||||
// reset errs
|
||||
errs = make([]error, 0)
|
||||
for _, ch := range chs {
|
||||
errs = append(errs, a.acceptChallenge(ctx, cl, ch))
|
||||
}
|
||||
|
||||
return utilerrors.NewAggregate(errs)
|
||||
}
|
||||
|
||||
// presentChallenge will process a challenge by talking to the acme server and
|
||||
// obtaining up to date status information.
|
||||
// If the challenge is still in a pending state, it will 'present' the
|
||||
// challenge using the appropriate solver.
|
||||
// It will then run the 'Check' function in order to determine whether the
|
||||
// challenge has propegated (e.g. to DNS resolvers, or to the ingress
|
||||
// controller).
|
||||
// If the check fails, an error will be returned.
|
||||
// Otherwise, it will return nil.
|
||||
func (a *Acme) presentChallenge(ctx context.Context, cl client.Interface, crt *v1alpha1.Certificate, ch v1alpha1.ACMEOrderChallenge) error {
|
||||
acmeCh, err := cl.GetChallenge(ctx, ch.URL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch acmeCh.Status {
|
||||
case acme.StatusValid:
|
||||
return nil
|
||||
case acme.StatusInvalid, acme.StatusDeactivated, acme.StatusRevoked:
|
||||
acmeErrReason := "unknown reason"
|
||||
if acmeCh.Error != nil {
|
||||
acmeErrReason = acmeCh.Error.Error()
|
||||
}
|
||||
return fmt.Errorf("challenge for domain %q failed: %s", ch.Domain, acmeErrReason)
|
||||
case acme.StatusPending, acme.StatusProcessing:
|
||||
default:
|
||||
return fmt.Errorf("unknown acme challenge status %q", acmeCh.Status)
|
||||
}
|
||||
|
||||
var failingSelfChecks []string
|
||||
for _, auth := range pending {
|
||||
selfCheckPassed, challenge, err := a.presentAuthorization(ctx, cl, crt, auth)
|
||||
solver, err := a.solverFor(ch.Type)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = solver.Present(ctx, crt, ch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ok, err := solver.Check(ch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !ok {
|
||||
return fmt.Errorf("Self check (%s) failed for domain %q", ch.Type, ch.Domain)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Acme) cleanupLastOrder(ctx context.Context, crt *v1alpha1.Certificate) error {
|
||||
err := a.cleanupIrrelevantChallenges(ctx, crt, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
crt.Status.ACMEStatus().Order.Challenges = nil
|
||||
crt.Status.ACMEStatus().Order.URL = ""
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO: ensure all DNS challenge solvers return non-error if the challenge
|
||||
// record doesn't exist
|
||||
func (a *Acme) cleanupIrrelevantChallenges(ctx context.Context, crt *v1alpha1.Certificate, keepChals []v1alpha1.ACMEOrderChallenge) error {
|
||||
var toCleanUp []v1alpha1.ACMEOrderChallenge
|
||||
for _, c := range crt.Status.ACMEStatus().Order.Challenges {
|
||||
keep := false
|
||||
for _, kc := range keepChals {
|
||||
if reflect.DeepEqual(kc, c) {
|
||||
keep = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !keep {
|
||||
toCleanUp = append(toCleanUp, c)
|
||||
}
|
||||
}
|
||||
for _, c := range toCleanUp {
|
||||
solver, err := a.solverFor(c.Type)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if selfCheckPassed {
|
||||
glog.Infof("Self check passed for domain %q", auth.Identifier.Value)
|
||||
err := a.acceptChallenge(ctx, cl, auth, challenge)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
glog.Infof("Self check failed for domain %q", auth.Identifier.Value)
|
||||
failingSelfChecks = append(failingSelfChecks, auth.Identifier.Value)
|
||||
|
||||
err = solver.CleanUp(ctx, crt, c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if len(failingSelfChecks) > 0 {
|
||||
return fmt.Errorf("self check failed for domains: %v", failingSelfChecks)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// getOrCreateOrder will attempt to retrieve an existing order for a
|
||||
// certificate using the status.acme.order.url field.
|
||||
//
|
||||
// - if it's not set, it will call createOrder and return
|
||||
//
|
||||
// - if it is set, and the order is not in an error state, it will be returned
|
||||
//
|
||||
// - if it is set, and the order is in an invalid state, an event will be
|
||||
// logged and createOrder will be called
|
||||
//
|
||||
// - if an error occurs obtaining the order, it will be returned
|
||||
func (a *Acme) getOrCreateOrder(ctx context.Context, cl client.Interface, crt *v1alpha1.Certificate) (*acme.Order, error) {
|
||||
orderURL := crt.Status.ACMEStatus().Order.URL
|
||||
glog.Infof("Checking existing order URL %q", orderURL)
|
||||
func (a *Acme) selectChallengesForAuthorizations(ctx context.Context, cl client.Interface, crt *v1alpha1.Certificate, allAuthorizations ...*acme.Authorization) ([]v1alpha1.ACMEOrderChallenge, error) {
|
||||
chals := make([]v1alpha1.ACMEOrderChallenge, len(allAuthorizations))
|
||||
var errs []error
|
||||
for i, authz := range allAuthorizations {
|
||||
domain := authz.Identifier.Value
|
||||
cfg, err := acmeSolverConfiguration(crt.Spec.ACME, domain)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
continue
|
||||
}
|
||||
|
||||
var challenge *acme.Challenge
|
||||
for _, ch := range authz.Challenges {
|
||||
switch {
|
||||
case ch.Type == "http-01" && cfg.HTTP01 != nil:
|
||||
challenge = ch
|
||||
case ch.Type == "dns-01" && cfg.DNS01 != nil:
|
||||
challenge = ch
|
||||
}
|
||||
}
|
||||
|
||||
if challenge == nil {
|
||||
errs = append(errs, fmt.Errorf("ACME server does not allow selected challenge type for domain %q", domain))
|
||||
continue
|
||||
}
|
||||
|
||||
internalCh, err := buildInternalChallengeType(cl, challenge, *cfg, domain, authz.URL)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
continue
|
||||
}
|
||||
|
||||
chals[i] = internalCh
|
||||
}
|
||||
return chals, utilerrors.NewAggregate(errs)
|
||||
}
|
||||
|
||||
func buildInternalChallengeType(cl client.Interface, ch *acme.Challenge, cfg v1alpha1.ACMESolverConfig, domain, authzURL string) (v1alpha1.ACMEOrderChallenge, error) {
|
||||
var key string
|
||||
var err error
|
||||
var order *acme.Order
|
||||
// if the existing order URL is blank, create a new order
|
||||
if orderURL == "" {
|
||||
glog.Infof("Existing order URL not set. Creating new order.")
|
||||
return a.createOrder(ctx, cl, crt)
|
||||
switch ch.Type {
|
||||
case "http-01":
|
||||
key, err = cl.HTTP01ChallengeResponse(ch.Token)
|
||||
case "dns-01":
|
||||
key, err = cl.DNS01ChallengeRecord(ch.Token)
|
||||
default:
|
||||
return v1alpha1.ACMEOrderChallenge{}, fmt.Errorf("unsupported challenge type %q", ch.Type)
|
||||
}
|
||||
|
||||
glog.Infof("Requesting order details for %q from ACME server", crt.Name)
|
||||
order, err = cl.GetOrder(ctx, orderURL)
|
||||
|
||||
if err != nil {
|
||||
glog.Infof("Error requesting existing order details for %q from ACME server: %v", crt.Name, err)
|
||||
return nil, err
|
||||
return v1alpha1.ACMEOrderChallenge{}, err
|
||||
}
|
||||
|
||||
if !orderIsValidForCertificate(order, crt) {
|
||||
glog.Infof("Existing order is not valid for requested DNS names. Creating new order.")
|
||||
return a.createOrder(ctx, cl, crt)
|
||||
}
|
||||
|
||||
glog.Infof("Order %q status is %q", order.URL, order.Status)
|
||||
switch order.Status {
|
||||
// create a new order if the old one is invalid
|
||||
case acme.StatusDeactivated, acme.StatusInvalid, acme.StatusRevoked:
|
||||
// TODO: log an event
|
||||
glog.Infof("Existing order is in state %q - creating a new order.", order.Status)
|
||||
return a.createOrder(ctx, cl, crt)
|
||||
case acme.StatusValid, acme.StatusPending, acme.StatusProcessing:
|
||||
return order, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("order %q unknown status: %q", order.URL, order.Status)
|
||||
return v1alpha1.ACMEOrderChallenge{
|
||||
URL: ch.URL,
|
||||
AuthzURL: authzURL,
|
||||
Type: ch.Type,
|
||||
Domain: domain,
|
||||
Token: ch.Token,
|
||||
Key: key,
|
||||
Config: cfg,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (a *Acme) acceptChallenge(ctx context.Context, cl client.Interface, auth *acme.Authorization, challenge *acme.Challenge) error {
|
||||
glog.Infof("Accepting challenge for domain %q", auth.Identifier.Value)
|
||||
var err error
|
||||
challenge, err = cl.AcceptChallenge(ctx, challenge)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
glog.Infof("Waiting for authorization for domain %q", auth.Identifier.Value)
|
||||
authorization, err := cl.WaitAuthorization(ctx, auth.URL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if authorization.Status != acme.StatusValid {
|
||||
return fmt.Errorf("expected acme domain authorization status for %q to be valid, but it is %q", authorization.Identifier.Value, authorization.Status)
|
||||
}
|
||||
|
||||
glog.Infof("Successfully authorized domain %q", auth.Identifier.Value)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// presentAuthorization will present the challenge required for the given
|
||||
// authorization using the supplied certificate configuration.
|
||||
// If ths authorization is already presented, it will return no error.
|
||||
// If the self-check for the authorization has passed, it will return true.
|
||||
// Otherwise it will return false.
|
||||
func (a *Acme) presentAuthorization(ctx context.Context, cl client.Interface, crt *v1alpha1.Certificate, auth *acme.Authorization) (bool, *acme.Challenge, error) {
|
||||
glog.Infof("Presenting challenge for domain %q", auth.Identifier.Value)
|
||||
challenge, err := a.challengeForAuthorization(cl, crt, auth)
|
||||
if err != nil {
|
||||
// TODO: handle error properly
|
||||
return false, nil, err
|
||||
}
|
||||
domain := auth.Identifier.Value
|
||||
token := challenge.Token
|
||||
key, err := keyForChallenge(cl, challenge)
|
||||
if err != nil {
|
||||
return false, challenge, err
|
||||
}
|
||||
solver, err := a.solverFor(challenge.Type)
|
||||
if err != nil {
|
||||
// TODO: handle error properly
|
||||
return false, challenge, err
|
||||
}
|
||||
err = solver.Present(ctx, crt, domain, token, key)
|
||||
if err != nil {
|
||||
// TODO: handle error properly
|
||||
return false, challenge, err
|
||||
}
|
||||
glog.Infof("Performing check to ensure challenge has propagated")
|
||||
ok, err := solver.Check(domain, token, key)
|
||||
if err != nil {
|
||||
return false, challenge, err
|
||||
}
|
||||
return ok, challenge, nil
|
||||
}
|
||||
|
||||
// cleanupAuthorization will clean up a given authorization.
|
||||
// To do this, it first determines the challenge type to use for the
|
||||
// authorization based on the Issuer and Certificate configuration.
|
||||
// It then calls CleanUp on the appropriate Solver.
|
||||
// CleanUp will clean up any remaining resources left over from attempting to
|
||||
// solve the given challenge.
|
||||
// If a valid challenge type is not configured, cleanupAuthorization will
|
||||
// return an error.
|
||||
func (a *Acme) cleanupAuthorization(ctx context.Context, cl client.Interface, crt *v1alpha1.Certificate, auth *acme.Authorization) error {
|
||||
glog.Infof("Cleaning up authorization for %q", auth.Identifier.Value)
|
||||
challenge, err := a.challengeForAuthorization(cl, crt, auth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
domain := auth.Identifier.Value
|
||||
token := challenge.Token
|
||||
key, err := keyForChallenge(cl, challenge)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
solver, err := a.solverFor(challenge.Type)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return solver.CleanUp(ctx, crt, domain, token, key)
|
||||
}
|
||||
|
||||
// keyForChallenge will return the key to use for solving a given acme
|
||||
// challenge.
|
||||
// Only http-01 and dns-01 challenges are supported.
|
||||
// An error will be returned if obtaining the key fails, or the challenge type
|
||||
// is unsupported.
|
||||
func keyForChallenge(cl client.Interface, challenge *acme.Challenge) (string, error) {
|
||||
func keyForChallenge(cl *acme.Client, challenge *acme.Challenge) (string, error) {
|
||||
var err error
|
||||
switch challenge.Type {
|
||||
case "http-01":
|
||||
@ -284,6 +309,122 @@ func keyForChallenge(cl client.Interface, challenge *acme.Challenge) (string, er
|
||||
return "", err
|
||||
}
|
||||
|
||||
// shouldAttemptValidation determines whether Present should actually run by
|
||||
// evaluating when the last present for the current desired certificate was
|
||||
// last attempted.
|
||||
//
|
||||
// It returns the duration that cert-manager should wait until attempting
|
||||
// another authorization, or an error.
|
||||
// If an existing order for the Certificate exists and is not invalid, it
|
||||
// will be returned as well.
|
||||
// Returning <= 0 indicates that an authorization should be attempted now.
|
||||
//
|
||||
// - If the existing order URL is not set, it will return 0
|
||||
//
|
||||
// - If the existing order URL is set, but querying it fails, an error is
|
||||
// returned
|
||||
//
|
||||
// - If the existing order is pending or valid, it will return 0
|
||||
//
|
||||
// - If the existing order has failed, it will return
|
||||
//
|
||||
// (5 minutes) - (time.Now() - lastFailureTime)
|
||||
//
|
||||
// This causes cert-manager to only attempt authorizations every 5 minutes
|
||||
// if the previous attempt for the same configuration failed
|
||||
//
|
||||
// TODO:
|
||||
// - If the existing order has failed, but the previously attempted
|
||||
// configuration is different to the new configuration, it should return 0
|
||||
func (a *Acme) shouldAttemptValidation(ctx context.Context, cl client.Interface, crt *v1alpha1.Certificate) (time.Duration, *acme.Order, error) {
|
||||
orderURL := crt.Status.ACMEStatus().Order.URL
|
||||
if orderURL == "" {
|
||||
return 0, nil, nil
|
||||
}
|
||||
|
||||
// attempt to obtain a copy of the existing order url from the acme server
|
||||
// TODO: should we cache some of this info? Specific the 'order state'?
|
||||
// This would help reduce calls to the ACME server.
|
||||
order, err := cl.GetOrder(ctx, orderURL)
|
||||
if err != nil {
|
||||
// check if the error is a 'not found' or unauthorized type error. If
|
||||
// it is, we should attempt to authorize as either the issuer identity
|
||||
// has changed, or the order URL is very old
|
||||
if acmeErr, ok := err.(*acme.Error); ok {
|
||||
if acmeErr.StatusCode >= 400 && acmeErr.StatusCode <= 499 {
|
||||
return 0, nil, nil
|
||||
}
|
||||
}
|
||||
// return the error otherwise
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
// if the previously attempted order was for a different set of domains to
|
||||
// that of the current Certificate resource, we should immediately attempt
|
||||
// authorizations
|
||||
if !orderIsValidForCertificate(order, crt) {
|
||||
return 0, nil, nil
|
||||
}
|
||||
|
||||
switch order.Status {
|
||||
case acme.StatusPending, acme.StatusProcessing, acme.StatusValid:
|
||||
// if the order has not failed, attempt authorization
|
||||
return 0, order, nil
|
||||
case acme.StatusRevoked, acme.StatusInvalid, acme.StatusUnknown:
|
||||
// if the certificate is not marked as failed, we should set the
|
||||
// condition on the resource
|
||||
if !crt.HasCondition(v1alpha1.CertificateCondition{
|
||||
Type: v1alpha1.CertificateConditionValidationFailed,
|
||||
Status: v1alpha1.ConditionTrue,
|
||||
}) {
|
||||
crt.UpdateStatusCondition(v1alpha1.CertificateConditionValidationFailed, v1alpha1.ConditionTrue, "OrderFailed", fmt.Sprintf("Order failed: %v", order.Error.Error()), true)
|
||||
}
|
||||
|
||||
// we know that we'll be able to find the appropriate condition because
|
||||
// HasCondition returned true above
|
||||
// If we don't, the lastTransitionTime will be set to 0, meaning we'll
|
||||
// trigger an immediate re-issue anyway
|
||||
var condition v1alpha1.CertificateCondition
|
||||
for _, cond := range crt.Status.Conditions {
|
||||
if cond.Type == v1alpha1.CertificateConditionValidationFailed {
|
||||
condition = cond
|
||||
}
|
||||
}
|
||||
|
||||
return prepareAttemptWaitPeriod - (time.Now().Sub(condition.LastTransitionTime.Time)), order, nil
|
||||
}
|
||||
|
||||
return 0, nil, fmt.Errorf("unrecognised existing acme order status: %q", order.Status)
|
||||
}
|
||||
|
||||
func (a *Acme) acceptChallenge(ctx context.Context, cl client.Interface, ch v1alpha1.ACMEOrderChallenge) error {
|
||||
glog.Infof("Accepting challenge for domain %q", ch.Domain)
|
||||
// We manually construct an ACME challenge here from our own internal type
|
||||
// to save additional round trips to the ACME server.
|
||||
acmeChal := &acme.Challenge{
|
||||
URL: ch.URL,
|
||||
Token: ch.Token,
|
||||
}
|
||||
_, err := cl.AcceptChallenge(ctx, acmeChal)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
glog.Infof("Waiting for authorization for domain %q", ch.Domain)
|
||||
authorization, err := cl.WaitAuthorization(ctx, ch.AuthzURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if authorization.Status != acme.StatusValid {
|
||||
return fmt.Errorf("expected acme domain authorization status for %q to be valid, but it is %q", authorization.Identifier.Value, authorization.Status)
|
||||
}
|
||||
|
||||
glog.Infof("Successfully authorized domain %q", authorization.Identifier.Value)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// getAuthorizations will query the ACME server for the Authorization resources
|
||||
// for the given list of authorization URLs using the given ACME client.
|
||||
// It will return an error if obtaining any of the given authorizations fails.
|
||||
@ -299,59 +440,14 @@ func getAuthorizations(ctx context.Context, cl client.Interface, urls ...string)
|
||||
return authzs, nil
|
||||
}
|
||||
|
||||
// partitionAuthorizations will split a list of Authorizations into failed,
|
||||
// pending and valid states.
|
||||
func partitionAuthorizations(authzs ...*acme.Authorization) (failed, pending, valid []*acme.Authorization) {
|
||||
for _, a := range authzs {
|
||||
switch a.Status {
|
||||
case acme.StatusDeactivated, acme.StatusInvalid, acme.StatusRevoked, acme.StatusUnknown:
|
||||
failed = append(failed, a)
|
||||
case acme.StatusPending, acme.StatusProcessing:
|
||||
pending = append(pending, a)
|
||||
case acme.StatusValid:
|
||||
valid = append(valid, a)
|
||||
}
|
||||
}
|
||||
return failed, pending, valid
|
||||
}
|
||||
|
||||
// pickChallengeType will select a challenge type to used based on the types
|
||||
// offered by the ACME server (i.e. auth.Challenges), the options configured on
|
||||
// the Certificate resource (cfg) and the providers configured on the
|
||||
// corresponding Issuer resource. If there is no challenge type that can be
|
||||
// used, it will return an error.
|
||||
func (a *Acme) pickChallengeType(domain string, auth *acme.Authorization, cfg []v1alpha1.ACMECertificateDomainConfig) (string, error) {
|
||||
for _, d := range cfg {
|
||||
func acmeSolverConfiguration(cfg *v1alpha1.ACMECertificateConfig, domain string) (*v1alpha1.ACMESolverConfig, error) {
|
||||
for _, d := range cfg.Config {
|
||||
for _, dom := range d.Domains {
|
||||
if dom == domain {
|
||||
for _, challenge := range auth.Challenges {
|
||||
switch {
|
||||
case challenge.Type == "http-01" && d.HTTP01 != nil && a.issuer.GetSpec().ACME.HTTP01 != nil:
|
||||
return challenge.Type, nil
|
||||
case challenge.Type == "dns-01" && d.DNS01 != nil && a.issuer.GetSpec().ACME.DNS01 != nil:
|
||||
return challenge.Type, nil
|
||||
}
|
||||
}
|
||||
if dom != domain {
|
||||
continue
|
||||
}
|
||||
return &d.ACMESolverConfig, nil
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("no configured and supported challenge type found")
|
||||
}
|
||||
|
||||
func (a *Acme) challengeForAuthorization(cl client.Interface, crt *v1alpha1.Certificate, auth *acme.Authorization) (*acme.Challenge, error) {
|
||||
domain := auth.Identifier.Value
|
||||
glog.Infof("picking challenge type for domain %q", domain)
|
||||
challengeType, err := a.pickChallengeType(domain, auth, crt.Spec.ACME.Config)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error picking challenge type to use for domain '%s': %s", domain, err.Error())
|
||||
}
|
||||
|
||||
for _, challenge := range auth.Challenges {
|
||||
if challenge.Type != challengeType {
|
||||
continue
|
||||
}
|
||||
glog.Infof("picked challenge type %q for domain %q", challenge.Type, domain)
|
||||
return challenge, nil
|
||||
}
|
||||
return nil, fmt.Errorf("challenge mechanism '%s' not allowed for domain", challengeType)
|
||||
return nil, fmt.Errorf("solver configuration for domain %q not found. Ensure you have configured a challenge mechanism using the certificate.spec.acme.config field", domain)
|
||||
}
|
||||
|
||||
@ -1,333 +1,5 @@
|
||||
package acme
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha1"
|
||||
"github.com/jetstack/cert-manager/pkg/issuer/acme/client"
|
||||
"github.com/jetstack/cert-manager/test/util/generate"
|
||||
"github.com/jetstack/cert-manager/third_party/crypto/acme"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultTestNamespace = "default"
|
||||
)
|
||||
|
||||
func TestGetOrCreateOrder(t *testing.T) {
|
||||
issuer := generate.Issuer(generate.IssuerConfig{
|
||||
Name: "test",
|
||||
Namespace: defaultTestNamespace,
|
||||
HTTP01: &v1alpha1.ACMEIssuerHTTP01Config{},
|
||||
ACMEServer: "fakeserver",
|
||||
ACMEEmail: "fakeemail",
|
||||
ACMEPrivateKeyName: "fakeprivkey",
|
||||
})
|
||||
certificate := generate.Certificate(generate.CertificateConfig{
|
||||
Name: "test-crt",
|
||||
Namespace: defaultTestNamespace,
|
||||
IssuerName: issuer.Name,
|
||||
IssuerKind: issuer.Kind,
|
||||
DNSNames: []string{"example.com"},
|
||||
ACMEOrderURL: "",
|
||||
})
|
||||
invalidOrderURL := "invalidorderurl"
|
||||
validOrderURL := "validorderurl"
|
||||
|
||||
tests := map[string]acmeFixture{
|
||||
"should call createOrder if order URL is blank": acmeFixture{
|
||||
Issuer: issuer,
|
||||
PreFn: func(a *acmeFixture) {
|
||||
crt, err := a.f.CertManagerClient().CertmanagerV1alpha1().Certificates(defaultTestNamespace).Create(certificate)
|
||||
if err != nil {
|
||||
t.Errorf("Error preparing test: %v", err)
|
||||
t.FailNow()
|
||||
}
|
||||
a.Certificate = crt
|
||||
},
|
||||
Client: &client.FakeACME{
|
||||
FakeCreateOrder: func(_ context.Context, order *acme.Order) (*acme.Order, error) {
|
||||
order.URL = validOrderURL
|
||||
return order, nil
|
||||
},
|
||||
},
|
||||
CheckFn: func(a *acmeFixture, args ...interface{}) {
|
||||
order := args[0].(*acme.Order)
|
||||
if order.URL != validOrderURL {
|
||||
t.Errorf("Expected order URL to be set to %q, but it is %q", validOrderURL, order.URL)
|
||||
}
|
||||
if a.Certificate.Status.ACME.Order.URL != validOrderURL {
|
||||
t.Errorf("Expected certificate acme order url to be set to %q but it was %q", validOrderURL, a.Certificate.Status.ACME.Order.URL)
|
||||
}
|
||||
},
|
||||
},
|
||||
"should return an error if GetOrder returns an error": acmeFixture{
|
||||
Issuer: issuer,
|
||||
Err: true,
|
||||
PreFn: func(a *acmeFixture) {
|
||||
t := a.f.T
|
||||
crt := certificate.DeepCopy()
|
||||
crt.Status.ACME.Order.URL = validOrderURL
|
||||
crt, err := a.f.CertManagerClient().CertmanagerV1alpha1().Certificates(defaultTestNamespace).Create(crt)
|
||||
if err != nil {
|
||||
t.Errorf("Error preparing test: %v", err)
|
||||
t.FailNow()
|
||||
}
|
||||
a.Certificate = crt
|
||||
},
|
||||
Client: &client.FakeACME{
|
||||
FakeGetOrder: func(_ context.Context, url string) (*acme.Order, error) {
|
||||
return nil, fmt.Errorf("fake error")
|
||||
},
|
||||
},
|
||||
CheckFn: func(a *acmeFixture, args ...interface{}) {
|
||||
t := a.f.T
|
||||
order := args[0].(*acme.Order)
|
||||
if order != nil {
|
||||
t.Errorf("expected order to be nil")
|
||||
}
|
||||
},
|
||||
},
|
||||
"should return existing order if it's pending": acmeFixture{
|
||||
Issuer: issuer,
|
||||
PreFn: func(a *acmeFixture) {
|
||||
t := a.f.T
|
||||
crt := certificate.DeepCopy()
|
||||
crt.Status.ACME.Order.URL = validOrderURL
|
||||
crt, err := a.f.CertManagerClient().CertmanagerV1alpha1().Certificates(defaultTestNamespace).Create(crt)
|
||||
if err != nil {
|
||||
t.Errorf("Error preparing test: %v", err)
|
||||
t.FailNow()
|
||||
}
|
||||
a.Certificate = crt
|
||||
},
|
||||
Client: &client.FakeACME{
|
||||
FakeGetOrder: func(_ context.Context, url string) (*acme.Order, error) {
|
||||
// we call buildOrder to ensure the dns names are correctly set
|
||||
order := acme.NewOrder("example.com")
|
||||
order.URL = url
|
||||
order.Status = acme.StatusPending
|
||||
return order, nil
|
||||
},
|
||||
},
|
||||
CheckFn: func(a *acmeFixture, args ...interface{}) {
|
||||
t := a.f.T
|
||||
order := args[0].(*acme.Order)
|
||||
if len(order.Identifiers) != 1 {
|
||||
t.Errorf("expected one identifier, but identifiers=%+v", order.Identifiers)
|
||||
t.Fail()
|
||||
}
|
||||
if order.Identifiers[0].Value != "example.com" {
|
||||
t.Errorf("expected identifier to be 'example.com' but it is %q", order.Identifiers[0].Value)
|
||||
}
|
||||
if order.Status != acme.StatusPending {
|
||||
t.Errorf("expected order status to be pending, but it is %q", order.Status)
|
||||
}
|
||||
},
|
||||
},
|
||||
"should create a new order if existing order is failed": acmeFixture{
|
||||
Issuer: issuer,
|
||||
PreFn: func(a *acmeFixture) {
|
||||
t := a.f.T
|
||||
crt := certificate.DeepCopy()
|
||||
crt.Status.ACME.Order.URL = validOrderURL
|
||||
crt, err := a.f.CertManagerClient().CertmanagerV1alpha1().Certificates(defaultTestNamespace).Create(crt)
|
||||
if err != nil {
|
||||
t.Errorf("Error preparing test: %v", err)
|
||||
t.FailNow()
|
||||
}
|
||||
a.Certificate = crt
|
||||
},
|
||||
Client: &client.FakeACME{
|
||||
FakeGetOrder: func(_ context.Context, url string) (*acme.Order, error) {
|
||||
// we call buildOrder to ensure the dns names are correctly set
|
||||
order := acme.NewOrder("example.com")
|
||||
order.URL = url
|
||||
order.Status = acme.StatusInvalid
|
||||
return order, nil
|
||||
},
|
||||
FakeCreateOrder: func(_ context.Context, order *acme.Order) (*acme.Order, error) {
|
||||
order.URL = validOrderURL
|
||||
return order, nil
|
||||
},
|
||||
},
|
||||
CheckFn: func(a *acmeFixture, args ...interface{}) {
|
||||
t := a.f.T
|
||||
order := args[0].(*acme.Order)
|
||||
if len(order.Identifiers) != 1 {
|
||||
t.Errorf("expected one identifier, but identifiers=%+v", order.Identifiers)
|
||||
t.Fail()
|
||||
}
|
||||
if order.Identifiers[0].Value != "example.com" {
|
||||
t.Errorf("expected identifier to be 'example.com' but it is %q", order.Identifiers[0].Value)
|
||||
}
|
||||
if order.Status == acme.StatusInvalid {
|
||||
t.Errorf("expected order status to not be invalid")
|
||||
}
|
||||
},
|
||||
},
|
||||
"should create a new order if existing order is for a different set of dns names": acmeFixture{
|
||||
Issuer: issuer,
|
||||
PreFn: func(a *acmeFixture) {
|
||||
t := a.f.T
|
||||
crt := certificate.DeepCopy()
|
||||
crt.Status.ACME.Order.URL = invalidOrderURL
|
||||
crt, err := a.f.CertManagerClient().CertmanagerV1alpha1().Certificates(defaultTestNamespace).Create(crt)
|
||||
if err != nil {
|
||||
t.Errorf("Error preparing test: %v", err)
|
||||
t.FailNow()
|
||||
}
|
||||
a.Certificate = crt
|
||||
},
|
||||
Client: &client.FakeACME{
|
||||
FakeGetOrder: func(_ context.Context, url string) (*acme.Order, error) {
|
||||
// we call buildOrder to ensure the dns names are correctly set
|
||||
order := acme.NewOrder("notexample.com")
|
||||
// todo: assert that this url = invalidOrderURL
|
||||
order.URL = url
|
||||
order.Status = acme.StatusPending
|
||||
return order, nil
|
||||
},
|
||||
FakeCreateOrder: func(_ context.Context, order *acme.Order) (*acme.Order, error) {
|
||||
order.URL = validOrderURL
|
||||
return order, nil
|
||||
},
|
||||
},
|
||||
CheckFn: func(a *acmeFixture, args ...interface{}) {
|
||||
t := a.f.T
|
||||
order := args[0].(*acme.Order)
|
||||
if len(order.Identifiers) != 1 {
|
||||
t.Errorf("expected one identifier, but identifiers=%+v", order.Identifiers)
|
||||
t.Fail()
|
||||
}
|
||||
if order.Identifiers[0].Value != "example.com" {
|
||||
t.Errorf("expected identifier to be 'example.com' but it is %q", order.Identifiers[0].Value)
|
||||
}
|
||||
if a.Certificate.Status.ACME.Order.URL != validOrderURL {
|
||||
t.Errorf("expected certificate order url status field to be %q but it is %q", validOrderURL, a.Certificate.Status.ACME.Order.URL)
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
for n, test := range tests {
|
||||
t.Run(n, func(t *testing.T) {
|
||||
test.Setup(t)
|
||||
defer test.f.Stop()
|
||||
order, err := test.Acme.getOrCreateOrder(test.Ctx, test.Client, test.Certificate)
|
||||
if err != nil && !test.Err {
|
||||
t.Errorf("Expected function to not error, but got: %v", err)
|
||||
t.FailNow()
|
||||
}
|
||||
if err == nil && test.Err {
|
||||
t.Errorf("Expected function to get an error, but got: %v", err)
|
||||
t.FailNow()
|
||||
}
|
||||
test.Finish(t, order, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPickChallengeType(t *testing.T) {
|
||||
type testT struct {
|
||||
Domain string
|
||||
OfferedChallenges []string
|
||||
CertConfigs []v1alpha1.ACMECertificateDomainConfig
|
||||
ACMEIssuer v1alpha1.ACMEIssuer
|
||||
|
||||
ExpectedType string
|
||||
Error bool
|
||||
}
|
||||
tests := map[string]testT{
|
||||
"correctly selects http01 validation": {
|
||||
Domain: "example.com",
|
||||
OfferedChallenges: []string{"http-01"},
|
||||
CertConfigs: []v1alpha1.ACMECertificateDomainConfig{
|
||||
{
|
||||
Domains: []string{"example.com"},
|
||||
HTTP01: &v1alpha1.ACMECertificateHTTP01Config{},
|
||||
},
|
||||
},
|
||||
ACMEIssuer: v1alpha1.ACMEIssuer{
|
||||
HTTP01: &v1alpha1.ACMEIssuerHTTP01Config{},
|
||||
},
|
||||
ExpectedType: "http-01",
|
||||
},
|
||||
"selects http01 challenge type and ignores the configured dns01 provider": {
|
||||
Domain: "example.com",
|
||||
OfferedChallenges: []string{"http-01", "dns-01"},
|
||||
CertConfigs: []v1alpha1.ACMECertificateDomainConfig{
|
||||
{
|
||||
Domains: []string{"www.example.com"},
|
||||
DNS01: &v1alpha1.ACMECertificateDNS01Config{},
|
||||
},
|
||||
{
|
||||
Domains: []string{"example.com"},
|
||||
HTTP01: &v1alpha1.ACMECertificateHTTP01Config{},
|
||||
},
|
||||
},
|
||||
ACMEIssuer: v1alpha1.ACMEIssuer{
|
||||
DNS01: &v1alpha1.ACMEIssuerDNS01Config{},
|
||||
HTTP01: &v1alpha1.ACMEIssuerHTTP01Config{},
|
||||
},
|
||||
ExpectedType: "http-01",
|
||||
},
|
||||
"correctly selects dns01 challenge type": {
|
||||
Domain: "www.example.com",
|
||||
OfferedChallenges: []string{"http-01", "dns-01"},
|
||||
CertConfigs: []v1alpha1.ACMECertificateDomainConfig{
|
||||
{
|
||||
Domains: []string{"www.example.com"},
|
||||
DNS01: &v1alpha1.ACMECertificateDNS01Config{},
|
||||
},
|
||||
{
|
||||
Domains: []string{"example.com"},
|
||||
HTTP01: &v1alpha1.ACMECertificateHTTP01Config{},
|
||||
},
|
||||
},
|
||||
ACMEIssuer: v1alpha1.ACMEIssuer{
|
||||
DNS01: &v1alpha1.ACMEIssuerDNS01Config{},
|
||||
HTTP01: &v1alpha1.ACMEIssuerHTTP01Config{},
|
||||
},
|
||||
ExpectedType: "dns-01",
|
||||
},
|
||||
"error if none of the offered challenges are configured on the issuer": {
|
||||
Domain: "example.com",
|
||||
OfferedChallenges: []string{"http-01", "dns-01"},
|
||||
CertConfigs: []v1alpha1.ACMECertificateDomainConfig{
|
||||
{
|
||||
Domains: []string{"example.com"},
|
||||
HTTP01: &v1alpha1.ACMECertificateHTTP01Config{},
|
||||
},
|
||||
},
|
||||
ACMEIssuer: v1alpha1.ACMEIssuer{},
|
||||
Error: true,
|
||||
},
|
||||
}
|
||||
for n, test := range tests {
|
||||
t.Run(n, func(t *testing.T) {
|
||||
a := &Acme{issuer: &v1alpha1.Issuer{Spec: v1alpha1.IssuerSpec{
|
||||
IssuerConfig: v1alpha1.IssuerConfig{
|
||||
ACME: &test.ACMEIssuer,
|
||||
},
|
||||
}}}
|
||||
challenges := make([]*acme.Challenge, len(test.OfferedChallenges))
|
||||
for i, c := range test.OfferedChallenges {
|
||||
challenges[i] = &acme.Challenge{Type: c}
|
||||
}
|
||||
acmeAuthz := &acme.Authorization{Challenges: challenges}
|
||||
pickedType, err := a.pickChallengeType(test.Domain, acmeAuthz, test.CertConfigs)
|
||||
if err != nil && !test.Error {
|
||||
t.Errorf("Error picking ACME challenge type, but no error was expected: %v", err)
|
||||
t.Fail()
|
||||
}
|
||||
if err == nil && test.Error {
|
||||
t.Errorf("Expected an error picking ACME challenge type, but instead got a type: %q", pickedType)
|
||||
t.Fail()
|
||||
}
|
||||
if pickedType != test.ExpectedType {
|
||||
t.Errorf("Expected picked type to be %q but it was instead %q", test.ExpectedType, pickedType)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -18,11 +18,11 @@ func (a *Acme) Renew(ctx context.Context, crt *v1alpha1.Certificate) ([]byte, []
|
||||
key, cert, err := a.obtainCertificate(ctx, crt)
|
||||
if err != nil {
|
||||
s := messageErrorIssueCert + err.Error()
|
||||
crt.UpdateStatusCondition(v1alpha1.CertificateConditionReady, v1alpha1.ConditionFalse, errorRenewCert, s)
|
||||
crt.UpdateStatusCondition(v1alpha1.CertificateConditionReady, v1alpha1.ConditionFalse, errorRenewCert, s, false)
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
crt.UpdateStatusCondition(v1alpha1.CertificateConditionReady, v1alpha1.ConditionTrue, successCertRenewed, messageCertRenewed)
|
||||
crt.UpdateStatusCondition(v1alpha1.CertificateConditionReady, v1alpha1.ConditionTrue, successCertRenewed, messageCertRenewed, false)
|
||||
|
||||
return key, cert, err
|
||||
}
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
package acme
|
||||
|
||||
import (
|
||||
"github.com/golang/glog"
|
||||
"github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha1"
|
||||
"github.com/jetstack/cert-manager/pkg/util"
|
||||
"github.com/jetstack/cert-manager/pkg/util/pki"
|
||||
@ -28,7 +27,6 @@ func buildOrder(crt *v1alpha1.Certificate) (*acme.Order, error) {
|
||||
func orderIsValidForCertificate(order *acme.Order, crt *v1alpha1.Certificate) bool {
|
||||
desiredDNSNames := pki.DNSNamesForCertificate(crt)
|
||||
orderDNSNames := authzIDListToStrings(order.Identifiers)
|
||||
glog.Infof("Comparing %+v and %+v", desiredDNSNames, orderDNSNames)
|
||||
return util.EqualUnsorted(desiredDNSNames, orderDNSNames)
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user