BIND named 'grants' using external authenticator

It might be the season for issuing Let's Encrypt certificates, in any
case both [1]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 [2]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
[3]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 [4]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 [5]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 [6]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 [7]BIND
source tree, but [8]it works fine for demonstration purposes, and I've
written a [9]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.examp
le, type=TXT at address 127.0.0.2

The key string contains the actual TSIG keyname used, its algorithm
(in this case is [10]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:

 * [11]DNS Zone Setup for ACME DNS-01 challenges
 * [12]Secure Let's Encrypt ACME DNS challenges with BIND
 * [13]DNS alias mode for [14]acme.sh
 * [15]RFC 2136 for Lego
 * [16]tserv.c, experimental authentication server for named external
grant (Unix domain socket)

References

  1. https://twitter.com/dlangille
  2. https://dan.langille.org/2020/12/19/creating-a-very-specific-txt-only-nsupdate-connection-for-lets-encrypt/
  3. https://www.isc.org/bind/
  4. https://jpmens.net/2012/06/29/dynamic-dns-updates-using-gss-tsig-and-kerberos/
  5. https://letsencrypt.org/docs/challenge-types/#dns-01-challenge
  6. https://bind9.readthedocs.io/en/v9_16_10/reference.html#dynamic-update-policies
  7. https://www.isc.org/bind/
  8. https://gist.github.com/jpmens/801710784f8704a2fbc6a6335b46b8d7
  9. https://gist.github.com/jpmens/7a3bc4bb5818b933ebb9c6abd0ea1b8e
 10. https://gitlab.isc.org/isc-projects/bind9/-/blob/main/lib/dns/include/dst/dst.h
 11. https://certhub.readthedocs.io/en/latest/practice/dns.html
 12. https://fanf.dreamwidth.org/123294.html
 13. https://github.com/acmesh-official/acme.sh/wiki/DNS-alias-mode
 14. https://github.com/acmesh-official/acme.sh
 15. https://go-acme.github.io/lego/dns/rfc2136/
 16. https://gist.github.com/jpmens/7a3bc4bb5818b933ebb9c6abd0ea1b8e