فهرست منبع

Merge pull request #2602 from p12tic/acme-unknown-virtual-host

feat: support ACME challenges for unknown virtual hosts
Nicolas Duchon 2 هفته پیش
والد
کامیت
554689cc08

+ 2 - 0
docs/README.md

@@ -459,6 +459,8 @@ By default nginx-proxy generates location blocks to handle ACME HTTP Challenge.
 - `false`: do not handle ACME HTTP Challenge at all.
 - `legacy`: legacy behavior for compatibility with older (<= `2.3`) versions of acme-companion, only handle ACME HTTP challenge when there is a certificate for the domain and `HTTPS_METHOD=redirect`.
 
+By default, nginx-proxy does not handle ACME HTTP Challenges for unknown virtual hosts. This may happen in cases when a container is not running at the time of the renewal. To enable handling of unknown virtual hosts, set `ACME_HTTP_CHALLENGE_ACCEPT_UNKNOWN_HOST` environment variable to `true` on the nginx-proxy container.
+
 ### Diffie-Hellman Groups
 
 [RFC7919 groups](https://datatracker.ietf.org/doc/html/rfc7919#appendix-A) with key lengths of 2048, 3072, and 4096 bits are [provided by `nginx-proxy`](https://github.com/nginx-proxy/nginx-proxy/dhparam). The ENV `DHPARAM_BITS` can be set to `2048` or `3072` to change from the default 4096-bit key. The DH key file will be located in the container at `/etc/nginx/dhparam/dhparam.pem`. Mounting a different `dhparam.pem` file at that location will override the RFC7919 key.

+ 11 - 0
nginx.tmpl

@@ -28,6 +28,7 @@
 {{- $_ := 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 "acme_http_challenge_accept_unknown_host" ($globals.Env.ACME_HTTP_CHALLENGE_ACCEPT_UNKNOWN_HOST | default "false" | parseBool) }}
 {{- $_ := 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") }}
@@ -861,6 +862,16 @@ server {
     ssl_reject_handshake on;
         {{- end }}
 
+        {{- if $globals.config.acme_http_challenge_accept_unknown_host }}
+    location ^~ /.well-known/acme-challenge/ {
+        auth_basic off;
+        allow all;
+        root /usr/share/nginx/html;
+        try_files $uri =404;
+        break;
+    }
+        {{- end }}
+
         {{- if (exists "/usr/share/nginx/html/errors/50x.html") }}
     error_page 500 502 503 504 /50x.html;
     location /50x.html {

+ 34 - 0
test/test_acme-http-challenge-location/test_acme-http-challenge-location-accept-unknown-host.py

@@ -0,0 +1,34 @@
+def test_redirect_acme_challenge_location_enabled(docker_compose, nginxproxy, acme_challenge_path):
+    r = nginxproxy.get(
+        f"http://web1.nginx-proxy.tld/{acme_challenge_path}",
+        allow_redirects=False
+    )
+    assert r.status_code == 200
+
+def test_redirect_acme_challenge_location_disabled(docker_compose, nginxproxy, acme_challenge_path):
+    r = nginxproxy.get(
+        f"http://web2.nginx-proxy.tld/{acme_challenge_path}",
+        allow_redirects=False
+    )
+    assert r.status_code == 301
+
+def test_noredirect_acme_challenge_location_enabled(docker_compose, nginxproxy, acme_challenge_path):
+    r = nginxproxy.get(
+        f"http://web3.nginx-proxy.tld/{acme_challenge_path}",
+        allow_redirects=False
+    )
+    assert r.status_code == 200
+
+def test_noredirect_acme_challenge_location_disabled(docker_compose, nginxproxy, acme_challenge_path):
+    r = nginxproxy.get(
+        f"http://web4.nginx-proxy.tld/{acme_challenge_path}",
+        allow_redirects=False
+    )
+    assert r.status_code == 404
+
+def test_unknown_domain_acme_challenge_location_default_enabled(docker_compose, nginxproxy, acme_challenge_path):
+    r = nginxproxy.get(
+        f"http://web-unknown.nginx-proxy.tld/{acme_challenge_path}",
+        allow_redirects=False
+    )
+    assert r.status_code == 200

+ 40 - 0
test/test_acme-http-challenge-location/test_acme-http-challenge-location-accept-unknown-host.yml

@@ -0,0 +1,40 @@
+services:
+  nginx-proxy:
+    environment:
+      ACME_HTTP_CHALLENGE_ACCEPT_UNKNOWN_HOST: "true"
+
+  web1:
+    image: web
+    expose:
+      - "81"
+    environment:
+      WEB_PORTS: "81"
+      VIRTUAL_HOST: "web1.nginx-proxy.tld"
+
+  web2:
+    image: web
+    expose:
+      - "82"
+    environment:
+      WEB_PORTS: "82"
+      VIRTUAL_HOST: "web2.nginx-proxy.tld"
+      ACME_HTTP_CHALLENGE_LOCATION: "false"
+
+  web3:
+    image: web
+    expose:
+      - "83"
+    environment:
+      WEB_PORTS: "83"
+      VIRTUAL_HOST: "web3.nginx-proxy.tld"
+      HTTPS_METHOD: noredirect
+
+  web4:
+    image: web
+    expose:
+      - "84"
+    environment:
+      WEB_PORTS: "84"
+      VIRTUAL_HOST: "web4.nginx-proxy.tld"
+      HTTPS_METHOD: noredirect
+      ACME_HTTP_CHALLENGE_LOCATION: "false"

+ 7 - 0
test/test_acme-http-challenge-location/test_acme-http-challenge-location-disabled.py

@@ -25,3 +25,10 @@ def test_noredirect_acme_challenge_location_enabled(docker_compose, nginxproxy,
         allow_redirects=False
     )
     assert r.status_code == 200
+
+def test_unknown_domain_acme_challenge_location_disabled(docker_compose, nginxproxy, acme_challenge_path):
+    r = nginxproxy.get(
+        f"http://web-unknown.nginx-proxy.tld/{acme_challenge_path}",
+        allow_redirects=False
+    )
+    assert r.status_code == 503

+ 7 - 0
test/test_acme-http-challenge-location/test_acme-http-challenge-location-enabled-is-default.py

@@ -25,3 +25,10 @@ def test_noredirect_acme_challenge_location_disabled(docker_compose, nginxproxy,
         allow_redirects=False
     )
     assert r.status_code == 404
+
+def test_unknown_domain_acme_challenge_location_default_enabled(docker_compose, nginxproxy, acme_challenge_path):
+    r = nginxproxy.get(
+        f"http://web-unknown.nginx-proxy.tld/{acme_challenge_path}",
+        allow_redirects=False
+    )
+    assert r.status_code == 503

+ 7 - 0
test/test_acme-http-challenge-location/test_acme-http-challenge-location-legacy.py

@@ -11,3 +11,10 @@ def test_noredirect_acme_challenge_location_legacy(docker_compose, nginxproxy, a
         allow_redirects=False
     )
     assert r.status_code == 404
+
+def test_unknown_domain_acme_challenge_location_legacy(docker_compose, nginxproxy, acme_challenge_path):
+    r = nginxproxy.get(
+        f"http://web-unknown.nginx-proxy.tld/{acme_challenge_path}",
+        allow_redirects=False
+    )
+    assert r.status_code == 503