I’m in the process of designing my own centralized Let’s Encrypt solution. It was Peter Wemm’s blog post about Let’s Encrypt in the FreeBSD cluster which got me started down this road. My rough notes are this this gist.
This blog post assumes you are already familiar with Let’s Encrypt and especially with the dns-01 challenge.
This previous post might also be useful.
In this post, I’ll show you have to generate a key so you can dynamically (and remotely) update your DNS zones with dns-01 challenge records.
In this post, I’m creating a key for use with nsupdate and configuring my BIND entry so that this key can be used only for amending TXT records.
Creating a key
Keys for this purpose can be generated with dnssec-keygen, which came as part of bind-tools-9.11.1 which I have installed on this server.
EDIT 2020-12-16 When Michael W Lucas admitted that I am the source for everything he writes, it was pointed out, not by one, but two helpful tweets, that the “ability to generate TSIG keys was removed from dnssec-keygen a release or two ago”. Nowadays, you should use tsig-keygen (or ddns-confgen).
It was not clear to me what algorithms can be used (i.e. the -a parameter). I eventually decided on HMAC-SHA512 and I issued this command:
$ dnssec-keygen -a HMAC-SHA512 -b 512 -n USER me.example.org Kme.example.org.+165+27280
I’m going to show you the keys generated by the above. Usually, you keep these secret. I won’t be using what you see here. I am going to generate new keys for production.
The above command has created two files:
$ ls -l Kme.example.org.+165+27280* -rw------- 1 dan dan 121 Jun 1 01:31 Kme.example.org.+165+27280.key -rw------- 1 dan dan 232 Jun 1 01:31 Kme.example.org.+165+27280.private
Notice they are both chmod 600.
What is in those files?
$ cat Kme.example.org.+165+27280.key me.example.org. IN KEY 0 3 165 TgDiZn2IpmHjlF2B4qaHWHuBH3G4G76weISdPbt9iSNn8o9H5/yTDBNp Jvw4y6wNz8+R5BpZ9r/wqnzeMphW8Q== $ cat Kme.example.org.+165+27280.private Private-key-format: v1.3 Algorithm: 165 (HMAC_SHA512) Key: TgDiZn2IpmHjlF2B4qaHWHuBH3G4G76weISdPbt9iSNn8o9H5/yTDBNpJvw4y6wNz8+R5BpZ9r/wqnzeMphW8Q== Bits: AAA= Created: 20170601013138 Publish: 20170601013138 Activate: 20170601013138
You will notice a space in the .key file which does not appear in the .private file. Apart from this, the two keys are identical. I’m told the space is there for easier text wrapping and is ignored. See the note in the next section.
Putting the key into BIND
On the DNS server, I added this entry to /usr/local/etc/namedb/named.conf:
key me.example.org { algorithm HMAC-SHA512; secret "TgDiZn2IpmHjlF2B4qaHWHuBH3G4G76weISdPbt9iSNn8o9H5/yTDBNpJvw4y6wNz8+R5BpZ9r/wqnzeMphW8Q=="; };
Things which go wrong:
- getting the value for algorithm wrong. Use the value from the dnssec-keygen command, not from the .private file. Why? underscore vs hyphen.
- Don’t forget your semi-colons
Allowing the key to update the zone
In the zone file declaration in (usually named.conf again, as above), I specified that this key can be used to update this domain:
zone "langille.org" { type master; file "langille.org.db"; allow-transfer { AllowZoneTransfer; }; update-policy { grant me.example.org zonesub TXT; }; };
This update-policy clause grants me.example.org the ability to add any subdomain TXT record.
Why am I being so specific? Because I can.
I do not want this key used for anything but TXT records. In short, restrict it as much as you can while still allowing the objectives to be obtained.
I’d like to restrict it more, say with a wildcard, but I didn’t figure out how to do that (e.g. allow only TXT records of the form add _acme-challenge.* to be added). That is, something like this:
update-policy { grant letsencrypt wildcard _acme-challenge.* TXT; };
But I don’t think that is a valid wildcard. It is not meant for what I want. I could not get it to work last night.
The relevant documentation is at https://ftp.isc.org/isc/bind9/cur/9.9/doc/arm/Bv9ARM.ch04.html#id-1.5.6.5 and https://ftp.isc.org/isc/bind9/cur/9.9/doc/arm/Bv9ARM.ch06.html#dynamic_update_policies.
Adding a record
Here is the command I used to add a new record:
nsupdate -k /usr/home/dan/Kme.example.org.+165+27280.key server ns1.example.org zone langille.org update delete _acme-challenge.www.langille.org. update add _acme-challenge.www.langille.org. 60 IN TXT "xa4I5BUsvkxYiIr9TRITOCt7yjlx8bftKuTwu23iHq8" send quit
I just copied/pasted that into my ssh-session. This is what happened:
[dan@certs:~] $ nsupdate -k /usr/home/dan/Kme.example.org.+165+27280.key > server ns1.example.org > zone langille.org > update delete _acme-challenge.www.langille.org. > update add _acme-challenge.www.langille.org. 60 IN TXT "xa4I5BUsvkxYiIr9TRITOCt7yjlx8bftKuTwu23iHq8" > send > quit [dan@certs:~] $
On the server, I saw this:
*** /var/log/named/update.log *** 01-Jun-2017 02:03:15.124 client 10.0.0.1#49545/key me.example.org: updating zone 'langille.org/IN': delete all rrsets from name '_acme-challenge.www.langille.org' 01-Jun-2017 02:03:15.124 client 10.0.0.1#49545/key me.example.org: updating zone 'langille.org/IN': adding an RR at '_acme-challengeXX.www.langille.org' TXT
More restrictions
Even though the incoming connection requires a key, I think I should add a firewall rule so that incoming connections to port 53 are more restrictive. This is feasible only because this is a hidden dns server. Nobody else connects to it except what I control. My public DNS servers are mirrors of this server.
Finally
This is one of several steps in my Let’s Encrypt strategy. I’m doing this slowly as time permits. More blog posts are coming up soon.