Dehydrated ACME Client
Dehydrated is a client for signing certificates with an ACME server (as provided by Let’s Encrypt and others) implemented as a relatively simple bash-script.
pdns_api.sh, is a simple DNS hook for Dehydrated, which uses the HTTP API of a PowerDNS server to answer the challenge requests of the ACME server.
Installation
Dehydrated Shell Script
$ cd /usr/local/lib
$ sudo git clone --no-checkout https://github.com/lukas2511/dehydrated.git
$ sudo git tag
v0.1.0
v0.2.0
v0.3.0
v0.3.1
$ sudo git checkout v0.3.1
PowerDNS API Hook Script
$ cd /usr/local/lib
$ sudo git clone --no-checkout https://github.com/silkeh/pdns_api.sh.git
$ cd pdns_api.sh
v0.1.0
v0.2.0
$ sudo git checkout v0.2.0
Configuration
Dehydrated Configuration
/etc/dehydrated/config
########################################################
# This is the main config file for dehydrated #
# #
# This file is looked for in the following locations: #
# $SCRIPTDIR/config (next to this script) #
# /usr/local/etc/dehydrated/config #
# /etc/dehydrated/config #
# ${PWD}/config (in current working-directory) #
# #
# Default values of this config are in comments #
########################################################
# Resolve names to addresses of IP version only (curl).
# supported values: 4, 6
# Default: <unset>
#IP_VERSION=
# Path to certificate authority.
# Default: https://acme-v01.api.letsencrypt.org/directory
#CA="https://acme-v01.api.letsencrypt.org/directory"
CA="https://acme-staging.api.letsencrypt.org/directory"
# Path to license agreement.
# default: https://letsencrypt.org/documents/LE-SA-v1.1.1-August-1-2016.pdf
#LICENSE="https://letsencrypt.org/documents/LE-SA-v1.1.1-August-1-2016.pdf"
# Which challenge should be used? Currently http-01 and dns-01 are supported.
#CHALLENGETYPE="http-01"
CHALLENGETYPE="dns-01"
# Path to a directory containing additional config files, allowing to override
# the defaults found in the main configuration file. Additional config files
# in this directory needs to be named with a '.sh' ending.
# Default: <unset>
#CONFIG_D=
# Base directory for account key, generated certificates and list of domains
# Default: $SCRIPTDIR -- uses config directory if undefined
BASEDIR="/etc/dehydrated"
# File containing the list of domains to request certificates for.
# Default: "$BASEDIR/domains.txt"
#DOMAINS_TXT="${BASEDIR}/domains.txt"
# Output directory for generated certificates.
# Default: "${BASEDIR}/certs"
#CERTDIR="${BASEDIR}/certs"
# Directory for account keys and registration information.
#ACCOUNTDIR="${BASEDIR}/accounts"
# Output directory for challenge-tokens to be served by webserver or deployed in
# HOOK
# Default: "/var/www/dehydrated"
WELLKNOWN="/var/lib/dehydrated"
# Default key size for private keys.
# Default: "4096"
#KEYSIZE="4096"
# Path to OpenSSL configuration file
# Default: <unset> - tries to figure out system default.
OPENSSL_CNF="${BASEDIR}/openssl.cnf"
# Program or function called in certain situations
#
# After generating the challenge-response, or after failed challenge (in this
# case altname is empty). Given arguments:
# clean_challenge|deploy_challenge
# altname
# token-filename
# token-content
#
# After successfully signing certificate. Given arguments:
# deploy_cert
# domain
# path/to/privkey.pem
# path/to/cert.pem
# path/to/fullchain.pem
#
# BASEDIR and WELLKNOWN variables are exported and can be used in an external
# program.
# Default: <unset>
HOOK="/usr/local/lib/pdns_api.sh/pdns_api.sh"
# Chain clean_challenge|deploy_challenge arguments together into one hook call
# per certificate
# Default: "no"
HOOK_CHAIN="yes"
# Minimum days before expiration to automatically renew certificate.
# Default: 30
#RENEW_DAYS="30"
# Regenerate private keys instead of just signing new certificates on renewal.
# Default: "yes"
PRIVATE_KEY_RENEW="no"
# Which public key algorithm should be used?
# Supported: rsa, prime256v1 and secp384r1
# Default: "rsa"
#KEY_ALGO="rsa"
# E-mail to use during the registration.
# Default: <unset>
#CONTACT_EMAIL=
CONTACT_EMAIL=webmaster@example.net
# Lock-file location, to prevent concurrent access.
# Default: $BASEDIR/lock
LOCKFILE="/var/run/dehydrated.lock"
# Option to add CSR-flag indicating OCSP stapling to be mandatory.
# Default: "no"
OCSP_MUST_STAPLE="yes"
# PowerDNS API settings for the hook script.
# Default:
HOST=ns0.example.net
# Default:
PORT=8081 # Optional. Defaults to 8081
# Default:
KEY=secret # API key
# Default:
SERVER=localhost # Optional. Server for the API to use, usually `localhost`
# Default:
VERSION=1 # Optional. API version, 0 for anything under PowerDNS 4
# Default:
WAIT=150 # Optional. Delay for when slaves are slow
OpenSSL
$ openssl version
OpenSSL 1.0.2g 1 Mar 2016
$ sudo wget --output-document=/etc/dehydrated/openssl.cnf
https://raw.githubusercontent.com/openssl/openssl/OpenSSL_1_0_2g/apps/openssl.cnf
Domains
/etc/dehydrated/domains.txt
example.net www.example.net cloud.example.net
example.org www.example.org xmpp.example.org
mail.exammple.net mail.example.org
autoconfig.example.net autoconfig.example.org
xmpp.example.net
Increase Serials on API-Requests
$ mysql -u root -p pdns
> INSERT INTO domainmetadata (domain_id, kind, content)
VALUES (
(SELECT domains.id FROM domains WHERE domains.name = 'example.net'),
'SOA-EDIT-API',
'INCEPTION-INCREMENT'
);
> INSERT INTO domainmetadata (domain_id, kind, content)
VALUES (
(SELECT domains.id FROM domains WHERE domains.name = 'example.org'),
'SOA-EDIT-API',
'INCEPTION-INCREMENT'
);
Re-use Existing Keys
$ sudo mkdir -p /etc/dehydrated/certs/{example.org,example.net}
$ sudo cp /etc/ssl/private/example.org.key.pem /etc/dehydrated/certs/example.org/privkey.pem
$ sudo cp /etc/ssl/private/example.net.key.pem /etc/dehydrated/certs/example.net/privkey.pem
OCSP Stapling and Mail Servers
Postfix SMTP server and Dovecot IMAP server both don’t do OCSP stapling, or maybe just my mail client Thunderbird doesn’t or other MX don’t. I don’t know exactly, but the fact remains: SMTP and IMAP Connections to the mail server fail with an OpenSSL error message “alert bad certificate: SSL alert number 42”.
Since we configured Dehydrated to request certificates with the x.509 extension “OCSP must staple”, they will not work with connecting clients if the server does not staple. We therefore make an exception in the Dehydrated configuration:
/etc/dehydrated/certs/mail.example.net/config
:
;;
;; Mail servers Postfix and Dovecot don't seem to support OCSP stapling (yet?).
;;
;; dovecot:
;; imap-login: Error: SSL: Stacked error:
;; error:14094412:SSL
;; routines:ssl3_read_bytes:sslv3
;; alert bad certificate: SSL alert number 42
;;
;; postfix/submission/smtpd:
;; warning: TLS library problem: error:14094412:SSL
;; routines:ssl3_read_bytes:sslv3
;; alert bad certificate:s3_pkt.c:1472:SSL alert number 42:
;;
;; Option to add CSR-flag indicating OCSP stapling to be mandatory
;; Default: no
OCSP_MUST_STAPLE="no"
OCSP Response Stapling
TBD …
cd /etc/dehydrated/certs
openssl ocsp \
-issuer example.net/chain.pem \
-CAfile example.net/chain.pem \
-VAfile example.net/chain.pem \
-cert example.net/cert.pem \
-url http://ocsp.int-x3.letsencrypt.org/ \
-header "HOST" "ocsp.int-x3.letsencrypt.org" \
-no_nonce
Keys and Certificates File Permissions
Private keys must be carefully protected, that’s why in most cases the private key files are owned by the root user and have strict file permissions, to not allow access to anyone else. Most servers start with root privileges to open IP ports lower then 1024 and read their certificate and private keys. They lower their privileges after that and run under their own dedicated user and group.
Some servers (e.g. Prosody) have a different approach. Their user profile is part of the ssl-cert group. The ssl-cert group is given read access to private keys and certificate files. That way such a server never needs to be started or run with root privileges to begin with.
Dehydrated does not currently allow read-access to the ssl-cert group.
chgrp -R ssl-cert/etc/dehydrated/certs/*
Restarting Servers
TBD …
Nginx
Postfix
Dovecot
Prosody
Test-Run
Make sure the line CA contains the Letsencrypt staging server:
CA="https://acme-staging.api.letsencrypt.org/directory"
Run the script against the staging server:
$ sudo /usr/local/lib/dehydrated/dehydrated --cron
Publish/Update TSLA Records
Avoid “3 0 1” and “3 0 2” TLSA records with LetsEncrypt certificates
TLSA Record Types
The Certificate Usage Field
Value |
Acronym |
Short Description |
0 |
PKIX-TA |
CA constraint |
1 |
PKIX-EE |
Service certificate constraint |
2 |
DANE-TA |
Trust anchor assertion |
3 |
DANE-EE |
Domain-issued certificate |
A commonly trusted top-level CA certificate or public key.
A commonly trusted server certificate or public key.
Certificate or public key of the signing CA. No common trust required.
Domain certificate or public key. No common trust required.
The Selector Field
Value |
Acronym |
Short Description |
0 |
Cert |
Full certificate |
1 |
SPKI |
SubjectPublicKeyInfo |
The full certificate.
The public key of the certificate.
The Matching Type Field
Value |
Acronym |
Short Description |
0 |
Full |
No hash used |
1 |
SHA2-256 |
256 bit hash by SHA2 |
2 |
SHA2-512 |
512 bit hash by SHA2 |
The full certificate or public key.
SHA-256 hash of the certificate or public key
SHA-512 hash of the certificate or public key
Not Recommended:
000 - Un-hashed full trusted CA certificate, too big might change
001 - SHA2-256 hash of trusted CA certificate, might change
002 - SHA2-512 hash of trusted CA certificate, might change
010 - Un-hashed public key of trusted CA certificate, too big.
100 - Un-hashed full trusted server certificate, too big, changes on renewal.
101 - SHA2-256 hashed trusted server certificate, changes on renewal.
102 - SHA2-512 hashed trusted server certificate, changes on renewal.
110 - Un-hashed server public key, too big.
111 - SHA2-256 hashed server public key.
112 - SHA2-512 hashed server public key.
200 - Un-hashed full CA certificate, too big, might change.
201 - SHA2-256 hashed CA certificate, might change.
202 - SHA2-512 hashed CA certificate, might change.
210 - Un-hashed CA public key, too big.
300 - Un-hashed server certificate, too big, changes on renewal.
301 - SHA2-256 hashed server certificate, changes on renewal.
302 - SHA2-512 hashed server certificate, changes on renewal.
310 - Un-hashed server public key, too big.
Possible, but still big:
012 - SHA-512 hashed public key of trusted CA
212 - SHA-512 hashed CA public key.
312 - SHA-512 hashed server public key.
Recommended:
011 - SHA-256 hashed public key of trusted CA
211 - SHA-256 hashed CA public key.
311 - SHA-256 hashed server public key.
Create TLSA Records
Download:
$ sudo wget -O /usr/local/bin/chaingen https://go6lab.si/DANE/chaingen
$ sudo chmod +x /usr/local/bin/chaingen
Run:
$ sudo -s
$ cd /etc/dehydrated/certs
$ chaingen /etc/dehydrated/certs/example.net/fullchain.pem example.org:443
Upgrade
Dehydrated
$ cd /usr/local/lib
$ sudo git fetch --tags
$ git checkout v0.3.1