diff --git a/tls-proxy/README.md b/tls-proxy/README.md new file mode 100644 index 0000000..6413e1e --- /dev/null +++ b/tls-proxy/README.md @@ -0,0 +1,2 @@ +# tls-proxy +Automated deployment of TLS reverse proxy provisioned with Let's Encrypt certificate diff --git a/tls-proxy/docker-compose.yml b/tls-proxy/docker-compose.yml new file mode 100644 index 0000000..f8ca534 --- /dev/null +++ b/tls-proxy/docker-compose.yml @@ -0,0 +1,27 @@ +services: + + proxy: + image: nginx:stable-bullseye + command: "/bin/sh -c 'while :; do sleep 6h & wait $${!}; nginx -s reload; done & nginx -g \"daemon off;\"'" + ports: + - 80:80 + - 443:443 + volumes: + - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro + - ./certbot/challenge:/data/certbot-challenge:ro + - ./certbot/certificates:/data/certificates:ro + + certbot: + image: certbot/certbot:v2.5.0 + volumes: + - ./certbot/certificates:/etc/letsencrypt + - ./certbot/challenge:/data-www-challenge + entrypoint: "/bin/sh -c 'sleep 300; trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'" + + # Hello-world http container useful for test/debugging the proxy + # an actual service would be used for production + example-webservice: + image: crccheck/hello-world + ports: + - 8000 + diff --git a/tls-proxy/initialize-tls-proxy.sh b/tls-proxy/initialize-tls-proxy.sh new file mode 100755 index 0000000..f219cbb --- /dev/null +++ b/tls-proxy/initialize-tls-proxy.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +if [[ -n "$CERC_SCRIPT_DEBUG" ]]; then + set -x +fi +# TODO: get from the caller +LACONIC_TLS_DOMAIN=example.com +# When we're called nginx and certbot container are up and running and certbot is sleeping before executing renew +# So we can now ask certbot to issue our initial cert +tls_certificate_directory=./certbot/certificates/live/${LACONIC_TLS_DOMAIN} +rm -rf ${tls_certificate_directory} +# TODO: pass in email from caller +# TODO: allow staging/dry-run mode +docker compose exec certbot \ + certbot certonly --webroot -w /data-www-challenge \ + --staging \ + --email ${EMAIL} \ + -d ${LACONIC_TLS_DOMAIN} \ + --rsa-key-size 4096 \ + --agree-tos \ + --force-renewal diff --git a/tls-proxy/nginx-config-template b/tls-proxy/nginx-config-template new file mode 100644 index 0000000..1979f03 --- /dev/null +++ b/tls-proxy/nginx-config-template @@ -0,0 +1,39 @@ +events { + worker_connections 1024; +} + +http { + server_tokens off; + charset utf-8; + + server { + listen 80 default_server; + + server_name _; + + location ~ /.well-known/acme-challenge/ { + root /data/certbot-challenge; + } + + location / { + proxy_pass http://webservice:8000/; + } + } + + server { + listen 443 ssl http2; + ssl_certificate /data/certificates/live/${LACONIC_TLS_DOMAIN}/fullchain.pem; + ssl_certificate_key /data/certificates/live/${LACONIC_TLS_DOMAIN}/privkey.pem; + server_name ${LACONIC_TLS_DOMAIN}; + root /var/www/html; + index index.php index.html index.htm; + + location / { + proxy_pass ${LACONIC_ORIGIN_SERVICE_URL}; + } + + location ~ /.well-known/acme-challenge/ { + root /data/certbot-challenge; + } + } +} diff --git a/tls-proxy/run-this-first.sh b/tls-proxy/run-this-first.sh new file mode 100755 index 0000000..b822507 --- /dev/null +++ b/tls-proxy/run-this-first.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +if [[ -n "$CERC_SCRIPT_DEBUG" ]]; then + set -x +fi +set -e +mkdir -p ./nginx +mkdir -p ./certbot/certificates +mkdir -p ./certbot/challenge +# TODO: get from the caller +LACONIC_TLS_DOMAIN=example.com +LACONIC_ORIGIN_SERVICE_URL=http://example-webservice:8000/ +# Expand the config template into the nginx config file +cat ./nginx-config-template | sed 's/${LACONIC_TLS_DOMAIN}/'${LACONIC_TLS_DOMAIN}'/' | \ + sed 's/${LACONIC_ORIGIN_SERVICE_URL}/'${LACONIC_ORIGIN_SERVICE_URL}'/' > ./nginx/nginx.conf +# Create a self-signed cert so nginx will start without us changing its config between pre and post certbot invocation. +# Check if we have a cert already +tls_certificate_directory=./certbot/certificates/live/${LACONIC_TLS_DOMAIN} +tls_certificate_directory_in_container=/etc/letsencrypt/live/${LACONIC_TLS_DOMAIN} +tls_certificate_file_name=${tls_certificate_directory}/fullchain.pem +# TODO: this won't work if there's a delay of more than one day between generating the +# self signed cert and starting the certbot enrollment process +if [[ ! -f ${tls_certificate_file_name} ]] ; then + echo "Generating self-signed certificate for ${LACONIC_TLS_DOMAIN}:" + mkdir -p ${tls_certificate_directory} + docker compose run --rm --entrypoint "\ + openssl req -x509 -nodes -newkey rsa:4096 -days 1 -keyout '${tls_certificate_directory_in_container}/privkey.pem' \ + -out '${tls_certificate_directory_in_container}/fullchain.pem' -subj '/CN=${LACONIC_TLS_DOMAIN}'" certbot +echo +fi