diff --git a/Dockerfile b/Dockerfile index 5c5b258..4186212 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,7 +21,7 @@ RUN echo "daemon off;" >> /etc/nginx/nginx.conf \ RUN wget -P /usr/local/bin https://godist.herokuapp.com/projects/ddollar/forego/releases/current/linux-amd64/forego \ && chmod u+x /usr/local/bin/forego -ENV DOCKER_GEN_VERSION 0.3.4 +ENV DOCKER_GEN_VERSION 0.3.6 RUN wget https://github.com/jwilder/docker-gen/releases/download/$DOCKER_GEN_VERSION/docker-gen-linux-amd64-$DOCKER_GEN_VERSION.tar.gz \ && tar -C /usr/local/bin -xvzf docker-gen-linux-amd64-$DOCKER_GEN_VERSION.tar.gz \ @@ -30,8 +30,9 @@ RUN wget https://github.com/jwilder/docker-gen/releases/download/$DOCKER_GEN_VER COPY . /app/ WORKDIR /app/ - -EXPOSE 80 +EXPOSE 80 443 ENV DOCKER_HOST unix:///tmp/docker.sock +VOLUME ["/etc/nginx/certs"] + CMD ["forego", "start", "-r"] diff --git a/README.md b/README.md index 24a6ad0..7bc86a7 100644 --- a/README.md +++ b/README.md @@ -51,3 +51,47 @@ $ docker run --volumes-from nginx \ Finally, start your containers with `VIRTUAL_HOST` environment variables. $ docker run -e VIRTUAL_HOST=foo.bar.com ... + +### SSL Support + +SSL is supported single host, wildcards and SNI certificates using naming conventions for +certificates or optionally specify a cert name (for SNI) as an environment variable. + +To enable SSL: + + $ docker run -d -p 80:80 -p 443:443 -v /path/to/certs:/etc/nginx/certs -v /var/run/docker.sock:/tmp/docker.sock jwilder/nginx-proxy + +The contents of `/path/to/certs` should contain the certificates and private keys for any virtual +hosts in use. The certificate and keys should be named after the virtual host with a `.crt` and +`.key` extension. For example, a container with `VIRTUAL_HOST=foo.bar.com` should have a +`foo.bar.com.crt` and 'foo.bar.com.key' file in the certs directory. + +#### Wildcard Certificates + +Wildcard certificate and keys should be name after the domain name with a `.crt` and `.key` extension. +For example `VIRTUAL_HOST=foo.bar.com` could also use cert name `bar.com.crt` and `bar.com.key`. + +#### SNI + +If your certificate(s) supports multiple domain names, you can start a container with `CERT_NAME=` +to identify the certificate to be used. For example, a certificate for `*.foo.com` and `*.bar.com` +could be name `shared.crt` and `shared.key`. A container running with `VIRTUAL_HOST=foo.bar.com` +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 +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 OCSP stapling, HSTS, and SSL +session caches. + +The behavior for the proxy when port 80 and 443 are exposed is as follows: + +* If a container has a usable cert, port 80 will redirect to 443 for that container so that HTTPS +is always preferred when available. +* If the container does not have a usable cert, a 503 will be returned. + +Note that in the latter case, a browser may get an connection error as no certificate is available +to establish a connection. A self-signed or generic cert can be defined as "default.crt" and "default.key" +which will allow a client browser to make a SSL connection (likely w/ a warning) and subsequently receive +a 503. \ No newline at end of file diff --git a/nginx.tmpl b/nginx.tmpl index 2f686a3..4c3295f 100644 --- a/nginx.tmpl +++ b/nginx.tmpl @@ -36,6 +36,7 @@ server { } {{ range $host, $containers := groupByMulti $ "Env.VIRTUAL_HOST" "," }} + upstream {{ $host }} { {{ range $container := $containers }} {{ $addrLen := len $container.Addresses }} @@ -65,6 +66,50 @@ upstream {{ $host }} { {{ end }} } +{{/* 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. */}} +{{ $vhostCert := (closest (dir "/etc/nginx/certs") (printf "%s.crt" $host))}} + +{{/* vhostCert is actually a filename so remove any suffixes since they are added later */}} +{{ $vhostCert := replace $vhostCert ".crt" "" -1 }} +{{ $vhostCert := replace $vhostCert ".key" "" -1 }} + +{{/* Use the cert specifid on the container or fallback to the best vhost match */}} +{{ $cert := (coalesce $certName $vhostCert) }} + +{{ if (ne $cert "") }} + +server { + server_name {{ $host }}; + rewrite ^(.*) https://{{ $host }}$1 permanent; +} + +server { + server_name {{ $host }}; + listen 443 ssl; + + ssl_protocols TLSv1 TLSv1.1 TLSv1.2; + ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA; + + ssl_prefer_server_ciphers on; + ssl_session_timeout 5m; + ssl_session_cache shared:SSL:50m; + ssl_stapling on; + ssl_stapling_verify on; + + ssl_certificate /etc/nginx/certs/{{ (printf "%s.crt" $cert) }}; + ssl_certificate_key /etc/nginx/certs/{{ (printf "%s.key" $cert) }}; + + add_header Strict-Transport-Security "max-age=31536000; includeSubdomains"; + + location / { + proxy_pass http://{{ $host }}; + } +} +{{ else }} + server { server_name {{ $host }}; @@ -72,4 +117,17 @@ server { proxy_pass http://{{ $host }}; } } + +server { + server_name {{ $host }}; + listen 443 ssl; + return 503; + + {{ if (and (exists "/etc/nginx/certs/default.crt") (exists "/etc/nginx/certs/default.key")) }} + ssl_certificate /etc/nginx/certs/default.crt; + ssl_certificate_key /etc/nginx/certs/default.key; + {{ end }} +} + +{{ end }} {{ end }}