1
0
mirror of https://github.com/go-acme/lego.git synced 2025-01-11 06:10:00 +02:00

feat: add dns.propagation-rns option (#2284)

This commit is contained in:
Ludovic Fernandez 2024-09-26 21:02:39 +02:00 committed by GitHub
parent d2898e1706
commit c704ba5832
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 129 additions and 60 deletions

View File

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"net" "net"
"strings" "strings"
"time"
"github.com/miekg/dns" "github.com/miekg/dns"
) )
@ -23,23 +24,47 @@ func WrapPreCheck(wrap WrapPreCheckFunc) ChallengeOption {
} }
} }
// DisableCompletePropagationRequirement obsolete.
// Deprecated: use DisableAuthoritativeNssPropagationRequirement instead.
func DisableCompletePropagationRequirement() ChallengeOption { func DisableCompletePropagationRequirement() ChallengeOption {
return DisableAuthoritativeNssPropagationRequirement()
}
func DisableAuthoritativeNssPropagationRequirement() ChallengeOption {
return func(chlg *Challenge) error { return func(chlg *Challenge) error {
chlg.preCheck.requireCompletePropagation = false chlg.preCheck.requireAuthoritativeNssPropagation = false
return nil return nil
} }
} }
func RecursiveNSsPropagationRequirement() ChallengeOption {
return func(chlg *Challenge) error {
chlg.preCheck.requireRecursiveNssPropagation = true
return nil
}
}
func PropagationWaitOnly(wait time.Duration) ChallengeOption {
return WrapPreCheck(func(domain, fqdn, value string, check PreCheckFunc) (bool, error) {
time.Sleep(wait)
return true, nil
})
}
type preCheck struct { type preCheck struct {
// checks DNS propagation before notifying ACME that the DNS challenge is ready. // checks DNS propagation before notifying ACME that the DNS challenge is ready.
checkFunc WrapPreCheckFunc checkFunc WrapPreCheckFunc
// require the TXT record to be propagated to all authoritative name servers // require the TXT record to be propagated to all authoritative name servers
requireCompletePropagation bool requireAuthoritativeNssPropagation bool
// require the TXT record to be propagated to all recursive name servers
requireRecursiveNssPropagation bool
} }
func newPreCheck() preCheck { func newPreCheck() preCheck {
return preCheck{ return preCheck{
requireCompletePropagation: true, requireAuthoritativeNssPropagation: true,
} }
} }
@ -53,32 +78,43 @@ func (p preCheck) call(domain, fqdn, value string) (bool, error) {
// checkDNSPropagation checks if the expected TXT record has been propagated to all authoritative nameservers. // checkDNSPropagation checks if the expected TXT record has been propagated to all authoritative nameservers.
func (p preCheck) checkDNSPropagation(fqdn, value string) (bool, error) { func (p preCheck) checkDNSPropagation(fqdn, value string) (bool, error) {
// Initial attempt to resolve at the recursive NS // Initial attempt to resolve at the recursive NS (require to get CNAME)
r, err := dnsQuery(fqdn, dns.TypeTXT, recursiveNameservers, true) r, err := dnsQuery(fqdn, dns.TypeTXT, recursiveNameservers, true)
if err != nil { if err != nil {
return false, err return false, err
} }
if !p.requireCompletePropagation {
return true, nil
}
if r.Rcode == dns.RcodeSuccess { if r.Rcode == dns.RcodeSuccess {
fqdn = updateDomainWithCName(r, fqdn) fqdn = updateDomainWithCName(r, fqdn)
} }
if p.requireRecursiveNssPropagation {
_, err = checkNameserversPropagation(fqdn, value, recursiveNameservers, false)
if err != nil {
return false, err
}
}
if !p.requireAuthoritativeNssPropagation {
return true, nil
}
authoritativeNss, err := lookupNameservers(fqdn) authoritativeNss, err := lookupNameservers(fqdn)
if err != nil { if err != nil {
return false, err return false, err
} }
return checkAuthoritativeNss(fqdn, value, authoritativeNss) return checkNameserversPropagation(fqdn, value, authoritativeNss, true)
} }
// checkAuthoritativeNss queries each of the given nameservers for the expected TXT record. // checkNameserversPropagation queries each of the given nameservers for the expected TXT record.
func checkAuthoritativeNss(fqdn, value string, nameservers []string) (bool, error) { func checkNameserversPropagation(fqdn, value string, nameservers []string, addPort bool) (bool, error) {
for _, ns := range nameservers { for _, ns := range nameservers {
r, err := dnsQuery(fqdn, dns.TypeTXT, []string{net.JoinHostPort(ns, "53")}, false) if addPort {
ns = net.JoinHostPort(ns, "53")
}
r, err := dnsQuery(fqdn, dns.TypeTXT, []string{ns}, false)
if err != nil { if err != nil {
return false, err return false, err
} }

View File

@ -72,7 +72,7 @@ func TestCheckAuthoritativeNss(t *testing.T) {
t.Parallel() t.Parallel()
ClearFqdnCache() ClearFqdnCache()
ok, _ := checkAuthoritativeNss(test.fqdn, test.value, test.ns) ok, _ := checkNameserversPropagation(test.fqdn, test.value, test.ns, true)
assert.Equal(t, test.expected, ok, test.fqdn) assert.Equal(t, test.expected, ok, test.fqdn)
}) })
} }
@ -106,7 +106,7 @@ func TestCheckAuthoritativeNssErr(t *testing.T) {
t.Parallel() t.Parallel()
ClearFqdnCache() ClearFqdnCache()
_, err := checkAuthoritativeNss(test.fqdn, test.value, test.ns) _, err := checkNameserversPropagation(test.fqdn, test.value, test.ns, true)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.Error(), test.error) assert.Contains(t, err.Error(), test.error)
}) })

View File

@ -1,6 +1,7 @@
package cmd package cmd
import ( import (
"fmt"
"time" "time"
"github.com/go-acme/lego/v4/certificate" "github.com/go-acme/lego/v4/certificate"
@ -11,38 +12,40 @@ import (
// Flag names. // Flag names.
const ( const (
flgDomains = "domains" flgDomains = "domains"
flgServer = "server" flgServer = "server"
flgAcceptTOS = "accept-tos" flgAcceptTOS = "accept-tos"
flgEmail = "email" flgEmail = "email"
flgCSR = "csr" flgCSR = "csr"
flgEAB = "eab" flgEAB = "eab"
flgKID = "kid" flgKID = "kid"
flgHMAC = "hmac" flgHMAC = "hmac"
flgKeyType = "key-type" flgKeyType = "key-type"
flgFilename = "filename" flgFilename = "filename"
flgPath = "path" flgPath = "path"
flgHTTP = "http" flgHTTP = "http"
flgHTTPPort = "http.port" flgHTTPPort = "http.port"
flgHTTPProxyHeader = "http.proxy-header" flgHTTPProxyHeader = "http.proxy-header"
flgHTTPWebroot = "http.webroot" flgHTTPWebroot = "http.webroot"
flgHTTPMemcachedHost = "http.memcached-host" flgHTTPMemcachedHost = "http.memcached-host"
flgHTTPS3Bucket = "http.s3-bucket" flgHTTPS3Bucket = "http.s3-bucket"
flgTLS = "tls" flgTLS = "tls"
flgTLSPort = "tls.port" flgTLSPort = "tls.port"
flgDNS = "dns" flgDNS = "dns"
flgDNSDisableCP = "dns.disable-cp" flgDNSDisableCP = "dns.disable-cp"
flgDNSPropagationWait = "dns.propagation-wait" flgDNSPropagationWait = "dns.propagation-wait"
flgDNSResolvers = "dns.resolvers" flgDNSPropagationDisableANS = "dns.propagation-disable-ans"
flgHTTPTimeout = "http-timeout" flgDNSPropagationRNS = "dns.propagation-rns"
flgDNSTimeout = "dns-timeout" flgDNSResolvers = "dns.resolvers"
flgPEM = "pem" flgHTTPTimeout = "http-timeout"
flgPFX = "pfx" flgDNSTimeout = "dns-timeout"
flgPFXPass = "pfx.pass" flgPEM = "pem"
flgPFXFormat = "pfx.format" flgPFX = "pfx"
flgCertTimeout = "cert.timeout" flgPFXPass = "pfx.pass"
flgOverallRequestLimit = "overall-request-limit" flgPFXFormat = "pfx.format"
flgUserAgent = "user-agent" flgCertTimeout = "cert.timeout"
flgOverallRequestLimit = "overall-request-limit"
flgUserAgent = "user-agent"
) )
func CreateFlags(defaultPath string) []cli.Flag { func CreateFlags(defaultPath string) []cli.Flag {
@ -147,11 +150,19 @@ func CreateFlags(defaultPath string) []cli.Flag {
}, },
&cli.BoolFlag{ &cli.BoolFlag{
Name: flgDNSDisableCP, Name: flgDNSDisableCP,
Usage: fmt.Sprintf("(deprecated) use %s instead.", flgDNSPropagationDisableANS),
},
&cli.BoolFlag{
Name: flgDNSPropagationDisableANS,
Usage: "By setting this flag to true, disables the need to await propagation of the TXT record to all authoritative name servers.", Usage: "By setting this flag to true, disables the need to await propagation of the TXT record to all authoritative name servers.",
}, },
&cli.BoolFlag{
Name: flgDNSPropagationRNS,
Usage: "By setting this flag to true, use all the recursive nameservers to check the propagation of the TXT record.",
},
&cli.DurationFlag{ &cli.DurationFlag{
Name: flgDNSPropagationWait, Name: flgDNSPropagationWait,
Usage: "By setting this flag, disables all the propagation checks and uses a wait duration instead.", Usage: "By setting this flag, disables all the propagation checks of the TXT record and uses a wait duration instead.",
}, },
&cli.StringSliceFlag{ &cli.StringSliceFlag{
Name: flgDNSResolvers, Name: flgDNSResolvers,

View File

@ -118,8 +118,9 @@ func setupTLSProvider(ctx *cli.Context) challenge.Provider {
} }
func setupDNS(ctx *cli.Context, client *lego.Client) error { func setupDNS(ctx *cli.Context, client *lego.Client) error {
if ctx.IsSet(flgDNSDisableCP) && ctx.Bool(flgDNSDisableCP) && ctx.IsSet(flgDNSPropagationWait) { err := checkPropagationExclusiveOptions(ctx)
return fmt.Errorf("'%s' and '%s' are mutually exclusive", flgDNSDisableCP, flgDNSPropagationWait) if err != nil {
return err
} }
wait := ctx.Duration(flgDNSPropagationWait) wait := ctx.Duration(flgDNSPropagationWait)
@ -138,15 +139,14 @@ func setupDNS(ctx *cli.Context, client *lego.Client) error {
dns01.CondOption(len(servers) > 0, dns01.CondOption(len(servers) > 0,
dns01.AddRecursiveNameservers(dns01.ParseNameservers(ctx.StringSlice(flgDNSResolvers)))), dns01.AddRecursiveNameservers(dns01.ParseNameservers(ctx.StringSlice(flgDNSResolvers)))),
dns01.CondOption(ctx.Bool(flgDNSDisableCP), dns01.CondOption(ctx.Bool(flgDNSDisableCP) || ctx.Bool(flgDNSPropagationDisableANS),
dns01.DisableCompletePropagationRequirement()), dns01.DisableAuthoritativeNssPropagationRequirement()),
dns01.CondOption(ctx.IsSet(flgDNSPropagationWait), dns01.WrapPreCheck( dns01.CondOption(ctx.Duration(flgDNSPropagationWait) > 0,
func(domain, fqdn, value string, check dns01.PreCheckFunc) (bool, error) { dns01.PropagationWaitOnly(wait)),
time.Sleep(wait)
return true, nil dns01.CondOption(ctx.Bool(flgDNSPropagationRNS),
}, dns01.RecursiveNSsPropagationRequirement()),
)),
dns01.CondOption(ctx.IsSet(flgDNSTimeout), dns01.CondOption(ctx.IsSet(flgDNSTimeout),
dns01.AddDNSTimeout(time.Duration(ctx.Int(flgDNSTimeout))*time.Second)), dns01.AddDNSTimeout(time.Duration(ctx.Int(flgDNSTimeout))*time.Second)),
@ -154,3 +154,23 @@ func setupDNS(ctx *cli.Context, client *lego.Client) error {
return err return err
} }
func checkPropagationExclusiveOptions(ctx *cli.Context) error {
if ctx.IsSet(flgDNSDisableCP) {
log.Println("The flag '%s' is deprecated use '%s' instead.", flgDNSDisableCP, flgDNSPropagationDisableANS)
}
if (isSetBool(ctx, flgDNSDisableCP) || isSetBool(ctx, flgDNSPropagationDisableANS)) && ctx.IsSet(flgDNSPropagationWait) {
return fmt.Errorf("'%s' and '%s' are mutually exclusive", flgDNSPropagationDisableANS, flgDNSPropagationWait)
}
if isSetBool(ctx, flgDNSPropagationRNS) && ctx.IsSet(flgDNSPropagationWait) {
return fmt.Errorf("'%s' and '%s' are mutually exclusive", flgDNSPropagationRNS, flgDNSPropagationWait)
}
return nil
}
func isSetBool(ctx *cli.Context, name string) bool {
return ctx.IsSet(name) && ctx.Bool(name)
}

View File

@ -39,8 +39,10 @@ GLOBAL OPTIONS:
--tls Use the TLS-ALPN-01 challenge to solve challenges. Can be mixed with other types of challenges. (default: false) --tls Use the TLS-ALPN-01 challenge to solve challenges. Can be mixed with other types of challenges. (default: false)
--tls.port value Set the port and interface to use for TLS-ALPN-01 based challenges to listen on. Supported: interface:port or :port. (default: ":443") --tls.port value Set the port and interface to use for TLS-ALPN-01 based challenges to listen on. Supported: interface:port or :port. (default: ":443")
--dns value Solve a DNS-01 challenge using the specified provider. Can be mixed with other types of challenges. Run 'lego dnshelp' for help on usage. --dns value Solve a DNS-01 challenge using the specified provider. Can be mixed with other types of challenges. Run 'lego dnshelp' for help on usage.
--dns.disable-cp By setting this flag to true, disables the need to await propagation of the TXT record to all authoritative name servers. (default: false) --dns.disable-cp (deprecated) use dns.propagation-disable-ans instead. (default: false)
--dns.propagation-wait value By setting this flag, disables all the propagation checks and uses a wait duration instead. (default: 0s) --dns.propagation-disable-ans By setting this flag to true, disables the need to await propagation of the TXT record to all authoritative name servers. (default: false)
--dns.propagation-rns By setting this flag, use all the recursive nameservers to check the propagation of the TXT record. (default: false)
--dns.propagation-wait value By setting this flag, disables all the propagation checks of the TXT record and uses a wait duration instead. (default: 0s)
--dns.resolvers value [ --dns.resolvers value ] Set the resolvers to use for performing (recursive) CNAME resolving and apex domain determination. For DNS-01 challenge verification, the authoritative DNS server is queried directly. Supported: host:port. The default is to use the system resolvers, or Google's DNS resolvers if the system's cannot be determined. --dns.resolvers value [ --dns.resolvers value ] Set the resolvers to use for performing (recursive) CNAME resolving and apex domain determination. For DNS-01 challenge verification, the authoritative DNS server is queried directly. Supported: host:port. The default is to use the system resolvers, or Google's DNS resolvers if the system's cannot be determined.
--http-timeout value Set the HTTP timeout value to a specific value in seconds. (default: 0) --http-timeout value Set the HTTP timeout value to a specific value in seconds. (default: 0)
--dns-timeout value Set the DNS timeout value to a specific value in seconds. Used only when performing authoritative name server queries. (default: 10) --dns-timeout value Set the DNS timeout value to a specific value in seconds. Used only when performing authoritative name server queries. (default: 10)

View File

@ -94,7 +94,7 @@ func TestChallengeDNS_Client_Obtain(t *testing.T) {
err = client.Challenge.SetDNS01Provider(provider, err = client.Challenge.SetDNS01Provider(provider,
dns01.AddRecursiveNameservers([]string{":8053"}), dns01.AddRecursiveNameservers([]string{":8053"}),
dns01.DisableCompletePropagationRequirement()) dns01.DisableAuthoritativeNssPropagationRequirement())
require.NoError(t, err) require.NoError(t, err)
reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true}) reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})