From 6a14e2c2a8881d5e90f1ee363ec4662a3f87402b Mon Sep 17 00:00:00 2001 From: Matt Holt Date: Thu, 30 Jul 2020 15:18:14 -0600 Subject: caddytls: Replace lego with acmez (#3621) * Replace lego with acmez; upgrade CertMagic * Update integration test --- modules/caddytls/acmeissuer.go | 67 ++++++++++-------------- modules/caddytls/automation.go | 13 +++-- modules/caddytls/connpolicy.go | 12 ++--- modules/caddytls/dnssolver.go | 113 ----------------------------------------- modules/caddytls/tls.go | 9 +++- 5 files changed, 50 insertions(+), 164 deletions(-) delete mode 100644 modules/caddytls/dnssolver.go (limited to 'modules/caddytls') diff --git a/modules/caddytls/acmeissuer.go b/modules/caddytls/acmeissuer.go index 195ddeb..8fe308d 100644 --- a/modules/caddytls/acmeissuer.go +++ b/modules/caddytls/acmeissuer.go @@ -24,7 +24,9 @@ import ( "github.com/caddyserver/caddy/v2" "github.com/caddyserver/certmagic" - "github.com/go-acme/lego/v3/challenge" + "github.com/mholt/acmez" + "github.com/mholt/acmez/acme" + "go.uber.org/zap" ) func init() { @@ -56,7 +58,7 @@ type ACMEIssuer struct { // If using an ACME CA that requires an external account // binding, specify the CA-provided credentials here. - ExternalAccount *ExternalAccountBinding `json:"external_account,omitempty"` + ExternalAccount *acme.EAB `json:"external_account,omitempty"` // Time to wait before timing out an ACME operation. ACMETimeout caddy.Duration `json:"acme_timeout,omitempty"` @@ -72,6 +74,7 @@ type ACMEIssuer struct { rootPool *x509.CertPool template certmagic.ACMEManager magic *certmagic.Config + logger *zap.Logger } // CaddyModule returns the Caddy module information. @@ -84,25 +87,29 @@ func (ACMEIssuer) CaddyModule() caddy.ModuleInfo { // Provision sets up m. func (m *ACMEIssuer) Provision(ctx caddy.Context) error { + m.logger = ctx.Logger(m) + // DNS providers if m.Challenges != nil && m.Challenges.DNS != nil && m.Challenges.DNS.ProviderRaw != nil { val, err := ctx.LoadModule(m.Challenges.DNS, "ProviderRaw") if err != nil { return fmt.Errorf("loading DNS provider module: %v", err) } - // TODO: For a temporary amount of time, we are allowing the use of - // DNS providers from go-acme/lego since there are so many implemented - // for it -- they are adapted as Caddy modules in this repository: - // https://github.com/caddy-dns/lego-deprecated - that module is - // a challenge.Provider value, so we use it directly. The user must set - // environment variables to configure it. Remove this shim once a sufficient - // number of DNS providers are implemented for the libdns APIs instead. - if grandfatheredProvider, ok := val.(challenge.Provider); ok { - m.Challenges.DNS.provider = grandfatheredProvider + + if deprecatedProvider, ok := val.(acmez.Solver); ok { + // TODO: For a temporary amount of time, we are allowing the use of DNS + // providers from go-acme/lego since there are so many providers implemented + // using that API -- they are adapted as an all-in-one Caddy module in this + // repository: https://github.com/caddy-dns/lego-deprecated - the module is a + // acmez.Solver type, so we use it directly. The user must set environment + // variables to configure it. Remove this shim once a sufficient number of + // DNS providers are implemented for the libdns APIs instead. + m.Challenges.DNS.solver = deprecatedProvider } else { - m.Challenges.DNS.provider = &solver{ - recordManager: val.(recordManager), - TTL: time.Duration(m.Challenges.DNS.TTL), + m.Challenges.DNS.solver = &certmagic.DNS01Solver{ + DNSProvider: val.(certmagic.ACMEDNSProvider), + TTL: time.Duration(m.Challenges.DNS.TTL), + PropagationTimeout: time.Duration(m.Challenges.DNS.PropagationTimeout), } } } @@ -137,16 +144,8 @@ func (m *ACMEIssuer) makeIssuerTemplate() (certmagic.ACMEManager, error) { Email: m.Email, CertObtainTimeout: time.Duration(m.ACMETimeout), TrustedRoots: m.rootPool, - } - - if m.ExternalAccount != nil { - if m.ExternalAccount.KeyID == "" || m.ExternalAccount.HMAC == "" { - return template, fmt.Errorf("when an external account binding is specified, both key ID and HMAC are required") - } - template.ExternalAccount = &certmagic.ExternalAccountBinding{ - KeyID: m.ExternalAccount.KeyID, - HMAC: m.ExternalAccount.HMAC, - } + ExternalAccount: m.ExternalAccount, + Logger: m.logger, } if m.Challenges != nil { @@ -159,7 +158,7 @@ func (m *ACMEIssuer) makeIssuerTemplate() (certmagic.ACMEManager, error) { template.AltTLSALPNPort = m.Challenges.TLSALPN.AlternatePort } if m.Challenges.DNS != nil { - template.DNSProvider = m.Challenges.DNS.provider + template.DNS01Solver = m.Challenges.DNS.solver } template.ListenHost = m.Challenges.BindHost } @@ -180,8 +179,8 @@ func (m *ACMEIssuer) SetConfig(cfg *certmagic.Config) { // we find the right place to do that just once and then re-use? // PreCheck implements the certmagic.PreChecker interface. -func (m *ACMEIssuer) PreCheck(names []string, interactive bool) error { - return certmagic.NewACMEManager(m.magic, m.template).PreCheck(names, interactive) +func (m *ACMEIssuer) PreCheck(ctx context.Context, names []string, interactive bool) error { + return certmagic.NewACMEManager(m.magic, m.template).PreCheck(ctx, names, interactive) } // Issue obtains a certificate for the given csr. @@ -195,8 +194,8 @@ func (m *ACMEIssuer) IssuerKey() string { } // Revoke revokes the given certificate. -func (m *ACMEIssuer) Revoke(ctx context.Context, cert certmagic.CertificateResource) error { - return certmagic.NewACMEManager(m.magic, m.template).Revoke(ctx, cert) +func (m *ACMEIssuer) Revoke(ctx context.Context, cert certmagic.CertificateResource, reason int) error { + return certmagic.NewACMEManager(m.magic, m.template).Revoke(ctx, cert, reason) } // onDemandAskRequest makes a request to the ask URL @@ -227,16 +226,6 @@ func onDemandAskRequest(ask string, name string) error { return nil } -// ExternalAccountBinding contains information for -// binding an external account to an ACME account. -type ExternalAccountBinding struct { - // The key identifier. - KeyID string `json:"key_id,omitempty"` - - // The HMAC. - HMAC string `json:"hmac,omitempty"` -} - // Interface guards var ( _ certmagic.PreChecker = (*ACMEIssuer)(nil) diff --git a/modules/caddytls/automation.go b/modules/caddytls/automation.go index 37d5010..502a631 100644 --- a/modules/caddytls/automation.go +++ b/modules/caddytls/automation.go @@ -22,12 +22,11 @@ import ( "github.com/caddyserver/caddy/v2" "github.com/caddyserver/certmagic" - "github.com/go-acme/lego/v3/challenge" + "github.com/mholt/acmez" "go.uber.org/zap" ) -// AutomationConfig designates configuration for the -// construction and use of ACME clients. +// AutomationConfig governs the automated management of TLS certificates. type AutomationConfig struct { // The list of automation policies. The first matching // policy will be applied for a given certificate/name. @@ -208,6 +207,7 @@ func (ap *AutomationPolicy) Provision(tlsApp *TLS) error { OnDemand: ond, Storage: storage, Issuer: ap.Issuer, // if nil, certmagic.New() will create one + Logger: tlsApp.logger, } if rev, ok := ap.Issuer.(certmagic.Revoker); ok { template.Revoker = rev @@ -244,6 +244,7 @@ type ChallengesConfig struct { // not enabled by default. This is the only challenge // type which does not require a direct connection // to Caddy from an external server. + // // NOTE: DNS providers are currently being upgraded, // and this API is subject to change, but should be // stabilized soon. @@ -281,6 +282,7 @@ type TLSALPNChallengeConfig struct { } // DNSChallengeConfig configures the ACME DNS challenge. +// // NOTE: This API is still experimental and is subject to change. type DNSChallengeConfig struct { // The DNS provider module to use which will manage @@ -290,7 +292,10 @@ type DNSChallengeConfig struct { // The TTL of the TXT record used for the DNS challenge. TTL caddy.Duration `json:"ttl,omitempty"` - provider challenge.Provider + // How long to wait for DNS record to propagate. + PropagationTimeout caddy.Duration `json:"propagation_timeout,omitempty"` + + solver acmez.Solver } // OnDemandConfig configures on-demand TLS, for obtaining diff --git a/modules/caddytls/connpolicy.go b/modules/caddytls/connpolicy.go index fec1fe2..7eda002 100644 --- a/modules/caddytls/connpolicy.go +++ b/modules/caddytls/connpolicy.go @@ -22,12 +22,12 @@ import ( "strings" "github.com/caddyserver/caddy/v2" - "github.com/go-acme/lego/v3/challenge/tlsalpn01" + "github.com/mholt/acmez" ) -// ConnectionPolicies is an ordered group of connection policies; -// the first matching policy will be used to configure TLS -// connections at handshake-time. +// ConnectionPolicies govern the establishment of TLS connections. It is +// an ordered group of connection policies; the first matching policy will +// be used to configure TLS connections at handshake-time. type ConnectionPolicies []*ConnectionPolicy // Provision sets up each connection policy. It should be called @@ -229,13 +229,13 @@ func (p *ConnectionPolicy) buildStandardTLSConfig(ctx caddy.Context) error { // ensure ALPN includes the ACME TLS-ALPN protocol var alpnFound bool for _, a := range p.ALPN { - if a == tlsalpn01.ACMETLS1Protocol { + if a == acmez.ACMETLS1Protocol { alpnFound = true break } } if !alpnFound { - cfg.NextProtos = append(cfg.NextProtos, tlsalpn01.ACMETLS1Protocol) + cfg.NextProtos = append(cfg.NextProtos, acmez.ACMETLS1Protocol) } // min and max protocol versions diff --git a/modules/caddytls/dnssolver.go b/modules/caddytls/dnssolver.go deleted file mode 100644 index c8a9c3a..0000000 --- a/modules/caddytls/dnssolver.go +++ /dev/null @@ -1,113 +0,0 @@ -// 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" - "fmt" - "sync" - "time" - - "github.com/go-acme/lego/v3/challenge" - "github.com/go-acme/lego/v3/challenge/dns01" - "github.com/libdns/libdns" -) - -// TODO: this is borrowed from https://github.com/mholt/acme - once we -// switch to that acme library, this file will go away - -// solver is a type that makes libdns providers usable as ACME challenge solvers. -type solver struct { - recordManager - - TTL time.Duration - - txtRecords map[string]libdns.Record // keyed by challenge token - txtRecordsMu sync.Mutex -} - -func (s *solver) Present(domain, token, keyAuth string) error { - fqdn, value := dns01.GetRecord(domain, keyAuth) - - rec := libdns.Record{ - Type: "TXT", - Name: fqdn, - Value: value, - TTL: s.TTL, - } - - zone, err := dns01.FindZoneByFqdn(fqdn) - if err != nil { - return fmt.Errorf("could not determine zone for domain %q: %v", fqdn, err) - } - - results, err := s.recordManager.AppendRecords(context.TODO(), zone, []libdns.Record{rec}) - if err != nil { - return err - } - if len(results) != 1 { - return fmt.Errorf("expected one record, got %d: %v", len(results), results) - } - - // keep this record handy so we can clean it up more efficiently - s.txtRecordsMu.Lock() - if s.txtRecords == nil { - s.txtRecords = make(map[string]libdns.Record) - } - s.txtRecords[keyAuth] = results[0] - s.txtRecordsMu.Unlock() - - // TODO: check for record propagation before continuing (accordig to config) - - return nil -} - -func (s *solver) CleanUp(domain, token, keyAuth string) error { - fqdn, _ := dns01.GetRecord(domain, keyAuth) - authZone, err := dns01.FindZoneByFqdn(fqdn) - if err != nil { - return err - } - - // retrieve the record we created - s.txtRecordsMu.Lock() - txtRec, ok := s.txtRecords[keyAuth] - if !ok { - s.txtRecordsMu.Unlock() - return fmt.Errorf("no memory of presenting a DNS record for %v", domain) - } - s.txtRecordsMu.Unlock() - - // clean up the record - _, err = s.recordManager.DeleteRecords(context.TODO(), authZone, []libdns.Record{txtRec}) - if err != nil { - return err - } - - // once it has been successfully cleaned up, we can forget about it - s.txtRecordsMu.Lock() - delete(s.txtRecords, keyAuth) - s.txtRecordsMu.Unlock() - - return nil -} - -// recordManager defines the set of operations required for ACME challenges. -type recordManager interface { - libdns.RecordAppender - libdns.RecordDeleter -} - -var _ challenge.Provider = (*solver)(nil) diff --git a/modules/caddytls/tls.go b/modules/caddytls/tls.go index b2d05be..8178026 100644 --- a/modules/caddytls/tls.go +++ b/modules/caddytls/tls.go @@ -87,6 +87,7 @@ func (t *TLS) Provision(ctx caddy.Context) error { GetConfigForCert: func(cert certmagic.Certificate) (*certmagic.Config, error) { return t.getConfigForName(cert.Names[0]), nil }, + Logger: t.logger.Named("cache"), } if t.Automation != nil { cacheOpts.OCSPCheckInterval = time.Duration(t.Automation.OCSPCheckInterval) @@ -95,6 +96,9 @@ func (t *TLS) Provision(ctx caddy.Context) error { if t.Cache != nil { cacheOpts.Capacity = t.Cache.Capacity } + if cacheOpts.Capacity <= 0 { + cacheOpts.Capacity = 10000 + } t.certCache = certmagic.NewCache(cacheOpts) // certificate loaders @@ -172,6 +176,7 @@ func (t *TLS) Provision(ctx caddy.Context) error { // commands like validate can be a better test magic := certmagic.New(t.certCache, certmagic.Config{ Storage: ctx.Storage(), + Logger: t.logger, }) for _, loader := range t.certificateLoaders { certs, err := loader.LoadCertificates() @@ -412,13 +417,13 @@ func (t *TLS) cleanStorageUnits() { } // start with the default storage - certmagic.CleanStorage(t.ctx.Storage(), options) + certmagic.CleanStorage(t.ctx, t.ctx.Storage(), options) // then clean each storage defined in ACME automation policies if t.Automation != nil { for _, ap := range t.Automation.Policies { if ap.storage != nil { - certmagic.CleanStorage(ap.storage, options) + certmagic.CleanStorage(t.ctx, ap.storage, options) } } } -- cgit v1.2.3