Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revision Previous revision
Next revision
Previous revision
userguide:let_s_encrypt_acme-dns [2018/05/30 12:24]
Dan Brown [Configure the hook script]
userguide:let_s_encrypt_acme-dns [2020/03/19 08:17] (current)
Marc [Conclusion]
Line 7: Line 7:
 Enter [[https://​github.com/​joohoi/​acme-dns|acme-dns]]. ​ acme-dns is a limited-purpose DNS server, whose only purpose is to serve the DNS TXT records needed for Let's Encrypt validation. ​ It's a lightweight application,​ and offers an API that ACME clients can use to automatically create and destroy those TXT records. ​ It automatically generates credentials that are only valid for a single subdomain. ​ Between this and the fact that acme-dns will only serve the TXT records used for Let's Encrypt validation, in the worst case, an attacker who compromised your acme-dns API credentials would only be able to mis-issue a certificate for one of your (sub)domains. ​ This still isn't a good thing by any means, but the exposure is far less than with most other DNS APIs. Enter [[https://​github.com/​joohoi/​acme-dns|acme-dns]]. ​ acme-dns is a limited-purpose DNS server, whose only purpose is to serve the DNS TXT records needed for Let's Encrypt validation. ​ It's a lightweight application,​ and offers an API that ACME clients can use to automatically create and destroy those TXT records. ​ It automatically generates credentials that are only valid for a single subdomain. ​ Between this and the fact that acme-dns will only serve the TXT records used for Let's Encrypt validation, in the worst case, an attacker who compromised your acme-dns API credentials would only be able to mis-issue a certificate for one of your (sub)domains. ​ This still isn't a good thing by any means, but the exposure is far less than with most other DNS APIs.
  
 +The effect of this is that if you can create NS and CNAME records on your DNS provider, you can then use acme-dns to automate the DNS updates needed to get and renew your certificates (whether wildcard or not).
 ==== Background and Theory ==== ==== Background and Theory ====
-acme-dns will act as the authoritative DNS server for a subdomain of your domain. ​ If your domain is ''​example.com'',​ that subdomain will be ''​acme.example.com''​. ​ The acme-dns software will generate random hostnames within this subdomain (one random hostname for each FQDN you want to obtain a cer for), of the form ''​32f5274d-51e3-466d-bf38-eb9980e7bcf3.acme.example.com''​. ​ You'll add a CNAME record for ''​%%_acme-challenge.example.com%%'',​ pointing to the random hostname. ​ When Let's Encrypt tries to validate domain control over example.com,​ it'll look for DNS records for %%_acme-challenge.example.com%%,​ see the CNAME to 32f5274d-51e3-466d-bf38-eb9980e7bcf3.acme.example.com,​ and then query for text records for %%_acme-challenge.32f5274d-51e3-466d-bf38-eb9980e7bcf3.acme.example.com%%. ​ At that point, acme-dns will serve the challenge that your client told it to serve, validation will succeed, and Let's Encrypt will issue the cert.+acme-dns will act as the authoritative DNS server for a subdomain of your domain. ​ If your domain is ''​example.com'',​ that subdomain will be ''​acme.example.com''​. ​ The acme-dns software will generate random hostnames within this subdomain (one random hostname for each FQDN you want to obtain a cert for), of the form ''​32f5274d-51e3-466d-bf38-eb9980e7bcf3.acme.example.com''​. ​ You'll add a CNAME record for ''​%%_acme-challenge.example.com%%'',​ pointing to the random hostname. ​ When Let's Encrypt tries to validate domain control over example.com,​ it'll look for DNS records for %%_acme-challenge.example.com%%,​ see the CNAME to 32f5274d-51e3-466d-bf38-eb9980e7bcf3.acme.example.com,​ and then query for text records for %%_acme-challenge.32f5274d-51e3-466d-bf38-eb9980e7bcf3.acme.example.com%%. ​ At that point, acme-dns will serve the challenge that your client told it to serve, validation will succeed, and Let's Encrypt will issue the cert.
  
 **Note:** Once you've set up acme-dns, you can use it for any domain you control. ​ In other words, you can use a single instance to validate control over any number of domains or subdomains, just by setting individual CNAME records for each desired (sub)domain. **Note:** Once you've set up acme-dns, you can use it for any domain you control. ​ In other words, you can use a single instance to validate control over any number of domains or subdomains, just by setting individual CNAME records for each desired (sub)domain.
Line 24: Line 25:
 </​code>​ </​code>​
 You're done with DNS records for the time being. You're done with DNS records for the time being.
 +
 +==== Important Upgrade Note ====
 +If you've installed a previous version (before version 0.8) of acme-dns, and activated HTTPS for the API, you **must** remove the DNS CNAME record you created for %%_acme-challenge.acme.example.com%%. ​ Acme-dns now handles its own certificate using DNS validation, but this record will conflict with that process.
  
 ==== Installing acme-dns ==== ==== Installing acme-dns ====
-acme-dns is not available as an RPM in any of the standard repositories (to my knowledge), but its author does have a precompiled binary available for [[https://​github.com/​joohoi/​acme-dns/​releases/​download/​v0.4/​acme-dns_0.4_linux_amd64.tar.gz|download]]. Download this package onto your server, extract it using ''​tar zxf'',​ and move the acme-dns binary to ''/​usr/​local/​acme-dns/​acme-dns''​. 
  
-Create a configuration file at ''/​etc/​acme-dns/​config.cfg''​ using your favorite text editor. Its contents should look like this:+Install the [[:​danb35_repository|danb35 repo]], then run:
 <​code>​ <​code>​
-[general] +yum --enablerepo=danb35 install nethserver-acme-dns 
-# dns interface +</​code>​
-listen ​"​$EXTERNAL_IP:​1053"​ +
-# protocol, "​udp",​ "​udp4",​ "​udp6"​ or "​tcp",​ "​tcp4",​ "​tcp6"​ +
-protocol = "​udp"​ +
-# domain name to serve the requests off of +
-domain = "acme.example.com"​ +
-# zone name server +
-nsname = "​ns1.acme.example.com"​ +
-# admin email address, where @ is substituted with . +
-nsadmin = "​admin.example.com"​ +
-# predefined records served in addition to the TXT +
-records = [ +
-    # default A +
-    "​acme.example.com. A $EXTERNAL_IP",​ +
-    # A +
-    "​ns1.acme.example.com. A $EXTERNAL_IP",​ +
-    "​ns2.acme.example.com. A $EXTERNAL_IP",​ +
-    # NS +
-    "​acme.example.com. NS ns1.acme.example.com.",​ +
-    "​acme.example.com. NS ns2.acme.example.com.",​ +
-+
-# debug messages from CORS etc +
-debug = false+
  
-[database] +=== Configuration === 
-# Database engine to use, sqlite3 or postgres +The nethserver-acme-dns ​RPM should configure everything properly, with the possible exception of your external IP address  If all of the following are true, then the templates should set this correctly:
-engine ​"​sqlite3"​ +
-# Connection string, filename for sqlite3 and postgres://​$username:​$password@$host/​$db_name for postgres +
-# Please note that the default Docker image uses path /​var/​lib/​acme-dns/acme-dns.db for sqlite3 +
-connection = "/​etc/​acme-dns/​acme-dns.db"​ +
-# connection = "​postgres://​user:password@localhost/​acmedns_db"​+
  
-[api] +  * You have oneand only onered interface 
-# domain name to listen requests formandatory if using tls = "​letsencrypt"​ +  Your red interface is configured to use a static IP address 
-api_domain = ""​ +  * Your red interface is configured ​to use a **public** static IP address 
-# listen ip eg. 127.0.0.1 +  * The public static IP address on your red interface is reachable ​from the Internet
-ip = "​0.0.0.0"​ +
-# disable registration endpoint +
-disable_registration = false +
-# autocert HTTP port, eg. 80 for answering Let's Encrypt HTTP-01 challenges. Mandatory if using tls = "​letsencrypt"​. +
-# autocert_port = "​80"​ +
-# listen port, eg. 443 for default HTTPS +
-port = "​8675"​ +
-# possible values: "​letsencrypt",​ "​cert",​ "​none"​ +
-tls = "​none"​ +
-only used if tls = "​cert"​ +
-#​tls_cert_privkey = "/​etc/​tls/​example.org/​privkey.pem"​ +
-#​tls_cert_fullchain = "/​etc/​tls/​example.org/​fullchain.pem"​ +
-# CORS AllowOriginswildcards can be used +
-corsorigins = [ +
-    "​*+
-+
-use HTTP header to get the client ip +
-use_header = false +
-# header name to pull the ip address ​/ list of ip addresses ​from +
-header_name = "​X-Forwarded-For"​+
  
-[logconfig] +If any of these is not trueyou’ll need to set the address manually. To do thisdo ''​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''​.
-# logging level: "​error"​"​warning"​"​info"​ or "​debug"​ +
-loglevel = "​debug"​ +
-# possible values: stdoutTODO file & integrations +
-logtype = "​stdout"​ +
-# file path for logfile TODO +
-# logfile = "./acme-dns.log" +
-# format, either "​json"​ or "​text"​ +
-logformat = "​text"​ +
-</​code>​+
  
-Nextyou’ll need to create ​service file for systemdUsing your favorite text editorcreate ​''​/​etc/​systemd/​system/​acme-dns.service'' ​with the contents below:+=== Testing === 
 +== TLS Certificate == 
 +By defaultthis module is configured ​to obtain ​TLS certificate from the Let's Encrypt staging server, in order to avoid exceeding the Let's Encrypt rate limits To ensure that a certificate has been issuedrun ''​%%openssl s_client ​-connect localhost:​8675%%''​.  At the beginning of the output, you should see this:
 <​code>​ <​code>​
-[Unit] +[root@neth staging]# openssl s_client ​-connect acme.example.com:8675 
-Description=acme-dns Service +CONNECTED(00000003) 
-After=network.target +depth=1 CN = Fake LE Intermediate X1 
- +verify error:num=20:unable to get local issuer certificate 
-[Service] +--- 
-Type=simple +Certificate chain 
-ExecStart=/usr/local/acme-dns/acme-dns + 0 s:/CN=acme.example.com 
- +   i:/CN=Fake LE Intermediate X1 
-[Install] + 1 s:/CN=Fake LE Intermediate X1 
-WantedBy=multi-user.target+   i:/CN=Fake LE Root X1 
 +---
 </​code>​ </​code>​
- +The inclusion of "Fake LE Root X1" ​and "Fake LE Intermediate X1" in the certificate chain shows that acme-dns ​is using a certificate from the staging environmentwhich further shows that all the steps in the issuance process are working. ​ To change ​to using a "​production"​ certificate,​ run ''​config setprop ​acme-dns-api TLSType letsencrypt''​ followed by ''​signal-event nethserver-acme-dns-update''​.  Then re-run ​the ''​openssl''​ command from above. ​ The output should now look like this:
-You can then enable ​and start the service by running ''​systemctl enable --now acme-dns''​. +
- +
-Nextwe’ll need to open port 53 on the Internet (red) interface and redirect it to port 1053 for acme-dns ​to listen to it. Create ​''​/etc/e-smith/​templates-custom/​etc/​shorewall/​rules/​95acme'' ​with the following contents:+
 <​code>​ <​code>​
-REDIRECT ​ net  1053  tcp  53 +[root@neth ~]# openssl s_client -connect localhost:​8675 
-REDIRECT ​ net  1053  udp  53+CONNECTED(00000003) 
 +depth=2 O = Digital Signature Trust Co., CN = DST Root CA X3 
 +verify return:1 
 +depth=1 C = US, O = Let's Encrypt, CN = Let's Encrypt Authority X3 
 +verify return:1 
 +depth=0 CN = acme.example.com 
 +verify return:1 
 +--- 
 +Certificate chain 
 + 0 s:/​CN=acme.example.com 
 +   ​i:/​C=US/​O=Let'​s Encrypt/​CN=Let'​s Encrypt Authority X3 
 + 1 s:/​C=US/​O=Let'​s Encrypt/​CN=Let'​s Encrypt Authority X3 
 +   ​i:/​O=Digital Signature Trust Co./CN=DST Root CA X3 
 +---
 </​code>​ </​code>​
 +The presence of "​Let'​s Encrypt Authority X3" shows that you now have a certificate from the production server.
 +
 +== Testing the API ==
  
-Then run+To confirm that your acme-dns instance is up and running, ​run ''​%%curl -s -X POST https://​acme.example.com:​8675/​register | python -m json.tool%%''​. You should get something like this as your output:
 <​code>​ <​code>​
-config set fw_acme-dns service status enabled UDPPort 53 TCPPort 53 access red +
-signal-event firewall-adjust+    "​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"​ 
 +}
 </​code>​ </​code>​
  
Line 130: Line 98:
  
 ==== The hook script ==== ==== The hook script ====
-Certbot doesn’t know, on its own, how to set DNS entries on acme-dns. Fortunately,​ the author of acme-dns has provided a [[https://​github.com/​joohoi/​acme-dns-certbot-joohoi|script]] to handle this. Download the script by doing ''​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''​.+Certbot doesn’t know, on its own, how to set DNS entries on acme-dns. Fortunately,​ the author of acme-dns has provided a [[https://​github.com/​joohoi/​acme-dns-certbot-joohoi|script]] to handle this. Download the script by doing ''​%%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.+You’ll need to edit the script. Near the top, change ACMEDNS_URL to ''​%%https://acme.example.com:8675%%''​. No other changes need to be made.
  
 ==== Issuing the certificate ==== ==== Issuing the certificate ====
Line 138: Line 106:
 <​code>​ <​code>​
 certbot certonly --manual --manual-auth-hook /​etc/​letsencrypt/​acme-dns-auth.py \ certbot certonly --manual --manual-auth-hook /​etc/​letsencrypt/​acme-dns-auth.py \
-   ​--preferred-challenges dns --debug-challenges --post-hook "​signal-event certificate-update"​ \+   ​--preferred-challenges dns --debug-challenges --post-hook "/​sbin/​e-smith/​signal-event certificate-update"​ \
    -d example.com -d \*.example.com    -d example.com -d \*.example.com
 </​code>​ </​code>​
Line 157: Line 125:
 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. 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.
  
-===== Enabling HTTPS for the acme-dns API ===== +===== Renewal ​===== 
-As noted above, once you have acme-dns running, you can use it 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. +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 editorcreate ​''/​etc/​cron.daily/certbot'' ​with the following contents:
- +
-==== 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:​ +
-<​code>​ +
-certbot certonly --manual --manual-auth-hook /​etc/​letsencrypt/​acme-dns-auth.py \ +
-   ​--preferred-challenges dns --debug-challenges --post-hook "​systemctl restart acme-dns"​ \ +
-   -d acme.example.com +
-</​code>​ +
-Also as above, youll be told to create a CNAME record with your DNS host. Do thatwait a couple of minutes, then press Enter. Your certificate will be generated. +
- +
-==== Configure acme-dns for HTTPS ==== +
- +
-You’ll need to make just a few changes to /​etc/​acme-dns/​config.cfg:​ +
-<​code>​ +
-tls = "​cert"​ +
-tls_cert_privkey = "/​etc/​letsencrypt/​live/​acme.example.com/​privkey.pem"​ +
-tls_cert_fullchain = "/​etc/​letsencrypt/​live/​acme.example.com/​fullchain.pem"​ +
-</​code>​ +
- +
-Then restart the service: ''​systemctl restart acme-dns''​. +
- +
-==== Configure the 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. +
- +
-==== Adjust ​the firewall settings ==== +
- +
-We need to open another port in the firewall for this:+
 <​code>​ <​code>​
-config set fw_acme-dns-api service status enabled TCPPort 8675 access red,green +#!/bin/sh 
-signal-event firewall-adjust+/​usr/​bin/​certbot renew --quiet
 </​code>​ </​code>​
 +Certbot will run every day, and when one of its certificates is within 30 days of expiration, will attempt to renew it.
  
 ===== Conclusion ===== ===== Conclusion =====
Line 198: Line 139:
 Note that, in this configuration,​ anyone on the Internet can access the API of your acme-dns instance. If the other hosts that might be using it are on your LAN, you might want to change the access property above to just green rather than red,green. Note that, in this configuration,​ anyone on the Internet can access the API of your acme-dns instance. If the other hosts that might be using it are on your LAN, you might want to change the access property above to just green rather than red,green.
  
-{{tag>​userguide ht_v7 ht_application}}+{{tag>​userguide ht_v7 ht_application ​module}}