Explorar o código

Merge pull request #2533 from nginx-proxy/2529

fix: use sha1 hash for config files name when using regex host
Nicolas Duchon hai 7 meses
pai
achega
a3db62bb14

+ 7 - 6
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 `/`:
 

+ 27 - 4
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
         ) }}

+ 0 - 0
test/test_custom/my_custom_proxy_settings.conf → test/test_custom/my_custom_proxy_settings_f00.conf


+ 1 - 1
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:

+ 1 - 1
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

+ 7 - 0
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   

+ 10 - 1
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$

+ 7 - 0
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   

+ 10 - 1
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$

+ 1 - 1
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

+ 1 - 0
test/test_htpasswd/htpasswd/561032515ede3ab3a015edfb244608b72409c430

@@ -0,0 +1 @@
+vhost:$2a$13$/aPYmoK0mmgyAI4TpKdFY.6441Ugo39MdXjhpm.Pp6D15rbz9tvz.

+ 13 - 0
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"

+ 17 - 0
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