[Project] Initial Commit

This commit is contained in:
Robert von Burg 2022-01-06 01:23:39 +01:00
commit 45aff8bc5b
11 changed files with 329 additions and 0 deletions

40
README.md Normal file
View File

@ -0,0 +1,40 @@
# nginx-lets-encrypt-proxy
This is a docker container as a nginx proxy with Let's Encrypt and virtual hosts support.
Inspirations:
* https://github.com/gilyes/docker-nginx-letsencrypt-sample
* https://ilhicas.com/2019/03/02/Nginx-Letsencrypt-Docker.html
* https://github.com/Ilhicas/nginx-letsencrypt
Create necessary directories and files:
mkdir ssl
mkdir nginx/modules-enabled
mkdir letsencrypt/
touch letsencrypt/configured-domains
Create the dhparams:
openssl dhparam -out ssl/dhparam.pem 2048
And then add your virtual hosts in `nginx/sites-enabled`, but make sure to use the following certificates:
ssl_certificate /etc/letsencrypt/snake-oil/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/snake-oil/private-key.pem;
The internal scripts will then replace this and reload nginx when necessary.
Now add your domains as separate lines in `letsencrypt/configured-domains`.
Now build the image:
docker-compose build --pull
And then start:
docker-compose up

1
build/.dockerignore Normal file
View File

@ -0,0 +1 @@
Dockerfile

12
build/Dockerfile Normal file
View File

@ -0,0 +1,12 @@
FROM nginx:alpine
RUN apk add bash inotify-tools certbot openssl ca-certificates
RUN mkdir -p /var/www/certbot/
RUN mkdir -p /etc/ssl/
WORKDIR /opt
COPY certbot-obtain.sh certbot-obtain.sh
COPY certbot-renewal.sh certbot-renewal.sh
COPY nginx-reload.sh nginx-reload.sh
COPY entrypoint.sh entrypoint.sh
COPY default.conf /etc/nginx/nginx.conf
RUN chmod +x certbot-obtain.sh && chmod +x certbot-renewal.sh && chmod +x nginx-reload.sh && chmod +x entrypoint.sh
ENTRYPOINT ["./entrypoint.sh"]

68
build/certbot-obtain.sh Normal file
View File

@ -0,0 +1,68 @@
#!/bin/bash
if [[ "$#" != "3" ]] ; then
echo -e "ERROR: Usage: $0 <watch_file> <letsencrypt_dir> <certbot_dir>"
exit 1
fi
WATCH_FILE="$1"
LETSENCRYPT_DIR="$2"
CERTBOT_WWW_DIR="$3"
WATCH_FILE_NAME=$(basename $(realpath ${WATCH_FILE}))
WATCH_DIR=$(dirname $(realpath ${WATCH_FILE}))
sleep 5s
echo -e "$(date +'%Y-%m-%d %H:%M:%S') INFO: Watching directory ${WATCH_DIR} for changes to file ${WATCH_FILE_NAME}..."
function parseFile() {
file="$1"
cat ${file} | while read line ; do
line=${line##*( )}
line=${line%%*( )}
[[ "$line" == "" ]] && continue
[[ $line =~ ^#.* ]] && continue
obtainCertificate ${line}
done
}
function obtainCertificate() {
domain="$1"
nginx_file="/etc/nginx/sites-enabled/${domain}"
letsencrypt_file="${LETSENCRYPT_DIR}/live/${domain}"
[[ ! -f ${nginx_file} ]] && continue
[[ -d ${letsencrypt_file} ]] && continue
echo -e "$(date +'%Y-%m-%d %H:%M:%S') INFO: Obtaining certificate for new domain ${domain}"
if ! echo certbot certonly \
--test-cert \
--dry-run \
--config-dir ${LETSENCRYPT_DIR} \
--agree-tos \
--domain "${domain}" \
--email "${LETS_ENCRYPT_EMAIL}" \
--expand \
--noninteractive \
--webroot \
--webroot-path ${CERTBOT_WWW_DIR} ; then
echo -e "$(date +'%Y-%m-%d %H:%M:%S') ERROR: Failed to obtain certificate for $domain"
echo nginx -s quit
exit 1
fi
}
echo -e "$(date +'%Y-%m-%d %H:%M:%S') INFO: Initially obtaining certificates..."
parseFile ${WATCH_FILE}
echo -e "$(date +'%Y-%m-%d %H:%M:%S') INFO: Done."
inotifywait -m -e CLOSE_WRITE "${WATCH_DIR}" | while read filename eventlist eventfile ; do
[[ $eventfile != ${WATCH_FILE_NAME} ]] && continue
echo -e "$(date +'%Y-%m-%d %H:%M:%S') INFO: Obtaining certificates as changes detected in file $filename $eventlist $eventfile"
parseFile ${WATCH_FILE}
done
exit 0

17
build/certbot-renewal.sh Normal file
View File

@ -0,0 +1,17 @@
#!/bin/bash
sleep 30s
echo -e "$(date +'%Y-%m-%d %H:%M:%S') INFO: Renewing certificates every 12h."
while :
do
sleep "12h"
echo -e "$(date +'%Y-%m-%d %H:%M:%S') INFO: Renewing certificates..."
if ! certbot -q renew --post-hook nginx -s reload ; then
echo -e "$(date +'%Y-%m-%d %H:%M:%S') ERROR: Failed to renew certificates stopping nginx!"
nginx -s stop
fi
done
exit 0

63
build/default.conf Normal file
View File

@ -0,0 +1,63 @@
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;
events {
worker_connections 1024;
}
http {
##
# Basic Settings
##
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
# server_tokens off;
# server_names_hash_bucket_size 64;
# server_name_in_redirect off;
include /etc/nginx/mime.types;
default_type application/octet-stream;
##
# SSL Settings
##
#ssl_protocols TLSv1.2 TLSv1.3; # Dropping SSLv3, ref: POODLE
#ssl_prefer_server_ciphers on;
##
# Logging Settings
##
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
##
# Gzip Settings
##
gzip on;
# gzip_vary on;
# gzip_proxied any;
# gzip_comp_level 6;
# gzip_buffers 16 8k;
# gzip_http_version 1.1;
# gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
##
# Virtual Host Configs
##
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}

48
build/entrypoint.sh Normal file
View File

@ -0,0 +1,48 @@
#!/bin/bash
# Create a self signed default certificate, so Nginx can start before we have
# any real certificates.
#Ensure we have folders available
WATCH_FILE="/etc/letsencrypt/configured-domains"
LETSENCRYPT_DIR="/etc/letsencrypt"
CERTBOT_WWW_DIR="/var/www/certbot"
if [[ ! -f "${WATCH_FILE}" ]] ; then
echo -e "E$(date +'%Y-%m-%d %H:%M:%S') RROR: Failed to read configured-domains file: ${WATCH_FILE}"
exit 1
fi
if [[ ! -d "${CERTBOT_WWW_DIR}" ]] ; then
echo -e "E$(date +'%Y-%m-%d %H:%M:%S') RROR: Certbot directory does not exist at ${CERTBOT_WWW_DIR}"
exit 1
fi
LETS_ENCRYPT_EMAIL=eitch@eitchnet.ch
NGINX_HOST=proxy.eitchnet.ch
NGINX_PORT=80
if [[ ! -f /etc/letsencrypt/snake-oil/fullchain.pem ]] ; then
echo -e "$(date +'%Y-%m-%d %H:%M:%S') INFO: Creating snake-oil certificate..."
mkdir -p /etc/letsencrypt/snake-oil
openssl genrsa -out /etc/letsencrypt/snake-oil/private-key.pem 4096
openssl req -new -key /etc/letsencrypt/snake-oil/private-key.pem -out /etc/letsencrypt/snake-oil/snake-oil.csr -nodes -subj \
"/C=CH/ST=Solothurn/L=Solothurn/O=Home/OU=Lab/CN=${NGINX_HOST}"
openssl x509 -req -days 365 -in /etc/letsencrypt/snake-oil/snake-oil.csr -signkey /etc/letsencrypt/snake-oil/private-key.pem -out /etc/letsencrypt/snake-oil/fullchain.pem
fi
trap "echo $(date +'%Y-%m-%d %H:%M:%S') exiting due to signal ; kill $(jobs -p)" SIGHUP SIGINT SIGTERM SIGQUIT SIGTRAP SIGABRT SIGSTOP
trap "echo $(date +'%Y-%m-%d %H:%M:%S') exiting due to error! ; exit" ERR
nginx -g "daemon off;" &
nginx_sid=$!
/opt/certbot-obtain.sh ${WATCH_FILE} ${LETSENCRYPT_DIR} ${CERTBOT_WWW_DIR} &
/opt/certbot-renewal.sh "12h" &
/opt/nginx-reload.sh &
echo -e "$(date +'%Y-%m-%d %H:%M:%S') INFO: Sleeping indefinitely..."
wait ${nginx_sid}
echo -e "INFO: nginx has exited, exiting script!"
kill $(jobs -p)
exit 0

24
build/nginx-reload.sh Normal file
View File

@ -0,0 +1,24 @@
#!/bin/bash
sleep 5s
WATCH_DIR="/etc/nginx/sites-enabled"
echo -e "$(date +'%Y-%m-%d %H:%M:%S') INFO: Watching directory ${WATCH_DIR} for changes to file ${WATCH_FILE_NAME}..."
lastReload=0
inotifywait -m -e CREATE -e CLOSE_WRITE "${WATCH_DIR}" | while read filename eventlist eventfile ; do
[[ $eventfile =~ ^\..* ]] && continue
now=$(date +%s)
diff=$((now-lastReload))
if [[ ${diff} -lt 10 ]] ; then
echo -e "INFO: Ignoring duplicate action to $eventfile"
continue
fi
echo -e "$(date +'%Y-%m-%d %H:%M:%S') INFO: Reloading nginx as changes detected in file $filename $eventlist $eventfile"
nginx -s reload
done
exit 0

30
docker-compose.yml Normal file
View File

@ -0,0 +1,30 @@
version: "3.9"
services:
proxy:
restart: unless-stopped
image: eitch-proxy
build: ./build
container_name: eitch-proxy
ports:
- "80:80"
- "443:443"
volumes:
- "./letsencrypt:/etc/letsencrypt"
- "./ssl/dhparam.pem:/etc/ssl/dhparam.pem:ro"
- "./nginx/conf.d:/etc/nginx/conf.d"
- "./nginx/modules-enabled:/etc/nginx/modules-enabled"
- "./nginx/sites-enabled:/etc/nginx/sites-enabled"
- "./nginx/cache:/var/cache/nginx"
- "./nginx/pid:/var/run"
environment:
- TZ=Europe/Zurich
- LETS_ENCRYPT_EMAIL=eitch@eitchnet.ch
- NGINX_HOST=proxy.eitchnet.ch
- NGINX_PORT=80
networks:
proxy-network:
ipv4_address: 10.254.0.2
networks:
proxy-network:
external: true

8
nginx/conf.d/ssl.conf Normal file
View File

@ -0,0 +1,8 @@
ssl_session_cache shared:SSL:50m;
ssl_session_timeout 5m;
ssl_dhparam /etc/ssl/dhparam.pem;
ssl_prefer_server_ciphers on;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4;

View File

@ -0,0 +1,18 @@
server {
listen 80;
server_name localhost;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl;
server_name localhost;
ssl_certificate /etc/letsencrypt/snake-oil/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/snake-oil/private-key.pem;
#include /etc/nginx/ssl.conf;
}