It might be the season for issuing Let’s Encrypt certificates, in any case both Dan and I have (independently from each-other) being issuing certificates. I for one have been restructuring our OwnTracks servers, but that’s beside the point.

Case in point is a blog post Dan wrote. It starts off pretty badly, because amongst others he blames me for his misery, but I’ll survive. :-) One of the things Dan rightly points out in his piece is there’s a bit of manual work involved when configuring grant statements in BIND, and while that portion could be automated by generating includes or whatnot, it occurred to me that an external authenticator might do the trick.

I wrote about that years ago in the context of Kerberos, so I’ll revisit with newer software and a different use case.

Let’s assume we have a zone in which the ACME dns-01 challenges will be updated dynamically. We might have one or more keys we use centrally or decentrally to do that, and we want to ensure those updates are performed a) by a TSIG key we have issued and b) only for domains we’re currently certifying.

A BIND dynamic update policy can contain all manner of rules (there are 16 different ones in 9.17). One of these allows named to delegate the decision on whether to allow a particular update to an external process.

zone "certs.example." IN {
    type primary;
    file "tests/certs.example";

    update-policy {
        grant local-ddns zonesub ANY;
        grant "local:/tmp/auth.sock" external * TXT;
    };
};

Focus on the second rule which uses the external rule type: named sends a message to the specified Unix domain socket from which it expects a 1 or 0 response to indicate whether the update should be granted or denied.

overview

The program (daemon) which creates the Unix domain socket can do whatever it pleases to verify whether an update is permitted or not, for example query a database to see whether a particular certificate name being issues belongs to us or not. I’ve not really grown past using a copy of bin/tests/system/tsiggss/authsock.pl from the BIND source tree, but it works fine for demonstration purposes, and I’ve written a small C program which does the trick nicely. The program creates the Unix domain socket, starts listening for requests from named and returns true for all checks, but it could decide based on key, IP address, or requested name to deny (0) update-permission.

Update by acme key=acme/163/59864 for name=www.example.net.certs.example, type=TXT at address 127.0.0.2

The key string contains the actual TSIG keyname used, its algorithm (in this case is 163 (HMACSHA256)), the key_id which is pretty useless, together formatted as

snprintf(cp, size, "%s/%s/%d", namestr, algstr, dst_key_id(key));

As usual, named logs our update request:

... /key acme: updating zone 'certs.example/IN': adding an RR at 'www.example.net.certs.example' TXT "hola"

If the authenticator program is not running or does not respond, named will deny the request, and if the authenticator grants the update but it’s not in the list of permitted RR types in the grant statement, named will reject it. Both of which I find very sane.

While this does mean an extra moving part on the DNS server, it should be something which can be implemented in a safe way, but the effort is likely only worthwhile for a large number of domains which need certificates; I’m not likely to do this to replace a few handfuls of grant statements.

Further reading:

DNS, ACME, and RFC2136 :: 20 Dec 2020 :: e-mail