acmeproxy
acmeproxy copied to clipboard
PowerDNS backend for serving ACME dns-01 challenge responses
acmeproxy
This PowerDNS backend only serves ACME dns-01 challenge responses, and exposes an HTTPS API to permit those challenge responses to be published by automated certificate renewal tools.
Dev environment
You can start a dev environemnt with
docker-compose up
The app will now be available at http://localhost:8080
Use with dehydrated or certbot
Example plugins for dehydrated and certbot are supplied in the plugins directory. Edit these plugin to specify the location of your acmeproxy installation and authorisation key registered with the API, then call them as a dns-01 hook.
For instance, with dehydrated:
dehydrated -c -t dns-01 -k ./acmeproxy-dehydrated.sh -d secure.example.com
Deployment
Install the app in a virtual environemnt with
pip install .
In a new directory make a copy of example_settings.py called acmeproxy_settings.py. Then fill in all the values.
With your working directory set as the newly created directory you can now run the app as a wsgi app. With the environment variable DJANGO_SETTINGS_MODULE set to acmeproxy_settings. and the wsgi app at acmeproxy.acmeproxy.wsgi:application.
Example Apache configuration when certbot is used for the API certificate
<VirtualHost *:443>
ServerName acme-proxy-ns1.example.com
DocumentRoot /var/www/html
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
ProxyPass / http://127.0.0.1:8000/
ProxyPassReverse / http://127.0.0.1:8000/
<Location /admin>
Require ip 127.0.0.1
</Location>
SSLCertificateFile /etc/letsencrypt/live/acme-proxy-ns1.example.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/acme-proxy-ns1.example.com/privkey.pem
Include /etc/letsencrypt/options-ssl-apache.conf
</VirtualHost>
Example PowerDNS configuration
In the directory with your settings file make a new file called backend.
In it put
#!/bin/sh
export DJANGO_SETTINGS_MODULE=acmeproxy_settings
/path/to/venv/django-admin pipeapi
/etc/powerdns/pdns.conf
config-dir=/etc/powerdns
include-dir=/etc/powerdns/pdns.d
setgid=pdns
setuid=pdns
/etc/powerdns/pdns.d/acmeproxy.conf
launch=pipe
pipe-command=/opt/acmeproxy/backend
pipe-timeout=4000
API documentation
HTTPS API usage
All API endpoints will return JSON containing a result key describing the result of the operation. If an operation fails this will be false, and the HTTP response code will indicate the nature of the failure.
Authorisations
Before delegating any names an authorisation should be requested using the create_authorisation endpoint.
$ curl --data "name=secure.example.com" https://acme-proxy-ns1.example.com/create_authorisation
{"result": {"secret": "52f562aedc99383c6af848bc7016380a", "authorisation": "secure.example.com", "suffix_match": false}}
If authentication is enabled in your installation (with the ACMEPROXY_AUTHORISATION_CREATION_SECRETS setting configured to something other than None) you will also need to supply a secret field corresponding to the account being used.
The randomly generated secret returned by this call is then used to identify this authorisation in further calls to the API.
To re-generate the authentication secret for a given authorisation the expire_authorisation endpoint may be used.
$ curl --data "name=secure.example.com&secret=52f562aedc99383c6af848bc7016380a" https://acme-proxy-ns1.example.com/expire_authorisation
{"result": {"secret": "e04541b1d63bc68d296137bc2ee86923", "authorisation": "secure.example.com", "suffix_match": false}}
Challenge responses
During an ACME dns-01 challenge it is necessary to publish a challenge response string supplied by the ACME client. The publish_response endpoint allows a response to be published for a name that has been registered with an authorisation.
$ curl --data "name=secure.example.com&response=evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA&secret=52f562aedc99383c6af848bc7016380a" https://acme-proxy-ns1.example.com/publish_response
{"result": {"authorisation": "secure.example.com", "suffix_match": false, "published": true}}
Optionally a client may request that all challenge responses for a name be expired once they are no longer required, however the backend will expire them regardless after five minutes.
$ curl --data "name=secure.example.com&secret=52f562aedc99383c6af848bc7016380a" https://acme-proxy-ns1.example.com/expire_response
{"result": {"authorisation": "secure.example.com", "suffix_match": false, "expired": true}}
DNS usage
Suppose you have a domain example.com and wish to issue certificates for secure.example.com without having an HTTP server running and without giving full control of the example.com zone to an ACME client.
By registering an authorisation through the HTTPS API then adding a delegation for the expected challenge, _acme-challenge.secure.example.com it is possible to response to those challenges with data supplied using an HTTPS POST to the server.
_acme-challenge.secure.example.com. IN NS acme-proxy-ns1.example.com
It should also be possible to load this backend along side your existing backends, though that functionality is not yet fully tested.
Once the delegation is made a response can be published using the publish_response endpoint of the HTTPS API during ACME certification issuance.
Command line tools
There are several Django management commands available in the proxy app.
listauthorisations
Lists all authorisations present in the database.
$ python manage.py listauthorisations
name created_by_ip created_at account
--------------------- --------------- -------------------------------- ---------
secure.example.com 192.0.2.1 2016-10-10 03:11:18.525111+00:00 operations
test.example.org 192.0.2.1 2016-10-10 03:50:35.827334+00:00 test
host1.example.com 192.0.2.1 2016-10-10 03:51:38.185688+00:00 operations
deleteauthorisation
Permanently remove an authorisation from the database.
$ python manage.py deleteauthorisation test.example.org
Successfully deleted authorisation "test.example.org"
listresponses
List the audit log of challenge responses that have been published, optionally between any two dates.
$ python manage.py listresponses --start=2016-09-01 --end=2016-12-30
name expired_at created_by_ip created_at
------------------- -------------------------------- --------------- --------------------------------
secure.example.com 2016-10-10 03:12:05.984890+00:00 192.0.2.1 2016-10-09 21:47:24.236299+00:00
test.example.org 2016-10-10 03:12:05.984890+00:00 192.0.2.1 2016-10-10 03:12:04.833764+00:00
A response will have an expired_at value if the ACME client explicitly marked all challenges for a name as complete.