Pi-hole v6 was recently released and adds support for HTTPS. In /etc/pihole/pihole.toml
under the webserver.tls
configuration block, the documentation mentions that Pi-hole expects the certificate and the key in the same .pem
file.
Tag Archives: acme.sh
Proxmox VE and Let’s Encrypt with DNS-01 Validation
One of the appealing reasons for using Proxmox VE as your hypervisor is that one can configure their system to obtain a TLS certificate for https from Let’s Encrypt on a regular basis.
The Environment
At the time of writing, I am running Proxmox VE version 7.2-4. The name of the node for this article will be pve
.
I have a dynamic DNS zone (i.e. acme.example.net
) running BIND for the purposes of enabling ACME clients (acme.sh) to update the dynamic zone with the appropriate TXT
record. A CNAME
will be created in the “top-level” zone (example.net
) such that querying _acme-challenge.pve.example.net
will be answered by _acme-challenge.pve.acme.example.net
.
Configuration
In PVE, go to Datacenter > ACME and then click Add under Accounts to register an ACME account.
The next step is to add a Challenge Plugin. On the same screen click Add under Challenge Plugins.
Plugin ID: nsupdate
Validation Delay: 30
(default)
DNS API: nsupdate (RFC 2136)
NSUPDATE_KEY=/var/lib/pve/nsupdate.key
NSUPDATE_SERVER=acme.ns.example.net
Since I am using nsupdate
as the DNS API, I generate a key locally:
$ ddns-confgen -a hmac-sha256 -k pve.example.net. -q > pve.key
Transfer the key to PVE to the location specified in NSUPDATE_KEY
. Below are the user/group and permissions for reference.
# ls -l /var/lib/pve/nsupdate.key
-rw-r--r-- 1 root root 128 Jun 21 19:43 /var/lib/pve/nsupdate.key
Now go to the node itself under Datacenter. Go to System > Certificates. Under ACME, click Add.
Select DNS as the Challenge Type, select nsupdate as the plugin, and enter the PVE host’s domain.
Since we have delegated the actual records to another DNS zone, we need to make one small change to the (PVE) node’s configuration. Under the DNS Validation through CNAME Alias of the documentation:
set the
https://pve.proxmox.com/wiki/Certificate_Management#sysadmin_certs_acme_dns_challengealias
property in the Proxmox VE node configuration file
To do that, I ssh-ed into the node (as root), opened /etc/pve/local/config
in nano
, and added alias=pve.acme.example.net
to the end of the line that has the domain (in my case, it was the line that started with acmedomain0
)
# cat /etc/pve/local/config acme: account=default acmedomain0: pve.example.net,plugin=nsupdate,alias=pve.acme.example.net
Save (CTRL+O
) and Exit (CTRL+X
)
Back in the web interface, in the Certificates screen (Datacenter > Your node (pve) > System > Certificates) you should be able to select the domain and click Order Certificates Now.
At this point PVE should be able to create a TXT _acme-challenge record in the (delegated) dynamic DNS zone, Let’s Encrypt should be able to validate it, and we should be able to get an TLS certificate for https.
Delegate ACME challenges to another nameserver
I have been using Let’s Encrypt’s DNS-01 challenge to prove that I control DNS for domain names. However, obtaining certificates and renewing them have been a manual process where I added scripts to getssl
to tell me the TXT record(s) to add for that domain. I did it this way because I did not like the idea of giving a script full capability (at the time) to play with my DNS records. I do not remember where, but I learned that we can CNAME TXT records which means that a TXT record can be a CNAME pointing at another record that can be updated. Neat.
Plan
- Set up a BIND 9 server
- Set up a dynamic zone for acme.example.net
- Generate ddns keys for servers that host a service that uses an SSL certificate
- Update DNS
- Add A and NS records such that acme.example.net queries are handled by another nameserver
- Add CNAME records for
_acme-challenge.foo.example.net
to point at_acme-challenge.foo.acme.example.net
- Request certificates for a given set of domains
Most of what I did was influenced by Dan Langille’s post at https://dan.langille.org/2019/02/01/acme-domain-alias-mode/.
BIND 9
Install
On Debian, installing BIND 9 was done by executing sudo apt install bind9
.
Disable recursion
One of the things that we need to do is to disable recursion such that our nameserver is not an open resolver. Recursion is disabled by adding the following to the options
block in /etc/bind/named.conf.options
recursion no;
Set up a dynamic zone
Copy /etc/bind/db.empty
to /var/lib/bind/db.acme.example.net
and edit accordingly.
$TTL 86400
@ IN SOA a.ns.example.net. no-reply.acme.example.net. (
2020010700 ; Serial
43200 ; Refresh
7200 ; Retry
2419200 ; Expire
86400 ) ; Negative Cache TTL
;
@ IN NS a.ns.example.net.
db.acme.example.net
will be light since it is a dynamic zone.
Update /etc/bind/named.conf.local
with the dynamic zone
zone "acme.example.net." {
type master;
file "/var/lib/bind/db.acme.example.net";
};
ddns key
We will now generate a dynamic DNS key that will allow our ACME client of choice to dynamically update the dynamic zone we have created.
On Arch Linux, I used the following ddns-confgen
invocation to generate a ddns key
ddns-confgen -a hmac-sha512 -k one.example.net. -q > one.example.net.key
The contents of one.example.net.key
key "one.example.net." { algorithm hmac-sha512; secret "iXdUnXCbxJqcr3rgxYVdvHVqLmJkHW6mNZEDsXwDXtvxIfVmoABHWjN4ko4+Rz2uEmgOk+IFfqgEVGrAYTWaIA=="; };
Now that we have a ddns key, we will need to make BIND 9 aware of it.
In /etc/bind/named.conf
, we will add an include for a new file at /etc/bind/named.conf.keys
include "/etc/bind/named.conf.keys";
The content of /etc/bind/named.conf.keys
will be the contents of one.example.net.key
. Setting it up this way allows us to add named.conf.keys
to .gitignore
if we’re using configuration management such as Ansible.
Last but not least, we will update the zone configuration in /etc/bind/named.conf.local
with an update-policy
that says this key (one.example.net.) is permitted to update this zone:
zone "acme.example.net." { type master; file "/var/lib/bind/db.acme.example.net"; update-policy { grant one.example.net. zonesub TXT; }; };
With the above configuration, the one.example.net.
key would be able to update any TXT record within the acme.example.net zone. If desired, we could restrict the key to a domain such as assets.example.net, we would replace the grant line with the following:
grant one.example.net. name _acme-challenge.assets.acme.example.net. TXT;
Add DNS records
To delegate the _acme-challenge
record to a subdomain, I added the following DNS records to the nameserver serving example.net:
a.ns IN A 192.168.26.42
acme IN NS a.ns.example.net.
Now for each domain I want to delegate the _acme-challenge
record to, I added a CNAME like the following:
_acme-challenge.subdomain IN CNAME _acme-challenge.subdomain.acme.mydomain.net.
Request Certificates
At this point, we upload one.example.net.key
to our box. I stash mine as /root/nsupdate.key
. Then I use the following acme.sh invocation to request a certificate for one.example.net
.NSUPDATE_SERVER
and NSUPDATE_KEY
are environment variables to clue acme.sh
to the name server to update and the key to use.
# NSUPDATE_SERVER=a.ns.example.net NSUPDATE_KEY=/root/nsupdate.key acme.sh --issue --dns dns_nsupdate -d one.example.net --challenge-alias one.acme.example.net --server letsencrypt --reloadcmd 'systemctl reload nginx'
With acme.sh
we could use letsencrypt_test
as the --server
so that we can test to make sure things are working as expected.