Hi Guys
I needed to use the DNS method for authenticating my SSL certificate ownership with Let’s Encrypt.
I took a copy of the existing web.letsencrypt.ssl.template.yml
file found in /var/discourse/templates/
and modified it to use the Automatic DNS API Integration method. Below is my sample template, I’d be happy if there were any suggestion on how to improve it.
I called this file web.letsencrypt.ssl.dns.template.yml
env:
LETSENCRYPT_DIR: "/shared/letsencrypt"
DISCOURSE_FORCE_HTTPS: true
hooks:
after_ssl:
- exec:
cmd:
- if [ -z "$LETSENCRYPT_ACCOUNT_EMAIL" ]; then echo "LETSENCRYPT_ACCOUNT_EMAIL ENV variable is required and has not been set."; exit 1; fi
- /bin/bash -c "if [[ ! \"$LETSENCRYPT_ACCOUNT_EMAIL\" =~ ([^@]+)@([^\.]+) ]]; then echo \"LETSENCRYPT_ACCOUNT_EMAIL is not a valid email address\"; exit 1; fi"
- exec:
cmd:
- cd /root && git clone --branch 3.0.7 --depth 1 https://github.com/acmesh-official/acme.sh.git && cd /root/acme.sh
- touch /var/spool/cron/crontabs/root
- install -d -m 0755 -g root -o root $LETSENCRYPT_DIR
- cd /root/acme.sh && LE_WORKING_DIR="${LETSENCRYPT_DIR}" ./acme.sh --install --log "${LETSENCRYPT_DIR}/acme.sh.log"
- cd /root/acme.sh && LE_WORKING_DIR="${LETSENCRYPT_DIR}" ./acme.sh --upgrade --auto-upgrade
- cd /root/acme.sh && LE_WORKING_DIR="${LETSENCRYPT_DIR}" ./acme.sh --set-default-ca --server letsencrypt
- file:
path: /etc/runit/1.d/letsencrypt
chmod: "+x"
contents: |
#!/bin/bash
issue_cert() {
export CF_Token="$$ENV_LETSENCRYPT_CF_TOKEN"
export CF_Account_ID="$$ENV_LETSENCRYPT_CF_ACCOUNT_ID"
export CF_Zone_ID="$$ENV_LETSENCRYPT_CF_ZONE_ID"
LE_WORKING_DIR="${LETSENCRYPT_DIR}" $$ENV_LETSENCRYPT_DIR/acme.sh --issue --dns $$ENV_LETSENCRYPT_DNS_PROVIDER $2 -d $$ENV_DISCOURSE_HOSTNAME --keylength $1 -w /var/www/discourse/public
}
cert_exists() {
[[ "$(cd $$ENV_LETSENCRYPT_DIR/$$ENV_DISCOURSE_HOSTNAME$1 && openssl verify -CAfile <(openssl x509 -in ca.cer) fullchain.cer | grep "OK")" ]]
}
########################################################
# RSA cert
########################################################
issue_cert "4096"
if ! cert_exists ""; then
# Try to issue the cert again if something goes wrong
issue_cert "4096" "--force"
fi
LE_WORKING_DIR="${LETSENCRYPT_DIR}" $$ENV_LETSENCRYPT_DIR/acme.sh \
--installcert \
-d $$ENV_DISCOURSE_HOSTNAME \
--fullchainpath /shared/ssl/$$ENV_DISCOURSE_HOSTNAME.cer \
--keypath /shared/ssl/$$ENV_DISCOURSE_HOSTNAME.key \
--reloadcmd "sv reload nginx"
########################################################
# ECDSA cert
########################################################
issue_cert "ec-256"
if ! cert_exists "_ecc"; then
# Try to issue the cert again if something goes wrong
issue_cert "ec-256" "--force"
fi
LE_WORKING_DIR="${LETSENCRYPT_DIR}" $$ENV_LETSENCRYPT_DIR/acme.sh \
--installcert --ecc \
-d $$ENV_DISCOURSE_HOSTNAME \
--fullchainpath /shared/ssl/$$ENV_DISCOURSE_HOSTNAME_ecc.cer \
--keypath /shared/ssl/$$ENV_DISCOURSE_HOSTNAME_ecc.key \
--reloadcmd "sv reload nginx"
if cert_exists "" || cert_exists "_ecc"; then
grep -q 'force_https' "/var/www/discourse/config/discourse.conf" || echo "force_https = 'true'" >> "/var/www/discourse/config/discourse.conf"
fi
- replace:
filename: "/etc/nginx/conf.d/discourse.conf"
from: /ssl_certificate.+/
to: |
ssl_certificate /shared/ssl/$$ENV_DISCOURSE_HOSTNAME.cer;
ssl_certificate /shared/ssl/$$ENV_DISCOURSE_HOSTNAME_ecc.cer;
- replace:
filename: /shared/letsencrypt/account.conf
from: /#?ACCOUNT_EMAIL=.+/
to: |
ACCOUNT_EMAIL=$$ENV_LETSENCRYPT_ACCOUNT_EMAIL
- replace:
filename: "/etc/nginx/conf.d/discourse.conf"
from: /ssl_certificate_key.+/
to: |
ssl_certificate_key /shared/ssl/$$ENV_DISCOURSE_HOSTNAME.key;
ssl_certificate_key /shared/ssl/$$ENV_DISCOURSE_HOSTNAME_ecc.key;
- replace:
filename: "/etc/nginx/conf.d/discourse.conf"
from: /add_header.+/
to: |
add_header Strict-Transport-Security 'max-age=63072000';
There are some extra environment variables you need to add to app.yml and possibly modify if you’re not using Cloudflare as your DNS provider. All the different providers API settings are here
This is what I added to my app.yml
under the templates
section
templates:
- "templates/postgres.template.yml"
- "templates/redis.template.yml"
- "templates/web.template.yml"
## Uncomment the next line to enable the IPv6 listener
#- "templates/web.ipv6.template.yml"
- "templates/web.ratelimited.template.yml"
## Uncomment these two lines if you wish to add Lets Encrypt (https)
- "templates/web.ssl.template.yml"
- "templates/web.letsencrypt.ssl.dns.template.yml"
#- "templates/web.letsencrypt.ssl.template.yml"
And further down under the env
section.
## If you added the Lets Encrypt template, uncomment below to get a free SSL certificate
LETSENCRYPT_ACCOUNT_EMAIL: me@mydomain.com
LETSENCRYPT_CF_TOKEN: "YOUR_TOKEN"
LETSENCRYPT_CF_ACCOUNT_ID: "YOUR_ACCOUNT_ID"
LETSENCRYPT_CF_ZONE_ID: "YOUR_ZONE_ID"
LETSENCRYPT_DNS_PROVIDER: "YOUR_DNS_PROVIDER" ## i.e. dns_cf
After updating those files I then simply ran the command to rebuild the docker app
cd /var/discourse
./launcher rebuild app
Once rebuilt you should then have your app running at https:// and there should be a cron job that checks daily if your certificate needs updating. If it does need updating it will get you a new certificate and install it automatically.
Hope this helps someone.