diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..aa766b6 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,3 @@ +.git +.gitignore +.gitattributes diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..0888f85 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,23 @@ +FROM chernetsov0/nginx-alpine:1.13.2 + +LABEL maintainer="Alexey Chernetsov " + +# Default environment +ENV DHLEVEL=2048 + +# Install Certbot and openssl +RUN apk add --no-cache certbot openssl + +# Create directories. +RUN mkdir -p /var/lib/nginx-letsencrypt /usr/share/nginx/acme + +# Add local files +COPY etc/ /etc/ +COPY nginx-letsencrypt.sh /root/nginx-letsencrypt + +# Make scripts executable +RUN chmod a+x \ + /root/nginx-letsencrypt \ + /etc/periodic/daily/10-letsencrypt-renew + +CMD /root/nginx-letsencrypt diff --git a/README.md b/README.md new file mode 100644 index 0000000..b5d5bb0 --- /dev/null +++ b/README.md @@ -0,0 +1,39 @@ +This container sets up an Nginx webserver with built-in letsencrypt client that automates free SSL server certificate generation and renewal processes. It is based on scripts from [linuxserver/letsencrypt](https://hub.docker.com/r/linuxserver/letsencrypt/) with some major differences: + +* Does not contain **s6-overlay**. +* Does not contain **PHP**. +* Does not contain **fail2ban**. +* Does not restart **NGINX** when renewing. certificate. +* Contains newer version of **Certbot** (*0.14.0* vs *0.9.3*). + +## Usage + +``` +docker create \ + --name=nginx-letsencrypt \ + -p 443:443 -p 80:80 \ + -v :/var/lib/nginx-letsencrypt \ + -v :/etc/letsencrypt \ + -e EMAIL= \ + -e DOMAINS= \ + -e DHLEVEL=2048 + -e TZ= \ + chernetsov0/nginx-letsencrypt +``` + +## Parameters + +* `-p 80 -p 443` - the port(s) used by **NGINX**. +* `-v :/var/lib/nginx-letsencrypt` - dhparams file and previous `DOMAINS` and `DHLEVEL` value reside here. +* `-v :/etc/letsencrypt` - all files letsencrypt generates and uses reside here (including certificate). +* `-e DOMAINS` - domains for letsencrypt to get certificate for. + +_Optional:_ +* `-e EMAIL` - your e-mail address for cert registration and notifications +* `-e DHLEVEL` - dhparams bit value, can be set to `1024`, `2048` or `4096`. +* `-e TZ` - timezone ie. `America/New_York`. + +## Setting up the application + +* Before running this container, make sure that all domains in `DOMAINS` are properly forwarded to this container's host, and that ports 80 and 443 are not being used by another service on the host. +* The container detects changes to `DOMAINS`, revokes existing certificate and generates new one during start. It also detects changes to the `DHLEVEL` parameter and replaces the dhparams file. diff --git a/etc/nginx/conf.d/default.conf b/etc/nginx/conf.d/default.conf new file mode 100644 index 0000000..a3a9eb1 --- /dev/null +++ b/etc/nginx/conf.d/default.conf @@ -0,0 +1,26 @@ +server { + listen 443 ssl default_server deferred; + server_name _; + + include include.ssl; + + location / { + root /usr/share/nginx/html; + index index.html index.htm; + } + + error_page 404 /404.html; + location = /404.html { + root /usr/share/nginx/html; + } + + + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /usr/share/nginx/html; + } + + location ~ /\.ht { + deny all; + } +} diff --git a/etc/nginx/conf.d/redirect-letsencrypt.conf b/etc/nginx/conf.d/redirect-letsencrypt.conf new file mode 100644 index 0000000..76c954d --- /dev/null +++ b/etc/nginx/conf.d/redirect-letsencrypt.conf @@ -0,0 +1,13 @@ +server { + listen 80 default_server deferred; + server_name _; + + location / { + return 301 https://$host$request_uri; + } + + location /.well-known { + root /usr/share/nginx/acme/.well-known; + allow all; + } +} diff --git a/etc/nginx/include.ssl b/etc/nginx/include.ssl new file mode 100644 index 0000000..12c958a --- /dev/null +++ b/etc/nginx/include.ssl @@ -0,0 +1,17 @@ +add_header Strict-Transport-Security "max-age=15768000; includeSubDomains; preload;"; +add_header X-Frame-Options SAMEORIGIN; +add_header X-Content-Type-Options nosniff; +add_header X-XSS-Protection "1; mode=block"; + +ssl_prefer_server_ciphers on; +ssl_protocols TLSv1 TLSv1.1 TLSv1.2; +ssl_ciphers ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:RSA+AESGCM:RSA+AES:!aNULL:!MD5:!DSS; +ssl_session_cache shared:SSL:10m; + +resolver 127.0.0.1; +ssl_stapling on; # Requires nginx >= 1.3.7 +ssl_stapling_verify on; # Requires nginx => 1.3.7 + +ssl_certificate /etc/letsencrypt/live/nginx-letsencrypt/fullchain.pem; +ssl_certificate_key /etc/letsencrypt/live/nginx-letsencrypt/privkey.pem; +ssl_dhparam /var/lib/nginx-letsencrypt/dhparams.pem; diff --git a/etc/nginx/nginx.conf b/etc/nginx/nginx.conf new file mode 100644 index 0000000..0da7dff --- /dev/null +++ b/etc/nginx/nginx.conf @@ -0,0 +1,25 @@ +worker_processes 1; + +error_log stderr notice; + +events { + worker_connections 1024; +} + +http { + include mime.types; + default_type application/octet-stream; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + sendfile on; + #tcp_nopush on; + + keepalive_timeout 65; + + #gzip on; + + include /etc/nginx/conf.d/*.conf; +} diff --git a/etc/periodic/daily/10-letsencrypt-renew b/etc/periodic/daily/10-letsencrypt-renew new file mode 100644 index 0000000..c7c33d8 --- /dev/null +++ b/etc/periodic/daily/10-letsencrypt-renew @@ -0,0 +1,8 @@ +#!/bin/sh + +echo "Running certbot renew on "$(date) + +certbot renew --noninteractive \ + --webroot --webroot-path /usr/share/nginx/acme \ + --preferred-challenges http \ + --post-hook "nginx -s reload" diff --git a/nginx-letsencrypt.sh b/nginx-letsencrypt.sh new file mode 100644 index 0000000..b1a77b2 --- /dev/null +++ b/nginx-letsencrypt.sh @@ -0,0 +1,66 @@ +#!/bin/sh + +# Check the environment o make sure that the required variables are set. +if [ -z "$DOMAINS" ]; then + echo "Please pass your DOMAINS as an environment variable" + exit 1 +fi + +# Load or create saved environment. +if [ ! -f "/var/lib/nginx-letsencrypt/current_env" ]; then + echo -e "CURRENT_DOMAINS=\"$DOMAINS\" CURRENT_DHLEVEL=\"$DHLEVEL\"" > /var/lib/nginx-letsencrypt/current_env +fi + +. /var/lib/nginx-letsencrypt/current_env + +# Compare dhparams existence and level. (Re)create if necessary. +if [ ! "$DHLEVEL" = "$CURRENT_DHLEVEL" ]; then + rm -rf /var/lib/nginx-letsencrypt/dhparams.pem + echo "DH parameters size changed, deleting old file" +fi + +if [ ! -f "/var/lib/nginx-letsencrypt/dhparams.pem" ]; then + openssl dhparam -out /var/lib/nginx-letsencrypt/dhparams.pem "$DHLEVEL" + echo "$DHLEVEL-bit parameters successfully created" +else + echo "$DHLEVEL-bit DH parameters present" +fi + +# Check whether to use e-mail when generating certificate. +if [ ! -z $EMAIL ]; then + echo "E-mail address entered: ${EMAIL}" + EMAILPARAM="-m ${EMAIL}" +else + echo "No e-mail address entered, proceeding unsafely" + EMAILPARAM="--register-unsafely-without-email" +fi + +# Check for changes in DOMAINS. +# Revoke certificate if necessary. +if [ ! "$DOMAINS" = "$CURRENT_DOMAINS" ]; then + echo "Different DOMAINS entered than what was used before" + echo "Revoking and deleting existing certificate" + + certbot revoke --non-interactive \ + --cert-path /etc/letsencrypt/live/nginx-letsencrypt/fullchain.pem +fi + +# Generate certificate if necessary. +if [ ! -f "/var/lib/nginx-letsencrypt/keys/fullchain.pem" ]; then + echo "Generating new certificate" + + certbot certonly --non-interactive --agree-tos \ + --standalone --preferred-challenges tls-sni --rsa-key-size 4096 \ + --cert-name nginx-letsencrypt $EMAILPARAM -d $DOMAINS +else + /etc/periodic/daily/10-letsencrypt-renew +fi + +# Save environment. +echo -e "CURRENT_DOMAINS=\"$DOMAINS\" CURRENT_DHLEVEL=\"$DHLEVEL\"" > /var/lib/nginx-letsencrypt/current_env + +# Start crond for autorenewal +crond -f -l 6 -L /dev/stdout & + +# Start NGINX. +nginx -g "daemon off;"