Skip to content

Repository files navigation

eDNS for libdns

Go Reference

A libdns provider for the eDNS DNS-01 challenge API (https://dns-challenge.edns.de) offered by edns.de.

It exists so that Caddy, CertMagic or any other libdns consumer can solve ACME DNS-01 challenges for zones hosted at eDNS — including wildcard certificates. For Caddy, use the module wrapper: caddy-dns/ednsde.

Scope: challenge records only

The eDNS challenge API is purpose-built for ACME. It can add and remove TXT challenge records, and it can do nothing else — in particular it has no endpoint that lists the records of a zone.

This provider therefore implements only two libdns interfaces:

InterfaceImplementedWhy
libdns.RecordAppenderyesaddChallengeRecord
libdns.RecordDeleteryesremoveChallengeRecord
libdns.RecordGetternothe API cannot list records
libdns.RecordSetternocannot be implemented without reading first

The two unsupported methods are absent rather than present-and-failing. Go interfaces are structural, so a stub returning "not supported" would still satisfy libdns.RecordGetter and move the failure from compile time into the middle of a certificate request. certmagic.DNSProvider requires only the two interfaces above, so nothing is missing for Caddy.

This is not a general-purpose DNS management library. It cannot create A records, it cannot read your zone, and it will refuse anything that is not TXT.

Usage

import (
"context""github.com/libdns/libdns"
ednsde "github.com/libdns/ednsde"
)
provider:=&ednsde.Provider{APIToken: os.Getenv("EDNS_TOKEN")}
added, err:=provider.AppendRecords(context.Background(), "example.com.", []libdns.Record{
libdns.TXT{Name: "_acme-challenge", Text: "<43-character ACME digest>"},
})

Getting an access token

  1. In the eDNS web interface, go to SSL-Zertifikate → Automation-API-Verwaltung → API-Zugang anlegen and create a token.
  2. Open the zone you want to use it for and select that token on the zone's DNS-01-Challenge tab.

Step 2 is easy to miss. Without it every request for that zone is answered with 401, with the same message as an entirely invalid token — the API does not distinguish the two cases. The error returned by this package says so.

One token can be assigned to several zones.

Behaviour worth knowing

Names are passed through verbatim. The API does not add an _acme-challenge prefix of its own; the libdns record name becomes the API's subdomain parameter unchanged. A record on the zone apex (libdns name @) is sent with the subdomain field omitted — sending it as an empty string is answered with 400.

TTL is not configurable. eDNS fixes challenge records at 300 seconds. The TTL of an input record is ignored, and the returned records report 300s, which is what is actually in the zone.

Challenge values must be 10–64 characters without whitespace. This is validated before the request goes out, so you get a useful message instead of a 400. An ACME key authorization digest is 43 characters and always fits.

Several values may share one name. This is what a SAN certificate covering both example.com and *.example.com needs, and it works. Two values on one name are no slower to publish than one.

Deleting only affects records this API created. Records added by hand in the web interface are reported as not found. Deleting something that is not there is not an error, but it is not reported as deleted either.

Propagation is not instant, and the zone's nameservers do not move together. The API confirms an add or a remove immediately; when each authoritative nameserver starts answering with it is a separate matter, and not one this package can influence.

Four records were published a minute apart and watched by querying both of the zone's nameservers directly, from two machines on different continents at the same time:

Recordvisible on ns4 aftervisible on ns3 after
117 s66 s
217 s29 s
365 s26 s
427 s27 s

So one server can be four times slower than the other on one record and faster on the next. Each flips atomically — a burst of 20 queries per server never caught a half-updated answer, and never saw two SOA serials behind one address — so this is two servers picking a change up independently, not a fleet of instances drifting apart.

The two observers agreed on every one of those eight moments to within three seconds, so where you look from does not matter; only which of the two servers you ask.

Removal is the one figure that repeats: ~307 s, almost exactly the 300 s record TTL, so eDNS lets a removed answer expire rather than pushing the change. It is also the least of it — ACME validation succeeds as long as some TXT record at the name carries the expected value, so a record left over from a previous run does no harm while it fades.

Two values on one name are no slower to publish than one.

What follows for a caller: give the propagation check a generous budget — ten minutes rather than the two-minute default. How much of the spread you actually feel depends on how your client checks. CertMagic is satisfied by the first authoritative server that carries the value, so it usually rides on the faster one; but it treats an NXDOMAIN from an earlier-listed server as "not ready" and gives up that round, so a lagging server still costs you. A check that insists on every server always pays the slower one.

A recursive resolver in the path adds your zone's SOA minimum on top. A challenge name does not exist before the first issuance, and that NXDOMAIN is cached for the SOA minimum (RFC 2308). This does not apply to CertMagic's own check, which queries the authoritative nameservers directly unless you configure resolvers; it does apply if you configure them, and to any client that checks recursively. If that is your setup, check the minimum and keep it low:

dig +short SOA example.com | awk '{print "negative TTL:", $NF}'

At 86400 a first issuance can stall for a day. At 300 it costs five minutes once.

Retries. Connection errors, 429 and 5xx are retried up to three times with a short backoff, as libdns expects. 4xx responses are returned immediately; they will not succeed on a retry.

Testing

Unit tests run everywhere and need no credentials:

go test ./...

Integration tests talk to the real API and write into a real zone. They clean up after themselves, including when an assertion fails:

EDNS_TOKEN=... EDNS_TEST_ZONE=example.com go test -tags integration -v ./...

Licence

MIT

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages