Skip to content

Commit

Permalink
Add certbot for managing a Lets Encrypt cert
Browse files Browse the repository at this point in the history
  • Loading branch information
timwoj committed Jan 21, 2025
1 parent 6d682c7 commit aabfe5c
Show file tree
Hide file tree
Showing 8 changed files with 231 additions and 4 deletions.
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,15 @@ cd zeek-pkg-web
`secrets/.env` has a set of variables for passwords and such that PHP will need
to connect to the database and update the packages list from GitHub.

## Initialize an SSL certificate

- Edit `cert_setup/ssl-update.sh` and set the `DOMAINS` and `EMAIL` values to
be sane for your installation. The `DOMAINS` value can be a space-separated
list of names, if necessary.
- Run the `cert_setup/init-certs.sh` script. This will generate a Let's Encrypt
certificate, store it in the location that nginx container will use, and add
a cron task to automatically update it.

## (For development only) Enable the database container

- Edit `docker-compose.yml` and uncomment the section for the `db` service
Expand Down
9 changes: 9 additions & 0 deletions cert_setup/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
services:
nginx:
image: nginx:1.27.3
volumes:
- ./nginx-certtool.conf:/etc/nginx/conf.d/default.conf
- ../data/certbot/letsencrypt:/etc/letsencrypt:ro
- ../data/certbot/www:/var/www/certbot:ro
ports:
- 80:80
17 changes: 17 additions & 0 deletions cert_setup/init-certs.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/usr/bin/env bash

SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)"
cd $SCRIPT_DIR

docker compose up -d nginx
bash ./ssl-update.sh
docker compose down nginx

curl \
-o ${SCRIPT_DIR}/../data/certbot/mozilla-dhparam.txt \
https://ssl-config.mozilla.org/ffdhe2048.txt

cat >/etc/cron.d/certbot.cron <<EOF
# Run Let's Encrypt certbot renew twice a day at random times
0 0,12 * * * root /usr/bin/python -c 'import random; import time; time.sleep(random.random() * 3600)' && $SCRIPT_DIR/ssl-update.sh
EOF
12 changes: 12 additions & 0 deletions cert_setup/nginx-certtool.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
server {
listen 80;
server_name mysite.com;

location /.well-known/acme-challenge/ {
root /var/www/certbot;
}

location / {
return 301 https://$host$request_uri;
}
}
137 changes: 137 additions & 0 deletions cert_setup/ssl-update.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
# This script is originally from https://gist.github.com/maxivak/4706c87698d14e9de0918b6ea2a41015
# with some adaptations for first-run.

#!/bin/bash

# Edit these two values before running this script or init-certs.sh for the
# first time.
DOMAINS="domain.com"
EMAIL="[email protected]"

SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)"
REPO_PATH="${SCRIPT_DIR}/.."
CERT_DIR_PATH="${REPO_PATH}/data/certbot/letsencrypt"
WEBROOT_PATH="${REPO_PATH}/data/certbot/www"
CERT_LOG_PATH="${REPO_PATH}/data/certbot/logs"
LE_RENEW_HOOK="docker restart zeek-pkg-web-nginx-1"
EXP_LIMIT="30"
CHECK_FREQ="30"
STAGING=0
FIRST_RUN=0

if [[ -z $DOMAINS ]]; then
echo "No domains set, please fill -e 'DOMAINS=example.com www.example.com'"
exit 1
fi

if [[ -z $EMAIL ]]; then
echo "No email set, please fill -e '[email protected]'"
exit 1
fi

if [[ -z $CERT_DIR_PATH ]]; then
echo "No cert dir path set, please fill -e 'CERT_DIR_PATH=/etc/letsencrypt'"
exit 1
fi

if [[ -z $WEBROOT_PATH ]]; then
echo "No webroot path set, please fill -e 'WEBROOT_PATH=/tmp/letsencrypt'"
exit 1
fi

if [[ $STAGING -eq 1 ]]; then
echo "Using the staging environment"
ADDITIONAL="--staging"
fi

DARRAYS=(${DOMAINS})
EMAIL_ADDRESS=${EMAIL}
LE_DOMAINS=("${DARRAYS[*]/#/-d }")

exp_limit="${EXP_LIMIT:-30}"
check_freq="${CHECK_FREQ:-30}"

le_hook() {
if [[ $FIRST_RUN -eq 1 ]]; then
return
fi

command=$(echo $LE_RENEW_HOOK)
echo "[INFO] Run: $command"
eval $command
}

le_fixpermissions() {
echo "[INFO] Fixing permissions"
chown -R ${CHOWN:-root:root} ${CERT_DIR_PATH}
find ${CERT_DIR_PATH} -type d -exec chmod 755 {} \;
find ${CERT_DIR_PATH} -type f -exec chmod ${CHMOD:-644} {} \;
}

le_renew() {
docker run --rm --name temp_certbot \
-v "${CERT_DIR_PATH}:/etc/letsencrypt" \
-v "${WEBROOT_PATH}:/tmp/letsencrypt" \
-v "/data/servers-data/certbot/log:/var/log" \
certbot/certbot:v3.1.0 certonly \
--webroot --agree-tos --renew-by-default --non-interactive \
--preferred-challenges http-01 \
--server https://acme-v02.api.letsencrypt.org/directory --text ${ADDITIONAL} \
--email ${EMAIL_ADDRESS} -w /tmp/letsencrypt ${LE_DOMAINS}

le_fixpermissions
le_hook
}

le_check() {
cert_file="$CERT_DIR_PATH/live/$DARRAYS/fullchain.pem"

echo "START check"
echo "file: $cert_file"

if [[ -e $cert_file ]]; then

exp=$(date -d "$(openssl x509 -in $cert_file -text -noout | grep "Not After" | cut -c 25-)" +%s)
datenow=$(date -d "now" +%s)
days_exp=$((($exp - $datenow) / 86400))

echo "Checking expiration date for $DARRAYS..."

if [ "$days_exp" -gt "$exp_limit" ]; then
echo "The certificate is up to date, no need for renewal ($days_exp days left)."
else
echo "The certificate for $DARRAYS is about to expire soon. Starting webroot renewal script..."
le_renew
echo "Renewal process finished for domain $DARRAYS"
fi

echo "Checking domains for $DARRAYS..."

domains=($(openssl x509 -in $cert_file -text -noout | grep -oP '(?<=DNS:)[^,]*'))
new_domains=($(
for domain in ${DARRAYS[@]}; do
[[ " ${domains[@]} " =~ " ${domain} " ]] || echo $domain
done
))

if [ -z "$new_domains" ]; then
echo "The certificate have no changes, no need for renewal"
else
echo "The list of domains for $DARRAYS certificate has been changed. Starting webroot renewal script..."
le_renew
echo "Renewal process finished for domain $DARRAYS"
fi

else
FIRST_RUN=1

echo "[INFO] certificate file not found for domain $DARRAYS. Starting webroot initial certificate request script..."
le_renew
echo "Certificate request process finished for domain $DARRAYS"
fi

}

echo "--- start. $(date)"

le_check $1
3 changes: 3 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ services:
- 443:443
volumes:
- ./bropkg/webroot:/var/www/html:ro
- ./data/certbot/letsencrypt:/etc/letsencrypt:ro
- ./data/certbot/www:/var/www/certbot:ro
- ./data/certbot/mozilla-dhparam.txt:/etc/nginx/mozilla-dhparam.txt:ro

php:
build:
Expand Down
6 changes: 6 additions & 0 deletions docker/Dockerfile.nginx
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
FROM nginx:1.27.3

RUN apt update -y
RUN DEBIAN_FRONTEND=noninteractive apt-get install -y \
certbot \
cron \
python3-certbot-nginx

COPY docker/nginx-default.conf /etc/nginx/conf.d/default.conf
42 changes: 38 additions & 4 deletions docker/nginx-default.conf
Original file line number Diff line number Diff line change
@@ -1,9 +1,23 @@
server {
listen 80;
listen [::]:80 ipv6only=on;

listen 80 default_server;
listen [::]:80 ipv6only=on default_server;
# listen 443 ssl default_server;
# listen [::]:443 ssl ipv6only=on default_server;
server_name _;

location /.well-known/acme-challenge/ {
root /var/www/certbot;
}

location / {
return 301 https://$host$request_uri;
}
}

server {
listen 443 ssl;
listen [::]:443 ssl ipv6only=on;

server_name _;

root /var/www/html;
index index.html index.php;
Expand Down Expand Up @@ -39,4 +53,24 @@ server {
location ~ /.ht {
deny all;
}

# HSTS (ngx_http_headers_module is required) (63072000 seconds)
add_header Strict-Transport-Security "max-age=63072000" always;

ssl_certificate /etc/letsencrypt/live/domain.com/cert.pem;
ssl_certificate_key /etc/letsencrypt/live/domain.com/privkey.pem;
}

# These options come from https://ssl-config.mozilla.org, as of 2025/01/16.
# intermediate configuration
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ecdh_curve X25519:prime256v1:secp384r1;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305;
ssl_prefer_server_ciphers off;

# see also ssl_session_ticket_key alternative to stateful session cache
ssl_session_timeout 1d;
ssl_session_cache shared:MozSSL:10m; # about 40000 sessions

# curl https://ssl-config.mozilla.org/ffdhe2048.txt > /path/to/dhparam
ssl_dhparam "/etc/nginx/mozilla-dhparam.txt";

0 comments on commit aabfe5c

Please sign in to comment.