[X] Yes, I'm using a binary release within 2 latest releases.
[X] Yes, I've searched similar issues on GitHub and didn't find any.
[X] Yes, I've included all information below (version, config, etc).
What did you expect to see?
Example using a google resolver, which returns the CNAME lower cased, not triggering the bug.
Lego correctly detects the CNAME in both the preparation and cleaning stage.
lego -a -d mycert.lego.lovedns.ch --email gryphius@lovedns.ch --dns.resolvers=8.8.8.8 --dns rfc2136 run
2023/07/19 08:28:25 [INFO] [mycert.lego.lovedns.ch] acme: Obtaining bundled SAN certificate
2023/07/19 08:28:25 [INFO] [mycert.lego.lovedns.ch] AuthURL: https://acme-v02.api.letsencrypt.org/acme/authz-v3/246936055727
2023/07/19 08:28:25 [INFO] [mycert.lego.lovedns.ch] acme: Could not find solver for: tls-alpn-01
2023/07/19 08:28:25 [INFO] [mycert.lego.lovedns.ch] acme: Could not find solver for: http-01
2023/07/19 08:28:25 [INFO] [mycert.lego.lovedns.ch] acme: use dns-01 solver
2023/07/19 08:28:25 [INFO] [mycert.lego.lovedns.ch] acme: Preparing to solve DNS-01
2023/07/19 08:28:25 [INFO] Found CNAME entry for "_acme-challenge.mycert.lego.lovedns.ch.": "_acme-challenge.mycert.acme-delegation.lovedns.ch."
2023/07/19 08:28:26 [INFO] [mycert.lego.lovedns.ch] acme: Cleaning DNS-01 challenge
2023/07/19 08:28:26 [INFO] Found CNAME entry for "_acme-challenge.mycert.lego.lovedns.ch.": "_acme-challenge.mycert.acme-delegation.lovedns.ch."
What did you see instead?
Lego does not follow a CNAME if the domain name in the answer section of the DNS response has upper case characters.
In a DNS response, the server must return the QUERY section in the same case than the client requested. However, the ANSWER section may have diffent casing.
Example, querying google resolvers for the zone cut of _acme-challenge.mycert.lego.lovedns.ch , with intentional upper case characters in the query shows that the case of the query is copied to both the query section and the answer section:
dig soa _acme-challenge.Mycert.Lego.Lovedns.ch @8.8.8.8
; <<>> DiG 9.18.16 <<>> soa _acme-challenge.Mycert.Lego.Lovedns.ch @8.8.8.8
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 11551
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 1, AUTHORITY: 1, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 512
;; QUESTION SECTION:
;_acme-challenge.Mycert.Lego.Lovedns.ch. IN SOA
;; ANSWER SECTION:
_acme-challenge.Mycert.Lego.Lovedns.ch. 20 IN CNAME _acme-challenge.mycert.acme-delegation.Lovedns.ch.
;; AUTHORITY SECTION:
acme-delegation.Lovedns.ch. 0 IN SOA bind.Lovedns.ch. Lego.Lovedns.ch. 4 10800 3600 604800 3600
;; Query time: 52 msec
;; SERVER: 8.8.8.8#53(8.8.8.8) (UDP)
;; WHEN: Wed Jul 19 08:07:13 CEST 2023
;; MSG SIZE rcvd: 161
Asking a ISC BIND resolver exactly the same question however returns the ANSWER section with different casing:
dig soa _acme-challenge.Mycert.Lego.Lovedns.ch @dns.switch.ch
; <<>> DiG 9.18.16 <<>> soa _acme-challenge.Mycert.Lego.Lovedns.ch @dns.switch.ch
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 4879
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 1, AUTHORITY: 1, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
; COOKIE: 184a956a9c96572a0100000064b780373a72d0f736c57559 (good)
;; QUESTION SECTION:
;_acme-challenge.Mycert.Lego.Lovedns.ch. IN SOA
;; ANSWER SECTION:
_acme-challenge.MYCERT.lego.lovedns.ch. 20 IN CNAME _acme-challenge.mycert.acme-delegation.lovedns.ch.
;; AUTHORITY SECTION:
acme-delegation.lovedns.ch. 0 IN SOA bind.lovedns.ch. lego.lovedns.ch. 4 10800 3600 604800 3600
;; Query time: 38 msec
;; SERVER: 2001:620:0:ff::2#53(dns.switch.ch) (UDP)
;; WHEN: Wed Jul 19 08:18:31 CEST 2023
;; MSG SIZE rcvd: 268
Resolver and authoritative servers
may modify the casing inadvertently
through
Packet Compression
Caching choices
When lego encounters such a response where the casing in the ANSWER section does not match its query, it will not follow it. See logs section below.
How do you use lego?
Binary
Reproduction steps
To trigger the bug, all of the following must be true:
the challenge record must be delegated using a CNAME record
the challenge FQDN is stored in the zone file with any upper case characters (Example: _acme-challenge.MYCERT 20 IN CNAME _acme-challenge.mycert.acme-delegation.lovedns.ch. )
the authoritative name server hosting that zone returns these records as-is, with upper case characters (Example: ISC BIND)
the resolver forwards these records with upper case characters to the client (Example: ISC BIND with a cold cache)
Version of lego
lego version 4.12.3 darwin/amd64
Logs
There are two versions how this bug may manifest itself.
Version 1: Asking a BIND resolver directly causes Lego to detect the CNAME in the cleaning stage, but not the preparation stage:
% lego -a -d mycert.lego.lovedns.ch --email gryphius@lovedns.ch --dns.resolvers=127.0.0.1:5300 --dns rfc2136 run
2023/07/19 09:30:11 [INFO] [mycert.lego.lovedns.ch] acme: Obtaining bundled SAN certificate
2023/07/19 09:30:12 [INFO] [mycert.lego.lovedns.ch] AuthURL: https://acme-v02.api.letsencrypt.org/acme/authz-v3/246949376697
2023/07/19 09:30:12 [INFO] [mycert.lego.lovedns.ch] acme: Could not find solver for: tls-alpn-01
2023/07/19 09:30:12 [INFO] [mycert.lego.lovedns.ch] acme: Could not find solver for: http-01
2023/07/19 09:30:12 [INFO] [mycert.lego.lovedns.ch] acme: use dns-01 solver
2023/07/19 09:30:12 [INFO] [mycert.lego.lovedns.ch] acme: Preparing to solve DNS-01
2023/07/19 09:30:12 [INFO] [mycert.lego.lovedns.ch] acme: Cleaning DNS-01 challenge
2023/07/19 09:30:12 [INFO] Found CNAME entry for "_acme-challenge.mycert.lego.lovedns.ch.": "_acme-challenge.mycert.acme-delegation.lovedns.ch."
Version 2: Asking a dnsdist load balancer in front of the resolver causes Lego to not detect the CNAME at all:
% lego -a -d mycert.lego.lovedns.ch --email gryphius@lovedns.ch --dns.resolvers=dns.switch.ch --dns rfc2136 run
2023/07/19 08:26:17 [INFO] [mycert.lego.lovedns.ch] acme: Obtaining bundled SAN certificate
2023/07/19 08:26:18 [INFO] [mycert.lego.lovedns.ch] AuthURL: https://acme-v02.api.letsencrypt.org/acme/authz-v3/246935631227
2023/07/19 08:26:18 [INFO] [mycert.lego.lovedns.ch] acme: Could not find solver for: tls-alpn-01
2023/07/19 08:26:18 [INFO] [mycert.lego.lovedns.ch] acme: Could not find solver for: http-01
2023/07/19 08:26:18 [INFO] [mycert.lego.lovedns.ch] acme: use dns-01 solver
2023/07/19 08:26:18 [INFO] [mycert.lego.lovedns.ch] acme: Preparing to solve DNS-01
2023/07/19 08:26:18 [INFO] [mycert.lego.lovedns.ch] acme: Cleaning DNS-01 challenge
2023/07/19 08:26:18 [WARN] [mycert.lego.lovedns.ch] acme: cleaning up failed: rfc2136: failed to remove: DNS update failed: server replied: REFUSED
Explanation for the difference: Caching. Apparently Lego sends two identical CNAME queries to the resolver. Once during the preparation stage and once during the challenge cleanup. With a cold cache, the BIND resolver answers the first query with upper case characters in the answer section. The second query is answered from cache with lower case characters.
With a dnsdist load balancer in front of the resolver, both queries are answered with upper case characters, most likely due to dnsdist's Packet Cache
Welcome
What did you expect to see?
Example using a google resolver, which returns the CNAME lower cased, not triggering the bug. Lego correctly detects the CNAME in both the preparation and cleaning stage.
What did you see instead?
Lego does not follow a CNAME if the domain name in the answer section of the DNS response has upper case characters.
In a DNS response, the server must return the QUERY section in the same case than the client requested. However, the ANSWER section may have diffent casing.
Example, querying google resolvers for the zone cut of
_acme-challenge.mycert.lego.lovedns.ch
, with intentional upper case characters in the query shows that the case of the query is copied to both the query section and the answer section:Asking a ISC BIND resolver exactly the same question however returns the ANSWER section with different casing:
BIND does this intentionally, as described here
When lego encounters such a response where the casing in the ANSWER section does not match its query, it will not follow it. See logs section below.
How do you use lego?
Binary
Reproduction steps
To trigger the bug, all of the following must be true:
_acme-challenge.MYCERT 20 IN CNAME _acme-challenge.mycert.acme-delegation.lovedns.ch.
)Version of lego
Logs
There are two versions how this bug may manifest itself.
Version 1: Asking a BIND resolver directly causes Lego to detect the CNAME in the cleaning stage, but not the preparation stage:
Version 2: Asking a dnsdist load balancer in front of the resolver causes Lego to not detect the CNAME at all:
Explanation for the difference: Caching. Apparently Lego sends two identical CNAME queries to the resolver. Once during the preparation stage and once during the challenge cleanup. With a cold cache, the BIND resolver answers the first query with upper case characters in the answer section. The second query is answered from cache with lower case characters. With a dnsdist load balancer in front of the resolver, both queries are answered with upper case characters, most likely due to dnsdist's Packet Cache
Go environment (if applicable)
No response