Skip to content

Commit c82ff34

Browse files
authored
Retry with new account if account disappeared remotely (#269)
* Retry with new account if account disappeared remotely * Emit log when account is missing from ACME server
1 parent c3c4a12 commit c82ff34

File tree

2 files changed

+44
-6
lines changed

2 files changed

+44
-6
lines changed

account.go

+10
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,16 @@ func (am *ACMEIssuer) saveAccount(ctx context.Context, ca string, account acme.A
171171
return storeTx(ctx, am.config.Storage, all)
172172
}
173173

174+
// deleteAccountLocally deletes the registration info and private key of the account
175+
// for the given CA from storage.
176+
func (am *ACMEIssuer) deleteAccountLocally(ctx context.Context, ca string, account acme.Account) error {
177+
primaryContact := getPrimaryContact(account)
178+
if err := am.config.Storage.Delete(ctx, am.storageKeyUserReg(ca, primaryContact)); err != nil {
179+
return err
180+
}
181+
return am.config.Storage.Delete(ctx, am.storageKeyUserPrivateKey(ca, primaryContact))
182+
}
183+
174184
// setEmail does everything it can to obtain an email address
175185
// from the user within the scope of memory and storage to use
176186
// for ACME TLS. If it cannot get an email address, it does nothing

acmeissuer.go

+34-6
Original file line numberDiff line numberDiff line change
@@ -393,12 +393,40 @@ func (am *ACMEIssuer) doIssue(ctx context.Context, csr *x509.CertificateRequest,
393393
}
394394
}
395395

396-
certChains, err := client.acmeClient.ObtainCertificateUsingCSR(ctx, client.account, csr)
397-
if err != nil {
398-
return nil, usingTestCA, fmt.Errorf("%v %w (ca=%s)", nameSet, err, client.acmeClient.Directory)
399-
}
400-
if len(certChains) == 0 {
401-
return nil, usingTestCA, fmt.Errorf("no certificate chains")
396+
// do this in a loop because there's an error case that may necessitate a retry, but not more than once
397+
var certChains []acme.Certificate
398+
for i := 0; i < 2; i++ {
399+
certChains, err = client.acmeClient.ObtainCertificateUsingCSR(ctx, client.account, csr)
400+
if err != nil {
401+
var prob acme.Problem
402+
if errors.As(err, &prob) && prob.Type == acme.ProblemTypeAccountDoesNotExist {
403+
am.Logger.Warn("ACME account does not exist on server; attempting to recreate",
404+
zap.Strings("account_contact", client.account.Contact),
405+
zap.String("account_location", client.account.Location),
406+
zap.Object("problem", prob))
407+
408+
// the account we have no longer exists on the CA, so we need to create a new one;
409+
// we could use the same key pair, but this is a good opportunity to rotate keys
410+
// (see https://caddy.community/t/acme-account-is-not-regenerated-when-acme-server-gets-reinstalled/22627)
411+
// (basically this happens if the CA gets reset or reinstalled; usually just internal PKI)
412+
err := am.deleteAccountLocally(ctx, client.iss.CA, client.account)
413+
if err != nil {
414+
return nil, usingTestCA, fmt.Errorf("%v ACME account no longer exists on CA, but resetting our local copy of the account info failed: %v", nameSet, err)
415+
}
416+
417+
// recreate account and try again
418+
client, err = am.newACMEClientWithAccount(ctx, useTestCA, false)
419+
if err != nil {
420+
return nil, false, err
421+
}
422+
continue
423+
}
424+
return nil, usingTestCA, fmt.Errorf("%v %w (ca=%s)", nameSet, err, client.acmeClient.Directory)
425+
}
426+
if len(certChains) == 0 {
427+
return nil, usingTestCA, fmt.Errorf("no certificate chains")
428+
}
429+
break
402430
}
403431

404432
preferredChain := am.selectPreferredChain(certChains)

0 commit comments

Comments
 (0)