diff --git a/Dockerfile.alpine b/Dockerfile.alpine index 9ead8e069..c602cdfc5 100644 --- a/Dockerfile.alpine +++ b/Dockerfile.alpine @@ -1,4 +1,4 @@ -FROM docker.io/nginxproxy/docker-gen:0.14.2 AS docker-gen +FROM docker.io/nginxproxy/docker-gen:0.14.3 AS docker-gen FROM docker.io/nginxproxy/forego:0.18.2 AS forego diff --git a/Dockerfile.debian b/Dockerfile.debian index 99cff868e..ec63da850 100644 --- a/Dockerfile.debian +++ b/Dockerfile.debian @@ -1,4 +1,4 @@ -FROM docker.io/nginxproxy/docker-gen:0.14.2-debian AS docker-gen +FROM docker.io/nginxproxy/docker-gen:0.14.3-debian AS docker-gen FROM docker.io/nginxproxy/forego:0.18.2-debian AS forego diff --git a/docs/README.md b/docs/README.md index 080b86b7e..ecbd724a5 100644 --- a/docs/README.md +++ b/docs/README.md @@ -181,8 +181,9 @@ In this example, the incoming request `http://example.tld/app1/foo` will be prox ### Per-VIRTUAL_PATH location configuration The same options as from [Per-VIRTUAL_HOST location configuration](#Per-VIRTUAL_HOST-location-configuration) are available on a `VIRTUAL_PATH` basis. -The only difference is that the filename gets an additional block `HASH=$(echo -n $VIRTUAL_PATH | sha1sum | awk '{ print $1 }')`. This is the sha1-hash of the `VIRTUAL_PATH` (no newline). This is done filename sanitization purposes. -The used filename is `${VIRTUAL_HOST}_${HASH}_location` +The only difference is that the filename gets an additional block `HASH=$(echo -n $VIRTUAL_PATH | sha1sum | awk '{ print $1 }')`. This is the sha1-hash of the `VIRTUAL_PATH` (no newline). This is done for filename sanitization purposes. + +The used filename is `${VIRTUAL_HOST}_${PATH_HASH}_location`, or when `VIRTUAL_HOST` is a regex, `${VIRTUAL_HOST_HASH}_${PATH_HASH}_location`. The filename of the previous example would be `example.tld_8610f6c344b4096614eab6e09d58885349f42faf_location`. @@ -328,7 +329,7 @@ See the [nginx keepalive documentation](https://nginx.org/en/docs/http/ngx_http_ ## Basic Authentication Support -In order to be able to secure your virtual host, you have to create a file named as its equivalent `VIRTUAL_HOST` variable in directory +In order to be able to secure your virtual host, you have to create a file named as its equivalent `VIRTUAL_HOST` variable (or if using a regex `VIRTUAL_HOST`, as the sha1 hash of the regex) in directory `/etc/nginx/htpasswd/{$VIRTUAL_HOST}` ```console @@ -738,7 +739,7 @@ docker run -d -p 80:80 -p 443:443 -v /path/to/my_proxy.conf:/etc/nginx/conf.d/my ### Per-VIRTUAL_HOST -To add settings on a per-`VIRTUAL_HOST` basis, add your configuration file under `/etc/nginx/vhost.d`. Unlike in the proxy-wide case, which allows multiple config files with any name ending in `.conf`, the per-`VIRTUAL_HOST` file must be named exactly after the `VIRTUAL_HOST`. +To add settings on a per-`VIRTUAL_HOST` basis, add your configuration file under `/etc/nginx/vhost.d`. Unlike in the proxy-wide case, which allows multiple config files with any name ending in `.conf`, the per-`VIRTUAL_HOST` file must be named exactly after the `VIRTUAL_HOST`, or if `VIRTUAL_HOST` is a regex, after the sha1 hash of the regex. In order to allow virtual hosts to be dynamically configured as backends are added and removed, it makes the most sense to mount an external directory as `/etc/nginx/vhost.d` as opposed to using derived images or mounting individual configuration files. @@ -762,7 +763,7 @@ If you want most of your virtual hosts to use a default single configuration and ### Per-VIRTUAL_HOST location configuration -To add settings to the "location" block on a per-`VIRTUAL_HOST` basis, add your configuration file under `/etc/nginx/vhost.d` just like the previous section except with the suffix `_location`. +To add settings to the "location" block on a per-`VIRTUAL_HOST` basis, add your configuration file under `/etc/nginx/vhost.d` just like the per-`VIRTUAL_HOST` section except with the suffix `_location` (like this section, if your `VIRTUAl_HOST` is a regex, use the sha1 hash of the regex instead, with the suffix `_location` appended). For example, if you have a virtual host named `app.example.com` and you have configured a proxy_cache `my-cache` in another custom file, you could tell it to use a proxy cache as follows: @@ -790,7 +791,7 @@ The `${VIRTUAL_HOST}_${PATH_HASH}_location`, `${VIRTUAL_HOST}_location`, and `de /etc/nginx/vhost.d/${VIRTUAL_HOST}_${PATH_HASH}_location_override ``` -where `${VIRTUAL_HOST}` is the name of the virtual host (the `VIRTUAL_HOST` environment variable) and `${PATH_HASH}` is the SHA-1 hash of the path, as [described above](#per-virtual_path-location-configuration). +where `${VIRTUAL_HOST}` is the name of the virtual host (the `VIRTUAL_HOST` environment variable), or the sha1 hash of `VIRTUAL_HOST` when it's a regex, and `${PATH_HASH}` is the SHA-1 hash of the path, as [described above](#per-virtual_path-location-configuration). For convenience, the `_${PATH_HASH}` part can be omitted if the path is `/`: @@ -981,7 +982,7 @@ docker exec nginx -T Pay attention to the `upstream` definition blocks, which should look like this: -```Nginx +```nginx # foo.example.com upstream foo.example.com { ## Can be connected with "my_network" network @@ -1001,6 +1002,113 @@ The effective `Port` is retrieved by order of precedence: 1. From the container's exposed port if there is only one 1. From the default port 80 when none of the above methods apply +### Debug endpoint + +The debug endpoint can be enabled: +- globally by setting the `DEBUG_ENDPOINT` environment variable to `true` on the nginx-proxy container. +- per container by setting the `com.github.nginx-proxy.nginx-proxy.debug-endpoint` label to `true` on a proxied container. + +Enabling it will expose the endpoint at `/nginx-proxy-debug`. + +Querying the debug endpoint will show the global config, along with the virtual host and per path configs in JSON format. + +```yaml +services: + nginx-proxy: + image: nginxproxy/nginx-proxy + ports: + - "80:80" + volumes: + - /var/run/docker.sock:/tmp/docker.sock:ro + environment: + DEBUG_ENDPOINT: "true" + + test: + image: nginx + environment: + VIRTUAL_HOST: test.nginx-proxy.tld +``` + +(on the CLI, using [`jq`](https://jqlang.github.io/jq/) to format the output of `curl` is recommended) + +```console +curl -s -H "Host: test.nginx-proxy.tld" localhost/nginx-proxy-debug | jq +``` + +```json +{ + "global": { + "acme_http_challenge": "true", + "default_cert_ok": false, + "default_host": null, + "default_root_response": "404", + "enable_access_log": true, + "enable_debug_endpoint": "true", + "enable_http2": "true", + "enable_http3": "false", + "enable_http_on_missing_cert": "true", + "enable_ipv6": false, + "enable_json_logs": false, + "external_http_port": "80", + "external_https_port": "443", + "hsts": "max-age=31536000", + "https_method": "redirect", + "log_format": null, + "log_format_escape": null, + "nginx_proxy_version": "1.6.3", + "resolvers": "127.0.0.11", + "sha1_upstream_name": false, + "ssl_policy": "Mozilla-Intermediate", + "trust_downstream_proxy": true + }, + "request": { + "host": "test.nginx-proxy.tld", + "http2": "", + "http3": "", + "https": "", + "ssl_cipher": "", + "ssl_protocol": "" + }, + "vhost": { + "acme_http_challenge_enabled": true, + "acme_http_challenge_legacy": false, + "cert": "", + "cert_ok": false, + "default": false, + "enable_debug_endpoint": true, + "hostname": "test.nginx-proxy.tld", + "hsts": "max-age=31536000", + "http2_enabled": true, + "http3_enabled": false, + "https_method": "noredirect", + "is_regexp": false, + "paths": { + "/": { + "dest": "", + "keepalive": "disabled", + "network_tag": "external", + "ports": { + "legacy": [ + { + "Name": "wip-test-1" + } + ] + }, + "proto": "http", + "upstream": "test.nginx-proxy.tld" + } + }, + "server_tokens": "", + "ssl_policy": "", + "upstream_name": "test.nginx-proxy.tld", + "vhost_root": "/var/www/public" + } +} +``` + +:warning: please be aware that the debug endpoint work by rendering the JSON response straight to the nginx configuration in plaintext. nginx has an upper limit on the size of the configuration files it can parse, so only activate it when needed, and preferably on a per container basis if your setup has a large number of virtual hosts. + + ⬆️ [back to table of contents](#table-of-contents) ## Contributing diff --git a/nginx.tmpl b/nginx.tmpl index 59e35732c..3d416c3c5 100644 --- a/nginx.tmpl +++ b/nginx.tmpl @@ -1,25 +1,44 @@ # nginx-proxy{{ if $.Env.NGINX_PROXY_VERSION }} version : {{ $.Env.NGINX_PROXY_VERSION }}{{ end }} {{- /* - * Global values. Values are stored in this map rather than in individual + * Global values. Values are stored in this map rather than in individual * global variables so that the values can be easily passed to embedded - * templates. (Go templates cannot access variables outside of their own - * scope.) + * templates (Go templates cannot access variables outside of their own + * scope) and displayed in the debug endpoint output. */}} {{- $globals := dict }} {{- $_ := set $globals "containers" $ }} {{- $_ := set $globals "Env" $.Env }} {{- $_ := set $globals "Docker" $.Docker }} {{- $_ := set $globals "CurrentContainer" (where $globals.containers "ID" $globals.Docker.CurrentContainerID | first) }} -{{- $_ := set $globals "default_cert_ok" (and (exists "/etc/nginx/certs/default.crt") (exists "/etc/nginx/certs/default.key")) }} -{{- $_ := set $globals "external_http_port" (coalesce $globals.Env.HTTP_PORT "80") }} -{{- $_ := set $globals "external_https_port" (coalesce $globals.Env.HTTPS_PORT "443") }} -{{- $_ := set $globals "sha1_upstream_name" (parseBool (coalesce $globals.Env.SHA1_UPSTREAM_NAME "false")) }} -{{- $_ := set $globals "default_root_response" (coalesce $globals.Env.DEFAULT_ROOT "404") }} -{{- $_ := set $globals "trust_downstream_proxy" (parseBool (coalesce $globals.Env.TRUST_DOWNSTREAM_PROXY "true")) }} -{{- $_ := set $globals "access_log" (or (and (not $globals.Env.DISABLE_ACCESS_LOGS) "access_log /var/log/nginx/access.log vhost;") "") }} -{{- $_ := set $globals "enable_ipv6" (parseBool (coalesce $globals.Env.ENABLE_IPV6 "false")) }} -{{- $_ := set $globals "ssl_policy" (or ($globals.Env.SSL_POLICY) "Mozilla-Intermediate") }} + +{{- $config := dict }} +{{- $_ := set $config "nginx_proxy_version" $.Env.NGINX_PROXY_VERSION }} +{{- $_ := set $config "default_cert_ok" (and (exists "/etc/nginx/certs/default.crt") (exists "/etc/nginx/certs/default.key")) }} +{{- $_ := set $config "external_http_port" ($globals.Env.HTTP_PORT | default "80") }} +{{- $_ := set $config "external_https_port" ($globals.Env.HTTPS_PORT | default "443") }} +{{- $_ := set $config "sha1_upstream_name" ($globals.Env.SHA1_UPSTREAM_NAME | default "false" | parseBool) }} +{{- $_ := set $config "default_root_response" ($globals.Env.DEFAULT_ROOT | default "404") }} +{{- $_ := set $config "trust_downstream_proxy" ($globals.Env.TRUST_DOWNSTREAM_PROXY | default "true" | parseBool) }} +{{- $_ := set $config "enable_access_log" ($globals.Env.DISABLE_ACCESS_LOGS | default "false" | parseBool | not) }} +{{- $_ := set $config "enable_ipv6" ($globals.Env.ENABLE_IPV6 | default "false" | parseBool) }} +{{- $_ := set $config "ssl_policy" ($globals.Env.SSL_POLICY | default "Mozilla-Intermediate") }} +{{- $_ := set $config "enable_debug_endpoint" ($globals.Env.DEBUG_ENDPOINT | default "false") }} +{{- $_ := set $config "hsts" ($globals.Env.HSTS | default "max-age=31536000") }} +{{- $_ := set $config "acme_http_challenge" ($globals.Env.ACME_HTTP_CHALLENGE_LOCATION | default "true") }} +{{- $_ := set $config "enable_http2" ($globals.Env.ENABLE_HTTP2 | default "true") }} +{{- $_ := set $config "enable_http3" ($globals.Env.ENABLE_HTTP3 | default "false") }} +{{- $_ := set $config "enable_http_on_missing_cert" ($globals.Env.ENABLE_HTTP_ON_MISSING_CERT | default "true") }} +{{- $_ := set $config "https_method" ($globals.Env.HTTPS_METHOD | default "redirect") }} +{{- $_ := set $config "default_host" $globals.Env.DEFAULT_HOST }} +{{- $_ := set $config "resolvers" $globals.Env.RESOLVERS }} +{{- /* LOG_JSON is a shorthand that sets logging defaults to JSON format */}} +{{- $_ := set $config "enable_json_logs" ($globals.Env.LOG_JSON | default "false" | parseBool) }} +{{- $_ := set $config "log_format" $globals.Env.LOG_FORMAT }} +{{- $_ := set $config "log_format_escape" $globals.Env.LOG_FORMAT_ESCAPE }} + +{{- $_ := set $globals "config" $config }} + {{- $_ := set $globals "vhosts" (dict) }} {{- $_ := set $globals "networks" (dict) }} # Networks available to the container running docker-gen (which are assumed to @@ -289,7 +308,7 @@ auth_basic "Restricted {{ .Host }}{{ .Path }}"; auth_basic_user_file {{ (printf "/etc/nginx/htpasswd/%s_%s" .Host (sha1 .Path)) }}; {{- else if (exists (printf "/etc/nginx/htpasswd/%s" .Host)) }} - auth_basic "Restricted {{ .Host }}"; + auth_basic "Restricted {{ .HostIsRegexp | ternary "access" .Host }}"; auth_basic_user_file {{ (printf "/etc/nginx/htpasswd/%s" .Host) }}; {{- end }} @@ -344,22 +363,76 @@ upstream {{ $vpath.upstream }} { } {{- end }} +{{- /* debug "endpoint" location template */}} +{{- define "debug_location" }} + {{- $debug_paths := dict }} + {{- range $path, $vpath := .VHost.paths }} + {{- $tmp_port := dict }} + {{- range $port, $containers := $vpath.ports }} + {{- $tmp_containers := list }} + {{- range $container := $containers }} + {{- $tmp_containers = dict "Name" $container.Name | append $tmp_containers }} + {{- end }} + {{- $_ := dict $port $tmp_containers | set $tmp_port "ports" }} + {{- $tmp_port = deepCopy $vpath | merge $tmp_port }} + {{- end }} + {{- $_ := set $debug_paths $path $tmp_port }} + {{- end }} + + {{- $debug_vhost := deepCopy .VHost }} + {{- /* If it's a regexp, do not render the Hostname to the response to avoid rendering config breaking characters */}} + {{- $_ := set $debug_vhost "hostname" (.VHost.is_regexp | ternary "Hostname is a regexp and unsafe to include in the debug response." .Hostname) }} + {{- $_ := set $debug_vhost "paths" $debug_paths }} + + {{- $debug_response := dict + "global" .GlobalConfig + "request" (dict + "host" "$host" + "https" "$https" + "http2" "$http2" + "http3" "$http3" + "ssl_cipher" "$ssl_cipher" + "ssl_protocol" "$ssl_protocol" + ) + "vhost" $debug_vhost + }} + + {{- /* + * The maximum line length in an nginx config is 4096 characters. + * If we're nearing this limit (with headroom for the rest + * of the directive), strip vhost.paths from the response. + */}} + {{- if gt (toJson $debug_response | len) 4000 }} + {{- $_ := unset $debug_vhost "paths" }} + {{- $_ := set $debug_response "warning" "Virtual paths configuration for this hostname is too large and has been stripped from response." }} + {{- end }} + + location /nginx-proxy-debug { + default_type application/json; + return 200 '{{ toJson $debug_response }}'; + } +{{- end }} + +{{- define "access_log" }} + {{- when .Enable "access_log /var/log/nginx/access.log vhost;" "" }} +{{- 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 {{ if $globals.trust_downstream_proxy }}$http_x_forwarded_proto{{ else }}$scheme{{ end }}; + default {{ if $globals.config.trust_downstream_proxy }}$http_x_forwarded_proto{{ else }}$scheme{{ end }}; '' $scheme; } map $http_x_forwarded_host $proxy_x_forwarded_host { - default {{ if $globals.trust_downstream_proxy }}$http_x_forwarded_host{{ else }}$host{{ end }}; + default {{ if $globals.config.trust_downstream_proxy }}$http_x_forwarded_host{{ else }}$host{{ end }}; '' $host; } # 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 {{ if $globals.trust_downstream_proxy }}$http_x_forwarded_port{{ else }}$server_port{{ end }}; + default {{ if $globals.config.trust_downstream_proxy }}$http_x_forwarded_port{{ else }}$server_port{{ end }}; '' $server_port; } @@ -420,19 +493,16 @@ gzip_types text/plain text/css application/javascript application/json applicati * LOG_FORMAT_ESCAPE sets the escape part of the log format * LOG_FORMAT sets the log format */}} -{{- $logEscape := printf "escape=%s" (or $globals.Env.LOG_FORMAT_ESCAPE "default") }} -{{- $logFormat := or $globals.Env.LOG_FORMAT `$host $remote_addr - $remote_user [$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent" "$upstream_addr"` }} +{{- $logEscape := $globals.config.log_format_escape | default "default" | printf "escape=%s" }} +{{- $logFormat := $globals.config.log_format | default `$host $remote_addr - $remote_user [$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent" "$upstream_addr"` }} -{{- if parseBool (or $globals.Env.LOG_JSON "false") }} - {{- /* LOG_JSON is a shorthand - * that sets logging defaults to JSON format - */}} +{{- if $globals.config.enable_json_logs }} # JSON Logging enabled (via LOG_JSON env variable) - {{- $logEscape = printf "escape=%s" (or $globals.Env.LOG_FORMAT_ESCAPE "json") }} - {{- $logFormat = or $globals.Env.LOG_FORMAT `{"time_local":"$time_iso8601","client_ip":"$http_x_forwarded_for","remote_addr":"$remote_addr","request":"$request","status":"$status","body_bytes_sent":"$body_bytes_sent","request_time":"$request_time","upstream_response_time":"$upstream_response_time","upstream_addr":"$upstream_addr","http_referrer":"$http_referer","http_user_agent":"$http_user_agent","request_id":"$request_id"}` }} + {{- $logEscape = $globals.config.log_format_escape | default "json" | printf "escape=%s" }} + {{- $logFormat = $globals.config.log_format | default `{"time_local":"$time_iso8601","client_ip":"$http_x_forwarded_for","remote_addr":"$remote_addr","request":"$request","status":"$status","body_bytes_sent":"$body_bytes_sent","request_time":"$request_time","upstream_response_time":"$upstream_response_time","upstream_addr":"$upstream_addr","http_referrer":"$http_referer","http_user_agent":"$http_user_agent","request_id":"$request_id"}` }} {{- end }} -log_format vhost {{ $logEscape }} '{{ or $globals.Env.LOG_FORMAT $logFormat }}'; +log_format vhost {{ $logEscape }} '{{ $logFormat }}'; access_log off; @@ -440,7 +510,7 @@ access_log off; * if at least one vhost use a TLSv1 or TLSv1.1 policy * so TLSv1 and TLSv1.1 can be enabled on those vhosts */}} -{{- $httpContextSslPolicy := $globals.ssl_policy }} +{{- $httpContextSslPolicy := $globals.config.ssl_policy }} {{- $inUseSslPolicies := groupByKeys $globals.containers "Env.SSL_POLICY" }} {{- range $tls1Policy := list "AWS-TLS13-1-1-2021-06" "AWS-TLS13-1-0-2021-06" "AWS-FS-1-1-2019-08" "AWS-FS-2018-06" "AWS-TLS-1-1-2017-01" "AWS-2016-08" "AWS-2015-05" "AWS-2015-03" "AWS-2015-02" "Mozilla-Old" }} {{- if has $tls1Policy $inUseSslPolicies }} @@ -453,8 +523,8 @@ access_log off; {{- template "ssl_policy" (dict "ssl_policy" $httpContextSslPolicy) }} error_log /dev/stderr; -{{- if $globals.Env.RESOLVERS }} -resolver {{ $globals.Env.RESOLVERS }}; +{{- if $globals.config.resolvers }} +resolver {{ $globals.config.resolvers }}; {{- end }} {{- if (exists "/etc/nginx/proxy.conf") }} @@ -493,7 +563,7 @@ proxy_set_header Proxy ""; {{- range $hostname, $vhost := $parsedVhosts }} {{- $vhost_data := when (hasKey $globals.vhosts $hostname) (get $globals.vhosts $hostname) (dict) }} - {{- $paths := coalesce $vhost_data.paths (dict) }} + {{- $paths := $vhost_data.paths | default (dict) }} {{- if (empty $vhost) }} {{ $vhost = dict "/" (dict) }} @@ -503,7 +573,7 @@ proxy_set_header Proxy ""; {{- if (empty $vpath) }} {{- $vpath = dict "dest" "" "port" "default" }} {{- end }} - {{- $dest := coalesce $vpath.dest "" }} + {{- $dest := $vpath.dest | default "" }} {{- $port := when (hasKey $vpath "port") (toString $vpath.port) "default" }} {{- $path_data := when (hasKey $paths $path) (get $paths $path) (dict) }} {{- $path_ports := when (hasKey $path_data "ports") (get $path_data "ports") (dict) }} @@ -518,7 +588,7 @@ proxy_set_header Proxy ""; {{- end }} {{- $_ := set $vhost_data "paths" $paths }} {{- $is_regexp := hasPrefix "~" $hostname }} - {{- $_ := set $vhost_data "upstream_name" (when (or $is_regexp $globals.sha1_upstream_name) (sha1 $hostname) $hostname) }} + {{- $_ := set $vhost_data "upstream_name" (when (or $is_regexp $globals.config.sha1_upstream_name) (sha1 $hostname) $hostname) }} {{- $_ := set $globals.vhosts $hostname $vhost_data }} {{- end }} {{- end }} @@ -544,12 +614,12 @@ proxy_set_header Proxy ""; {{- end }} {{- $vhost_data := when (hasKey $globals.vhosts $hostname) (get $globals.vhosts $hostname) (dict) }} - {{- $paths := coalesce $vhost_data.paths (dict) }} + {{- $paths := $vhost_data.paths | default (dict) }} {{- $tmp_paths := groupByWithDefault $containers "Env.VIRTUAL_PATH" "/" }} {{- range $path, $containers := $tmp_paths }} - {{- $dest := or (first (groupByKeys $containers "Env.VIRTUAL_DEST")) "" }} + {{- $dest := groupByKeys $containers "Env.VIRTUAL_DEST" | first | default "" }} {{- $port := "legacy" }} {{- $path_data := when (hasKey $paths $path) (get $paths $path) (dict) }} {{- $path_ports := when (hasKey $path_data "ports") (get $path_data "ports") (dict) }} @@ -564,13 +634,15 @@ proxy_set_header Proxy ""; {{- end }} {{- $_ := set $vhost_data "paths" $paths }} {{- $is_regexp := hasPrefix "~" $hostname }} - {{- $_ := set $vhost_data "upstream_name" (when (or $is_regexp $globals.sha1_upstream_name) (sha1 $hostname) $hostname) }} + {{- $_ := set $vhost_data "upstream_name" (when (or $is_regexp $globals.config.sha1_upstream_name) (sha1 $hostname) $hostname) }} {{- $_ := set $globals.vhosts $hostname $vhost_data }} {{- end }} {{- /* Loop over $globals.vhosts and update it with the remaining informations about each vhost. */}} {{- range $hostname, $vhost_data := $globals.vhosts }} + {{- $is_regexp := hasPrefix "~" $hostname }} {{- $vhost_containers := list }} + {{- range $path, $vpath_data := $vhost_data.paths }} {{- $vpath_containers := list }} {{- range $port, $vport_containers := $vpath_data.ports }} @@ -578,12 +650,12 @@ proxy_set_header Proxy ""; {{- end }} {{- /* Get the VIRTUAL_PROTO defined by containers w/ the same vhost-vpath, falling back to "http". */}} - {{- $proto := trim (or (first (groupByKeys $vpath_containers "Env.VIRTUAL_PROTO")) "http") }} + {{- $proto := groupByKeys $vpath_containers "Env.VIRTUAL_PROTO" | first | default "http" | trim }} {{- /* Get the NETWORK_ACCESS defined by containers w/ the same vhost, falling back to "external". */}} - {{- $network_tag := or (first (groupByKeys $vpath_containers "Env.NETWORK_ACCESS")) "external" }} + {{- $network_tag := groupByKeys $vpath_containers "Env.NETWORK_ACCESS" | first | default "external" }} - {{- $loadbalance := first (keys (groupByLabel $vpath_containers "com.github.nginx-proxy.nginx-proxy.loadbalance")) }} - {{- $keepalive := coalesce (first (keys (groupByLabel $vpath_containers "com.github.nginx-proxy.nginx-proxy.keepalive"))) "disabled" }} + {{- $loadbalance := groupByLabel $vpath_containers "com.github.nginx-proxy.nginx-proxy.loadbalance" | keys | first }} + {{- $keepalive := groupByLabel $vpath_containers "com.github.nginx-proxy.nginx-proxy.keepalive" | keys | first | default "disabled" }} {{- $upstream := $vhost_data.upstream_name }} {{- if (not (eq $path "/")) }} @@ -601,23 +673,24 @@ proxy_set_header Proxy ""; {{ $vhost_containers = concat $vhost_containers $vpath_containers }} {{- end }} - {{- $certName := first (groupByKeys $vhost_containers "Env.CERT_NAME") }} + {{- $certName := groupByKeys $vhost_containers "Env.CERT_NAME" | first }} {{- $vhostCert := closest (dir "/etc/nginx/certs") (printf "%s.crt" $hostname) }} {{- $vhostCert = trimSuffix ".crt" $vhostCert }} {{- $vhostCert = trimSuffix ".key" $vhostCert }} {{- $cert := or $certName $vhostCert }} {{- $cert_ok := and (ne $cert "") (exists (printf "/etc/nginx/certs/%s.crt" $cert)) (exists (printf "/etc/nginx/certs/%s.key" $cert)) }} - {{- $default := eq $globals.Env.DEFAULT_HOST $hostname }} - {{- $https_method := or (first (groupByKeys $vhost_containers "Env.HTTPS_METHOD")) $globals.Env.HTTPS_METHOD "redirect" }} - {{- $enable_http_on_missing_cert := parseBool (or (first (groupByKeys $vhost_containers "Env.ENABLE_HTTP_ON_MISSING_CERT")) $globals.Env.ENABLE_HTTP_ON_MISSING_CERT "true") }} + {{- $enable_debug_endpoint := groupByLabel $vhost_containers "com.github.nginx-proxy.nginx-proxy.debug-endpoint" | keys | first | default $globals.config.enable_debug_endpoint | parseBool }} + {{- $default := eq $globals.config.default_host $hostname }} + {{- $https_method := groupByKeys $vhost_containers "Env.HTTPS_METHOD" | first | default $globals.config.https_method }} + {{- $enable_http_on_missing_cert := groupByKeys $vhost_containers "Env.ENABLE_HTTP_ON_MISSING_CERT" | first | default $globals.config.enable_http_on_missing_cert | parseBool }} {{- /* When the certificate is missing we want to ensure that HTTP is enabled; hence switching from 'nohttp' or 'redirect' to 'noredirect' */}} {{- if (and $enable_http_on_missing_cert (not $cert_ok) (or (eq $https_method "nohttp") (eq $https_method "redirect"))) }} {{- $https_method = "noredirect" }} {{- end }} - {{- $http2_enabled := parseBool (or (first (keys (groupByLabel $vhost_containers "com.github.nginx-proxy.nginx-proxy.http2.enable"))) $globals.Env.ENABLE_HTTP2 "true")}} - {{- $http3_enabled := parseBool (or (first (keys (groupByLabel $vhost_containers "com.github.nginx-proxy.nginx-proxy.http3.enable"))) $globals.Env.ENABLE_HTTP3 "false")}} - {{- $acme_http_challenge := or (first (groupByKeys $vhost_containers "Env.ACME_HTTP_CHALLENGE_LOCATION")) $globals.Env.ACME_HTTP_CHALLENGE_LOCATION "true" }} + {{- $http2_enabled := groupByLabel $vhost_containers "com.github.nginx-proxy.nginx-proxy.http2.enable" | keys | first | default $globals.config.enable_http2 | parseBool }} + {{- $http3_enabled := groupByLabel $vhost_containers "com.github.nginx-proxy.nginx-proxy.http3.enable" | keys | first | default $globals.config.enable_http3 | parseBool }} + {{- $acme_http_challenge := groupByKeys $vhost_containers "Env.ACME_HTTP_CHALLENGE_LOCATION" | first | default $globals.config.acme_http_challenge }} {{- $acme_http_challenge_legacy := eq $acme_http_challenge "legacy" }} {{- $acme_http_challenge_enabled := false }} {{- if (not $acme_http_challenge_legacy) }} @@ -625,25 +698,27 @@ proxy_set_header Proxy ""; {{- end }} {{- /* Get the SERVER_TOKENS defined by containers w/ the same vhost, falling back to "". */}} - {{- $server_tokens := trim (or (first (groupByKeys $vhost_containers "Env.SERVER_TOKENS")) "") }} + {{- $server_tokens := groupByKeys $vhost_containers "Env.SERVER_TOKENS" | first | default "" | trim }} {{- /* Get the SSL_POLICY defined by containers w/ the same vhost, falling back to empty string (use default). */}} - {{- $ssl_policy := or (first (groupByKeys $vhost_containers "Env.SSL_POLICY")) "" }} + {{- $ssl_policy := groupByKeys $vhost_containers "Env.SSL_POLICY" | first | default "" }} {{- /* Get the HSTS defined by containers w/ the same vhost, falling back to "max-age=31536000". */}} - {{- $hsts := or (first (groupByKeys $vhost_containers "Env.HSTS")) (or $globals.Env.HSTS "max-age=31536000") }} + {{- $hsts := groupByKeys $vhost_containers "Env.HSTS" | first | default $globals.config.hsts }} {{- /* Get the VIRTUAL_ROOT By containers w/ use fastcgi root */}} - {{- $vhost_root := or (first (groupByKeys $vhost_containers "Env.VIRTUAL_ROOT")) "/var/www/public" }} + {{- $vhost_root := groupByKeys $vhost_containers "Env.VIRTUAL_ROOT" | first | default "/var/www/public" }} {{- $vhost_data = merge $vhost_data (dict "cert" $cert "cert_ok" $cert_ok + "enable_debug_endpoint" $enable_debug_endpoint "default" $default "hsts" $hsts "https_method" $https_method "http2_enabled" $http2_enabled "http3_enabled" $http3_enabled + "is_regexp" $is_regexp "acme_http_challenge_legacy" $acme_http_challenge_legacy "acme_http_challenge_enabled" $acme_http_challenge_enabled "server_tokens" $server_tokens @@ -697,30 +772,30 @@ proxy_set_header Proxy ""; server { server_name _; # This is just an invalid value which will never trigger on a real hostname. server_tokens off; - {{ $globals.access_log }} + {{ template "access_log" (dict "Enable" $globals.config.enable_access_log) }} http2 on; {{- if $fallback_http }} - listen {{ $globals.external_http_port }}; {{- /* Do not add `default_server` (see comment above). */}} - {{- if $globals.enable_ipv6 }} - listen [::]:{{ $globals.external_http_port }}; {{- /* Do not add `default_server` (see comment above). */}} + listen {{ $globals.config.external_http_port }}; {{- /* Do not add `default_server` (see comment above). */}} + {{- if $globals.config.enable_ipv6 }} + listen [::]:{{ $globals.config.external_http_port }}; {{- /* Do not add `default_server` (see comment above). */}} {{- end }} {{- end }} {{- if $fallback_https }} - listen {{ $globals.external_https_port }} ssl; {{- /* Do not add `default_server` (see comment above). */}} - {{- if $globals.enable_ipv6 }} - listen [::]:{{ $globals.external_https_port }} ssl; {{- /* Do not add `default_server` (see comment above). */}} + listen {{ $globals.config.external_https_port }} ssl; {{- /* Do not add `default_server` (see comment above). */}} + {{- if $globals.config.enable_ipv6 }} + listen [::]:{{ $globals.config.external_https_port }} ssl; {{- /* Do not add `default_server` (see comment above). */}} {{- end }} {{- if $http3_enabled }} http3 on; - listen {{ $globals.external_https_port }} quic reuseport; {{- /* Do not add `default_server` (see comment above). */}} - {{- if $globals.enable_ipv6 }} - listen [::]:{{ $globals.external_https_port }} quic reuseport; {{- /* Do not add `default_server` (see comment above). */}} + listen {{ $globals.config.external_https_port }} quic reuseport; {{- /* Do not add `default_server` (see comment above). */}} + {{- if $globals.config.enable_ipv6 }} + listen [::]:{{ $globals.config.external_https_port }} quic reuseport; {{- /* Do not add `default_server` (see comment above). */}} {{- end }} {{- end }} ssl_session_cache shared:SSL:50m; ssl_session_tickets off; {{- end }} - {{- if $globals.default_cert_ok }} + {{- if $globals.config.default_cert_ok }} ssl_certificate /etc/nginx/certs/default.crt; ssl_certificate_key /etc/nginx/certs/default.key; {{- else }} @@ -756,10 +831,10 @@ server { {{- if $vhost.server_tokens }} server_tokens {{ $vhost.server_tokens }}; {{- end }} - {{ $globals.access_log }} - listen {{ $globals.external_http_port }} {{ $default_server }}; - {{- if $globals.enable_ipv6 }} - listen [::]:{{ $globals.external_http_port }} {{ $default_server }}; + {{ template "access_log" (dict "Enable" $globals.config.enable_access_log) }} + listen {{ $globals.config.external_http_port }} {{ $default_server }}; + {{- if $globals.config.enable_ipv6 }} + listen [::]:{{ $globals.config.external_http_port }} {{ $default_server }}; {{- end }} {{- if (or $vhost.acme_http_challenge_legacy $vhost.acme_http_challenge_enabled) }} @@ -773,30 +848,51 @@ server { break; } {{- end }} + + {{- if $vhost.enable_debug_endpoint }} + {{ template "debug_location" (dict "GlobalConfig" $globals.config "Hostname" $hostname "VHost" $vhost) }} + {{- end }} location / { - {{- if eq $globals.external_https_port "443" }} + {{- if eq $globals.config.external_https_port "443" }} return 301 https://$host$request_uri; {{- else }} - return 301 https://$host:{{ $globals.external_https_port }}$request_uri; + return 301 https://$host:{{ $globals.config.external_https_port }}$request_uri; {{- end }} } } {{- end }} server { + {{- if $vhost.is_regexp }} + {{- if or + (printf "/etc/nginx/vhost.d/%s" $hostname | exists) + (printf "/etc/nginx/vhost.d/%s_location" $hostname | exists) + (printf "/etc/nginx/vhost.d/%s_location_override" $hostname | exists) + (printf "/etc/nginx/htpasswd/%s" $hostname | exists) + }} + # https://github.com/nginx-proxy/nginx-proxy/issues/2529#issuecomment-2437609249 + # Support for vhost config file(s) named like a regexp ({{ $hostname }}) has been removed from nginx-proxy. + # Please name your vhost config file(s) with the sha1 of the regexp instead ({{ $hostname }} -> {{ sha1 $hostname }}) : + # - /etc/nginx/vhost.d/{{ sha1 $hostname }} + # - /etc/nginx/vhost.d/{{ sha1 $hostname }}_location + # - /etc/nginx/vhost.d/{{ sha1 $hostname }}_location_override + # - /etc/nginx/htpasswd/{{ sha1 $hostname }} + {{- end }} + {{- end }} + server_name {{ $hostname }}; {{- if $vhost.server_tokens }} server_tokens {{ $vhost.server_tokens }}; {{- end }} - {{ $globals.access_log }} + {{ template "access_log" (dict "Enable" $globals.config.enable_access_log) }} {{- if $vhost.http2_enabled }} http2 on; {{- end }} {{- if or (eq $vhost.https_method "nohttps") (eq $vhost.https_method "noredirect") }} - listen {{ $globals.external_http_port }} {{ $default_server }}; - {{- if $globals.enable_ipv6 }} - listen [::]:{{ $globals.external_http_port }} {{ $default_server }}; + listen {{ $globals.config.external_http_port }} {{ $default_server }}; + {{- if $globals.config.enable_ipv6 }} + listen [::]:{{ $globals.config.external_http_port }} {{ $default_server }}; {{- end }} {{- if (and (eq $vhost.https_method "noredirect") $vhost.acme_http_challenge_enabled) }} @@ -810,17 +906,17 @@ server { {{- end }} {{- end }} {{- if ne $vhost.https_method "nohttps" }} - listen {{ $globals.external_https_port }} ssl {{ $default_server }}; - {{- if $globals.enable_ipv6 }} - listen [::]:{{ $globals.external_https_port }} ssl {{ $default_server }}; + listen {{ $globals.config.external_https_port }} ssl {{ $default_server }}; + {{- if $globals.config.enable_ipv6 }} + listen [::]:{{ $globals.config.external_https_port }} ssl {{ $default_server }}; {{- end }} {{- if $vhost.http3_enabled }} http3 on; - add_header alt-svc 'h3=":{{ $globals.external_https_port }}"; ma=86400;'; - listen {{ $globals.external_https_port }} quic {{ $default_server }}; - {{- if $globals.enable_ipv6 }} - listen [::]:{{ $globals.external_https_port }} quic {{ $default_server }}; + add_header alt-svc 'h3=":{{ $globals.config.external_https_port }}"; ma=86400;'; + listen {{ $globals.config.external_https_port }} quic {{ $default_server }}; + {{- if $globals.config.enable_ipv6 }} + listen [::]:{{ $globals.config.external_https_port }} quic {{ $default_server }}; {{- end }} {{- end }} @@ -851,7 +947,7 @@ server { } add_header Strict-Transport-Security $sts_header always; {{- end }} - {{- else if $globals.default_cert_ok }} + {{- else if $globals.config.default_cert_ok }} # No certificate found for this vhost, so use the default certificate and # return an error code if the user connects via https. ssl_certificate /etc/nginx/certs/default.crt; @@ -865,24 +961,31 @@ server { {{- end }} {{- end }} - {{- if (exists (printf "/etc/nginx/vhost.d/%s" $hostname)) }} - include {{ printf "/etc/nginx/vhost.d/%s" $hostname }}; + {{- $vhostFileName := $vhost.is_regexp | ternary (sha1 $hostname) $hostname }} + + {{- if (exists (printf "/etc/nginx/vhost.d/%s" $vhostFileName)) }} + include {{ printf "/etc/nginx/vhost.d/%s" $vhostFileName }}; {{- else if (exists "/etc/nginx/vhost.d/default") }} include /etc/nginx/vhost.d/default; {{- end }} + {{- if $vhost.enable_debug_endpoint }} + {{ template "debug_location" (dict "GlobalConfig" $globals.config "Hostname" $hostname "VHost" $vhost) }} + {{- end }} + {{- range $path, $vpath := $vhost.paths }} {{- template "location" (dict "Path" $path - "Host" $hostname + "Host" $vhostFileName + "HostIsRegexp" $vhost.is_regexp "VhostRoot" $vhost.vhost_root "VPath" $vpath ) }} {{- end }} - {{- if and (not (contains $vhost.paths "/")) (ne $globals.default_root_response "none")}} + {{- if and (not (contains $vhost.paths "/")) (ne $globals.config.default_root_response "none")}} location / { - return {{ $globals.default_root_response }}; + return {{ $globals.config.default_root_response }}; } {{- end }} } diff --git a/test/test_custom/my_custom_proxy_settings.conf b/test/test_custom/my_custom_proxy_settings_f00.conf similarity index 100% rename from test/test_custom/my_custom_proxy_settings.conf rename to test/test_custom/my_custom_proxy_settings_f00.conf diff --git a/test/test_custom/test_defaults-location.yml b/test/test_custom/test_defaults-location.yml index 9a3ab44f3..6e8965057 100644 --- a/test/test_custom/test_defaults-location.yml +++ b/test/test_custom/test_defaults-location.yml @@ -5,7 +5,7 @@ services: image: nginxproxy/nginx-proxy:test volumes: - /var/run/docker.sock:/tmp/docker.sock:ro - - ./my_custom_proxy_settings.conf:/etc/nginx/vhost.d/default_location:ro + - ./my_custom_proxy_settings_f00.conf:/etc/nginx/vhost.d/default_location:ro - ./my_custom_proxy_settings_bar.conf:/etc/nginx/vhost.d/web3.nginx-proxy.example_location:ro web1: diff --git a/test/test_custom/test_defaults.yml b/test/test_custom/test_defaults.yml index d6a959a48..2f25387ba 100644 --- a/test/test_custom/test_defaults.yml +++ b/test/test_custom/test_defaults.yml @@ -5,7 +5,7 @@ services: image: nginxproxy/nginx-proxy:test volumes: - /var/run/docker.sock:/tmp/docker.sock:ro - - ./my_custom_proxy_settings.conf:/etc/nginx/proxy.conf:ro + - ./my_custom_proxy_settings_f00.conf:/etc/nginx/proxy.conf:ro web1: image: web diff --git a/test/test_custom/test_location-per-vhost.py b/test/test_custom/test_location-per-vhost.py index 53a146b55..8218ed073 100644 --- a/test/test_custom/test_location-per-vhost.py +++ b/test/test_custom/test_location-per-vhost.py @@ -12,6 +12,13 @@ def test_custom_conf_applies_to_web1(docker_compose, nginxproxy): assert "X-test" in r.headers assert "f00" == r.headers["X-test"] +def test_custom_conf_applies_to_regex(docker_compose, nginxproxy): + r = nginxproxy.get("http://regex.foo.nginx-proxy.example/port") + assert r.status_code == 200 + assert r.text == "answer from port 83\n" + assert "X-test" in r.headers + assert "bar" == r.headers["X-test"] + def test_custom_conf_does_not_apply_to_web2(docker_compose, nginxproxy): r = nginxproxy.get("http://web2.nginx-proxy.example/port") assert r.status_code == 200 diff --git a/test/test_custom/test_location-per-vhost.yml b/test/test_custom/test_location-per-vhost.yml index 52943086e..71e606ab4 100644 --- a/test/test_custom/test_location-per-vhost.yml +++ b/test/test_custom/test_location-per-vhost.yml @@ -5,7 +5,8 @@ services: image: nginxproxy/nginx-proxy:test volumes: - /var/run/docker.sock:/tmp/docker.sock:ro - - ./my_custom_proxy_settings.conf:/etc/nginx/vhost.d/web1.nginx-proxy.example_location:ro + - ./my_custom_proxy_settings_f00.conf:/etc/nginx/vhost.d/web1.nginx-proxy.example_location:ro + - ./my_custom_proxy_settings_bar.conf:/etc/nginx/vhost.d/561032515ede3ab3a015edfb244608b72409c430_location:ro web1: image: web @@ -22,3 +23,11 @@ services: environment: WEB_PORTS: 82 VIRTUAL_HOST: web2.nginx-proxy.example + + regex: + image: web + expose: + - "83" + environment: + WEB_PORTS: 83 + VIRTUAL_HOST: ~^regex.*\.nginx-proxy\.example$ diff --git a/test/test_custom/test_per-vhost.py b/test/test_custom/test_per-vhost.py index 6a85e6948..7394472d2 100644 --- a/test/test_custom/test_per-vhost.py +++ b/test/test_custom/test_per-vhost.py @@ -12,6 +12,13 @@ def test_custom_conf_applies_to_web1(docker_compose, nginxproxy): assert "X-test" in r.headers assert "f00" == r.headers["X-test"] +def test_custom_conf_applies_to_regex(docker_compose, nginxproxy): + r = nginxproxy.get("http://regex.foo.nginx-proxy.example/port") + assert r.status_code == 200 + assert r.text == "answer from port 83\n" + assert "X-test" in r.headers + assert "bar" == r.headers["X-test"] + def test_custom_conf_does_not_apply_to_web2(docker_compose, nginxproxy): r = nginxproxy.get("http://web2.nginx-proxy.example/port") assert r.status_code == 200 diff --git a/test/test_custom/test_per-vhost.yml b/test/test_custom/test_per-vhost.yml index 63d33b2b1..0795cef17 100644 --- a/test/test_custom/test_per-vhost.yml +++ b/test/test_custom/test_per-vhost.yml @@ -5,7 +5,8 @@ services: image: nginxproxy/nginx-proxy:test volumes: - /var/run/docker.sock:/tmp/docker.sock:ro - - ./my_custom_proxy_settings.conf:/etc/nginx/vhost.d/web1.nginx-proxy.example:ro + - ./my_custom_proxy_settings_f00.conf:/etc/nginx/vhost.d/web1.nginx-proxy.example:ro + - ./my_custom_proxy_settings_bar.conf:/etc/nginx/vhost.d/561032515ede3ab3a015edfb244608b72409c430:ro web1: image: web @@ -22,3 +23,11 @@ services: environment: WEB_PORTS: 82 VIRTUAL_HOST: web2.nginx-proxy.example + + regex: + image: web + expose: + - "83" + environment: + WEB_PORTS: 83 + VIRTUAL_HOST: ~^regex.*\.nginx-proxy\.example$ diff --git a/test/test_custom/test_proxy-wide.yml b/test/test_custom/test_proxy-wide.yml index 1322bcde7..22e5c1857 100644 --- a/test/test_custom/test_proxy-wide.yml +++ b/test/test_custom/test_proxy-wide.yml @@ -5,7 +5,7 @@ services: image: nginxproxy/nginx-proxy:test volumes: - /var/run/docker.sock:/tmp/docker.sock:ro - - ./my_custom_proxy_settings.conf:/etc/nginx/conf.d/my_custom_proxy_settings.conf:ro + - ./my_custom_proxy_settings_f00.conf:/etc/nginx/conf.d/my_custom_proxy_settings_f00.conf:ro web1: image: web diff --git a/test/test_debug_endpoint/test_global.py b/test/test_debug_endpoint/test_global.py new file mode 100644 index 000000000..aaa7b1fc0 --- /dev/null +++ b/test/test_debug_endpoint/test_global.py @@ -0,0 +1,46 @@ +import json +import pytest + +def test_debug_endpoint_is_enabled_globally(docker_compose, nginxproxy): + r = nginxproxy.get("http://enabled.debug.nginx-proxy.example/nginx-proxy-debug") + assert r.status_code == 200 + r = nginxproxy.get("http://stripped.debug.nginx-proxy.example/nginx-proxy-debug") + assert r.status_code == 200 + + +def test_debug_endpoint_response_contains_expected_values(docker_compose, nginxproxy): + r = nginxproxy.get("http://enabled.debug.nginx-proxy.example/nginx-proxy-debug") + assert r.status_code == 200 + try: + jsonResponse = json.loads(r.text) + except ValueError as err: + pytest.fail("Failed to parse debug endpoint response as JSON: %s" % err, pytrace=False) + assert jsonResponse["global"]["enable_debug_endpoint"] == "true" + assert jsonResponse["vhost"]["enable_debug_endpoint"] == True + + +def test_debug_endpoint_paths_stripped_if_response_too_long(docker_compose, nginxproxy): + r = nginxproxy.get("http://stripped.debug.nginx-proxy.example/nginx-proxy-debug") + assert r.status_code == 200 + try: + jsonResponse = json.loads(r.text) + except ValueError as err: + pytest.fail("Failed to parse debug endpoint response as JSON: %s" % err, pytrace=False) + if "paths" in jsonResponse["vhost"]: + pytest.fail("Expected paths to be stripped from debug endpoint response", pytrace=False) + assert jsonResponse["warning"] == "Virtual paths configuration for this hostname is too large and has been stripped from response." + + +def test_debug_endpoint_hostname_replaced_by_warning_if_regexp(docker_compose, nginxproxy): + r = nginxproxy.get("http://regexp.foo.debug.nginx-proxy.example/nginx-proxy-debug") + assert r.status_code == 200 + try: + jsonResponse = json.loads(r.text) + except ValueError as err: + pytest.fail("Failed to parse debug endpoint response as JSON: %s" % err, pytrace=False) + assert jsonResponse["vhost"]["hostname"] == "Hostname is a regexp and unsafe to include in the debug response." + + +def test_debug_endpoint_is_disabled_per_container(docker_compose, nginxproxy): + r = nginxproxy.get("http://disabled.debug.nginx-proxy.example/nginx-proxy-debug") + assert r.status_code == 404 diff --git a/test/test_debug_endpoint/test_global.yml b/test/test_debug_endpoint/test_global.yml new file mode 100644 index 000000000..179703899 --- /dev/null +++ b/test/test_debug_endpoint/test_global.yml @@ -0,0 +1,62 @@ +services: + nginx-proxy: + image: nginxproxy/nginx-proxy:test + volumes: + - /var/run/docker.sock:/tmp/docker.sock:ro + environment: + DEBUG_ENDPOINT: "true" + + debug_enabled: + image: web + expose: + - "81" + environment: + WEB_PORTS: 81 + VIRTUAL_HOST: enabled.debug.nginx-proxy.example + + debug_stripped: + image: web + expose: + - "82" + environment: + WEB_PORTS: 82 + VIRTUAL_HOST_MULTIPORTS: |- + stripped.debug.nginx-proxy.example: + "/1": + "/2": + "/3": + "/4": + "/5": + "/6": + "/7": + "/8": + "/9": + "/10": + "/11": + "/12": + "/13": + "/14": + "/15": + "/16": + "/17": + "/18": + "/19": + "/20": + + debug_regexp: + image: web + expose: + - "84" + environment: + WEB_PORTS: 84 + VIRTUAL_HOST: ~^regexp.*\.debug.nginx-proxy.example + + debug_disabled: + image: web + expose: + - "83" + environment: + WEB_PORTS: 83 + VIRTUAL_HOST: disabled.debug.nginx-proxy.example + labels: + com.github.nginx-proxy.nginx-proxy.debug-endpoint: "false" diff --git a/test/test_debug_endpoint/test_per_container.py b/test/test_debug_endpoint/test_per_container.py new file mode 100644 index 000000000..16c680c33 --- /dev/null +++ b/test/test_debug_endpoint/test_per_container.py @@ -0,0 +1,24 @@ +import json +import pytest + +def test_debug_endpoint_is_disabled_globally(docker_compose, nginxproxy): + r = nginxproxy.get("http://disabled1.debug.nginx-proxy.example/nginx-proxy-debug") + assert r.status_code == 404 + r = nginxproxy.get("http://disabled2.debug.nginx-proxy.example/nginx-proxy-debug") + assert r.status_code == 404 + + +def test_debug_endpoint_is_enabled_per_container(docker_compose, nginxproxy): + r = nginxproxy.get("http://enabled.debug.nginx-proxy.example/nginx-proxy-debug") + assert r.status_code == 200 + + +def test_debug_endpoint_response_contains_expected_values(docker_compose, nginxproxy): + r = nginxproxy.get("http://enabled.debug.nginx-proxy.example/nginx-proxy-debug") + assert r.status_code == 200 + try: + jsonResponse = json.loads(r.text) + except ValueError as err: + pytest.fail("Failed to parse debug endpoint response as JSON:: %s" % err, pytrace=False) + assert jsonResponse["global"]["enable_debug_endpoint"] == "false" + assert jsonResponse["vhost"]["enable_debug_endpoint"] == True diff --git a/test/test_debug_endpoint/test_per_container.yml b/test/test_debug_endpoint/test_per_container.yml new file mode 100644 index 000000000..56c975cfe --- /dev/null +++ b/test/test_debug_endpoint/test_per_container.yml @@ -0,0 +1,32 @@ +services: + nginx-proxy: + image: nginxproxy/nginx-proxy:test + volumes: + - /var/run/docker.sock:/tmp/docker.sock:ro + + debug_disabled1: + image: web + expose: + - "81" + environment: + WEB_PORTS: 81 + VIRTUAL_HOST: disabled1.debug.nginx-proxy.example + + debug_disabled2: + image: web + expose: + - "82" + environment: + WEB_PORTS: 82 + VIRTUAL_HOST: disabled2.debug.nginx-proxy.example + + + debug_enabled: + image: web + expose: + - "83" + environment: + WEB_PORTS: 83 + VIRTUAL_HOST: enabled.debug.nginx-proxy.example + labels: + com.github.nginx-proxy.nginx-proxy.debug-endpoint: "true" diff --git a/test/test_htpasswd/htpasswd/561032515ede3ab3a015edfb244608b72409c430 b/test/test_htpasswd/htpasswd/561032515ede3ab3a015edfb244608b72409c430 new file mode 100644 index 000000000..336275a1b --- /dev/null +++ b/test/test_htpasswd/htpasswd/561032515ede3ab3a015edfb244608b72409c430 @@ -0,0 +1 @@ +vhost:$2a$13$/aPYmoK0mmgyAI4TpKdFY.6441Ugo39MdXjhpm.Pp6D15rbz9tvz. diff --git a/test/test_htpasswd/test_htpasswd_regex_virtual_host.py b/test/test_htpasswd/test_htpasswd_regex_virtual_host.py new file mode 100644 index 000000000..1b169d001 --- /dev/null +++ b/test/test_htpasswd/test_htpasswd_regex_virtual_host.py @@ -0,0 +1,13 @@ +import pytest + +def test_htpasswd_regex_virtual_host_is_restricted(docker_compose, nginxproxy): + r = nginxproxy.get("http://regex.htpasswd.nginx-proxy.example/port") + assert r.status_code == 401 + assert "WWW-Authenticate" in r.headers + assert r.headers["WWW-Authenticate"] == 'Basic realm="Restricted access"' + + +def test_htpasswd_regex_virtual_host_basic_auth(docker_compose, nginxproxy): + r = nginxproxy.get("http://regex.htpasswd.nginx-proxy.example/port", auth=("vhost", "password")) + assert r.status_code == 200 + assert r.text == "answer from port 80\n" diff --git a/test/test_htpasswd/test_htpasswd_regex_virtual_host.yml b/test/test_htpasswd/test_htpasswd_regex_virtual_host.yml new file mode 100644 index 000000000..7f0d1bc96 --- /dev/null +++ b/test/test_htpasswd/test_htpasswd_regex_virtual_host.yml @@ -0,0 +1,17 @@ +version: "2" + +services: + regex: + image: web + expose: + - "80" + environment: + WEB_PORTS: 80 + VIRTUAL_HOST: ~^regex.*\.nginx-proxy\.example$ + + sut: + container_name: sut + image: nginxproxy/nginx-proxy:test + volumes: + - /var/run/docker.sock:/tmp/docker.sock:ro + - ./htpasswd:/etc/nginx/htpasswd:ro diff --git a/test/test_logs/test_log_disabled.py b/test/test_logs/test_log_disabled.py new file mode 100644 index 000000000..2870b9057 --- /dev/null +++ b/test/test_logs/test_log_disabled.py @@ -0,0 +1,11 @@ +import pytest + +def test_log_disabled(docker_compose, nginxproxy): + r = nginxproxy.get("http://nginx-proxy.test/port") + assert r.status_code == 200 + assert r.text == "answer from port 81\n" + sut_container = docker_compose.containers.get("sut") + docker_logs = sut_container.logs(stdout=True, stderr=True, stream=False, follow=False) + docker_logs = docker_logs.decode("utf-8").splitlines() + docker_logs = [line for line in docker_logs if "GET /port" in line] + assert len(docker_logs) == 0 diff --git a/test/test_logs/test_log_disabled.yml b/test/test_logs/test_log_disabled.yml new file mode 100644 index 000000000..2d8a59c91 --- /dev/null +++ b/test/test_logs/test_log_disabled.yml @@ -0,0 +1,18 @@ +version: "2" + +services: + web1: + image: web + expose: + - "81" + environment: + WEB_PORTS: 81 + VIRTUAL_HOST: nginx-proxy.test + + sut: + container_name: sut + image: nginxproxy/nginx-proxy:test + volumes: + - /var/run/docker.sock:/tmp/docker.sock:ro + environment: + DISABLE_ACCESS_LOGS: true