mirror of
https://github.com/go-acme/lego
synced 2026-03-14 14:35:48 +01:00
Fix lints.
This commit is contained in:
parent
a45f117613
commit
07c36f6495
14 changed files with 137 additions and 52 deletions
|
|
@ -59,6 +59,7 @@ type Challenge struct {
|
|||
userSuppliedIssuerDomainName string
|
||||
persistUntil *time.Time
|
||||
recursiveNameservers []string
|
||||
authoritativeNSPort string
|
||||
|
||||
propagationTimeout time.Duration
|
||||
propagationInterval time.Duration
|
||||
|
|
@ -72,6 +73,7 @@ func NewChallenge(core *api.Core, validate ValidateFunc, opts ...ChallengeOption
|
|||
resolver: NewResolver(nil),
|
||||
preCheck: newPreCheck(),
|
||||
recursiveNameservers: DefaultNameservers(),
|
||||
authoritativeNSPort: defaultAuthoritativeNSPort,
|
||||
|
||||
propagationTimeout: DefaultPropagationTimeout,
|
||||
propagationInterval: DefaultPollingInterval,
|
||||
|
|
@ -97,7 +99,9 @@ func WithResolver(resolver *Resolver) ChallengeOption {
|
|||
if resolver == nil {
|
||||
return errors.New("dnspersist01: resolver is nil")
|
||||
}
|
||||
|
||||
chlg.resolver = resolver
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
|
@ -116,7 +120,9 @@ func WithDNSTimeout(timeout time.Duration) ChallengeOption {
|
|||
if chlg.resolver == nil {
|
||||
chlg.resolver = NewResolver(nil)
|
||||
}
|
||||
|
||||
chlg.resolver.Timeout = timeout
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
|
@ -129,7 +135,9 @@ func WithAccountURI(accountURI string) ChallengeOption {
|
|||
if accountURI == "" {
|
||||
return errors.New("dnspersist01: ACME account URI cannot be empty")
|
||||
}
|
||||
|
||||
chlg.accountURI = accountURI
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
|
@ -151,6 +159,7 @@ func WithIssuerDomainName(issuerDomainName string) ChallengeOption {
|
|||
}
|
||||
|
||||
chlg.userSuppliedIssuerDomainName = normalized
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
|
@ -165,6 +174,7 @@ func WithPersistUntil(persistUntil time.Time) ChallengeOption {
|
|||
|
||||
ts := persistUntil.UTC().Truncate(time.Second)
|
||||
chlg.persistUntil = &ts
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
|
@ -175,7 +185,9 @@ func WithPropagationTimeout(timeout time.Duration) ChallengeOption {
|
|||
if timeout <= 0 {
|
||||
return errors.New("dnspersist01: propagation timeout must be positive")
|
||||
}
|
||||
|
||||
chlg.propagationTimeout = timeout
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
|
@ -186,7 +198,9 @@ func WithPropagationInterval(interval time.Duration) ChallengeOption {
|
|||
if interval <= 0 {
|
||||
return errors.New("dnspersist01: propagation interval must be positive")
|
||||
}
|
||||
|
||||
chlg.propagationInterval = interval
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
|
@ -194,6 +208,8 @@ func WithPropagationInterval(interval time.Duration) ChallengeOption {
|
|||
// Solve validates the dns-persist-01 challenge by prompting the user to create
|
||||
// the required TXT record (if necessary) then performing propagation checks (or
|
||||
// a wait-only delay) before notifying the ACME server.
|
||||
//
|
||||
//nolint:gocyclo // challenge flow has several required branches (reuse/manual/wait/propagation/validate).
|
||||
func (c *Challenge) Solve(ctx context.Context, authz acme.Authorization) error {
|
||||
if c.resolver == nil {
|
||||
return errors.New("dnspersist01: resolver is nil")
|
||||
|
|
@ -215,6 +231,7 @@ func (c *Challenge) Solve(ctx context.Context, authz acme.Authorization) error {
|
|||
}
|
||||
|
||||
fqdn := GetAuthorizationDomainName(domain)
|
||||
|
||||
result, err := c.resolver.LookupTXT(fqdn)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -230,15 +247,16 @@ func (c *Challenge) Solve(ctx context.Context, authz acme.Authorization) error {
|
|||
}
|
||||
|
||||
if !matcher(result.Records) {
|
||||
info, err := GetChallengeInfo(domain, issuerDomainName, c.accountURI, authz.Wildcard, c.persistUntil)
|
||||
if err != nil {
|
||||
return err
|
||||
info, infoErr := GetChallengeInfo(domain, issuerDomainName, c.accountURI, authz.Wildcard, c.persistUntil)
|
||||
if infoErr != nil {
|
||||
return infoErr
|
||||
}
|
||||
|
||||
displayRecordCreationInstructions(info.FQDN, info.Value)
|
||||
err = waitForUser()
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
waitErr := waitForUser()
|
||||
if waitErr != nil {
|
||||
return waitErr
|
||||
}
|
||||
} else {
|
||||
fmt.Printf("dnspersist01: Found existing matching TXT record for %s, no need to create a new one\n", fqdn)
|
||||
|
|
@ -250,15 +268,16 @@ func (c *Challenge) Solve(ctx context.Context, authz acme.Authorization) error {
|
|||
log.Info("acme: Checking DNS-PERSIST-01 record propagation.",
|
||||
log.DomainAttr(domain), slog.String("nameservers", strings.Join(c.getRecursiveNameservers(), ",")),
|
||||
)
|
||||
|
||||
time.Sleep(interval)
|
||||
|
||||
err = wait.For("propagation", timeout, interval, func() (bool, error) {
|
||||
ok, err := c.preCheck.call(domain, fqdn, matcher, c.checkDNSPropagation)
|
||||
if !ok || err != nil {
|
||||
ok, callErr := c.preCheck.call(domain, fqdn, matcher, c.checkDNSPropagation)
|
||||
if !ok || callErr != nil {
|
||||
log.Info("acme: Waiting for DNS-PERSIST-01 record propagation.", log.DomainAttr(domain))
|
||||
}
|
||||
|
||||
return ok, err
|
||||
return ok, callErr
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -289,6 +308,7 @@ func GetChallengeInfo(domain, issuerDomainName, accountURI string, wildcard bool
|
|||
if domain == "" {
|
||||
return ChallengeInfo{}, errors.New("dnspersist01: domain cannot be empty")
|
||||
}
|
||||
|
||||
if accountURI == "" {
|
||||
return ChallengeInfo{}, errors.New("dnspersist01: ACME account URI cannot be empty")
|
||||
}
|
||||
|
|
@ -362,6 +382,7 @@ func (c *Challenge) selectIssuerDomainName(challIssuers []string, records []TXTR
|
|||
|
||||
return c.userSuppliedIssuerDomainName, nil
|
||||
}
|
||||
|
||||
for _, issuerDomainName := range sortedIssuers {
|
||||
if c.hasMatchingRecord(records, issuerDomainName, wildcard) {
|
||||
return issuerDomainName, nil
|
||||
|
|
@ -377,15 +398,19 @@ func (c *Challenge) hasMatchingRecord(records []TXTRecord, issuerDomainName stri
|
|||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if parsed.IssuerDomainName != issuerDomainName {
|
||||
continue
|
||||
}
|
||||
|
||||
if parsed.AccountURI != c.accountURI {
|
||||
continue
|
||||
}
|
||||
if wildcard && strings.ToLower(parsed.Policy) != policyWildcard {
|
||||
|
||||
if wildcard && !strings.EqualFold(parsed.Policy, policyWildcard) {
|
||||
continue
|
||||
}
|
||||
|
||||
if c.persistUntil == nil {
|
||||
if parsed.PersistUntil != nil {
|
||||
continue
|
||||
|
|
@ -395,6 +420,7 @@ func (c *Challenge) hasMatchingRecord(records []TXTRecord, issuerDomainName stri
|
|||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -43,7 +43,8 @@ func splitTXTValue(value string) []string {
|
|||
chunks = append(chunks, value[:maxTXTStringOctets])
|
||||
value = value[maxTXTStringOctets:]
|
||||
}
|
||||
if len(value) > 0 {
|
||||
|
||||
if value != "" {
|
||||
chunks = append(chunks, value)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -139,6 +139,7 @@ func TestGetChallengeInfo(t *testing.T) {
|
|||
if test.expectErr != "" {
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), test.expectErr)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -229,6 +230,7 @@ func TestWithIssuerDomainName(t *testing.T) {
|
|||
for _, test := range testCases {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
chlg := &Challenge{}
|
||||
|
||||
err := WithIssuerDomainName(test.input)(chlg)
|
||||
if test.expectErr {
|
||||
require.Error(t, err)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package dnspersist01
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
|
@ -51,12 +52,14 @@ func trimWSP(s string) string {
|
|||
|
||||
// ParseIssueValues parses an issue-value string. Unknown parameters are
|
||||
// preserved in Params.
|
||||
//
|
||||
//nolint:gocyclo // parsing and validating tagged parameters requires branching per field.
|
||||
func ParseIssueValues(value string) (IssueValue, error) {
|
||||
fields := strings.Split(value, ";")
|
||||
|
||||
issuerDomainName := trimWSP(fields[0])
|
||||
if issuerDomainName == "" {
|
||||
return IssueValue{}, fmt.Errorf("missing issuer-domain-name")
|
||||
return IssueValue{}, errors.New("missing issuer-domain-name")
|
||||
}
|
||||
|
||||
parsed := IssueValue{
|
||||
|
|
@ -69,7 +72,7 @@ func ParseIssueValues(value string) (IssueValue, error) {
|
|||
for _, raw := range fields[1:] {
|
||||
part := trimWSP(raw)
|
||||
if part == "" {
|
||||
return IssueValue{}, fmt.Errorf("empty parameter or trailing semicolon provided")
|
||||
return IssueValue{}, errors.New("empty parameter or trailing semicolon provided")
|
||||
}
|
||||
|
||||
tagValue := strings.SplitN(part, "=", 2)
|
||||
|
|
@ -79,6 +82,7 @@ func ParseIssueValues(value string) (IssueValue, error) {
|
|||
|
||||
tag := trimWSP(tagValue[0])
|
||||
val := trimWSP(tagValue[1])
|
||||
|
||||
if tag == "" {
|
||||
return IssueValue{}, fmt.Errorf("malformed parameter %q, empty tag", part)
|
||||
}
|
||||
|
|
@ -87,6 +91,7 @@ func ParseIssueValues(value string) (IssueValue, error) {
|
|||
if seenTags[key] {
|
||||
return IssueValue{}, fmt.Errorf("duplicate parameter %q", tag)
|
||||
}
|
||||
|
||||
seenTags[key] = true
|
||||
|
||||
for _, r := range val {
|
||||
|
|
@ -100,19 +105,22 @@ func ParseIssueValues(value string) (IssueValue, error) {
|
|||
switch key {
|
||||
case paramAccountURI:
|
||||
if val == "" {
|
||||
return IssueValue{}, fmt.Errorf("empty value provided for mandatory accounturi")
|
||||
return IssueValue{}, errors.New("empty value provided for mandatory accounturi")
|
||||
}
|
||||
|
||||
parsed.AccountURI = val
|
||||
case paramPolicy:
|
||||
if val != "" && strings.ToLower(val) != policyWildcard {
|
||||
if val != "" && !strings.EqualFold(val, policyWildcard) {
|
||||
val = ""
|
||||
}
|
||||
|
||||
parsed.Policy = val
|
||||
case paramPersistUntil:
|
||||
ts, err := strconv.ParseInt(val, 10, 64)
|
||||
if err != nil {
|
||||
return IssueValue{}, fmt.Errorf("malformed persistUntil timestamp %q", val)
|
||||
}
|
||||
|
||||
persistUntil := time.Unix(ts, 0).UTC()
|
||||
parsed.PersistUntil = &persistUntil
|
||||
default:
|
||||
|
|
|
|||
|
|
@ -190,10 +190,12 @@ func TestParseIssueValues(t *testing.T) {
|
|||
if test.expectErrContains != "" {
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), test.expectErrContains)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
|
||||
expected := test.expected
|
||||
expected.PersistUntil = test.expectedPersistUTC
|
||||
assert.Equal(t, expected, parsed)
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
package dnspersist01
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/net/idna"
|
||||
)
|
||||
|
||||
//nolint:gochecknoglobals // test seam for injecting IDNA conversion failures/variants.
|
||||
var issuerDomainNameToASCII = idna.Lookup.ToASCII
|
||||
|
||||
// validateIssuerDomainName validates a single issuer-domain-name according to
|
||||
|
|
@ -19,26 +21,31 @@ var issuerDomainNameToASCII = idna.Lookup.ToASCII
|
|||
// - A-label (Punycode, RFC5890)
|
||||
func validateIssuerDomainName(name string) error {
|
||||
if name == "" {
|
||||
return fmt.Errorf("issuer-domain-name cannot be empty")
|
||||
return errors.New("issuer-domain-name cannot be empty")
|
||||
}
|
||||
|
||||
if strings.ToLower(name) != name {
|
||||
return fmt.Errorf("issuer-domain-name must be lowercase")
|
||||
return errors.New("issuer-domain-name must be lowercase")
|
||||
}
|
||||
|
||||
if strings.HasSuffix(name, ".") {
|
||||
return fmt.Errorf("issuer-domain-name must not have a trailing dot")
|
||||
return errors.New("issuer-domain-name must not have a trailing dot")
|
||||
}
|
||||
|
||||
if len(name) > 253 {
|
||||
return fmt.Errorf("issuer-domain-name exceeds maximum length of 253 octets")
|
||||
return errors.New("issuer-domain-name exceeds maximum length of 253 octets")
|
||||
}
|
||||
|
||||
labels := strings.SplitSeq(name, ".")
|
||||
for label := range labels {
|
||||
if label == "" {
|
||||
return fmt.Errorf("issuer-domain-name contains an empty label")
|
||||
return errors.New("issuer-domain-name contains an empty label")
|
||||
}
|
||||
|
||||
if len(label) > 63 {
|
||||
return fmt.Errorf("issuer-domain-name label exceeds maximum length of 63 octets")
|
||||
return errors.New("issuer-domain-name label exceeds maximum length of 63 octets")
|
||||
}
|
||||
|
||||
if !isLDHLabel(label) {
|
||||
return fmt.Errorf("issuer-domain-name label %q must be a lowercase LDH label", label)
|
||||
}
|
||||
|
|
@ -48,26 +55,32 @@ func validateIssuerDomainName(name string) error {
|
|||
if err != nil {
|
||||
return fmt.Errorf("issuer-domain-name must be represented in A-label format: %w", err)
|
||||
}
|
||||
|
||||
if ascii != name {
|
||||
return fmt.Errorf("issuer-domain-name must be represented in A-label format")
|
||||
return errors.New("issuer-domain-name must be represented in A-label format")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func isLDHLabel(label string) bool {
|
||||
if len(label) == 0 {
|
||||
if label == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
if !isLowerAlphaNum(label[0]) || !isLowerAlphaNum(label[len(label)-1]) {
|
||||
return false
|
||||
}
|
||||
for i := 0; i < len(label); i++ {
|
||||
|
||||
for i := range len(label) {
|
||||
c := label[i]
|
||||
if isLowerAlphaNum(c) || c == '-' {
|
||||
continue
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
@ -86,5 +99,6 @@ func normalizeUserSuppliedIssuerDomainName(name string) (string, error) {
|
|||
if err != nil {
|
||||
return "", fmt.Errorf("normalizing supplied issuer-domain-name %q: %w", n, err)
|
||||
}
|
||||
|
||||
return ascii, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ func TestValidateIssuerDomainName_Errors(t *testing.T) {
|
|||
|
||||
func TestValidateIssuerDomainName_ErrorNonCanonicalALabel(t *testing.T) {
|
||||
originalToASCII := issuerDomainNameToASCII
|
||||
|
||||
t.Cleanup(func() {
|
||||
issuerDomainNameToASCII = originalToASCII
|
||||
})
|
||||
|
|
@ -63,6 +64,7 @@ func TestValidateIssuerDomainName_ErrorNonCanonicalALabel(t *testing.T) {
|
|||
|
||||
func TestValidateIssuerDomainName_Valid(t *testing.T) {
|
||||
originalToASCII := issuerDomainNameToASCII
|
||||
|
||||
t.Cleanup(func() {
|
||||
issuerDomainNameToASCII = originalToASCII
|
||||
})
|
||||
|
|
@ -76,12 +78,14 @@ func TestValidateIssuerDomainName_Valid(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestValidateIssuerDomainName_ErrorWrap(t *testing.T) {
|
||||
sentinelErr := errors.New("sentinel idna failure")
|
||||
|
||||
originalToASCII := issuerDomainNameToASCII
|
||||
|
||||
t.Cleanup(func() {
|
||||
issuerDomainNameToASCII = originalToASCII
|
||||
})
|
||||
|
||||
sentinelErr := errors.New("sentinel idna failure")
|
||||
issuerDomainNameToASCII = func(string) (string, error) {
|
||||
return "", sentinelErr
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,19 +33,14 @@ func fakeTXT(name, value string, ttl uint32) *dns.TXT {
|
|||
|
||||
// mockResolver modifies the default DNS resolver to use a custom network address during the test execution.
|
||||
// IMPORTANT: it modifies global variables.
|
||||
func mockResolver(t *testing.T, addr net.Addr) {
|
||||
func mockResolver(t *testing.T, addr net.Addr) string {
|
||||
t.Helper()
|
||||
|
||||
_, port, err := net.SplitHostPort(addr.String())
|
||||
require.NoError(t, err)
|
||||
|
||||
originalDefaultNameserverPort := defaultNameserverPort
|
||||
t.Cleanup(func() {
|
||||
defaultNameserverPort = originalDefaultNameserverPort
|
||||
})
|
||||
defaultNameserverPort = port
|
||||
|
||||
originalResolver := net.DefaultResolver
|
||||
|
||||
t.Cleanup(func() {
|
||||
net.DefaultResolver = originalResolver
|
||||
})
|
||||
|
|
@ -58,4 +53,6 @@ func mockResolver(t *testing.T, addr net.Addr) {
|
|||
return d.DialContext(ctx, network, addr.String())
|
||||
},
|
||||
}
|
||||
|
||||
return port
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,8 +9,7 @@ import (
|
|||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
// defaultNameserverPort used by authoritative NS.
|
||||
var defaultNameserverPort = "53"
|
||||
const defaultAuthoritativeNSPort = "53"
|
||||
|
||||
// RecordMatcher returns true when the expected record is present.
|
||||
type RecordMatcher func(records []TXTRecord) bool
|
||||
|
|
@ -133,7 +132,7 @@ func (c *Challenge) checkDNSPropagation(fqdn string, matcher RecordMatcher) (boo
|
|||
func (c *Challenge) checkNameserversPropagation(fqdn string, nameservers []string, addPort, recursive bool, matcher RecordMatcher) (bool, error) {
|
||||
for _, ns := range nameservers {
|
||||
if addPort {
|
||||
ns = net.JoinHostPort(ns, defaultNameserverPort)
|
||||
ns = net.JoinHostPort(ns, c.getAuthoritativeNSPort())
|
||||
}
|
||||
|
||||
result, err := c.resolver.lookupTXT(fqdn, []string{ns}, recursive)
|
||||
|
|
@ -149,6 +148,14 @@ func (c *Challenge) checkNameserversPropagation(fqdn string, nameservers []strin
|
|||
return true, nil
|
||||
}
|
||||
|
||||
func (c *Challenge) getAuthoritativeNSPort() string {
|
||||
if c == nil || c.authoritativeNSPort == "" {
|
||||
return defaultAuthoritativeNSPort
|
||||
}
|
||||
|
||||
return c.authoritativeNSPort
|
||||
}
|
||||
|
||||
func txtValues(records []TXTRecord) []string {
|
||||
values := make([]string, 0, len(records))
|
||||
for _, record := range records {
|
||||
|
|
|
|||
|
|
@ -44,13 +44,14 @@ func Test_preCheck_checkDNSPropagation(t *testing.T) {
|
|||
).
|
||||
Build(t)
|
||||
|
||||
mockResolver(t, addr)
|
||||
port := mockResolver(t, addr)
|
||||
|
||||
resolver := NewResolver([]string{addr.String()})
|
||||
chlg := &Challenge{
|
||||
resolver: resolver,
|
||||
preCheck: newPreCheck(),
|
||||
recursiveNameservers: ParseNameservers([]string{addr.String()}),
|
||||
authoritativeNSPort: port,
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import (
|
|||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
|
@ -15,11 +14,6 @@ import (
|
|||
|
||||
const defaultResolvConf = "/etc/resolv.conf"
|
||||
|
||||
var defaultNameservers = []string{
|
||||
"google-public-dns-a.google.com:53",
|
||||
"google-public-dns-b.google.com:53",
|
||||
}
|
||||
|
||||
// Resolver performs DNS lookups using the configured nameservers and timeout.
|
||||
type Resolver struct {
|
||||
Nameservers []string
|
||||
|
|
@ -55,12 +49,19 @@ func NewResolver(nameservers []string) *Resolver {
|
|||
func DefaultNameservers() []string {
|
||||
config, err := dns.ClientConfigFromFile(defaultResolvConf)
|
||||
if err != nil || len(config.Servers) == 0 {
|
||||
return slices.Clone(defaultNameservers)
|
||||
return defaultFallbackNameservers()
|
||||
}
|
||||
|
||||
return ParseNameservers(config.Servers)
|
||||
}
|
||||
|
||||
func defaultFallbackNameservers() []string {
|
||||
return []string{
|
||||
"google-public-dns-a.google.com:53",
|
||||
"google-public-dns-b.google.com:53",
|
||||
}
|
||||
}
|
||||
|
||||
// ParseNameservers ensures all servers have a port number.
|
||||
func ParseNameservers(servers []string) []string {
|
||||
var resolvers []string
|
||||
|
|
@ -109,6 +110,7 @@ func (r *Resolver) lookupTXT(fqdn string, nameservers []string, recursive bool)
|
|||
if _, ok := seen[name]; ok {
|
||||
return result, fmt.Errorf("CNAME loop detected for %s", name)
|
||||
}
|
||||
|
||||
seen[name] = struct{}{}
|
||||
|
||||
msg, err := dnsQueryWithTimeout(name, dns.TypeTXT, nameservers, recursive, timeout)
|
||||
|
|
@ -188,9 +190,11 @@ func dnsQueryWithTimeout(fqdn string, rtype uint16, nameservers []string, recurs
|
|||
return nil, &DNSError{Message: "empty list of nameservers"}
|
||||
}
|
||||
|
||||
var msg *dns.Msg
|
||||
var err error
|
||||
var errAll error
|
||||
var (
|
||||
msg *dns.Msg
|
||||
err error
|
||||
errAll error
|
||||
)
|
||||
|
||||
for _, ns := range nameservers {
|
||||
msg, err = sendDNSQuery(m, ns, timeout)
|
||||
|
|
@ -267,6 +271,7 @@ func (d *DNSError) Error() string {
|
|||
for _, question := range questions {
|
||||
parts = append(parts, strings.ReplaceAll(strings.TrimPrefix(question.String(), ";"), "\t", " "))
|
||||
}
|
||||
|
||||
return strings.Join(parts, ";")
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -30,9 +30,11 @@ func (a byType) Less(i, j int) bool {
|
|||
if a[i].Type == string(challenge.DNS01) && a[j].Type == string(challenge.DNSPersist01) {
|
||||
return true
|
||||
}
|
||||
|
||||
if a[i].Type == string(challenge.DNSPersist01) && a[j].Type == string(challenge.DNS01) {
|
||||
return false
|
||||
}
|
||||
|
||||
return a[i].Type > a[j].Type
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net"
|
||||
|
|
@ -22,6 +23,7 @@ import (
|
|||
"github.com/urfave/cli/v3"
|
||||
)
|
||||
|
||||
//nolint:gocyclo // challenge setup dispatch is expected to branch by enabled challenge type.
|
||||
func setupChallenges(cmd *cli.Command, client *lego.Client, account registration.User) {
|
||||
if !cmd.Bool(flgHTTP) && !cmd.Bool(flgTLS) && !cmd.IsSet(flgDNS) && !cmd.Bool(flgDNSPersist) {
|
||||
log.Fatal(fmt.Sprintf("No challenge selected. You must specify at least one challenge: `--%s`, `--%s`, `--%s`, `--%s`.", flgHTTP, flgTLS, flgDNS, flgDNSPersist))
|
||||
|
|
@ -205,9 +207,10 @@ func setupDNS(cmd *cli.Command, client *lego.Client) error {
|
|||
return err
|
||||
}
|
||||
|
||||
//nolint:gocyclo // option assembly mirrors CLI flags and challenge configuration branches.
|
||||
func setupDNSPersist(cmd *cli.Command, client *lego.Client, account registration.User) error {
|
||||
if account == nil || account.GetRegistration() == nil || account.GetRegistration().URI == "" {
|
||||
return fmt.Errorf("dns-persist-01 requires a registered account with an account URI")
|
||||
return errors.New("dns-persist-01 requires a registered account with an account URI")
|
||||
}
|
||||
|
||||
err := validateDNSPersistPropagationExclusiveOptions(cmd)
|
||||
|
|
@ -233,8 +236,10 @@ func setupDNSPersist(cmd *cli.Command, client *lego.Client, account registration
|
|||
if cmd.IsSet(flgDNSPersistResolvers) {
|
||||
resolvers := cmd.StringSlice(flgDNSPersistResolvers)
|
||||
if len(resolvers) > 0 {
|
||||
opts = append(opts, dnspersist01.WithNameservers(resolvers))
|
||||
opts = append(opts, dnspersist01.AddRecursiveNameservers(resolvers))
|
||||
opts = append(opts,
|
||||
dnspersist01.WithNameservers(resolvers),
|
||||
dnspersist01.AddRecursiveNameservers(resolvers),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ func setTXTRecordRaw(host, value string) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
|
|
@ -76,6 +77,7 @@ func clearTXTRecord(t *testing.T, host string) {
|
|||
|
||||
resp, err := http.Post("http://localhost:8055/clear-txt", "application/json", bytes.NewReader(body))
|
||||
require.NoError(t, err)
|
||||
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
|
|
@ -157,10 +159,12 @@ func waitForCLIAccountURI(ctx context.Context, email string) (string, error) {
|
|||
if os.IsNotExist(err) {
|
||||
continue
|
||||
}
|
||||
|
||||
return "", err
|
||||
}
|
||||
|
||||
var account accountFile
|
||||
|
||||
err = json.Unmarshal(content, &account)
|
||||
if err != nil {
|
||||
continue
|
||||
|
|
@ -234,6 +238,7 @@ func TestChallengeDNSPersist_Run(t *testing.T) {
|
|||
|
||||
err := os.Setenv("LEGO_CA_CERTIFICATES", "../fixtures/certs/pebble.minica.pem")
|
||||
require.NoError(t, err)
|
||||
|
||||
defer func() { _ = os.Unsetenv("LEGO_CA_CERTIFICATES") }()
|
||||
|
||||
accountURI := createCLIAccountState(t, testPersistCLIEmail)
|
||||
|
|
@ -266,28 +271,32 @@ func TestChallengeDNSPersist_Run_NewAccount(t *testing.T) {
|
|||
|
||||
err := os.Setenv("LEGO_CA_CERTIFICATES", "../fixtures/certs/pebble.minica.pem")
|
||||
require.NoError(t, err)
|
||||
|
||||
defer func() { _ = os.Unsetenv("LEGO_CA_CERTIFICATES") }()
|
||||
|
||||
txtHost := fmt.Sprintf("_validation-persist.%s", testPersistCLIDomain)
|
||||
defer clearTXTRecord(t, txtHost)
|
||||
|
||||
stdinReader, stdinWriter := io.Pipe()
|
||||
|
||||
defer func() { _ = stdinReader.Close() }()
|
||||
|
||||
errChan := make(chan error, 1)
|
||||
|
||||
go func() {
|
||||
defer func() { _ = stdinWriter.Close() }()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
accountURI, err := waitForCLIAccountURI(ctx, testPersistCLIFreshEmail)
|
||||
if err != nil {
|
||||
errChan <- fmt.Errorf("wait for account URI: %w", err)
|
||||
accountURI, waitErr := waitForCLIAccountURI(ctx, testPersistCLIFreshEmail)
|
||||
if waitErr != nil {
|
||||
errChan <- fmt.Errorf("wait for account URI: %w", waitErr)
|
||||
return
|
||||
}
|
||||
|
||||
txtValue := dnspersist01.BuildIssueValues(testPersistIssuer, accountURI, true, nil)
|
||||
|
||||
err = setTXTRecordRaw(txtHost, txtValue)
|
||||
if err != nil {
|
||||
errChan <- fmt.Errorf("set TXT record: %w", err)
|
||||
|
|
@ -299,6 +308,7 @@ func TestChallengeDNSPersist_Run_NewAccount(t *testing.T) {
|
|||
errChan <- fmt.Errorf("send enter to lego: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
errChan <- nil
|
||||
}()
|
||||
|
||||
|
|
@ -325,6 +335,7 @@ func TestChallengeDNSPersist_Renew(t *testing.T) {
|
|||
|
||||
err := os.Setenv("LEGO_CA_CERTIFICATES", "../fixtures/certs/pebble.minica.pem")
|
||||
require.NoError(t, err)
|
||||
|
||||
defer func() { _ = os.Unsetenv("LEGO_CA_CERTIFICATES") }()
|
||||
|
||||
accountURI := createCLIAccountState(t, testPersistCLIRenewEmail)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue