From 73ba28091a8b0bad96ee1f6b02a016c33900bd69 Mon Sep 17 00:00:00 2001 From: Nicolas Duchon Date: Fri, 1 Nov 2024 20:32:00 +0100 Subject: [PATCH 1/4] fix: use sha1 hash for config files when using regex host --- nginx.tmpl | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/nginx.tmpl b/nginx.tmpl index 59e3573..2348ea7 100644 --- a/nginx.tmpl +++ b/nginx.tmpl @@ -289,7 +289,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 }} @@ -570,7 +570,9 @@ proxy_set_header Proxy ""; {{- /* 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 }} @@ -644,6 +646,7 @@ proxy_set_header Proxy ""; "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 @@ -785,6 +788,23 @@ server { {{- 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 }}; @@ -865,8 +885,10 @@ 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 }} @@ -874,7 +896,8 @@ server { {{- range $path, $vpath := $vhost.paths }} {{- template "location" (dict "Path" $path - "Host" $hostname + "Host" $vhostFileName + "HostIsRegexp" $vhost.is_regexp "VhostRoot" $vhost.vhost_root "VPath" $vpath ) }} From 5baf4a163f4747827e50b29576d70d232fa70f2b Mon Sep 17 00:00:00 2001 From: Nicolas Duchon Date: Fri, 1 Nov 2024 21:36:00 +0100 Subject: [PATCH 2/4] docs: update docs regex file name change --- docs/README.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/README.md b/docs/README.md index 080b86b..3990a7a 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 `/`: From 1cd7b97e8f6ae014cd2cb1d88447d274642a0850 Mon Sep 17 00:00:00 2001 From: Nicolas Duchon Date: Fri, 1 Nov 2024 21:37:29 +0100 Subject: [PATCH 3/4] test: regex vhost file names --- test/test_custom/test_location-per-vhost.py | 7 +++++++ test/test_custom/test_location-per-vhost.yml | 9 +++++++++ test/test_custom/test_per-vhost.py | 7 +++++++ test/test_custom/test_per-vhost.yml | 9 +++++++++ .../561032515ede3ab3a015edfb244608b72409c430 | 1 + .../test_htpasswd_regex_virtual_host.py | 13 +++++++++++++ .../test_htpasswd_regex_virtual_host.yml | 17 +++++++++++++++++ 7 files changed, 63 insertions(+) create mode 100644 test/test_htpasswd/htpasswd/561032515ede3ab3a015edfb244608b72409c430 create mode 100644 test/test_htpasswd/test_htpasswd_regex_virtual_host.py create mode 100644 test/test_htpasswd/test_htpasswd_regex_virtual_host.yml diff --git a/test/test_custom/test_location-per-vhost.py b/test/test_custom/test_location-per-vhost.py index 53a146b..8218ed0 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 5294308..5a6164c 100644 --- a/test/test_custom/test_location-per-vhost.yml +++ b/test/test_custom/test_location-per-vhost.yml @@ -6,6 +6,7 @@ services: 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_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 6a85e69..7394472 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 63d33b2..337fc56 100644 --- a/test/test_custom/test_per-vhost.yml +++ b/test/test_custom/test_per-vhost.yml @@ -6,6 +6,7 @@ services: 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_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_htpasswd/htpasswd/561032515ede3ab3a015edfb244608b72409c430 b/test/test_htpasswd/htpasswd/561032515ede3ab3a015edfb244608b72409c430 new file mode 100644 index 0000000..336275a --- /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 0000000..1b169d0 --- /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 0000000..7f0d1bc --- /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 From 4c67b2455280d30c70447334548640933e38ad5a Mon Sep 17 00:00:00 2001 From: Nicolas Duchon Date: Fri, 1 Nov 2024 21:54:59 +0100 Subject: [PATCH 4/4] test: rename file for clarity --- ...om_proxy_settings.conf => my_custom_proxy_settings_f00.conf} | 0 test/test_custom/test_defaults-location.yml | 2 +- test/test_custom/test_defaults.yml | 2 +- test/test_custom/test_location-per-vhost.yml | 2 +- test/test_custom/test_per-vhost.yml | 2 +- test/test_custom/test_proxy-wide.yml | 2 +- 6 files changed, 5 insertions(+), 5 deletions(-) rename test/test_custom/{my_custom_proxy_settings.conf => my_custom_proxy_settings_f00.conf} (100%) 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 9a3ab44..6e89650 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 d6a959a..2f25387 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.yml b/test/test_custom/test_location-per-vhost.yml index 5a6164c..71e606a 100644 --- a/test/test_custom/test_location-per-vhost.yml +++ b/test/test_custom/test_location-per-vhost.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/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: diff --git a/test/test_custom/test_per-vhost.yml b/test/test_custom/test_per-vhost.yml index 337fc56..0795cef 100644 --- a/test/test_custom/test_per-vhost.yml +++ b/test/test_custom/test_per-vhost.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/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: diff --git a/test/test_custom/test_proxy-wide.yml b/test/test_custom/test_proxy-wide.yml index 1322bcd..22e5c18 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