From 5495a17748cb43887f9415a1cb8060c2153d38f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=97=E5=AD=90?= Date: Thu, 14 May 2026 19:37:37 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E8=A3=B8=E5=9F=9F?= =?UTF-8?q?=E5=90=8D=E4=B8=8E=E6=B3=9B=E5=9F=9F=E5=90=8D=E5=85=B1=E7=94=A8?= =?UTF-8?q?=20ACME=20TXT=20=E8=AE=B0=E5=BD=95=E6=97=B6=E7=9B=B8=E4=BA=92?= =?UTF-8?q?=E8=A6=86=E7=9B=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit abc.com 与 *.abc.com 共用 _acme-challenge.abc.com,需同时存在两条 TXT 记录,但 SetRecords 以 (name, type) 为单位覆盖,导致后写入的 challenge 覆盖前者,DNS 验证一直卡死。改为按记录名累积所有 keyAuth 一次性写入。 Co-Authored-By: Claude Opus 4.7 (1M context) --- pkg/acme/client.go | 7 ++++--- pkg/acme/solvers.go | 29 +++++++++++++++++++---------- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/pkg/acme/client.go b/pkg/acme/client.go index db842cdc..d7df7c41 100644 --- a/pkg/acme/client.go +++ b/pkg/acme/client.go @@ -34,9 +34,10 @@ type DnsOption struct { // UseDns 使用 DNS 接口验证 func (c *Client) UseDns(dnsType DnsType, param DNSParam, opt ...DnsOption) { solver := &dnsSolver{ - dns: dnsType, - param: param, - records: make(map[string][]libdns.Record), + dns: dnsType, + param: param, + keyAuths: make(map[string][]string), + records: make(map[string][]libdns.Record), } if len(opt) > 0 { solver.alias = opt[0].Alias diff --git a/pkg/acme/solvers.go b/pkg/acme/solvers.go index a6306a60..613d0e29 100644 --- a/pkg/acme/solvers.go +++ b/pkg/acme/solvers.go @@ -414,7 +414,8 @@ type dnsSolver struct { mu sync.Mutex dns DnsType param DNSParam - records map[string][]libdns.Record // dnsName|keyAuth → records + keyAuths map[string][]string // dnsName → keyAuth 列表 + records map[string][]libdns.Record // dnsName → 已设置的记录 alias map[string]string // DNS 验证别名映射 (domain → delegated domain) dnsServer string // DNS 验证服务器地址 skipVerify bool // 跳过解析验证 @@ -434,12 +435,20 @@ func (s *dnsSolver) Present(ctx context.Context, challenge acme.Challenge) error s.report(fmt.Sprintf("setting DNS TXT record %s", dnsName)) - rec := libdns.TXT{ - Name: libdns.RelativeName(dnsName+".", zone+"."), - Text: keyAuth, + // 同名 TXT 记录可能对应多个 challenge, SetRecords 以 (name, type) 为单位覆盖 + // 因此需把该记录名下所有 keyAuth 一次性写入,避免后者覆盖前者 + s.mu.Lock() + s.keyAuths[dnsName] = append(s.keyAuths[dnsName], keyAuth) + recs := make([]libdns.Record, 0, len(s.keyAuths[dnsName])) + for _, ka := range s.keyAuths[dnsName] { + recs = append(recs, libdns.TXT{ + Name: libdns.RelativeName(dnsName+".", zone+"."), + Text: ka, + }) } + s.mu.Unlock() - results, err := provider.SetRecords(ctx, zone+".", []libdns.Record{rec}) + results, err := provider.SetRecords(ctx, zone+".", recs) if err != nil { return fmt.Errorf("failed to set DNS record %q for %q: %w", dnsName, zone, err) } @@ -448,8 +457,7 @@ func (s *dnsSolver) Present(ctx context.Context, challenge acme.Challenge) error } s.mu.Lock() - key := dnsName + "|" + keyAuth - s.records[key] = append(s.records[key], results...) + s.records[dnsName] = results s.mu.Unlock() s.report(fmt.Sprintf("DNS TXT record %s set successfully", dnsName)) @@ -538,10 +546,11 @@ func (s *dnsSolver) CleanUp(ctx context.Context, challenge acme.Challenge) error s.report("cleaning up DNS TXT records") + // 同名 TXT 记录下的多条 challenge 记录由首次 CleanUp 一并删除 s.mu.Lock() - key := dnsName + "|" + challenge.DNS01KeyAuthorization() - records := s.records[key] - delete(s.records, key) + records := s.records[dnsName] + delete(s.records, dnsName) + delete(s.keyAuths, dnsName) s.mu.Unlock() if len(records) > 0 {