Dynamic DNS updates and letsencrypt.org ... yummy! :-)
So, not too long back, I had a wee little mini-project that gave me
motivation to start using, or at to at least seriously consider
starting to use, dynamic DNS updates.
I'll skip the full details on that, but key bit was, to be able to give
specific individual(s), ability to relatively easily and quickly edit
DNS data ... but only very specific DNS data. Notably, generally, that
of a specific subdomain (Pi.BerkeleyLUG.com) - and any subdomains
thereunder. And without giving them ability to alter any other DNS
data in or under the zone (BerkeleyLUG.com).
Without dynamic DNS updates, there would generally be two possible ways
to approach that. One would be to create delegated subdomain. That
then fully hands that off, but then burdens them with additional need
to have DNS servers, and needing static Internet Protocol addresses
(IPs) for those etc., and part of the earlier motivation was that they
didn't have static IPs. So that also would burden them with DNS
nameservers and need for static IPs - so that would at best be far from
idea.
Other approach, would be to leverage existing DNS infrastructure, and
allow them only to change data in and under the subdomain
(Pi.BerkeleyLUG.com) of existing zone (BerkeleyLUG.com). That would be
rather to quite difficult, as allowing them to only make DNS changes in
the zone file appropriate for the subdomain (and any thereunder), would
be a quite non-trivial configuration task. So, such an approach also
wasn't very feasible. Not to mention there are generally hazards with
allowing folks to edit DNS master zone file(s) - notably errors there
can cause significant problems with DNS (up to and including breakage
or other issues with up to the entire zone).
Well, enter dynamic DNS. With that, I could set up key that could be
used only to alter DNS data for the subdomain (and any subdomains
thereunder), but that key couldn't change any other DNS data at all.
So that was the general approach I pursued. To test, and before
enabling dynamic DNS on production (BerkeleyLUG.com) domain/zone, I
created a temporary test delegated subdomain, and tested dynamic DNS on
that zone (and restricting to a subdomain thereof, and any subdomains
thereunder). I then created one more such temporary test delegated
subdomain for some further testing.
After I was fully satisfied with all that, I moved on to dropping those
two temporary test delegated subdomains, and enabling dynamic DNS
updates for the production domain/zone, and setting up key that could
only change the specifically configured subdomain (Pi.BerkeleyLUG.com)
within (and any subdomains thereunder).
Anyway, after having that infrastructure in place, I thought of another
potentially very useful use case. letsencrypt.org Certificate
Authority (CA) TLS(/"SSL") certificates (certs). To validate those,
http can be used - but not for wildcard certs, or DNS and including
wildcard certs. Since many of the certs I use and prefer (if not
"require" - does make things quite a bit simpler) use wildcards, well,
DNS validation is the way to go on that. But that's been a quite
semi-manual process. Notably, I'd been doing it where requesting
client tells me what DNS data to add, I manually add it (add it to zone
file and update serial number, reload, wait for that to propagate to
the responding authoritative nameservers) ... and do that for each such
verification data that needs be added, manually doing each, getting the
cert, then removing those temporarily added no longer needed bits of
DNS data. Well, certbot(8) (client for letsencrypt.org) has
hooks/capabilities to use dynamic DNS updates and/or call programs in
place of (or as part of) the "manual" verification (and cleanup) steps.
So, by either by giving it access to suitable key (and/)or by having
it call programs to handle creating the validation data, those steps
can be automated. For DNS, such program(s) would be being given a
[sub]domain, and a specific text string, and would then create the
relevant TXT record under that [sub]domain. So, I set up a key with
the appropriate access to use dynamic DNS (and had earlier set dynamic
DNS up across all the zones where I'm master), and set up some helper
programs, to allow certbot(8) to leverage the specific key, to make the
DNS additions - and also remove them again after validations have
completed.
So ... generating keys, CSRs, requesting certs, temporarily inserting
DNS validation data, getting certs, and removing that temporarily
inserted data. That used to be a semi-manual process. The manual part
mostly being adding DNS data, checking its propagation, proceeding
through that validation step ... for numerous domains across multiple
certs. Typically that would take me, oh, maybe roughly 30 minutes or
more, maybe once every about 85 days or so (the certs have a lifetime of
only 90 days).
But with dynamic DNS, and certbot(8) then using the hooks to programs I
wrote ... full automated. What used to typically take about 30 minutes
or more about every 85 days or so ... much faster and fully automated.
Now down to ... under 4 minutes (3m21.050s)!
Anyway, I show that below - the/my command used (I did this one against
letsencrypt.org test environment, as I don't need to get new certs at
present, and test doesn't have the rate limiting of the production
environment, and also saves production from having that additional
traffic where it's not needed - otherwise production is essentially
identical).
$ time myCERTBOT_EMAIL= \
> myCERTBOT_OPTS='--preferred-challenges dns --test-cert --server
> https://acme-v02.api.letsencrypt.org/directory --manual-auth-hook
> mymanual-auth-hook --manual-cleanup-hook mymanual-cleanup-hook' \
> Getcerts \
> '*.balug.org,balug.org,*.archive.balug.org,*.beta.balug.org,*.ipv4.balug.org,*.ipv6.balug.org,*.new.balug.org,*.secure.balug.org,*.staging.balug.org,*.test.balug.org,*.php.test.balug.org,*.wiki.balug.org'
> \
> '*.sf-lug.org,sf-lug.org,*.ipv4.sf-lug.org,*.ipv6.sf-lug.org,*.sflug.org,sflug.org,*.sflug.com,sflug.com,*.sflug.net,sflug.net,sf-lug.net,www.sf-lug.net,sf-lug.com,www.sf-lug.com'
> \
> 'mpaoli.net,*.mpaoli.net,*.blackie.mpaoli.net,*.old-debian.mpaoli.net,digita…'
> \
> '*.balug.org,balug.org,*.lists.balug.org,berkeleylug.com,*.berkeleylug.com' \
> 'berkeleylug.com,*.berkeleylug.com,berkeleylug.org,*.berkeleylug.org' \
> '*.pi.berkeleylug.com,pi.berkeleylug.com'; echo "$?"
...
real 3m21.050s
user 0m23.092s
sys 0m4.706s
$ (for f in *_cert.pem; do openssl x509 -noout -text -in "$f" 2>&1
> done) |
> sed -ne 's/^ *\(Not After :.*\)$/\1/p
> /DNS:/{s/^ *//;s/, */,/g;s/DNS://g;p}'
Not After : Jun 25 16:57:17 2020 GMT
*.archive.balug.org,*.balug.org,*.beta.balug.org,*.ipv4.balug.org,*.ipv6.balug.org,*.new.balug.org,*.php.test.balug.org,*.secure.balug.org,*.staging.balug.org,*.test.balug.org,*.wiki.balug.org,balug.org
Not After : Jun 25 16:57:24 2020 GMT
*.ipv4.sf-lug.org,*.ipv6.sf-lug.org,*.sf-lug.org,*.sflug.com,*.sflug.net,*.sflug.org,sf-lug.com,sf-lug.net,sf-lug.org,sflug.com,sflug.net,sflug.org,www.sf-lug.com,www.sf-lug.net
Not After : Jun 25 16:57:28 2020 GMT
*.blackie.mpaoli.net,*.digitalwitness.org,*.mpaoli.net,*.old-debian.mpaoli.net,digitalwitness.org,mpaoli.net
Not After : Jun 25 16:57:33 2020 GMT
*.balug.org,*.berkeleylug.com,*.lists.balug.org,balug.org,berkeleylug.com
Not After : Jun 25 16:57:38 2020 GMT
*.berkeleylug.com,*.berkeleylug.org,berkeleylug.com,berkeleylug.org
Not After : Jun 25 16:57:43 2020 GMT
*.pi.berkeleylug.com,pi.berkeleylug.com
$
And at the end there, for the certs obtained I show when they expire and
the domains/names in their SAN data.
Note also in the above, the leading "> " is not literally entered, but
PS2 (essentially the shell prompting us that it needs more input to
complete the command).
So, 6 certs, each with Subject Alternative Name (SAN) having multiple
names/domains, and each with at least one, if not more, having wildcard
... all done automatically in 3m21.050s elapsed time.
Oh, and another advantage with dynamic DNS updates. Serial numbers
handled automatically - one less place for us mere mortal humans to
screw up DNS.
Also, on the helper programs ... I only wanted the invoking ID to be
able to make very certain specific changes to DNS records - the
validation records are of a very specific type and format, etc. I
could restrict the key a fair bit on that, but not fully to only and
exactly just the needed. So, I handle that via the helper programs and
a little bit of sudo. The ID doing the request never actually has
access to the key itself. Instead, via sudo, the key is used on its
behalf - and in program that further checks and restricts, such that
only and exactly needed validation data is added (and later removed) -
nothing else is allowed. Very feasible that way. Also, the helper
programs that add the DNS data - also checks that it's propagated to the
responding authoritative nameservers, before continuing (so it should
then pass letsencrypt.org's validation tests).
But back to, e.g. Pi.BerkeleyLUG.com. Were that to instead be done the
"old school" way of editing master zone file, would be quite
challenging to set something up that could only update DNS data for
that subdomain and subdomains thereunder ... but very easy to limit to
only and exactly that on dynamic DNS.
So, dynamic DNS updates ... already able to do multiple useful things
for me (and other(s)). :-)
test DNS that returns SERVFAIL? ... ! :-)
For when one may want a target DNS domain to test against that will
generally return SERVFAIL ... I didn't super easily find one out there,
so ... (at least for now) created one.
... on the master (IPs for the MNAME in SOA are on this host)
$ hostname
balug-sf-lug-v2.balug.org
$ dig +noall +answer +multiline balug.org. SOA | awk '{if(NR==1)print $5;}'
ns0.balug.org.
$ dig +short ns0.balug.org. A ns0.balug.org. AAAA
96.86.170.229
2001:470:1f05:19e::2
$ ip a s | egrep 'inet.*(96\.86\.170\.229|2001:470:1f05:19e::2)'
inet 96.86.170.229/29 brd 96.86.170.231 scope global eth0
inet6 2001:470:1f05:19e::2/64 scope global
$
So, add RR:
# nsupdate -l << \.
update add servfail.balug.org. 300 IN NS servfail.balug.org.
send
.
#
Ah yes, I'm quite starting to get used to and like/prefer dynamic DNS
update. Significantly more goof-resistant, and most of the time I don't
even have to think about the zone serial number. Which reminds me,
I do still want to add some version "control" (tracking) ... driven via
cron, so I'll at least have periodic snapshots of changes (since no
longer using ye olde manual method & manual version control). For
more recent changes, and fine-grained history of changes, logs cover
that quite well. But for the longer historical record ... wee bit 'o
gap presently to fill on that.
Automation is generally a good thing. :-)
And with no other (explicit - some DNSSEC automagic bits may be added
but we'll ignore those presently) RRs for that domain.
So, we then typically get, e.g.:
$ dig +noall +answer +comments servfail.balug.org.
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: SERVFAIL, id: 26642
;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
$
Note that not all flavors of query against servfail.balug.org. will
return SERVFAIL:
$ dig +noall +norecurse +answer +authority +comments @ns0.balug.org.
servfail.balug.org.
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 20976
;; flags: qr; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; AUTHORITY SECTION:
servfail.balug.org. 300 IN NS servfail.balug.org.
$
But in general, trying to do a recursive query on the domain for most RR
types, will give SERVFAIL. (Useful for testing, ...)