From b4f8a53594149304d41d0ca8d9918121bb2a83f3 Mon Sep 17 00:00:00 2001 From: Mikael Capelle Date: Tue, 28 May 2019 08:32:43 +0000 Subject: [PATCH] Initial commit. --- Dockerfile | 3 + docker-compose.yml | 30 +++++ domains.list | 44 ++++++++ nginx.tmpl | 275 +++++++++++++++++++++++++++++++++++++++++++++ renew_certs.sh | 36 ++++++ 5 files changed, 388 insertions(+) create mode 100644 Dockerfile create mode 100644 docker-compose.yml create mode 100644 domains.list create mode 100644 nginx.tmpl create mode 100755 renew_certs.sh diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..d6b7214 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,3 @@ +FROM jwilder/docker-gen:latest + +COPY ./nginx.tmpl /etc/docker-gen/templates/nginx.tmpl diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..92da4fe --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,30 @@ +version: '2' +services: + nginx: + restart: always + image: nginx + container_name: nginx_proxy + ports: + - "80:80" + - "443:443" + volumes: + - /var/www + - /etc/nginx/conf.d + - /etc/letsencrypt:/etc/letsencrypt:ro + - /var/docker/proxy/vhost.d:/etc/nginx/vhost.d:ro + dockergen: + restart: always + depends_on: + - nginx + build: . + image: typename/docker-gen + command: -notify-sighup nginx_proxy -watch /etc/docker-gen/templates/nginx.tmpl /etc/nginx/conf.d/default.conf + volumes_from: + - nginx + volumes: + - /var/run/docker.sock:/tmp/docker.sock:ro + +networks: + default: + external: + name: nginxproxy diff --git a/domains.list b/domains.list new file mode 100644 index 0000000..51a77fc --- /dev/null +++ b/domains.list @@ -0,0 +1,44 @@ +# Cloud +cloud.typename.fr +# pydio.typename.fr + +# Docker registry +docker.typename.fr + +# Teaching +pl.insa.typename.fr + +# Storage +data.typename.fr +pdf.typename.fr + +# Git +gitea.typename.fr +gitlab.typename.fr +# gituto.typename.fr + +# LDAP +ldap.typename.fr +ldapadmin.typename.fr + +# Jupyter +jupyter.typename.fr + +# Mail +# mail.typename.fr + +# Messaging +mattermost.typename.fr + +# Office +office.typename.fr + +# Temporary +domain1.typename.fr +domain2.typename.fr +domain3.typename.fr +zik.typename.fr +zikq.typename.fr + +# Tools +webtools.typename.fr diff --git a/nginx.tmpl b/nginx.tmpl new file mode 100644 index 0000000..0061fee --- /dev/null +++ b/nginx.tmpl @@ -0,0 +1,275 @@ +{{ $CurrentContainer := where $ "ID" .Docker.CurrentContainerID | first }} + +{{ define "upstream" }} + {{ if .Address }} + {{/* If we got the containers from swarm and this container's port is published to host, use host IP:PORT */}} + {{ if and .Container.Node.ID .Address.HostPort }} + # {{ .Container.Node.Name }}/{{ .Container.Name }} + server {{ .Container.Node.Address.IP }}:{{ .Address.HostPort }}; + {{/* If there is no swarm node or the port is not published on host, use container's IP:PORT */}} + {{ else if .Network }} + # {{ .Container.Name }} + server {{ .Network.IP }}:{{ .Address.Port }}; + {{ end }} + {{ else if .Network }} + # {{ .Container.Name }} + server {{ .Network.IP }} down; + {{ end }} +{{ end }} + +# If we receive X-Forwarded-Proto, pass it through; otherwise, pass along the +# scheme used to connect to this server +map $http_x_forwarded_proto $proxy_x_forwarded_proto { + default $http_x_forwarded_proto; + '' $scheme; +} + +# If we receive X-Forwarded-Port, pass it through; otherwise, pass along the +# server port the client connected to +map $http_x_forwarded_port $proxy_x_forwarded_port { + default $http_x_forwarded_port; + '' $server_port; +} + +# If we receive Upgrade, set Connection to "upgrade"; otherwise, delete any +# Connection header that may have been passed to this server +map $http_upgrade $proxy_connection { + default upgrade; + '' close; +} + +# Set appropriate X-Forwarded-Ssl header +map $scheme $proxy_x_forwarded_ssl { + default off; + https on; +} + +gzip_types text/plain text/css application/javascript application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript; + +log_format vhost '$host $remote_addr - $remote_user [$time_local] ' + '"$request" $status $body_bytes_sent ' + '"$http_referer" "$http_user_agent"'; + +access_log off; + +{{ if (exists "/etc/nginx/proxy.conf") }} +include /etc/nginx/proxy.conf; +{{ else }} +# HTTP 1.1 support +proxy_http_version 1.1; +proxy_buffering off; +proxy_set_header Host $http_host; +proxy_set_header Upgrade $http_upgrade; +proxy_set_header Connection $proxy_connection; +proxy_set_header X-Real-IP $remote_addr; +proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; +proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto; +proxy_set_header X-Forwarded-Ssl $proxy_x_forwarded_ssl; +proxy_set_header X-Forwarded-Port $proxy_x_forwarded_port; + +# Mitigate httpoxy attack (see README for details) +proxy_set_header Proxy ""; +{{ end }} + +server { + server_name _; # This is just an invalid value which will never trigger on a real hostname. + listen 80; + access_log /var/log/nginx/access.log vhost; + location /.well-known/ { + root /var/www/certbot; + } + + location / { + return 503; + } +} + +{{ if (and (exists "/etc/nginx/certs/default.crt") (exists "/etc/nginx/certs/default.key")) }} +server { + server_name _; # This is just an invalid value which will never trigger on a real hostname. + listen 443 ssl http2; + access_log /var/log/nginx/access.log vhost; + + ssl_session_tickets off; +# ssl_certificate /etc/nginx/certs/default.crt; +# ssl_certificate_key /etc/nginx/certs/default.key; + + location /.well-known/ { + root /var/www/certbot; + } + + location / { + return 503; + } + +} +{{ end }} + +{{ range $host, $containers := groupByMulti $ "Env.VIRTUAL_HOST" "," }} +{{ $upstream_name := sha1 $host }} +# {{ $host }} +upstream {{ $upstream_name }} { +{{ range $container := $containers }} + {{ $addrLen := len $container.Addresses }} + + {{ range $knownNetwork := $CurrentContainer.Networks }} + {{ range $containerNetwork := $container.Networks }} + {{ if eq $knownNetwork.Name $containerNetwork.Name }} + ## Can be connect with "{{ $containerNetwork.Name }}" network + + {{/* If only 1 port exposed, use that */}} + {{ if eq $addrLen 1 }} + {{ $address := index $container.Addresses 0 }} + {{ template "upstream" (dict "Container" $container "Address" $address "Network" $containerNetwork) }} + {{/* If more than one port exposed, use the one matching VIRTUAL_PORT env var, falling back to standard web port 80 */}} + {{ else }} + {{ $port := coalesce $container.Env.VIRTUAL_PORT "80" }} + {{ $address := where $container.Addresses "Port" $port | first }} + {{ template "upstream" (dict "Container" $container "Address" $address "Network" $containerNetwork) }} + {{ end }} + {{ end }} + {{ end }} + {{ end }} +{{ end }} +} + +{{ $default_host := or ($.Env.DEFAULT_HOST) "" }} +{{ $default_server := index (dict $host "" $default_host "default_server") $host }} + +{{/* Get the VIRTUAL_PROTO defined by containers w/ the same vhost, falling back to "http" */}} +{{ $proto := or (first (groupByKeys $containers "Env.VIRTUAL_PROTO")) "http" }} + +{{/* Get the HTTPS_METHOD defined by containers w/ the same vhost, falling back to "redirect" */}} +{{ $https_method := or (first (groupByKeys $containers "Env.HTTPS_METHOD")) "redirect" }} + +{{/* Get the first cert name defined by containers w/ the same vhost */}} +{{ $certName := (first (groupByKeys $containers "Env.CERT_NAME")) }} + +{{/* Get the best matching cert by name for the vhost. */}} +{{ $vhostCertDir := (closest (dir "/etc/letsencrypt/live/") (printf "%s" $host))}} + +{{/* vhostCert is actually a filename so remove any suffixes since they are added later */}} +{{/* $vhostCert := trimSuffix ".crt" $vhostCert */}} +{{/* $vhostCert := trimSuffix ".key" $vhostCert */}} + +{{/* Use the cert specified on the container or fallback to the best vhost match */}} +{{/* $cert := (coalesce $certName $vhostCert) */}} + +{{ $is_https := (and (ne $https_method "nohttps") (ne $vhostCertDir "") (exists (printf "/etc/letsencrypt/live/%s/fullchain.pem" $host)) (exists (printf "/etc/letsencrypt/live/%s/privkey.pem" $host))) }} + +{{ if $is_https }} + +{{ if eq $https_method "redirect" }} +server { + server_name {{ $host }}; + listen 80 {{ $default_server }}; + access_log /var/log/nginx/access.log vhost; + return 301 https://$host$request_uri; +} +{{ end }} + +server { + server_name {{ $host }}; + listen 443 ssl http2 {{ $default_server }}; + access_log /var/log/nginx/access.log vhost; + + ssl_protocols TLSv1 TLSv1.1 TLSv1.2; + ssl_ciphers 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS'; + + ssl_prefer_server_ciphers on; + ssl_session_timeout 5m; + ssl_session_cache shared:SSL:50m; + ssl_session_tickets off; + + ssl_certificate /etc/letsencrypt/live/{{ (printf "%s" $host) }}/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/{{ (printf "%s" $host) }}/privkey.pem; + + {{ if (exists (printf "/etc/letsencrypt/live/%s.dhparam.pem" $host)) }} + ssl_dhparam {{ printf "/etc/letsencrypt/live/%s.dhparam.pem" $host }}; + {{ end }} + + {{ if (ne $https_method "noredirect") }} + add_header Strict-Transport-Security "max-age=31536000"; + {{ end }} + + {{ if (exists (printf "/etc/nginx/vhost.d/%s" $host)) }} + include {{ printf "/etc/nginx/vhost.d/%s" $host }}; + {{ else if (exists "/etc/nginx/vhost.d/default") }} + include /etc/nginx/vhost.d/default; + {{ end }} + + location /.well-known/ { + root /var/www/certbot; + } + + location / { + {{ if eq $proto "uwsgi" }} + include uwsgi_params; + uwsgi_pass {{ trim $proto }}://{{ trim $upstream_name }}; + {{ else }} + proxy_pass {{ trim $proto }}://{{ trim $upstream_name }}; + {{ end }} + {{ if (exists (printf "/etc/nginx/htpasswd/%s" $host)) }} + auth_basic "Restricted {{ $host }}"; + auth_basic_user_file {{ (printf "/etc/nginx/htpasswd/%s" $host) }}; + {{ end }} + {{ if (exists (printf "/etc/nginx/vhost.d/%s_location" $host)) }} + include {{ printf "/etc/nginx/vhost.d/%s_location" $host}}; + {{ else if (exists "/etc/nginx/vhost.d/default_location") }} + include /etc/nginx/vhost.d/default_location; + {{ end }} + } +} + +{{ end }} + +{{ if or (not $is_https) (eq $https_method "noredirect") }} + +server { + server_name {{ $host }}; + listen 80 {{ $default_server }}; + access_log /var/log/nginx/access.log vhost; + + {{ if (exists (printf "/etc/nginx/vhost.d/%s" $host)) }} + include {{ printf "/etc/nginx/vhost.d/%s" $host }}; + {{ else if (exists "/etc/nginx/vhost.d/default") }} + include /etc/nginx/vhost.d/default; + {{ end }} + + location /.well-known/ { + root /var/www/certbot; + } + + location / { + {{ if eq $proto "uwsgi" }} + include uwsgi_params; + uwsgi_pass {{ trim $proto }}://{{ trim $upstream_name }}; + {{ else }} + proxy_pass {{ trim $proto }}://{{ trim $upstream_name }}; + {{ end }} + {{ if (exists (printf "/etc/nginx/htpasswd/%s" $host)) }} + auth_basic "Restricted {{ $host }}"; + auth_basic_user_file {{ (printf "/etc/nginx/htpasswd/%s" $host) }}; + {{ end }} + {{ if (exists (printf "/etc/nginx/vhost.d/%s_location" $host)) }} + include {{ printf "/etc/nginx/vhost.d/%s_location" $host}}; + {{ else if (exists "/etc/nginx/vhost.d/default_location") }} + include /etc/nginx/vhost.d/default_location; + {{ end }} + } +} + +{{ if (and (not $is_https) (exists "/etc/nginx/certs/default.crt") (exists "/etc/nginx/certs/default.key")) }} +server { + server_name {{ $host }}; + listen 443 ssl http2 {{ $default_server }}; + access_log /var/log/nginx/access.log vhost; + return 500; + + ssl_certificate /etc/nginx/certs/default.crt; + ssl_certificate_key /etc/nginx/certs/default.key; +} +{{ end }} + +{{ end }} +{{ end }} diff --git a/renew_certs.sh b/renew_certs.sh new file mode 100755 index 0000000..2c97318 --- /dev/null +++ b/renew_certs.sh @@ -0,0 +1,36 @@ +#!/bin/bash + +if [ $(id -u) -ne 0 ]; then + echo "This script must be run as root." > /dev/stderr + exit 1 +fi + +domains=$(cat domains.list | grep -v -E '^[[:space:]]*(#.*)?$') + +docker exec -it nginx_proxy mkdir -p /var/www/certbot + +docker run -it --rm --name letsencrypt \ + --volumes-from nginx_proxy \ + -v /etc/letsencrypt:/etc/letsencrypt:rw \ + --network nginxproxy \ + certbot/certbot \ + certonly --webroot --webroot-path /var/www/certbot \ + --config-dir=/etc/letsencrypt \ + --agree-tos --renew-by-default \ + --force-renewal \ + --cert-name typename.fr \ + -d $(echo typename.fr ${domains[*]} | tr ' ' ',') + +for domain in ${domains[*]}; do + echo "Creating symbolic links for ${domain}... " + dir=/etc/letsencrypt/live/${domain} + if [ -e "${dir}" ]; then + rm -rf ${dir} + fi + mkdir ${dir} + for link in /etc/letsencrypt/live/typename.fr/*.pem; do + ln -s $(readlink $link) ${dir}/$(basename $link) + done +done + +docker-compose restart