====== letsencrypt with dns challenge - i.e. for non public pages and wildcard certificates ====== By default, letsencrypt's ''certbot'' uses a method where it verifies control over a webpage by prvoiding a challenge keyword via http to the letsencrypt server. If the webserver is however an intranet server which is not publicly accessible or if we want to generate a wildcard certificate, we need to use a DNS challenge, where ''certbot'' can prove to the letsencrypt servers, that we are in control of the DNS for our domain. there are various DNS services out there who provide an API interface wiht support for certbot, but if you, like me, run your own bind based dns server, you can do this too by using rfc2136 dynamic updates (same as is used for dynamic dns services) here is the documentation for the certbot side: https://certbot-dns-rfc2136.readthedocs.io/en/stable/ in the following I will describe how i have set this up for my domains, assuming my domain is called ''example.ch'' and I want to create a wildcard certificate for ''example.ch'' and ''www.example.ch'' ===== bind9 configuration ===== somehwere in your bind configs you should find your ''zone'' definition. we need to add a ''update-policy'' to the zone for ''example.ch'' that will allow us to update a ''TXT'' record for ''_acme-challenge.example.ch'' and ''_acme-challenge.www.example.ch'' as well. While you could use ''allow-update'' I highly discurage that, because if your TSIG key gets stolen, the attacker would have full control over your domain and could add new host entries as well as txt records to create SSH certificates, and we don't want that! By using a ''update-policy'' we can define more granular, waht a specific key is allowed to do. here is the zone definition for example.ch zone "example.ch" { type master; file "/etc/bind/db.example.ch"; notify yes; also-notify { 111.111.111.111; }; # ip of additional DNS server(s) update-policy { grant letsencrypt.example.ch. name _acme-challenge.example.ch. txt; grant letsencrypt.example.ch. name _acme-challenge.www.example.ch. txt; }; allow-query { any; }; }; ''letsencrypt.example.ch.'' (notice the dot at the end) is the name of the key which will be used and what follows after name is the name of the dns entry that we grant permission to edit, ''txt'' is the type of entry we want to grant access to. certbot needs a txt entry. somewhere before this zone definition, add an include statement to include our key file which will hold the TSIG keys used to grant such access. we will create the keyfile later on. So add this line somewhere above your zone definition: include "/etc/bind/keys.conf"; next we create the key using the ''tsig-keygen'' command: tsig-keygen letsencrypt.example.ch | tee -a /etc/bind/keys.conf this should show something like this: key "letsencrypt.example.ch" { algorithm hmac-sha256; secret "V0ncaFTw0uvlSC3NlreznNRqankXkPFIr3NWTDSe7B0="; }; note down the key for later use.. in fact, copy the complete output to a file on your workstation to test this later on. this key should be editable by root only and readable to bind only, so lets change the permissions accordingly: chmod 640 /etc/bind/keys.conf chown root.bind /etc/bind/keys.conf also we need to make sure that the directory where our zone file is located and the zonefile itself is writeable by bind: chown bind.bind /etc/bind/db.example.ch /etc/bind chmod 775 /etc/bind/db.example.ch /etc/bind now lets reload the dns configuration rndc reload we should now be able to set the text entry from a remote computer.. using the key we saved above, we can now use ''nsupdate'' to test our dns configuration. so run this on your local workstation where ''key.conf'' is the key you saved before nsupdate -k key.conf -v > server ns1.example.ch > zone example.ch > update add _acme-challenge.example.ch. 60 IN TXT "test123" > send > quit this should have updated the DNS entry, so let's query it using dig: dig _acme-challenge.example.ch TXT @ns1.example.ch you should now see your "test123" in the output. now clean up and remove the test entry: nsupdate -k key.conf -v > server ns1.example.ch > zone example.ch > del _acme-challenge.example.ch. > send > quit ===== set up the certbot side ===== for certbot we need to create a ''credentials'' file which contains some info and our key. here is an example for our case: # Target DNS server IP address dns_rfc2136_server = 123.123.123.123 # Target DNS port dns_rfc2136_port = 53 # TSIG key name dns_rfc2136_name = letsencrypt.example.ch. # TSIG key secret dns_rfc2136_secret = V0ncaFTw0uvlSC3NlreznNRqankXkPFIr3NWTDSe7B0= # TSIG key algorithm dns_rfc2136_algorithm = HMAC-SHA256 pleas note,t hat the server address **can only be an IP address** there are no hostnames allowed here!(as of now.. there is an [[https://github.com/certbot/certbot/issues/6282|open issue]] to resolve this but it has been open for ages! make sure this file is well protected, i.e. make it mode ''600'' and give it to the user that runs certbot for your renewals in the future. now lets run certbot with this credentials file to request our certificate for ''example.ch'' and ''www.example.ch'' certbot certonly --dns-rfc2136 --dns-rfc2136-credentials /tmp/credentials -d "www.example.ch" -d "example.ch" you can also request a wildcard certificate (needs the same line as the example.ch entry in the zone config of bind) like so: certbot certonly --dns-rfc2136 --dns-rfc2136-credentials /tmp/credentials -d "*.example.ch" that's it! now continue with your certbot setup as usual, i.e. add a cron job that regularly renews your certificate etc.