Rename ACMEManager -> ACMEIssuer, CertificateManager -> Manager

This is necessary to eliminate confusing naming conventions, since now
we have Manager types, having an issuer called ACMEManager was
confusing.

CertificateManager is a redundant name as this package is called
CertMagic, so that a Manager manages certificates should be obvious.
It's also more succinct. Plus, it's consistent with Issuer which is not
named CertificateIssuer.
This commit is contained in:
Matthew Holt 2022-03-24 11:34:31 -06:00
parent ae2a5ddada
commit 55be6d8695
No known key found for this signature in database
GPG Key ID: 2A349DD577D586A5
14 changed files with 130 additions and 130 deletions

View File

@ -176,7 +176,7 @@ Note that Let's Encrypt imposes [strict rate limits](https://letsencrypt.org/doc
While developing your application and testing it, use [their staging endpoint](https://letsencrypt.org/docs/staging-environment/) which has much higher rate limits. Even then, don't hammer it: but it's much safer for when you're testing. When deploying, though, use their production CA because their staging CA doesn't issue trusted certificates. While developing your application and testing it, use [their staging endpoint](https://letsencrypt.org/docs/staging-environment/) which has much higher rate limits. Even then, don't hammer it: but it's much safer for when you're testing. When deploying, though, use their production CA because their staging CA doesn't issue trusted certificates.
To use staging, set `certmagic.DefaultACME.CA = certmagic.LetsEncryptStagingCA` or set `CA` of every `ACMEManager` struct. To use staging, set `certmagic.DefaultACME.CA = certmagic.LetsEncryptStagingCA` or set `CA` of every `ACMEIssuer` struct.
@ -256,7 +256,7 @@ magic := certmagic.New(cache, certmagic.Config{
// any customizations you need go here // any customizations you need go here
}) })
myACME := certmagic.NewACMEManager(magic, certmagic.ACMEManager{ myACME := certmagic.NewACMEIssuer(magic, certmagic.ACMEIssuer{
CA: certmagic.LetsEncryptStagingCA, CA: certmagic.LetsEncryptStagingCA,
Email: "you@yours.com", Email: "you@yours.com",
Agreed: true, Agreed: true,
@ -344,7 +344,7 @@ If wrapping your handler is not a good solution, try this inside your `ServeHTTP
```go ```go
magic := certmagic.NewDefault() magic := certmagic.NewDefault()
myACME := certmagic.NewACMEManager(magic, certmagic.DefaultACME) myACME := certmagic.NewACMEIssuer(magic, certmagic.DefaultACME)
func ServeHTTP(w http.ResponseWriter, req *http.Request) { func ServeHTTP(w http.ResponseWriter, req *http.Request) {
if myACME.HandleHTTPChallenge(w, r) { if myACME.HandleHTTPChallenge(w, r) {
@ -388,7 +388,7 @@ The DNS challenge is perhaps the most useful challenge because it allows you to
This challenge works by setting a special record in the domain's zone. To do this automatically, your DNS provider needs to offer an API by which changes can be made to domain names, and the changes need to take effect immediately for best results. CertMagic supports [all DNS providers with `libdns` implementations](https://github.com/libdns)! It always cleans up the temporary record after the challenge completes. This challenge works by setting a special record in the domain's zone. To do this automatically, your DNS provider needs to offer an API by which changes can be made to domain names, and the changes need to take effect immediately for best results. CertMagic supports [all DNS providers with `libdns` implementations](https://github.com/libdns)! It always cleans up the temporary record after the challenge completes.
To enable it, just set the `DNS01Solver` field on a `certmagic.ACMEManager` struct, or set the default `certmagic.ACMEManager.DNS01Solver` variable. For example, if my domains' DNS was served by Cloudflare: To enable it, just set the `DNS01Solver` field on a `certmagic.ACMEIssuer` struct, or set the default `certmagic.ACMEIssuer.DNS01Solver` variable. For example, if my domains' DNS was served by Cloudflare:
```go ```go
import "github.com/libdns/cloudflare" import "github.com/libdns/cloudflare"
@ -400,7 +400,7 @@ certmagic.DefaultACME.DNS01Solver = &certmagic.DNS01Solver{
} }
``` ```
Now the DNS challenge will be used by default, and I can obtain certificates for wildcard domains, too. Enabling the DNS challenge disables the other challenges for that `certmagic.ACMEManager` instance. Now the DNS challenge will be used by default, and I can obtain certificates for wildcard domains, too. Enabling the DNS challenge disables the other challenges for that `certmagic.ACMEIssuer` instance.
## On-Demand TLS ## On-Demand TLS

View File

@ -37,7 +37,7 @@ import (
// getAccount either loads or creates a new account, depending on if // getAccount either loads or creates a new account, depending on if
// an account can be found in storage for the given CA + email combo. // an account can be found in storage for the given CA + email combo.
func (am *ACMEManager) getAccount(ctx context.Context, ca, email string) (acme.Account, error) { func (am *ACMEIssuer) getAccount(ctx context.Context, ca, email string) (acme.Account, error) {
acct, err := am.loadAccount(ctx, ca, email) acct, err := am.loadAccount(ctx, ca, email)
if errors.Is(err, fs.ErrNotExist) { if errors.Is(err, fs.ErrNotExist) {
return am.newAccount(email) return am.newAccount(email)
@ -46,7 +46,7 @@ func (am *ACMEManager) getAccount(ctx context.Context, ca, email string) (acme.A
} }
// loadAccount loads an account from storage, but does not create a new one. // loadAccount loads an account from storage, but does not create a new one.
func (am *ACMEManager) loadAccount(ctx context.Context, ca, email string) (acme.Account, error) { func (am *ACMEIssuer) loadAccount(ctx context.Context, ca, email string) (acme.Account, error) {
regBytes, err := am.config.Storage.Load(ctx, am.storageKeyUserReg(ca, email)) regBytes, err := am.config.Storage.Load(ctx, am.storageKeyUserReg(ca, email))
if err != nil { if err != nil {
return acme.Account{}, err return acme.Account{}, err
@ -71,7 +71,7 @@ func (am *ACMEManager) loadAccount(ctx context.Context, ca, email string) (acme.
// newAccount generates a new private key for a new ACME account, but // newAccount generates a new private key for a new ACME account, but
// it does not register or save the account. // it does not register or save the account.
func (*ACMEManager) newAccount(email string) (acme.Account, error) { func (*ACMEIssuer) newAccount(email string) (acme.Account, error) {
var acct acme.Account var acct acme.Account
if email != "" { if email != "" {
acct.Contact = []string{"mailto:" + email} // TODO: should we abstract the contact scheme? acct.Contact = []string{"mailto:" + email} // TODO: should we abstract the contact scheme?
@ -87,7 +87,7 @@ func (*ACMEManager) newAccount(email string) (acme.Account, error) {
// GetAccount first tries loading the account with the associated private key from storage. // GetAccount first tries loading the account with the associated private key from storage.
// If it does not exist in storage, it will be retrieved from the ACME server and added to storage. // If it does not exist in storage, it will be retrieved from the ACME server and added to storage.
// The account must already exist; it does not create a new account. // The account must already exist; it does not create a new account.
func (am *ACMEManager) GetAccount(ctx context.Context, privateKeyPEM []byte) (acme.Account, error) { func (am *ACMEIssuer) GetAccount(ctx context.Context, privateKeyPEM []byte) (acme.Account, error) {
account, err := am.loadAccountByKey(ctx, privateKeyPEM) account, err := am.loadAccountByKey(ctx, privateKeyPEM)
if errors.Is(err, fs.ErrNotExist) { if errors.Is(err, fs.ErrNotExist) {
account, err = am.lookUpAccount(ctx, privateKeyPEM) account, err = am.lookUpAccount(ctx, privateKeyPEM)
@ -98,7 +98,7 @@ func (am *ACMEManager) GetAccount(ctx context.Context, privateKeyPEM []byte) (ac
// loadAccountByKey loads the account with the given private key from storage, if it exists. // loadAccountByKey loads the account with the given private key from storage, if it exists.
// If it does not exist, an error of type fs.ErrNotExist is returned. This is not very efficient // If it does not exist, an error of type fs.ErrNotExist is returned. This is not very efficient
// for lots of accounts. // for lots of accounts.
func (am *ACMEManager) loadAccountByKey(ctx context.Context, privateKeyPEM []byte) (acme.Account, error) { func (am *ACMEIssuer) loadAccountByKey(ctx context.Context, privateKeyPEM []byte) (acme.Account, error) {
accountList, err := am.config.Storage.List(ctx, am.storageKeyUsersPrefix(am.CA), false) accountList, err := am.config.Storage.List(ctx, am.storageKeyUsersPrefix(am.CA), false)
if err != nil { if err != nil {
return acme.Account{}, err return acme.Account{}, err
@ -118,7 +118,7 @@ func (am *ACMEManager) loadAccountByKey(ctx context.Context, privateKeyPEM []byt
// lookUpAccount looks up the account associated with privateKeyPEM from the ACME server. // lookUpAccount looks up the account associated with privateKeyPEM from the ACME server.
// If the account is found by the server, it will be saved to storage and returned. // If the account is found by the server, it will be saved to storage and returned.
func (am *ACMEManager) lookUpAccount(ctx context.Context, privateKeyPEM []byte) (acme.Account, error) { func (am *ACMEIssuer) lookUpAccount(ctx context.Context, privateKeyPEM []byte) (acme.Account, error) {
client, err := am.newACMEClient(false) client, err := am.newACMEClient(false)
if err != nil { if err != nil {
return acme.Account{}, fmt.Errorf("creating ACME client: %v", err) return acme.Account{}, fmt.Errorf("creating ACME client: %v", err)
@ -147,7 +147,7 @@ func (am *ACMEManager) lookUpAccount(ctx context.Context, privateKeyPEM []byte)
// saveAccount persists an ACME account's info and private key to storage. // saveAccount persists an ACME account's info and private key to storage.
// It does NOT register the account via ACME or prompt the user. // It does NOT register the account via ACME or prompt the user.
func (am *ACMEManager) saveAccount(ctx context.Context, ca string, account acme.Account) error { func (am *ACMEIssuer) saveAccount(ctx context.Context, ca string, account acme.Account) error {
regBytes, err := json.MarshalIndent(account, "", "\t") regBytes, err := json.MarshalIndent(account, "", "\t")
if err != nil { if err != nil {
return err return err
@ -178,7 +178,7 @@ func (am *ACMEManager) saveAccount(ctx context.Context, ca string, account acme.
// the consequences of an empty email.) This function MAY prompt // the consequences of an empty email.) This function MAY prompt
// the user for input. If allowPrompts is false, the user // the user for input. If allowPrompts is false, the user
// will NOT be prompted and an empty email may be returned. // will NOT be prompted and an empty email may be returned.
func (am *ACMEManager) getEmail(ctx context.Context, allowPrompts bool) error { func (am *ACMEIssuer) getEmail(ctx context.Context, allowPrompts bool) error {
leEmail := am.Email leEmail := am.Email
// First try package default email, or a discovered email address // First try package default email, or a discovered email address
@ -227,7 +227,7 @@ func (am *ACMEManager) getEmail(ctx context.Context, allowPrompts bool) error {
// be the empty string). If no error is returned, then Agreed // be the empty string). If no error is returned, then Agreed
// will also be set to true, since continuing through the // will also be set to true, since continuing through the
// prompt signifies agreement. // prompt signifies agreement.
func (am *ACMEManager) promptUserForEmail() (string, error) { func (am *ACMEIssuer) promptUserForEmail() (string, error) {
// prompt the user for an email address and terms agreement // prompt the user for an email address and terms agreement
reader := bufio.NewReader(stdin) reader := bufio.NewReader(stdin)
am.promptUserAgreement("") am.promptUserAgreement("")
@ -246,7 +246,7 @@ func (am *ACMEManager) promptUserForEmail() (string, error) {
// promptUserAgreement simply outputs the standard user // promptUserAgreement simply outputs the standard user
// agreement prompt with the given agreement URL. // agreement prompt with the given agreement URL.
// It outputs a newline after the message. // It outputs a newline after the message.
func (am *ACMEManager) promptUserAgreement(agreementURL string) { func (am *ACMEIssuer) promptUserAgreement(agreementURL string) {
userAgreementPrompt := `Your sites will be served over HTTPS automatically using an automated CA. userAgreementPrompt := `Your sites will be served over HTTPS automatically using an automated CA.
By continuing, you agree to the CA's terms of service` By continuing, you agree to the CA's terms of service`
if agreementURL == "" { if agreementURL == "" {
@ -259,7 +259,7 @@ By continuing, you agree to the CA's terms of service`
// askUserAgreement prompts the user to agree to the agreement // askUserAgreement prompts the user to agree to the agreement
// at the given agreement URL via stdin. It returns whether the // at the given agreement URL via stdin. It returns whether the
// user agreed or not. // user agreed or not.
func (am *ACMEManager) askUserAgreement(agreementURL string) bool { func (am *ACMEIssuer) askUserAgreement(agreementURL string) bool {
am.promptUserAgreement(agreementURL) am.promptUserAgreement(agreementURL)
fmt.Print("Do you agree to the terms? (y/n): ") fmt.Print("Do you agree to the terms? (y/n): ")
@ -277,32 +277,32 @@ func storageKeyACMECAPrefix(issuerKey string) string {
return path.Join(prefixACME, StorageKeys.Safe(issuerKey)) return path.Join(prefixACME, StorageKeys.Safe(issuerKey))
} }
func (am *ACMEManager) storageKeyCAPrefix(caURL string) string { func (am *ACMEIssuer) storageKeyCAPrefix(caURL string) string {
return storageKeyACMECAPrefix(am.issuerKey(caURL)) return storageKeyACMECAPrefix(am.issuerKey(caURL))
} }
func (am *ACMEManager) storageKeyUsersPrefix(caURL string) string { func (am *ACMEIssuer) storageKeyUsersPrefix(caURL string) string {
return path.Join(am.storageKeyCAPrefix(caURL), "users") return path.Join(am.storageKeyCAPrefix(caURL), "users")
} }
func (am *ACMEManager) storageKeyUserPrefix(caURL, email string) string { func (am *ACMEIssuer) storageKeyUserPrefix(caURL, email string) string {
if email == "" { if email == "" {
email = emptyEmail email = emptyEmail
} }
return path.Join(am.storageKeyUsersPrefix(caURL), StorageKeys.Safe(email)) return path.Join(am.storageKeyUsersPrefix(caURL), StorageKeys.Safe(email))
} }
func (am *ACMEManager) storageKeyUserReg(caURL, email string) string { func (am *ACMEIssuer) storageKeyUserReg(caURL, email string) string {
return am.storageSafeUserKey(caURL, email, "registration", ".json") return am.storageSafeUserKey(caURL, email, "registration", ".json")
} }
func (am *ACMEManager) storageKeyUserPrivateKey(caURL, email string) string { func (am *ACMEIssuer) storageKeyUserPrivateKey(caURL, email string) string {
return am.storageSafeUserKey(caURL, email, "private", ".key") return am.storageSafeUserKey(caURL, email, "private", ".key")
} }
// storageSafeUserKey returns a key for the given email, with the default // storageSafeUserKey returns a key for the given email, with the default
// filename, and the filename ending in the given extension. // filename, and the filename ending in the given extension.
func (am *ACMEManager) storageSafeUserKey(ca, email, defaultFilename, extension string) string { func (am *ACMEIssuer) storageSafeUserKey(ca, email, defaultFilename, extension string) string {
if email == "" { if email == "" {
email = emptyEmail email = emptyEmail
} }
@ -317,7 +317,7 @@ func (am *ACMEManager) storageSafeUserKey(ca, email, defaultFilename, extension
// emailUsername returns the username portion of an email address (part before // emailUsername returns the username portion of an email address (part before
// '@') or the original input if it can't find the "@" symbol. // '@') or the original input if it can't find the "@" symbol.
func (*ACMEManager) emailUsername(email string) string { func (*ACMEIssuer) emailUsername(email string) string {
at := strings.Index(email, "@") at := strings.Index(email, "@")
if at == -1 { if at == -1 {
return email return email
@ -331,7 +331,7 @@ func (*ACMEManager) emailUsername(email string) string {
// in storage. Since this is part of a complex sequence to get a user // in storage. Since this is part of a complex sequence to get a user
// account, errors here are discarded to simplify code flow in // account, errors here are discarded to simplify code flow in
// the caller, and errors are not important here anyway. // the caller, and errors are not important here anyway.
func (am *ACMEManager) mostRecentAccountEmail(ctx context.Context, caURL string) (string, bool) { func (am *ACMEIssuer) mostRecentAccountEmail(ctx context.Context, caURL string) (string, bool) {
accountList, err := am.config.Storage.List(ctx, am.storageKeyUsersPrefix(caURL), false) accountList, err := am.config.Storage.List(ctx, am.storageKeyUsersPrefix(caURL), false)
if err != nil || len(accountList) == 0 { if err != nil || len(accountList) == 0 {
return "", false return "", false

View File

@ -26,7 +26,7 @@ import (
) )
func TestNewAccount(t *testing.T) { func TestNewAccount(t *testing.T) {
am := &ACMEManager{CA: dummyCA} am := &ACMEIssuer{CA: dummyCA}
testConfig := &Config{ testConfig := &Config{
Issuers: []Issuer{am}, Issuers: []Issuer{am},
Storage: &FileStorage{Path: "./_testdata_tmp"}, Storage: &FileStorage{Path: "./_testdata_tmp"},
@ -53,7 +53,7 @@ func TestNewAccount(t *testing.T) {
func TestSaveAccount(t *testing.T) { func TestSaveAccount(t *testing.T) {
ctx := context.Background() ctx := context.Background()
am := &ACMEManager{CA: dummyCA} am := &ACMEIssuer{CA: dummyCA}
testConfig := &Config{ testConfig := &Config{
Issuers: []Issuer{am}, Issuers: []Issuer{am},
Storage: &FileStorage{Path: "./_testdata1_tmp"}, Storage: &FileStorage{Path: "./_testdata1_tmp"},
@ -88,7 +88,7 @@ func TestSaveAccount(t *testing.T) {
func TestGetAccountDoesNotAlreadyExist(t *testing.T) { func TestGetAccountDoesNotAlreadyExist(t *testing.T) {
ctx := context.Background() ctx := context.Background()
am := &ACMEManager{CA: dummyCA} am := &ACMEIssuer{CA: dummyCA}
testConfig := &Config{ testConfig := &Config{
Issuers: []Issuer{am}, Issuers: []Issuer{am},
Storage: &FileStorage{Path: "./_testdata_tmp"}, Storage: &FileStorage{Path: "./_testdata_tmp"},
@ -109,7 +109,7 @@ func TestGetAccountDoesNotAlreadyExist(t *testing.T) {
func TestGetAccountAlreadyExists(t *testing.T) { func TestGetAccountAlreadyExists(t *testing.T) {
ctx := context.Background() ctx := context.Background()
am := &ACMEManager{CA: dummyCA} am := &ACMEIssuer{CA: dummyCA}
testConfig := &Config{ testConfig := &Config{
Issuers: []Issuer{am}, Issuers: []Issuer{am},
Storage: &FileStorage{Path: "./_testdata2_tmp"}, Storage: &FileStorage{Path: "./_testdata2_tmp"},
@ -163,7 +163,7 @@ func TestGetEmailFromPackageDefault(t *testing.T) {
discoveredEmail = "" discoveredEmail = ""
}() }()
am := &ACMEManager{CA: dummyCA} am := &ACMEIssuer{CA: dummyCA}
testConfig := &Config{ testConfig := &Config{
Issuers: []Issuer{am}, Issuers: []Issuer{am},
Storage: &FileStorage{Path: "./_testdata2_tmp"}, Storage: &FileStorage{Path: "./_testdata2_tmp"},
@ -184,7 +184,7 @@ func TestGetEmailFromPackageDefault(t *testing.T) {
func TestGetEmailFromUserInput(t *testing.T) { func TestGetEmailFromUserInput(t *testing.T) {
ctx := context.Background() ctx := context.Background()
am := &ACMEManager{CA: dummyCA} am := &ACMEIssuer{CA: dummyCA}
testConfig := &Config{ testConfig := &Config{
Issuers: []Issuer{am}, Issuers: []Issuer{am},
Storage: &FileStorage{Path: "./_testdata3_tmp"}, Storage: &FileStorage{Path: "./_testdata3_tmp"},
@ -218,7 +218,7 @@ func TestGetEmailFromUserInput(t *testing.T) {
func TestGetEmailFromRecent(t *testing.T) { func TestGetEmailFromRecent(t *testing.T) {
ctx := context.Background() ctx := context.Background()
am := &ACMEManager{CA: dummyCA} am := &ACMEIssuer{CA: dummyCA}
testConfig := &Config{ testConfig := &Config{
Issuers: []Issuer{am}, Issuers: []Issuer{am},
Storage: &FileStorage{Path: "./_testdata4_tmp"}, Storage: &FileStorage{Path: "./_testdata4_tmp"},

View File

@ -39,9 +39,9 @@ func init() {
// acmeClient holds state necessary to perform ACME operations // acmeClient holds state necessary to perform ACME operations
// for certificate management with an ACME account. Call // for certificate management with an ACME account. Call
// ACMEManager.newACMEClientWithAccount() to get a valid one. // ACMEIssuer.newACMEClientWithAccount() to get a valid one.
type acmeClient struct { type acmeClient struct {
mgr *ACMEManager iss *ACMEIssuer
acmeClient *acmez.Client acmeClient *acmez.Client
account acme.Account account acme.Account
} }
@ -49,19 +49,19 @@ type acmeClient struct {
// newACMEClientWithAccount creates an ACME client ready to use with an account, including // newACMEClientWithAccount creates an ACME client ready to use with an account, including
// loading one from storage or registering a new account with the CA if necessary. If // loading one from storage or registering a new account with the CA if necessary. If
// useTestCA is true, am.TestCA will be used if set; otherwise, the primary CA will be used. // useTestCA is true, am.TestCA will be used if set; otherwise, the primary CA will be used.
func (am *ACMEManager) newACMEClientWithAccount(ctx context.Context, useTestCA, interactive bool) (*acmeClient, error) { func (iss *ACMEIssuer) newACMEClientWithAccount(ctx context.Context, useTestCA, interactive bool) (*acmeClient, error) {
// first, get underlying ACME client // first, get underlying ACME client
client, err := am.newACMEClient(useTestCA) client, err := iss.newACMEClient(useTestCA)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// look up or create the ACME account // look up or create the ACME account
var account acme.Account var account acme.Account
if am.AccountKeyPEM != "" { if iss.AccountKeyPEM != "" {
account, err = am.GetAccount(ctx, []byte(am.AccountKeyPEM)) account, err = iss.GetAccount(ctx, []byte(iss.AccountKeyPEM))
} else { } else {
account, err = am.getAccount(ctx, client.Directory, am.Email) account, err = iss.getAccount(ctx, client.Directory, iss.Email)
} }
if err != nil { if err != nil {
return nil, fmt.Errorf("getting ACME account: %v", err) return nil, fmt.Errorf("getting ACME account: %v", err)
@ -69,8 +69,8 @@ func (am *ACMEManager) newACMEClientWithAccount(ctx context.Context, useTestCA,
// register account if it is new // register account if it is new
if account.Status == "" { if account.Status == "" {
if am.NewAccountFunc != nil { if iss.NewAccountFunc != nil {
account, err = am.NewAccountFunc(ctx, am, account) account, err = iss.NewAccountFunc(ctx, iss, account)
if err != nil { if err != nil {
return nil, fmt.Errorf("account pre-registration callback: %v", err) return nil, fmt.Errorf("account pre-registration callback: %v", err)
} }
@ -78,7 +78,7 @@ func (am *ACMEManager) newACMEClientWithAccount(ctx context.Context, useTestCA,
// agree to terms // agree to terms
if interactive { if interactive {
if !am.Agreed { if !iss.Agreed {
var termsURL string var termsURL string
dir, err := client.GetDirectory(ctx) dir, err := client.GetDirectory(ctx)
if err != nil { if err != nil {
@ -88,8 +88,8 @@ func (am *ACMEManager) newACMEClientWithAccount(ctx context.Context, useTestCA,
termsURL = dir.Meta.TermsOfService termsURL = dir.Meta.TermsOfService
} }
if termsURL != "" { if termsURL != "" {
am.Agreed = am.askUserAgreement(termsURL) iss.Agreed = iss.askUserAgreement(termsURL)
if !am.Agreed { if !iss.Agreed {
return nil, fmt.Errorf("user must agree to CA terms") return nil, fmt.Errorf("user must agree to CA terms")
} }
} }
@ -97,13 +97,13 @@ func (am *ACMEManager) newACMEClientWithAccount(ctx context.Context, useTestCA,
} else { } else {
// can't prompt a user who isn't there; they should // can't prompt a user who isn't there; they should
// have reviewed the terms beforehand // have reviewed the terms beforehand
am.Agreed = true iss.Agreed = true
} }
account.TermsOfServiceAgreed = am.Agreed account.TermsOfServiceAgreed = iss.Agreed
// associate account with external binding, if configured // associate account with external binding, if configured
if am.ExternalAccount != nil { if iss.ExternalAccount != nil {
err := account.SetExternalAccountBinding(ctx, client.Client, *am.ExternalAccount) err := account.SetExternalAccountBinding(ctx, client.Client, *iss.ExternalAccount)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -116,14 +116,14 @@ func (am *ACMEManager) newACMEClientWithAccount(ctx context.Context, useTestCA,
} }
// persist the account to storage // persist the account to storage
err = am.saveAccount(ctx, client.Directory, account) err = iss.saveAccount(ctx, client.Directory, account)
if err != nil { if err != nil {
return nil, fmt.Errorf("could not save account %v: %v", account.Contact, err) return nil, fmt.Errorf("could not save account %v: %v", account.Contact, err)
} }
} }
c := &acmeClient{ c := &acmeClient{
mgr: am, iss: iss,
acmeClient: client, acmeClient: client,
account: account, account: account,
} }
@ -134,19 +134,19 @@ func (am *ACMEManager) newACMEClientWithAccount(ctx context.Context, useTestCA,
// newACMEClient creates a new underlying ACME client using the settings in am, // newACMEClient creates a new underlying ACME client using the settings in am,
// independent of any particular ACME account. If useTestCA is true, am.TestCA // independent of any particular ACME account. If useTestCA is true, am.TestCA
// will be used if it is set; otherwise, the primary CA will be used. // will be used if it is set; otherwise, the primary CA will be used.
func (am *ACMEManager) newACMEClient(useTestCA bool) (*acmez.Client, error) { func (iss *ACMEIssuer) newACMEClient(useTestCA bool) (*acmez.Client, error) {
// ensure defaults are filled in // ensure defaults are filled in
var caURL string var caURL string
if useTestCA { if useTestCA {
caURL = am.TestCA caURL = iss.TestCA
} }
if caURL == "" { if caURL == "" {
caURL = am.CA caURL = iss.CA
} }
if caURL == "" { if caURL == "" {
caURL = DefaultACME.CA caURL = DefaultACME.CA
} }
certObtainTimeout := am.CertObtainTimeout certObtainTimeout := iss.CertObtainTimeout
if certObtainTimeout == 0 { if certObtainTimeout == 0 {
certObtainTimeout = DefaultACME.CertObtainTimeout certObtainTimeout = DefaultACME.CertObtainTimeout
} }
@ -168,31 +168,31 @@ func (am *ACMEManager) newACMEClient(useTestCA bool) (*acmez.Client, error) {
Timeout: 30 * time.Second, Timeout: 30 * time.Second,
KeepAlive: 2 * time.Minute, KeepAlive: 2 * time.Minute,
} }
if am.Resolver != "" { if iss.Resolver != "" {
dialer.Resolver = &net.Resolver{ dialer.Resolver = &net.Resolver{
PreferGo: true, PreferGo: true,
Dial: func(ctx context.Context, network, _ string) (net.Conn, error) { Dial: func(ctx context.Context, network, _ string) (net.Conn, error) {
return (&net.Dialer{ return (&net.Dialer{
Timeout: 15 * time.Second, Timeout: 15 * time.Second,
}).DialContext(ctx, network, am.Resolver) }).DialContext(ctx, network, iss.Resolver)
}, },
} }
} }
// TODO: we could potentially reuse the HTTP transport and client // TODO: we could potentially reuse the HTTP transport and client
hc := am.httpClient // TODO: is this racey? hc := iss.httpClient // TODO: is this racey?
if am.httpClient == nil { if iss.httpClient == nil {
transport := &http.Transport{ transport := &http.Transport{
Proxy: http.ProxyFromEnvironment, Proxy: http.ProxyFromEnvironment,
DialContext: dialer.DialContext, DialContext: dialer.DialContext,
TLSHandshakeTimeout: 15 * time.Second, TLSHandshakeTimeout: 30 * time.Second, // increase to 30s requested in #175
ResponseHeaderTimeout: 15 * time.Second, ResponseHeaderTimeout: 30 * time.Second, // increase to 30s requested in #175
ExpectContinueTimeout: 2 * time.Second, ExpectContinueTimeout: 2 * time.Second,
ForceAttemptHTTP2: true, ForceAttemptHTTP2: true,
} }
if am.TrustedRoots != nil { if iss.TrustedRoots != nil {
transport.TLSClientConfig = &tls.Config{ transport.TLSClientConfig = &tls.Config{
RootCAs: am.TrustedRoots, RootCAs: iss.TrustedRoots,
} }
} }
@ -201,7 +201,7 @@ func (am *ACMEManager) newACMEClient(useTestCA bool) (*acmez.Client, error) {
Timeout: HTTPTimeout, Timeout: HTTPTimeout,
} }
am.httpClient = hc iss.httpClient = hc
} }
client := &acmez.Client{ client := &acmez.Client{
@ -213,55 +213,55 @@ func (am *ACMEManager) newACMEClient(useTestCA bool) (*acmez.Client, error) {
}, },
ChallengeSolvers: make(map[string]acmez.Solver), ChallengeSolvers: make(map[string]acmez.Solver),
} }
if am.Logger != nil { if iss.Logger != nil {
l := am.Logger.Named("acme_client") l := iss.Logger.Named("acme_client")
client.Client.Logger, client.Logger = l, l client.Client.Logger, client.Logger = l, l
} }
// configure challenges (most of the time, DNS challenge is // configure challenges (most of the time, DNS challenge is
// exclusive of other ones because it is usually only used // exclusive of other ones because it is usually only used
// in situations where the default challenges would fail) // in situations where the default challenges would fail)
if am.DNS01Solver == nil { if iss.DNS01Solver == nil {
// enable HTTP-01 challenge // enable HTTP-01 challenge
if !am.DisableHTTPChallenge { if !iss.DisableHTTPChallenge {
useHTTPPort := HTTPChallengePort useHTTPPort := HTTPChallengePort
if HTTPPort > 0 && HTTPPort != HTTPChallengePort { if HTTPPort > 0 && HTTPPort != HTTPChallengePort {
useHTTPPort = HTTPPort useHTTPPort = HTTPPort
} }
if am.AltHTTPPort > 0 { if iss.AltHTTPPort > 0 {
useHTTPPort = am.AltHTTPPort useHTTPPort = iss.AltHTTPPort
} }
client.ChallengeSolvers[acme.ChallengeTypeHTTP01] = distributedSolver{ client.ChallengeSolvers[acme.ChallengeTypeHTTP01] = distributedSolver{
storage: am.config.Storage, storage: iss.config.Storage,
storageKeyIssuerPrefix: am.storageKeyCAPrefix(client.Directory), storageKeyIssuerPrefix: iss.storageKeyCAPrefix(client.Directory),
solver: &httpSolver{ solver: &httpSolver{
acmeManager: am, acmeIssuer: iss,
address: net.JoinHostPort(am.ListenHost, strconv.Itoa(useHTTPPort)), address: net.JoinHostPort(iss.ListenHost, strconv.Itoa(useHTTPPort)),
}, },
} }
} }
// enable TLS-ALPN-01 challenge // enable TLS-ALPN-01 challenge
if !am.DisableTLSALPNChallenge { if !iss.DisableTLSALPNChallenge {
useTLSALPNPort := TLSALPNChallengePort useTLSALPNPort := TLSALPNChallengePort
if HTTPSPort > 0 && HTTPSPort != TLSALPNChallengePort { if HTTPSPort > 0 && HTTPSPort != TLSALPNChallengePort {
useTLSALPNPort = HTTPSPort useTLSALPNPort = HTTPSPort
} }
if am.AltTLSALPNPort > 0 { if iss.AltTLSALPNPort > 0 {
useTLSALPNPort = am.AltTLSALPNPort useTLSALPNPort = iss.AltTLSALPNPort
} }
client.ChallengeSolvers[acme.ChallengeTypeTLSALPN01] = distributedSolver{ client.ChallengeSolvers[acme.ChallengeTypeTLSALPN01] = distributedSolver{
storage: am.config.Storage, storage: iss.config.Storage,
storageKeyIssuerPrefix: am.storageKeyCAPrefix(client.Directory), storageKeyIssuerPrefix: iss.storageKeyCAPrefix(client.Directory),
solver: &tlsALPNSolver{ solver: &tlsALPNSolver{
config: am.config, config: iss.config,
address: net.JoinHostPort(am.ListenHost, strconv.Itoa(useTLSALPNPort)), address: net.JoinHostPort(iss.ListenHost, strconv.Itoa(useTLSALPNPort)),
}, },
} }
} }
} else { } else {
// use DNS challenge exclusively // use DNS challenge exclusively
client.ChallengeSolvers[acme.ChallengeTypeDNS01] = am.DNS01Solver client.ChallengeSolvers[acme.ChallengeTypeDNS01] = iss.DNS01Solver
} }
// wrap solvers in our wrapper so that we can keep track of challenge // wrap solvers in our wrapper so that we can keep track of challenge
@ -288,7 +288,7 @@ func (am *ACMEManager) newACMEClient(useTestCA bool) (*acmez.Client, error) {
func (c *acmeClient) throttle(ctx context.Context, names []string) error { func (c *acmeClient) throttle(ctx context.Context, names []string) error {
// throttling is scoped to CA + account email // throttling is scoped to CA + account email
rateLimiterKey := c.acmeClient.Directory + "," + c.mgr.Email rateLimiterKey := c.acmeClient.Directory + "," + c.iss.Email
rateLimitersMu.Lock() rateLimitersMu.Lock()
rl, ok := rateLimiters[rateLimiterKey] rl, ok := rateLimiters[rateLimiterKey]
if !ok { if !ok {
@ -297,29 +297,29 @@ func (c *acmeClient) throttle(ctx context.Context, names []string) error {
// TODO: stop rate limiter when it is garbage-collected... // TODO: stop rate limiter when it is garbage-collected...
} }
rateLimitersMu.Unlock() rateLimitersMu.Unlock()
if c.mgr.Logger != nil { if c.iss.Logger != nil {
c.mgr.Logger.Info("waiting on internal rate limiter", c.iss.Logger.Info("waiting on internal rate limiter",
zap.Strings("identifiers", names), zap.Strings("identifiers", names),
zap.String("ca", c.acmeClient.Directory), zap.String("ca", c.acmeClient.Directory),
zap.String("account", c.mgr.Email), zap.String("account", c.iss.Email),
) )
} }
err := rl.Wait(ctx) err := rl.Wait(ctx)
if err != nil { if err != nil {
return err return err
} }
if c.mgr.Logger != nil { if c.iss.Logger != nil {
c.mgr.Logger.Info("done waiting on internal rate limiter", c.iss.Logger.Info("done waiting on internal rate limiter",
zap.Strings("identifiers", names), zap.Strings("identifiers", names),
zap.String("ca", c.acmeClient.Directory), zap.String("ca", c.acmeClient.Directory),
zap.String("account", c.mgr.Email), zap.String("account", c.iss.Email),
) )
} }
return nil return nil
} }
func (c *acmeClient) usingTestCA() bool { func (c *acmeClient) usingTestCA() bool {
return c.mgr.TestCA != "" && c.acmeClient.Directory == c.mgr.TestCA return c.iss.TestCA != "" && c.acmeClient.Directory == c.iss.TestCA
} }
func (c *acmeClient) revoke(ctx context.Context, cert *x509.Certificate, reason int) error { func (c *acmeClient) revoke(ctx context.Context, cert *x509.Certificate, reason int) error {

View File

@ -16,14 +16,14 @@ import (
"go.uber.org/zap" "go.uber.org/zap"
) )
// ACMEManager gets certificates using ACME. It implements the PreChecker, // ACMEIssuer gets certificates using ACME. It implements the PreChecker,
// Issuer, and Revoker interfaces. // Issuer, and Revoker interfaces.
// //
// It is NOT VALID to use an ACMEManager without calling NewACMEManager(). // It is NOT VALID to use an ACMEIssuer without calling NewACMEIssuer().
// It fills in any default values from DefaultACME as well as setting up // It fills in any default values from DefaultACME as well as setting up
// internal state that is necessary for valid use. Always call // internal state that is necessary for valid use. Always call
// NewACMEManager() to get a valid ACMEManager value. // NewACMEIssuer() to get a valid ACMEIssuer value.
type ACMEManager struct { type ACMEIssuer struct {
// The endpoint of the directory for the ACME // The endpoint of the directory for the ACME
// CA we are to use // CA we are to use
CA string CA string
@ -99,9 +99,9 @@ type ACMEManager struct {
// Callback function that is called before a // Callback function that is called before a
// new ACME account is registered with the CA; // new ACME account is registered with the CA;
// it allows for last-second config changes // it allows for last-second config changes
// of the ACMEManager and the Account. // of the ACMEIssuer and the Account.
// (TODO: this feature is still EXPERIMENTAL and subject to change) // (TODO: this feature is still EXPERIMENTAL and subject to change)
NewAccountFunc func(context.Context, *ACMEManager, acme.Account) (acme.Account, error) NewAccountFunc func(context.Context, *ACMEIssuer, acme.Account) (acme.Account, error)
// Preferences for selecting alternate // Preferences for selecting alternate
// certificate chains // certificate chains
@ -114,17 +114,17 @@ type ACMEManager struct {
httpClient *http.Client httpClient *http.Client
} }
// NewACMEManager constructs a valid ACMEManager based on a template // NewACMEIssuer constructs a valid ACMEIssuer based on a template
// configuration; any empty values will be filled in by defaults in // configuration; any empty values will be filled in by defaults in
// DefaultACME, and if any required values are still empty, sensible // DefaultACME, and if any required values are still empty, sensible
// defaults will be used. // defaults will be used.
// //
// Typically, you'll create the Config first with New() or NewDefault(), // Typically, you'll create the Config first with New() or NewDefault(),
// then call NewACMEManager(), then assign the return value to the Issuers // then call NewACMEIssuer(), then assign the return value to the Issuers
// field of the Config. // field of the Config.
func NewACMEManager(cfg *Config, template ACMEManager) *ACMEManager { func NewACMEIssuer(cfg *Config, template ACMEIssuer) *ACMEIssuer {
if cfg == nil { if cfg == nil {
panic("cannot make valid ACMEManager without an associated CertMagic config") panic("cannot make valid ACMEIssuer without an associated CertMagic config")
} }
if template.CA == "" { if template.CA == "" {
template.CA = DefaultACME.CA template.CA = DefaultACME.CA
@ -187,11 +187,11 @@ func NewACMEManager(cfg *Config, template ACMEManager) *ACMEManager {
// IssuerKey returns the unique issuer key for the // IssuerKey returns the unique issuer key for the
// confgured CA endpoint. // confgured CA endpoint.
func (am *ACMEManager) IssuerKey() string { func (am *ACMEIssuer) IssuerKey() string {
return am.issuerKey(am.CA) return am.issuerKey(am.CA)
} }
func (*ACMEManager) issuerKey(ca string) string { func (*ACMEIssuer) issuerKey(ca string) string {
key := ca key := ca
if caURL, err := url.Parse(key); err == nil { if caURL, err := url.Parse(key); err == nil {
key = caURL.Host key = caURL.Host
@ -217,7 +217,7 @@ func (*ACMEManager) issuerKey(ca string) string {
// renewing a certificate with ACME, and returns whether this // renewing a certificate with ACME, and returns whether this
// batch is eligible for certificates if using Let's Encrypt. // batch is eligible for certificates if using Let's Encrypt.
// It also ensures that an email address is available. // It also ensures that an email address is available.
func (am *ACMEManager) PreCheck(ctx context.Context, names []string, interactive bool) error { func (am *ACMEIssuer) PreCheck(ctx context.Context, names []string, interactive bool) error {
publicCA := strings.Contains(am.CA, "api.letsencrypt.org") || strings.Contains(am.CA, "acme.zerossl.com") publicCA := strings.Contains(am.CA, "api.letsencrypt.org") || strings.Contains(am.CA, "acme.zerossl.com")
if publicCA { if publicCA {
for _, name := range names { for _, name := range names {
@ -231,9 +231,9 @@ func (am *ACMEManager) PreCheck(ctx context.Context, names []string, interactive
// Issue implements the Issuer interface. It obtains a certificate for the given csr using // Issue implements the Issuer interface. It obtains a certificate for the given csr using
// the ACME configuration am. // the ACME configuration am.
func (am *ACMEManager) Issue(ctx context.Context, csr *x509.CertificateRequest) (*IssuedCertificate, error) { func (am *ACMEIssuer) Issue(ctx context.Context, csr *x509.CertificateRequest) (*IssuedCertificate, error) {
if am.config == nil { if am.config == nil {
panic("missing config pointer (must use NewACMEManager)") panic("missing config pointer (must use NewACMEIssuer)")
} }
var isRetry bool var isRetry bool
@ -297,7 +297,7 @@ func (am *ACMEManager) Issue(ctx context.Context, csr *x509.CertificateRequest)
return cert, err return cert, err
} }
func (am *ACMEManager) doIssue(ctx context.Context, csr *x509.CertificateRequest, useTestCA bool) (*IssuedCertificate, bool, error) { func (am *ACMEIssuer) doIssue(ctx context.Context, csr *x509.CertificateRequest, useTestCA bool) (*IssuedCertificate, bool, error) {
client, err := am.newACMEClientWithAccount(ctx, useTestCA, false) client, err := am.newACMEClientWithAccount(ctx, useTestCA, false)
if err != nil { if err != nil {
return nil, false, err return nil, false, err
@ -333,7 +333,7 @@ func (am *ACMEManager) doIssue(ctx context.Context, csr *x509.CertificateRequest
// selectPreferredChain sorts and then filters the certificate chains to find the optimal // selectPreferredChain sorts and then filters the certificate chains to find the optimal
// chain preferred by the client. If there's only one chain, that is returned without any // chain preferred by the client. If there's only one chain, that is returned without any
// processing. If there are no matches, the first chain is returned. // processing. If there are no matches, the first chain is returned.
func (am *ACMEManager) selectPreferredChain(certChains []acme.Certificate) acme.Certificate { func (am *ACMEIssuer) selectPreferredChain(certChains []acme.Certificate) acme.Certificate {
if len(certChains) == 1 { if len(certChains) == 1 {
if am.Logger != nil && (len(am.PreferredChains.AnyCommonName) > 0 || len(am.PreferredChains.RootCommonName) > 0) { if am.Logger != nil && (len(am.PreferredChains.AnyCommonName) > 0 || len(am.PreferredChains.RootCommonName) > 0) {
am.Logger.Debug("there is only one chain offered; selecting it regardless of preferences", am.Logger.Debug("there is only one chain offered; selecting it regardless of preferences",
@ -411,7 +411,7 @@ func (am *ACMEManager) selectPreferredChain(certChains []acme.Certificate) acme.
} }
// Revoke implements the Revoker interface. It revokes the given certificate. // Revoke implements the Revoker interface. It revokes the given certificate.
func (am *ACMEManager) Revoke(ctx context.Context, cert CertificateResource, reason int) error { func (am *ACMEIssuer) Revoke(ctx context.Context, cert CertificateResource, reason int) error {
client, err := am.newACMEClientWithAccount(ctx, false, false) client, err := am.newACMEClientWithAccount(ctx, false, false)
if err != nil { if err != nil {
return err return err
@ -441,9 +441,9 @@ type ChainPreference struct {
AnyCommonName []string AnyCommonName []string
} }
// DefaultACME specifies default settings to use for ACMEManagers. // DefaultACME specifies default settings to use for ACMEIssuers.
// Using this value is optional but can be convenient. // Using this value is optional but can be convenient.
var DefaultACME = ACMEManager{ var DefaultACME = ACMEIssuer{
CA: LetsEncryptProductionCA, CA: LetsEncryptProductionCA,
TestCA: LetsEncryptStagingCA, TestCA: LetsEncryptStagingCA,
} }
@ -460,7 +460,7 @@ const prefixACME = "acme"
// Interface guards // Interface guards
var ( var (
_ PreChecker = (*ACMEManager)(nil) _ PreChecker = (*ACMEIssuer)(nil)
_ Issuer = (*ACMEManager)(nil) _ Issuer = (*ACMEIssuer)(nil)
_ Revoker = (*ACMEManager)(nil) _ Revoker = (*ACMEIssuer)(nil)
) )

View File

@ -129,7 +129,7 @@ func HTTPS(domainNames []string, mux http.Handler) error {
BaseContext: func(listener net.Listener) context.Context { return ctx }, BaseContext: func(listener net.Listener) context.Context { return ctx },
} }
if len(cfg.Issuers) > 0 { if len(cfg.Issuers) > 0 {
if am, ok := cfg.Issuers[0].(*ACMEManager); ok { if am, ok := cfg.Issuers[0].(*ACMEIssuer); ok {
httpServer.Handler = am.HTTPChallengeHandler(http.HandlerFunc(httpRedirectHandler)) httpServer.Handler = am.HTTPChallengeHandler(http.HandlerFunc(httpRedirectHandler))
} }
} }
@ -378,12 +378,12 @@ type Revoker interface {
Revoke(ctx context.Context, cert CertificateResource, reason int) error Revoke(ctx context.Context, cert CertificateResource, reason int) error
} }
// CertificateManager is a type that manages certificates (keeps them renewed) // Manager is a type that manages certificates (keeps them renewed) such
// such that we can get certificates during TLS handshakes to immediately serve // that we can get certificates during TLS handshakes to immediately serve
// to clients. // to clients.
// //
// TODO: This is an EXPERIMENTAL API. It is subject to change/removal. // TODO: This is an EXPERIMENTAL API. It is subject to change/removal.
type CertificateManager interface { type Manager interface {
// GetCertificate returns the certificate to use to complete the handshake. // GetCertificate returns the certificate to use to complete the handshake.
// Since this is called during every TLS handshake, it must be very fast and not block. // Since this is called during every TLS handshake, it must be very fast and not block.
// Returning (nil, nil) is valid and is simply treated as a no-op. // Returning (nil, nil) is valid and is simply treated as a no-op.

View File

@ -74,7 +74,7 @@ type Config struct {
MustStaple bool MustStaple bool
// Sources for getting new, managed certificates; // Sources for getting new, managed certificates;
// the default Issuer is ACMEManager. If multiple // the default Issuer is ACMEIssuer. If multiple
// issuers are specified, they will be tried in // issuers are specified, they will be tried in
// turn until one succeeds. // turn until one succeeds.
Issuers []Issuer Issuers []Issuer
@ -86,7 +86,7 @@ type Config struct {
// the in-memory cache. // the in-memory cache.
// //
// TODO: EXPERIMENTAL: subject to change and/or removal. // TODO: EXPERIMENTAL: subject to change and/or removal.
Managers []CertificateManager Managers []Manager
// The source of new private keys for certificates; // The source of new private keys for certificates;
// the default KeySource is StandardKeyGenerator. // the default KeySource is StandardKeyGenerator.
@ -218,7 +218,7 @@ func newWithCache(certCache *Cache, cfg Config) *Config {
cfg.Issuers = Default.Issuers cfg.Issuers = Default.Issuers
if len(cfg.Issuers) == 0 { if len(cfg.Issuers) == 0 {
// at least one issuer is absolutely required // at least one issuer is absolutely required
cfg.Issuers = []Issuer{NewACMEManager(&cfg, DefaultACME)} cfg.Issuers = []Issuer{NewACMEIssuer(&cfg, DefaultACME)}
} }
} }

View File

@ -26,7 +26,7 @@ import (
func TestSaveCertResource(t *testing.T) { func TestSaveCertResource(t *testing.T) {
ctx := context.Background() ctx := context.Background()
am := &ACMEManager{CA: "https://example.com/acme/directory"} am := &ACMEIssuer{CA: "https://example.com/acme/directory"}
testConfig := &Config{ testConfig := &Config{
Issuers: []Issuer{am}, Issuers: []Issuer{am},
Storage: &FileStorage{Path: "./_testdata_tmp"}, Storage: &FileStorage{Path: "./_testdata_tmp"},

View File

@ -258,7 +258,7 @@ func (cfg *Config) getCertDuringHandshake(hello *tls.ClientHelloInfo, loadIfNece
return cert, nil return cert, nil
} }
// If an external CertificateManager is configured, try to get it from them. // If an external Manager is configured, try to get it from them.
// Only continue to use our own logic if it returns empty+nil. // Only continue to use our own logic if it returns empty+nil.
externalCert, err := cfg.getCertFromAnyCertManager(ctx, hello, log) externalCert, err := cfg.getCertFromAnyCertManager(ctx, hello, log)
if err != nil { if err != nil {

View File

@ -28,7 +28,7 @@ import (
// the challenge state is stored between initiation and solution. // the challenge state is stored between initiation and solution.
// //
// If a request is not an ACME HTTP challenge, h will be invoked. // If a request is not an ACME HTTP challenge, h will be invoked.
func (am *ACMEManager) HTTPChallengeHandler(h http.Handler) http.Handler { func (am *ACMEIssuer) HTTPChallengeHandler(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if am.HandleHTTPChallenge(w, r) { if am.HandleHTTPChallenge(w, r) {
return return
@ -49,7 +49,7 @@ func (am *ACMEManager) HTTPChallengeHandler(h http.Handler) http.Handler {
// It returns true if it handled the request; if so, the response has // It returns true if it handled the request; if so, the response has
// already been written. If false is returned, this call was a no-op and // already been written. If false is returned, this call was a no-op and
// the request has not been handled. // the request has not been handled.
func (am *ACMEManager) HandleHTTPChallenge(w http.ResponseWriter, r *http.Request) bool { func (am *ACMEIssuer) HandleHTTPChallenge(w http.ResponseWriter, r *http.Request) bool {
if am == nil { if am == nil {
return false return false
} }
@ -66,7 +66,7 @@ func (am *ACMEManager) HandleHTTPChallenge(w http.ResponseWriter, r *http.Reques
// request was initiated by this or another instance which uses the // request was initiated by this or another instance which uses the
// same storage as am does, and attempts to complete the challenge for // same storage as am does, and attempts to complete the challenge for
// it. It returns true if the request was handled; false otherwise. // it. It returns true if the request was handled; false otherwise.
func (am *ACMEManager) distributedHTTPChallengeSolver(w http.ResponseWriter, r *http.Request) bool { func (am *ACMEIssuer) distributedHTTPChallengeSolver(w http.ResponseWriter, r *http.Request) bool {
if am == nil { if am == nil {
return false return false
} }

View File

@ -22,7 +22,7 @@ import (
) )
func TestHTTPChallengeHandlerNoOp(t *testing.T) { func TestHTTPChallengeHandlerNoOp(t *testing.T) {
am := &ACMEManager{CA: "https://example.com/acme/directory"} am := &ACMEIssuer{CA: "https://example.com/acme/directory"}
testConfig := &Config{ testConfig := &Config{
Issuers: []Issuer{am}, Issuers: []Issuer{am},
Storage: &FileStorage{Path: "./_testdata_tmp"}, Storage: &FileStorage{Path: "./_testdata_tmp"},

View File

@ -46,9 +46,9 @@ import (
// can access the keyAuth material is by loading it // can access the keyAuth material is by loading it
// from storage, which is done by distributedSolver. // from storage, which is done by distributedSolver.
type httpSolver struct { type httpSolver struct {
closed int32 // accessed atomically closed int32 // accessed atomically
acmeManager *ACMEManager acmeIssuer *ACMEIssuer
address string address string
} }
// Present starts an HTTP server if none is already listening on s.address. // Present starts an HTTP server if none is already listening on s.address.
@ -88,7 +88,7 @@ func (s *httpSolver) serve(ctx context.Context, si *solverInfo) {
}() }()
defer close(si.done) defer close(si.done)
httpServer := &http.Server{ httpServer := &http.Server{
Handler: s.acmeManager.HTTPChallengeHandler(http.NewServeMux()), Handler: s.acmeIssuer.HTTPChallengeHandler(http.NewServeMux()),
BaseContext: func(listener net.Listener) context.Context { return ctx }, BaseContext: func(listener net.Listener) context.Context { return ctx },
} }
httpServer.SetKeepAlivesEnabled(false) httpServer.SetKeepAlivesEnabled(false)
@ -476,7 +476,7 @@ type distributedSolver struct {
// Present invokes the underlying solver's Present method // Present invokes the underlying solver's Present method
// and also stores domain, token, and keyAuth to the storage // and also stores domain, token, and keyAuth to the storage
// backing the certificate cache of dhs.acmeManager. // backing the certificate cache of dhs.acmeIssuer.
func (dhs distributedSolver) Present(ctx context.Context, chal acme.Challenge) error { func (dhs distributedSolver) Present(ctx context.Context, chal acme.Challenge) error {
infoBytes, err := json.Marshal(chal) infoBytes, err := json.Marshal(chal)
if err != nil { if err != nil {

View File

@ -20,7 +20,7 @@ import (
) )
func TestPrefixAndKeyBuilders(t *testing.T) { func TestPrefixAndKeyBuilders(t *testing.T) {
am := &ACMEManager{CA: "https://example.com/acme-ca/directory"} am := &ACMEIssuer{CA: "https://example.com/acme-ca/directory"}
base := path.Join("certificates", "example.com-acme-ca-directory") base := path.Join("certificates", "example.com-acme-ca-directory")