diff --git a/volumes/proxy/templates/nginx-compose-v2.tmpl b/volumes/proxy/templates/nginx-compose-v2.tmpl index a48da6f..a5b1d32 100644 --- a/volumes/proxy/templates/nginx-compose-v2.tmpl +++ b/volumes/proxy/templates/nginx-compose-v2.tmpl @@ -1,18 +1,20 @@ +{{ $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 }} - # {{ .Container.Name }} - server {{ .Address.IP }}:{{ .Address.Port }}; - {{ end }} - {{ else }} - # {{ .Container.Name }} - server {{ .Container.IP }} down; - {{ end }} + {{ 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 @@ -22,6 +24,13 @@ map $http_x_forwarded_proto $proxy_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 { @@ -29,6 +38,12 @@ map $http_upgrade $proxy_connection { '' 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] ' @@ -49,68 +64,66 @@ 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 }} +{{ $enable_ipv6 := eq (or ($.Env.ENABLE_IPV6) "") "true" }} 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; - return 503; + server_name _; # This is just an invalid value which will never trigger on a real hostname. + listen 80; + {{ if $enable_ipv6 }} + listen [::]:80; + {{ end }} + access_log /var/log/nginx/access.log vhost; + 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; - return 503; - - ssl_certificate /etc/nginx/certs/default.crt; - ssl_certificate_key /etc/nginx/certs/default.key; + server_name _; # This is just an invalid value which will never trigger on a real hostname. + listen 443 ssl http2; + {{ if $enable_ipv6 }} + listen [::]:443 ssl http2; + {{ end }} + access_log /var/log/nginx/access.log vhost; + return 503; + + ssl_session_tickets off; + ssl_certificate /etc/nginx/certs/default.crt; + ssl_certificate_key /etc/nginx/certs/default.key; } {{ end }} {{ range $host, $containers := groupByMulti $ "Env.VIRTUAL_HOST" "," }} - -upstream {{ $host }} { - -{{ range $index, $value := $containers }} - - {{ $addrLen := len $value.Addresses }} - {{/* If only 1 port exposed, use that */}} - {{ if eq $addrLen 1 }} - {{ with $address := index $value.Addresses 0 }} - # {{$value.Name}} - server {{ $address.IP }}:{{ $address.Port }}; - {{ end }} - - {{/* If a VIRTUAL_NETWORK is specified use use its IP */}} - {{ else if $value.Env.VIRTUAL_NETWORK }} - {{ range $i, $network := $value.Networks }} - {{ if eq $network.Name $value.Env.VIRTUAL_NETWORK }} - # Container: {{$value.Name}}@{{$network.Name}} - server {{ $network.IP }}:{{ $value.Env.VIRTUAL_PORT }}; - {{ end }} - {{ end }} - - {{/* If more than one port exposed, use the one matching VIRTUAL_PORT env var */}} - {{ else if $value.Env.VIRTUAL_PORT }} - {{ range $i, $address := $value.Addresses }} - {{ if eq $address.Port $value.Env.VIRTUAL_PORT }} - # {{$value.Name}} - server {{ $address.IP }}:{{ $address.Port }}; - {{ end }} - {{ end }} - - {{/* Else default to standard web port 80 */}} - {{ else }} - {{ range $i, $address := $value.Addresses }} - {{ if eq $address.Port "80" }} - # {{$value.Name}} - server {{ $address.IP }}:{{ $address.Port }}; - {{ end }} - {{ end }} - {{ end }} +{{ $is_regexp := hasPrefix "~" $host }} +{{ $upstream_name := when $is_regexp (sha1 $host) $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 }} } @@ -120,6 +133,9 @@ upstream {{ $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")) }} @@ -127,97 +143,129 @@ upstream {{ $host }} { {{ $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 }} +{{ $vhostCert := trimSuffix ".crt" $vhostCert }} +{{ $vhostCert := trimSuffix ".key" $vhostCert }} -{{/* Use the cert specifid on the container or fallback to the best vhost match */}} +{{/* Use the cert specified on the container or fallback to the best vhost match */}} {{ $cert := (coalesce $certName $vhostCert) }} -{{ if (and (ne $cert "") (exists (printf "/etc/nginx/certs/%s.crt" $cert)) (exists (printf "/etc/nginx/certs/%s.key" $cert))) }} +{{ $is_https := (and (ne $https_method "nohttps") (ne $cert "") (exists (printf "/etc/nginx/certs/%s.crt" $cert)) (exists (printf "/etc/nginx/certs/%s.key" $cert))) }} +{{ 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; + server_name {{ $host }}; + listen 80 {{ $default_server }}; + {{ if $enable_ipv6 }} + listen [::]:80 {{ $default_server }}; + {{ end }} + 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-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_certificate /etc/nginx/certs/{{ (printf "%s.crt" $cert) }}; - ssl_certificate_key /etc/nginx/certs/{{ (printf "%s.key" $cert) }}; - - {{ if (exists (printf "/etc/nginx/certs/%s.dhparam.pem" $cert)) }} - ssl_dhparam {{ printf "/etc/nginx/certs/%s.dhparam.pem" $cert }}; - {{ end }} - - add_header Strict-Transport-Security "max-age=31536000"; - - {{ 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 / { - proxy_pass {{ trim $proto }}://{{ trim $host }}; - {{ if (exists (printf "/etc/nginx/htpasswd/%s" $host)) }} - auth_basic "Restricted {{ $host }}"; - auth_basic_user_file {{ (printf "/etc/nginx/htpasswd/%s" $host) }}; - {{ end }} + server_name {{ $host }}; + listen 443 ssl http2 {{ $default_server }}; + {{ if $enable_ipv6 }} + listen [::]:443 ssl http2 {{ $default_server }}; + {{ end }} + 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/nginx/certs/{{ (printf "%s.crt" $cert) }}; + ssl_certificate_key /etc/nginx/certs/{{ (printf "%s.key" $cert) }}; + + {{ if (exists (printf "/etc/nginx/certs/%s.dhparam.pem" $cert)) }} + ssl_dhparam {{ printf "/etc/nginx/certs/%s.dhparam.pem" $cert }}; + {{ 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 / { + {{ 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 }} - } + } } -{{ else }} + +{{ 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 / { - proxy_pass {{ trim $proto }}://{{ trim $host }}; - {{ if (exists (printf "/etc/nginx/htpasswd/%s" $host)) }} - auth_basic "Restricted {{ $host }}"; - auth_basic_user_file {{ (printf "/etc/nginx/htpasswd/%s" $host) }}; - {{ end }} + server_name {{ $host }}; + listen 80 {{ $default_server }}; + {{ if $enable_ipv6 }} + listen [::]:80 {{ $default_server }}; + {{ end }} + 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 / { + {{ 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 (exists "/etc/nginx/certs/default.crt") (exists "/etc/nginx/certs/default.key")) }} +{{ 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 503; - - ssl_certificate /etc/nginx/certs/default.crt; - ssl_certificate_key /etc/nginx/certs/default.key; + server_name {{ $host }}; + listen 443 ssl http2 {{ $default_server }}; + {{ if $enable_ipv6 }} + listen [::]:443 ssl http2 {{ $default_server }}; + {{ end }} + 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 }}