Parcourir la source

Merge pull request #589 from kamermans/feature_ssl_improvement

SSL security enhancement
Jason Wilder il y a 8 ans
Parent
commit
02121df3b9
40 fichiers modifiés avec 327 ajouts et 48 suppressions
  1. 1 0
      .gitignore
  2. 2 2
      Dockerfile
  3. 3 3
      Dockerfile.alpine
  4. 22 5
      README.md
  5. 8 0
      dhparam.pem.default
  6. 5 1
      docker-entrypoint.sh
  7. 45 0
      generate-dhparam.sh
  8. 7 1
      nginx.tmpl
  9. 8 0
      test/lib/ssl/dhparam.pem
  10. 6 1
      test/requirements/Dockerfile-nginx-proxy-tester
  11. 3 3
      test/test_DOCKER_HOST_unix_socket.yml
  12. 3 2
      test/test_composev2.yml
  13. 5 4
      test/test_custom/test_defaults-location.yml
  14. 4 3
      test/test_custom/test_defaults.yml
  15. 4 3
      test/test_custom/test_location-per-vhost.yml
  16. 4 3
      test/test_custom/test_per-vhost.yml
  17. 4 3
      test/test_custom/test_proxy-wide.yml
  18. 1 0
      test/test_default-host.yml
  19. 2 1
      test/test_dockergen/test_dockergen_v2.yml
  20. 1 1
      test/test_dockergen/test_dockergen_v3.py
  21. 2 1
      test/test_dockergen/test_dockergen_v3.yml
  22. 1 0
      test/test_events.yml
  23. 2 1
      test/test_headers/test_http.yml
  24. 1 0
      test/test_headers/test_https.yml
  25. 3 2
      test/test_ipv6.yml
  26. 1 0
      test/test_multiple-hosts.yml
  27. 4 3
      test/test_multiple-networks.yml
  28. 1 0
      test/test_multiple-ports/test_VIRTUAL_PORT.yml
  29. 1 0
      test/test_multiple-ports/test_default-80.yml
  30. 2 1
      test/test_multiple-ports/test_single-port-not-80.yml
  31. 3 2
      test/test_nominal.yml
  32. 93 0
      test/test_ssl/test_dhparam.py
  33. 16 0
      test/test_ssl/test_dhparam.yml
  34. 44 0
      test/test_ssl/test_dhparam_generation.py
  35. 8 0
      test/test_ssl/test_dhparam_generation.yml
  36. 1 0
      test/test_ssl/test_nohttp.yml
  37. 2 1
      test/test_ssl/test_nohttps.yml
  38. 1 0
      test/test_ssl/test_noredirect.yml
  39. 1 0
      test/test_ssl/test_wildcard.yml
  40. 2 1
      test/test_wildcard_host.yml

+ 1 - 0
.gitignore

@@ -1,2 +1,3 @@
 **/__pycache__/
 **/.cache/
+.idea/

+ 2 - 2
Dockerfile

@@ -9,9 +9,9 @@ RUN apt-get update \
  && apt-get clean \
  && rm -r /var/lib/apt/lists/*
 
+
 # Configure Nginx and apply fix for very long server names
 RUN echo "daemon off;" >> /etc/nginx/nginx.conf \
- && sed -i 's/^http {/&\n    server_names_hash_bucket_size 128;/g' /etc/nginx/nginx.conf \
  && sed -i 's/worker_processes  1/worker_processes  auto/' /etc/nginx/nginx.conf
 
 # Install Forego
@@ -29,7 +29,7 @@ WORKDIR /app/
 
 ENV DOCKER_HOST unix:///tmp/docker.sock
 
-VOLUME ["/etc/nginx/certs"]
+VOLUME ["/etc/nginx/certs", "/etc/nginx/dhparam"]
 
 ENTRYPOINT ["/app/docker-entrypoint.sh"]
 CMD ["forego", "start", "-r"]

+ 3 - 3
Dockerfile.alpine

@@ -3,12 +3,12 @@ MAINTAINER Jason Wilder mail@jasonwilder.com
 
 # Install wget and install/updates certificates
 RUN apk add --no-cache --virtual .run-deps \
-    ca-certificates bash wget \
+    ca-certificates bash wget openssl \
     && update-ca-certificates
 
+
 # Configure Nginx and apply fix for very long server names
 RUN echo "daemon off;" >> /etc/nginx/nginx.conf \
- && sed -i 's/^http {/&\n    server_names_hash_bucket_size 128;/g' /etc/nginx/nginx.conf \
  && sed -i 's/worker_processes  1/worker_processes  auto/' /etc/nginx/nginx.conf
 
 # Install Forego
@@ -26,7 +26,7 @@ WORKDIR /app/
 
 ENV DOCKER_HOST unix:///tmp/docker.sock
 
-VOLUME ["/etc/nginx/certs"]
+VOLUME ["/etc/nginx/certs", "/etc/nginx/dhparam"]
 
 ENTRYPOINT ["/app/docker-entrypoint.sh"]
 CMD ["forego", "start", "-r"]

+ 22 - 5
README.md

@@ -100,6 +100,8 @@ In this example, the `my-nginx-proxy` container will be connected to `my-network
 
 If you would like the reverse proxy to connect to your backend using HTTPS instead of HTTP, set `VIRTUAL_PROTO=https` on the backend container.
 
+> Note: If you use `VIRTUAL_PROTO=https` and your backend container exposes port 80 and 443, `nginx-proxy` will use HTTPS on port 80.  This is almost certainly not what you want, so you should also include `VIRTUAL_PORT=443`.
+
 ### uWSGI Backends
 
 If you would like to connect to uWSGI backend, set `VIRTUAL_PROTO=uwsgi` on the
@@ -171,9 +173,21 @@ By default, Docker is not able to mount directories on the host machine to conta
 
 #### Diffie-Hellman Groups
 
-If you have Diffie-Hellman groups enabled, the files should be named after the virtual host with a
+Diffie-Hellman groups are enabled by default, with a pregenerated key in `/etc/nginx/dhparam/dhparam.pem`.
+You can mount a different `dhparam.pem` file at that location to override the default cert.
+To use custom `dhparam.pem` files per-virtual-host, the files should be named after the virtual host with a
 `dhparam` suffix and `.pem` extension. For example, a container with `VIRTUAL_HOST=foo.bar.com`
-should have a `foo.bar.com.dhparam.pem` file in the certs directory.
+should have a `foo.bar.com.dhparam.pem` file in the `/etc/nginx/certs` directory.
+
+> NOTE: If you don't mount a `dhparam.pem` file at `/etc/nginx/dhparam/dhparam.pem`, one will be generated
+at startup.  Since it can take minutes to generate a new `dhparam.pem`, it is done at low priority in the
+background.  Once generation is complete, the `dhparams.pem` is saved on a persistent volume and nginx
+is reloaded.  This generation process only occurs the first time you start `nginx-proxy`.
+
+> COMPATIBILITY WARNING: The default generated `dhparam.pem` key is 2048 bits for A+ security.  Some 
+> older clients (like Java 6 and 7) do not support DH keys with over 1024 bits.  In order to support these
+> clients, you must either provide your own `dhparam.pem`, or tell `nginx-proxy` to generate a 1024-bit
+> key on startup by passing `-e DHPARAM_BITS=1024`.
 
 #### Wildcard Certificates
 
@@ -189,10 +203,13 @@ and `CERT_NAME=shared` will then use this shared cert.
 
 #### How SSL Support Works
 
-The SSL cipher configuration is based on [mozilla nginx intermediate profile](https://wiki.mozilla.org/Security/Server_Side_TLS#Nginx) which
+The SSL cipher configuration is based on the [Mozilla nginx intermediate profile](https://wiki.mozilla.org/Security/Server_Side_TLS#Nginx) which
 should provide compatibility with clients back to Firefox 1, Chrome 1, IE 7, Opera 5, Safari 1,
-Windows XP IE8, Android 2.3, Java 7.  The configuration also enables HSTS, and SSL
-session caches.
+Windows XP IE8, Android 2.3, Java 7.  Note that the DES-based TLS ciphers were removed for security.
+The configuration also enables HSTS, PFS, and SSL session caches.  Currently TLS 1.0, 1.1 and 1.2
+are supported.  TLS 1.0 is deprecated but its end of life is not until June 30, 2018.  It is being 
+included because the following browsers will stop working when it is removed: Chrome < 22, Firefox < 27,
+IE < 11, Safari < 7, iOS < 5, Android Browser < 5.
 
 The default behavior for the proxy when port 80 and 443 are exposed is as follows:
 

+ 8 - 0
dhparam.pem.default

@@ -0,0 +1,8 @@
+-----BEGIN DH PARAMETERS-----
+MIIBCAKCAQEAzB2nIGzpVq7afJnKBm1X0d64avwOlP2oneiKwxRHdDI/5+6TpH1P
+F8ipodGuZBUMmupoB3D34pu2Qq5boNW983sm18ww9LMz2i/pxhSdB+mYAew+A6h6
+ltQ5pNtyn4NaKw1SDFkqvde3GNPhaWoPDbZDJhpHGblR3w1b/ag+lTLZUvVwcD8L
+jYS9f9YWAC6T7WxAxh4zvu1Z0I1EKde8KYBxrreZNheXpXHqMNyJYZCaY2Hb/4oI
+EL65qZq1GCWezpWMjhk6pOnV5gbvqfhoazCv/4OdRv6RoWOIYBNs9BmGho4AtXqV
+FYLdYDhOvN4aVs9Ir+G8ouwiRnix24+UewIBAg==
+-----END DH PARAMETERS-----

+ 5 - 1
docker-entrypoint.sh

@@ -2,7 +2,7 @@
 set -e
 
 # Warn if the DOCKER_HOST socket does not exist
-if [[ $DOCKER_HOST == unix://* ]]; then
+if [[ $DOCKER_HOST = unix://* ]]; then
 	socket_file=${DOCKER_HOST#unix://}
 	if ! [ -S $socket_file ]; then
 		cat >&2 <<-EOT
@@ -14,6 +14,10 @@ if [[ $DOCKER_HOST == unix://* ]]; then
 	fi
 fi
 
+# Generate dhparam file if required
+# Note: if $DHPARAM_BITS is not defined, generate-dhparam.sh will use 2048 as a default
+/app/generate-dhparam.sh $DHPARAM_BITS
+
 # If the user has run the default command and the socket doesn't exist, fail
 if [ "$socketMissing" = 1 -a "$1" = forego -a "$2" = start -a "$3" = '-r' ]; then
 	exit 1

+ 45 - 0
generate-dhparam.sh

@@ -0,0 +1,45 @@
+#!/bin/bash -e
+
+# The first argument is the bit depth of the dhparam, or 2048 if unspecified
+DHPARAM_BITS=${1:-2048}
+
+# If a dhparam file is not available, use the pre-generated one and generate a new one in the background.
+# Note that /etc/nginx/dhparam is a volume, so this dhparam will persist restarts.
+PREGEN_DHPARAM_FILE="/app/dhparam.pem.default"
+DHPARAM_FILE="/etc/nginx/dhparam/dhparam.pem"
+GEN_LOCKFILE="/tmp/dhparam_generating.lock"
+
+# The hash of the pregenerated dhparam file is used to check if the pregen dhparam is already in use
+PREGEN_HASH=$(md5sum $PREGEN_DHPARAM_FILE | cut -d" " -f1)
+if [[ -f $DHPARAM_FILE ]]; then
+    CURRENT_HASH=$(md5sum $DHPARAM_FILE | cut -d" " -f1)
+    if [[ $PREGEN_HASH != $CURRENT_HASH ]]; then
+        # There is already a dhparam, and it's not the default
+        echo "Custom dhparam.pem file found, generation skipped"
+        exit 0
+    fi
+
+    if [[ -f $GEN_LOCKFILE ]]; then
+        # Generation is already in progress
+        exit 0
+    fi
+fi
+
+cat >&2 <<-EOT
+WARNING: $DHPARAM_FILE was not found. A pre-generated dhparam.pem will be used for now while a new one
+is being generated in the background.  Once the new dhparam.pem is in place, nginx will be reloaded.
+EOT
+
+# Put the default dhparam file in place so we can start immediately
+cp $PREGEN_DHPARAM_FILE $DHPARAM_FILE
+touch $GEN_LOCKFILE
+
+# Generate a new dhparam in the background in a low priority and reload nginx when finished (grep removes the progress indicator).
+(
+    (
+        nice -n +5 openssl dhparam -out $DHPARAM_FILE $DHPARAM_BITS 2>&1 \
+        && echo "dhparam generation complete, reloading nginx" \
+        && nginx -s reload
+    ) | grep -vE '^[\.+]+'
+    rm $GEN_LOCKFILE
+) &disown

+ 7 - 1
nginx.tmpl

@@ -38,6 +38,12 @@ map $http_upgrade $proxy_connection {
   '' close;
 }
 
+# Apply fix for very long server names
+server_names_hash_bucket_size 128;
+
+# Default dhparam
+ssl_dhparam /etc/nginx/dhparam/dhparam.pem;
+
 # Set appropriate X-Forwarded-Ssl header
 map $scheme $proxy_x_forwarded_ssl {
   default off;
@@ -178,7 +184,7 @@ 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_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:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:!DSS';
 
 	ssl_prefer_server_ciphers on;
 	ssl_session_timeout 5m;

+ 8 - 0
test/lib/ssl/dhparam.pem

@@ -0,0 +1,8 @@
+-----BEGIN DH PARAMETERS-----
+MIIBCAKCAQEA1cae6HqPSgicEuAuSCf6Ii3d6qMX9Ta8lnwoX0JQ0CWK7mzaiiIi
+dY7oHmc4cq0S3SH+g0tdLP9yqygFS9hdUGINwS2VV6poj2/vdL/dUshegyxpEH58
+nofCPnFDeKkcPDMYAlGS8zjp60TsBkRJKcrxxwnjod1Q5mWuMN5KH3sxs842udKH
+0nHFE9kKW/NfXb+EGsjpocGpf786cGuCO2d00THsoItOEcM9/aI8DX1QcyxAHR6D
+HaYTFJnyyx8Q44u27M15idI4pbNoKORlotiuOwCTGYCfbN14aOV+Ict7aSF8FWpP
+48j9SMNuIu2DlF9pNLo6fsrOjYY3c9X12wIBAg==
+-----END DH PARAMETERS-----

+ 6 - 1
test/requirements/Dockerfile-nginx-proxy-tester

@@ -1,5 +1,10 @@
-FROM python:2.7
+FROM python:2.7-alpine
+
+# Note: we're using alpine because it has openssl 1.0.2, which we need for testing
+RUN apk add --update bash openssl curl && rm -rf /var/cache/apk/*
+
 COPY python-requirements.txt /requirements.txt
 RUN pip install -r /requirements.txt
+
 WORKDIR /test
 ENTRYPOINT ["pytest"]

+ 3 - 3
test/test_DOCKER_HOST_unix_socket.yml

@@ -1,5 +1,5 @@
 web1:
-  image: web 
+  image: web
   expose:
     - "81"
   environment:
@@ -8,7 +8,7 @@ web1:
 
 web2:
   image: web
-  expose: 
+  expose:
     - "82"
   environment:
     WEB_PORTS: 82
@@ -19,6 +19,6 @@ sut:
   image: jwilder/nginx-proxy:test
   volumes:
     - /var/run/docker.sock:/f00.sock:ro
+    - ./lib/ssl/dhparam.pem:/etc/nginx/dhparam/dhparam.pem:ro
   environment:
     DOCKER_HOST: unix:///f00.sock
-

+ 3 - 2
test/test_composev2.yml

@@ -4,11 +4,12 @@ services:
     image: jwilder/nginx-proxy:test
     volumes:
       - /var/run/docker.sock:/tmp/docker.sock:ro
+      - ./lib/ssl/dhparam.pem:/etc/nginx/dhparam/dhparam.pem:ro
 
   web:
-    image: web 
+    image: web
     expose:
       - "81"
     environment:
       WEB_PORTS: 81
-      VIRTUAL_HOST: web.nginx-proxy.local
+      VIRTUAL_HOST: web.nginx-proxy.local

+ 5 - 4
test/test_custom/test_defaults-location.yml

@@ -2,11 +2,12 @@ nginx-proxy:
   image: jwilder/nginx-proxy:test
   volumes:
     - /var/run/docker.sock:/tmp/docker.sock:ro
+    - ../lib/ssl/dhparam.pem:/etc/nginx/dhparam/dhparam.pem:ro
     - ./my_custom_proxy_settings.conf:/etc/nginx/vhost.d/default_location:ro
     - ./my_custom_proxy_settings_bar.conf:/etc/nginx/vhost.d/web3.nginx-proxy.local_location:ro
 
 web1:
-  image: web 
+  image: web
   expose:
     - "81"
   environment:
@@ -14,7 +15,7 @@ web1:
     VIRTUAL_HOST: web1.nginx-proxy.local
 
 web2:
-  image: web 
+  image: web
   expose:
     - "82"
   environment:
@@ -22,9 +23,9 @@ web2:
     VIRTUAL_HOST: web2.nginx-proxy.local
 
 web3:
-  image: web 
+  image: web
   expose:
     - "83"
   environment:
     WEB_PORTS: 83
-    VIRTUAL_HOST: web3.nginx-proxy.local
+    VIRTUAL_HOST: web3.nginx-proxy.local

+ 4 - 3
test/test_custom/test_defaults.yml

@@ -4,10 +4,11 @@ services:
     image: jwilder/nginx-proxy:test
     volumes:
       - /var/run/docker.sock:/tmp/docker.sock:ro
+      - ../lib/ssl/dhparam.pem:/etc/nginx/dhparam/dhparam.pem:ro
       - ./my_custom_proxy_settings.conf:/etc/nginx/proxy.conf:ro
 
   web1:
-    image: web 
+    image: web
     expose:
       - "81"
     environment:
@@ -15,9 +16,9 @@ services:
       VIRTUAL_HOST: web1.nginx-proxy.local
 
   web2:
-    image: web 
+    image: web
     expose:
       - "82"
     environment:
       WEB_PORTS: 82
-      VIRTUAL_HOST: web2.nginx-proxy.local
+      VIRTUAL_HOST: web2.nginx-proxy.local

+ 4 - 3
test/test_custom/test_location-per-vhost.yml

@@ -4,10 +4,11 @@ services:
     image: jwilder/nginx-proxy:test
     volumes:
       - /var/run/docker.sock:/tmp/docker.sock:ro
+      - ../lib/ssl/dhparam.pem:/etc/nginx/dhparam/dhparam.pem:ro
       - ./my_custom_proxy_settings.conf:/etc/nginx/vhost.d/web1.nginx-proxy.local_location:ro
 
   web1:
-    image: web 
+    image: web
     expose:
       - "81"
     environment:
@@ -15,9 +16,9 @@ services:
       VIRTUAL_HOST: web1.nginx-proxy.local
 
   web2:
-    image: web 
+    image: web
     expose:
       - "82"
     environment:
       WEB_PORTS: 82
-      VIRTUAL_HOST: web2.nginx-proxy.local
+      VIRTUAL_HOST: web2.nginx-proxy.local

+ 4 - 3
test/test_custom/test_per-vhost.yml

@@ -4,10 +4,11 @@ services:
     image: jwilder/nginx-proxy:test
     volumes:
       - /var/run/docker.sock:/tmp/docker.sock:ro
+      - ../lib/ssl/dhparam.pem:/etc/nginx/dhparam/dhparam.pem:ro
       - ./my_custom_proxy_settings.conf:/etc/nginx/vhost.d/web1.nginx-proxy.local:ro
 
   web1:
-    image: web 
+    image: web
     expose:
       - "81"
     environment:
@@ -15,9 +16,9 @@ services:
       VIRTUAL_HOST: web1.nginx-proxy.local
 
   web2:
-    image: web 
+    image: web
     expose:
       - "82"
     environment:
       WEB_PORTS: 82
-      VIRTUAL_HOST: web2.nginx-proxy.local
+      VIRTUAL_HOST: web2.nginx-proxy.local

+ 4 - 3
test/test_custom/test_proxy-wide.yml

@@ -4,10 +4,11 @@ services:
     image: jwilder/nginx-proxy:test
     volumes:
       - /var/run/docker.sock:/tmp/docker.sock:ro
+      - ../lib/ssl/dhparam.pem:/etc/nginx/dhparam/dhparam.pem:ro
       - ./my_custom_proxy_settings.conf:/etc/nginx/conf.d/my_custom_proxy_settings.conf:ro
 
   web1:
-    image: web 
+    image: web
     expose:
       - "81"
     environment:
@@ -15,9 +16,9 @@ services:
       VIRTUAL_HOST: web1.nginx-proxy.local
 
   web2:
-    image: web 
+    image: web
     expose:
       - "82"
     environment:
       WEB_PORTS: 82
-      VIRTUAL_HOST: web2.nginx-proxy.local
+      VIRTUAL_HOST: web2.nginx-proxy.local

+ 1 - 0
test/test_default-host.yml

@@ -13,5 +13,6 @@ sut:
   image: jwilder/nginx-proxy:test
   volumes:
     - /var/run/docker.sock:/tmp/docker.sock:ro
+    - ./lib/ssl/dhparam.pem:/etc/nginx/dhparam/dhparam.pem:ro
   environment:
     DEFAULT_HOST: web1.tld

+ 2 - 1
test/test_dockergen/test_dockergen_v2.yml

@@ -6,6 +6,7 @@ services:
     container_name: nginx
     volumes:
       - /etc/nginx/conf.d
+      - ../lib/ssl/dhparam.pem:/etc/nginx/dhparam/dhparam.pem:ro
 
   dockergen:
     image: jwilder/docker-gen
@@ -23,4 +24,4 @@ services:
       - "80"
     environment:
       WEB_PORTS: 80
-      VIRTUAL_HOST: whoami.nginx.container.docker
+      VIRTUAL_HOST: whoami.nginx.container.docker

+ 1 - 1
test/test_dockergen/test_dockergen_v3.py

@@ -2,7 +2,7 @@ import os
 import docker
 import logging
 import pytest
-
+import re
 
 def versiontuple(v):
     """

+ 2 - 1
test/test_dockergen/test_dockergen_v3.yml

@@ -5,6 +5,7 @@ services:
     container_name: nginx
     volumes:
       - nginx_conf:/etc/nginx/conf.d
+      - ../lib/ssl/dhparam.pem:/etc/nginx/dhparam/dhparam.pem:ro
 
   dockergen:
     image: jwilder/docker-gen
@@ -24,4 +25,4 @@ services:
       VIRTUAL_HOST: whoami.nginx.container.docker
 
 volumes:
-  nginx_conf: {}
+  nginx_conf: {}

+ 1 - 0
test/test_events.yml

@@ -2,3 +2,4 @@ nginxproxy:
   image: jwilder/nginx-proxy:test
   volumes:
     - /var/run/docker.sock:/tmp/docker.sock:ro
+    - ./lib/ssl/dhparam.pem:/etc/nginx/dhparam/dhparam.pem:ro

+ 2 - 1
test/test_headers/test_http.yml

@@ -10,4 +10,5 @@ web:
 sut:
   image: jwilder/nginx-proxy:test
   volumes:
-    - /var/run/docker.sock:/tmp/docker.sock:ro
+    - /var/run/docker.sock:/tmp/docker.sock:ro
+    - ../lib/ssl/dhparam.pem:/etc/nginx/dhparam/dhparam.pem:ro

+ 1 - 0
test/test_headers/test_https.yml

@@ -13,3 +13,4 @@ sut:
     - /var/run/docker.sock:/tmp/docker.sock:ro
     - ./certs/web.nginx-proxy.tld.crt:/etc/nginx/certs/web.nginx-proxy.tld.crt:ro
     - ./certs/web.nginx-proxy.tld.key:/etc/nginx/certs/web.nginx-proxy.tld.key:ro
+    - ../lib/ssl/dhparam.pem:/etc/nginx/dhparam/dhparam.pem:ro

+ 3 - 2
test/test_ipv6.yml

@@ -1,5 +1,5 @@
 web1:
-  image: web 
+  image: web
   expose:
     - "81"
   environment:
@@ -8,7 +8,7 @@ web1:
 
 web2:
   image: web
-  expose: 
+  expose:
     - "82"
   environment:
     WEB_PORTS: 82
@@ -19,5 +19,6 @@ sut:
   image: jwilder/nginx-proxy:test
   volumes:
     - /var/run/docker.sock:/tmp/docker.sock:ro
+    - ./lib/ssl/dhparam.pem:/etc/nginx/dhparam/dhparam.pem:ro
   environment:
     ENABLE_IPV6: "true"

+ 1 - 0
test/test_multiple-hosts.yml

@@ -11,3 +11,4 @@ sut:
   image: jwilder/nginx-proxy:test
   volumes:
     - /var/run/docker.sock:/tmp/docker.sock:ro
+    - ./lib/ssl/dhparam.pem:/etc/nginx/dhparam/dhparam.pem:ro

+ 4 - 3
test/test_multiple-networks.yml

@@ -9,12 +9,13 @@ services:
     image: jwilder/nginx-proxy:test
     volumes:
       - /var/run/docker.sock:/tmp/docker.sock:ro
+      - ./lib/ssl/dhparam.pem:/etc/nginx/dhparam/dhparam.pem:ro
     networks:
       - net1
       - net2
 
   web1:
-    image: web 
+    image: web
     expose:
       - "81"
     environment:
@@ -24,11 +25,11 @@ services:
       - net1
 
   web2:
-    image: web 
+    image: web
     expose:
       - "82"
     environment:
       WEB_PORTS: 82
       VIRTUAL_HOST: web2.nginx-proxy.local
     networks:
-      - net2
+      - net2

+ 1 - 0
test/test_multiple-ports/test_VIRTUAL_PORT.yml

@@ -12,3 +12,4 @@ sut:
   image: jwilder/nginx-proxy:test
   volumes:
     - /var/run/docker.sock:/tmp/docker.sock:ro
+    - ../lib/ssl/dhparam.pem:/etc/nginx/dhparam/dhparam.pem:ro

+ 1 - 0
test/test_multiple-ports/test_default-80.yml

@@ -11,3 +11,4 @@ sut:
   image: jwilder/nginx-proxy:test
   volumes:
     - /var/run/docker.sock:/tmp/docker.sock:ro
+    - ../lib/ssl/dhparam.pem:/etc/nginx/dhparam/dhparam.pem:ro

+ 2 - 1
test/test_multiple-ports/test_single-port-not-80.yml

@@ -10,4 +10,5 @@ web:
 sut:
   image: jwilder/nginx-proxy:test
   volumes:
-    - /var/run/docker.sock:/tmp/docker.sock:ro
+    - /var/run/docker.sock:/tmp/docker.sock:ro
+    - ../lib/ssl/dhparam.pem:/etc/nginx/dhparam/dhparam.pem:ro

+ 3 - 2
test/test_nominal.yml

@@ -1,5 +1,5 @@
 web1:
-  image: web 
+  image: web
   expose:
     - "81"
   environment:
@@ -8,7 +8,7 @@ web1:
 
 web2:
   image: web
-  expose: 
+  expose:
     - "82"
   environment:
     WEB_PORTS: 82
@@ -19,3 +19,4 @@ sut:
   image: jwilder/nginx-proxy:test
   volumes:
     - /var/run/docker.sock:/tmp/docker.sock:ro
+    - ./lib/ssl/dhparam.pem:/etc/nginx/dhparam/dhparam.pem:ro

+ 93 - 0
test/test_ssl/test_dhparam.py

@@ -0,0 +1,93 @@
+import re
+import subprocess
+
+import backoff
+import docker
+import pytest
+
+docker_client = docker.from_env()
+
+
+###############################################################################
+#
+# Tests helpers
+#
+###############################################################################
+
+@backoff.on_exception(backoff.constant, AssertionError, interval=2, max_tries=15, jitter=None)
+def assert_log_contains(expected_log_line):
+    """
+    Check that the nginx-proxy container log contains a given string.
+    The backoff decorator will retry the check 15 times with a 2 seconds delay.
+
+    :param expected_log_line: string to search for
+    :return: None
+    :raises: AssertError if the expected string is not found in the log
+    """
+    sut_container = docker_client.containers.get("nginxproxy")
+    docker_logs = sut_container.logs(stdout=True, stderr=True, stream=False, follow=False)
+    assert expected_log_line in docker_logs
+
+
+def require_openssl(required_version):
+    """
+    This function checks that the required version of OpenSSL is present, and skips the test if not.
+    Use it as a test function decorator:
+
+        @require_openssl("2.3.4")
+        def test_something():
+            ...
+
+    :param required_version: minimal required version as a string: "1.2.3"
+    """
+
+    def versiontuple(v):
+        clean_v = re.sub("[^\d\.]", "", v)
+        return tuple(map(int, (clean_v.split("."))))
+
+    try:
+        command_output = subprocess.check_output(["openssl", "version"])
+    except OSError:
+        return pytest.mark.skip("openssl command is not available in test environment")
+    else:
+        if not command_output:
+            raise Exception("Could not get openssl version")
+        openssl_version = command_output.split()[1]
+        return pytest.mark.skipif(
+            versiontuple(openssl_version) < versiontuple(required_version),
+            reason="openssl v%s is less than required version %s" % (openssl_version, required_version))
+
+
+###############################################################################
+#
+# Tests
+#
+###############################################################################
+
+def test_dhparam_is_not_generated_if_present(docker_compose):
+    sut_container = docker_client.containers.get("nginxproxy")
+    assert sut_container.status == "running"
+
+    assert_log_contains("Custom dhparam.pem file found, generation skipped")
+
+    # Make sure the dhparam in use is not the default, pre-generated one
+    default_checksum = sut_container.exec_run("md5sum /app/dhparam.pem.default").split()
+    current_checksum = sut_container.exec_run("md5sum /etc/nginx/dhparam/dhparam.pem").split()
+    assert default_checksum[0] != current_checksum[0]
+
+
+def test_web5_https_works(docker_compose, nginxproxy):
+    r = nginxproxy.get("https://web5.nginx-proxy.tld/port", allow_redirects=False)
+    assert r.status_code == 200
+    assert "answer from port 85\n" in r.text
+
+
+@require_openssl("1.0.2")
+def test_web5_dhparam_is_used(docker_compose):
+    sut_container = docker_client.containers.get("nginxproxy")
+    assert sut_container.status == "running"
+
+    host = "%s:443" % sut_container.attrs["NetworkSettings"]["IPAddress"]
+    r = subprocess.check_output(
+        "echo '' | openssl s_client -verify 0 -connect %s -cipher 'EDH' | grep 'Server Temp Key'" % host, shell=True)
+    assert "Server Temp Key: DH, 2048 bits\n" == r

+ 16 - 0
test/test_ssl/test_dhparam.yml

@@ -0,0 +1,16 @@
+web5:
+  image: web
+  expose:
+    - "85"
+  environment:
+    WEB_PORTS: "85"
+    VIRTUAL_HOST: "web5.nginx-proxy.tld"
+
+
+sut:
+  image: jwilder/nginx-proxy:test
+  container_name: nginxproxy
+  volumes:
+    - /var/run/docker.sock:/tmp/docker.sock:ro
+    - ../lib/ssl/dhparam.pem:/etc/nginx/dhparam/dhparam.pem:ro
+    - ./certs:/etc/nginx/certs:ro

+ 44 - 0
test/test_ssl/test_dhparam_generation.py

@@ -0,0 +1,44 @@
+import backoff
+import docker
+
+docker_client = docker.from_env()
+
+
+###############################################################################
+#
+# Tests helpers
+#
+###############################################################################
+
+@backoff.on_exception(backoff.constant, AssertionError, interval=2, max_tries=15, jitter=None)
+def assert_log_contains(expected_log_line):
+    """
+    Check that the nginx-proxy container log contains a given string.
+    The backoff decorator will retry the check 15 times with a 2 seconds delay.
+
+    :param expected_log_line: string to search for
+    :return: None
+    :raises: AssertError if the expected string is not found in the log
+    """
+    sut_container = docker_client.containers.get("nginxproxy")
+    docker_logs = sut_container.logs(stdout=True, stderr=True, stream=False, follow=False)
+    assert expected_log_line in docker_logs
+
+
+###############################################################################
+#
+# Tests
+#
+###############################################################################
+
+def test_dhparam_is_generated_if_missing(docker_compose):
+    sut_container = docker_client.containers.get("nginxproxy")
+    assert sut_container.status == "running"
+
+    assert_log_contains("Generating DH parameters")
+    assert_log_contains("dhparam generation complete, reloading nginx")
+
+    # Make sure the dhparam in use is not the default, pre-generated one
+    default_checksum = sut_container.exec_run("md5sum /app/dhparam.pem.default").split()
+    generated_checksum = sut_container.exec_run("md5sum /etc/nginx/dhparam/dhparam.pem").split()
+    assert default_checksum[0] != generated_checksum[0]

+ 8 - 0
test/test_ssl/test_dhparam_generation.yml

@@ -0,0 +1,8 @@
+sut:
+  image: jwilder/nginx-proxy:test
+  container_name: nginxproxy
+  volumes:
+    - /var/run/docker.sock:/tmp/docker.sock:ro
+    - ./certs:/etc/nginx/certs:ro
+  environment:
+    - DHPARAM_BITS=256

+ 1 - 0
test/test_ssl/test_nohttp.yml

@@ -12,4 +12,5 @@ sut:
   image: jwilder/nginx-proxy:test
   volumes:
     - /var/run/docker.sock:/tmp/docker.sock:ro
+    - ../lib/ssl/dhparam.pem:/etc/nginx/dhparam/dhparam.pem:ro
     - ./certs:/etc/nginx/certs:ro

+ 2 - 1
test/test_ssl/test_nohttps.yml

@@ -11,4 +11,5 @@ web:
 sut:
   image: jwilder/nginx-proxy:test
   volumes:
-    - /var/run/docker.sock:/tmp/docker.sock:ro
+    - /var/run/docker.sock:/tmp/docker.sock:ro
+    - ../lib/ssl/dhparam.pem:/etc/nginx/dhparam/dhparam.pem:ro

+ 1 - 0
test/test_ssl/test_noredirect.yml

@@ -12,4 +12,5 @@ sut:
   image: jwilder/nginx-proxy:test
   volumes:
     - /var/run/docker.sock:/tmp/docker.sock:ro
+    - ../lib/ssl/dhparam.pem:/etc/nginx/dhparam/dhparam.pem:ro
     - ./certs:/etc/nginx/certs:ro

+ 1 - 0
test/test_ssl/test_wildcard.yml

@@ -10,4 +10,5 @@ sut:
   image: jwilder/nginx-proxy:test
   volumes:
     - /var/run/docker.sock:/tmp/docker.sock:ro
+    - ../lib/ssl/dhparam.pem:/etc/nginx/dhparam/dhparam.pem:ro
     - ./certs:/etc/nginx/certs:ro

+ 2 - 1
test/test_wildcard_host.yml

@@ -34,4 +34,5 @@ web4:
 sut:
   image: jwilder/nginx-proxy:test
   volumes:
-    - /var/run/docker.sock:/tmp/docker.sock:ro
+    - /var/run/docker.sock:/tmp/docker.sock:ro
+    - ./lib/ssl/dhparam.pem:/etc/nginx/dhparam/dhparam.pem:ro