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.
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
})
myACME := certmagic.NewACMEManager(magic, certmagic.ACMEManager{
myACME := certmagic.NewACMEIssuer(magic, certmagic.ACMEIssuer{
CA: certmagic.LetsEncryptStagingCA,
Email: "you@yours.com",
Agreed: true,
@ -344,7 +344,7 @@ If wrapping your handler is not a good solution, try this inside your `ServeHTTP
```go
magic := certmagic.NewDefault()
myACME := certmagic.NewACMEManager(magic, certmagic.DefaultACME)
myACME := certmagic.NewACMEIssuer(magic, certmagic.DefaultACME)
func ServeHTTP(w http.ResponseWriter, req *http.Request) {
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.
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
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

View File

@ -37,7 +37,7 @@ import (
// getAccount either loads or creates a new account, depending on if
// 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)
if errors.Is(err, fs.ErrNotExist) {
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.
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))
if err != nil {
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
// 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
if email != "" {
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.
// 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.
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)
if errors.Is(err, fs.ErrNotExist) {
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.
// If it does not exist, an error of type fs.ErrNotExist is returned. This is not very efficient
// 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)
if err != nil {
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.
// 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)
if err != nil {
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.
// 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")
if err != nil {
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 user for input. If allowPrompts is false, the user
// 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
// 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
// will also be set to true, since continuing through the
// 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
reader := bufio.NewReader(stdin)
am.promptUserAgreement("")
@ -246,7 +246,7 @@ func (am *ACMEManager) promptUserForEmail() (string, error) {
// promptUserAgreement simply outputs the standard user
// agreement prompt with the given agreement URL.
// 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.
By continuing, you agree to the CA's terms of service`
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
// at the given agreement URL via stdin. It returns whether the
// user agreed or not.
func (am *ACMEManager) askUserAgreement(agreementURL string) bool {
func (am *ACMEIssuer) askUserAgreement(agreementURL string) bool {
am.promptUserAgreement(agreementURL)
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))
}
func (am *ACMEManager) storageKeyCAPrefix(caURL string) string {
func (am *ACMEIssuer) storageKeyCAPrefix(caURL string) string {
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")
}
func (am *ACMEManager) storageKeyUserPrefix(caURL, email string) string {
func (am *ACMEIssuer) storageKeyUserPrefix(caURL, email string) string {
if email == "" {
email = emptyEmail
}
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")
}
func (am *ACMEManager) storageKeyUserPrivateKey(caURL, email string) string {
func (am *ACMEIssuer) storageKeyUserPrivateKey(caURL, email string) string {
return am.storageSafeUserKey(caURL, email, "private", ".key")
}
// storageSafeUserKey returns a key for the given email, with the default
// 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 == "" {
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
// '@') 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, "@")
if at == -1 {
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
// account, errors here are discarded to simplify code flow in
// 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)
if err != nil || len(accountList) == 0 {
return "", false

View File

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

View File

@ -39,9 +39,9 @@ func init() {
// acmeClient holds state necessary to perform ACME operations
// 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 {
mgr *ACMEManager
iss *ACMEIssuer
acmeClient *acmez.Client
account acme.Account
}
@ -49,19 +49,19 @@ type acmeClient struct {
// 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
// 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
client, err := am.newACMEClient(useTestCA)
client, err := iss.newACMEClient(useTestCA)
if err != nil {
return nil, err
}
// look up or create the ACME account
var account acme.Account
if am.AccountKeyPEM != "" {
account, err = am.GetAccount(ctx, []byte(am.AccountKeyPEM))
if iss.AccountKeyPEM != "" {
account, err = iss.GetAccount(ctx, []byte(iss.AccountKeyPEM))
} else {
account, err = am.getAccount(ctx, client.Directory, am.Email)
account, err = iss.getAccount(ctx, client.Directory, iss.Email)
}
if err != nil {
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
if account.Status == "" {
if am.NewAccountFunc != nil {
account, err = am.NewAccountFunc(ctx, am, account)
if iss.NewAccountFunc != nil {
account, err = iss.NewAccountFunc(ctx, iss, account)
if err != nil {
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
if interactive {
if !am.Agreed {
if !iss.Agreed {
var termsURL string
dir, err := client.GetDirectory(ctx)
if err != nil {
@ -88,8 +88,8 @@ func (am *ACMEManager) newACMEClientWithAccount(ctx context.Context, useTestCA,
termsURL = dir.Meta.TermsOfService
}
if termsURL != "" {
am.Agreed = am.askUserAgreement(termsURL)
if !am.Agreed {
iss.Agreed = iss.askUserAgreement(termsURL)
if !iss.Agreed {
return nil, fmt.Errorf("user must agree to CA terms")
}
}
@ -97,13 +97,13 @@ func (am *ACMEManager) newACMEClientWithAccount(ctx context.Context, useTestCA,
} else {
// can't prompt a user who isn't there; they should
// 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
if am.ExternalAccount != nil {
err := account.SetExternalAccountBinding(ctx, client.Client, *am.ExternalAccount)
if iss.ExternalAccount != nil {
err := account.SetExternalAccountBinding(ctx, client.Client, *iss.ExternalAccount)
if err != nil {
return nil, err
}
@ -116,14 +116,14 @@ func (am *ACMEManager) newACMEClientWithAccount(ctx context.Context, useTestCA,
}
// persist the account to storage
err = am.saveAccount(ctx, client.Directory, account)
err = iss.saveAccount(ctx, client.Directory, account)
if err != nil {
return nil, fmt.Errorf("could not save account %v: %v", account.Contact, err)
}
}
c := &acmeClient{
mgr: am,
iss: iss,
acmeClient: client,
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,
// 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.
func (am *ACMEManager) newACMEClient(useTestCA bool) (*acmez.Client, error) {
func (iss *ACMEIssuer) newACMEClient(useTestCA bool) (*acmez.Client, error) {
// ensure defaults are filled in
var caURL string
if useTestCA {
caURL = am.TestCA
caURL = iss.TestCA
}
if caURL == "" {
caURL = am.CA
caURL = iss.CA
}
if caURL == "" {
caURL = DefaultACME.CA
}
certObtainTimeout := am.CertObtainTimeout
certObtainTimeout := iss.CertObtainTimeout
if certObtainTimeout == 0 {
certObtainTimeout = DefaultACME.CertObtainTimeout
}
@ -168,31 +168,31 @@ func (am *ACMEManager) newACMEClient(useTestCA bool) (*acmez.Client, error) {
Timeout: 30 * time.Second,
KeepAlive: 2 * time.Minute,
}
if am.Resolver != "" {
if iss.Resolver != "" {
dialer.Resolver = &net.Resolver{
PreferGo: true,
Dial: func(ctx context.Context, network, _ string) (net.Conn, error) {
return (&net.Dialer{
Timeout: 15 * time.Second,
}).DialContext(ctx, network, am.Resolver)
}).DialContext(ctx, network, iss.Resolver)
},
}
}
// TODO: we could potentially reuse the HTTP transport and client
hc := am.httpClient // TODO: is this racey?
if am.httpClient == nil {
hc := iss.httpClient // TODO: is this racey?
if iss.httpClient == nil {
transport := &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: dialer.DialContext,
TLSHandshakeTimeout: 15 * time.Second,
ResponseHeaderTimeout: 15 * time.Second,
TLSHandshakeTimeout: 30 * time.Second, // increase to 30s requested in #175
ResponseHeaderTimeout: 30 * time.Second, // increase to 30s requested in #175
ExpectContinueTimeout: 2 * time.Second,
ForceAttemptHTTP2: true,
}
if am.TrustedRoots != nil {
if iss.TrustedRoots != nil {
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,
}
am.httpClient = hc
iss.httpClient = hc
}
client := &acmez.Client{
@ -213,55 +213,55 @@ func (am *ACMEManager) newACMEClient(useTestCA bool) (*acmez.Client, error) {
},
ChallengeSolvers: make(map[string]acmez.Solver),
}
if am.Logger != nil {
l := am.Logger.Named("acme_client")
if iss.Logger != nil {
l := iss.Logger.Named("acme_client")
client.Client.Logger, client.Logger = l, l
}
// configure challenges (most of the time, DNS challenge is
// exclusive of other ones because it is usually only used
// in situations where the default challenges would fail)
if am.DNS01Solver == nil {
if iss.DNS01Solver == nil {
// enable HTTP-01 challenge
if !am.DisableHTTPChallenge {
if !iss.DisableHTTPChallenge {
useHTTPPort := HTTPChallengePort
if HTTPPort > 0 && HTTPPort != HTTPChallengePort {
useHTTPPort = HTTPPort
}
if am.AltHTTPPort > 0 {
useHTTPPort = am.AltHTTPPort
if iss.AltHTTPPort > 0 {
useHTTPPort = iss.AltHTTPPort
}
client.ChallengeSolvers[acme.ChallengeTypeHTTP01] = distributedSolver{
storage: am.config.Storage,
storageKeyIssuerPrefix: am.storageKeyCAPrefix(client.Directory),
storage: iss.config.Storage,
storageKeyIssuerPrefix: iss.storageKeyCAPrefix(client.Directory),
solver: &httpSolver{
acmeManager: am,
address: net.JoinHostPort(am.ListenHost, strconv.Itoa(useHTTPPort)),
acmeIssuer: iss,
address: net.JoinHostPort(iss.ListenHost, strconv.Itoa(useHTTPPort)),
},
}
}
// enable TLS-ALPN-01 challenge
if !am.DisableTLSALPNChallenge {
if !iss.DisableTLSALPNChallenge {
useTLSALPNPort := TLSALPNChallengePort
if HTTPSPort > 0 && HTTPSPort != TLSALPNChallengePort {
useTLSALPNPort = HTTPSPort
}
if am.AltTLSALPNPort > 0 {
useTLSALPNPort = am.AltTLSALPNPort
if iss.AltTLSALPNPort > 0 {
useTLSALPNPort = iss.AltTLSALPNPort
}
client.ChallengeSolvers[acme.ChallengeTypeTLSALPN01] = distributedSolver{
storage: am.config.Storage,
storageKeyIssuerPrefix: am.storageKeyCAPrefix(client.Directory),
storage: iss.config.Storage,
storageKeyIssuerPrefix: iss.storageKeyCAPrefix(client.Directory),
solver: &tlsALPNSolver{
config: am.config,
address: net.JoinHostPort(am.ListenHost, strconv.Itoa(useTLSALPNPort)),
config: iss.config,
address: net.JoinHostPort(iss.ListenHost, strconv.Itoa(useTLSALPNPort)),
},
}
}
} else {
// 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
@ -288,7 +288,7 @@ func (am *ACMEManager) newACMEClient(useTestCA bool) (*acmez.Client, error) {
func (c *acmeClient) throttle(ctx context.Context, names []string) error {
// throttling is scoped to CA + account email
rateLimiterKey := c.acmeClient.Directory + "," + c.mgr.Email
rateLimiterKey := c.acmeClient.Directory + "," + c.iss.Email
rateLimitersMu.Lock()
rl, ok := rateLimiters[rateLimiterKey]
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...
}
rateLimitersMu.Unlock()
if c.mgr.Logger != nil {
c.mgr.Logger.Info("waiting on internal rate limiter",
if c.iss.Logger != nil {
c.iss.Logger.Info("waiting on internal rate limiter",
zap.Strings("identifiers", names),
zap.String("ca", c.acmeClient.Directory),
zap.String("account", c.mgr.Email),
zap.String("account", c.iss.Email),
)
}
err := rl.Wait(ctx)
if err != nil {
return err
}
if c.mgr.Logger != nil {
c.mgr.Logger.Info("done waiting on internal rate limiter",
if c.iss.Logger != nil {
c.iss.Logger.Info("done waiting on internal rate limiter",
zap.Strings("identifiers", names),
zap.String("ca", c.acmeClient.Directory),
zap.String("account", c.mgr.Email),
zap.String("account", c.iss.Email),
)
}
return nil
}
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 {

View File

@ -16,14 +16,14 @@ import (
"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.
//
// 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
// internal state that is necessary for valid use. Always call
// NewACMEManager() to get a valid ACMEManager value.
type ACMEManager struct {
// NewACMEIssuer() to get a valid ACMEIssuer value.
type ACMEIssuer struct {
// The endpoint of the directory for the ACME
// CA we are to use
CA string
@ -99,9 +99,9 @@ type ACMEManager struct {
// Callback function that is called before a
// new ACME account is registered with the CA;
// 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)
NewAccountFunc func(context.Context, *ACMEManager, acme.Account) (acme.Account, error)
NewAccountFunc func(context.Context, *ACMEIssuer, acme.Account) (acme.Account, error)
// Preferences for selecting alternate
// certificate chains
@ -114,17 +114,17 @@ type ACMEManager struct {
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
// DefaultACME, and if any required values are still empty, sensible
// defaults will be used.
//
// 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.
func NewACMEManager(cfg *Config, template ACMEManager) *ACMEManager {
func NewACMEIssuer(cfg *Config, template ACMEIssuer) *ACMEIssuer {
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 == "" {
template.CA = DefaultACME.CA
@ -187,11 +187,11 @@ func NewACMEManager(cfg *Config, template ACMEManager) *ACMEManager {
// IssuerKey returns the unique issuer key for the
// confgured CA endpoint.
func (am *ACMEManager) IssuerKey() string {
func (am *ACMEIssuer) IssuerKey() string {
return am.issuerKey(am.CA)
}
func (*ACMEManager) issuerKey(ca string) string {
func (*ACMEIssuer) issuerKey(ca string) string {
key := ca
if caURL, err := url.Parse(key); err == nil {
key = caURL.Host
@ -217,7 +217,7 @@ func (*ACMEManager) issuerKey(ca string) string {
// renewing a certificate with ACME, and returns whether this
// batch is eligible for certificates if using Let's Encrypt.
// 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")
if publicCA {
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
// 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 {
panic("missing config pointer (must use NewACMEManager)")
panic("missing config pointer (must use NewACMEIssuer)")
}
var isRetry bool
@ -297,7 +297,7 @@ func (am *ACMEManager) Issue(ctx context.Context, csr *x509.CertificateRequest)
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)
if err != nil {
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
// 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.
func (am *ACMEManager) selectPreferredChain(certChains []acme.Certificate) acme.Certificate {
func (am *ACMEIssuer) selectPreferredChain(certChains []acme.Certificate) acme.Certificate {
if len(certChains) == 1 {
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",
@ -411,7 +411,7 @@ func (am *ACMEManager) selectPreferredChain(certChains []acme.Certificate) acme.
}
// 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)
if err != nil {
return err
@ -441,9 +441,9 @@ type ChainPreference struct {
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.
var DefaultACME = ACMEManager{
var DefaultACME = ACMEIssuer{
CA: LetsEncryptProductionCA,
TestCA: LetsEncryptStagingCA,
}
@ -460,7 +460,7 @@ const prefixACME = "acme"
// Interface guards
var (
_ PreChecker = (*ACMEManager)(nil)
_ Issuer = (*ACMEManager)(nil)
_ Revoker = (*ACMEManager)(nil)
_ PreChecker = (*ACMEIssuer)(nil)
_ Issuer = (*ACMEIssuer)(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 },
}
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))
}
}
@ -378,12 +378,12 @@ type Revoker interface {
Revoke(ctx context.Context, cert CertificateResource, reason int) error
}
// CertificateManager is a type that manages certificates (keeps them renewed)
// such that we can get certificates during TLS handshakes to immediately serve
// Manager is a type that manages certificates (keeps them renewed) such
// that we can get certificates during TLS handshakes to immediately serve
// to clients.
//
// 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.
// 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.

View File

@ -74,7 +74,7 @@ type Config struct {
MustStaple bool
// 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
// turn until one succeeds.
Issuers []Issuer
@ -86,7 +86,7 @@ type Config struct {
// the in-memory cache.
//
// TODO: EXPERIMENTAL: subject to change and/or removal.
Managers []CertificateManager
Managers []Manager
// The source of new private keys for certificates;
// the default KeySource is StandardKeyGenerator.
@ -218,7 +218,7 @@ func newWithCache(certCache *Cache, cfg Config) *Config {
cfg.Issuers = Default.Issuers
if len(cfg.Issuers) == 0 {
// 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) {
ctx := context.Background()
am := &ACMEManager{CA: "https://example.com/acme/directory"}
am := &ACMEIssuer{CA: "https://example.com/acme/directory"}
testConfig := &Config{
Issuers: []Issuer{am},
Storage: &FileStorage{Path: "./_testdata_tmp"},

View File

@ -258,7 +258,7 @@ func (cfg *Config) getCertDuringHandshake(hello *tls.ClientHelloInfo, loadIfNece
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.
externalCert, err := cfg.getCertFromAnyCertManager(ctx, hello, log)
if err != nil {

View File

@ -28,7 +28,7 @@ import (
// the challenge state is stored between initiation and solution.
//
// 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) {
if am.HandleHTTPChallenge(w, r) {
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
// already been written. If false is returned, this call was a no-op and
// 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 {
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
// same storage as am does, and attempts to complete the challenge for
// 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 {
return false
}

View File

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

View File

@ -47,7 +47,7 @@ import (
// from storage, which is done by distributedSolver.
type httpSolver struct {
closed int32 // accessed atomically
acmeManager *ACMEManager
acmeIssuer *ACMEIssuer
address string
}
@ -88,7 +88,7 @@ func (s *httpSolver) serve(ctx context.Context, si *solverInfo) {
}()
defer close(si.done)
httpServer := &http.Server{
Handler: s.acmeManager.HTTPChallengeHandler(http.NewServeMux()),
Handler: s.acmeIssuer.HTTPChallengeHandler(http.NewServeMux()),
BaseContext: func(listener net.Listener) context.Context { return ctx },
}
httpServer.SetKeepAlivesEnabled(false)
@ -476,7 +476,7 @@ type distributedSolver struct {
// Present invokes the underlying solver's Present method
// 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 {
infoBytes, err := json.Marshal(chal)
if err != nil {

View File

@ -20,7 +20,7 @@ import (
)
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")