diff --git a/Dockerfile b/Dockerfile index 0b76fd2b43..0c055c6003 100644 --- a/Dockerfile +++ b/Dockerfile @@ -84,6 +84,7 @@ RUN apt-get -q dist-upgrade -y && \ rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* COPY entrypoint.sh /sbin/entrypoint.sh +COPY certbot-deploy-hook /sbin/certbot-deploy-hook VOLUME ["$DATA_DIR"] EXPOSE 80 443 diff --git a/README.md b/README.md index e331f80e05..4ea9aa1f25 100644 --- a/README.md +++ b/README.md @@ -168,11 +168,11 @@ which you need to encode in the YAML file. For example, comma-separated list of the backend names (E.g. `"EmailAuthBackend,GitHubAuthBackend"`). -**SSL Certificates**. By default, the image will generate a - self-signed cert. We - [will soon also support certbot](https://github.com/zulip/docker-zulip/issues/120) - for this, just like we do in normal Zulip installations - (contributions welcome!). +**SSL Certificates**. By default, the image will generate a self-signed cert. +You can set `SSL_CERTIFICATE_GENERATION: "certbot"` within `docker-compose.yml` +to enable automatically-renewed Let's Encrypt certificates. By using certbot +here, you are agreeing to the [Let's Encrypt +ToS](https://community.letsencrypt.org/tos). You can also provide an SSL certificate for your Zulip server by putting it in `/opt/docker/zulip/zulip/certs/` (by default, the diff --git a/certbot-deploy-hook b/certbot-deploy-hook new file mode 100755 index 0000000000..942050ba92 --- /dev/null +++ b/certbot-deploy-hook @@ -0,0 +1,29 @@ +#!/usr/bin/env bash + +set -euo pipefail + +backup() { + if [ -e "$1" ]; then + # If the user is setting up our automatic certbot-management on a + # system that already has certs for Zulip, use some extra caution + # to keep the old certs available. This naming is consistent with Zulip's + # own setup-certbot backups. + mv -f --backup=numbered "$1" "$1".setup-certbot || true + fi +} + +source_cert_dir=/etc/letsencrypt/live/"$SETTING_EXTERNAL_HOST" +dest_cert_dir="$DATA_DIR"/certs + +# Persist the certs to the data directory. +backup "$dest_cert_dir"/zulip.key +backup "$dest_cert_dir"/zulip.combined-chain.crt +cp -f "$source_cert_dir"/privkey.pem "$dest_cert_dir"/zulip.key +cp -f "$source_cert_dir"/fullchain.pem "$dest_cert_dir"/zulip.combined-chain.crt + +# Ensure nginx can find them. +ln -nsf "$dest_cert_dir"/zulip.key /etc/ssl/private/zulip.key +ln -nsf "$dest_cert_dir"/zulip.combined-chain.crt /etc/ssl/certs/zulip.combined-chain.crt + +# Restart various services so the new certs can be used. +supervisorctl restart nginx diff --git a/entrypoint.sh b/entrypoint.sh index 077fd3a3db..182b3ec7be 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -168,12 +168,18 @@ configureCerts() { ;; esac if [ ! -e "$DATA_DIR/certs/zulip.key" ] && [ ! -e "$DATA_DIR/certs/zulip.combined-chain.crt" ]; then + if [ "$GENERATE_CERTBOT_CERT" = "True" ]; then - echo "Certbot not yet supported" - exit 1 - # TODO: Run setup-certbot and move /etc/letsencrypt to the data dir? - # /home/zulip/deployments/current/setup/setup-certbot "$SETTING_EXTERNAL_HOST" - elif [ "$GENERATE_SELF_SIGNED_CERT" = "True" ]; then + # Zulip isn't yet running, so the certbot's challenge can't be met. + # We'll schedule this for later. + echo "Scheduling LetsEncrypt cert generation ..." + GENERATE_CERTBOT_CERT_SCHEDULED=True + + # Generate self-signed certs just to get Zulip going. + GENERATE_SELF_SIGNED_CERT=True + fi + + if [ "$GENERATE_SELF_SIGNED_CERT" = "True" ]; then echo "Generating self-signed certificates ..." mkdir -p "$DATA_DIR/certs" /home/zulip/deployments/current/scripts/setup/generate-self-signed-cert "$SETTING_EXTERNAL_HOST" @@ -407,12 +413,43 @@ runPostSetupScripts() { set -e echo "Post setup scripts execution succeeded." } +function runCertbotAsNeeded() { + if [ ! "$GENERATE_CERTBOT_CERT_SCHEDULED" = "True" ]; then + echo "Certbot is not scheduled to run." + return + fi + + echo "Waiting for nginx to come online before generating certbot certificate ..." + while ! curl -sk "$SETTING_EXTERNAL_HOST" >/dev/null 2>&1; do + sleep 1; + done + + echo "Generating LetsEncrypt/certbot certificate ..." + + # Remove the self-signed certs which were only needed to get Zulip going. + rm -f "$DATA_DIR"/certs/zulip.key "$DATA_DIR"/certs/zulip.combined-chain.crt + + ZULIP_CERTBOT_DEPLOY_HOOK="/sbin/certbot-deploy-hook" + + # Accept the terms of service automatically. + /home/zulip/deployments/current/scripts/setup/setup-certbot \ + --agree-tos \ + --hostname="$SETTING_EXTERNAL_HOST" \ + --email="$SETTING_ZULIP_ADMINISTRATOR" \ + --deploy-hook "$ZULIP_CERTBOT_DEPLOY_HOOK" + + echo "LetsEncrypt cert generated." +} bootstrappingEnvironment() { echo "=== Begin Bootstrap Phase ===" waitingForDatabase zulipFirstStartInit zulipMigration runPostSetupScripts + # Hack: We run this in the background, since we need nginx to be + # started before we can create the certificate. See #142 for + # details on how we can clean this up. + runCertbotAsNeeded & echo "=== End Bootstrap Phase ===" } # END appRun functions