go-acme / lego

Let's Encrypt/ACME client and library written in Go
https://go-acme.github.io/lego/
MIT License
7.84k stars 1.01k forks source link

lego does not follow CNAME if DNS answer section contains uppercase characters #1955

Closed gryphius closed 1 year ago

gryphius commented 1 year ago

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.

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

BIND does this intentionally, as described here

Resolver and authoritative servers may modify the casing inadvertently through

  1. Packet Compression
  2. 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:

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

Go environment (if applicable)

No response