fix(acme): stop double-normalizing DNS-01 challenge params

YoonHyunWoo committed Feb 26, 2026 at 00:00 UTC 9b33c068b693cea19e60ca6139fa1c2f0f262cee
2 files changed +13 -52
portal/utils/cert/acme.go
+9 -47
@@ -6,9 +6,7 @@ import (
6 "crypto/ecdsa"
7 "crypto/elliptic"
8 "crypto/rand"
9 - "crypto/sha256"
9 "crypto/x509"
11 - "encoding/base64"
10 "encoding/pem"
11 "fmt"
12 "os"
@@ -17,7 +15,6 @@ import (
15 "time"
16
17 "github.com/go-acme/lego/v4/certificate"
20 - "github.com/go-acme/lego/v4/challenge"
18 "github.com/go-acme/lego/v4/lego"
19 "github.com/go-acme/lego/v4/providers/dns/cloudflare"
20 "github.com/go-acme/lego/v4/providers/dns/route53"
@@ -82,8 +79,7 @@ func NewACMEManager(ctx context.Context, cfg *ACMEConfig) (*ACMEManager, error)
79 }
80
81 // Set up DNS-01 challenge
85 - provider := &dnsProviderAdapter{dnsProvider: dnsProvider}
86 - if err := client.Challenge.SetDNS01Provider(provider); err != nil {
82 + if err := client.Challenge.SetDNS01Provider(dnsProvider); err != nil {
83 return nil, fmt.Errorf("set DNS01 provider: %w", err)
84 }
85
@@ -274,51 +270,17 @@ func (u *acmeUser) GetPrivateKey() crypto.PrivateKey {
270 return u.key
271 }
272
277 -// dnsProviderAdapter adapts our DNSProvider to lego's challenge.Provider interface
278 -type dnsProviderAdapter struct {
279 - dnsProvider DNSProvider
280 -}
281 -
282 -func (a *dnsProviderAdapter) Present(domain, token, keyAuth string) error {
283 - fqdn, value := extractDNS01Record(domain, keyAuth)
284 - return a.dnsProvider.Present(context.Background(), fqdn, value)
285 -}
286 -
287 -func (a *dnsProviderAdapter) CleanUp(domain, token, keyAuth string) error {
288 - fqdn, value := extractDNS01Record(domain, keyAuth)
289 - return a.dnsProvider.CleanUp(context.Background(), fqdn, value)
290 -}
291 -
292 -func (a *dnsProviderAdapter) Timeout() (timeout, interval time.Duration) {
293 - return a.dnsProvider.Timeout()
294 -}
295 -
296 -// extractDNS01Record computes the FQDN and value for DNS-01 challenge
297 -func extractDNS01Record(domain, keyAuth string) (fqdn, value string) {
298 - // DNS-01 challenge uses _acme-challenge subdomain
299 - fqdn = "_acme-challenge." + domain
300 -
301 - // Value is base64url-encoded SHA256 of keyAuth
302 - h := sha256.Sum256([]byte(keyAuth))
303 - value = base64.RawURLEncoding.EncodeToString(h[:])
304 -
305 - return fqdn, value
306 -}
307 -
308 -// Ensure dnsProviderAdapter implements challenge.Provider
309 -var _ challenge.Provider = (*dnsProviderAdapter)(nil)
310 -
273 // cloudflareProviderAdapter adapts cloudflare provider to our DNSProvider interface
274 type cloudflareProviderAdapter struct {
275 provider *cloudflare.DNSProvider
276 }
277
316 -func (a *cloudflareProviderAdapter) Present(ctx context.Context, fqdn, value string) error {
317 - return a.provider.Present(fqdn, "", value)
278 +func (a *cloudflareProviderAdapter) Present(domain, token, keyAuth string) error {
279 + return a.provider.Present(domain, token, keyAuth)
280 }
281
320 -func (a *cloudflareProviderAdapter) CleanUp(ctx context.Context, fqdn, value string) error {
321 - return a.provider.CleanUp(fqdn, "", value)
282 +func (a *cloudflareProviderAdapter) CleanUp(domain, token, keyAuth string) error {
283 + return a.provider.CleanUp(domain, token, keyAuth)
284 }
285
286 func (a *cloudflareProviderAdapter) Timeout() (timeout, interval time.Duration) {
@@ -330,12 +292,12 @@ type route53ProviderAdapter struct {
292 provider *route53.DNSProvider
293 }
294
333 -func (a *route53ProviderAdapter) Present(ctx context.Context, fqdn, value string) error {
334 - return a.provider.Present(fqdn, "", value)
295 +func (a *route53ProviderAdapter) Present(domain, token, keyAuth string) error {
296 + return a.provider.Present(domain, token, keyAuth)
297 }
298
337 -func (a *route53ProviderAdapter) CleanUp(ctx context.Context, fqdn, value string) error {
338 - return a.provider.CleanUp(fqdn, "", value)
299 +func (a *route53ProviderAdapter) CleanUp(domain, token, keyAuth string) error {
300 + return a.provider.CleanUp(domain, token, keyAuth)
301 }
302
303 func (a *route53ProviderAdapter) Timeout() (timeout, interval time.Duration) {
portal/utils/cert/cert.go
+4 -5
@@ -29,15 +29,14 @@ type Manager interface {
29 GetCACertificate(ctx context.Context) ([]byte, error)
30 }
31
32 -// DNSProvider handles DNS record management for ACME DNS-01 challenges.
32 +// DNSProvider handles DNS-01 challenge lifecycle.
33 +// Signature matches lego's challenge.Provider.
34 type DNSProvider interface {
35 // Present creates a TXT record for the DNS-01 challenge.
35 - // fqdn is the full domain name (e.g., "_acme-challenge.app1.portal.com")
36 - // value is the challenge token.
37 - Present(ctx context.Context, fqdn, value string) error
36 + Present(domain, token, keyAuth string) error
37
38 // CleanUp removes the TXT record after the challenge is complete.
40 - CleanUp(ctx context.Context, fqdn, value string) error
39 + CleanUp(domain, token, keyAuth string) error
40
41 // Timeout returns the timeout and interval for DNS propagation checking.
42 Timeout() (timeout time.Duration, interval time.Duration)