mirror of
				https://github.com/caddyserver/caddy.git
				synced 2025-11-03 19:17:29 -05:00 
			
		
		
		
	
		
			Some checks failed
		
		
	
	Tests / test (./cmd/caddy/caddy, ~1.22.3, ubuntu-latest, 0, 1.22, linux) (push) Failing after 2m43s
				
			Tests / test (./cmd/caddy/caddy, ~1.23.0, ubuntu-latest, 0, 1.23, linux) (push) Failing after 2m26s
				
			Tests / test (s390x on IBM Z) (push) Has been skipped
				
			Tests / goreleaser-check (push) Successful in 40s
				
			Cross-Build / build (~1.22.3, 1.22, aix) (push) Successful in 2m55s
				
			Cross-Build / build (~1.22.3, 1.22, dragonfly) (push) Successful in 3m1s
				
			Cross-Build / build (~1.22.3, 1.22, freebsd) (push) Successful in 3m1s
				
			Cross-Build / build (~1.22.3, 1.22, netbsd) (push) Successful in 3m1s
				
			Cross-Build / build (~1.22.3, 1.22, windows) (push) Successful in 3m1s
				
			Cross-Build / build (~1.22.3, 1.22, darwin) (push) Failing after 13m23s
				
			Cross-Build / build (~1.23.0, 1.23, aix) (push) Successful in 2m40s
				
			Cross-Build / build (~1.22.3, 1.22, illumos) (push) Failing after 11m51s
				
			Cross-Build / build (~1.22.3, 1.22, linux) (push) Failing after 11m47s
				
			Cross-Build / build (~1.23.0, 1.23, darwin) (push) Successful in 2m29s
				
			Cross-Build / build (~1.23.0, 1.23, linux) (push) Successful in 2m39s
				
			Cross-Build / build (~1.22.3, 1.22, openbsd) (push) Failing after 13m27s
				
			Cross-Build / build (~1.22.3, 1.22, solaris) (push) Failing after 13m27s
				
			Cross-Build / build (~1.23.0, 1.23, netbsd) (push) Successful in 2m41s
				
			Cross-Build / build (~1.23.0, 1.23, openbsd) (push) Successful in 2m54s
				
			Cross-Build / build (~1.23.0, 1.23, solaris) (push) Successful in 2m40s
				
			Cross-Build / build (~1.23.0, 1.23, dragonfly) (push) Failing after 14m19s
				
			Cross-Build / build (~1.23.0, 1.23, freebsd) (push) Failing after 14m10s
				
			Cross-Build / build (~1.23.0, 1.23, illumos) (push) Failing after 14m2s
				
			Cross-Build / build (~1.23.0, 1.23, windows) (push) Failing after 11m58s
				
			Lint / lint (ubuntu-latest, linux) (push) Failing after 3m44s
				
			Lint / govulncheck (push) Successful in 2m18s
				
			Tests / test (./cmd/caddy/caddy, ~1.22.3, macos-14, 0, 1.22, mac) (push) Has been cancelled
				
			Tests / test (./cmd/caddy/caddy, ~1.23.0, macos-14, 0, 1.23, mac) (push) Has been cancelled
				
			Tests / test (./cmd/caddy/caddy.exe, ~1.22.3, windows-latest, True, 1.22, windows) (push) Has been cancelled
				
			Tests / test (./cmd/caddy/caddy.exe, ~1.23.0, windows-latest, True, 1.23, windows) (push) Has been cancelled
				
			Lint / lint (macos-14, mac) (push) Has been cancelled
				
			Lint / lint (windows-latest, windows) (push) Has been cancelled
				
			* perf: use zap's Check() to prevent useless allocs * fix * fix * fix * fix * restore previous replacer behavior * fix linter
		
			
				
	
	
		
			669 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			669 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// Copyright 2015 Matthew Holt and The Caddy Authors
 | 
						|
//
 | 
						|
// Licensed under the Apache License, Version 2.0 (the "License");
 | 
						|
// you may not use this file except in compliance with the License.
 | 
						|
// You may obtain a copy of the License at
 | 
						|
//
 | 
						|
//     http://www.apache.org/licenses/LICENSE-2.0
 | 
						|
//
 | 
						|
// Unless required by applicable law or agreed to in writing, software
 | 
						|
// distributed under the License is distributed on an "AS IS" BASIS,
 | 
						|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
						|
// See the License for the specific language governing permissions and
 | 
						|
// limitations under the License.
 | 
						|
 | 
						|
package caddytls
 | 
						|
 | 
						|
import (
 | 
						|
	"context"
 | 
						|
	"crypto/x509"
 | 
						|
	"encoding/json"
 | 
						|
	"fmt"
 | 
						|
	"net/http"
 | 
						|
	"net/url"
 | 
						|
	"os"
 | 
						|
	"strconv"
 | 
						|
	"strings"
 | 
						|
	"time"
 | 
						|
 | 
						|
	"github.com/caddyserver/certmagic"
 | 
						|
	"github.com/caddyserver/zerossl"
 | 
						|
	"github.com/mholt/acmez/v2/acme"
 | 
						|
	"go.uber.org/zap"
 | 
						|
	"go.uber.org/zap/zapcore"
 | 
						|
 | 
						|
	"github.com/caddyserver/caddy/v2"
 | 
						|
	"github.com/caddyserver/caddy/v2/caddyconfig"
 | 
						|
	"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
 | 
						|
)
 | 
						|
 | 
						|
func init() {
 | 
						|
	caddy.RegisterModule(ACMEIssuer{})
 | 
						|
}
 | 
						|
 | 
						|
// ACMEIssuer manages certificates using the ACME protocol (RFC 8555).
 | 
						|
type ACMEIssuer struct {
 | 
						|
	// The URL to the CA's ACME directory endpoint. Default:
 | 
						|
	// https://acme-v02.api.letsencrypt.org/directory
 | 
						|
	CA string `json:"ca,omitempty"`
 | 
						|
 | 
						|
	// The URL to the test CA's ACME directory endpoint.
 | 
						|
	// This endpoint is only used during retries if there
 | 
						|
	// is a failure using the primary CA. Default:
 | 
						|
	// https://acme-staging-v02.api.letsencrypt.org/directory
 | 
						|
	TestCA string `json:"test_ca,omitempty"`
 | 
						|
 | 
						|
	// Your email address, so the CA can contact you if necessary.
 | 
						|
	// Not required, but strongly recommended to provide one so
 | 
						|
	// you can be reached if there is a problem. Your email is
 | 
						|
	// not sent to any Caddy mothership or used for any purpose
 | 
						|
	// other than ACME transactions.
 | 
						|
	Email string `json:"email,omitempty"`
 | 
						|
 | 
						|
	// If you have an existing account with the ACME server, put
 | 
						|
	// the private key here in PEM format. The ACME client will
 | 
						|
	// look up your account information with this key first before
 | 
						|
	// trying to create a new one. You can use placeholders here,
 | 
						|
	// for example if you have it in an environment variable.
 | 
						|
	AccountKey string `json:"account_key,omitempty"`
 | 
						|
 | 
						|
	// If using an ACME CA that requires an external account
 | 
						|
	// binding, specify the CA-provided credentials here.
 | 
						|
	ExternalAccount *acme.EAB `json:"external_account,omitempty"`
 | 
						|
 | 
						|
	// Time to wait before timing out an ACME operation.
 | 
						|
	// Default: 0 (no timeout)
 | 
						|
	ACMETimeout caddy.Duration `json:"acme_timeout,omitempty"`
 | 
						|
 | 
						|
	// Configures the various ACME challenge types.
 | 
						|
	Challenges *ChallengesConfig `json:"challenges,omitempty"`
 | 
						|
 | 
						|
	// An array of files of CA certificates to accept when connecting to the
 | 
						|
	// ACME CA. Generally, you should only use this if the ACME CA endpoint
 | 
						|
	// is internal or for development/testing purposes.
 | 
						|
	TrustedRootsPEMFiles []string `json:"trusted_roots_pem_files,omitempty"`
 | 
						|
 | 
						|
	// Preferences for selecting alternate certificate chains, if offered
 | 
						|
	// by the CA. By default, the first offered chain will be selected.
 | 
						|
	// If configured, the chains may be sorted and the first matching chain
 | 
						|
	// will be selected.
 | 
						|
	PreferredChains *ChainPreference `json:"preferred_chains,omitempty"`
 | 
						|
 | 
						|
	// The validity period to ask the CA to issue a certificate for.
 | 
						|
	// Default: 0 (CA chooses lifetime).
 | 
						|
	// This value is used to compute the "notAfter" field of the ACME order;
 | 
						|
	// therefore the system must have a reasonably synchronized clock.
 | 
						|
	// NOTE: Not all CAs support this. Check with your CA's ACME
 | 
						|
	// documentation to see if this is allowed and what values may
 | 
						|
	// be used. EXPERIMENTAL: Subject to change.
 | 
						|
	CertificateLifetime caddy.Duration `json:"certificate_lifetime,omitempty"`
 | 
						|
 | 
						|
	rootPool *x509.CertPool
 | 
						|
	logger   *zap.Logger
 | 
						|
 | 
						|
	template certmagic.ACMEIssuer  // set at Provision
 | 
						|
	magic    *certmagic.Config     // set at PreCheck
 | 
						|
	issuer   *certmagic.ACMEIssuer // set at PreCheck; result of template + magic
 | 
						|
}
 | 
						|
 | 
						|
// CaddyModule returns the Caddy module information.
 | 
						|
func (ACMEIssuer) CaddyModule() caddy.ModuleInfo {
 | 
						|
	return caddy.ModuleInfo{
 | 
						|
		ID:  "tls.issuance.acme",
 | 
						|
		New: func() caddy.Module { return new(ACMEIssuer) },
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// Provision sets up iss.
 | 
						|
func (iss *ACMEIssuer) Provision(ctx caddy.Context) error {
 | 
						|
	iss.logger = ctx.Logger()
 | 
						|
 | 
						|
	repl := caddy.NewReplacer()
 | 
						|
 | 
						|
	// expand email address, if non-empty
 | 
						|
	if iss.Email != "" {
 | 
						|
		email, err := repl.ReplaceOrErr(iss.Email, true, true)
 | 
						|
		if err != nil {
 | 
						|
			return fmt.Errorf("expanding email address '%s': %v", iss.Email, err)
 | 
						|
		}
 | 
						|
		iss.Email = email
 | 
						|
	}
 | 
						|
 | 
						|
	// expand account key, if non-empty
 | 
						|
	if iss.AccountKey != "" {
 | 
						|
		accountKey, err := repl.ReplaceOrErr(iss.AccountKey, true, true)
 | 
						|
		if err != nil {
 | 
						|
			return fmt.Errorf("expanding account key PEM '%s': %v", iss.AccountKey, err)
 | 
						|
		}
 | 
						|
		iss.AccountKey = accountKey
 | 
						|
	}
 | 
						|
 | 
						|
	// DNS providers
 | 
						|
	if iss.Challenges != nil && iss.Challenges.DNS != nil && iss.Challenges.DNS.ProviderRaw != nil {
 | 
						|
		val, err := ctx.LoadModule(iss.Challenges.DNS, "ProviderRaw")
 | 
						|
		if err != nil {
 | 
						|
			return fmt.Errorf("loading DNS provider module: %v", err)
 | 
						|
		}
 | 
						|
		iss.Challenges.DNS.solver = &certmagic.DNS01Solver{
 | 
						|
			DNSManager: certmagic.DNSManager{
 | 
						|
				DNSProvider:        val.(certmagic.DNSProvider),
 | 
						|
				TTL:                time.Duration(iss.Challenges.DNS.TTL),
 | 
						|
				PropagationDelay:   time.Duration(iss.Challenges.DNS.PropagationDelay),
 | 
						|
				PropagationTimeout: time.Duration(iss.Challenges.DNS.PropagationTimeout),
 | 
						|
				Resolvers:          iss.Challenges.DNS.Resolvers,
 | 
						|
				OverrideDomain:     iss.Challenges.DNS.OverrideDomain,
 | 
						|
			},
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// add any custom CAs to trust store
 | 
						|
	if len(iss.TrustedRootsPEMFiles) > 0 {
 | 
						|
		iss.rootPool = x509.NewCertPool()
 | 
						|
		for _, pemFile := range iss.TrustedRootsPEMFiles {
 | 
						|
			pemData, err := os.ReadFile(pemFile)
 | 
						|
			if err != nil {
 | 
						|
				return fmt.Errorf("loading trusted root CA's PEM file: %s: %v", pemFile, err)
 | 
						|
			}
 | 
						|
			if !iss.rootPool.AppendCertsFromPEM(pemData) {
 | 
						|
				return fmt.Errorf("unable to add %s to trust pool: %v", pemFile, err)
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	var err error
 | 
						|
	iss.template, err = iss.makeIssuerTemplate()
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (iss *ACMEIssuer) makeIssuerTemplate() (certmagic.ACMEIssuer, error) {
 | 
						|
	template := certmagic.ACMEIssuer{
 | 
						|
		CA:                iss.CA,
 | 
						|
		TestCA:            iss.TestCA,
 | 
						|
		Email:             iss.Email,
 | 
						|
		AccountKeyPEM:     iss.AccountKey,
 | 
						|
		CertObtainTimeout: time.Duration(iss.ACMETimeout),
 | 
						|
		TrustedRoots:      iss.rootPool,
 | 
						|
		ExternalAccount:   iss.ExternalAccount,
 | 
						|
		NotAfter:          time.Duration(iss.CertificateLifetime),
 | 
						|
		Logger:            iss.logger,
 | 
						|
	}
 | 
						|
 | 
						|
	if iss.Challenges != nil {
 | 
						|
		if iss.Challenges.HTTP != nil {
 | 
						|
			template.DisableHTTPChallenge = iss.Challenges.HTTP.Disabled
 | 
						|
			template.AltHTTPPort = iss.Challenges.HTTP.AlternatePort
 | 
						|
		}
 | 
						|
		if iss.Challenges.TLSALPN != nil {
 | 
						|
			template.DisableTLSALPNChallenge = iss.Challenges.TLSALPN.Disabled
 | 
						|
			template.AltTLSALPNPort = iss.Challenges.TLSALPN.AlternatePort
 | 
						|
		}
 | 
						|
		if iss.Challenges.DNS != nil {
 | 
						|
			template.DNS01Solver = iss.Challenges.DNS.solver
 | 
						|
		}
 | 
						|
		template.ListenHost = iss.Challenges.BindHost
 | 
						|
	}
 | 
						|
 | 
						|
	if iss.PreferredChains != nil {
 | 
						|
		template.PreferredChains = certmagic.ChainPreference{
 | 
						|
			Smallest:       iss.PreferredChains.Smallest,
 | 
						|
			AnyCommonName:  iss.PreferredChains.AnyCommonName,
 | 
						|
			RootCommonName: iss.PreferredChains.RootCommonName,
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// ZeroSSL requires EAB, but we can generate that automatically (requires an email address be configured)
 | 
						|
	if strings.HasPrefix(iss.CA, "https://acme.zerossl.com/") {
 | 
						|
		template.NewAccountFunc = func(ctx context.Context, acmeIss *certmagic.ACMEIssuer, acct acme.Account) (acme.Account, error) {
 | 
						|
			if acmeIss.ExternalAccount != nil {
 | 
						|
				return acct, nil
 | 
						|
			}
 | 
						|
			var err error
 | 
						|
			acmeIss.ExternalAccount, acct, err = iss.generateZeroSSLEABCredentials(ctx, acct)
 | 
						|
			return acct, err
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return template, nil
 | 
						|
}
 | 
						|
 | 
						|
// SetConfig sets the associated certmagic config for this issuer.
 | 
						|
// This is required because ACME needs values from the config in
 | 
						|
// order to solve the challenges during issuance. This implements
 | 
						|
// the ConfigSetter interface.
 | 
						|
func (iss *ACMEIssuer) SetConfig(cfg *certmagic.Config) {
 | 
						|
	iss.magic = cfg
 | 
						|
	iss.issuer = certmagic.NewACMEIssuer(cfg, iss.template)
 | 
						|
}
 | 
						|
 | 
						|
// PreCheck implements the certmagic.PreChecker interface.
 | 
						|
func (iss *ACMEIssuer) PreCheck(ctx context.Context, names []string, interactive bool) error {
 | 
						|
	return iss.issuer.PreCheck(ctx, names, interactive)
 | 
						|
}
 | 
						|
 | 
						|
// Issue obtains a certificate for the given csr.
 | 
						|
func (iss *ACMEIssuer) Issue(ctx context.Context, csr *x509.CertificateRequest) (*certmagic.IssuedCertificate, error) {
 | 
						|
	return iss.issuer.Issue(ctx, csr)
 | 
						|
}
 | 
						|
 | 
						|
// IssuerKey returns the unique issuer key for the configured CA endpoint.
 | 
						|
func (iss *ACMEIssuer) IssuerKey() string {
 | 
						|
	return iss.issuer.IssuerKey()
 | 
						|
}
 | 
						|
 | 
						|
// Revoke revokes the given certificate.
 | 
						|
func (iss *ACMEIssuer) Revoke(ctx context.Context, cert certmagic.CertificateResource, reason int) error {
 | 
						|
	return iss.issuer.Revoke(ctx, cert, reason)
 | 
						|
}
 | 
						|
 | 
						|
// GetACMEIssuer returns iss. This is useful when other types embed ACMEIssuer, because
 | 
						|
// type-asserting them to *ACMEIssuer will fail, but type-asserting them to an interface
 | 
						|
// with only this method will succeed, and will still allow the embedded ACMEIssuer
 | 
						|
// to be accessed and manipulated.
 | 
						|
func (iss *ACMEIssuer) GetACMEIssuer() *ACMEIssuer { return iss }
 | 
						|
 | 
						|
// GetRenewalInfo wraps the underlying GetRenewalInfo method and satisfies
 | 
						|
// the CertMagic interface for ARI support.
 | 
						|
func (iss *ACMEIssuer) GetRenewalInfo(ctx context.Context, cert certmagic.Certificate) (acme.RenewalInfo, error) {
 | 
						|
	return iss.issuer.GetRenewalInfo(ctx, cert)
 | 
						|
}
 | 
						|
 | 
						|
// generateZeroSSLEABCredentials generates ZeroSSL EAB credentials for the primary contact email
 | 
						|
// on the issuer. It should only be usedif the CA endpoint is ZeroSSL. An email address is required.
 | 
						|
func (iss *ACMEIssuer) generateZeroSSLEABCredentials(ctx context.Context, acct acme.Account) (*acme.EAB, acme.Account, error) {
 | 
						|
	if strings.TrimSpace(iss.Email) == "" {
 | 
						|
		return nil, acme.Account{}, fmt.Errorf("your email address is required to use ZeroSSL's ACME endpoint")
 | 
						|
	}
 | 
						|
 | 
						|
	if len(acct.Contact) == 0 {
 | 
						|
		// we borrow the email from config or the default email, so ensure it's saved with the account
 | 
						|
		acct.Contact = []string{"mailto:" + iss.Email}
 | 
						|
	}
 | 
						|
 | 
						|
	endpoint := zerossl.BaseURL + "/acme/eab-credentials-email"
 | 
						|
	form := url.Values{"email": []string{iss.Email}}
 | 
						|
	body := strings.NewReader(form.Encode())
 | 
						|
 | 
						|
	req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, body)
 | 
						|
	if err != nil {
 | 
						|
		return nil, acct, fmt.Errorf("forming request: %v", err)
 | 
						|
	}
 | 
						|
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
 | 
						|
	req.Header.Set("User-Agent", certmagic.UserAgent)
 | 
						|
 | 
						|
	resp, err := http.DefaultClient.Do(req)
 | 
						|
	if err != nil {
 | 
						|
		return nil, acct, fmt.Errorf("performing EAB credentials request: %v", err)
 | 
						|
	}
 | 
						|
	defer resp.Body.Close()
 | 
						|
 | 
						|
	var result struct {
 | 
						|
		Success bool `json:"success"`
 | 
						|
		Error   struct {
 | 
						|
			Code int    `json:"code"`
 | 
						|
			Type string `json:"type"`
 | 
						|
		} `json:"error"`
 | 
						|
		EABKID     string `json:"eab_kid"`
 | 
						|
		EABHMACKey string `json:"eab_hmac_key"`
 | 
						|
	}
 | 
						|
	err = json.NewDecoder(resp.Body).Decode(&result)
 | 
						|
	if err != nil {
 | 
						|
		return nil, acct, fmt.Errorf("decoding API response: %v", err)
 | 
						|
	}
 | 
						|
	if result.Error.Code != 0 {
 | 
						|
		// do this check first because ZeroSSL's API returns 200 on errors
 | 
						|
		return nil, acct, fmt.Errorf("failed getting EAB credentials: HTTP %d: %s (code %d)",
 | 
						|
			resp.StatusCode, result.Error.Type, result.Error.Code)
 | 
						|
	}
 | 
						|
	if resp.StatusCode != http.StatusOK {
 | 
						|
		return nil, acct, fmt.Errorf("failed getting EAB credentials: HTTP %d", resp.StatusCode)
 | 
						|
	}
 | 
						|
 | 
						|
	if c := iss.logger.Check(zapcore.InfoLevel, "generated EAB credentials"); c != nil {
 | 
						|
		c.Write(zap.String("key_id", result.EABKID))
 | 
						|
	}
 | 
						|
 | 
						|
	return &acme.EAB{
 | 
						|
		KeyID:  result.EABKID,
 | 
						|
		MACKey: result.EABHMACKey,
 | 
						|
	}, acct, nil
 | 
						|
}
 | 
						|
 | 
						|
// UnmarshalCaddyfile deserializes Caddyfile tokens into iss.
 | 
						|
//
 | 
						|
//	... acme [<directory_url>] {
 | 
						|
//	    dir <directory_url>
 | 
						|
//	    test_dir <test_directory_url>
 | 
						|
//	    email <email>
 | 
						|
//	    timeout <duration>
 | 
						|
//	    disable_http_challenge
 | 
						|
//	    disable_tlsalpn_challenge
 | 
						|
//	    alt_http_port    <port>
 | 
						|
//	    alt_tlsalpn_port <port>
 | 
						|
//	    eab <key_id> <mac_key>
 | 
						|
//	    trusted_roots <pem_files...>
 | 
						|
//	    dns <provider_name> [<options>]
 | 
						|
//	    propagation_delay <duration>
 | 
						|
//	    propagation_timeout <duration>
 | 
						|
//	    resolvers <dns_servers...>
 | 
						|
//	    dns_ttl <duration>
 | 
						|
//	    dns_challenge_override_domain <domain>
 | 
						|
//	    preferred_chains [smallest] {
 | 
						|
//	        root_common_name <common_names...>
 | 
						|
//	        any_common_name  <common_names...>
 | 
						|
//	    }
 | 
						|
//	}
 | 
						|
func (iss *ACMEIssuer) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
 | 
						|
	d.Next() // consume issuer name
 | 
						|
 | 
						|
	if d.NextArg() {
 | 
						|
		iss.CA = d.Val()
 | 
						|
		if d.NextArg() {
 | 
						|
			return d.ArgErr()
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	for d.NextBlock(0) {
 | 
						|
		switch d.Val() {
 | 
						|
		case "lifetime":
 | 
						|
			var lifetimeStr string
 | 
						|
			if !d.AllArgs(&lifetimeStr) {
 | 
						|
				return d.ArgErr()
 | 
						|
			}
 | 
						|
			lifetime, err := caddy.ParseDuration(lifetimeStr)
 | 
						|
			if err != nil {
 | 
						|
				return d.Errf("invalid lifetime %s: %v", lifetimeStr, err)
 | 
						|
			}
 | 
						|
			if lifetime < 0 {
 | 
						|
				return d.Errf("lifetime must be >= 0: %s", lifetime)
 | 
						|
			}
 | 
						|
			iss.CertificateLifetime = caddy.Duration(lifetime)
 | 
						|
 | 
						|
		case "dir":
 | 
						|
			if iss.CA != "" {
 | 
						|
				return d.Errf("directory is already specified: %s", iss.CA)
 | 
						|
			}
 | 
						|
			if !d.AllArgs(&iss.CA) {
 | 
						|
				return d.ArgErr()
 | 
						|
			}
 | 
						|
 | 
						|
		case "test_dir":
 | 
						|
			if !d.AllArgs(&iss.TestCA) {
 | 
						|
				return d.ArgErr()
 | 
						|
			}
 | 
						|
 | 
						|
		case "email":
 | 
						|
			if !d.AllArgs(&iss.Email) {
 | 
						|
				return d.ArgErr()
 | 
						|
			}
 | 
						|
 | 
						|
		case "timeout":
 | 
						|
			var timeoutStr string
 | 
						|
			if !d.AllArgs(&timeoutStr) {
 | 
						|
				return d.ArgErr()
 | 
						|
			}
 | 
						|
			timeout, err := caddy.ParseDuration(timeoutStr)
 | 
						|
			if err != nil {
 | 
						|
				return d.Errf("invalid timeout duration %s: %v", timeoutStr, err)
 | 
						|
			}
 | 
						|
			iss.ACMETimeout = caddy.Duration(timeout)
 | 
						|
 | 
						|
		case "disable_http_challenge":
 | 
						|
			if d.NextArg() {
 | 
						|
				return d.ArgErr()
 | 
						|
			}
 | 
						|
			if iss.Challenges == nil {
 | 
						|
				iss.Challenges = new(ChallengesConfig)
 | 
						|
			}
 | 
						|
			if iss.Challenges.HTTP == nil {
 | 
						|
				iss.Challenges.HTTP = new(HTTPChallengeConfig)
 | 
						|
			}
 | 
						|
			iss.Challenges.HTTP.Disabled = true
 | 
						|
 | 
						|
		case "disable_tlsalpn_challenge":
 | 
						|
			if d.NextArg() {
 | 
						|
				return d.ArgErr()
 | 
						|
			}
 | 
						|
			if iss.Challenges == nil {
 | 
						|
				iss.Challenges = new(ChallengesConfig)
 | 
						|
			}
 | 
						|
			if iss.Challenges.TLSALPN == nil {
 | 
						|
				iss.Challenges.TLSALPN = new(TLSALPNChallengeConfig)
 | 
						|
			}
 | 
						|
			iss.Challenges.TLSALPN.Disabled = true
 | 
						|
 | 
						|
		case "alt_http_port":
 | 
						|
			if !d.NextArg() {
 | 
						|
				return d.ArgErr()
 | 
						|
			}
 | 
						|
			port, err := strconv.Atoi(d.Val())
 | 
						|
			if err != nil {
 | 
						|
				return d.Errf("invalid port %s: %v", d.Val(), err)
 | 
						|
			}
 | 
						|
			if iss.Challenges == nil {
 | 
						|
				iss.Challenges = new(ChallengesConfig)
 | 
						|
			}
 | 
						|
			if iss.Challenges.HTTP == nil {
 | 
						|
				iss.Challenges.HTTP = new(HTTPChallengeConfig)
 | 
						|
			}
 | 
						|
			iss.Challenges.HTTP.AlternatePort = port
 | 
						|
 | 
						|
		case "alt_tlsalpn_port":
 | 
						|
			if !d.NextArg() {
 | 
						|
				return d.ArgErr()
 | 
						|
			}
 | 
						|
			port, err := strconv.Atoi(d.Val())
 | 
						|
			if err != nil {
 | 
						|
				return d.Errf("invalid port %s: %v", d.Val(), err)
 | 
						|
			}
 | 
						|
			if iss.Challenges == nil {
 | 
						|
				iss.Challenges = new(ChallengesConfig)
 | 
						|
			}
 | 
						|
			if iss.Challenges.TLSALPN == nil {
 | 
						|
				iss.Challenges.TLSALPN = new(TLSALPNChallengeConfig)
 | 
						|
			}
 | 
						|
			iss.Challenges.TLSALPN.AlternatePort = port
 | 
						|
 | 
						|
		case "eab":
 | 
						|
			iss.ExternalAccount = new(acme.EAB)
 | 
						|
			if !d.AllArgs(&iss.ExternalAccount.KeyID, &iss.ExternalAccount.MACKey) {
 | 
						|
				return d.ArgErr()
 | 
						|
			}
 | 
						|
 | 
						|
		case "trusted_roots":
 | 
						|
			iss.TrustedRootsPEMFiles = d.RemainingArgs()
 | 
						|
 | 
						|
		case "dns":
 | 
						|
			if !d.NextArg() {
 | 
						|
				return d.ArgErr()
 | 
						|
			}
 | 
						|
			provName := d.Val()
 | 
						|
			if iss.Challenges == nil {
 | 
						|
				iss.Challenges = new(ChallengesConfig)
 | 
						|
			}
 | 
						|
			if iss.Challenges.DNS == nil {
 | 
						|
				iss.Challenges.DNS = new(DNSChallengeConfig)
 | 
						|
			}
 | 
						|
			unm, err := caddyfile.UnmarshalModule(d, "dns.providers."+provName)
 | 
						|
			if err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
			iss.Challenges.DNS.ProviderRaw = caddyconfig.JSONModuleObject(unm, "name", provName, nil)
 | 
						|
 | 
						|
		case "propagation_delay":
 | 
						|
			if !d.NextArg() {
 | 
						|
				return d.ArgErr()
 | 
						|
			}
 | 
						|
			delayStr := d.Val()
 | 
						|
			delay, err := caddy.ParseDuration(delayStr)
 | 
						|
			if err != nil {
 | 
						|
				return d.Errf("invalid propagation_delay duration %s: %v", delayStr, err)
 | 
						|
			}
 | 
						|
			if iss.Challenges == nil {
 | 
						|
				iss.Challenges = new(ChallengesConfig)
 | 
						|
			}
 | 
						|
			if iss.Challenges.DNS == nil {
 | 
						|
				iss.Challenges.DNS = new(DNSChallengeConfig)
 | 
						|
			}
 | 
						|
			iss.Challenges.DNS.PropagationDelay = caddy.Duration(delay)
 | 
						|
 | 
						|
		case "propagation_timeout":
 | 
						|
			if !d.NextArg() {
 | 
						|
				return d.ArgErr()
 | 
						|
			}
 | 
						|
			timeoutStr := d.Val()
 | 
						|
			var timeout time.Duration
 | 
						|
			if timeoutStr == "-1" {
 | 
						|
				timeout = time.Duration(-1)
 | 
						|
			} else {
 | 
						|
				var err error
 | 
						|
				timeout, err = caddy.ParseDuration(timeoutStr)
 | 
						|
				if err != nil {
 | 
						|
					return d.Errf("invalid propagation_timeout duration %s: %v", timeoutStr, err)
 | 
						|
				}
 | 
						|
			}
 | 
						|
			if iss.Challenges == nil {
 | 
						|
				iss.Challenges = new(ChallengesConfig)
 | 
						|
			}
 | 
						|
			if iss.Challenges.DNS == nil {
 | 
						|
				iss.Challenges.DNS = new(DNSChallengeConfig)
 | 
						|
			}
 | 
						|
			iss.Challenges.DNS.PropagationTimeout = caddy.Duration(timeout)
 | 
						|
 | 
						|
		case "resolvers":
 | 
						|
			if iss.Challenges == nil {
 | 
						|
				iss.Challenges = new(ChallengesConfig)
 | 
						|
			}
 | 
						|
			if iss.Challenges.DNS == nil {
 | 
						|
				iss.Challenges.DNS = new(DNSChallengeConfig)
 | 
						|
			}
 | 
						|
			iss.Challenges.DNS.Resolvers = d.RemainingArgs()
 | 
						|
			if len(iss.Challenges.DNS.Resolvers) == 0 {
 | 
						|
				return d.ArgErr()
 | 
						|
			}
 | 
						|
 | 
						|
		case "dns_ttl":
 | 
						|
			if !d.NextArg() {
 | 
						|
				return d.ArgErr()
 | 
						|
			}
 | 
						|
			ttlStr := d.Val()
 | 
						|
			ttl, err := caddy.ParseDuration(ttlStr)
 | 
						|
			if err != nil {
 | 
						|
				return d.Errf("invalid dns_ttl duration %s: %v", ttlStr, err)
 | 
						|
			}
 | 
						|
			if iss.Challenges == nil {
 | 
						|
				iss.Challenges = new(ChallengesConfig)
 | 
						|
			}
 | 
						|
			if iss.Challenges.DNS == nil {
 | 
						|
				iss.Challenges.DNS = new(DNSChallengeConfig)
 | 
						|
			}
 | 
						|
			iss.Challenges.DNS.TTL = caddy.Duration(ttl)
 | 
						|
 | 
						|
		case "dns_challenge_override_domain":
 | 
						|
			arg := d.RemainingArgs()
 | 
						|
			if len(arg) != 1 {
 | 
						|
				return d.ArgErr()
 | 
						|
			}
 | 
						|
			if iss.Challenges == nil {
 | 
						|
				iss.Challenges = new(ChallengesConfig)
 | 
						|
			}
 | 
						|
			if iss.Challenges.DNS == nil {
 | 
						|
				iss.Challenges.DNS = new(DNSChallengeConfig)
 | 
						|
			}
 | 
						|
			iss.Challenges.DNS.OverrideDomain = arg[0]
 | 
						|
 | 
						|
		case "preferred_chains":
 | 
						|
			chainPref, err := ParseCaddyfilePreferredChainsOptions(d)
 | 
						|
			if err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
			iss.PreferredChains = chainPref
 | 
						|
 | 
						|
		default:
 | 
						|
			return d.Errf("unrecognized ACME issuer property: %s", d.Val())
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func ParseCaddyfilePreferredChainsOptions(d *caddyfile.Dispenser) (*ChainPreference, error) {
 | 
						|
	chainPref := new(ChainPreference)
 | 
						|
	if d.NextArg() {
 | 
						|
		smallestOpt := d.Val()
 | 
						|
		if smallestOpt == "smallest" {
 | 
						|
			trueBool := true
 | 
						|
			chainPref.Smallest = &trueBool
 | 
						|
			if d.NextArg() { // Only one argument allowed
 | 
						|
				return nil, d.ArgErr()
 | 
						|
			}
 | 
						|
			if d.NextBlock(d.Nesting()) { // Don't allow other options when smallest == true
 | 
						|
				return nil, d.Err("No more options are accepted when using the 'smallest' option")
 | 
						|
			}
 | 
						|
		} else { // Smallest option should always be 'smallest' or unset
 | 
						|
			return nil, d.Errf("Invalid argument '%s'", smallestOpt)
 | 
						|
		}
 | 
						|
	}
 | 
						|
	for nesting := d.Nesting(); d.NextBlock(nesting); {
 | 
						|
		switch d.Val() {
 | 
						|
		case "root_common_name":
 | 
						|
			rootCommonNameOpt := d.RemainingArgs()
 | 
						|
			chainPref.RootCommonName = rootCommonNameOpt
 | 
						|
			if rootCommonNameOpt == nil {
 | 
						|
				return nil, d.ArgErr()
 | 
						|
			}
 | 
						|
			if chainPref.AnyCommonName != nil {
 | 
						|
				return nil, d.Err("Can't set root_common_name when any_common_name is already set")
 | 
						|
			}
 | 
						|
 | 
						|
		case "any_common_name":
 | 
						|
			anyCommonNameOpt := d.RemainingArgs()
 | 
						|
			chainPref.AnyCommonName = anyCommonNameOpt
 | 
						|
			if anyCommonNameOpt == nil {
 | 
						|
				return nil, d.ArgErr()
 | 
						|
			}
 | 
						|
			if chainPref.RootCommonName != nil {
 | 
						|
				return nil, d.Err("Can't set any_common_name when root_common_name is already set")
 | 
						|
			}
 | 
						|
 | 
						|
		default:
 | 
						|
			return nil, d.Errf("Received unrecognized parameter '%s'", d.Val())
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if chainPref.Smallest == nil && chainPref.RootCommonName == nil && chainPref.AnyCommonName == nil {
 | 
						|
		return nil, d.Err("No options for preferred_chains received")
 | 
						|
	}
 | 
						|
 | 
						|
	return chainPref, nil
 | 
						|
}
 | 
						|
 | 
						|
// ChainPreference describes the client's preferred certificate chain,
 | 
						|
// useful if the CA offers alternate chains. The first matching chain
 | 
						|
// will be selected.
 | 
						|
type ChainPreference struct {
 | 
						|
	// Prefer chains with the fewest number of bytes.
 | 
						|
	Smallest *bool `json:"smallest,omitempty"`
 | 
						|
 | 
						|
	// Select first chain having a root with one of
 | 
						|
	// these common names.
 | 
						|
	RootCommonName []string `json:"root_common_name,omitempty"`
 | 
						|
 | 
						|
	// Select first chain that has any issuer with one
 | 
						|
	// of these common names.
 | 
						|
	AnyCommonName []string `json:"any_common_name,omitempty"`
 | 
						|
}
 | 
						|
 | 
						|
// Interface guards
 | 
						|
var (
 | 
						|
	_ certmagic.PreChecker        = (*ACMEIssuer)(nil)
 | 
						|
	_ certmagic.Issuer            = (*ACMEIssuer)(nil)
 | 
						|
	_ certmagic.Revoker           = (*ACMEIssuer)(nil)
 | 
						|
	_ certmagic.RenewalInfoGetter = (*ACMEIssuer)(nil)
 | 
						|
	_ caddy.Provisioner           = (*ACMEIssuer)(nil)
 | 
						|
	_ ConfigSetter                = (*ACMEIssuer)(nil)
 | 
						|
	_ caddyfile.Unmarshaler       = (*ACMEIssuer)(nil)
 | 
						|
)
 |