Forráskód Böngészése

Merge pull request #589 from kamermans/feature_ssl_improvement

SSL security enhancement
Jason Wilder 8 éve
szülő
commit
02121df3b9
40 módosított fájl, 327 hozzáadás és 48 törlés
  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__/
 **/__pycache__/
 **/.cache/
 **/.cache/
+.idea/

+ 2 - 2
Dockerfile

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

+ 3 - 3
Dockerfile.alpine

@@ -3,12 +3,12 @@ MAINTAINER Jason Wilder mail@jasonwilder.com
 
 
 # Install wget and install/updates certificates
 # Install wget and install/updates certificates
 RUN apk add --no-cache --virtual .run-deps \
 RUN apk add --no-cache --virtual .run-deps \
-    ca-certificates bash wget \
+    ca-certificates bash wget openssl \
     && update-ca-certificates
     && update-ca-certificates
 
 
+
 # Configure Nginx and apply fix for very long server names
 # Configure Nginx and apply fix for very long server names
 RUN echo "daemon off;" >> /etc/nginx/nginx.conf \
 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
  && sed -i 's/worker_processes  1/worker_processes  auto/' /etc/nginx/nginx.conf
 
 
 # Install Forego
 # Install Forego
@@ -26,7 +26,7 @@ WORKDIR /app/
 
 
 ENV DOCKER_HOST unix:///tmp/docker.sock
 ENV DOCKER_HOST unix:///tmp/docker.sock
 
 
-VOLUME ["/etc/nginx/certs"]
+VOLUME ["/etc/nginx/certs", "/etc/nginx/dhparam"]
 
 
 ENTRYPOINT ["/app/docker-entrypoint.sh"]
 ENTRYPOINT ["/app/docker-entrypoint.sh"]
 CMD ["forego", "start", "-r"]
 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.
 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
 ### uWSGI Backends
 
 
 If you would like to connect to uWSGI backend, set `VIRTUAL_PROTO=uwsgi` on the
 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
 #### 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`
 `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
 #### Wildcard Certificates
 
 
@@ -189,10 +203,13 @@ and `CERT_NAME=shared` will then use this shared cert.
 
 
 #### How SSL Support Works
 #### 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,
 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:
 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
 set -e
 
 
 # Warn if the DOCKER_HOST socket does not exist
 # Warn if the DOCKER_HOST socket does not exist
-if [[ $DOCKER_HOST == unix://* ]]; then
+if [[ $DOCKER_HOST = unix://* ]]; then
 	socket_file=${DOCKER_HOST#unix://}
 	socket_file=${DOCKER_HOST#unix://}
 	if ! [ -S $socket_file ]; then
 	if ! [ -S $socket_file ]; then
 		cat >&2 <<-EOT
 		cat >&2 <<-EOT
@@ -14,6 +14,10 @@ if [[ $DOCKER_HOST == unix://* ]]; then
 	fi
 	fi
 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 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
 if [ "$socketMissing" = 1 -a "$1" = forego -a "$2" = start -a "$3" = '-r' ]; then
 	exit 1
 	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;
   '' 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
 # Set appropriate X-Forwarded-Ssl header
 map $scheme $proxy_x_forwarded_ssl {
 map $scheme $proxy_x_forwarded_ssl {
   default off;
   default off;
@@ -178,7 +184,7 @@ server {
 	access_log /var/log/nginx/access.log vhost;
 	access_log /var/log/nginx/access.log vhost;
 
 
 	ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
 	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_prefer_server_ciphers on;
 	ssl_session_timeout 5m;
 	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
 COPY python-requirements.txt /requirements.txt
 RUN pip install -r /requirements.txt
 RUN pip install -r /requirements.txt
+
 WORKDIR /test
 WORKDIR /test
 ENTRYPOINT ["pytest"]
 ENTRYPOINT ["pytest"]

+ 3 - 3
test/test_DOCKER_HOST_unix_socket.yml

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

+ 3 - 2
test/test_composev2.yml

@@ -4,11 +4,12 @@ services:
     image: jwilder/nginx-proxy:test
     image: jwilder/nginx-proxy:test
     volumes:
     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
 
 
   web:
   web:
-    image: web 
+    image: web
     expose:
     expose:
       - "81"
       - "81"
     environment:
     environment:
       WEB_PORTS: 81
       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
   image: jwilder/nginx-proxy:test
   volumes:
   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
     - ./my_custom_proxy_settings.conf:/etc/nginx/vhost.d/default_location: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
     - ./my_custom_proxy_settings_bar.conf:/etc/nginx/vhost.d/web3.nginx-proxy.local_location:ro
 
 
 web1:
 web1:
-  image: web 
+  image: web
   expose:
   expose:
     - "81"
     - "81"
   environment:
   environment:
@@ -14,7 +15,7 @@ web1:
     VIRTUAL_HOST: web1.nginx-proxy.local
     VIRTUAL_HOST: web1.nginx-proxy.local
 
 
 web2:
 web2:
-  image: web 
+  image: web
   expose:
   expose:
     - "82"
     - "82"
   environment:
   environment:
@@ -22,9 +23,9 @@ web2:
     VIRTUAL_HOST: web2.nginx-proxy.local
     VIRTUAL_HOST: web2.nginx-proxy.local
 
 
 web3:
 web3:
-  image: web 
+  image: web
   expose:
   expose:
     - "83"
     - "83"
   environment:
   environment:
     WEB_PORTS: 83
     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
     image: jwilder/nginx-proxy:test
     volumes:
     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
       - ./my_custom_proxy_settings.conf:/etc/nginx/proxy.conf:ro
       - ./my_custom_proxy_settings.conf:/etc/nginx/proxy.conf:ro
 
 
   web1:
   web1:
-    image: web 
+    image: web
     expose:
     expose:
       - "81"
       - "81"
     environment:
     environment:
@@ -15,9 +16,9 @@ services:
       VIRTUAL_HOST: web1.nginx-proxy.local
       VIRTUAL_HOST: web1.nginx-proxy.local
 
 
   web2:
   web2:
-    image: web 
+    image: web
     expose:
     expose:
       - "82"
       - "82"
     environment:
     environment:
       WEB_PORTS: 82
       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
     image: jwilder/nginx-proxy:test
     volumes:
     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
       - ./my_custom_proxy_settings.conf:/etc/nginx/vhost.d/web1.nginx-proxy.local_location:ro
       - ./my_custom_proxy_settings.conf:/etc/nginx/vhost.d/web1.nginx-proxy.local_location:ro
 
 
   web1:
   web1:
-    image: web 
+    image: web
     expose:
     expose:
       - "81"
       - "81"
     environment:
     environment:
@@ -15,9 +16,9 @@ services:
       VIRTUAL_HOST: web1.nginx-proxy.local
       VIRTUAL_HOST: web1.nginx-proxy.local
 
 
   web2:
   web2:
-    image: web 
+    image: web
     expose:
     expose:
       - "82"
       - "82"
     environment:
     environment:
       WEB_PORTS: 82
       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
     image: jwilder/nginx-proxy:test
     volumes:
     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
       - ./my_custom_proxy_settings.conf:/etc/nginx/vhost.d/web1.nginx-proxy.local:ro
       - ./my_custom_proxy_settings.conf:/etc/nginx/vhost.d/web1.nginx-proxy.local:ro
 
 
   web1:
   web1:
-    image: web 
+    image: web
     expose:
     expose:
       - "81"
       - "81"
     environment:
     environment:
@@ -15,9 +16,9 @@ services:
       VIRTUAL_HOST: web1.nginx-proxy.local
       VIRTUAL_HOST: web1.nginx-proxy.local
 
 
   web2:
   web2:
-    image: web 
+    image: web
     expose:
     expose:
       - "82"
       - "82"
     environment:
     environment:
       WEB_PORTS: 82
       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
     image: jwilder/nginx-proxy:test
     volumes:
     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
       - ./my_custom_proxy_settings.conf:/etc/nginx/conf.d/my_custom_proxy_settings.conf:ro
       - ./my_custom_proxy_settings.conf:/etc/nginx/conf.d/my_custom_proxy_settings.conf:ro
 
 
   web1:
   web1:
-    image: web 
+    image: web
     expose:
     expose:
       - "81"
       - "81"
     environment:
     environment:
@@ -15,9 +16,9 @@ services:
       VIRTUAL_HOST: web1.nginx-proxy.local
       VIRTUAL_HOST: web1.nginx-proxy.local
 
 
   web2:
   web2:
-    image: web 
+    image: web
     expose:
     expose:
       - "82"
       - "82"
     environment:
     environment:
       WEB_PORTS: 82
       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
   image: jwilder/nginx-proxy:test
   volumes:
   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
   environment:
   environment:
     DEFAULT_HOST: web1.tld
     DEFAULT_HOST: web1.tld

+ 2 - 1
test/test_dockergen/test_dockergen_v2.yml

@@ -6,6 +6,7 @@ services:
     container_name: nginx
     container_name: nginx
     volumes:
     volumes:
       - /etc/nginx/conf.d
       - /etc/nginx/conf.d
+      - ../lib/ssl/dhparam.pem:/etc/nginx/dhparam/dhparam.pem:ro
 
 
   dockergen:
   dockergen:
     image: jwilder/docker-gen
     image: jwilder/docker-gen
@@ -23,4 +24,4 @@ services:
       - "80"
       - "80"
     environment:
     environment:
       WEB_PORTS: 80
       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 docker
 import logging
 import logging
 import pytest
 import pytest
-
+import re
 
 
 def versiontuple(v):
 def versiontuple(v):
     """
     """

+ 2 - 1
test/test_dockergen/test_dockergen_v3.yml

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

+ 1 - 0
test/test_events.yml

@@ -2,3 +2,4 @@ nginxproxy:
   image: jwilder/nginx-proxy:test
   image: jwilder/nginx-proxy:test
   volumes:
   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

+ 2 - 1
test/test_headers/test_http.yml

@@ -10,4 +10,5 @@ web:
 sut:
 sut:
   image: jwilder/nginx-proxy:test
   image: jwilder/nginx-proxy:test
   volumes:
   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
     - /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.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
     - ./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:
 web1:
-  image: web 
+  image: web
   expose:
   expose:
     - "81"
     - "81"
   environment:
   environment:
@@ -8,7 +8,7 @@ web1:
 
 
 web2:
 web2:
   image: web
   image: web
-  expose: 
+  expose:
     - "82"
     - "82"
   environment:
   environment:
     WEB_PORTS: 82
     WEB_PORTS: 82
@@ -19,5 +19,6 @@ sut:
   image: jwilder/nginx-proxy:test
   image: jwilder/nginx-proxy:test
   volumes:
   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
   environment:
   environment:
     ENABLE_IPV6: "true"
     ENABLE_IPV6: "true"

+ 1 - 0
test/test_multiple-hosts.yml

@@ -11,3 +11,4 @@ sut:
   image: jwilder/nginx-proxy:test
   image: jwilder/nginx-proxy:test
   volumes:
   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

+ 4 - 3
test/test_multiple-networks.yml

@@ -9,12 +9,13 @@ services:
     image: jwilder/nginx-proxy:test
     image: jwilder/nginx-proxy:test
     volumes:
     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
     networks:
     networks:
       - net1
       - net1
       - net2
       - net2
 
 
   web1:
   web1:
-    image: web 
+    image: web
     expose:
     expose:
       - "81"
       - "81"
     environment:
     environment:
@@ -24,11 +25,11 @@ services:
       - net1
       - net1
 
 
   web2:
   web2:
-    image: web 
+    image: web
     expose:
     expose:
       - "82"
       - "82"
     environment:
     environment:
       WEB_PORTS: 82
       WEB_PORTS: 82
       VIRTUAL_HOST: web2.nginx-proxy.local
       VIRTUAL_HOST: web2.nginx-proxy.local
     networks:
     networks:
-      - net2
+      - net2

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

@@ -12,3 +12,4 @@ sut:
   image: jwilder/nginx-proxy:test
   image: jwilder/nginx-proxy:test
   volumes:
   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_multiple-ports/test_default-80.yml

@@ -11,3 +11,4 @@ sut:
   image: jwilder/nginx-proxy:test
   image: jwilder/nginx-proxy:test
   volumes:
   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

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

@@ -10,4 +10,5 @@ web:
 sut:
 sut:
   image: jwilder/nginx-proxy:test
   image: jwilder/nginx-proxy:test
   volumes:
   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:
 web1:
-  image: web 
+  image: web
   expose:
   expose:
     - "81"
     - "81"
   environment:
   environment:
@@ -8,7 +8,7 @@ web1:
 
 
 web2:
 web2:
   image: web
   image: web
-  expose: 
+  expose:
     - "82"
     - "82"
   environment:
   environment:
     WEB_PORTS: 82
     WEB_PORTS: 82
@@ -19,3 +19,4 @@ sut:
   image: jwilder/nginx-proxy:test
   image: jwilder/nginx-proxy:test
   volumes:
   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

+ 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
   image: jwilder/nginx-proxy:test
   volumes:
   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
     - ./certs:/etc/nginx/certs:ro
     - ./certs:/etc/nginx/certs:ro

+ 2 - 1
test/test_ssl/test_nohttps.yml

@@ -11,4 +11,5 @@ web:
 sut:
 sut:
   image: jwilder/nginx-proxy:test
   image: jwilder/nginx-proxy:test
   volumes:
   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
   image: jwilder/nginx-proxy:test
   volumes:
   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
     - ./certs:/etc/nginx/certs:ro
     - ./certs:/etc/nginx/certs:ro

+ 1 - 0
test/test_ssl/test_wildcard.yml

@@ -10,4 +10,5 @@ sut:
   image: jwilder/nginx-proxy:test
   image: jwilder/nginx-proxy:test
   volumes:
   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
     - ./certs:/etc/nginx/certs:ro
     - ./certs:/etc/nginx/certs:ro

+ 2 - 1
test/test_wildcard_host.yml

@@ -34,4 +34,5 @@ web4:
 sut:
 sut:
   image: jwilder/nginx-proxy:test
   image: jwilder/nginx-proxy:test
   volumes:
   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