Skip to content

Fix Hetzner TXT quoting and Porkbun record IDs for DNS-01 - #459

Merged
drudge merged 1 commit into
mainfrom
fix/dns01-provider-shapes
Oct 14, 2026
Merged

drudge merged 1 commit into
mainfrom
fix/dns01-provider-shapes

Conversation

@drudge

@drudge drudge commented Oct 14, 2026

Copy link
Copy Markdown
Owner

Follow-up to #458: I checked every DNS-01 and Dynamic DNS provider's request and response shapes against its published API. Two more were wrong.

Hetzner: DNS-01 TXT value not quoted

Hetzner Cloud DNS takes RRSet record values in zone-file presentation form, and its TXT docs say "The value has to be quoted." Present sent the bare key authorization for both add_records and remove_records. It now sends strconv.Quote(value), a single <character-string> per RFC 1035 §5.1. ACME values are base64url (RFC 8555 §8.4), 43 octets, so one string is always enough and needs no escaping. This matches lego's hetznerv1 provider. Dynamic DNS is unaffected because A and AAAA values aren't quoted.

Porkbun: numeric record IDs became floats

The create response's id (and the ids from retrieveByNameType) was decoded into any and formatted with fmt.Sprint. Porkbun's v3 spec now types it as a string, but it has been sent as a JSON number, and fmt.Sprint(float64(106926659)) is 1.06926659e+08. DNS-01 cleanup would then call /dns/delete/<zone>/1.06926659e+08 and leave the challenge TXT record behind. Dynamic DNS edit and delete-by-ID had the same issue. A porkbunID type now accepts either a string or a number.

Checked and fine

Provider DNS-01 Dynamic DNS
Cloudflare result.id ✔ list/PATCH/DELETE ✔
DigitalOcean domain_record.id ✔ ✔
GoDaddy (v3) recordId top-level on 201, DELETE 204 ✔ items[], PUT replace ✔
Namecheap getHosts/setHosts XML ✔ ✔
Route 53 XML UPSERT/DELETE ✔ ✔
RFC 2136 UPDATE + TSIG ✔ ✔
OVHcloud fixed in #458 fixed in #458

Tests

  • TestHetznerProviderAddsAndRemovesOnlyChallengeValue now asserts the quoted body for both add and remove.
  • New TestPorkbunProviderCleansUpCreatedRecordByID covers string and numeric IDs. The Dynamic DNS duplicate test now mixes both.
  • Both fail on main (value":"challenge-Xy_9", delete/example.com/1.06926659e+08) and pass here. mage verify is green.

No UI changes.

Hetzner Cloud DNS takes RRSet values in presentation form, so a DNS-01
TXT value has to be a quoted character-string (RFC 1035 section 5.1);
the unquoted value was rejected or would not match on removal.

Porkbun documents record IDs as strings but has sent them as numbers.
Decoding into any turned a numeric ID into a float, so DNS-01 cleanup
deleted /dns/delete/<zone>/1.06926659e+08 and left the TXT record behind.
@drudge
drudge merged commit ca5710c into main Oct 14, 2026
4 checks passed
@drudge
drudge deleted the fix/dns01-provider-shapes branch October 14, 2026 19:19
This was referenced Oct 14, 2026
@drudge

drudge commented Oct 14, 2026

Copy link
Copy Markdown
Owner Author

Post-merge review (Gray) of #458 (dde3359) and #459 (ca5710c): LGTM, shipped in v1.7.1.

Checked: GOEXPERIMENT=jsonv2 go test -race ./internal/dnsprovider/ passes on main 1af3942. With the pre-#458 provider files swapped back in, 4 of the new or changed tests fail (OVH Present, OVH EnsureRecord, Hetzner add/remove body, Porkbun cleanup by ID), so the tests guard the fixes. Hetzner's cleanup reuses the same quoted body, so remove_records matches what add_records created. Hetzner EnsureRecord only handles A/AAAA, so it needs no quoting.

Nits (non-blocking):

  1. TestPorkbunEnsureRecordCollapsesDuplicateRecordsByID now sends "id":11 as a number, but the old any/fmt.Sprint code already turned 11 into "11", so this test still passes on the old code. A 7+ digit ID (e.g. 106926660) would make it guard the EnsureRecord path the way the new Present test does.
  2. strconv.Quote writes Go escapes (é, \x01) and not RFC 1035 \DDD for non-printable or non-ASCII bytes. ACME values are base64url, so this is fine for DNS-01. The comment could say it only holds for ASCII-printable values, in case someone reuses it for general TXT.
  3. OVH: a bare-number response (the old fixture shape) now fails to decode with a JSON error and not the "did not return the created record ID" message. That's fine if OVH always returns the object, as its API docs say.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant