Acme-dns on Nethserver (now with RPM-y goodness!)

letsencrypt
v7

(Dan) #1

I previously wrote up a method to install acme-dns on your Neth server, which gives a couple of significant benefits:

  • It allows you to complete the Let’s Encrypt DNS challenge, and therefore obtain wildcard certificates, with just about any DNS provider, and
  • Even if your existing DNS provider has an API that’s supported by your preferred ACME client, acme-dns is much more limited, and therefore there’s much less exposure if your credentials should be compromised.

I’ve now prepared RPMs which should greatly simplify the process of installing and configuring acme-dns. Read on for instructions.

Caution: This module is in a pre-beta stage. It shouldn’t hurt anything, but may not behave as expected. If you notice unexpected behavior, let me know here and I’ll see what I can do.

Conventions

Throughout this guide, your domain is represented as example.com. If you have more than one domain, this will be your primary domain. If you aren’t sure which domain is primary, config show DomainName will tell you. The external IP address of your Neth server is represented as $EXTERNAL_IP. Make appropriate substitutions below.

DNS Configuration

You’ll need to start by publishing the DNS records establishing your acme-dns instance as the authoritative nameserver for acme.example.com. To do this, log in to your DNS provider and add the records below:

ns1.acme.example.com	A	$EXTERNAL_IP
ns2.acme.example.com	A	$EXTERNAL_IP
acme.example.com		NS	ns1.acme.example.com
acme.example.com		NS	ns2.acme.example.com

You’re done with DNS records for the time being.

Installing acme-dns

Begin by installing my repository; see the wiki for instructions. Then, simply yum --enablerepo=danb35 install nethserver-acme-dns. Your system will download and install the necessary packages.

Configuration

The templates should configure acme-dns appropriately. One possible exception is the external IP address on which acme-dns will listen for DNS queries. If all of the following are true, then the templates should set this correctly:

  • You have one, and only one, red interface
  • Your red interface is configured to use a static IP address
  • Your red interface is configured to use a public static IP address
  • The public static IP address on your red interface is reachable from the Internet

If any of these is not true, you’ll need to set the address manually. To do this, do config setprop acme-dns ExternalIP 1.2.3.4, replacing 1.2.3.4 with your public IP address. Then do signal-event nethserver-acme-dns-update.

Testing

To confirm that your acme-dns instance is up and running, run curl -s -X POST http://localhost:8675/register | python -m json.tool. You should get something like this as your output:

{
    "allowfrom": [],
    "fulldomain": "44255c4e-d669-41f3-a141-672a8bd859e6.acme.example.com",
    "password": "x_Trpa04HpgQ4_ZOY7LCF6z23kf6o8i-VV_4qQk4",
    "subdomain": "44255c4e-d669-41f3-a141-672a8bd859e6",
    "username": "cc2d8066-2583-4e2c-a68f-ca45810c4f31"
}

Install the hook script

Nethserver ships with certbot installed, and certbot doesn’t, by itself, know how to talk to acme-dns. Fortunately, its author has written a script to handle this. Install it by running curl -o /etc/letsencrypt/acme-dns-auth.py https://raw.githubusercontent.com/joohoi/acme-dns-certbot-joohoi/master/acme-dns-auth.py followed by chmod 0700 /etc/letsencrypt/acme-dns-auth.py. You’ll need to edit the script. Near the top, change ACMEDNS_URL to http://localhost:8675. No other changes need to be made.

Issue a certificate

All the prep work is now done, and it’s time to issue the certificate. Run

certbot certonly --manual --manual-auth-hook /etc/letsencrypt/acme-dns-auth.py \
   --preferred-challenges dns --debug-challenges --post-hook "signal-event certificate-update" \
   -d example.com -d \*.example.com

If you want more domains, add them with additional -d flags at the end. Certbot will ask you a few questions (email address, agree to TOS, share email with EFF, log IP address), and then show a message like this:

Output from acme-dns-auth.py:
Please add the following CNAME record to your main DNS zone:
_acme-challenge.example.com CNAME 32f5274d-51e3-466d-bf38-eb9980e7bcf3.acme.example.com.

Waiting for verification...

-------------------------------------------------------------------------------
Challenges loaded. Press continue to submit to CA. Pass "-v" for more info about
challenges.
-------------------------------------------------------------------------------
Press Enter to Continue

You’ll need to once again log into your DNS host and add the record specified. You’ll only need to do this once for each hostname. Wait a few minutes, then press Enter.

Enable HTTPS for acme-dns API

You can use acme-dns to validate any domain you control, as long as you can set the appropriate CNAME records. But if you’re going to be accessing its API remotely, you should really secure its API using HTTPS.

Get a certificate

We just created a wildcard certificate above, so we could use that, but it’s better practice to use a unique certificate for this service. To issue that certificate, run the following:

certbot certonly --manual --manual-auth-hook /etc/letsencrypt/acme-dns-auth.py \
   --preferred-challenges dns --debug-challenges --post-hook "signal-event nethserver-acme-dns-update" \
   -d acme.example.com

Also as above, you’ll be told to create a CNAME record with your DNS host. Do that, wait a couple of minutes, then press Enter. Your certificate will be generated.

Configure acme-dns for HTTPS

This will take a couple of configuration entries:

config setprop acme-dns-api UseTLS enabled KeyPath /etc/letsencrypt/live/acme.example.com/privkey.pem FullchainPath /etc/letsencrypt/live/acme.example.com/fullchain.pem access red,green
signal-event nethserver-acme-dns-update

Testing

As above, run curl -s -X POST https://acme.example.com:8675/register | python -m json.tool. You should get something like this as your output:

{
    "allowfrom": [],
    "fulldomain": "44255c4e-d669-41f3-a141-672a8bd859e6.acme.example.com",
    "password": "x_Trpa04HpgQ4_ZOY7LCF6z23kf6o8i-VV_4qQk4",
    "subdomain": "44255c4e-d669-41f3-a141-672a8bd859e6",
    "username": "cc2d8066-2583-4e2c-a68f-ca45810c4f31"
}

Adjust hook script

You’ll also need to update the hook script. Edit /etc/letsencrypt/acme-dns-auth.py and change ACMEDNS_URL to https://acme.example.com:8675, then save and exit.

Renewal

Part of the reason for using Let’s Encrypt is that you can automate issuance and renewal of your certificates. To do this, you’ll need to create a simple cron job. Using your favorite text editor, create /etc/cron.daily/certbot with the following contents:

#!/bin/sh
/usr/bin/certbot renew --quiet

Certbot will run every day, and when one of its certificates is within 30 days of expiration, will attempt to renew it.

TODO

  • Test, test, test
  • Make sure that the acme-dns systemd service is controlled by the status of the acmedns Nethserver service
  • Build the acme-dns binary from the Go source, rather than just packaging the author’s precompiled binary as an RPM

Certificate SSL Letsencrypt with wilcards crash ldap
(Dan) #2

On this point–it doesn’t look like it is working as it should, and I think the problem is the disagreement between the name of the service in systemd and the config database keys I’m using. The systemd service is called acme-dns, since that’s what the software is called. But hyphens aren’t valid characters in Perl variable names, so if I try to retrieve a config property of $acme-dns{foo} in a template, Perl complains. As a result, I’m using database keys of acmedns and acmednsapi, and the templates work fine with those.

So I’m probably missing something that’s already there, but I need to have the acme-dns systemd service enabled (and started) when $acmedns{status} = enabled.


(Stéphane de Labrusse) #3
${'acme-dns'}{'status'} eq 'enabled'

(Dan) #4

Is it that simple? Wow. Well, that should be easy to fix, anyway.


(Stéphane de Labrusse) #5

Tip

grep -srni 'acme\-dns' /etc/e-smith/templates

This should give back what you missed, grep is the better tools I know for a padawan developer, at least it helped me so much.

Install a nethserver with all modules and you will have an ultimate bank of example


(Dan) #6

That was it indeed. Fantastic. I’m sure it would benefit me to embark on a systematic study of Perl, but it just seems like that would take a lot of time. Though maybe not as much as trying random things I saw in a template somewhere…


(Stéphane de Labrusse) #7

test, break, try, this is what I do most of time :stuck_out_tongue:

when you want to code something, try to figure what you could search, var name is a good start.


(Dan) #8

That got me to the point of knowing that hyphens weren’t legal in Perl variable names, but not to the point of knowing what to do about it–at least yet. Probably would have found something eventually, but you beat me to it.

I still think this is something Neth should consider providing themselves, perhaps as a subscription benefit–it would avoid the issue of access to port 80, as well as enable wildcard certificates, about as simply as possible for the users. The only thing the users would need to do is set up the CNAME records with their DNS host, once for each (sub)domain. The only thing that would need to be packaged with Neth itself is the hook script.


(Dan) #9

Running into another issue, and I think this is due to how my config database keys are set up. In short, when installing software (including updates) through the software center, I get a red error message at the top of the screen indicating a problem with acme-dns. The problem appears to only be cosmetic–the software installs and runs without issues–but “big red warning banner” is a significant cosmetic issue. Looking in the log, I see

Jul  4 07:06:31 neth esmith::event[21173]: Failed to start acme-dns-api.service: Unit not found.

I use two config database keys, acme-dns and acme-dns-api. The main reason I do this is because the firewall settings will (or at least can) be different between the two–acme-dns itself must be accessible on the red interface (it must answer DNS queries from the outside world), while the API doesn’t need to be. However, there’s only one systemd service, and that’s acme-dns. Here’s how those keys are configured by default:

[root@neth ~]# config show acme-dns
acme-dns=service
    Debug=disabled
    TCPPort=53
    UDPPort=53
    access=red
    status=enabled
[root@neth ~]# config show acme-dns-api
acme-dns-api=service
    FullchainPath=/etc/letsencrypt/live/acme.familybrown.org/fullchain.pem
    KeyPath=/etc/letsencrypt/live/acme.familybrown.org/privkey.pem
    TCPPort=8675
    UseTLS=enabled
    access=red,green
    status=enabled

(OK, mostly by default–the fullchain, key, and UseTLS enabled aren’t there by default). How do I need to change this to avoid these errors?


(Markus Neuberger) #10

You may try to change the acme-dns-api to a network service instead of a service:

http://docs.nethserver.org/projects/nethserver-devel/en/v7/services.html#add-a-new-service
http://docs.nethserver.org/projects/nethserver-devel/en/v7/services.html#add-a-new-network-service


(Dan) #11

I’d thought about that, but had somehow gotten the idea that the fw_ keys were only appropriate when all you were doing was setting the firewall (the fw_ prefix probably helped me reach that conclusion). If that’s not the case, that sounds like the easy answer.


(Markus Neuberger) #12

The fw_ prefix is not mandatory, it’s just there to have an optical difference between NS controlled services and “firewall only” services.