From 9711ade7a640249d99c425a10b15ed730a565779 Mon Sep 17 00:00:00 2001 From: Knapoc Date: Mon, 24 Jul 2023 11:36:17 +0200 Subject: [PATCH 01/54] feat: allow nginx / docker-gen network segregation * fix merge conflicts --- docs/README.md | 6 ++++++ nginx.tmpl | 52 +++++++++++++++++++++++++++++++++----------------- 2 files changed, 41 insertions(+), 17 deletions(-) diff --git a/docs/README.md b/docs/README.md index 3e3938825..d99d8be5b 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1157,6 +1157,12 @@ Finally, start your containers with `VIRTUAL_HOST` environment variables. docker run -e VIRTUAL_HOST=foo.bar.com ... ``` +To allow for network segregation of the nginx and docker-gen containers, the label `com.github.nginx-proxy.nginx-proxy.nginx` must be applied to the nginx container, otherwise it is assumed that nginx and docker-gen share the same network: + +```console +docker run -d -p 80:80 --name nginx -l "com.github.nginx-proxy.nginx-proxy.nginx" -v /tmp/nginx:/etc/nginx/conf.d -t nginx +``` + ⬆️ [back to table of contents](#table-of-contents) ## Docker Compose diff --git a/nginx.tmpl b/nginx.tmpl index 14e30ca54..3666625f7 100644 --- a/nginx.tmpl +++ b/nginx.tmpl @@ -11,6 +11,7 @@ {{- $_ := set $globals "Env" $.Env }} {{- $_ := set $globals "Docker" $.Docker }} {{- $_ := set $globals "CurrentContainer" (where $globals.containers "ID" $globals.Docker.CurrentContainerID | first) }} +{{- $_ := set $globals "NginxContainer" (whereLabelExists $globals.containers "com.github.nginx-proxy.nginx-proxy.nginx" | first) }} {{- $config := dict }} {{- $_ := set $config "nginx_proxy_version" $.Env.NGINX_PROXY_VERSION }} @@ -44,14 +45,21 @@ {{- $_ := set $globals "vhosts" (dict) }} {{- $_ := set $globals "networks" (dict) }} -# Networks available to the container running docker-gen (which are assumed to +# Networks available to the container labeled "com.github.nginx-proxy.nginx-proxy.nginx" or the one running docker-gen (which are assumed to # match the networks available to the container running nginx): {{- /* * Note: $globals.CurrentContainer may be nil in some circumstances due to * . For more context * see . */}} -{{- if $globals.CurrentContainer }} +{{- if $globals.NginxContainer }} + {{- range sortObjectsByKeysAsc $globals.NginxContainer.Networks "Name" }} + {{- $_ := set $globals.networks .Name . }} +# {{ .Name }} + {{- else }} +# (none) + {{- end }} +{{- else if $globals.CurrentContainer }} {{- range sortObjectsByKeysAsc $globals.CurrentContainer.Networks "Name" }} {{- $_ := set $globals.networks .Name . }} # {{ .Name }} @@ -97,11 +105,21 @@ {{- $ipv4 = "127.0.0.1" }} {{- continue }} {{- end }} - {{- range sortObjectsByKeysAsc $.globals.CurrentContainer.Networks "Name" }} - {{- if and . .Gateway (not .Internal) }} + {{- if $.globals.NginxContainer }} + {{- range sortObjectsByKeysAsc $.globals.NginxContainer.Networks "Name" }} + {{- if and . .Gateway (not .Internal) }} # container is in host network mode, using {{ .Name }} gateway IP - {{- $ipv4 = .Gateway }} - {{- break }} + {{- $ipv4 = .Gateway }} + {{- break }} + {{- end }} + {{- end }} + {{- else }} + {{- range sortObjectsByKeysAsc $.globals.CurrentContainer.Networks "Name" }} + {{- if and . .Gateway (not .Internal) }} + # container is in host network mode, using {{ .Name }} gateway IP + {{- $ipv4 = .Gateway }} + {{- break }} + {{- end }} {{- end }} {{- end }} {{- if $ipv4 }} @@ -114,7 +132,7 @@ {{- end }} {{- /* * Do not emit multiple `server` directives for this container if it - * is reachable over multiple networks or multiple IP stacks. This avoids + * is reachable over multiple networks or multiple IP stacks. This avoids * accidentally inflating the effective round-robin weight of a server due * to the redundant upstream addresses that nginx sees as belonging to * distinct servers. @@ -397,7 +415,7 @@ upstream {{ $vpath.upstream }} { {{- $debug_vpath := deepCopy $vpath | merge (dict "ports" $tmp_ports) }} {{- $_ := set $debug_paths $path $debug_vpath }} {{- 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) }} @@ -606,7 +624,7 @@ proxy_set_header Proxy ""; {{- $path_port_containers := get $path_ports $port | default (list) | concat $containers }} {{- $_ := set $path_ports $port $path_port_containers }} {{- $_ := set $path_data "ports" $path_ports }} - + {{- if (not (hasKey $path_data "dest")) }} {{- $_ := set $path_data "dest" $dest }} {{- end }} @@ -614,7 +632,7 @@ proxy_set_header Proxy ""; {{- if (not (hasKey $path_data "proto")) }} {{- $_ := set $path_data "proto" $proto }} {{- end }} - + {{- $_ := set $paths $path $path_data }} {{- end }} {{- $_ := set $vhost_data "paths" $paths }} @@ -666,7 +684,7 @@ proxy_set_header Proxy ""; {{- if (not (hasKey $path_data "proto")) }} {{- $_ := set $path_data "proto" $proto }} {{- end }} - + {{- $_ := set $paths $path $path_data }} {{- end }} {{- $_ := set $vhost_data "paths" $paths }} @@ -708,7 +726,7 @@ proxy_set_header Proxy ""; {{- end }} {{- $userIdentifiedCert := groupByKeys $vhost_containers "Env.CERT_NAME" | first }} - + {{- $vhostCert := "" }} {{- if exists (printf "/etc/nginx/certs/%s.crt" $hostname) }} {{- $vhostCert = $hostname }} @@ -721,10 +739,10 @@ proxy_set_header Proxy ""; {{- $parentVhostCert = $parentHostname }} {{- end }} {{- end }} - + {{- $trust_default_cert := groupByLabel $vhost_containers "com.github.nginx-proxy.nginx-proxy.trust-default-cert" | keys | first | default $globals.config.trust_default_cert | parseBool }} {{- $defaultCert := and $trust_default_cert $globals.config.default_cert_ok | ternary "default" "" }} - + {{- $cert := or $userIdentifiedCert $vhostCert $parentVhostCert $defaultCert }} {{- $cert_ok := and (ne $cert "") (exists (printf "/etc/nginx/certs/%s.crt" $cert)) (exists (printf "/etc/nginx/certs/%s.key" $cert)) }} @@ -738,10 +756,10 @@ proxy_set_header Proxy ""; {{- $https_method = "noredirect" }} {{- end }} {{- $non_get_redirect := groupByLabel $vhost_containers "com.github.nginx-proxy.nginx-proxy.non-get-redirect" | keys | first | default $globals.config.non_get_redirect }} - + {{- $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 }} @@ -903,7 +921,7 @@ server { break; } {{- end }} - + {{- if $vhost.enable_debug_endpoint }} {{ template "debug_location" (dict "GlobalConfig" $globals.config "Hostname" $hostname "VHost" $vhost) }} {{- end }} From ded6f89c56ee0737e3eb3f04b3afd8ed4e996aef Mon Sep 17 00:00:00 2001 From: Knapoc <21982227+Knapoc@users.noreply.github.com> Date: Tue, 22 Apr 2025 12:04:04 +0200 Subject: [PATCH 02/54] test: check docker-gen network segregation --- .../test_dockergen_network_segregation_v2.py | 10 +++++ .../test_dockergen_network_segregation_v2.yml | 38 ++++++++++++++++++ .../test_dockergen_network_segregation_v3.py | 27 +++++++++++++ .../test_dockergen_network_segregation_v3.yml | 40 +++++++++++++++++++ 4 files changed, 115 insertions(+) create mode 100644 test/test_dockergen/test_dockergen_network_segregation_v2.py create mode 100644 test/test_dockergen/test_dockergen_network_segregation_v2.yml create mode 100644 test/test_dockergen/test_dockergen_network_segregation_v3.py create mode 100644 test/test_dockergen/test_dockergen_network_segregation_v3.yml diff --git a/test/test_dockergen/test_dockergen_network_segregation_v2.py b/test/test_dockergen/test_dockergen_network_segregation_v2.py new file mode 100644 index 000000000..dbb15d47f --- /dev/null +++ b/test/test_dockergen/test_dockergen_network_segregation_v2.py @@ -0,0 +1,10 @@ +def test_unknown_virtual_host_is_503(docker_compose, nginxproxy): + r = nginxproxy.get("http://unknown.nginx.container.docker/") + assert r.status_code == 503 + + +def test_forwards_to_whoami(docker_compose, nginxproxy): + r = nginxproxy.get("http://whoami.nginx.container.docker/") + assert r.status_code == 200 + whoami_container = docker_compose.containers.get("whoami") + assert r.text == f"I'm {whoami_container.id[:12]}\n" diff --git a/test/test_dockergen/test_dockergen_network_segregation_v2.yml b/test/test_dockergen/test_dockergen_network_segregation_v2.yml new file mode 100644 index 000000000..949e282b8 --- /dev/null +++ b/test/test_dockergen/test_dockergen_network_segregation_v2.yml @@ -0,0 +1,38 @@ +version: '2' + +services: + nginx: + image: nginx + container_name: nginx + volumes: + - "/etc/nginx/conf.d" + labels: + - "com.github.nginx-proxy.nginx-proxy.nginx" + networks: + - proxy + + dockergen: + image: nginxproxy/docker-gen + command: -notify-sighup nginx -watch /etc/docker-gen/templates/nginx.tmpl /etc/nginx/conf.d/default.conf + volumes_from: + - nginx + volumes: + - /var/run/docker.sock:/tmp/docker.sock:ro + - ../../nginx.tmpl:/etc/docker-gen/templates/nginx.tmpl + networks: + - internal + + web: + image: web + container_name: whoami + expose: + - "80" + environment: + WEB_PORTS: "80" + VIRTUAL_HOST: "whoami.nginx.container.docker" + networks: + - proxy + +networks: + proxy: + internal: diff --git a/test/test_dockergen/test_dockergen_network_segregation_v3.py b/test/test_dockergen/test_dockergen_network_segregation_v3.py new file mode 100644 index 000000000..b696e6c62 --- /dev/null +++ b/test/test_dockergen/test_dockergen_network_segregation_v3.py @@ -0,0 +1,27 @@ +import docker +import pytest +from distutils.version import LooseVersion + + +raw_version = docker.from_env().version()["Version"] +pytestmark = pytest.mark.skipif( + LooseVersion(raw_version) < LooseVersion("1.13"), + reason="Docker compose syntax v3 requires docker engine v1.13 or later (got {raw_version})" +) + + +def test_unknown_virtual_host_is_503(docker_compose, nginxproxy): + r = nginxproxy.get("http://unknown.nginx.container.docker/") + assert r.status_code == 503 + + +def test_forwards_to_whoami(docker_compose, nginxproxy): + r = nginxproxy.get("http://whoami.nginx.container.docker/") + assert r.status_code == 200 + whoami_container = docker_compose.containers.get("whoami") + assert r.text == f"I'm {whoami_container.id[:12]}\n" + + +if __name__ == "__main__": + import doctest + doctest.testmod() diff --git a/test/test_dockergen/test_dockergen_network_segregation_v3.yml b/test/test_dockergen/test_dockergen_network_segregation_v3.yml new file mode 100644 index 000000000..c873c318c --- /dev/null +++ b/test/test_dockergen/test_dockergen_network_segregation_v3.yml @@ -0,0 +1,40 @@ +version: '3' + +services: + nginx: + image: nginx + container_name: nginx + volumes: + - "nginx_conf:/etc/nginx/conf.d" + labels: + - "com.github.nginx-proxy.nginx-proxy.nginx" + networks: + - proxy + + dockergen: + image: nginxproxy/docker-gen + command: -notify-sighup nginx -watch /etc/docker-gen/templates/nginx.tmpl /etc/nginx/conf.d/default.conf + volumes: + - "/var/run/docker.sock:/tmp/docker.sock:ro" + - "../../nginx.tmpl:/etc/docker-gen/templates/nginx.tmpl" + - "nginx_conf:/etc/nginx/conf.d" + networks: + - internal + + web: + image: web + container_name: whoami + expose: + - "80" + environment: + WEB_PORTS: "80" + VIRTUAL_HOST: "whoami.nginx.container.docker" + networks: + - proxy + +networks: + proxy: + internal: + +volumes: + nginx_conf: {} From c338e1bcdfdf56d939005dde1b2cecc7f9c7ac68 Mon Sep 17 00:00:00 2001 From: Knapoc <21982227+Knapoc@users.noreply.github.com> Date: Tue, 22 Apr 2025 12:31:09 +0200 Subject: [PATCH 03/54] test: docker-gen network segregation * fix tests * remove obsolete compose version --- ...st_dockergen_network_segregation.base.yml} | 25 +++++++----- ... => test_dockergen_network_segregation.py} | 8 ++-- .../test_dockergen_network_segregation_v2.py | 10 ----- .../test_dockergen_network_segregation_v3.yml | 40 ------------------- 4 files changed, 18 insertions(+), 65 deletions(-) rename test/test_dockergen/{test_dockergen_network_segregation_v2.yml => test_dockergen_network_segregation.base.yml} (69%) rename test/test_dockergen/{test_dockergen_network_segregation_v3.py => test_dockergen_network_segregation.py} (72%) delete mode 100644 test/test_dockergen/test_dockergen_network_segregation_v2.py delete mode 100644 test/test_dockergen/test_dockergen_network_segregation_v3.yml diff --git a/test/test_dockergen/test_dockergen_network_segregation_v2.yml b/test/test_dockergen/test_dockergen_network_segregation.base.yml similarity index 69% rename from test/test_dockergen/test_dockergen_network_segregation_v2.yml rename to test/test_dockergen/test_dockergen_network_segregation.base.yml index 949e282b8..8040b479c 100644 --- a/test/test_dockergen/test_dockergen_network_segregation_v2.yml +++ b/test/test_dockergen/test_dockergen_network_segregation.base.yml @@ -1,38 +1,41 @@ -version: '2' - services: - nginx: + nginx-proxy-nginx: image: nginx container_name: nginx volumes: - - "/etc/nginx/conf.d" - labels: - - "com.github.nginx-proxy.nginx-proxy.nginx" + - nginx_conf:/etc/nginx/conf.d:ro + ports: + - "80:80" + - "443:443" networks: - proxy + labels: + - "com.github.nginx-proxy.nginx-proxy.nginx" - dockergen: + nginx-proxy-dockergen: image: nginxproxy/docker-gen command: -notify-sighup nginx -watch /etc/docker-gen/templates/nginx.tmpl /etc/nginx/conf.d/default.conf - volumes_from: - - nginx volumes: - /var/run/docker.sock:/tmp/docker.sock:ro - ../../nginx.tmpl:/etc/docker-gen/templates/nginx.tmpl + - nginx_conf:/etc/nginx/conf.d networks: - internal web: image: web - container_name: whoami + container_name: whoami2 expose: - "80" environment: WEB_PORTS: "80" - VIRTUAL_HOST: "whoami.nginx.container.docker" + VIRTUAL_HOST: whoami2.nginx.container.docker networks: - proxy networks: proxy: internal: + +volumes: + nginx_conf: diff --git a/test/test_dockergen/test_dockergen_network_segregation_v3.py b/test/test_dockergen/test_dockergen_network_segregation.py similarity index 72% rename from test/test_dockergen/test_dockergen_network_segregation_v3.py rename to test/test_dockergen/test_dockergen_network_segregation.py index b696e6c62..ad487d99f 100644 --- a/test/test_dockergen/test_dockergen_network_segregation_v3.py +++ b/test/test_dockergen/test_dockergen_network_segregation.py @@ -1,11 +1,11 @@ import docker import pytest -from distutils.version import LooseVersion +from packaging.version import Version raw_version = docker.from_env().version()["Version"] pytestmark = pytest.mark.skipif( - LooseVersion(raw_version) < LooseVersion("1.13"), + Version(raw_version) < Version("1.13"), reason="Docker compose syntax v3 requires docker engine v1.13 or later (got {raw_version})" ) @@ -16,9 +16,9 @@ def test_unknown_virtual_host_is_503(docker_compose, nginxproxy): def test_forwards_to_whoami(docker_compose, nginxproxy): - r = nginxproxy.get("http://whoami.nginx.container.docker/") + r = nginxproxy.get("http://whoami2.nginx.container.docker/") assert r.status_code == 200 - whoami_container = docker_compose.containers.get("whoami") + whoami_container = docker_compose.containers.get("whoami2") assert r.text == f"I'm {whoami_container.id[:12]}\n" diff --git a/test/test_dockergen/test_dockergen_network_segregation_v2.py b/test/test_dockergen/test_dockergen_network_segregation_v2.py deleted file mode 100644 index dbb15d47f..000000000 --- a/test/test_dockergen/test_dockergen_network_segregation_v2.py +++ /dev/null @@ -1,10 +0,0 @@ -def test_unknown_virtual_host_is_503(docker_compose, nginxproxy): - r = nginxproxy.get("http://unknown.nginx.container.docker/") - assert r.status_code == 503 - - -def test_forwards_to_whoami(docker_compose, nginxproxy): - r = nginxproxy.get("http://whoami.nginx.container.docker/") - assert r.status_code == 200 - whoami_container = docker_compose.containers.get("whoami") - assert r.text == f"I'm {whoami_container.id[:12]}\n" diff --git a/test/test_dockergen/test_dockergen_network_segregation_v3.yml b/test/test_dockergen/test_dockergen_network_segregation_v3.yml deleted file mode 100644 index c873c318c..000000000 --- a/test/test_dockergen/test_dockergen_network_segregation_v3.yml +++ /dev/null @@ -1,40 +0,0 @@ -version: '3' - -services: - nginx: - image: nginx - container_name: nginx - volumes: - - "nginx_conf:/etc/nginx/conf.d" - labels: - - "com.github.nginx-proxy.nginx-proxy.nginx" - networks: - - proxy - - dockergen: - image: nginxproxy/docker-gen - command: -notify-sighup nginx -watch /etc/docker-gen/templates/nginx.tmpl /etc/nginx/conf.d/default.conf - volumes: - - "/var/run/docker.sock:/tmp/docker.sock:ro" - - "../../nginx.tmpl:/etc/docker-gen/templates/nginx.tmpl" - - "nginx_conf:/etc/nginx/conf.d" - networks: - - internal - - web: - image: web - container_name: whoami - expose: - - "80" - environment: - WEB_PORTS: "80" - VIRTUAL_HOST: "whoami.nginx.container.docker" - networks: - - proxy - -networks: - proxy: - internal: - -volumes: - nginx_conf: {} From 67aa2aafd20717d53b5eea8fc929851307ecc435 Mon Sep 17 00:00:00 2001 From: Nicolas Duchon Date: Sun, 11 May 2025 11:38:13 +0200 Subject: [PATCH 04/54] chore: update issue templates --- .github/ISSUE_TEMPLATE/bug_report.md | 44 ++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 000000000..66d881f3e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,44 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +# ⚠️ PLEASE READ ⚠️ + +## Questions or Features + +If you have a question or want to request a feature, please **DO NOT SUBMIT** a new issue. + +Instead please use the relevant Discussions section's category: +- 🙏 [Ask a question](https://github.com/nginx-proxy/nginx-proxy/discussions/categories/q-a) +- 💡 [Request a feature](https://github.com/nginx-proxy/nginx-proxy/discussions/categories/ideas) + +## Bugs + +If you are logging a bug, please search the current open issues first to see if there is already a bug opened. + +For bugs, the easier you make it to reproduce the issue you see and the more initial information you provide, the easier and faster the bug can be identified and can get fixed. + +Please at least provide: +- the exact nginx-proxy version you're using (if using `latest` please make sure it is up to date and provide the version number printed at container startup). +- complete configuration (compose file, command line, etc) of both your nginx-proxy container(s) and proxied containers. You should redact sensitive info if needed but please provide **full** configurations. +- generated nginx configuration obtained with `docker exec nameofyournginxproxycontainer nginx -T` + +If you can provide a script or docker-compose file that reproduces the problems, that is very helpful. + +## General advice about `latest` + +Do not use the `latest` tag for production setups. + +`latest` is nothing more than a convenient default used by Docker if no specific tag is provided, there isn't any strict convention on what goes into this tag over different projects, and it does not carry any promise of stability. + +Using `latest` will most certainly put you at risk of experiencing uncontrolled updates to non backward compatible versions (or versions with breaking changes) and makes it harder for maintainers to track which exact version of the container you are experiencing an issue with. + +This recommendation stands for pretty much every Docker image in existence, not just nginx-proxy's ones. + +Thanks, +Nicolas From be1fd947787c80b3f27b300e962775797aa0b577 Mon Sep 17 00:00:00 2001 From: Nicolas Duchon Date: Sun, 11 May 2025 12:01:38 +0200 Subject: [PATCH 05/54] chore: configure the issue template chooser --- .github/ISSUE_TEMPLATE.md | 23 ----------------------- .github/ISSUE_TEMPLATE/config.yml | 5 +++++ 2 files changed, 5 insertions(+), 23 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/config.yml diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 9824d632a..65b80f03b 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -8,28 +8,5 @@ Instead please use the relevant Discussions section's category: - 🙏 [Ask a question](https://github.com/nginx-proxy/nginx-proxy/discussions/categories/q-a) - 💡 [Request a feature](https://github.com/nginx-proxy/nginx-proxy/discussions/categories/ideas) -## Bugs - -If you are logging a bug, please search the current open issues first to see if there is already a bug opened. - -For bugs, the easier you make it to reproduce the issue you see and the more initial information you provide, the easier and faster the bug can be identified and can get fixed. - -Please at least provide: -- the exact nginx-proxy version you're using (if using `latest` please make sure it is up to date and provide the version number printed at container startup). -- complete configuration (compose file, command line, etc) of both your nginx-proxy container(s) and proxied containers. You should redact sensitive info if needed but please provide **full** configurations. -- generated nginx configuration obtained with `docker exec nameofyournginxproxycontainer nginx -T` - -If you can provide a script or docker-compose file that reproduces the problems, that is very helpful. - -## General advice about `latest` - -Do not use the `latest` tag for production setups. - -`latest` is nothing more than a convenient default used by Docker if no specific tag is provided, there isn't any strict convention on what goes into this tag over different projects, and it does not carry any promise of stability. - -Using `latest` will most certainly put you at risk of experiencing uncontrolled updates to non backward compatible versions (or versions with breaking changes) and makes it harder for maintainers to track which exact version of the container you are experiencing an issue with. - -This recommendation stands for pretty much every Docker image in existence, not just nginx-proxy's ones. - Thanks, Nicolas diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 000000000..7c19e5e3e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: true +contact_links: + - name: GitHub Discussions + url: https://github.com/orgs/community/discussions + about: Please ask and answer questions here. \ No newline at end of file From 40744f6f413399d5aedb081a3e39ef9d4875d8d9 Mon Sep 17 00:00:00 2001 From: Nicolas Duchon Date: Sun, 11 May 2025 12:26:24 +0200 Subject: [PATCH 06/54] refactor: deduplicate code --- nginx.tmpl | 55 ++++++++++++++++++++---------------------------------- 1 file changed, 20 insertions(+), 35 deletions(-) diff --git a/nginx.tmpl b/nginx.tmpl index 3666625f7..dd1c4448d 100644 --- a/nginx.tmpl +++ b/nginx.tmpl @@ -10,11 +10,10 @@ {{- $_ := set $globals "containers" $ }} {{- $_ := set $globals "Env" $.Env }} {{- $_ := set $globals "Docker" $.Docker }} -{{- $_ := set $globals "CurrentContainer" (where $globals.containers "ID" $globals.Docker.CurrentContainerID | first) }} -{{- $_ := set $globals "NginxContainer" (whereLabelExists $globals.containers "com.github.nginx-proxy.nginx-proxy.nginx" | first) }} {{- $config := dict }} {{- $_ := set $config "nginx_proxy_version" $.Env.NGINX_PROXY_VERSION }} +{{- $_ := set $config "nginx_container_label" ($.Env.NGINX_CONTAINER_LABEL | default "com.github.nginx-proxy.nginx-proxy.nginx") }} {{- $_ := 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") }} @@ -45,33 +44,29 @@ {{- $_ := set $globals "vhosts" (dict) }} {{- $_ := set $globals "networks" (dict) }} -# Networks available to the container labeled "com.github.nginx-proxy.nginx-proxy.nginx" or the one running docker-gen (which are assumed to -# match the networks available to the container running nginx): + +{{- $currentContainer := where $globals.containers "ID" $globals.Docker.CurrentContainerID | first }} +{{- $labeledContainer := whereLabelExists $globals.containers $globals.config.nginx_container_label | first }} +{{- $_ := set $globals "NetworkContainer" ($labeledContainer | default $currentContainer) }} +# Networks available to the container labeled "{{ $globals.config.nginx_container_label }}" or the one running docker-gen +# (which are assumed to match the networks available to the container running nginx): {{- /* - * Note: $globals.CurrentContainer may be nil in some circumstances due to - * . For more context - * see . + * Note: + * $globals.NetworkContainer may be nil in some circumstances due to https://github.com/nginx-proxy/docker-gen/issues/458. + * For more context see https://github.com/nginx-proxy/nginx-proxy/issues/2189. */}} -{{- if $globals.NginxContainer }} - {{- range sortObjectsByKeysAsc $globals.NginxContainer.Networks "Name" }} - {{- $_ := set $globals.networks .Name . }} -# {{ .Name }} - {{- else }} -# (none) - {{- end }} -{{- else if $globals.CurrentContainer }} - {{- range sortObjectsByKeysAsc $globals.CurrentContainer.Networks "Name" }} +{{- if $globals.NetworkContainer }} + {{- range sortObjectsByKeysAsc $globals.NetworkContainer.Networks "Name" }} {{- $_ := set $globals.networks .Name . }} # {{ .Name }} {{- else }} # (none) {{- end }} {{- else }} -# /!\ WARNING: Failed to find the Docker container running docker-gen. All -# upstream (backend) application containers will appear to be -# unreachable. Try removing the -only-exposed and -only-published -# arguments to docker-gen if you pass either of those. See -# . +# /!\ WARNING: Failed to find the Docker container labeled "{{ $globals.config.nginx_container_label }}" or the one running docker-gen. +# All upstream (backend) application containers will appear to be unreachable. +# Try removing the -only-exposed and -only-published arguments to docker-gen if you pass either of those. +# See https://github.com/nginx-proxy/docker-gen/issues/458. {{- end }} {{- /* @@ -105,21 +100,11 @@ {{- $ipv4 = "127.0.0.1" }} {{- continue }} {{- end }} - {{- if $.globals.NginxContainer }} - {{- range sortObjectsByKeysAsc $.globals.NginxContainer.Networks "Name" }} - {{- if and . .Gateway (not .Internal) }} - # container is in host network mode, using {{ .Name }} gateway IP - {{- $ipv4 = .Gateway }} - {{- break }} - {{- end }} - {{- end }} - {{- else }} - {{- range sortObjectsByKeysAsc $.globals.CurrentContainer.Networks "Name" }} - {{- if and . .Gateway (not .Internal) }} + {{- range sortObjectsByKeysAsc $.globals.NetworkContainer.Networks "Name" }} + {{- if and . .Gateway (not .Internal) }} # container is in host network mode, using {{ .Name }} gateway IP - {{- $ipv4 = .Gateway }} - {{- break }} - {{- end }} + {{- $ipv4 = .Gateway }} + {{- break }} {{- end }} {{- end }} {{- if $ipv4 }} From bfabd460548816244da650d29f76ff80bc069a16 Mon Sep 17 00:00:00 2001 From: Nicolas Duchon Date: Sun, 11 May 2025 12:40:11 +0200 Subject: [PATCH 07/54] test: network segregation w/ internal Docker network --- ...test_dockergen_network_segregation.base.yml | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/test/test_dockergen/test_dockergen_network_segregation.base.yml b/test/test_dockergen/test_dockergen_network_segregation.base.yml index 8040b479c..c0e85ac0b 100644 --- a/test/test_dockergen/test_dockergen_network_segregation.base.yml +++ b/test/test_dockergen/test_dockergen_network_segregation.base.yml @@ -1,3 +1,12 @@ +networks: + proxy: + private: + internal: true + +volumes: + nginx_conf: + + services: nginx-proxy-nginx: image: nginx @@ -20,7 +29,7 @@ services: - ../../nginx.tmpl:/etc/docker-gen/templates/nginx.tmpl - nginx_conf:/etc/nginx/conf.d networks: - - internal + - private web: image: web @@ -32,10 +41,3 @@ services: VIRTUAL_HOST: whoami2.nginx.container.docker networks: - proxy - -networks: - proxy: - internal: - -volumes: - nginx_conf: From db51154175d7f3b3b8d603634f81446ea46abca7 Mon Sep 17 00:00:00 2001 From: Nicolas Duchon Date: Sun, 11 May 2025 12:46:45 +0200 Subject: [PATCH 08/54] test: custom nginx network segregation label --- ..._network_segregation-custom-label.base.yml | 45 +++++++++++++++++++ ...kergen_network_segregation-custom-label.py | 27 +++++++++++ 2 files changed, 72 insertions(+) create mode 100644 test/test_dockergen/test_dockergen_network_segregation-custom-label.base.yml create mode 100644 test/test_dockergen/test_dockergen_network_segregation-custom-label.py diff --git a/test/test_dockergen/test_dockergen_network_segregation-custom-label.base.yml b/test/test_dockergen/test_dockergen_network_segregation-custom-label.base.yml new file mode 100644 index 000000000..1429e9f96 --- /dev/null +++ b/test/test_dockergen/test_dockergen_network_segregation-custom-label.base.yml @@ -0,0 +1,45 @@ +networks: + proxy: + private: + internal: true + +volumes: + nginx_conf: + + +services: + nginx-proxy-nginx: + image: nginx + container_name: nginx + volumes: + - nginx_conf:/etc/nginx/conf.d:ro + ports: + - "80:80" + - "443:443" + networks: + - proxy + labels: + - "com.github.nginx-proxy.nginx-proxy.foobarbuzz" + + nginx-proxy-dockergen: + image: nginxproxy/docker-gen + command: -notify-sighup nginx -watch /etc/docker-gen/templates/nginx.tmpl /etc/nginx/conf.d/default.conf + volumes: + - /var/run/docker.sock:/tmp/docker.sock:ro + - ../../nginx.tmpl:/etc/docker-gen/templates/nginx.tmpl + - nginx_conf:/etc/nginx/conf.d + environment: + NGINX_CONTAINER_LABEL: "com.github.nginx-proxy.nginx-proxy.foobarbuzz" + networks: + - private + + web: + image: web + container_name: whoami2 + expose: + - "80" + environment: + WEB_PORTS: "80" + VIRTUAL_HOST: whoami2.nginx.container.docker + networks: + - proxy diff --git a/test/test_dockergen/test_dockergen_network_segregation-custom-label.py b/test/test_dockergen/test_dockergen_network_segregation-custom-label.py new file mode 100644 index 000000000..ad487d99f --- /dev/null +++ b/test/test_dockergen/test_dockergen_network_segregation-custom-label.py @@ -0,0 +1,27 @@ +import docker +import pytest +from packaging.version import Version + + +raw_version = docker.from_env().version()["Version"] +pytestmark = pytest.mark.skipif( + Version(raw_version) < Version("1.13"), + reason="Docker compose syntax v3 requires docker engine v1.13 or later (got {raw_version})" +) + + +def test_unknown_virtual_host_is_503(docker_compose, nginxproxy): + r = nginxproxy.get("http://unknown.nginx.container.docker/") + assert r.status_code == 503 + + +def test_forwards_to_whoami(docker_compose, nginxproxy): + r = nginxproxy.get("http://whoami2.nginx.container.docker/") + assert r.status_code == 200 + whoami_container = docker_compose.containers.get("whoami2") + assert r.text == f"I'm {whoami_container.id[:12]}\n" + + +if __name__ == "__main__": + import doctest + doctest.testmod() From eb9f0f31d702381919bec10d39dbc7f6a3f7b80e Mon Sep 17 00:00:00 2001 From: Nicolas Duchon Date: Sun, 11 May 2025 13:05:12 +0200 Subject: [PATCH 09/54] docs: add NGINX_CONTAINER_LABEL to docs --- docs/README.md | 50 ++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 42 insertions(+), 8 deletions(-) diff --git a/docs/README.md b/docs/README.md index d99d8be5b..95d9b2756 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1136,31 +1136,65 @@ I'm 5b129ab83266 To run nginx proxy as a separate container you'll need to have [nginx.tmpl](https://github.com/nginx-proxy/nginx-proxy/blob/main/nginx.tmpl) on your host system. -First start nginx with a volume: +First start nginx with a volume mounted to `/etc/nginx/conf.d`: ```console -docker run -d -p 80:80 --name nginx -v /tmp/nginx:/etc/nginx/conf.d -t nginx +docker run --detach \ + --name nginx \ + --publish 80:80 \ + --volume /tmp/nginx:/etc/nginx/conf.d \ + nginx ``` Then start the docker-gen container with the shared volume and template: ```console -docker run --volumes-from nginx \ - -v /var/run/docker.sock:/tmp/docker.sock:ro \ - -v $(pwd):/etc/docker-gen/templates \ - -t nginxproxy/docker-gen -notify-sighup nginx -watch /etc/docker-gen/templates/nginx.tmpl /etc/nginx/conf.d/default.conf +docker run --detach \ + --name docker-gen \ + --volumes-from nginx \ + --volume /var/run/docker.sock:/tmp/docker.sock:ro \ + --volume $(pwd):/etc/docker-gen/templates \ + nginxproxy/docker-gen -notify-sighup nginx -watch /etc/docker-gen/templates/nginx.tmpl /etc/nginx/conf.d/default.conf ``` Finally, start your containers with `VIRTUAL_HOST` environment variables. ```console -docker run -e VIRTUAL_HOST=foo.bar.com ... +docker run --env VIRTUAL_HOST=foo.bar.com ... ``` +### Network segregation + To allow for network segregation of the nginx and docker-gen containers, the label `com.github.nginx-proxy.nginx-proxy.nginx` must be applied to the nginx container, otherwise it is assumed that nginx and docker-gen share the same network: ```console -docker run -d -p 80:80 --name nginx -l "com.github.nginx-proxy.nginx-proxy.nginx" -v /tmp/nginx:/etc/nginx/conf.d -t nginx +docker run --detach \ + --name nginx \ + --publish 80:80 \ + --label "com.github.nginx-proxy.nginx-proxy.nginx" \ + --volume /tmp/nginx:/etc/nginx/conf.d \ + nginx +``` + +Network segregation make it possible to run the docker-gen container in an [internal network](https://docs.docker.com/reference/cli/docker/network/create/#internal), unreachable from the outside. + +You can also customise the label being used by docker-gen to find the nginx container with the `NGINX_CONTAINER_LABEL`environment variable (on the docker-gen container): + +```console +docker run --detach \ + --name docker-gen \ + --volumes-from nginx \ + --volume /var/run/docker.sock:/tmp/docker.sock:ro \ + --volume $(pwd):/etc/docker-gen/templates \ + --env "NGINX_CONTAINER_LABEL=com.github.foobarbuzz" \ + nginxproxy/docker-gen -notify-sighup nginx -watch /etc/docker-gen/templates/nginx.tmpl /etc/nginx/conf.d/default.conf + +docker run --detach \ + --name nginx \ + --publish 80:80 \ + --label "com.github.foobarbuzz" \ + --volume "/tmp/nginx:/etc/nginx/conf.d" \ + nginx ``` ⬆️ [back to table of contents](#table-of-contents) From 7dd39e69033d8cf83232b25c2b29adce6e37e747 Mon Sep 17 00:00:00 2001 From: Nicolas Duchon Date: Sun, 18 May 2025 20:32:29 +0200 Subject: [PATCH 10/54] fix(build): correctly grab docker-gen version from dockerfile (#2622) --- .github/workflows/build-publish-dispatch.yml | 2 +- .github/workflows/build-publish.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-publish-dispatch.yml b/.github/workflows/build-publish-dispatch.yml index 01a7b379b..145e4dc1e 100644 --- a/.github/workflows/build-publish-dispatch.yml +++ b/.github/workflows/build-publish-dispatch.yml @@ -27,7 +27,7 @@ jobs: - name: Retrieve docker-gen version id: docker-gen_version - run: sed -n -e 's;^FROM nginxproxy/docker-gen:\([0-9.]*\).*;VERSION=\1;p' Dockerfile.${{ matrix.base }} >> "$GITHUB_OUTPUT" + run: sed -n -e 's;^FROM docker.io/nginxproxy/docker-gen:\([0-9.]*\).*;VERSION=\1;p' Dockerfile.${{ matrix.base }} >> "$GITHUB_OUTPUT" - name: Get Docker tags id: docker_meta diff --git a/.github/workflows/build-publish.yml b/.github/workflows/build-publish.yml index 70798e13f..defbaf936 100644 --- a/.github/workflows/build-publish.yml +++ b/.github/workflows/build-publish.yml @@ -38,7 +38,7 @@ jobs: - name: Retrieve docker-gen version id: docker-gen_version - run: sed -n -e 's;^FROM nginxproxy/docker-gen:\([0-9.]*\).*;VERSION=\1;p' Dockerfile.${{ matrix.base }} >> "$GITHUB_OUTPUT" + run: sed -n -e 's;^FROM docker.io/nginxproxy/docker-gen:\([0-9.]*\).*;VERSION=\1;p' Dockerfile.${{ matrix.base }} >> "$GITHUB_OUTPUT" - name: Get Docker tags id: docker_meta From a1372d2a9b33e7f55e09f62b23f2db21a3d6e12f Mon Sep 17 00:00:00 2001 From: Nicolas Duchon Date: Sun, 18 May 2025 20:39:59 +0200 Subject: [PATCH 11/54] docs: standadize docker cli invocation style --- docs/README.md | 153 ++++++++++++++++++++++++++++++------------------- 1 file changed, 94 insertions(+), 59 deletions(-) diff --git a/docs/README.md b/docs/README.md index 9993ecab0..ca1d56c71 100644 --- a/docs/README.md +++ b/docs/README.md @@ -33,13 +33,19 @@ You can also use wildcards at the beginning and the end of host name, like `*.ba To set the default host for nginx use the env var `DEFAULT_HOST=foo.bar.com` for example ```console -docker run -d -p 80:80 -e DEFAULT_HOST=foo.bar.com -v /var/run/docker.sock:/tmp/docker.sock:ro nginxproxy/nginx-proxy +docker run --detach \ + --publish 80:80 \ + --env DEFAULT_HOST=foo.bar.com \ + --volume /var/run/docker.sock:/tmp/docker.sock:ro \ + nginxproxy/nginx-proxy ``` nginx-proxy will then redirect all requests to a container where `VIRTUAL_HOST` is set to `DEFAULT_HOST`, if they don't match any (other) `VIRTUAL_HOST`. Using the example above requests without matching `VIRTUAL_HOST` will be redirected to a plain nginx instance after running the following command: ```console -docker run -d -e VIRTUAL_HOST=foo.bar.com nginx +docker run --detach \ + --env VIRTUAL_HOST=foo.bar.com \ + nginx ``` ### Virtual Ports @@ -179,7 +185,12 @@ If the application runs natively on this sub-path or has a setting to do so, `VI If the requests are expected to not contain a sub-path and the generated links contain the sub-path, `VIRTUAL_DEST=/` should be used. ```console -$ docker run -d -e VIRTUAL_HOST=example.tld -e VIRTUAL_PATH=/app1/ -e VIRTUAL_DEST=/ --name app1 app +docker run --detach \ + --name app1 \ + --env VIRTUAL_HOST=example.tld \ + --env VIRTUAL_PATH=/app1/ \ + --env VIRTUAL_DEST=/ \ + app ``` In this example, the incoming request `http://example.tld/app1/foo` will be proxied as `http://app1/foo` instead of `http://app1/app1/foo`. @@ -221,7 +232,13 @@ Nginx variables such as `$scheme`, `$host`, and `$request_uri` can be used. Howe If you want to use `nginx-proxy` with different external ports that the default ones of `80` for `HTTP` traffic and `443` for `HTTPS` traffic, you'll have to use the environment variable(s) `HTTP_PORT` and/or `HTTPS_PORT` in addition to the changes to the Docker port mapping. If you change the `HTTPS` port, the redirect for `HTTPS` traffic will also be configured to redirect to the custom port. Typical usage, here with the custom ports `1080` and `10443`: ```console -docker run -d -p 1080:1080 -p 10443:10443 -e HTTP_PORT=1080 -e HTTPS_PORT=10443 -v /var/run/docker.sock:/tmp/docker.sock:ro nginxproxy/nginx-proxy +docker run --detach \ + --publish 1080:1080 \ + --publish 10443:10443 \ + --env HTTP_PORT=1080 \ + --env HTTPS_PORT=10443 \ + --volume /var/run/docker.sock:/tmp/docker.sock:ro \ + nginxproxy/nginx-proxy ``` ### Multiple Networks @@ -231,8 +248,12 @@ With the addition of [overlay networking](https://docs.docker.com/engine/usergui If you want your `nginx-proxy` container to be attached to a different network, you must pass the `--net=my-network` option in your `docker create` or `docker run` command. At the time of this writing, only a single network can be specified at container creation time. To attach to other networks, you can use the `docker network connect` command after your container is created: ```console -docker run -d -p 80:80 -v /var/run/docker.sock:/tmp/docker.sock:ro \ - --name my-nginx-proxy --net my-network nginxproxy/nginx-proxy +docker run --detach \ + --name my-nginx-proxy \ + --publish 80:80 \ + --volume /var/run/docker.sock:/tmp/docker.sock:ro \ + --net my-network \ + nginxproxy/nginx-proxy docker network connect my-other-network my-nginx-proxy ``` @@ -336,10 +357,12 @@ In order to be able to secure your virtual host, you have to create a file named `/etc/nginx/htpasswd/`. Example: `/etc/nginx/htpasswd/app.example.com`. ```console -docker run -d -p 80:80 -p 443:443 \ - -v /path/to/htpasswd:/etc/nginx/htpasswd \ - -v /path/to/certs:/etc/nginx/certs \ - -v /var/run/docker.sock:/tmp/docker.sock:ro \ +docker run --detach \ + --publish 80:80 \ + --publish 443:443 \ + --volume /path/to/htpasswd:/etc/nginx/htpasswd \ + --volume /path/to/certs:/etc/nginx/certs \ + --volume /var/run/docker.sock:/tmp/docker.sock:ro \ nginxproxy/nginx-proxy ``` @@ -399,10 +422,10 @@ To remove colors from the container log output, set the [`NO_COLOR` environment ```console docker run --detach \ - --publish 80:80 \ - --env NO_COLOR=1 \ - --volume /var/run/docker.sock:/tmp/docker.sock:ro \ - nginxproxy/nginx-proxy + --publish 80:80 \ + --env NO_COLOR=1 \ + --volume /var/run/docker.sock:/tmp/docker.sock:ro \ + nginxproxy/nginx-proxy ``` ⬆️ [back to table of contents](#table-of-contents) @@ -414,7 +437,12 @@ SSL is supported using single host, wildcard and SAN certificates using naming c To enable SSL: ```console -docker run -d -p 80:80 -p 443:443 -v /path/to/certs:/etc/nginx/certs -v /var/run/docker.sock:/tmp/docker.sock:ro nginxproxy/nginx-proxy +docker run --detach \ + --publish 80:80 \ + --publish 443:443 \ + --volume /path/to/certs:/etc/nginx/certs \ + --volume /var/run/docker.sock:/tmp/docker.sock:ro \ + nginxproxy/nginx-proxy ``` The contents of `/path/to/certs` should contain the certificates and private keys for any virtual hosts in use. The certificate and keys should be named after the virtual host with a `.crt` and `.key` extension. For example, a container with `VIRTUAL_HOST=foo.bar.com` should have a `foo.bar.com.crt` and `foo.bar.com.key` file in the certs directory. @@ -445,7 +473,7 @@ In the separate container setup, no pre-generated key will be available and neit Set `DHPARAM_SKIP` environment variable to `true` to disable using default Diffie-Hellman parameters. The default value is `false`. ```console -docker run -e DHPARAM_SKIP=true .... +docker run --env DHPARAM_SKIP=true .... ``` ### Wildcard Certificates @@ -661,7 +689,11 @@ IPv4 and IPv6 are never both used at the same time on containers that use both I By default the nginx-proxy container will only listen on IPv4. To enable listening on IPv6 too, set the `ENABLE_IPV6` environment variable to `true`: ```console -docker run -d -p 80:80 -e ENABLE_IPV6=true -v /var/run/docker.sock:/tmp/docker.sock:ro nginxproxy/nginx-proxy +docker run --detach \ + --publish 80:80 \ + --env ENABLE_IPV6=true \ + --volume /var/run/docker.sock:/tmp/docker.sock:ro \ + nginxproxy/nginx-proxy ``` ### Scoped IPv6 Resolvers @@ -694,8 +726,11 @@ More reading on the potential TCP head-of-line blocking issue with HTTP/2: [HTTP HTTP/3 use the QUIC protocol over UDP (unlike HTTP/1.1 and HTTP/2 which work over TCP), so if you want to use HTTP/3 you'll have to explicitely publish the 443/udp port of the proxy in addition to the 443/tcp port: ```console -docker run -d -p 80:80 -p 443:443/tcp -p 443:443/udp \ - -v /var/run/docker.sock:/tmp/docker.sock:ro \ +docker run --detach \ + --publish 80:80 \ + --publish 443:443/tcp \ + --publish 443:443/udp \ + --volume /var/run/docker.sock:/tmp/docker.sock:ro \ nginxproxy/nginx-proxy ``` @@ -788,12 +823,12 @@ client_max_body_size 100m; ```console docker run --detach \ - --name nginx-proxy \ - --publish 80:80 \ - --publish 443:443 \ - --volume /var/run/docker.sock:/tmp/docker.sock:ro \ - --volume /path/to/my_proxy.conf:/etc/nginx/conf.d/my_proxy.conf:ro \ - nginxproxy/nginx-proxy + --name nginx-proxy \ + --publish 80:80 \ + --publish 443:443 \ + --volume /var/run/docker.sock:/tmp/docker.sock:ro \ + --volume /path/to/my_proxy.conf:/etc/nginx/conf.d/my_proxy.conf:ro \ + nginxproxy/nginx-proxy ``` @@ -842,12 +877,12 @@ client_max_body_size 100m; ```console docker run --detach \ - --name nginx-proxy \ - --publish 80:80 \ - --publish 443:443 \ - --volume /var/run/docker.sock:/tmp/docker.sock:ro \ - --volume /path/to/custom-vhost-config.conf:/etc/nginx/vhost.d/app.example.com:ro \ - nginxproxy/nginx-proxy + --name nginx-proxy \ + --publish 80:80 \ + --publish 443:443 \ + --volume /var/run/docker.sock:/tmp/docker.sock:ro \ + --volume /path/to/custom-vhost-config.conf:/etc/nginx/vhost.d/app.example.com:ro \ + nginxproxy/nginx-proxy ``` @@ -877,13 +912,13 @@ If you are using multiple hostnames for a single container (e.g. `VIRTUAL_HOST=e ```console docker run --detach \ - --name nginx-proxy \ - --publish 80:80 \ - --publish 443:443 \ - --volume /path/to/custom-vhost-config.conf:/etc/nginx/vhost.d/example.com:ro \ - --volume /path/to/custom-vhost-config.conf:/etc/nginx/vhost.d/www.example.com:ro \ - --volume /var/run/docker.sock:/tmp/docker.sock:ro \ - nginxproxy/nginx-proxy + --name nginx-proxy \ + --publish 80:80 \ + --publish 443:443 \ + --volume /path/to/custom-vhost-config.conf:/etc/nginx/vhost.d/example.com:ro \ + --volume /path/to/custom-vhost-config.conf:/etc/nginx/vhost.d/www.example.com:ro \ + --volume /var/run/docker.sock:/tmp/docker.sock:ro \ + nginxproxy/nginx-proxy ``` @@ -933,12 +968,12 @@ proxy_cache_valid 404 1m; ```console docker run --detach \ - --name nginx-proxy \ - --publish 80:80 \ - --publish 443:443 \ - --volume /var/run/docker.sock:/tmp/docker.sock:ro \ - --volume /path/to/custom-vhost-location-config.conf:/etc/nginx/vhost.d/app.example.com_location:ro \ - nginxproxy/nginx-proxy + --name nginx-proxy \ + --publish 80:80 \ + --publish 443:443 \ + --volume /var/run/docker.sock:/tmp/docker.sock:ro \ + --volume /path/to/custom-vhost-location-config.conf:/etc/nginx/vhost.d/app.example.com_location:ro \ + nginxproxy/nginx-proxy ``` @@ -968,13 +1003,13 @@ If you are using multiple hostnames for a single container (e.g. `VIRTUAL_HOST=e ```console docker run --detach \ - --name nginx-proxy \ - --publish 80:80 \ - --publish 443:443 \ - --volume /var/run/docker.sock:/tmp/docker.sock:ro \ - --volume /path/to/custom-vhost-location-config.conf:/etc/nginx/vhost.d/example.com_location:ro \ - --volume /path/to/custom-vhost-location-config.conf:/etc/nginx/vhost.d/www.example.com_location:ro \ - nginxproxy/nginx-proxy + --name nginx-proxy \ + --publish 80:80 \ + --publish 443:443 \ + --volume /var/run/docker.sock:/tmp/docker.sock:ro \ + --volume /path/to/custom-vhost-location-config.conf:/etc/nginx/vhost.d/example.com_location:ro \ + --volume /path/to/custom-vhost-location-config.conf:/etc/nginx/vhost.d/www.example.com_location:ro \ + nginxproxy/nginx-proxy ``` @@ -1245,14 +1280,14 @@ Pay attention to the `upstream` definition blocks, which should look like this: ```nginx # foo.example.com upstream foo.example.com { - ## Can be connected with "my_network" network - # Exposed ports: [{ tcp } { tcp } ...] - # Default virtual port: - # VIRTUAL_PORT: - # foo - server 172.18.0.9:; - # Fallback entry - server 127.0.0.1 down; + ## Can be connected with "my_network" network + # Exposed ports: [{ tcp } { tcp } ...] + # Default virtual port: + # VIRTUAL_PORT: + # foo + server 172.18.0.9:; + # Fallback entry + server 127.0.0.1 down; } ``` From 72210064f0b89d40980f0d5d89b87910552ea349 Mon Sep 17 00:00:00 2001 From: Povilas Kanapickas Date: Fri, 14 Mar 2025 01:28:11 +0200 Subject: [PATCH 12/54] test: add test for unknown domain responses to acme challenge --- .../test_acme-http-challenge-location-disabled.py | 7 +++++++ ...test_acme-http-challenge-location-enabled-is-default.py | 7 +++++++ .../test_acme-http-challenge-location-legacy.py | 7 +++++++ 3 files changed, 21 insertions(+) diff --git a/test/test_acme-http-challenge-location/test_acme-http-challenge-location-disabled.py b/test/test_acme-http-challenge-location/test_acme-http-challenge-location-disabled.py index ae12fa64a..5588167b4 100644 --- a/test/test_acme-http-challenge-location/test_acme-http-challenge-location-disabled.py +++ b/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 diff --git a/test/test_acme-http-challenge-location/test_acme-http-challenge-location-enabled-is-default.py b/test/test_acme-http-challenge-location/test_acme-http-challenge-location-enabled-is-default.py index 88cb07d7e..80eea97e8 100644 --- a/test/test_acme-http-challenge-location/test_acme-http-challenge-location-enabled-is-default.py +++ b/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 diff --git a/test/test_acme-http-challenge-location/test_acme-http-challenge-location-legacy.py b/test/test_acme-http-challenge-location/test_acme-http-challenge-location-legacy.py index ed9f25a63..495ad834a 100644 --- a/test/test_acme-http-challenge-location/test_acme-http-challenge-location-legacy.py +++ b/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 From 4c8f22ebcc4c3bb7a2d192991da2fc1d2abb40fe Mon Sep 17 00:00:00 2001 From: Povilas Kanapickas Date: Fri, 14 Mar 2025 01:28:12 +0200 Subject: [PATCH 13/54] feat: support ACME challenges for unknown virtual hosts Currently any ACME challenge for unknown virtual host returns 503. This is inconvenient because if the user does not use wildcard certificates, then the user must match the configuration of certificate renewal script to what virtual hosts are enabled at the time. This must be done automatically, because due to short certificate lifetime the renewal script runs automatically. Additionally, enabling a previously disabled virtual host forces certificate renewal. Accordingly, it's worthwhile supporting unknown virtual hosts for the purposes of passing ACME challenges. This is done by introducing a global ACME_HTTP_CHALLENGE_ACCEPT_UNKNOWN_HOST variable to control this. --- docs/README.md | 2 + nginx.tmpl | 11 +++++ ...-challenge-location-accept-unknown-host.py | 34 ++++++++++++++++ ...challenge-location-accept-unknown-host.yml | 40 +++++++++++++++++++ 4 files changed, 87 insertions(+) create mode 100644 test/test_acme-http-challenge-location/test_acme-http-challenge-location-accept-unknown-host.py create mode 100644 test/test_acme-http-challenge-location/test_acme-http-challenge-location-accept-unknown-host.yml diff --git a/docs/README.md b/docs/README.md index ca1d56c71..4e6788dc2 100644 --- a/docs/README.md +++ b/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. diff --git a/nginx.tmpl b/nginx.tmpl index dd1c4448d..afc9f166c 100644 --- a/nginx.tmpl +++ b/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 { diff --git a/test/test_acme-http-challenge-location/test_acme-http-challenge-location-accept-unknown-host.py b/test/test_acme-http-challenge-location/test_acme-http-challenge-location-accept-unknown-host.py new file mode 100644 index 000000000..8643b4bf9 --- /dev/null +++ b/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 diff --git a/test/test_acme-http-challenge-location/test_acme-http-challenge-location-accept-unknown-host.yml b/test/test_acme-http-challenge-location/test_acme-http-challenge-location-accept-unknown-host.yml new file mode 100644 index 000000000..25965e493 --- /dev/null +++ b/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" From 4af3be397af689aba32cd03e3f6e37fefb5dc0a9 Mon Sep 17 00:00:00 2001 From: Nicolas Duchon Date: Fri, 23 May 2025 20:55:29 +0200 Subject: [PATCH 14/54] test: wait for docker-gen container to be ready too --- test/conftest.py | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/test/conftest.py b/test/conftest.py index 15d18bc32..83f5865e3 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -348,16 +348,26 @@ def docker_compose_down(compose_files: List[str], project_name: str): def wait_for_nginxproxy_to_be_ready(): """ - If one (and only one) container started from image nginxproxy/nginx-proxy:test is found, - wait for its log to contain substring "Watching docker events" + If one (and only one) container started from image nginxproxy/nginx-proxy:test + or nginxproxy/docker-gen:latest is found, wait for its log to contain the substring "Watching docker events" """ - containers = docker_client.containers.list(filters={"ancestor": "nginxproxy/nginx-proxy:test"}) - if len(containers) != 1: + nginx_proxy_containers = docker_client.containers.list(filters={"ancestor": "nginxproxy/nginx-proxy:test"}) + docker_gen_containers = docker_client.containers.list(filters={"ancestor": "nginxproxy/docker-gen:latest"}) + + container_name = "nginx-proxy" + + if len(nginx_proxy_containers) == 1: + container = nginx_proxy_containers.pop() + elif len(docker_gen_containers) == 1: + container = docker_gen_containers.pop() + container_name = "docker-gen" + else: + logging.debug("Either more than one or no nginx-proxy or docker-gen container found, skipping container readiness check") return - container = containers[0] + for line in container.logs(stream=True): if b"Watching docker events" in line: - logging.debug("nginx-proxy ready") + logging.debug(f"{container_name} ready") break From 0479cb02a2d6fec892ebd56b67706aace7b1c0e4 Mon Sep 17 00:00:00 2001 From: Nicolas Duchon Date: Fri, 23 May 2025 21:33:33 +0200 Subject: [PATCH 15/54] test: fix docker-gen flaky tests --- test/conftest.py | 34 +++++++++++++++---- test/test_dockergen/test_dockergen.base.yml | 2 +- test/test_dockergen/test_dockergen.py | 4 +-- ..._network_segregation-custom-label.base.yml | 2 +- ...kergen_network_segregation-custom-label.py | 4 +-- ...est_dockergen_network_segregation.base.yml | 2 +- .../test_dockergen_network_segregation.py | 4 +-- 7 files changed, 37 insertions(+), 15 deletions(-) diff --git a/test/conftest.py b/test/conftest.py index 83f5865e3..c0a060b43 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -199,26 +199,48 @@ def container_ipv6(container: Container) -> str: def nginx_proxy_dns_resolver(domain_name: str) -> Optional[str]: """ if "nginx-proxy" if found in host, return the ip address of the docker container - issued from the docker image nginxproxy/nginx-proxy:test. + issued from the docker image nginxproxy/nginx-proxy:test or nginx:latest. :return: IP or None """ log = logging.getLogger('DNS') log.debug(f"nginx_proxy_dns_resolver({domain_name!r})") + if 'nginx-proxy' in domain_name: nginxproxy_containers = docker_client.containers.list(filters={"status": "running", "ancestor": "nginxproxy/nginx-proxy:test"}) - if len(nginxproxy_containers) == 0: - log.warning(f"no container found from image nginxproxy/nginx-proxy:test while resolving {domain_name!r}") + nginx_containers = docker_client.containers.list(filters={"status": "running", "ancestor": "nginx:latest"}) + + if len(nginxproxy_containers) == 0 and len(nginx_containers) == 0: + log.warning(f"no runninf container found from image nginxproxy/nginx-proxy:test or nginx:latest while resolving {domain_name!r}") + exited_nginxproxy_containers = docker_client.containers.list(filters={"status": "exited", "ancestor": "nginxproxy/nginx-proxy:test"}) + exited_nginx_containers = docker_client.containers.list(filters={"status": "exited", "ancestor": "nginx:latest"}) + if len(exited_nginxproxy_containers) > 0: exited_nginxproxy_container_logs = exited_nginxproxy_containers[0].logs() log.warning(f"nginxproxy/nginx-proxy:test container might have exited unexpectedly. Container logs: " + "\n" + exited_nginxproxy_container_logs.decode()) + if len(exited_nginx_containers) > 0: + exited_nginx_container_logs = exited_nginx_containers[0].logs() + log.warning(f"nginx:latest container might have exited unexpectedly. Container logs: " + "\n" + exited_nginx_container_logs.decode()) + return None - nginxproxy_container = nginxproxy_containers[0] - ip = container_ip(nginxproxy_container) - log.info(f"resolving domain name {domain_name!r} as IP address {ip} of nginx-proxy container {nginxproxy_container.name}") + + container = None + container_type = "nginx-proxy" + + if len(nginxproxy_containers) >= 1: + container = nginxproxy_containers[0] + if len(nginx_containers) >= 1: + container = nginx_containers[0] + container_type = "nginx" + + ip = container_ip(container) + log.info(f"resolving domain name {domain_name!r} as IP address {ip} of {container_type} container {container.name}") return ip + return None + + def docker_container_dns_resolver(domain_name: str) -> Optional[str]: """ if domain name is of the form "XXX.container.docker" or "anything.XXX.container.docker", diff --git a/test/test_dockergen/test_dockergen.base.yml b/test/test_dockergen/test_dockergen.base.yml index 69f6c85a2..3012abefa 100644 --- a/test/test_dockergen/test_dockergen.base.yml +++ b/test/test_dockergen/test_dockergen.base.yml @@ -27,4 +27,4 @@ services: - "80" environment: WEB_PORTS: "80" - VIRTUAL_HOST: whoami.nginx.container.docker + VIRTUAL_HOST: whoami.nginx-proxy.tld diff --git a/test/test_dockergen/test_dockergen.py b/test/test_dockergen/test_dockergen.py index 6d419cd37..8784ed5f7 100644 --- a/test/test_dockergen/test_dockergen.py +++ b/test/test_dockergen/test_dockergen.py @@ -11,12 +11,12 @@ def test_unknown_virtual_host_is_503(docker_compose, nginxproxy): - r = nginxproxy.get("http://unknown.nginx.container.docker/") + r = nginxproxy.get("http://unknown.nginx-proxy.tld/") assert r.status_code == 503 def test_forwards_to_whoami(docker_compose, nginxproxy): - r = nginxproxy.get("http://whoami.nginx.container.docker/") + r = nginxproxy.get("http://whoami.nginx-proxy.tld/") assert r.status_code == 200 whoami_container = docker_compose.containers.get("whoami") assert r.text == f"I'm {whoami_container.id[:12]}\n" diff --git a/test/test_dockergen/test_dockergen_network_segregation-custom-label.base.yml b/test/test_dockergen/test_dockergen_network_segregation-custom-label.base.yml index 1429e9f96..bc79d2dba 100644 --- a/test/test_dockergen/test_dockergen_network_segregation-custom-label.base.yml +++ b/test/test_dockergen/test_dockergen_network_segregation-custom-label.base.yml @@ -40,6 +40,6 @@ services: - "80" environment: WEB_PORTS: "80" - VIRTUAL_HOST: whoami2.nginx.container.docker + VIRTUAL_HOST: whoami2.nginx-proxy.tld networks: - proxy diff --git a/test/test_dockergen/test_dockergen_network_segregation-custom-label.py b/test/test_dockergen/test_dockergen_network_segregation-custom-label.py index ad487d99f..ed5c20bd6 100644 --- a/test/test_dockergen/test_dockergen_network_segregation-custom-label.py +++ b/test/test_dockergen/test_dockergen_network_segregation-custom-label.py @@ -11,12 +11,12 @@ def test_unknown_virtual_host_is_503(docker_compose, nginxproxy): - r = nginxproxy.get("http://unknown.nginx.container.docker/") + r = nginxproxy.get("http://unknown.nginx-proxy.tld/") assert r.status_code == 503 def test_forwards_to_whoami(docker_compose, nginxproxy): - r = nginxproxy.get("http://whoami2.nginx.container.docker/") + r = nginxproxy.get("http://whoami2.nginx-proxy.tld/") assert r.status_code == 200 whoami_container = docker_compose.containers.get("whoami2") assert r.text == f"I'm {whoami_container.id[:12]}\n" diff --git a/test/test_dockergen/test_dockergen_network_segregation.base.yml b/test/test_dockergen/test_dockergen_network_segregation.base.yml index c0e85ac0b..10f61a76b 100644 --- a/test/test_dockergen/test_dockergen_network_segregation.base.yml +++ b/test/test_dockergen/test_dockergen_network_segregation.base.yml @@ -38,6 +38,6 @@ services: - "80" environment: WEB_PORTS: "80" - VIRTUAL_HOST: whoami2.nginx.container.docker + VIRTUAL_HOST: whoami2.nginx-proxy.tld networks: - proxy diff --git a/test/test_dockergen/test_dockergen_network_segregation.py b/test/test_dockergen/test_dockergen_network_segregation.py index ad487d99f..ed5c20bd6 100644 --- a/test/test_dockergen/test_dockergen_network_segregation.py +++ b/test/test_dockergen/test_dockergen_network_segregation.py @@ -11,12 +11,12 @@ def test_unknown_virtual_host_is_503(docker_compose, nginxproxy): - r = nginxproxy.get("http://unknown.nginx.container.docker/") + r = nginxproxy.get("http://unknown.nginx-proxy.tld/") assert r.status_code == 503 def test_forwards_to_whoami(docker_compose, nginxproxy): - r = nginxproxy.get("http://whoami2.nginx.container.docker/") + r = nginxproxy.get("http://whoami2.nginx-proxy.tld/") assert r.status_code == 200 whoami_container = docker_compose.containers.get("whoami2") assert r.text == f"I'm {whoami_container.id[:12]}\n" From f4c297995d102e7444f803b3d67d40eb90932524 Mon Sep 17 00:00:00 2001 From: Nicolas Duchon Date: Sun, 18 May 2025 21:53:32 +0200 Subject: [PATCH 16/54] docs: config summary --- docs/README.md | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/docs/README.md b/docs/README.md index 4e6788dc2..f14bc2be7 100644 --- a/docs/README.md +++ b/docs/README.md @@ -15,6 +15,7 @@ - [Unhashed vs SHA1 upstream names](#unhashed-vs-sha1-upstream-names) - [Separate Containers](#separate-containers) - [Docker Compose](#docker-compose) +- [Configuration Summary](#configuration-summary) - [Troubleshooting](#troubleshooting) - [Contributing](#contributing) @@ -1269,6 +1270,74 @@ I'm 5b129ab83266 ⬆️ [back to table of contents](#table-of-contents) +## Configuration summary + +This section summarize the configurations available on the proxy and proxied container. + +### Proxy container + +Configuration available either on the nginx-proxy container, or the docker-gen container in a [separate containers setup](#separate-containers): + +| Environment Variable | Default Value | +|---------------------|---------------| +| [`ACME_HTTP_CHALLENGE_LOCATION`](#ssl-support-using-an-acme-ca) | `true` | +| [`ACME_HTTP_CHALLENGE_ACCEPT_UNKNOWN_HOST`](#ssl-support-using-an-acme-ca) | `false` | +| [`DEBUG_ENDPOINT`](#debug-endpoint) | `false` | +| [`DEFAULT_HOST`](#default-host) | no default value | +| [`DEFAULT_ROOT`](#default_root) | `404` | +| [`DHPARAM_SKIP`](#diffie-hellman-groups) | `false` | +| [`DHPARAM_BITS`](#diffie-hellman-groups) | `4096` | +| [`DISABLE_ACCESS_LOGS`](#disable-access-logs) | `false` | +| [`ENABLE_HTTP_ON_MISSING_CERT`](#default-and-missing-certificate) | `true` | +| [`ENABLE_HTTP2`](#http2-support) | `true` | +| [`ENABLE_HTTP3`](#http3-support) | `false` | +| [`ENABLE_IPV6`](#listening-on-ipv6) | `false` | +| [`HTTP_PORT`](#custom-external-httphttps-ports) | `80` | +| [`HTTPS_PORT`](#custom-external-httphttps-ports) | `443` | +| [`HTTPS_METHOD`](#how-ssl-support-works) | `redirect` | +| [`HSTS`](#how-ssl-support-works) | `max-age=31536000` | +| [`LOG_FORMAT`](#custom-log-format) | no default value | +| [`LOG_FORMAT_ESCAPE`](#log-format-escaping) | no default value | +| [`LOG_JSON`](#json-log-format) | `false` | +| [`NGINX_CONTAINER_LABEL`](#network-segregation) | `com.github.nginx-proxy.nginx-proxy.nginx` | +| [`NON_GET_REDIRECT`](#how-ssl-support-works) | `301` | +| [`PREFER_IPV6_NETWORK`](#ipv6-docker-networks) | `false` | +| `RESOLVERS` | no default value | +| [`SHA1_UPSTREAM_NAME`](#unhashed-vs-sha1-upstream-names) | `false` | +| [`SSL_POLICY`](#how-ssl-support-works) | `Mozilla-Intermediate` | +| [`TRUST_DEFAULT_CERT`](#default-and-missing-certificate) | `true` | +| [`TRUST_DOWNSTREAM_PROXY`](#trusting-downstream-proxy-headers) | `true` | + +### Proxyied container + +Configuration available on each proxied container, either by environment variable or by label: + +| Environment Variable | Label | Default Value | +|---------------------|---------------|---------------| +| [`ACME_HTTP_CHALLENGE_LOCATION`](#ssl-support-using-an-acme-ca) | n/a | global (proxy) value | +| [`CERT_NAME`](#san-certificates) | n/a | no default value | +| n/a | [`com.github.nginx-proxy.nginx-proxy.debug-endpoint`](#debug-endpoint) | global (proxy) value | +| [`ENABLE_HTTP_ON_MISSING_CERT`](#default-and-missing-certificate) | n/a | global (proxy) value | +| [`HSTS`](#how-ssl-support-works) | n/a | global (proxy) value | +| n/a | [`com.github.nginx-proxy.nginx-proxy.http2.enable`](#http2-support) | global (proxy) value | +| n/a | [`com.github.nginx-proxy.nginx-proxy.http3.enable`](#http3-support) | global (proxy) value | +| [`HTTPS_METHOD`](#how-ssl-support-works) | n/a | global (proxy) value | +| n/a | [`com.github.nginx-proxy.nginx-proxy.keepalive`](#upstream-server-http-keep-alive-support) | `auto` | +| n/a | [`com.github.nginx-proxy.nginx-proxy.loadbalance`](#upstream-server-http-load-balancing-support) | no default value | +| n/a | [`com.github.nginx-proxy.nginx-proxy.non-get-redirect`](#how-ssl-support-works) | global (proxy) value | +| [`SERVER_TOKENS`](#per-virtual_host-server_tokens-configuration) | n/a | no default value | +| [`SSL_POLICY`](#how-ssl-support-works) | n/a | global (proxy) value | +| n/a | [`com.github.nginx-proxy.nginx-proxy.trust-default-cert`](#default-and-missing-certificate) | global (proxy) value | +| [`VIRTUAL_DEST`](#virtual_dest) | n/a | `empty string` | +| [`VIRTUAL_HOST`](#virtual-hosts-and-ports) | n/a | no default value | +| [`VIRTUAL_HOST_MULTIPORTS`](#multiple-ports) | n/a | no default value | +| [`VIRTUAL_PATH`](#path-based-routing) | n/a | `/` | +| [`VIRTUAL_PORT`](#virtual-ports) | n/a | no default value | +| [`VIRTUAL_PROTO`](#upstream-backend-features) | n/a | `http` | +| [`VIRTUAL_ROOT`](#fastcgi-file-root-directory) | n/a | `/var/www/public` | + +⬆️ [back to table of contents](#table-of-contents) + ## Troubleshooting If you can't access your `VIRTUAL_HOST`, inspect the generated nginx configuration: From 0bb98ef41560543a071d3dd8757c1e6dd1589f90 Mon Sep 17 00:00:00 2001 From: Nicolas Duchon Date: Sat, 24 May 2025 14:43:35 +0200 Subject: [PATCH 17/54] test: simplify container readiness check --- test/conftest.py | 29 +++++++++++------------------ 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/test/conftest.py b/test/conftest.py index c0a060b43..ccc46ed8a 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -370,27 +370,20 @@ def docker_compose_down(compose_files: List[str], project_name: str): def wait_for_nginxproxy_to_be_ready(): """ - If one (and only one) container started from image nginxproxy/nginx-proxy:test - or nginxproxy/docker-gen:latest is found, wait for its log to contain the substring "Watching docker events" + Wait for logs of running containers started from image nginxproxy/nginx-proxy:test and/or + nginxproxy/docker-gen:latest to contain the substring "Watching docker events" """ - nginx_proxy_containers = docker_client.containers.list(filters={"ancestor": "nginxproxy/nginx-proxy:test"}) - docker_gen_containers = docker_client.containers.list(filters={"ancestor": "nginxproxy/docker-gen:latest"}) + nginx_proxy_containers = docker_client.containers.list(filters={"status": "running", "ancestor": "nginxproxy/nginx-proxy:test"}) + docker_gen_containers = docker_client.containers.list(filters={"status": "running", "ancestor": "nginxproxy/docker-gen:latest"}) - container_name = "nginx-proxy" + containers = nginx_proxy_containers + docker_gen_containers - if len(nginx_proxy_containers) == 1: - container = nginx_proxy_containers.pop() - elif len(docker_gen_containers) == 1: - container = docker_gen_containers.pop() - container_name = "docker-gen" - else: - logging.debug("Either more than one or no nginx-proxy or docker-gen container found, skipping container readiness check") - return - - for line in container.logs(stream=True): - if b"Watching docker events" in line: - logging.debug(f"{container_name} ready") - break + for container in containers: + logging.debug(f"waiting for container {container.name} to be ready") + for line in container.logs(stream=True): + if b"Watching docker events" in line: + logging.debug(f"{container.name} ready") + break @pytest.fixture From def3131239408d0ff6f1039f70c36c8b6150a132 Mon Sep 17 00:00:00 2001 From: Nicolas Duchon Date: Sat, 24 May 2025 14:57:12 +0200 Subject: [PATCH 18/54] ci: disable fail-fast on test workflow --- .github/workflows/test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 60e833115..390db4f79 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -21,6 +21,7 @@ jobs: strategy: matrix: base_docker_image: [alpine, debian] + fail-fast: false steps: - uses: actions/checkout@v4 From 3f17dd29497e5ece8529d710ab97c7b231a50b55 Mon Sep 17 00:00:00 2001 From: Nicolas Duchon Date: Sat, 24 May 2025 15:00:06 +0200 Subject: [PATCH 19/54] test: typo --- test/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/conftest.py b/test/conftest.py index ccc46ed8a..c95e42ca6 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -211,7 +211,7 @@ def nginx_proxy_dns_resolver(domain_name: str) -> Optional[str]: nginx_containers = docker_client.containers.list(filters={"status": "running", "ancestor": "nginx:latest"}) if len(nginxproxy_containers) == 0 and len(nginx_containers) == 0: - log.warning(f"no runninf container found from image nginxproxy/nginx-proxy:test or nginx:latest while resolving {domain_name!r}") + log.warning(f"no running container found from image nginxproxy/nginx-proxy:test or nginx:latest while resolving {domain_name!r}") exited_nginxproxy_containers = docker_client.containers.list(filters={"status": "exited", "ancestor": "nginxproxy/nginx-proxy:test"}) exited_nginx_containers = docker_client.containers.list(filters={"status": "exited", "ancestor": "nginx:latest"}) From d3a3d32e183706ac9cc761d1d0069a3a57632691 Mon Sep 17 00:00:00 2001 From: Nicolas Duchon Date: Mon, 26 May 2025 22:56:28 +0200 Subject: [PATCH 20/54] test: drop old checks in docker-gen tests docker engine 1.13 has been out for more than 8 years now --- test/test_dockergen/test_dockergen.py | 17 ----------------- ...ockergen_network_segregation-custom-label.py | 17 ----------------- .../test_dockergen_network_segregation.py | 17 ----------------- 3 files changed, 51 deletions(-) diff --git a/test/test_dockergen/test_dockergen.py b/test/test_dockergen/test_dockergen.py index 8784ed5f7..8fd4be910 100644 --- a/test/test_dockergen/test_dockergen.py +++ b/test/test_dockergen/test_dockergen.py @@ -1,15 +1,3 @@ -import docker -import pytest -from packaging.version import Version - - -raw_version = docker.from_env().version()["Version"] -pytestmark = pytest.mark.skipif( - Version(raw_version) < Version("1.13"), - reason="Docker compose syntax v3 requires docker engine v1.13 or later (got {raw_version})" -) - - def test_unknown_virtual_host_is_503(docker_compose, nginxproxy): r = nginxproxy.get("http://unknown.nginx-proxy.tld/") assert r.status_code == 503 @@ -20,8 +8,3 @@ def test_forwards_to_whoami(docker_compose, nginxproxy): assert r.status_code == 200 whoami_container = docker_compose.containers.get("whoami") assert r.text == f"I'm {whoami_container.id[:12]}\n" - - -if __name__ == "__main__": - import doctest - doctest.testmod() diff --git a/test/test_dockergen/test_dockergen_network_segregation-custom-label.py b/test/test_dockergen/test_dockergen_network_segregation-custom-label.py index ed5c20bd6..7efd79162 100644 --- a/test/test_dockergen/test_dockergen_network_segregation-custom-label.py +++ b/test/test_dockergen/test_dockergen_network_segregation-custom-label.py @@ -1,15 +1,3 @@ -import docker -import pytest -from packaging.version import Version - - -raw_version = docker.from_env().version()["Version"] -pytestmark = pytest.mark.skipif( - Version(raw_version) < Version("1.13"), - reason="Docker compose syntax v3 requires docker engine v1.13 or later (got {raw_version})" -) - - def test_unknown_virtual_host_is_503(docker_compose, nginxproxy): r = nginxproxy.get("http://unknown.nginx-proxy.tld/") assert r.status_code == 503 @@ -20,8 +8,3 @@ def test_forwards_to_whoami(docker_compose, nginxproxy): assert r.status_code == 200 whoami_container = docker_compose.containers.get("whoami2") assert r.text == f"I'm {whoami_container.id[:12]}\n" - - -if __name__ == "__main__": - import doctest - doctest.testmod() diff --git a/test/test_dockergen/test_dockergen_network_segregation.py b/test/test_dockergen/test_dockergen_network_segregation.py index ed5c20bd6..7efd79162 100644 --- a/test/test_dockergen/test_dockergen_network_segregation.py +++ b/test/test_dockergen/test_dockergen_network_segregation.py @@ -1,15 +1,3 @@ -import docker -import pytest -from packaging.version import Version - - -raw_version = docker.from_env().version()["Version"] -pytestmark = pytest.mark.skipif( - Version(raw_version) < Version("1.13"), - reason="Docker compose syntax v3 requires docker engine v1.13 or later (got {raw_version})" -) - - def test_unknown_virtual_host_is_503(docker_compose, nginxproxy): r = nginxproxy.get("http://unknown.nginx-proxy.tld/") assert r.status_code == 503 @@ -20,8 +8,3 @@ def test_forwards_to_whoami(docker_compose, nginxproxy): assert r.status_code == 200 whoami_container = docker_compose.containers.get("whoami2") assert r.text == f"I'm {whoami_container.id[:12]}\n" - - -if __name__ == "__main__": - import doctest - doctest.testmod() From 70659362445faa3daf53d1189594325c678d3ab6 Mon Sep 17 00:00:00 2001 From: Shane St Savage Date: Thu, 29 May 2025 18:45:25 +0000 Subject: [PATCH 21/54] Set auth_request off for all acme challenge locations Adds missing `auth_request off;` to a few `.well-known/acme-challenge` location blocks. This is needed to allow unrestricted access to `.well-known/acme-challenge` files on servers where `auth_request` is otherwise globally applied. See #1409, nginx-proxy/acme-companion#570 --- nginx.tmpl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nginx.tmpl b/nginx.tmpl index afc9f166c..fa23bd47b 100644 --- a/nginx.tmpl +++ b/nginx.tmpl @@ -865,6 +865,7 @@ server { {{- if $globals.config.acme_http_challenge_accept_unknown_host }} location ^~ /.well-known/acme-challenge/ { auth_basic off; + auth_request off; allow all; root /usr/share/nginx/html; try_files $uri =404; @@ -970,6 +971,7 @@ server { {{- if (and (eq $vhost.https_method "noredirect") $vhost.acme_http_challenge_enabled) }} location /.well-known/acme-challenge/ { auth_basic off; + auth_request off; allow all; root /usr/share/nginx/html; try_files $uri =404; From 409b0e6fbb682a28ed49165ec5b085d45f0f81cd Mon Sep 17 00:00:00 2001 From: Niek <100143256+SchoNie@users.noreply.github.com> Date: Thu, 27 Feb 2025 13:20:45 +0100 Subject: [PATCH 22/54] feat: SSL client certificate validation --- nginx.tmpl | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/nginx.tmpl b/nginx.tmpl index fa23bd47b..6dbea625b 100644 --- a/nginx.tmpl +++ b/nginx.tmpl @@ -759,6 +759,9 @@ proxy_set_header Proxy ""; {{- /* Get the SSL_POLICY defined by containers w/ the same vhost, falling back to empty string (use default). */}} {{- $ssl_policy := groupByKeys $vhost_containers "Env.SSL_POLICY" | first | default "" }} + {{- /* Get ssl_verify_client defined by containers w/ the same vhost, falling back to "on" */}} + {{- $ssl_verify_client := groupByLabel $vhost_containers "com.github.nginx-proxy.nginx-proxy.ssl_verify_client" | keys | first | default "on" }} + {{- /* Get the HSTS defined by containers w/ the same vhost, falling back to "max-age=31536000". */}} {{- $hsts := groupByKeys $vhost_containers "Env.HSTS" | first | default $globals.config.hsts }} @@ -780,6 +783,7 @@ proxy_set_header Proxy ""; "acme_http_challenge_enabled" $acme_http_challenge_enabled "server_tokens" $server_tokens "ssl_policy" $ssl_policy + "ssl_verify_client" $ssl_verify_client "trust_default_cert" $trust_default_cert "upstream_name" $upstream_name "vhost_root" $vhost_root @@ -1038,6 +1042,25 @@ server { include /etc/nginx/vhost.d/default; {{- end }} + {{/* SSL Client Certificate Validation */}} + {{/* If vhost(hash).ca.crt exists, include CA */}} + {{- if (exists (printf "/etc/nginx/certs/%s.ca.crt" $vhostFileName)) }} + ssl_client_certificate {{ printf "/etc/nginx/certs/%s.ca.crt" $vhostFileName }}; + ssl_verify_client {{ $vhost.ssl_verify_client }}; + {{/* If vhost(hash).crl.pem exists, include CRL */}} + {{- if (exists (printf "/etc/nginx/certs/%s.crl.pem" $vhostFileName)) }} + ssl_crl {{ printf "/etc/nginx/certs/%s.crl.pem" $vhostFileName }}; + {{ end }} + {{/* If no vhost CA file exists, but a global ca.crt exists include it */}} + {{ else if (exists "/etc/nginx/certs/ca.crt") }} + ssl_client_certificate /etc/nginx/certs/ca.crt; + ssl_verify_client {{ $vhost.ssl_verify_client }}; + {{/* If no vhost CA file exists, but a global ca.crl.pem exists include it */}} + {{ if (exists "/etc/nginx/certs/ca.crl.pem")}} + ssl_crl /etc/nginx/certs/ca.crl.pem; + {{ end }} + {{ end }} + {{- if $vhost.enable_debug_endpoint }} {{ template "debug_location" (dict "GlobalConfig" $globals.config "Hostname" $hostname "VHost" $vhost) }} {{- end }} From 5e77e2910b93b248eedb842690008300210ffd99 Mon Sep 17 00:00:00 2001 From: Niek <100143256+SchoNie@users.noreply.github.com> Date: Thu, 27 Feb 2025 13:22:03 +0100 Subject: [PATCH 23/54] tests: SSL client certificate validation mTLS --- ...1b655182b052fed458ec701f9ae1524e1c2.ca.crt | 21 +++++ ...b655182b052fed458ec701f9ae1524e1c2.crl.pem | 13 +++ test/test_ssl/certs_mtls/ca.crl.pem | 13 +++ test/test_ssl/certs_mtls/ca.crt | 21 +++++ .../mtls-enabled.nginx-proxy.tld.ca.crt | 21 +++++ .../mtls-enabled.nginx-proxy.tld.crl.pem | 13 +++ .../certs_mtls/mtls-optional-foo-bar_location | 4 + .../mtls-optional.nginx-proxy.tld.ca.crt | 21 +++++ test/test_ssl/certs_mtls/nginx-proxy.tld.crt | 70 +++++++++++++++ test/test_ssl/certs_mtls/nginx-proxy.tld.key | 27 ++++++ test/test_ssl/clientcerts/Revoked.crt | 85 +++++++++++++++++++ test/test_ssl/clientcerts/Revoked.key | 28 ++++++ test/test_ssl/clientcerts/Valid.crt | 85 +++++++++++++++++++ test/test_ssl/clientcerts/Valid.key | 28 ++++++ test/test_ssl/test_mtls-client-certificate.py | 59 +++++++++++++ .../test_ssl/test_mtls-client-certificate.yml | 69 +++++++++++++++ 16 files changed, 578 insertions(+) create mode 100644 test/test_ssl/certs_mtls/9ae5d1b655182b052fed458ec701f9ae1524e1c2.ca.crt create mode 100644 test/test_ssl/certs_mtls/9ae5d1b655182b052fed458ec701f9ae1524e1c2.crl.pem create mode 100644 test/test_ssl/certs_mtls/ca.crl.pem create mode 100644 test/test_ssl/certs_mtls/ca.crt create mode 100644 test/test_ssl/certs_mtls/mtls-enabled.nginx-proxy.tld.ca.crt create mode 100644 test/test_ssl/certs_mtls/mtls-enabled.nginx-proxy.tld.crl.pem create mode 100644 test/test_ssl/certs_mtls/mtls-optional-foo-bar_location create mode 100644 test/test_ssl/certs_mtls/mtls-optional.nginx-proxy.tld.ca.crt create mode 100644 test/test_ssl/certs_mtls/nginx-proxy.tld.crt create mode 100644 test/test_ssl/certs_mtls/nginx-proxy.tld.key create mode 100644 test/test_ssl/clientcerts/Revoked.crt create mode 100644 test/test_ssl/clientcerts/Revoked.key create mode 100644 test/test_ssl/clientcerts/Valid.crt create mode 100644 test/test_ssl/clientcerts/Valid.key create mode 100644 test/test_ssl/test_mtls-client-certificate.py create mode 100644 test/test_ssl/test_mtls-client-certificate.yml diff --git a/test/test_ssl/certs_mtls/9ae5d1b655182b052fed458ec701f9ae1524e1c2.ca.crt b/test/test_ssl/certs_mtls/9ae5d1b655182b052fed458ec701f9ae1524e1c2.ca.crt new file mode 100644 index 000000000..e3d27c54b --- /dev/null +++ b/test/test_ssl/certs_mtls/9ae5d1b655182b052fed458ec701f9ae1524e1c2.ca.crt @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDbDCCAlSgAwIBAgIUNvSKwCRCnOATsXaluKSE3RI6fy0wDQYJKoZIhvcNAQEL +BQAwITEfMB0GA1UEAwwWbmdpbngtcHJveHktdGVzdC1zdWl0ZTAeFw0yNTAxMDMx +MjE0NTdaFw0zNTAxMDExMjE0NTdaMCExHzAdBgNVBAMMFm5naW54LXByb3h5LXRl +c3Qtc3VpdGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCb78gTRxTy +6NrnApjBZ9Gjuj+0FcqE6ARpYBcQU+F/yokyNhE0SR4AyQaGtymDkqPcrfdDVd4b +ylz3wsbJ5jCjHyPFOQ+6trVZ6eKfQ+VoMTlTCj+ystKAba85bWdXriLdMNoqZdkY +jT6ryHUQ/mmeVdhUKBtCrqayiYNbjGTkhCwH2h2jYBdK5ngZ5mc1Z+4NGCUi14aY +AHU+nwSae/y6OU2gcYhr4NuFtwxLfxXXj2vgdTMcqoeu08u6kEjY0g7A7dzMEJ0r +VGB6+aMVK22KklPLn2IgZ/w4TC/kq/DhyWivL4HYjsKbmA6O9tM6xQqpxUwIyo+y +CdTbtv5uSX3jAgMBAAGjgZswgZgwDAYDVR0TBAUwAwEB/zAdBgNVHQ4EFgQU9X5P +1mF9ZBIYOSikqH40bUmpgRYwXAYDVR0jBFUwU4AU9X5P1mF9ZBIYOSikqH40bUmp +gRahJaQjMCExHzAdBgNVBAMMFm5naW54LXByb3h5LXRlc3Qtc3VpdGWCFDb0isAk +QpzgE7F2pbikhN0SOn8tMAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEA +kE32r6zEm6ntNRVdu3wYR6oSkk7wQr1m2YmVQjb4LIdSL3nP8VJ9PhBxUpLV03tX +evUqrAiMRL6luwFaTSb3mDxm4RGfWUzd0OVjS5NMki7Tr2RXwCCxvKApYcoJ8pwP +dNsqAmhxEwhQXIx3lG8EAxiSXe26S9JU6ILVDgdnYgfr/S9k99OjtUQOS88XiKuL +fwV7n/xSAQDiczGjwWpJLdZiKgic5VFtKOVHHMo5BIX30SJSd//BjZ5Z3N++FhLg +RzwEjbnVxxOkyz30N8VD5M7I5XwTa+VVTj5Cqoj+ImBy+/IsqjqIX8KIjl2AoJER ++2EdFJ1a5odHU3olzbuRcA== +-----END CERTIFICATE----- diff --git a/test/test_ssl/certs_mtls/9ae5d1b655182b052fed458ec701f9ae1524e1c2.crl.pem b/test/test_ssl/certs_mtls/9ae5d1b655182b052fed458ec701f9ae1524e1c2.crl.pem new file mode 100644 index 000000000..a4bbd9562 --- /dev/null +++ b/test/test_ssl/certs_mtls/9ae5d1b655182b052fed458ec701f9ae1524e1c2.crl.pem @@ -0,0 +1,13 @@ +-----BEGIN X509 CRL----- +MIICADCB6QIBATANBgkqhkiG9w0BAQsFADAhMR8wHQYDVQQDDBZuZ2lueC1wcm94 +eS10ZXN0LXN1aXRlFw0yNTAxMDMxMjMwNTBaFw0yNTA3MDIxMjMwNTBaMDIwMAIR +AK8a1AmezG56vTp5WqtpnScXDTI1MDEwMzEyMzAwN1owDDAKBgNVHRUEAwoBBaBg +MF4wXAYDVR0jBFUwU4AU9X5P1mF9ZBIYOSikqH40bUmpgRahJaQjMCExHzAdBgNV +BAMMFm5naW54LXByb3h5LXRlc3Qtc3VpdGWCFDb0isAkQpzgE7F2pbikhN0SOn8t +MA0GCSqGSIb3DQEBCwUAA4IBAQCGaKW8kJy1Mznc3T2OHkCx8GudvOo0ZBsZ+pTm +sAnlxDQTIqm8e4gU19WF/SISlfr7qEERqif8+SlUgS9CWtJa70gk+9oobuWfBNIT +VXD4ujO/47nqt2MdRUSSGX+K+9Ox2gyU6kHO1ZrT8VmsL22Bhfa2Pw/3OBL/QHMU +b1hAZyed0CoPCnMqjG0X5zMo3ByGW3TkxG2GhzKCWLGXVbzdHFpS98hpkpaxvIlE +juSYuPItwEftHdB8JHAHL18uDJapZ5mOCuUn/HoZBWOudFjtFQUUzq4eTsB56My4 +qDGb1/ReAoGyheuV0fEtg9MJkGEuGrb38JN6hcdfpW5u0Hwb +-----END X509 CRL----- diff --git a/test/test_ssl/certs_mtls/ca.crl.pem b/test/test_ssl/certs_mtls/ca.crl.pem new file mode 100644 index 000000000..a4bbd9562 --- /dev/null +++ b/test/test_ssl/certs_mtls/ca.crl.pem @@ -0,0 +1,13 @@ +-----BEGIN X509 CRL----- +MIICADCB6QIBATANBgkqhkiG9w0BAQsFADAhMR8wHQYDVQQDDBZuZ2lueC1wcm94 +eS10ZXN0LXN1aXRlFw0yNTAxMDMxMjMwNTBaFw0yNTA3MDIxMjMwNTBaMDIwMAIR +AK8a1AmezG56vTp5WqtpnScXDTI1MDEwMzEyMzAwN1owDDAKBgNVHRUEAwoBBaBg +MF4wXAYDVR0jBFUwU4AU9X5P1mF9ZBIYOSikqH40bUmpgRahJaQjMCExHzAdBgNV +BAMMFm5naW54LXByb3h5LXRlc3Qtc3VpdGWCFDb0isAkQpzgE7F2pbikhN0SOn8t +MA0GCSqGSIb3DQEBCwUAA4IBAQCGaKW8kJy1Mznc3T2OHkCx8GudvOo0ZBsZ+pTm +sAnlxDQTIqm8e4gU19WF/SISlfr7qEERqif8+SlUgS9CWtJa70gk+9oobuWfBNIT +VXD4ujO/47nqt2MdRUSSGX+K+9Ox2gyU6kHO1ZrT8VmsL22Bhfa2Pw/3OBL/QHMU +b1hAZyed0CoPCnMqjG0X5zMo3ByGW3TkxG2GhzKCWLGXVbzdHFpS98hpkpaxvIlE +juSYuPItwEftHdB8JHAHL18uDJapZ5mOCuUn/HoZBWOudFjtFQUUzq4eTsB56My4 +qDGb1/ReAoGyheuV0fEtg9MJkGEuGrb38JN6hcdfpW5u0Hwb +-----END X509 CRL----- diff --git a/test/test_ssl/certs_mtls/ca.crt b/test/test_ssl/certs_mtls/ca.crt new file mode 100644 index 000000000..e3d27c54b --- /dev/null +++ b/test/test_ssl/certs_mtls/ca.crt @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDbDCCAlSgAwIBAgIUNvSKwCRCnOATsXaluKSE3RI6fy0wDQYJKoZIhvcNAQEL +BQAwITEfMB0GA1UEAwwWbmdpbngtcHJveHktdGVzdC1zdWl0ZTAeFw0yNTAxMDMx +MjE0NTdaFw0zNTAxMDExMjE0NTdaMCExHzAdBgNVBAMMFm5naW54LXByb3h5LXRl +c3Qtc3VpdGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCb78gTRxTy +6NrnApjBZ9Gjuj+0FcqE6ARpYBcQU+F/yokyNhE0SR4AyQaGtymDkqPcrfdDVd4b +ylz3wsbJ5jCjHyPFOQ+6trVZ6eKfQ+VoMTlTCj+ystKAba85bWdXriLdMNoqZdkY +jT6ryHUQ/mmeVdhUKBtCrqayiYNbjGTkhCwH2h2jYBdK5ngZ5mc1Z+4NGCUi14aY +AHU+nwSae/y6OU2gcYhr4NuFtwxLfxXXj2vgdTMcqoeu08u6kEjY0g7A7dzMEJ0r +VGB6+aMVK22KklPLn2IgZ/w4TC/kq/DhyWivL4HYjsKbmA6O9tM6xQqpxUwIyo+y +CdTbtv5uSX3jAgMBAAGjgZswgZgwDAYDVR0TBAUwAwEB/zAdBgNVHQ4EFgQU9X5P +1mF9ZBIYOSikqH40bUmpgRYwXAYDVR0jBFUwU4AU9X5P1mF9ZBIYOSikqH40bUmp +gRahJaQjMCExHzAdBgNVBAMMFm5naW54LXByb3h5LXRlc3Qtc3VpdGWCFDb0isAk +QpzgE7F2pbikhN0SOn8tMAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEA +kE32r6zEm6ntNRVdu3wYR6oSkk7wQr1m2YmVQjb4LIdSL3nP8VJ9PhBxUpLV03tX +evUqrAiMRL6luwFaTSb3mDxm4RGfWUzd0OVjS5NMki7Tr2RXwCCxvKApYcoJ8pwP +dNsqAmhxEwhQXIx3lG8EAxiSXe26S9JU6ILVDgdnYgfr/S9k99OjtUQOS88XiKuL +fwV7n/xSAQDiczGjwWpJLdZiKgic5VFtKOVHHMo5BIX30SJSd//BjZ5Z3N++FhLg +RzwEjbnVxxOkyz30N8VD5M7I5XwTa+VVTj5Cqoj+ImBy+/IsqjqIX8KIjl2AoJER ++2EdFJ1a5odHU3olzbuRcA== +-----END CERTIFICATE----- diff --git a/test/test_ssl/certs_mtls/mtls-enabled.nginx-proxy.tld.ca.crt b/test/test_ssl/certs_mtls/mtls-enabled.nginx-proxy.tld.ca.crt new file mode 100644 index 000000000..e3d27c54b --- /dev/null +++ b/test/test_ssl/certs_mtls/mtls-enabled.nginx-proxy.tld.ca.crt @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDbDCCAlSgAwIBAgIUNvSKwCRCnOATsXaluKSE3RI6fy0wDQYJKoZIhvcNAQEL +BQAwITEfMB0GA1UEAwwWbmdpbngtcHJveHktdGVzdC1zdWl0ZTAeFw0yNTAxMDMx +MjE0NTdaFw0zNTAxMDExMjE0NTdaMCExHzAdBgNVBAMMFm5naW54LXByb3h5LXRl +c3Qtc3VpdGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCb78gTRxTy +6NrnApjBZ9Gjuj+0FcqE6ARpYBcQU+F/yokyNhE0SR4AyQaGtymDkqPcrfdDVd4b +ylz3wsbJ5jCjHyPFOQ+6trVZ6eKfQ+VoMTlTCj+ystKAba85bWdXriLdMNoqZdkY +jT6ryHUQ/mmeVdhUKBtCrqayiYNbjGTkhCwH2h2jYBdK5ngZ5mc1Z+4NGCUi14aY +AHU+nwSae/y6OU2gcYhr4NuFtwxLfxXXj2vgdTMcqoeu08u6kEjY0g7A7dzMEJ0r +VGB6+aMVK22KklPLn2IgZ/w4TC/kq/DhyWivL4HYjsKbmA6O9tM6xQqpxUwIyo+y +CdTbtv5uSX3jAgMBAAGjgZswgZgwDAYDVR0TBAUwAwEB/zAdBgNVHQ4EFgQU9X5P +1mF9ZBIYOSikqH40bUmpgRYwXAYDVR0jBFUwU4AU9X5P1mF9ZBIYOSikqH40bUmp +gRahJaQjMCExHzAdBgNVBAMMFm5naW54LXByb3h5LXRlc3Qtc3VpdGWCFDb0isAk +QpzgE7F2pbikhN0SOn8tMAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEA +kE32r6zEm6ntNRVdu3wYR6oSkk7wQr1m2YmVQjb4LIdSL3nP8VJ9PhBxUpLV03tX +evUqrAiMRL6luwFaTSb3mDxm4RGfWUzd0OVjS5NMki7Tr2RXwCCxvKApYcoJ8pwP +dNsqAmhxEwhQXIx3lG8EAxiSXe26S9JU6ILVDgdnYgfr/S9k99OjtUQOS88XiKuL +fwV7n/xSAQDiczGjwWpJLdZiKgic5VFtKOVHHMo5BIX30SJSd//BjZ5Z3N++FhLg +RzwEjbnVxxOkyz30N8VD5M7I5XwTa+VVTj5Cqoj+ImBy+/IsqjqIX8KIjl2AoJER ++2EdFJ1a5odHU3olzbuRcA== +-----END CERTIFICATE----- diff --git a/test/test_ssl/certs_mtls/mtls-enabled.nginx-proxy.tld.crl.pem b/test/test_ssl/certs_mtls/mtls-enabled.nginx-proxy.tld.crl.pem new file mode 100644 index 000000000..a4bbd9562 --- /dev/null +++ b/test/test_ssl/certs_mtls/mtls-enabled.nginx-proxy.tld.crl.pem @@ -0,0 +1,13 @@ +-----BEGIN X509 CRL----- +MIICADCB6QIBATANBgkqhkiG9w0BAQsFADAhMR8wHQYDVQQDDBZuZ2lueC1wcm94 +eS10ZXN0LXN1aXRlFw0yNTAxMDMxMjMwNTBaFw0yNTA3MDIxMjMwNTBaMDIwMAIR +AK8a1AmezG56vTp5WqtpnScXDTI1MDEwMzEyMzAwN1owDDAKBgNVHRUEAwoBBaBg +MF4wXAYDVR0jBFUwU4AU9X5P1mF9ZBIYOSikqH40bUmpgRahJaQjMCExHzAdBgNV +BAMMFm5naW54LXByb3h5LXRlc3Qtc3VpdGWCFDb0isAkQpzgE7F2pbikhN0SOn8t +MA0GCSqGSIb3DQEBCwUAA4IBAQCGaKW8kJy1Mznc3T2OHkCx8GudvOo0ZBsZ+pTm +sAnlxDQTIqm8e4gU19WF/SISlfr7qEERqif8+SlUgS9CWtJa70gk+9oobuWfBNIT +VXD4ujO/47nqt2MdRUSSGX+K+9Ox2gyU6kHO1ZrT8VmsL22Bhfa2Pw/3OBL/QHMU +b1hAZyed0CoPCnMqjG0X5zMo3ByGW3TkxG2GhzKCWLGXVbzdHFpS98hpkpaxvIlE +juSYuPItwEftHdB8JHAHL18uDJapZ5mOCuUn/HoZBWOudFjtFQUUzq4eTsB56My4 +qDGb1/ReAoGyheuV0fEtg9MJkGEuGrb38JN6hcdfpW5u0Hwb +-----END X509 CRL----- diff --git a/test/test_ssl/certs_mtls/mtls-optional-foo-bar_location b/test/test_ssl/certs_mtls/mtls-optional-foo-bar_location new file mode 100644 index 000000000..285a3bd36 --- /dev/null +++ b/test/test_ssl/certs_mtls/mtls-optional-foo-bar_location @@ -0,0 +1,4 @@ +if ($ssl_client_verify = SUCCESS) { + add_header Content-Type text/plain; + return 418 'ssl_client_verify is SUCCESS'; +} diff --git a/test/test_ssl/certs_mtls/mtls-optional.nginx-proxy.tld.ca.crt b/test/test_ssl/certs_mtls/mtls-optional.nginx-proxy.tld.ca.crt new file mode 100644 index 000000000..e3d27c54b --- /dev/null +++ b/test/test_ssl/certs_mtls/mtls-optional.nginx-proxy.tld.ca.crt @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDbDCCAlSgAwIBAgIUNvSKwCRCnOATsXaluKSE3RI6fy0wDQYJKoZIhvcNAQEL +BQAwITEfMB0GA1UEAwwWbmdpbngtcHJveHktdGVzdC1zdWl0ZTAeFw0yNTAxMDMx +MjE0NTdaFw0zNTAxMDExMjE0NTdaMCExHzAdBgNVBAMMFm5naW54LXByb3h5LXRl +c3Qtc3VpdGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCb78gTRxTy +6NrnApjBZ9Gjuj+0FcqE6ARpYBcQU+F/yokyNhE0SR4AyQaGtymDkqPcrfdDVd4b +ylz3wsbJ5jCjHyPFOQ+6trVZ6eKfQ+VoMTlTCj+ystKAba85bWdXriLdMNoqZdkY +jT6ryHUQ/mmeVdhUKBtCrqayiYNbjGTkhCwH2h2jYBdK5ngZ5mc1Z+4NGCUi14aY +AHU+nwSae/y6OU2gcYhr4NuFtwxLfxXXj2vgdTMcqoeu08u6kEjY0g7A7dzMEJ0r +VGB6+aMVK22KklPLn2IgZ/w4TC/kq/DhyWivL4HYjsKbmA6O9tM6xQqpxUwIyo+y +CdTbtv5uSX3jAgMBAAGjgZswgZgwDAYDVR0TBAUwAwEB/zAdBgNVHQ4EFgQU9X5P +1mF9ZBIYOSikqH40bUmpgRYwXAYDVR0jBFUwU4AU9X5P1mF9ZBIYOSikqH40bUmp +gRahJaQjMCExHzAdBgNVBAMMFm5naW54LXByb3h5LXRlc3Qtc3VpdGWCFDb0isAk +QpzgE7F2pbikhN0SOn8tMAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEA +kE32r6zEm6ntNRVdu3wYR6oSkk7wQr1m2YmVQjb4LIdSL3nP8VJ9PhBxUpLV03tX +evUqrAiMRL6luwFaTSb3mDxm4RGfWUzd0OVjS5NMki7Tr2RXwCCxvKApYcoJ8pwP +dNsqAmhxEwhQXIx3lG8EAxiSXe26S9JU6ILVDgdnYgfr/S9k99OjtUQOS88XiKuL +fwV7n/xSAQDiczGjwWpJLdZiKgic5VFtKOVHHMo5BIX30SJSd//BjZ5Z3N++FhLg +RzwEjbnVxxOkyz30N8VD5M7I5XwTa+VVTj5Cqoj+ImBy+/IsqjqIX8KIjl2AoJER ++2EdFJ1a5odHU3olzbuRcA== +-----END CERTIFICATE----- diff --git a/test/test_ssl/certs_mtls/nginx-proxy.tld.crt b/test/test_ssl/certs_mtls/nginx-proxy.tld.crt new file mode 100644 index 000000000..cd7284b06 --- /dev/null +++ b/test/test_ssl/certs_mtls/nginx-proxy.tld.crt @@ -0,0 +1,70 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 4096 (0x1000) + Signature Algorithm: sha256WithRSAEncryption + Issuer: O=nginx-proxy test suite, CN=www.nginx-proxy.tld + Validity + Not Before: Jan 10 00:08:52 2017 GMT + Not After : May 28 00:08:52 2044 GMT + Subject: CN=*.nginx-proxy.tld + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (2048 bit) + Modulus: + 00:cb:45:f4:14:9b:fe:64:85:79:4a:36:8d:3d:d1: + 27:d0:7c:36:28:30:e6:73:80:6f:7c:49:23:d0:6c: + 17:e4:44:c0:77:4d:9a:c2:bc:24:84:e3:a5:4d:ba: + d2:da:51:7b:a1:2a:12:d4:c0:19:55:69:2c:22:27: + 2d:1a:f6:fc:4b:7f:e9:cb:a8:3c:e8:69:b8:d2:4f: + de:4e:50:e2:d0:74:30:7c:42:5a:ae:aa:85:a5:b1: + 71:4d:c9:7e:86:8b:62:8c:3e:0d:e3:3b:c3:f5:81: + 0b:8c:68:79:fe:bf:10:fb:ae:ec:11:49:6d:64:5e: + 1a:7d:b3:92:93:4e:96:19:3a:98:04:a7:66:b2:74: + 61:2d:41:13:0c:a4:54:0d:2c:78:fd:b4:a3:e8:37: + 78:9a:de:fa:bc:2e:a8:0f:67:14:58:ce:c3:87:d5: + 14:0e:8b:29:7d:48:19:b2:a9:f5:b4:e8:af:32:21: + 67:15:7e:43:52:8b:20:cf:9f:38:43:bf:fd:c8:24: + 7f:52:a3:88:f2:f1:4a:14:91:2a:6e:91:6f:fb:7d: + 6a:78:c6:6d:2e:dd:1e:4c:2b:63:bb:3a:43:9c:91: + f9:df:d3:08:13:63:86:7d:ce:e8:46:cf:f1:6c:1f: + ca:f7:4c:de:d8:4b:e0:da:bc:06:d9:87:0f:ff:96: + 45:85 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Alternative Name: + DNS:*.nginx-proxy.tld + Signature Algorithm: sha256WithRSAEncryption + 6e:a5:0e:e4:d3:cc:d5:b7:fc:34:75:89:4e:98:8c:e7:08:06: + a8:5b:ec:13:7d:83:99:a2:61:b8:d5:12:6e:c5:b4:53:4e:9a: + 22:cd:ad:14:30:6a:7d:58:d7:23:d9:a4:2a:96:a0:40:9e:50: + 9f:ce:f2:fe:8c:dd:9a:ac:99:39:5b:89:2d:ca:e5:3e:c3:bc: + 03:04:1c:12:d9:6e:b8:9f:f0:3a:be:12:44:7e:a4:21:86:73: + af:d5:00:51:3f:2c:56:70:34:8f:26:b0:7f:b0:cf:cf:7f:f9: + 40:6f:00:29:c4:cf:c3:b7:c2:49:3d:3f:b0:26:78:87:b9:c7: + 6c:1b:aa:6a:1a:dd:c5:eb:f2:69:ba:6d:46:0b:92:49:b5:11: + 3c:eb:48:c7:2f:fb:33:a6:6a:82:a2:ab:f8:1e:5f:7d:e3:b7: + f2:fd:f5:88:a5:09:4d:a0:bc:f4:3b:cd:d2:8b:d7:57:1f:86: + 3b:d2:3e:a4:92:21:b0:02:0b:e9:e0:c4:1c:f1:78:e2:58:a7: + 26:5f:4c:29:c8:23:f0:6e:12:3f:bd:ad:44:7b:0b:bd:db:ba: + 63:8d:07:c6:9d:dc:46:cc:63:40:ba:5e:45:82:dd:9a:e5:50: + e8:e7:d7:27:88:fc:6f:1d:8a:e7:5c:49:28:aa:10:29:75:28: + c7:52:de:f9 +-----BEGIN CERTIFICATE----- +MIIC9zCCAd+gAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwPzEfMB0GA1UECgwWbmdp +bngtcHJveHkgdGVzdCBzdWl0ZTEcMBoGA1UEAwwTd3d3Lm5naW54LXByb3h5LnRs +ZDAeFw0xNzAxMTAwMDA4NTJaFw00NDA1MjgwMDA4NTJaMBwxGjAYBgNVBAMMESou +bmdpbngtcHJveHkudGxkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA +y0X0FJv+ZIV5SjaNPdEn0Hw2KDDmc4BvfEkj0GwX5ETAd02awrwkhOOlTbrS2lF7 +oSoS1MAZVWksIictGvb8S3/py6g86Gm40k/eTlDi0HQwfEJarqqFpbFxTcl+hoti +jD4N4zvD9YELjGh5/r8Q+67sEUltZF4afbOSk06WGTqYBKdmsnRhLUETDKRUDSx4 +/bSj6Dd4mt76vC6oD2cUWM7Dh9UUDospfUgZsqn1tOivMiFnFX5DUosgz584Q7/9 +yCR/UqOI8vFKFJEqbpFv+31qeMZtLt0eTCtjuzpDnJH539MIE2OGfc7oRs/xbB/K +90ze2Evg2rwG2YcP/5ZFhQIDAQABoyAwHjAcBgNVHREEFTATghEqLm5naW54LXBy +b3h5LnRsZDANBgkqhkiG9w0BAQsFAAOCAQEAbqUO5NPM1bf8NHWJTpiM5wgGqFvs +E32DmaJhuNUSbsW0U06aIs2tFDBqfVjXI9mkKpagQJ5Qn87y/ozdmqyZOVuJLcrl +PsO8AwQcEtluuJ/wOr4SRH6kIYZzr9UAUT8sVnA0jyawf7DPz3/5QG8AKcTPw7fC +ST0/sCZ4h7nHbBuqahrdxevyabptRguSSbURPOtIxy/7M6ZqgqKr+B5ffeO38v31 +iKUJTaC89DvN0ovXVx+GO9I+pJIhsAIL6eDEHPF44linJl9MKcgj8G4SP72tRHsL +vdu6Y40Hxp3cRsxjQLpeRYLdmuVQ6OfXJ4j8bx2K51xJKKoQKXUox1Le+Q== +-----END CERTIFICATE----- diff --git a/test/test_ssl/certs_mtls/nginx-proxy.tld.key b/test/test_ssl/certs_mtls/nginx-proxy.tld.key new file mode 100644 index 000000000..91adb14e1 --- /dev/null +++ b/test/test_ssl/certs_mtls/nginx-proxy.tld.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAy0X0FJv+ZIV5SjaNPdEn0Hw2KDDmc4BvfEkj0GwX5ETAd02a +wrwkhOOlTbrS2lF7oSoS1MAZVWksIictGvb8S3/py6g86Gm40k/eTlDi0HQwfEJa +rqqFpbFxTcl+hotijD4N4zvD9YELjGh5/r8Q+67sEUltZF4afbOSk06WGTqYBKdm +snRhLUETDKRUDSx4/bSj6Dd4mt76vC6oD2cUWM7Dh9UUDospfUgZsqn1tOivMiFn +FX5DUosgz584Q7/9yCR/UqOI8vFKFJEqbpFv+31qeMZtLt0eTCtjuzpDnJH539MI +E2OGfc7oRs/xbB/K90ze2Evg2rwG2YcP/5ZFhQIDAQABAoIBAQCjAro2PNLJMfCO +fyjNRgmzu6iCmpR0U68T8GN0JPsT576g7e8J828l0pkhuIyW33lRSThIvLSUNf9a +dChL032H3lBTLduKVh4NKleQXnVFzaeEPoISSFVdButiAhAhPW4OIUVp0OfY3V+x +fac3j2nDLAfL5SKAtqZv363Py9m66EBYm5BmGTQqT/frQWeCEBvlErQef5RIaU8p +e2zMWgSNNojVai8U3nKNRvYHWeWXM6Ck7lCvkHhMF+RpbmCZuqhbEARVnehU/Jdn +QHJ3nxeA2OWpoWKXvAHtSnno49yxq1UIstiQvY+ng5C5i56UlB60UiU2NJ6doZkB +uQ7/1MaBAoGBAORdcFtgdgRALjXngFWhpCp0CseyUehn1KhxDCG+D1pJ142/ymcf +oJOzKJPMRNDdDUBMnR1GBfy7rmwvYevI/SMNy2Qs7ofcXPbdtwwvTCToZ1V9/54k +VfuPBFT+3QzWRvG1tjTV3E4L2VV3nrl2qNPhE5DlfIaU3nQq5Fl0HprJAoGBAOPf +MWOTGev61CdODO5KN3pLAoamiPs5lEUlz3kM3L1Q52YLITxNDjRj9hWBUATJZOS2 +pLOoYRwmhD7vrnimMc41+NuuFX+4T7hWPc8uSuOxX0VijYtULyNRK57mncG1Fq9M +RMLbOJ7FD+8jdXNsSMqpQ+pxLJRX/A10O2fOQnbdAoGAL5hV4YWSM0KZHvz332EI +ER0MXiCJN7HkPZMKH0I4eu3m8hEmAyYxVndBnsQ1F37q0xrkqAQ/HTSUntGlS/og +4Bxw5pkCwegoq/77tpto+ExDtSrEitYx4XMmSPyxX4qNULU5m3tzJgUML+b1etwD +Rd2kMU/TC02dq4KBAy/TbRkCgYAl1xN5iJz+XenLGR/2liZ+TWR+/bqzlU006mF4 +pZUmbv/uJxz+yYD5XDwqOA4UrWjuvhG9r9FoflDprp2XdWnB556KxG7XhcDfSJr9 +A5/2DadXe1Ur9O/a+oi2228JEsxQkea9QPA3FVxfBtFjOHEiDlez39VaUP4PMeUH +iO3qlQKBgFQhdTb7HeYnApYIDHLmd1PvjRvp8XKR1CpEN0nkw8HpHcT1q1MUjQCr +iT6FQupULEvGmO3frQsgVeRIQDbEdZK3C5xCtn6qOw70sYATVf361BbTtidmU9yV +THFxwDSVLiVZgFryoY/NtAc27sVdJnGsPRjjaeVgALAsLbmZ1K/H +-----END RSA PRIVATE KEY----- diff --git a/test/test_ssl/clientcerts/Revoked.crt b/test/test_ssl/clientcerts/Revoked.crt new file mode 100644 index 000000000..2fcd03ec9 --- /dev/null +++ b/test/test_ssl/clientcerts/Revoked.crt @@ -0,0 +1,85 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + af:1a:d4:09:9e:cc:6e:7a:bd:3a:79:5a:ab:69:9d:27 + Signature Algorithm: sha256WithRSAEncryption + Issuer: CN=nginx-proxy-test-suite + Validity + Not Before: Jan 3 12:28:03 2025 GMT + Not After : Jan 1 12:28:03 2035 GMT + Subject: CN=Revoked + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (2048 bit) + Modulus: + 00:c0:ba:0d:13:f3:c9:8e:1a:e8:53:03:0e:fd:1e: + c1:a9:08:b5:7a:0a:64:47:9f:a2:1c:d1:da:4a:cb: + 9b:b5:ab:32:6e:34:03:ee:0d:54:cf:0d:1b:5b:0a: + b3:0c:f0:16:38:81:b2:d9:f9:7d:49:d4:b8:b6:d0: + bc:ce:25:ad:e2:8b:24:58:d6:77:3f:50:4e:32:0e: + 77:84:65:39:6d:41:4c:98:f6:dc:f4:01:e4:f1:b4: + 0d:43:da:54:ed:46:a7:eb:b7:fb:8e:51:dd:f8:77: + 60:be:76:be:43:14:7d:e7:2c:e3:46:0c:40:cc:b1: + 39:cc:7d:ee:67:18:c6:02:79:5e:4e:f3:25:4a:19: + c8:81:9f:d2:11:f0:84:14:ab:f6:e7:4a:b6:c6:e1: + a0:d1:4d:62:03:50:49:41:87:c2:79:32:b0:66:be: + a2:fa:08:31:f4:79:4c:cc:4a:4d:ad:81:3c:a6:6f: + e5:a1:41:d9:8c:f0:ba:68:50:f6:11:23:f7:27:8e: + 0d:2f:27:06:43:d0:b8:78:cd:ef:7d:6b:36:28:7e: + 7c:96:17:3b:6e:34:b7:4f:14:3c:b2:a6:79:98:9b: + 97:08:9b:19:ab:06:21:a9:fa:7d:55:c2:7b:7f:f4: + c1:e4:c7:5a:b8:0e:70:ef:1a:59:cd:05:bd:29:4f: + 43:67 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + X509v3 Subject Key Identifier: + 13:7E:71:8D:FC:AD:51:DE:05:9F:5F:48:A0:BB:BB:C6:D9:26:1B:61 + X509v3 Authority Key Identifier: + keyid:F5:7E:4F:D6:61:7D:64:12:18:39:28:A4:A8:7E:34:6D:49:A9:81:16 + DirName:/CN=nginx-proxy-test-suite + serial:36:F4:8A:C0:24:42:9C:E0:13:B1:76:A5:B8:A4:84:DD:12:3A:7F:2D + X509v3 Extended Key Usage: + TLS Web Client Authentication + X509v3 Key Usage: + Digital Signature + Signature Algorithm: sha256WithRSAEncryption + Signature Value: + 50:ae:d0:c6:32:da:28:c4:5b:d9:35:9b:46:f1:f1:22:eb:40: + 88:e9:4b:3e:a6:f8:06:60:8e:e0:03:15:07:26:9d:51:04:db: + a8:5b:72:3e:55:cf:87:89:a9:c1:4d:94:07:cc:4a:ab:bf:a6: + e2:54:19:ee:ee:f8:cb:22:60:cb:fc:e6:25:1d:a0:b2:7f:61: + ed:de:2a:62:24:f2:14:eb:b3:e8:04:51:85:b9:c7:26:2e:42: + ef:94:bf:dd:67:b3:8d:44:78:14:4e:83:93:4e:c3:f3:75:82: + 00:9c:bc:aa:45:c2:45:80:30:43:28:ea:57:c8:a3:1b:fc:d7: + f7:9f:29:46:c1:05:8e:09:ff:e3:70:0b:fb:35:e7:94:15:dd: + 72:b9:e6:18:f7:68:65:57:cb:c7:31:c2:22:cc:94:a4:ee:3b: + b1:8b:90:80:56:da:6e:f7:01:84:74:e8:8b:65:ac:a2:1c:c4: + bd:e5:b6:d8:03:c2:97:6b:f0:da:24:fa:df:a0:1c:8b:f4:4f: + 47:f4:58:34:4f:de:40:99:7d:37:94:20:9f:0e:51:b1:49:c8: + 53:ef:39:b6:ea:25:ab:f4:4c:23:c2:a1:ae:40:8f:b0:bc:1e: + 6e:fa:4f:d9:2b:b3:4c:d0:55:cf:19:c5:7a:ed:52:4f:da:4a: + 24:03:7c:1f +-----BEGIN CERTIFICATE----- +MIIDbDCCAlSgAwIBAgIRAK8a1AmezG56vTp5WqtpnScwDQYJKoZIhvcNAQELBQAw +ITEfMB0GA1UEAwwWbmdpbngtcHJveHktdGVzdC1zdWl0ZTAeFw0yNTAxMDMxMjI4 +MDNaFw0zNTAxMDExMjI4MDNaMBIxEDAOBgNVBAMMB1Jldm9rZWQwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDAug0T88mOGuhTAw79HsGpCLV6CmRHn6Ic +0dpKy5u1qzJuNAPuDVTPDRtbCrMM8BY4gbLZ+X1J1Li20LzOJa3iiyRY1nc/UE4y +DneEZTltQUyY9tz0AeTxtA1D2lTtRqfrt/uOUd34d2C+dr5DFH3nLONGDEDMsTnM +fe5nGMYCeV5O8yVKGciBn9IR8IQUq/bnSrbG4aDRTWIDUElBh8J5MrBmvqL6CDH0 +eUzMSk2tgTymb+WhQdmM8LpoUPYRI/cnjg0vJwZD0Lh4ze99azYofnyWFztuNLdP +FDyypnmYm5cImxmrBiGp+n1Vwnt/9MHkx1q4DnDvGlnNBb0pT0NnAgMBAAGjga0w +gaowCQYDVR0TBAIwADAdBgNVHQ4EFgQUE35xjfytUd4Fn19IoLu7xtkmG2EwXAYD +VR0jBFUwU4AU9X5P1mF9ZBIYOSikqH40bUmpgRahJaQjMCExHzAdBgNVBAMMFm5n +aW54LXByb3h5LXRlc3Qtc3VpdGWCFDb0isAkQpzgE7F2pbikhN0SOn8tMBMGA1Ud +JQQMMAoGCCsGAQUFBwMCMAsGA1UdDwQEAwIHgDANBgkqhkiG9w0BAQsFAAOCAQEA +UK7QxjLaKMRb2TWbRvHxIutAiOlLPqb4BmCO4AMVByadUQTbqFtyPlXPh4mpwU2U +B8xKq7+m4lQZ7u74yyJgy/zmJR2gsn9h7d4qYiTyFOuz6ARRhbnHJi5C75S/3Wez +jUR4FE6Dk07D83WCAJy8qkXCRYAwQyjqV8ijG/zX958pRsEFjgn/43AL+zXnlBXd +crnmGPdoZVfLxzHCIsyUpO47sYuQgFbabvcBhHToi2WsohzEveW22APCl2vw2iT6 +36Aci/RPR/RYNE/eQJl9N5Qgnw5RsUnIU+85tuolq/RMI8KhrkCPsLwebvpP2Suz +TNBVzxnFeu1ST9pKJAN8Hw== +-----END CERTIFICATE----- diff --git a/test/test_ssl/clientcerts/Revoked.key b/test/test_ssl/clientcerts/Revoked.key new file mode 100644 index 000000000..42f0dc8cb --- /dev/null +++ b/test/test_ssl/clientcerts/Revoked.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDAug0T88mOGuhT +Aw79HsGpCLV6CmRHn6Ic0dpKy5u1qzJuNAPuDVTPDRtbCrMM8BY4gbLZ+X1J1Li2 +0LzOJa3iiyRY1nc/UE4yDneEZTltQUyY9tz0AeTxtA1D2lTtRqfrt/uOUd34d2C+ +dr5DFH3nLONGDEDMsTnMfe5nGMYCeV5O8yVKGciBn9IR8IQUq/bnSrbG4aDRTWID +UElBh8J5MrBmvqL6CDH0eUzMSk2tgTymb+WhQdmM8LpoUPYRI/cnjg0vJwZD0Lh4 +ze99azYofnyWFztuNLdPFDyypnmYm5cImxmrBiGp+n1Vwnt/9MHkx1q4DnDvGlnN +Bb0pT0NnAgMBAAECggEALzFf4nLf+Bw+p5UoJnNRmMK5LZk91QwR9lysx4P0LRgu +0S2LiM9a5RigijqkfZaM2mloElg1hc7BLIMQuKohWkgYLmjV6nsPqtJAEft3hHlo ++Ev67wVHuqgMV4EvKqsSk3YJ81+4qw8QcZNCI8rwyZsETDLT60u6i4iKyFQYqKIC +ivkWxI1WUTe1VYfsL3peTlCfT3oYUR7vk6+gJ6vLgCA+V8jjoAh++MxkPCTEU2vL +BRx0jJYy5C7hyNTzHz0Xq+vu9m+DzLmP0/maUMlsKACF5RsGfO5Jdp499LL//sC0 +/5Z5rC55sJs8/bHGt8nA5N8qNLpTNDSTwuAT5fMqgQKBgQDv4tY2NWND+p9+RIdS +Uc4XsXbvj3Se3Rsx4FB8ApX89KdKUO74dBJ8/0lGttNApYoWL44TfmJDFemmC5eT ++ya7BwQLPmOKnhzc9U8DKziFEQhWrF2FndUm3prAlWolqe/5ddoTel51fEgXooEP +2M4kwBv6Y7DWA+4X72Fvk8tL0wKBgQDNrD8bh4C+vmsqf5u0EV/ST14nLtpg+jS6 +r+a6PfzzhmYos249ZVoHog4f+0mcmO/F+K1X/XAPBnDoFWgHJ9ooVIykO1D5ias8 +CBEzHEiw0E3ayMkYNR1bRbo9ykTuUjNXH7ctdCl+QEAXM2cf83VXvc8fsXQxyRpD +iJZpkExRnQKBgA3zD+fZFOuoEExEaeYUkbp4/GST4AE49FLjK2r6r4QlKfE9YZgb +D9Qq+DTffstclPoTS9zAVbB2/r5EIE1fpnHrx4Vr3Ff8N8t1jGGvyrqaMfTwUwPp +GLMI8NDQH3sPNcA45TSKwiFs17hgH8cvIVWrwjS+RVM8qUTFC9J0Mrc3AoGACeq8 +QD+QXaIg2LO+djhAPovFJm6D6RknYbkJjwFeKP/Z/SxprFwusx+FPtWG/x4AsbMD +6LI3rQHKf+ZIVc/+HOO2xFR32xBgSUy6R5SdjKj+mAYGbDxjZfs+t6wBFtyvzQui +cXagaY3/iR7ZYhkDF2/3hLexupTPx2HWntBuXaECgYBLXINRHusQaJLN1Jjxe22h +9Tx6mPNdnM0KzNeg16F+ix1O5hArNUXaHEwPj6XX6mOq1HbM6crKeGXEzaTw7vTa +o82m4nYjxa+Yh4nDiH0/4bxjZxLRl83+Y13PSxv87lqWXNdzsGdpzkWRasrrs+0J +85NwFxyxcH1wiylc86g0BQ== +-----END PRIVATE KEY----- diff --git a/test/test_ssl/clientcerts/Valid.crt b/test/test_ssl/clientcerts/Valid.crt new file mode 100644 index 000000000..03558ff9c --- /dev/null +++ b/test/test_ssl/clientcerts/Valid.crt @@ -0,0 +1,85 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + e8:b3:fd:e8:a7:be:5e:a0:ea:3f:23:33:bc:2d:72:da + Signature Algorithm: sha256WithRSAEncryption + Issuer: CN=nginx-proxy-test-suite + Validity + Not Before: Jan 3 12:21:59 2025 GMT + Not After : Jan 1 12:21:59 2035 GMT + Subject: CN=Valid + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (2048 bit) + Modulus: + 00:d8:40:8d:5d:2e:a1:19:de:c5:20:01:fe:dc:9a: + ee:15:fe:9e:e1:30:a0:c7:a2:5a:22:dd:99:6e:dd: + 34:4d:6e:ad:ec:32:17:a6:e4:37:99:11:ea:67:b1: + db:1f:52:7c:17:c8:02:46:04:7f:86:b3:54:6e:55: + 18:26:af:e8:ce:84:c4:52:7c:42:fd:37:ca:ce:bf: + 88:65:09:30:4d:91:55:6f:33:0d:66:bd:e9:49:08: + 4f:65:a3:bd:41:17:67:78:57:41:94:55:6f:da:91: + cb:48:da:a1:df:fb:7d:35:16:c8:1c:ef:9a:d7:fe: + c9:19:4b:c5:89:e1:29:da:5d:01:8f:99:d4:45:da: + 9b:a8:a2:d6:f1:c2:83:36:f5:8f:bd:4b:6a:54:03: + 37:6b:d8:62:e9:15:31:97:34:43:15:a5:6a:df:2f: + b6:df:19:90:4e:53:c5:42:2a:95:b0:94:43:03:02: + 73:a4:f0:20:10:77:71:5d:55:9c:f8:d4:08:11:07: + 94:c6:bb:45:62:93:c5:bd:61:95:75:49:27:ea:b5: + e4:0b:be:27:52:61:9d:cd:11:c8:12:b3:29:d9:3a: + e9:80:6c:65:44:b0:84:cd:7f:8d:e6:ce:07:86:1c: + c1:a2:75:2f:bf:e4:66:b1:b2:b2:2f:5a:fc:44:82: + e4:f1 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + X509v3 Subject Key Identifier: + ED:86:4A:75:0A:B2:ED:D1:B8:D9:88:3C:BE:42:DF:8B:18:C3:99:E8 + X509v3 Authority Key Identifier: + keyid:F5:7E:4F:D6:61:7D:64:12:18:39:28:A4:A8:7E:34:6D:49:A9:81:16 + DirName:/CN=nginx-proxy-test-suite + serial:36:F4:8A:C0:24:42:9C:E0:13:B1:76:A5:B8:A4:84:DD:12:3A:7F:2D + X509v3 Extended Key Usage: + TLS Web Client Authentication + X509v3 Key Usage: + Digital Signature + Signature Algorithm: sha256WithRSAEncryption + Signature Value: + 25:aa:b5:6e:07:0a:3e:d2:ed:61:33:6b:48:ef:81:41:3b:7f: + 02:1c:58:74:13:25:7f:29:d2:5e:14:31:10:7a:12:81:9b:9f: + 6f:3b:b2:3e:07:32:d8:f8:3b:15:5a:18:ec:00:41:15:b1:49: + ea:c1:38:d1:7f:c9:55:bd:dd:99:a0:b9:a8:80:76:f1:6c:db: + 75:bd:23:23:2b:e4:71:9b:d7:fd:74:42:51:31:29:cd:28:32: + a9:d9:09:c5:1f:48:93:e4:6d:e6:45:97:4e:a8:4e:bd:48:d1: + e7:45:e5:34:88:74:aa:98:a3:8d:03:af:c1:10:5b:1d:ec:cb: + 55:90:7c:b4:4c:d0:33:92:63:81:eb:39:50:2b:6e:39:e3:1b: + 74:9d:67:1b:55:76:95:af:8e:83:24:47:c2:fc:aa:c5:7d:f1: + 10:e2:d1:8b:7a:b4:da:5e:40:c4:ce:32:e1:93:d9:17:76:fa: + a2:5d:a8:48:73:ff:0a:b4:b3:ac:97:e4:8b:6e:18:56:09:e0: + 05:be:42:3e:c6:8e:30:cf:29:da:0f:a1:05:c0:7d:3d:8f:8f: + 52:27:e0:dd:06:f2:06:5b:8a:40:e5:0f:5c:a5:3b:16:10:32: + 49:9f:9c:ca:d8:02:9f:f2:5c:b1:20:96:b9:b0:ff:87:7a:20: + 15:1c:4b:88 +-----BEGIN CERTIFICATE----- +MIIDajCCAlKgAwIBAgIRAOiz/einvl6g6j8jM7wtctowDQYJKoZIhvcNAQELBQAw +ITEfMB0GA1UEAwwWbmdpbngtcHJveHktdGVzdC1zdWl0ZTAeFw0yNTAxMDMxMjIx +NTlaFw0zNTAxMDExMjIxNTlaMBAxDjAMBgNVBAMMBVZhbGlkMIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2ECNXS6hGd7FIAH+3JruFf6e4TCgx6JaIt2Z +bt00TW6t7DIXpuQ3mRHqZ7HbH1J8F8gCRgR/hrNUblUYJq/ozoTEUnxC/TfKzr+I +ZQkwTZFVbzMNZr3pSQhPZaO9QRdneFdBlFVv2pHLSNqh3/t9NRbIHO+a1/7JGUvF +ieEp2l0Bj5nURdqbqKLW8cKDNvWPvUtqVAM3a9hi6RUxlzRDFaVq3y+23xmQTlPF +QiqVsJRDAwJzpPAgEHdxXVWc+NQIEQeUxrtFYpPFvWGVdUkn6rXkC74nUmGdzRHI +ErMp2TrpgGxlRLCEzX+N5s4HhhzBonUvv+RmsbKyL1r8RILk8QIDAQABo4GtMIGq +MAkGA1UdEwQCMAAwHQYDVR0OBBYEFO2GSnUKsu3RuNmIPL5C34sYw5noMFwGA1Ud +IwRVMFOAFPV+T9ZhfWQSGDkopKh+NG1JqYEWoSWkIzAhMR8wHQYDVQQDDBZuZ2lu +eC1wcm94eS10ZXN0LXN1aXRlghQ29IrAJEKc4BOxdqW4pITdEjp/LTATBgNVHSUE +DDAKBggrBgEFBQcDAjALBgNVHQ8EBAMCB4AwDQYJKoZIhvcNAQELBQADggEBACWq +tW4HCj7S7WEza0jvgUE7fwIcWHQTJX8p0l4UMRB6EoGbn287sj4HMtj4OxVaGOwA +QRWxSerBONF/yVW93ZmguaiAdvFs23W9IyMr5HGb1/10QlExKc0oMqnZCcUfSJPk +beZFl06oTr1I0edF5TSIdKqYo40Dr8EQWx3sy1WQfLRM0DOSY4HrOVArbjnjG3Sd +ZxtVdpWvjoMkR8L8qsV98RDi0Yt6tNpeQMTOMuGT2Rd2+qJdqEhz/wq0s6yX5Itu +GFYJ4AW+Qj7GjjDPKdoPoQXAfT2Pj1In4N0G8gZbikDlD1ylOxYQMkmfnMrYAp/y +XLEglrmw/4d6IBUcS4g= +-----END CERTIFICATE----- diff --git a/test/test_ssl/clientcerts/Valid.key b/test/test_ssl/clientcerts/Valid.key new file mode 100644 index 000000000..ff63056fb --- /dev/null +++ b/test/test_ssl/clientcerts/Valid.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDYQI1dLqEZ3sUg +Af7cmu4V/p7hMKDHoloi3Zlu3TRNbq3sMhem5DeZEepnsdsfUnwXyAJGBH+Gs1Ru +VRgmr+jOhMRSfEL9N8rOv4hlCTBNkVVvMw1mvelJCE9lo71BF2d4V0GUVW/akctI +2qHf+301Fsgc75rX/skZS8WJ4SnaXQGPmdRF2puootbxwoM29Y+9S2pUAzdr2GLp +FTGXNEMVpWrfL7bfGZBOU8VCKpWwlEMDAnOk8CAQd3FdVZz41AgRB5TGu0Vik8W9 +YZV1SSfqteQLvidSYZ3NEcgSsynZOumAbGVEsITNf43mzgeGHMGidS+/5GaxsrIv +WvxEguTxAgMBAAECggEAA9DrzbOncyHrv5tZaIIxZmD2Y8nsZGl4hrn7Xan4pM8p +sYvWwAKx+nkG9mp8j+pwNyk35Q1qRLHAcSv+P5yGErLkFgKMWhSUdx6JJSNK32uL +ouk3ONNsHPZRFF2V1uK3WDfN9/SkAyrkd/YnuiChfoDW6i/OPzaHTPN52muHRguq +z5Iaw9BTTJXFhzdLqd+U6JfdXpn6brmoySXVANYwi/Ug470pR8wyulTIXIhyu8gc +9O9U/QTfpV3uRIxCdci72w9UICVHljPG1ARW1JAZsmTAgxpioGjXcwn5soSNlQ4g +dO6MJ9M7Y/amkvLfAh0Cabj4vRrEJ83TGKaYhlAquQKBgQDwKtQdftvxrQtQiU0Y +7zglGtjBRKtVjb3rcgRs2cvU1GITjIEAuMyMHnyJmGDGRVPdI2us7EZwtpsOmlk1 +nZppLOe478GBA7twaS6vONxBbegZTBtSClyktY8RG5g/siIE6y3m+JcJnZO7XELA +p1r1JUeVLWXJkO2dmgpDjVJIPQKBgQDmgh71G4iLdIxSR/VGmrHX8kwnBRV3rn2+ +O0XcY0XQZPk4XL4zx3Qw5QUC7QMxikaLDQOXKZXylyjyGGyFAjJI8HrLr7Sm7xb4 +g91EjlfQWmTjrvI/6RBndwsExiEzgkASDxm6iOMXFx658tDK6dIsZWhh/59thUVE +u+ZSBp1mxQKBgQDfQVDxIk5fONc9xISw2x+8DlrUPntvClY0GkdW0JeUfuG0/nWl +MCSlVGm8lrPPW/77oMOlefZ5LKazSnQHTTyO7LlzxxyAS/HgK0bEh/znrb2GVqNG +/m7khgo6gwZin7rUC7Md9JSi0aLVFozO/kOlg0QpvovSdjEMwncsGKEWmQKBgQDS +TiuSc1Fr8qTHuVFF3oOdwznJa/D/JZshwZBml8gtbsKWsr7yHOqcZYbh+X4tZ7we +x3vcIZvmHhXEc5Ym8C8Srx1J0wAeQgsSJ7TsBHaH6MEdnhL1Tl2iGFFcRKwsA40T +LOXLc3LFMVneS3RFfXk8+jR3HLLHSI0/PbPQaKqZBQKBgHggyUNuWtSwWTByicnU +MmGv+fb+0NEFN/c+lf2HGb5GFifhFTppAi2L+FT/D5cW+vFpC0f/O7m+ymIoa5YC +fjLvUE6L83D17H/TN34pwxpUxZg/cWl3HkVHRVLnpm4L5HKnt2Qj7q1n4q2FTjhR +vuihVh5K2G3n+Sysf1rViP5I +-----END PRIVATE KEY----- diff --git a/test/test_ssl/test_mtls-client-certificate.py b/test/test_ssl/test_mtls-client-certificate.py new file mode 100644 index 000000000..c7c03aea3 --- /dev/null +++ b/test/test_ssl/test_mtls-client-certificate.py @@ -0,0 +1,59 @@ +import pathlib + +import pytest +from requests.exceptions import SSLError + + +@pytest.fixture(scope="session") +def clientcerts(): + """ + Pytest fixture to provide paths to client certificates and keys. + """ + current_file_path = pathlib.Path(__file__) + clientcerts_path = current_file_path.parent.joinpath("clientcerts") + + return { + "valid_client_cert": clientcerts_path.joinpath("Valid.crt"), + "valid_client_key": clientcerts_path.joinpath("Valid.key"), + "revoked_client_cert": clientcerts_path.joinpath("Revoked.crt"), + "revoked_client_key": clientcerts_path.joinpath("Revoked.key"), + } + +@pytest.mark.parametrize("description, url, cert, expected_code, expected_text", [ + #Enforced: Test connection to a website with mTLS enabled without providing a client certificate. + ("Enforced: No client certificate, virtual_host", "https://mtls-enabled.nginx-proxy.tld/port", None, 400, "400 No required SSL certificate was sent"), + ("Enforced: No client certificate, virtual_path", "https://mtls-enabled.nginx-proxy.tld/bar/port", None, 400, "400 No required SSL certificate was sent"), + ("Enforced: No client certificate, regex", "https://regex.nginx-proxy.tld/port", None, 400, "400 No required SSL certificate was sent"), + ("Enforced: No client certificate, global CA", "https://global-mtls-enabled.nginx-proxy.tld/port", None, 400, "400 No required SSL certificate was sent"), + #Authenticated: Test connection to a website with mTLS enabled providing a valid client certificate. + ("Authenticated: Valid client certificate, virtual_host", "https://mtls-enabled.nginx-proxy.tld/port", "valid", 200, "answer from port 81\n"), + ("Authenticated: Valid client certificate, virtual_path", "https://mtls-enabled.nginx-proxy.tld/bar/port", "valid", 200, "answer from port 83\n"), + ("Authenticated: Valid client certificate, regex", "https://regex.nginx-proxy.tld/port", "valid", 200, "answer from port 85\n"), + ("Authenticated: Valid client certificate, global CA", "https://global-mtls-enabled.nginx-proxy.tld/port", "valid", 200, "answer from port 81\n"), + #Revoked: Test connection to a website with mTLS enabled providing a revoked client certificate on the CRL. + ("Revoked: Invalid client certificate, virtual_host", "https://mtls-enabled.nginx-proxy.tld/port", "revoked", 400, "400 The SSL certificate error"), + ("Revoked: Invalid client certificate, virtual_path", "https://mtls-enabled.nginx-proxy.tld/bar/port", "revoked", 400, "400 The SSL certificate error"), + ("Revoked: Invalid client certificate, regex", "https://regex.nginx-proxy.tld/port", "revoked", 400, "400 The SSL certificate error"), + ("Revoked: Invalid client certificate, global CA", "https://global-mtls-enabled.nginx-proxy.tld/port", "revoked", 400, "400 The SSL certificate error"), + #Optional: Test connection to a website with optional mTLS. Access is not blocked but can be controlled with "$ssl_client_verify" directive. We assert on /foo if $ssl_client_verify = SUCCESS response with status code 418. + ("Optional, Not enforced: No client certificate", "https://mtls-optional.nginx-proxy.tld/port", None, 200, "answer from port 82\n"), + ("Optional: Enforced, Valid client certificate", "https://mtls-optional.nginx-proxy.tld/foo/port", "valid", 418, "ssl_client_verify is SUCCESS"), + ("Optional, Not enforced: No client certificate", "https://mtls-optional.nginx-proxy.tld/bar/port", None, 200, "answer from port 84\n"), + ("Optional: Enforced, Valid client certificate", "https://mtls-optional.nginx-proxy.tld/foo/bar/port", "valid", 418, "ssl_client_verify is SUCCESS"), + ("Optional, Not enforced: No client certificate, global CA", "https://global-mtls-optional.nginx-proxy.tld/port", None, 200, "answer from port 82\n"), + ("Optional: Enforced, Valid client certificate, global CA", "https://global-mtls-optional.nginx-proxy.tld/foo/port", "valid", 418, "ssl_client_verify is SUCCESS"), +]) +def test_mtls_client_certificates(docker_compose, nginxproxy, clientcerts, description, url, cert, expected_code, expected_text): + """ + Parameterized test for mTLS client certificate scenarios. + """ + if cert == "valid": + client_cert = (clientcerts["valid_client_cert"], clientcerts["valid_client_key"]) + elif cert == "revoked": + client_cert = (clientcerts["revoked_client_cert"], clientcerts["revoked_client_key"]) + else: + client_cert = None + + r = nginxproxy.get(url, cert=client_cert if client_cert else None) + assert r.status_code == expected_code + assert expected_text in r.text diff --git a/test/test_ssl/test_mtls-client-certificate.yml b/test/test_ssl/test_mtls-client-certificate.yml new file mode 100644 index 000000000..99edc83fd --- /dev/null +++ b/test/test_ssl/test_mtls-client-certificate.yml @@ -0,0 +1,69 @@ +services: + mtls-vhost-enabled: + image: web + expose: + - "81" + environment: + WEB_PORTS: "81" + VIRTUAL_HOST: "mtls-enabled.nginx-proxy.tld,global-mtls-enabled.nginx-proxy.tld" + + mtls-vhost-optional: + image: web + expose: + - "82" + environment: + WEB_PORTS: "82" + VIRTUAL_HOST_MULTIPORTS: |- + mtls-optional.nginx-proxy.tld: + "/": + dest: "/" + "/foo": + dest: "/" + global-mtls-optional.nginx-proxy.tld: + "/": + dest: "/" + "/foo": + dest: "/" + labels: + com.github.nginx-proxy.nginx-proxy.ssl_verify_client: "optional" + + mtls-vpath-enabled: + image: web + expose: + - "83" + environment: + WEB_PORTS: "83" + VIRTUAL_HOST: "mtls-enabled.nginx-proxy.tld" + VIRTUAL_PATH: /bar/ + VIRTUAL_DEST: / + + mtls-vpath-optional: + image: web + expose: + - "84" + environment: + WEB_PORTS: "84" + VIRTUAL_HOST_MULTIPORTS: |- + mtls-optional.nginx-proxy.tld: + "/bar": + dest: "/" + "/foo/bar": + dest: "/" + labels: + com.github.nginx-proxy.nginx-proxy.ssl_verify_client: "optional" + + mtls-regex-enabled: + image: web + expose: + - "85" + environment: + WEB_PORTS: "85" + VIRTUAL_HOST: ~^regex.*\.nginx-proxy\.tld$ + CERT_NAME: nginx-proxy.tld + + nginx-proxy: + volumes: + - ${PYTEST_MODULE_PATH}/certs_mtls:/etc/nginx/certs:ro + - ${PYTEST_MODULE_PATH}/certs_mtls/mtls-optional-foo-bar_location:/etc/nginx/vhost.d/mtls-optional.nginx-proxy.tld_6dbd548cc03e44b8b44b6e68e56255ce4273ae49_location:ro #/foo + - ${PYTEST_MODULE_PATH}/certs_mtls/mtls-optional-foo-bar_location:/etc/nginx/vhost.d/mtls-optional.nginx-proxy.tld_a82cce35fd860de6f63f97e6c482dc6a14d002e8_location:ro #/bar + - ${PYTEST_MODULE_PATH}/certs_mtls/mtls-optional-foo-bar_location:/etc/nginx/vhost.d/global-mtls-optional.nginx-proxy.tld_6dbd548cc03e44b8b44b6e68e56255ce4273ae49_location:ro #/foo From 7edf50a9e5deb3f8935264a92e4b58b0383c03a5 Mon Sep 17 00:00:00 2001 From: Niek <100143256+SchoNie@users.noreply.github.com> Date: Wed, 4 Jun 2025 15:42:25 +0200 Subject: [PATCH 24/54] docs: SSL client certificate validation mTLS --- docs/README.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/docs/README.md b/docs/README.md index f14bc2be7..d5f714b79 100644 --- a/docs/README.md +++ b/docs/README.md @@ -5,6 +5,7 @@ - [Docker Networking](#docker-networking) - [Upstream (Backend) features](#upstream-backend-features) - [Basic Authentication Support](#basic-authentication-support) +- [mTLS client side certificate authentication](#mtls-client-side-certificate-authentication) - [Logging](#logging) - [SSL Support](#ssl-support) - [IPv6 Support](#ipv6-nat) @@ -374,6 +375,36 @@ You'll need apache2-utils on the machine where you plan to create the htpasswd f ⬆️ [back to table of contents](#table-of-contents) +## mTLS client side certificate authentication +In mTLS, both the client and server have a certificate, and both sides authenticate using their public/private key pair. +A "root" TLS certificate is necessary for mTLS; this enables an organization to be their own certificate authority. The certificates used by authorized clients and servers have to correspond to this root certificate. The root certificate is self-signed, meaning that the organization creates it themselves. +Make sure you have a root certificate (CA) and client public/private key pair. There is a [howto in the wiki](https://github.com/nginx-proxy/nginx-proxy/wiki/mTLS-client-side-certificate-authentication). + +### Certificate Authority (CA) +#### Per-VIRTUAL_HOST CA +In order to secure a virtual host, you have to copy your CA certificate file (ca.crt) named as its equivalent `VIRTUAL_HOST` variable or if `VIRTUAL_HOST` is a regex, after the sha1 hash of the regex with the suffix `.ca.crt` in directory +`/etc/nginx/certs/`. Example: `/etc/nginx/certs/app.example.com.ca.crt`. +Or if your `VIRTUAL_HOST` is a regex: `/etc/nginx/certs/9ae5d1b655182b052fed458ec701f9ae1524e1c2.ca.crt`. + +#### Global CA +If you want to secure everything globally you can copy your CA certificate file (ca.crt) named as `ca.crt` in directory +`/etc/nginx/certs/`. Example: `/etc/nginx/certs/ca.crt`. + +### Certificate Revocation List (CRL) +#### Per-VIRTUAL_HOST CRL +In order to use a certificate revocation list, you have to copy your CRL file named as its equivalent `VIRTUAL_HOST` variable or if `VIRTUAL_HOST` is a regex, after the sha1 hash of the regex with the suffix `.crl.pem` in directory +`/etc/nginx/certs/`. Example: `/etc/nginx/certs/app.example.com.crl.pem`. +Or if your `VIRTUAL_HOST` is a regex: `/etc/nginx/certs/9ae5d1b655182b052fed458ec701f9ae1524e1c2.crl.pem`. + +#### Global CRL +If you want to use a global CRL file you have to copy your CRL file named as `ca.crl.pem` in directory +`/etc/nginx/certs/`. Example: `/etc/nginx/certs/ca.crl.pem`. + +### optional ssl_verify_client +Optional [`ssl_verify_client`](https://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_verify_client) can be activated by using the `com.github.nginx-proxy.nginx-proxy.ssl_verify_client: "optional"` label on a proxied container. If this label is set on a proxied container access is not blocked but the result of the mTLS verify is stored in the [$ssl_client_verify](https://nginx.org/en/docs/http/ngx_http_ssl_module.html#var_ssl_client_verify) variable which you can use this in the [Per-VIRTUAL_HOST location](https://github.com/nginx-proxy/nginx-proxy/tree/main/docs#per-virtual_host-location-configuration) and [Per-VIRTUAL_PATH location](https://github.com/nginx-proxy/nginx-proxy/tree/main/docs#per-virtual_path-location-configuration) configurations. + +⬆️ [back to table of contents](#table-of-contents) + ## Logging The default nginx access log format is @@ -1327,6 +1358,7 @@ Configuration available on each proxied container, either by environment variabl | n/a | [`com.github.nginx-proxy.nginx-proxy.non-get-redirect`](#how-ssl-support-works) | global (proxy) value | | [`SERVER_TOKENS`](#per-virtual_host-server_tokens-configuration) | n/a | no default value | | [`SSL_POLICY`](#how-ssl-support-works) | n/a | global (proxy) value | +| n/a | [`com.github.nginx-proxy.nginx-proxy.ssl_verify_client`](#optional-ssl_verify_client) | `on` | | n/a | [`com.github.nginx-proxy.nginx-proxy.trust-default-cert`](#default-and-missing-certificate) | global (proxy) value | | [`VIRTUAL_DEST`](#virtual_dest) | n/a | `empty string` | | [`VIRTUAL_HOST`](#virtual-hosts-and-ports) | n/a | no default value | From 6a92955a16dc74977355017c8bd30ccf9cc5502c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Jun 2025 05:02:04 +0000 Subject: [PATCH 25/54] ci: bump pytest from 8.3.5 to 8.4.0 in /test/requirements Bumps [pytest](https://github.com/pytest-dev/pytest) from 8.3.5 to 8.4.0. - [Release notes](https://github.com/pytest-dev/pytest/releases) - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest/compare/8.3.5...8.4.0) --- updated-dependencies: - dependency-name: pytest dependency-version: 8.4.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- test/requirements/python-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/requirements/python-requirements.txt b/test/requirements/python-requirements.txt index 7a5eead5c..b3cb14b83 100644 --- a/test/requirements/python-requirements.txt +++ b/test/requirements/python-requirements.txt @@ -1,6 +1,6 @@ backoff==2.2.1 docker==7.1.0 packaging==25.0 -pytest==8.3.5 +pytest==8.4.0 requests==2.32.3 urllib3==2.4.0 From 03403dedd679b28863eddae37fb33593753efb4f Mon Sep 17 00:00:00 2001 From: Niek <100143256+SchoNie@users.noreply.github.com> Date: Tue, 10 Jun 2025 10:40:00 +0200 Subject: [PATCH 26/54] chore: styling --- nginx.tmpl | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/nginx.tmpl b/nginx.tmpl index 6dbea625b..939aa2e9c 100644 --- a/nginx.tmpl +++ b/nginx.tmpl @@ -1047,18 +1047,18 @@ server { {{- if (exists (printf "/etc/nginx/certs/%s.ca.crt" $vhostFileName)) }} ssl_client_certificate {{ printf "/etc/nginx/certs/%s.ca.crt" $vhostFileName }}; ssl_verify_client {{ $vhost.ssl_verify_client }}; - {{/* If vhost(hash).crl.pem exists, include CRL */}} - {{- if (exists (printf "/etc/nginx/certs/%s.crl.pem" $vhostFileName)) }} + {{/* If vhost(hash).crl.pem exists, include CRL */}} + {{- if (exists (printf "/etc/nginx/certs/%s.crl.pem" $vhostFileName)) }} ssl_crl {{ printf "/etc/nginx/certs/%s.crl.pem" $vhostFileName }}; - {{ end }} - {{/* If no vhost CA file exists, but a global ca.crt exists include it */}} + {{ end }} + {{/* Else if no vhost CA file exists, but a global ca.crt exists include it */}} {{ else if (exists "/etc/nginx/certs/ca.crt") }} ssl_client_certificate /etc/nginx/certs/ca.crt; ssl_verify_client {{ $vhost.ssl_verify_client }}; - {{/* If no vhost CA file exists, but a global ca.crl.pem exists include it */}} - {{ if (exists "/etc/nginx/certs/ca.crl.pem")}} + {{/* If no vhost CA file exists, but a global ca.crl.pem exists include it */}} + {{ if (exists "/etc/nginx/certs/ca.crl.pem")}} ssl_crl /etc/nginx/certs/ca.crl.pem; - {{ end }} + {{ end }} {{ end }} {{- if $vhost.enable_debug_endpoint }} From df85d14f6c388c65457eb4c9f2df593f0eeeb8ac Mon Sep 17 00:00:00 2001 From: Niek <100143256+SchoNie@users.noreply.github.com> Date: Tue, 10 Jun 2025 10:40:54 +0200 Subject: [PATCH 27/54] docs: note about global and per-vhost CRL --- docs/README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/README.md b/docs/README.md index d5f714b79..feeb5f1d0 100644 --- a/docs/README.md +++ b/docs/README.md @@ -400,6 +400,9 @@ Or if your `VIRTUAL_HOST` is a regex: `/etc/nginx/certs/9ae5d1b655182b052fed458e If you want to use a global CRL file you have to copy your CRL file named as `ca.crl.pem` in directory `/etc/nginx/certs/`. Example: `/etc/nginx/certs/ca.crl.pem`. +> [!NOTE] +> Use Per-VIRTUAL_HOST CRL if you configured the [Per-VIRTUAL_HOST CA](#per-virtual_host-ca) or Global CRL if you configured the [Global CA](#global-ca) + ### optional ssl_verify_client Optional [`ssl_verify_client`](https://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_verify_client) can be activated by using the `com.github.nginx-proxy.nginx-proxy.ssl_verify_client: "optional"` label on a proxied container. If this label is set on a proxied container access is not blocked but the result of the mTLS verify is stored in the [$ssl_client_verify](https://nginx.org/en/docs/http/ngx_http_ssl_module.html#var_ssl_client_verify) variable which you can use this in the [Per-VIRTUAL_HOST location](https://github.com/nginx-proxy/nginx-proxy/tree/main/docs#per-virtual_host-location-configuration) and [Per-VIRTUAL_PATH location](https://github.com/nginx-proxy/nginx-proxy/tree/main/docs#per-virtual_path-location-configuration) configurations. From 0cf0e80aa9eca5b89fd3cca1c2324476f67334ba Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Jun 2025 04:13:45 +0000 Subject: [PATCH 28/54] ci: bump requests from 2.32.3 to 2.32.4 in /test/requirements Bumps [requests](https://github.com/psf/requests) from 2.32.3 to 2.32.4. - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.3...v2.32.4) --- updated-dependencies: - dependency-name: requests dependency-version: 2.32.4 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- test/requirements/python-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/requirements/python-requirements.txt b/test/requirements/python-requirements.txt index b3cb14b83..899f52a87 100644 --- a/test/requirements/python-requirements.txt +++ b/test/requirements/python-requirements.txt @@ -2,5 +2,5 @@ backoff==2.2.1 docker==7.1.0 packaging==25.0 pytest==8.4.0 -requests==2.32.3 +requests==2.32.4 urllib3==2.4.0 From f7d3c59ffc0ea44cf3333c1799a296c433f63df4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Jun 2025 05:12:55 +0000 Subject: [PATCH 29/54] ci: bump urllib3 from 2.4.0 to 2.5.0 in /test/requirements Bumps [urllib3](https://github.com/urllib3/urllib3) from 2.4.0 to 2.5.0. - [Release notes](https://github.com/urllib3/urllib3/releases) - [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst) - [Commits](https://github.com/urllib3/urllib3/compare/2.4.0...2.5.0) --- updated-dependencies: - dependency-name: urllib3 dependency-version: 2.5.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- test/requirements/python-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/requirements/python-requirements.txt b/test/requirements/python-requirements.txt index 899f52a87..64b143606 100644 --- a/test/requirements/python-requirements.txt +++ b/test/requirements/python-requirements.txt @@ -3,4 +3,4 @@ docker==7.1.0 packaging==25.0 pytest==8.4.0 requests==2.32.4 -urllib3==2.4.0 +urllib3==2.5.0 From a212d80509ffc702853b8c31c961307e35aceedd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 24 Jun 2025 06:27:47 +0000 Subject: [PATCH 30/54] ci: bump pytest from 8.4.0 to 8.4.1 in /test/requirements Bumps [pytest](https://github.com/pytest-dev/pytest) from 8.4.0 to 8.4.1. - [Release notes](https://github.com/pytest-dev/pytest/releases) - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest/compare/8.4.0...8.4.1) --- updated-dependencies: - dependency-name: pytest dependency-version: 8.4.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- test/requirements/python-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/requirements/python-requirements.txt b/test/requirements/python-requirements.txt index 64b143606..ca89fd32c 100644 --- a/test/requirements/python-requirements.txt +++ b/test/requirements/python-requirements.txt @@ -1,6 +1,6 @@ backoff==2.2.1 docker==7.1.0 packaging==25.0 -pytest==8.4.0 +pytest==8.4.1 requests==2.32.4 urllib3==2.5.0 From a13372d0259e4368a271fd24ef61e6697ca6e942 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 25 Jun 2025 04:30:01 +0000 Subject: [PATCH 31/54] build: bump library/nginx from 1.27.5 to 1.29.0 Bumps library/nginx from 1.27.5 to 1.29.0. --- updated-dependencies: - dependency-name: library/nginx dependency-version: 1.29.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- Dockerfile.alpine | 2 +- Dockerfile.debian | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile.alpine b/Dockerfile.alpine index 36d6867c7..7996af553 100644 --- a/Dockerfile.alpine +++ b/Dockerfile.alpine @@ -4,7 +4,7 @@ FROM docker.io/nginxproxy/docker-gen:0.14.7 AS docker-gen FROM docker.io/nginxproxy/forego:0.18.3 AS forego # Build the final image -FROM docker.io/library/nginx:1.27.5-alpine +FROM docker.io/library/nginx:1.29.0-alpine ARG NGINX_PROXY_VERSION # Add DOCKER_GEN_VERSION environment variable because diff --git a/Dockerfile.debian b/Dockerfile.debian index 5b612a8d3..ac1dc084d 100644 --- a/Dockerfile.debian +++ b/Dockerfile.debian @@ -4,7 +4,7 @@ FROM docker.io/nginxproxy/docker-gen:0.14.7-debian AS docker-gen FROM docker.io/nginxproxy/forego:0.18.3-debian AS forego # Build the final image -FROM docker.io/library/nginx:1.27.5 +FROM docker.io/library/nginx:1.29.0 ARG NGINX_PROXY_VERSION # Add DOCKER_GEN_VERSION environment variable because From f185cd43b7a8c94aed511c646f81cad83cbde4b6 Mon Sep 17 00:00:00 2001 From: Niek <100143256+SchoNie@users.noreply.github.com> Date: Wed, 9 Jul 2025 12:25:46 +0200 Subject: [PATCH 32/54] ci: recreate CRL with 3650 days expiration Recreate CRL files with 3650 days (10 years) expire date. --- ...ae5d1b655182b052fed458ec701f9ae1524e1c2.crl.pem | 14 +++++++------- test/test_ssl/certs_mtls/ca.crl.pem | 14 +++++++------- .../mtls-enabled.nginx-proxy.tld.crl.pem | 14 +++++++------- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/test/test_ssl/certs_mtls/9ae5d1b655182b052fed458ec701f9ae1524e1c2.crl.pem b/test/test_ssl/certs_mtls/9ae5d1b655182b052fed458ec701f9ae1524e1c2.crl.pem index a4bbd9562..de292c16d 100644 --- a/test/test_ssl/certs_mtls/9ae5d1b655182b052fed458ec701f9ae1524e1c2.crl.pem +++ b/test/test_ssl/certs_mtls/9ae5d1b655182b052fed458ec701f9ae1524e1c2.crl.pem @@ -1,13 +1,13 @@ -----BEGIN X509 CRL----- MIICADCB6QIBATANBgkqhkiG9w0BAQsFADAhMR8wHQYDVQQDDBZuZ2lueC1wcm94 -eS10ZXN0LXN1aXRlFw0yNTAxMDMxMjMwNTBaFw0yNTA3MDIxMjMwNTBaMDIwMAIR +eS10ZXN0LXN1aXRlFw0yNTA3MDkxMDEyNDFaFw0zNTA3MDcxMDEyNDFaMDIwMAIR AK8a1AmezG56vTp5WqtpnScXDTI1MDEwMzEyMzAwN1owDDAKBgNVHRUEAwoBBaBg MF4wXAYDVR0jBFUwU4AU9X5P1mF9ZBIYOSikqH40bUmpgRahJaQjMCExHzAdBgNV BAMMFm5naW54LXByb3h5LXRlc3Qtc3VpdGWCFDb0isAkQpzgE7F2pbikhN0SOn8t -MA0GCSqGSIb3DQEBCwUAA4IBAQCGaKW8kJy1Mznc3T2OHkCx8GudvOo0ZBsZ+pTm -sAnlxDQTIqm8e4gU19WF/SISlfr7qEERqif8+SlUgS9CWtJa70gk+9oobuWfBNIT -VXD4ujO/47nqt2MdRUSSGX+K+9Ox2gyU6kHO1ZrT8VmsL22Bhfa2Pw/3OBL/QHMU -b1hAZyed0CoPCnMqjG0X5zMo3ByGW3TkxG2GhzKCWLGXVbzdHFpS98hpkpaxvIlE -juSYuPItwEftHdB8JHAHL18uDJapZ5mOCuUn/HoZBWOudFjtFQUUzq4eTsB56My4 -qDGb1/ReAoGyheuV0fEtg9MJkGEuGrb38JN6hcdfpW5u0Hwb +MA0GCSqGSIb3DQEBCwUAA4IBAQAji33L7enDzhw8qNYLtMxrJuuLAMJeRDO4qYeI +pIJu38K+9RTKG2U/BPPKmdtos/M1NEVJrLqZ/eKHoEU/+u0f1pod3Vh2tAlyB+qp +aGwsg5o07hdB85VDAJ7zwPLFjHtChhhVTS5qOqidaSdVBE0/IFifWBEyHyC7yJDl +dlNY7jmarlmFnpDWmXqAdgMqNlS/t9KN8RtCjiHlF8lF+qjimCWAcfecMmdbAUFC +RFHmo6ENxmcDXQDRVqKAXMzmk/YAe0SCqdT0EsWSvUmRBKdtXSBHAQRz8hl2xI2Z +6CtJXYw6Oy4eA+Ge2JMSRUuEKYwpVSLGdxCoHAkZkz+2rU2X -----END X509 CRL----- diff --git a/test/test_ssl/certs_mtls/ca.crl.pem b/test/test_ssl/certs_mtls/ca.crl.pem index a4bbd9562..de292c16d 100644 --- a/test/test_ssl/certs_mtls/ca.crl.pem +++ b/test/test_ssl/certs_mtls/ca.crl.pem @@ -1,13 +1,13 @@ -----BEGIN X509 CRL----- MIICADCB6QIBATANBgkqhkiG9w0BAQsFADAhMR8wHQYDVQQDDBZuZ2lueC1wcm94 -eS10ZXN0LXN1aXRlFw0yNTAxMDMxMjMwNTBaFw0yNTA3MDIxMjMwNTBaMDIwMAIR +eS10ZXN0LXN1aXRlFw0yNTA3MDkxMDEyNDFaFw0zNTA3MDcxMDEyNDFaMDIwMAIR AK8a1AmezG56vTp5WqtpnScXDTI1MDEwMzEyMzAwN1owDDAKBgNVHRUEAwoBBaBg MF4wXAYDVR0jBFUwU4AU9X5P1mF9ZBIYOSikqH40bUmpgRahJaQjMCExHzAdBgNV BAMMFm5naW54LXByb3h5LXRlc3Qtc3VpdGWCFDb0isAkQpzgE7F2pbikhN0SOn8t -MA0GCSqGSIb3DQEBCwUAA4IBAQCGaKW8kJy1Mznc3T2OHkCx8GudvOo0ZBsZ+pTm -sAnlxDQTIqm8e4gU19WF/SISlfr7qEERqif8+SlUgS9CWtJa70gk+9oobuWfBNIT -VXD4ujO/47nqt2MdRUSSGX+K+9Ox2gyU6kHO1ZrT8VmsL22Bhfa2Pw/3OBL/QHMU -b1hAZyed0CoPCnMqjG0X5zMo3ByGW3TkxG2GhzKCWLGXVbzdHFpS98hpkpaxvIlE -juSYuPItwEftHdB8JHAHL18uDJapZ5mOCuUn/HoZBWOudFjtFQUUzq4eTsB56My4 -qDGb1/ReAoGyheuV0fEtg9MJkGEuGrb38JN6hcdfpW5u0Hwb +MA0GCSqGSIb3DQEBCwUAA4IBAQAji33L7enDzhw8qNYLtMxrJuuLAMJeRDO4qYeI +pIJu38K+9RTKG2U/BPPKmdtos/M1NEVJrLqZ/eKHoEU/+u0f1pod3Vh2tAlyB+qp +aGwsg5o07hdB85VDAJ7zwPLFjHtChhhVTS5qOqidaSdVBE0/IFifWBEyHyC7yJDl +dlNY7jmarlmFnpDWmXqAdgMqNlS/t9KN8RtCjiHlF8lF+qjimCWAcfecMmdbAUFC +RFHmo6ENxmcDXQDRVqKAXMzmk/YAe0SCqdT0EsWSvUmRBKdtXSBHAQRz8hl2xI2Z +6CtJXYw6Oy4eA+Ge2JMSRUuEKYwpVSLGdxCoHAkZkz+2rU2X -----END X509 CRL----- diff --git a/test/test_ssl/certs_mtls/mtls-enabled.nginx-proxy.tld.crl.pem b/test/test_ssl/certs_mtls/mtls-enabled.nginx-proxy.tld.crl.pem index a4bbd9562..de292c16d 100644 --- a/test/test_ssl/certs_mtls/mtls-enabled.nginx-proxy.tld.crl.pem +++ b/test/test_ssl/certs_mtls/mtls-enabled.nginx-proxy.tld.crl.pem @@ -1,13 +1,13 @@ -----BEGIN X509 CRL----- MIICADCB6QIBATANBgkqhkiG9w0BAQsFADAhMR8wHQYDVQQDDBZuZ2lueC1wcm94 -eS10ZXN0LXN1aXRlFw0yNTAxMDMxMjMwNTBaFw0yNTA3MDIxMjMwNTBaMDIwMAIR +eS10ZXN0LXN1aXRlFw0yNTA3MDkxMDEyNDFaFw0zNTA3MDcxMDEyNDFaMDIwMAIR AK8a1AmezG56vTp5WqtpnScXDTI1MDEwMzEyMzAwN1owDDAKBgNVHRUEAwoBBaBg MF4wXAYDVR0jBFUwU4AU9X5P1mF9ZBIYOSikqH40bUmpgRahJaQjMCExHzAdBgNV BAMMFm5naW54LXByb3h5LXRlc3Qtc3VpdGWCFDb0isAkQpzgE7F2pbikhN0SOn8t -MA0GCSqGSIb3DQEBCwUAA4IBAQCGaKW8kJy1Mznc3T2OHkCx8GudvOo0ZBsZ+pTm -sAnlxDQTIqm8e4gU19WF/SISlfr7qEERqif8+SlUgS9CWtJa70gk+9oobuWfBNIT -VXD4ujO/47nqt2MdRUSSGX+K+9Ox2gyU6kHO1ZrT8VmsL22Bhfa2Pw/3OBL/QHMU -b1hAZyed0CoPCnMqjG0X5zMo3ByGW3TkxG2GhzKCWLGXVbzdHFpS98hpkpaxvIlE -juSYuPItwEftHdB8JHAHL18uDJapZ5mOCuUn/HoZBWOudFjtFQUUzq4eTsB56My4 -qDGb1/ReAoGyheuV0fEtg9MJkGEuGrb38JN6hcdfpW5u0Hwb +MA0GCSqGSIb3DQEBCwUAA4IBAQAji33L7enDzhw8qNYLtMxrJuuLAMJeRDO4qYeI +pIJu38K+9RTKG2U/BPPKmdtos/M1NEVJrLqZ/eKHoEU/+u0f1pod3Vh2tAlyB+qp +aGwsg5o07hdB85VDAJ7zwPLFjHtChhhVTS5qOqidaSdVBE0/IFifWBEyHyC7yJDl +dlNY7jmarlmFnpDWmXqAdgMqNlS/t9KN8RtCjiHlF8lF+qjimCWAcfecMmdbAUFC +RFHmo6ENxmcDXQDRVqKAXMzmk/YAe0SCqdT0EsWSvUmRBKdtXSBHAQRz8hl2xI2Z +6CtJXYw6Oy4eA+Ge2JMSRUuEKYwpVSLGdxCoHAkZkz+2rU2X -----END X509 CRL----- From 76fc9bbdabc371d1978402ebe1e8b8522a39fdf7 Mon Sep 17 00:00:00 2001 From: Niek <100143256+SchoNie@users.noreply.github.com> Date: Wed, 9 Jul 2025 13:00:26 +0200 Subject: [PATCH 33/54] docs: note about CRL expiration --- docs/README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/README.md b/docs/README.md index feeb5f1d0..7ab167538 100644 --- a/docs/README.md +++ b/docs/README.md @@ -403,6 +403,9 @@ If you want to use a global CRL file you have to copy your CRL file named as `ca > [!NOTE] > Use Per-VIRTUAL_HOST CRL if you configured the [Per-VIRTUAL_HOST CA](#per-virtual_host-ca) or Global CRL if you configured the [Global CA](#global-ca) +> [!IMPORTANT] +> Make sure you rotate the CRL before it's expiration date, even if nothing has changed. An expired CRL will make Nginx unable to validate the certificates that were issued. + ### optional ssl_verify_client Optional [`ssl_verify_client`](https://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_verify_client) can be activated by using the `com.github.nginx-proxy.nginx-proxy.ssl_verify_client: "optional"` label on a proxied container. If this label is set on a proxied container access is not blocked but the result of the mTLS verify is stored in the [$ssl_client_verify](https://nginx.org/en/docs/http/ngx_http_ssl_module.html#var_ssl_client_verify) variable which you can use this in the [Per-VIRTUAL_HOST location](https://github.com/nginx-proxy/nginx-proxy/tree/main/docs#per-virtual_host-location-configuration) and [Per-VIRTUAL_PATH location](https://github.com/nginx-proxy/nginx-proxy/tree/main/docs#per-virtual_path-location-configuration) configurations. From ed3f93adde5004e10242e6efc19dd04c62c955fa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 23 Jul 2025 04:56:17 +0000 Subject: [PATCH 34/54] build: bump nginxproxy/docker-gen from 0.14.7-debian to 0.15.0-debian Bumps nginxproxy/docker-gen from 0.14.7-debian to 0.15.0-debian. --- updated-dependencies: - dependency-name: nginxproxy/docker-gen dependency-version: 0.15.0-debian dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- Dockerfile.alpine | 2 +- Dockerfile.debian | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile.alpine b/Dockerfile.alpine index 7996af553..11a010cea 100644 --- a/Dockerfile.alpine +++ b/Dockerfile.alpine @@ -1,5 +1,5 @@ # syntax=docker/dockerfile:1 -FROM docker.io/nginxproxy/docker-gen:0.14.7 AS docker-gen +FROM docker.io/nginxproxy/docker-gen:0.15.0 AS docker-gen FROM docker.io/nginxproxy/forego:0.18.3 AS forego diff --git a/Dockerfile.debian b/Dockerfile.debian index ac1dc084d..a51d5cccb 100644 --- a/Dockerfile.debian +++ b/Dockerfile.debian @@ -1,5 +1,5 @@ # syntax=docker/dockerfile:1 -FROM docker.io/nginxproxy/docker-gen:0.14.7-debian AS docker-gen +FROM docker.io/nginxproxy/docker-gen:0.15.0-debian AS docker-gen FROM docker.io/nginxproxy/forego:0.18.3-debian AS forego From e6d78e747466cc6821b761ce50ea66e22393fbd7 Mon Sep 17 00:00:00 2001 From: Nicolas Duchon Date: Sat, 26 Jul 2025 18:27:18 +0200 Subject: [PATCH 35/54] docs: update nginx version badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e0d028d62..d691e670f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ [![Test](https://github.com/nginx-proxy/nginx-proxy/actions/workflows/test.yml/badge.svg)](https://github.com/nginx-proxy/nginx-proxy/actions/workflows/test.yml) [![GitHub release](https://img.shields.io/github/v/release/nginx-proxy/nginx-proxy)](https://github.com/nginx-proxy/nginx-proxy/releases) -[![nginx 1.27.5](https://img.shields.io/badge/nginx-1.27.5-brightgreen.svg?logo=nginx)](https://nginx.org/en/CHANGES) +[![nginx 1.29.0](https://img.shields.io/badge/nginx-1.29.0-brightgreen.svg?logo=nginx)](https://nginx.org/en/CHANGES) [![Docker Image Size](https://img.shields.io/docker/image-size/nginxproxy/nginx-proxy?sort=semver)](https://hub.docker.com/r/nginxproxy/nginx-proxy "Click to view the image on Docker Hub") [![Docker stars](https://img.shields.io/docker/stars/nginxproxy/nginx-proxy.svg)](https://hub.docker.com/r/nginxproxy/nginx-proxy "DockerHub") [![Docker pulls](https://img.shields.io/docker/pulls/nginxproxy/nginx-proxy.svg)](https://hub.docker.com/r/nginxproxy/nginx-proxy "DockerHub") From 8911ceffafa12784ed610123adcab595acf8011b Mon Sep 17 00:00:00 2001 From: JamBalaya56562 Date: Sun, 27 Jul 2025 09:22:12 +0900 Subject: [PATCH 36/54] docs: bump docker image version --- README.md | 6 +++--- docs/README.md | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index d691e670f..132bb6738 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ docker run --detach \ --name nginx-proxy \ --publish 80:80 \ --volume /var/run/docker.sock:/tmp/docker.sock:ro \ - nginxproxy/nginx-proxy:1.7 + nginxproxy/nginx-proxy:1.8 ``` Then start any containers (here an nginx container) you want proxied with an env var `VIRTUAL_HOST=subdomain.yourdomain.com` @@ -48,7 +48,7 @@ The nginx-proxy images are available in two flavors. This image is based on the nginx:mainline image, itself based on the debian slim image. ```console -docker pull nginxproxy/nginx-proxy:1.7 +docker pull nginxproxy/nginx-proxy:1.8 ``` #### Alpine based version (`-alpine` suffix) @@ -56,7 +56,7 @@ docker pull nginxproxy/nginx-proxy:1.7 This image is based on the nginx:alpine image. ```console -docker pull nginxproxy/nginx-proxy:1.7-alpine +docker pull nginxproxy/nginx-proxy:1.8-alpine ``` > [!IMPORTANT] diff --git a/docs/README.md b/docs/README.md index 7ab167538..90f5bb48f 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1460,7 +1460,7 @@ curl -s -H "Host: test.nginx-proxy.tld" localhost/nginx-proxy-debug | jq "https_method": "redirect", "log_format": null, "log_format_escape": null, - "nginx_proxy_version": "1.7.0", + "nginx_proxy_version": "1.8.0", "resolvers": "127.0.0.11", "sha1_upstream_name": false, "ssl_policy": "Mozilla-Intermediate", From 872e5b564639282396b69c1ee3b588817978c6b0 Mon Sep 17 00:00:00 2001 From: Antonio Mika Date: Tue, 18 Feb 2025 15:08:34 -0500 Subject: [PATCH 37/54] feat: global proxy protocol support --- docs/README.md | 20 ++++++ nginx.tmpl | 64 +++++++++++++------ .../test_proxy-protocol-global-disabled.py | 58 +++++++++++++++++ .../test_proxy-protocol-global-disabled.yml | 12 ++++ .../test_proxy-protocol-global-enabled.py | 31 +++++++++ .../test_proxy-protocol-global-enabled.yml | 12 ++++ 6 files changed, 179 insertions(+), 18 deletions(-) create mode 100644 test/test_proxy_protocol/test_proxy-protocol-global-disabled.py create mode 100644 test/test_proxy_protocol/test_proxy-protocol-global-disabled.yml create mode 100644 test/test_proxy_protocol/test_proxy-protocol-global-enabled.py create mode 100644 test/test_proxy_protocol/test_proxy-protocol-global-enabled.yml diff --git a/docs/README.md b/docs/README.md index 7ab167538..924c9f0d5 100644 --- a/docs/README.md +++ b/docs/README.md @@ -805,6 +805,25 @@ For legacy compatibility reasons, `nginx-proxy` forwards any client-supplied `X- The default for `TRUST_DOWNSTREAM_PROXY` may change to `false` in a future version of `nginx-proxy`. If you require it to be enabled, you are encouraged to explicitly set it to `true` to avoid compatibility problems when upgrading. +### Proxy Protocol Support + +`nginx-proxy` has support for the [Proxy Protocol](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt). This allows a separate proxy to send requests to `nginx-proxy` and encode information about the client connection without relying on HTTP headers. This can be enabled by setting `ENABLE_PROXY_PROTOCOL=true` on the main `nginx-proxy` container. It's important to note that enabling the proxy protocol will require all connections to `nginx-proxy` to use the protocol. + +You can use this feature in conjunction with the `realip` module in nginx. This will allow for setting the `$remote_addr` and `$remote_port` nginx variables to the IP and port that are provided from the protocol message. Documenation for this functionality can be found in the [nginx documentation](https://nginx.org/en/docs/http/ngx_http_realip_module.html). + +A simple example is as follows: + +1. Create a configuration file for nginx, this can be global (in `conf.d`) or host specific (in `vhost.d`) +2. Add your `realip` configuration: + +```nginx +# Your proxy server ip address +set_real_ip_from 192.168.1.0/24; + +# Where to replace `$remote_addr` and `$remote_port` from +real_ip_header proxy_protocol; +``` + ⬆️ [back to table of contents](#table-of-contents) ## Custom Nginx Configuration @@ -1451,6 +1470,7 @@ curl -s -H "Host: test.nginx-proxy.tld" localhost/nginx-proxy-debug | jq "enable_debug_endpoint": "true", "enable_http2": "true", "enable_http3": "false", + "enable_proxy_protocol": "false", "enable_http_on_missing_cert": "true", "enable_ipv6": false, "enable_json_logs": false, diff --git a/nginx.tmpl b/nginx.tmpl index 939aa2e9c..cf8ccb27e 100644 --- a/nginx.tmpl +++ b/nginx.tmpl @@ -31,6 +31,7 @@ {{- $_ := 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_proxy_protocol" ($globals.Env.ENABLE_PROXY_PROTOCOL | default "false" | parseBool) }} {{- $_ := 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 "non_get_redirect" ($globals.Env.NON_GET_REDIRECT | default "301") }} @@ -440,6 +441,19 @@ upstream {{ $vpath.upstream }} { {{- when .Enable "access_log /var/log/nginx/access.log vhost;" "" }} {{- end }} +map $proxy_add_x_forwarded_for $proxy_x_forwarded_for { + {{- if $globals.config.trust_downstream_proxy }} + {{- if $globals.config.enable_proxy_protocol }} + default $proxy_protocol_addr; + {{- else }} + default $proxy_add_x_forwarded_for; + {{- end }} + {{- else }} + default $remote_addr; + {{- end }} + '' $remote_addr; +} + # 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 { @@ -454,8 +468,20 @@ map $http_x_forwarded_host $proxy_x_forwarded_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.config.trust_downstream_proxy }}$http_x_forwarded_port{{ else }}$server_port{{ end }}; +map $http_x_forwarded_port $_proxy_x_forwarded_port { + {{ if $globals.config.trust_downstream_proxy }} + {{ if $globals.config.enable_proxy_protocol }} + default $proxy_protocol_server_port; + {{- else }} + default $http_x_forwarded_port; + {{- end }} + {{- else }} + default $server_port; + {{- end }} +} + +map $_proxy_x_forwarded_port $proxy_x_forwarded_port { + default $_proxy_x_forwarded_port; '' $server_port; } @@ -559,7 +585,7 @@ proxy_set_header Host $host$host_port; proxy_set_header Upgrade $http_upgrade; 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-For $proxy_x_forwarded_for; proxy_set_header X-Forwarded-Host $proxy_x_forwarded_host; proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto; proxy_set_header X-Forwarded-Ssl $proxy_x_forwarded_ssl; @@ -821,6 +847,7 @@ proxy_set_header Proxy ""; {{- $default_https_exists = or $default_https_exists (and $https $vhost.default) }} {{- $http3_enabled = or $http3_enabled $vhost.http3_enabled }} {{- end }} + {{- $proxy_protocol := when $globals.config.enable_proxy_protocol " proxy_protocol" "" }} {{- $fallback_http := not $default_http_exists }} {{- $fallback_https := not $default_https_exists }} {{- /* @@ -838,21 +865,21 @@ server { {{ template "access_log" (dict "Enable" $globals.config.enable_access_log) }} http2 on; {{- if $fallback_http }} - listen {{ $globals.config.external_http_port }}; {{- /* Do not add `default_server` (see comment above). */}} + listen {{ $globals.config.external_http_port }} {{- $proxy_protocol }}; {{- /* 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). */}} + listen [::]:{{ $globals.config.external_http_port }} {{- $proxy_protocol }}; {{- /* Do not add `default_server` (see comment above). */}} {{- end }} {{- end }} {{- if $fallback_https }} - listen {{ $globals.config.external_https_port }} ssl; {{- /* Do not add `default_server` (see comment above). */}} + listen {{ $globals.config.external_https_port }} ssl {{- $proxy_protocol }}; {{- /* 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). */}} + listen [::]:{{ $globals.config.external_https_port }} ssl {{- $proxy_protocol }}; {{- /* Do not add `default_server` (see comment above). */}} {{- end }} {{- if $http3_enabled }} http3 on; - listen {{ $globals.config.external_https_port }} quic reuseport; {{- /* Do not add `default_server` (see comment above). */}} + listen {{ $globals.config.external_https_port }} quic reuseport {{- $proxy_protocol }}; {{- /* 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). */}} + listen [::]:{{ $globals.config.external_https_port }} quic reuseport {{- $proxy_protocol }}; {{- /* Do not add `default_server` (see comment above). */}} {{- end }} {{- end }} ssl_session_cache shared:SSL:50m; @@ -892,7 +919,8 @@ server { {{- end }} {{- range $hostname, $vhost := $globals.vhosts }} - {{- $default_server := when $vhost.default "default_server" "" }} + {{- $default_server := when $vhost.default " default_server" "" }} + {{- $proxy_protocol := when $globals.config.enable_proxy_protocol " proxy_protocol" "" }} {{- range $path, $vpath := $vhost.paths }} # {{ $hostname }}{{ $path }} @@ -906,9 +934,9 @@ server { server_tokens {{ $vhost.server_tokens }}; {{- end }} {{ template "access_log" (dict "Enable" $globals.config.enable_access_log) }} - listen {{ $globals.config.external_http_port }} {{ $default_server }}; + listen {{ $globals.config.external_http_port }} {{- $default_server }} {{- $proxy_protocol }}; {{- if $globals.config.enable_ipv6 }} - listen [::]:{{ $globals.config.external_http_port }} {{ $default_server }}; + listen [::]:{{ $globals.config.external_http_port }} {{- $default_server }} {{- $proxy_protocol }}; {{- end }} {{- if (or $vhost.acme_http_challenge_legacy $vhost.acme_http_challenge_enabled) }} @@ -967,9 +995,9 @@ server { http2 on; {{- end }} {{- if or (eq $vhost.https_method "nohttps") (eq $vhost.https_method "noredirect") }} - listen {{ $globals.config.external_http_port }} {{ $default_server }}; + listen {{ $globals.config.external_http_port }} {{- $default_server }} {{- $proxy_protocol }}; {{- if $globals.config.enable_ipv6 }} - listen [::]:{{ $globals.config.external_http_port }} {{ $default_server }}; + listen [::]:{{ $globals.config.external_http_port }} {{- $default_server }} {{- $proxy_protocol }}; {{- end }} {{- if (and (eq $vhost.https_method "noredirect") $vhost.acme_http_challenge_enabled) }} @@ -984,17 +1012,17 @@ server { {{- end }} {{- end }} {{- if ne $vhost.https_method "nohttps" }} - listen {{ $globals.config.external_https_port }} ssl {{ $default_server }}; + listen {{ $globals.config.external_https_port }} ssl {{- $default_server }} {{- $proxy_protocol }}; {{- if $globals.config.enable_ipv6 }} - listen [::]:{{ $globals.config.external_https_port }} ssl {{ $default_server }}; + listen [::]:{{ $globals.config.external_https_port }} ssl {{- $default_server }} {{- $proxy_protocol }}; {{- end }} {{- if $vhost.http3_enabled }} http3 on; add_header alt-svc 'h3=":{{ $globals.config.external_https_port }}"; ma=86400;'; - listen {{ $globals.config.external_https_port }} quic {{ $default_server }}; + listen {{ $globals.config.external_https_port }} quic {{- $default_server }} {{- $proxy_protocol }}; {{- if $globals.config.enable_ipv6 }} - listen [::]:{{ $globals.config.external_https_port }} quic {{ $default_server }}; + listen [::]:{{ $globals.config.external_https_port }} quic {{- $default_server }} {{- $proxy_protocol }}; {{- end }} {{- end }} diff --git a/test/test_proxy_protocol/test_proxy-protocol-global-disabled.py b/test/test_proxy_protocol/test_proxy-protocol-global-disabled.py new file mode 100644 index 000000000..b4ea76fc7 --- /dev/null +++ b/test/test_proxy_protocol/test_proxy-protocol-global-disabled.py @@ -0,0 +1,58 @@ +import socket + + +def test_proxy_protocol_global_disabled_X_Forwarded_For_is_generated( + docker_compose, nginxproxy +): + r = nginxproxy.get("http://proxy-protocol-global-disabled.nginx-proxy.tld/headers") + assert r.status_code == 200 + assert "X-Forwarded-For:" in r.text + + +def test_proxy_protocol_global_disabled_X_Forwarded_For_is_passed_on( + docker_compose, nginxproxy +): + r = nginxproxy.get( + "http://proxy-protocol-global-disabled.nginx-proxy.tld/headers", + headers={"X-Forwarded-For": "1.2.3.4"}, + ) + assert r.status_code == 200 + assert "X-Forwarded-For: 1.2.3.4, " in r.text + + +def test_proxy_protocol_global_disabled_X_Forwarded_Port_is_generated( + docker_compose, nginxproxy +): + r = nginxproxy.get("http://proxy-protocol-global-disabled.nginx-proxy.tld/headers") + assert r.status_code == 200 + assert "X-Forwarded-Port: 80\n" in r.text + + +def test_proxy_protocol_global_disabled_X_Forwarded_Port_is_passed_on( + docker_compose, nginxproxy +): + r = nginxproxy.get( + "http://proxy-protocol-global-disabled.nginx-proxy.tld/headers", + headers={"X-Forwarded-Port": "1234"}, + ) + assert r.status_code == 200 + assert "X-Forwarded-Port: 1234\n" in r.text + + +def test_proxy_protocol_global_disabled_proto_request_fails(docker_compose, nginxproxy): + client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + client.connect((nginxproxy.get_ip(), 80)) + + # 1.2.3.4 is the client IP + # 4.3.2.1 is the proxy server IP + # 8080 is the client port + # 9090 is the proxy server port + client.send(f"PROXY TCP4 1.2.3.4 4.3.2.1 8080 9090\r\n".encode("utf-8")) + client.send( + "GET /headers HTTP/1.1\r\nHost: proxy-protocol-global-enabled.nginx-proxy.tld\r\n\r\n".encode( + "utf-8" + ) + ) + + response = client.recv(4096).decode("utf-8") + assert "HTTP/1.1 400 Bad Request" in response diff --git a/test/test_proxy_protocol/test_proxy-protocol-global-disabled.yml b/test/test_proxy_protocol/test_proxy-protocol-global-disabled.yml new file mode 100644 index 000000000..593e21f8d --- /dev/null +++ b/test/test_proxy_protocol/test_proxy-protocol-global-disabled.yml @@ -0,0 +1,12 @@ +services: +# nginx-proxy: +# environment: +# ENABLE_PROXY_PROTOCOL: "false" #Disabled by default + + proxy-protocol-global-disabled: + image: web + expose: + - "80" + environment: + WEB_PORTS: "80" + VIRTUAL_HOST: proxy-protocol-global-disabled.nginx-proxy.tld diff --git a/test/test_proxy_protocol/test_proxy-protocol-global-enabled.py b/test/test_proxy_protocol/test_proxy-protocol-global-enabled.py new file mode 100644 index 000000000..48a8f7e71 --- /dev/null +++ b/test/test_proxy_protocol/test_proxy-protocol-global-enabled.py @@ -0,0 +1,31 @@ +import socket + + +def test_proxy_protocol_global_enabled_normal_request_fails(docker_compose, nginxproxy): + try: + r = nginxproxy.get( + "http://proxy-protocol-global-enabled.nginx-proxy.tld/headers" + ) + assert False + except Exception as e: + assert "Remote end closed connection without response" in str(e) + + +def test_proxy_protocol_global_enabled_proto_request_works(docker_compose, nginxproxy): + client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + client.connect((nginxproxy.get_ip(), 80)) + + # 1.2.3.4 is the client IP + # 4.3.2.1 is the proxy server IP + # 8080 is the client port + # 9090 is the proxy server port + client.send(f"PROXY TCP4 1.2.3.4 4.3.2.1 8080 9090\r\n".encode("utf-8")) + client.send( + "GET /headers HTTP/1.1\r\nHost: proxy-protocol-global-enabled.nginx-proxy.tld\r\n\r\n".encode( + "utf-8" + ) + ) + + response = client.recv(4096).decode("utf-8") + assert "X-Forwarded-For: 1.2.3.4" in response + assert "X-Forwarded-Port: 9090" in response diff --git a/test/test_proxy_protocol/test_proxy-protocol-global-enabled.yml b/test/test_proxy_protocol/test_proxy-protocol-global-enabled.yml new file mode 100644 index 000000000..04f7a379e --- /dev/null +++ b/test/test_proxy_protocol/test_proxy-protocol-global-enabled.yml @@ -0,0 +1,12 @@ +services: + nginx-proxy: + environment: + ENABLE_PROXY_PROTOCOL: "true" + + proxy-protocol-global-enabled: + image: web + expose: + - "80" + environment: + WEB_PORTS: "80" + VIRTUAL_HOST: proxy-protocol-global-enabled.nginx-proxy.tld From 4c86502dc440ea957f6a0885ea8eaedd886aa4f3 Mon Sep 17 00:00:00 2001 From: Antonio Mika Date: Sun, 27 Jul 2025 08:22:41 -0400 Subject: [PATCH 38/54] docs: fix spelling Co-authored-by: Niek <100143256+SchoNie@users.noreply.github.com> --- docs/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/README.md b/docs/README.md index 924c9f0d5..a7c9cbfbc 100644 --- a/docs/README.md +++ b/docs/README.md @@ -809,7 +809,7 @@ The default for `TRUST_DOWNSTREAM_PROXY` may change to `false` in a future versi `nginx-proxy` has support for the [Proxy Protocol](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt). This allows a separate proxy to send requests to `nginx-proxy` and encode information about the client connection without relying on HTTP headers. This can be enabled by setting `ENABLE_PROXY_PROTOCOL=true` on the main `nginx-proxy` container. It's important to note that enabling the proxy protocol will require all connections to `nginx-proxy` to use the protocol. -You can use this feature in conjunction with the `realip` module in nginx. This will allow for setting the `$remote_addr` and `$remote_port` nginx variables to the IP and port that are provided from the protocol message. Documenation for this functionality can be found in the [nginx documentation](https://nginx.org/en/docs/http/ngx_http_realip_module.html). +You can use this feature in conjunction with the `realip` module in nginx. This will allow for setting the `$remote_addr` and `$remote_port` nginx variables to the IP and port that are provided from the protocol message. Documentation for this functionality can be found in the [nginx documentation](https://nginx.org/en/docs/http/ngx_http_realip_module.html). A simple example is as follows: From 121e7020fc1a58180ff14da14dcbf516f8717c98 Mon Sep 17 00:00:00 2001 From: Nicolas Duchon Date: Sun, 27 Jul 2025 15:27:56 +0200 Subject: [PATCH 39/54] docs: add proxy protocol to configuration summary --- docs/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/README.md b/docs/README.md index a7c9cbfbc..cb44418e0 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1348,6 +1348,7 @@ Configuration available either on the nginx-proxy container, or the docker-gen c | [`ENABLE_HTTP2`](#http2-support) | `true` | | [`ENABLE_HTTP3`](#http3-support) | `false` | | [`ENABLE_IPV6`](#listening-on-ipv6) | `false` | +| [`ENABLE_PROXY_PROTOCOL`](#proxy-protocol-support) | `false` | | [`HTTP_PORT`](#custom-external-httphttps-ports) | `80` | | [`HTTPS_PORT`](#custom-external-httphttps-ports) | `443` | | [`HTTPS_METHOD`](#how-ssl-support-works) | `redirect` | From 7391ce96fe130a24cb5ff5de636c83ae018a3005 Mon Sep 17 00:00:00 2001 From: Artem Ukrainskii Date: Sat, 2 Aug 2025 02:38:17 +0300 Subject: [PATCH 40/54] feat: pass X-Real-IP header to gRPC backends --- nginx.tmpl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nginx.tmpl b/nginx.tmpl index cf8ccb27e..523ad05b4 100644 --- a/nginx.tmpl +++ b/nginx.tmpl @@ -321,8 +321,10 @@ {{- end }} {{- else if eq $proto "grpc" }} grpc_pass {{ trim $proto }}://{{ trim $upstream }}; + grpc_set_header X-Real-IP $remote_addr; {{- else if eq $proto "grpcs" }} grpc_pass {{ trim $proto }}://{{ trim $upstream }}; + grpc_set_header X-Real-IP $remote_addr; {{- else }} proxy_pass {{ trim $proto }}://{{ trim $upstream }}{{ trim $dest }}; set $upstream_keepalive {{ if ne $keepalive "disabled" }}true{{ else }}false{{ end }}; From f56261606a2d3908b2d16ddb412938530b563822 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Aug 2025 09:08:56 +0000 Subject: [PATCH 41/54] ci: bump actions/checkout from 4 to 5 Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/build-publish-dispatch.yml | 2 +- .github/workflows/build-publish.yml | 2 +- .github/workflows/dockerhub-description.yml | 2 +- .github/workflows/test.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-publish-dispatch.yml b/.github/workflows/build-publish-dispatch.yml index 145e4dc1e..1c4138553 100644 --- a/.github/workflows/build-publish-dispatch.yml +++ b/.github/workflows/build-publish-dispatch.yml @@ -17,7 +17,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 0 diff --git a/.github/workflows/build-publish.yml b/.github/workflows/build-publish.yml index defbaf936..b88108a14 100644 --- a/.github/workflows/build-publish.yml +++ b/.github/workflows/build-publish.yml @@ -28,7 +28,7 @@ jobs: if: (github.event_name == 'schedule' && github.repository == 'nginx-proxy/nginx-proxy') || (github.event_name != 'schedule') steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 0 diff --git a/.github/workflows/dockerhub-description.yml b/.github/workflows/dockerhub-description.yml index 4be8cc29f..5656884e7 100644 --- a/.github/workflows/dockerhub-description.yml +++ b/.github/workflows/dockerhub-description.yml @@ -15,7 +15,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Docker Hub Description uses: peter-evans/dockerhub-description@v4 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 390db4f79..233a3a201 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -24,7 +24,7 @@ jobs: fail-fast: false steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up Python 3.12 uses: actions/setup-python@v5 From 310028cac3d5debc305a892a4004f301d61989db Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 15 Aug 2025 04:17:33 +0000 Subject: [PATCH 42/54] build: bump library/nginx from 1.29.0 to 1.29.1 Bumps library/nginx from 1.29.0 to 1.29.1. --- updated-dependencies: - dependency-name: library/nginx dependency-version: 1.29.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Dockerfile.alpine | 2 +- Dockerfile.debian | 2 +- README.md | 2 +- test/certs/create_server_certificate.sh | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Dockerfile.alpine b/Dockerfile.alpine index 11a010cea..a1e73e5d2 100644 --- a/Dockerfile.alpine +++ b/Dockerfile.alpine @@ -4,7 +4,7 @@ FROM docker.io/nginxproxy/docker-gen:0.15.0 AS docker-gen FROM docker.io/nginxproxy/forego:0.18.3 AS forego # Build the final image -FROM docker.io/library/nginx:1.29.0-alpine +FROM docker.io/library/nginx:1.29.1-alpine ARG NGINX_PROXY_VERSION # Add DOCKER_GEN_VERSION environment variable because diff --git a/Dockerfile.debian b/Dockerfile.debian index a51d5cccb..52f712c97 100644 --- a/Dockerfile.debian +++ b/Dockerfile.debian @@ -4,7 +4,7 @@ FROM docker.io/nginxproxy/docker-gen:0.15.0-debian AS docker-gen FROM docker.io/nginxproxy/forego:0.18.3-debian AS forego # Build the final image -FROM docker.io/library/nginx:1.29.0 +FROM docker.io/library/nginx:1.29.1 ARG NGINX_PROXY_VERSION # Add DOCKER_GEN_VERSION environment variable because diff --git a/README.md b/README.md index 132bb6738..5349ddd21 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ [![Test](https://github.com/nginx-proxy/nginx-proxy/actions/workflows/test.yml/badge.svg)](https://github.com/nginx-proxy/nginx-proxy/actions/workflows/test.yml) [![GitHub release](https://img.shields.io/github/v/release/nginx-proxy/nginx-proxy)](https://github.com/nginx-proxy/nginx-proxy/releases) -[![nginx 1.29.0](https://img.shields.io/badge/nginx-1.29.0-brightgreen.svg?logo=nginx)](https://nginx.org/en/CHANGES) +[![nginx 1.29.1](https://img.shields.io/badge/nginx-1.29.1-brightgreen.svg?logo=nginx)](https://nginx.org/en/CHANGES) [![Docker Image Size](https://img.shields.io/docker/image-size/nginxproxy/nginx-proxy?sort=semver)](https://hub.docker.com/r/nginxproxy/nginx-proxy "Click to view the image on Docker Hub") [![Docker stars](https://img.shields.io/docker/stars/nginxproxy/nginx-proxy.svg)](https://hub.docker.com/r/nginxproxy/nginx-proxy "DockerHub") [![Docker pulls](https://img.shields.io/docker/pulls/nginxproxy/nginx-proxy.svg)](https://hub.docker.com/r/nginxproxy/nginx-proxy "DockerHub") diff --git a/test/certs/create_server_certificate.sh b/test/certs/create_server_certificate.sh index ac3701572..c2e28e112 100755 --- a/test/certs/create_server_certificate.sh +++ b/test/certs/create_server_certificate.sh @@ -24,7 +24,7 @@ fi # Create a nginx container (which conveniently provides the `openssl` command) ############################################################################### -CONTAINER=$(docker run -d -v $DIR:/work -w /work -e SAN="$ALTERNATE_DOMAINS" nginx:1.27.5) +CONTAINER=$(docker run -d -v $DIR:/work -w /work -e SAN="$ALTERNATE_DOMAINS" nginx:1.29.1) # Configure openssl docker exec $CONTAINER bash -c ' mkdir -p /ca/{certs,crl,private,newcerts} 2>/dev/null From 3a26fe1e292b65b8d4ce964299f9f7ee46315d75 Mon Sep 17 00:00:00 2001 From: Nicolas Duchon Date: Sat, 16 Aug 2025 11:14:51 +0200 Subject: [PATCH 43/54] ci: add workflows permissions --- .github/workflows/build-publish-dispatch.yml | 4 ++++ .github/workflows/build-publish.yml | 4 ++++ .github/workflows/dockerhub-description.yml | 3 +++ .github/workflows/test.yml | 3 +++ 4 files changed, 14 insertions(+) diff --git a/.github/workflows/build-publish-dispatch.yml b/.github/workflows/build-publish-dispatch.yml index 1c4138553..3dc1d3d95 100644 --- a/.github/workflows/build-publish-dispatch.yml +++ b/.github/workflows/build-publish-dispatch.yml @@ -1,5 +1,9 @@ name: Build and publish Docker images on demand +permissions: + contents: read + packages: write + on: workflow_dispatch: inputs: diff --git a/.github/workflows/build-publish.yml b/.github/workflows/build-publish.yml index b88108a14..a86ab0d9d 100644 --- a/.github/workflows/build-publish.yml +++ b/.github/workflows/build-publish.yml @@ -1,5 +1,9 @@ name: Build and publish Docker images +permissions: + contents: read + packages: write + on: workflow_dispatch: schedule: diff --git a/.github/workflows/dockerhub-description.yml b/.github/workflows/dockerhub-description.yml index 5656884e7..dfcab9342 100644 --- a/.github/workflows/dockerhub-description.yml +++ b/.github/workflows/dockerhub-description.yml @@ -1,5 +1,8 @@ name: Update Docker Hub Description +permissions: + contents: read + on: push: branches: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 233a3a201..14fe66812 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,5 +1,8 @@ name: Tests +permissions: + contents: read + on: workflow_dispatch: push: From 32743b04ac1c07d68d68206f0358394ade3b2132 Mon Sep 17 00:00:00 2001 From: Sjoerd de Wit Date: Sun, 17 Aug 2025 17:31:38 +0200 Subject: [PATCH 44/54] fix: prevent incorrect loading of wildcard domains in vhost configuration Co-authored-by: Sjoerd de Wit Co-authored-by: Minjong Kim --- nginx.tmpl | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/nginx.tmpl b/nginx.tmpl index 523ad05b4..b3cf568eb 100644 --- a/nginx.tmpl +++ b/nginx.tmpl @@ -65,9 +65,9 @@ # (none) {{- end }} {{- else }} -# /!\ WARNING: Failed to find the Docker container labeled "{{ $globals.config.nginx_container_label }}" or the one running docker-gen. +# /!\ WARNING: Failed to find the Docker container labeled "{{ $globals.config.nginx_container_label }}" or the one running docker-gen. # All upstream (backend) application containers will appear to be unreachable. -# Try removing the -only-exposed and -only-published arguments to docker-gen if you pass either of those. +# Try removing the -only-exposed and -only-published arguments to docker-gen if you pass either of those. # See https://github.com/nginx-proxy/docker-gen/issues/458. {{- end }} @@ -291,7 +291,7 @@ {{- $override = printf "/etc/nginx/vhost.d/%s_location_override" .Host }} {{- end }} {{- if exists $override }} - include {{ $override }}; + include {{ printf "%s" (replace $override "*" "\\*" -1) }}; {{- else }} {{- $keepalive := $vpath.keepalive }} location {{ .Path }} { @@ -339,9 +339,9 @@ {{- end }} {{- if (exists (printf "/etc/nginx/vhost.d/%s_%s_location" .Host (sha1 .Path) )) }} - include {{ printf "/etc/nginx/vhost.d/%s_%s_location" .Host (sha1 .Path) }}; + include {{ printf "/etc/nginx/vhost.d/%s_%s_location" (replace .Host "*" "\\*" -1) (sha1 .Path) }}; {{- else if (exists (printf "/etc/nginx/vhost.d/%s_location" .Host)) }} - include {{ printf "/etc/nginx/vhost.d/%s_location" .Host}}; + include {{ printf "/etc/nginx/vhost.d/%s_location" (replace .Host "*" "\\*" -1) }}; {{- else if (exists "/etc/nginx/vhost.d/default_location") }} include /etc/nginx/vhost.d/default_location; {{- end }} @@ -788,7 +788,7 @@ proxy_set_header Proxy ""; {{- $ssl_policy := groupByKeys $vhost_containers "Env.SSL_POLICY" | first | default "" }} {{- /* Get ssl_verify_client defined by containers w/ the same vhost, falling back to "on" */}} - {{- $ssl_verify_client := groupByLabel $vhost_containers "com.github.nginx-proxy.nginx-proxy.ssl_verify_client" | keys | first | default "on" }} + {{- $ssl_verify_client := groupByLabel $vhost_containers "com.github.nginx-proxy.nginx-proxy.ssl_verify_client" | keys | first | default "on" }} {{- /* Get the HSTS defined by containers w/ the same vhost, falling back to "max-age=31536000". */}} {{- $hsts := groupByKeys $vhost_containers "Env.HSTS" | first | default $globals.config.hsts }} @@ -811,7 +811,7 @@ proxy_set_header Proxy ""; "acme_http_challenge_enabled" $acme_http_challenge_enabled "server_tokens" $server_tokens "ssl_policy" $ssl_policy - "ssl_verify_client" $ssl_verify_client + "ssl_verify_client" $ssl_verify_client "trust_default_cert" $trust_default_cert "upstream_name" $upstream_name "vhost_root" $vhost_root @@ -1067,7 +1067,7 @@ server { {{- $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 }}; + include {{ printf "/etc/nginx/vhost.d/%s" (replace $vhostFileName "*" "\\*" -1) }}; {{- else if (exists "/etc/nginx/vhost.d/default") }} include /etc/nginx/vhost.d/default; {{- end }} @@ -1075,11 +1075,11 @@ server { {{/* SSL Client Certificate Validation */}} {{/* If vhost(hash).ca.crt exists, include CA */}} {{- if (exists (printf "/etc/nginx/certs/%s.ca.crt" $vhostFileName)) }} - ssl_client_certificate {{ printf "/etc/nginx/certs/%s.ca.crt" $vhostFileName }}; + ssl_client_certificate {{ printf "/etc/nginx/certs/%s.ca.crt" $vhostFileName }}; ssl_verify_client {{ $vhost.ssl_verify_client }}; {{/* If vhost(hash).crl.pem exists, include CRL */}} {{- if (exists (printf "/etc/nginx/certs/%s.crl.pem" $vhostFileName)) }} - ssl_crl {{ printf "/etc/nginx/certs/%s.crl.pem" $vhostFileName }}; + ssl_crl {{ printf "/etc/nginx/certs/%s.crl.pem" $vhostFileName }}; {{ end }} {{/* Else if no vhost CA file exists, but a global ca.crt exists include it */}} {{ else if (exists "/etc/nginx/certs/ca.crt") }} From 572537dda52376be29001b22b532d58171c6f394 Mon Sep 17 00:00:00 2001 From: Nicolas Duchon Date: Tue, 19 Aug 2025 14:16:25 +0200 Subject: [PATCH 45/54] test: default custom config for wildcard hosts --- test/test_custom/test_defaults.py | 7 +++++++ test/test_custom/test_defaults.yml | 8 ++++++++ ...est_defaults-location.py => test_location-defaults.py} | 8 +++++++- ...t_defaults-location.yml => test_location-defaults.yml} | 8 ++++++++ test/test_custom/test_proxy-wide.py | 7 +++++++ test/test_custom/test_proxy-wide.yml | 8 ++++++++ 6 files changed, 45 insertions(+), 1 deletion(-) rename test/test_custom/{test_defaults-location.py => test_location-defaults.py} (78%) rename test/test_custom/{test_defaults-location.yml => test_location-defaults.yml} (80%) diff --git a/test/test_custom/test_defaults.py b/test/test_custom/test_defaults.py index dc0fec9ce..74e17c99b 100644 --- a/test/test_custom/test_defaults.py +++ b/test/test_custom/test_defaults.py @@ -16,3 +16,10 @@ def test_custom_conf_applies_to_web2(docker_compose, nginxproxy): assert r.text == "answer from port 82\n" assert "X-test" in r.headers assert "f00" == r.headers["X-test"] + +def test_custom_conf_applies_to_wildcard(docker_compose, nginxproxy): + r = nginxproxy.get("http://wildcard.nginx-proxy.example/port") + assert r.status_code == 200 + assert r.text == "answer from port 83\n" + assert "X-test" in r.headers + assert "f00" == r.headers["X-test"] diff --git a/test/test_custom/test_defaults.yml b/test/test_custom/test_defaults.yml index 3df04b782..4826fb1e5 100644 --- a/test/test_custom/test_defaults.yml +++ b/test/test_custom/test_defaults.yml @@ -19,3 +19,11 @@ services: environment: WEB_PORTS: "82" VIRTUAL_HOST: web2.nginx-proxy.example + + wildcard: + image: web + expose: + - "83" + environment: + WEB_PORTS: "83" + VIRTUAL_HOST: "*.nginx-proxy.example" # wildcard for all subdomains diff --git a/test/test_custom/test_defaults-location.py b/test/test_custom/test_location-defaults.py similarity index 78% rename from test/test_custom/test_defaults-location.py rename to test/test_custom/test_location-defaults.py index 94d0fa17e..de4faf04e 100644 --- a/test/test_custom/test_defaults-location.py +++ b/test/test_custom/test_location-defaults.py @@ -17,10 +17,16 @@ def test_custom_default_conf_applies_to_web2(docker_compose, nginxproxy): assert "X-test" in r.headers assert "f00" == r.headers["X-test"] - def test_custom_default_conf_is_overriden_for_web3(docker_compose, nginxproxy): r = nginxproxy.get("http://web3.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_default_conf_applies_to_wildcard(docker_compose, nginxproxy): + r = nginxproxy.get("http://wildcard.nginx-proxy.example/port") + assert r.status_code == 200 + assert r.text == "answer from port 84\n" + assert "X-test" in r.headers + assert "f00" == r.headers["X-test"] diff --git a/test/test_custom/test_defaults-location.yml b/test/test_custom/test_location-defaults.yml similarity index 80% rename from test/test_custom/test_defaults-location.yml rename to test/test_custom/test_location-defaults.yml index c408099b2..e76030224 100644 --- a/test/test_custom/test_defaults-location.yml +++ b/test/test_custom/test_location-defaults.yml @@ -28,3 +28,11 @@ services: environment: WEB_PORTS: "83" VIRTUAL_HOST: web3.nginx-proxy.example + + wildcard: + image: web + expose: + - "84" + environment: + WEB_PORTS: "84" + VIRTUAL_HOST: "*.nginx-proxy.example" # wildcard for all subdomains diff --git a/test/test_custom/test_proxy-wide.py b/test/test_custom/test_proxy-wide.py index dc0fec9ce..74e17c99b 100644 --- a/test/test_custom/test_proxy-wide.py +++ b/test/test_custom/test_proxy-wide.py @@ -16,3 +16,10 @@ def test_custom_conf_applies_to_web2(docker_compose, nginxproxy): assert r.text == "answer from port 82\n" assert "X-test" in r.headers assert "f00" == r.headers["X-test"] + +def test_custom_conf_applies_to_wildcard(docker_compose, nginxproxy): + r = nginxproxy.get("http://wildcard.nginx-proxy.example/port") + assert r.status_code == 200 + assert r.text == "answer from port 83\n" + assert "X-test" in r.headers + assert "f00" == r.headers["X-test"] diff --git a/test/test_custom/test_proxy-wide.yml b/test/test_custom/test_proxy-wide.yml index 747bddae3..725ffa86e 100644 --- a/test/test_custom/test_proxy-wide.yml +++ b/test/test_custom/test_proxy-wide.yml @@ -19,3 +19,11 @@ services: environment: WEB_PORTS: "82" VIRTUAL_HOST: web2.nginx-proxy.example + + wildcard: + image: web + expose: + - "83" + environment: + WEB_PORTS: "83" + VIRTUAL_HOST: "*.nginx-proxy.example" # wildcard for all subdomains From 8d3b5c957ada11da921b490ad584617bbee421bb Mon Sep 17 00:00:00 2001 From: Nicolas Duchon Date: Tue, 19 Aug 2025 14:55:36 +0200 Subject: [PATCH 46/54] test: per vhost custom config for wildcard hosts --- .../my_custom_proxy_settings_baz.conf | 1 + test/test_custom/test_location-per-vhost.py | 20 +++++++++++++++---- test/test_custom/test_location-per-vhost.yml | 13 ++++++++++-- test/test_custom/test_per-vhost.py | 20 +++++++++++++++---- test/test_custom/test_per-vhost.yml | 13 ++++++++++-- 5 files changed, 55 insertions(+), 12 deletions(-) create mode 100644 test/test_custom/my_custom_proxy_settings_baz.conf diff --git a/test/test_custom/my_custom_proxy_settings_baz.conf b/test/test_custom/my_custom_proxy_settings_baz.conf new file mode 100644 index 000000000..d13493050 --- /dev/null +++ b/test/test_custom/my_custom_proxy_settings_baz.conf @@ -0,0 +1 @@ +add_header X-test-2 baz; \ No newline at end of file diff --git a/test/test_custom/test_location-per-vhost.py b/test/test_custom/test_location-per-vhost.py index 20d033a91..95328a619 100644 --- a/test/test_custom/test_location-per-vhost.py +++ b/test/test_custom/test_location-per-vhost.py @@ -2,26 +2,38 @@ def test_custom_conf_does_not_apply_to_unknown_vhost(docker_compose, nginxproxy) r = nginxproxy.get("http://nginx-proxy/") assert r.status_code == 503 assert "X-test" not in r.headers + assert "X-test-2" not in r.headers def test_custom_conf_applies_to_web1(docker_compose, nginxproxy): r = nginxproxy.get("http://web1.nginx-proxy.example/port") - assert r.status_code == 200 + assert r.status_code == 200 assert r.text == "answer from port 81\n" assert "X-test" in r.headers + assert "X-test-2" not 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 + r = nginxproxy.get("http://foo.nginx-proxy.regex/port") + assert r.status_code == 200 assert r.text == "answer from port 83\n" assert "X-test" in r.headers + assert "X-test-2" not in r.headers assert "bar" == r.headers["X-test"] +def test_custom_conf_applies_to_wildcard(docker_compose, nginxproxy): + r = nginxproxy.get("http://foo.nginx-proxy.example/port") + assert r.status_code == 200 + assert r.text == "answer from port 84\n" + assert "X-test" not in r.headers + assert "X-test-2" in r.headers + assert "baz" == r.headers["X-test-2"] + 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 + assert r.status_code == 200 assert r.text == "answer from port 82\n" assert "X-test" not in r.headers + assert "X-test-2" not in r.headers def test_custom_block_is_present_in_nginx_generated_conf(docker_compose, nginxproxy): assert b"include /etc/nginx/vhost.d/web1.nginx-proxy.example_location;" in nginxproxy.get_conf() \ No newline at end of file diff --git a/test/test_custom/test_location-per-vhost.yml b/test/test_custom/test_location-per-vhost.yml index 07f081798..b8501f2a6 100644 --- a/test/test_custom/test_location-per-vhost.yml +++ b/test/test_custom/test_location-per-vhost.yml @@ -3,7 +3,8 @@ services: volumes: - /var/run/docker.sock:/tmp/docker.sock:ro - ${PYTEST_MODULE_PATH}/my_custom_proxy_settings_f00.conf:/etc/nginx/vhost.d/web1.nginx-proxy.example_location:ro - - ${PYTEST_MODULE_PATH}/my_custom_proxy_settings_bar.conf:/etc/nginx/vhost.d/561032515ede3ab3a015edfb244608b72409c430_location:ro + - ${PYTEST_MODULE_PATH}/my_custom_proxy_settings_bar.conf:/etc/nginx/vhost.d/97db860fb631ba3c047db9fec60638fd72a180b1_location:ro + - ${PYTEST_MODULE_PATH}/my_custom_proxy_settings_baz.conf:/etc/nginx/vhost.d/*.nginx-proxy.example_location:ro web1: image: web @@ -27,4 +28,12 @@ services: - "83" environment: WEB_PORTS: "83" - VIRTUAL_HOST: ~^regex.*\.nginx-proxy\.example$$ # we need to double the `$` because of docker compose variable interpolation + VIRTUAL_HOST: ~^.*\.nginx-proxy\.regex$$ # we need to double the `$` because of docker compose variable interpolation + + wildcard: + image: web + expose: + - "84" + environment: + WEB_PORTS: "84" + VIRTUAL_HOST: "*.nginx-proxy.example" # wildcard for all subdomains diff --git a/test/test_custom/test_per-vhost.py b/test/test_custom/test_per-vhost.py index 24dd437fd..f291c49e7 100644 --- a/test/test_custom/test_per-vhost.py +++ b/test/test_custom/test_per-vhost.py @@ -2,23 +2,35 @@ def test_custom_conf_does_not_apply_to_unknown_vhost(docker_compose, nginxproxy) r = nginxproxy.get("http://nginx-proxy/") assert r.status_code == 503 assert "X-test" not in r.headers + assert "X-test-2" not in r.headers def test_custom_conf_applies_to_web1(docker_compose, nginxproxy): r = nginxproxy.get("http://web1.nginx-proxy.example/port") - assert r.status_code == 200 + assert r.status_code == 200 assert r.text == "answer from port 81\n" assert "X-test" in r.headers + assert "X-test-2" not 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 + r = nginxproxy.get("http://foo.nginx-proxy.regex/port") + assert r.status_code == 200 assert r.text == "answer from port 83\n" assert "X-test" in r.headers + assert "X-test-2" not in r.headers assert "bar" == r.headers["X-test"] +def test_custom_conf_applies_to_wildcard(docker_compose, nginxproxy): + r = nginxproxy.get("http://foo.nginx-proxy.example/port") + assert r.status_code == 200 + assert r.text == "answer from port 84\n" + assert "X-test" not in r.headers + assert "X-test-2" in r.headers + assert "baz" == r.headers["X-test-2"] + 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 + assert r.status_code == 200 assert r.text == "answer from port 82\n" assert "X-test" not in r.headers + assert "X-test-2" not in r.headers diff --git a/test/test_custom/test_per-vhost.yml b/test/test_custom/test_per-vhost.yml index 317e20835..bc21ed6d2 100644 --- a/test/test_custom/test_per-vhost.yml +++ b/test/test_custom/test_per-vhost.yml @@ -3,7 +3,8 @@ services: volumes: - /var/run/docker.sock:/tmp/docker.sock:ro - ${PYTEST_MODULE_PATH}/my_custom_proxy_settings_f00.conf:/etc/nginx/vhost.d/web1.nginx-proxy.example:ro - - ${PYTEST_MODULE_PATH}/my_custom_proxy_settings_bar.conf:/etc/nginx/vhost.d/561032515ede3ab3a015edfb244608b72409c430:ro + - ${PYTEST_MODULE_PATH}/my_custom_proxy_settings_bar.conf:/etc/nginx/vhost.d/97db860fb631ba3c047db9fec60638fd72a180b1:ro + - ${PYTEST_MODULE_PATH}/my_custom_proxy_settings_baz.conf:/etc/nginx/vhost.d/*.nginx-proxy.example:ro web1: image: web @@ -27,4 +28,12 @@ services: - "83" environment: WEB_PORTS: "83" - VIRTUAL_HOST: ~^regex.*\.nginx-proxy\.example$$ # we need to double the `$` because of docker compose variable interpolation + VIRTUAL_HOST: ~^.*\.nginx-proxy\.regex$$ # we need to double the `$` because of docker compose variable interpolation + + wildcard: + image: web + expose: + - "84" + environment: + WEB_PORTS: "84" + VIRTUAL_HOST: "*.nginx-proxy.example" # wildcard for all subdomains From 0979b5f7b3262fe98ae7ce810502893f3d5f1085 Mon Sep 17 00:00:00 2001 From: Nicolas Duchon Date: Tue, 19 Aug 2025 14:41:39 +0200 Subject: [PATCH 47/54] test: mark docker-gen segregation tests as flaky --- .../test_dockergen_network_segregation-custom-label.py | 5 +++++ test/test_dockergen/test_dockergen_network_segregation.py | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/test/test_dockergen/test_dockergen_network_segregation-custom-label.py b/test/test_dockergen/test_dockergen_network_segregation-custom-label.py index 7efd79162..672a987a8 100644 --- a/test/test_dockergen/test_dockergen_network_segregation-custom-label.py +++ b/test/test_dockergen/test_dockergen_network_segregation-custom-label.py @@ -1,8 +1,13 @@ +import pytest + + +@pytest.mark.xfail(reason="flaky test") def test_unknown_virtual_host_is_503(docker_compose, nginxproxy): r = nginxproxy.get("http://unknown.nginx-proxy.tld/") assert r.status_code == 503 +@pytest.mark.xfail(reason="flaky test") def test_forwards_to_whoami(docker_compose, nginxproxy): r = nginxproxy.get("http://whoami2.nginx-proxy.tld/") assert r.status_code == 200 diff --git a/test/test_dockergen/test_dockergen_network_segregation.py b/test/test_dockergen/test_dockergen_network_segregation.py index 7efd79162..672a987a8 100644 --- a/test/test_dockergen/test_dockergen_network_segregation.py +++ b/test/test_dockergen/test_dockergen_network_segregation.py @@ -1,8 +1,13 @@ +import pytest + + +@pytest.mark.xfail(reason="flaky test") def test_unknown_virtual_host_is_503(docker_compose, nginxproxy): r = nginxproxy.get("http://unknown.nginx-proxy.tld/") assert r.status_code == 503 +@pytest.mark.xfail(reason="flaky test") def test_forwards_to_whoami(docker_compose, nginxproxy): r = nginxproxy.get("http://whoami2.nginx-proxy.tld/") assert r.status_code == 200 From 21c63b5e5090f3759b17b6823a3adef498c24a53 Mon Sep 17 00:00:00 2001 From: Nicolas Duchon Date: Tue, 19 Aug 2025 22:04:21 +0200 Subject: [PATCH 48/54] test: clarify wildcard host custom conf tests --- test/test_custom/test_location-per-vhost.py | 4 +++- test/test_custom/test_per-vhost.py | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/test/test_custom/test_location-per-vhost.py b/test/test_custom/test_location-per-vhost.py index 95328a619..6e8b52555 100644 --- a/test/test_custom/test_location-per-vhost.py +++ b/test/test_custom/test_location-per-vhost.py @@ -24,9 +24,11 @@ def test_custom_conf_applies_to_wildcard(docker_compose, nginxproxy): r = nginxproxy.get("http://foo.nginx-proxy.example/port") assert r.status_code == 200 assert r.text == "answer from port 84\n" - assert "X-test" not in r.headers + # we should get the config from *.nginx-proxy.example_location.conf assert "X-test-2" in r.headers assert "baz" == r.headers["X-test-2"] + # but not the config from web1.nginx-proxy.example_location.conf + assert "X-test" not in r.headers def test_custom_conf_does_not_apply_to_web2(docker_compose, nginxproxy): r = nginxproxy.get("http://web2.nginx-proxy.example/port") diff --git a/test/test_custom/test_per-vhost.py b/test/test_custom/test_per-vhost.py index f291c49e7..8cd8f3bd6 100644 --- a/test/test_custom/test_per-vhost.py +++ b/test/test_custom/test_per-vhost.py @@ -24,9 +24,11 @@ def test_custom_conf_applies_to_wildcard(docker_compose, nginxproxy): r = nginxproxy.get("http://foo.nginx-proxy.example/port") assert r.status_code == 200 assert r.text == "answer from port 84\n" - assert "X-test" not in r.headers + # we should get the config from *.nginx-proxy.example.conf assert "X-test-2" in r.headers assert "baz" == r.headers["X-test-2"] + # but not the config from web1.nginx-proxy.example.conf + assert "X-test" not in r.headers def test_custom_conf_does_not_apply_to_web2(docker_compose, nginxproxy): r = nginxproxy.get("http://web2.nginx-proxy.example/port") From af1c403d74ed71c09d840f68eb68fa17e8aa6f18 Mon Sep 17 00:00:00 2001 From: Nicolas Duchon Date: Tue, 19 Aug 2025 22:11:24 +0200 Subject: [PATCH 49/54] test: use pytest-ignore-flaky instead of xfail --- .github/workflows/test.yml | 2 +- test/requirements/python-requirements.txt | 1 + .../test_dockergen_network_segregation-custom-label.py | 4 ++-- test/test_dockergen/test_dockergen_network_segregation.py | 4 ++-- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 14fe66812..50d998b1c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -50,5 +50,5 @@ jobs: run: make build-nginx-proxy-test-${{ matrix.base_docker_image }} - name: Run tests - run: pytest + run: pytest --ignore-flaky working-directory: test diff --git a/test/requirements/python-requirements.txt b/test/requirements/python-requirements.txt index ca89fd32c..cf0a1d74c 100644 --- a/test/requirements/python-requirements.txt +++ b/test/requirements/python-requirements.txt @@ -2,5 +2,6 @@ backoff==2.2.1 docker==7.1.0 packaging==25.0 pytest==8.4.1 +pytest-ignore-flaky==2.2.1 requests==2.32.4 urllib3==2.5.0 diff --git a/test/test_dockergen/test_dockergen_network_segregation-custom-label.py b/test/test_dockergen/test_dockergen_network_segregation-custom-label.py index 672a987a8..09e7e6d12 100644 --- a/test/test_dockergen/test_dockergen_network_segregation-custom-label.py +++ b/test/test_dockergen/test_dockergen_network_segregation-custom-label.py @@ -1,13 +1,13 @@ import pytest -@pytest.mark.xfail(reason="flaky test") +@pytest.mark.flaky def test_unknown_virtual_host_is_503(docker_compose, nginxproxy): r = nginxproxy.get("http://unknown.nginx-proxy.tld/") assert r.status_code == 503 -@pytest.mark.xfail(reason="flaky test") +@pytest.mark.flaky def test_forwards_to_whoami(docker_compose, nginxproxy): r = nginxproxy.get("http://whoami2.nginx-proxy.tld/") assert r.status_code == 200 diff --git a/test/test_dockergen/test_dockergen_network_segregation.py b/test/test_dockergen/test_dockergen_network_segregation.py index 672a987a8..09e7e6d12 100644 --- a/test/test_dockergen/test_dockergen_network_segregation.py +++ b/test/test_dockergen/test_dockergen_network_segregation.py @@ -1,13 +1,13 @@ import pytest -@pytest.mark.xfail(reason="flaky test") +@pytest.mark.flaky def test_unknown_virtual_host_is_503(docker_compose, nginxproxy): r = nginxproxy.get("http://unknown.nginx-proxy.tld/") assert r.status_code == 503 -@pytest.mark.xfail(reason="flaky test") +@pytest.mark.flaky def test_forwards_to_whoami(docker_compose, nginxproxy): r = nginxproxy.get("http://whoami2.nginx-proxy.tld/") assert r.status_code == 200 From 9d46cad224b7b0a6c58dc727376334b8c8ac3ce0 Mon Sep 17 00:00:00 2001 From: Nicolas Duchon Date: Wed, 20 Aug 2025 02:01:25 +0200 Subject: [PATCH 50/54] docs: add files to configuration summary (#2652) --- docs/README.md | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/docs/README.md b/docs/README.md index 276687797..aaba147ce 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1394,6 +1394,53 @@ Configuration available on each proxied container, either by environment variabl | [`VIRTUAL_PROTO`](#upstream-backend-features) | n/a | `http` | | [`VIRTUAL_ROOT`](#fastcgi-file-root-directory) | n/a | `/var/www/public` | +### Configuration by files + +Additional configuration and/or features available by mounting files to the nginx-proxy container (or to both the nginx and docker-gen containers in a [separate containers setup](#separate-containers)). + +In the following tables, `` is the value of the `VIRTUAL_HOST` environment variable, or the SHA-1 hash of the regex if `VIRTUAL_HOST` is a regex. `` is the SHA-1 hash of the path, as described in [Per-Virtual Path Location Configuration](#per-virtual_path-location-configuration). + +#### Proxy-wide + +| File Path | Description | +|-----------|-------------| +| [`/etc/nginx/conf.d/proxy.conf`](#replacing-default-proxy-settings) | Proxy-wide configuration, replacing the default proxy settings. | +| [`/etc/nginx/conf.d/.conf`](#proxy-wide) | Proxy-wide configuration, augmenting the default proxy settings. | +| [`/etc/nginx/toplevel.conf.d/.conf`](#tcp-and-udp-stream) | Custom Nginx configuration, augmenting the default Nginx settings. | +| [`/usr/share/nginx/html/errors/50x.html`](#custom-error-page) | Custom error page for 50x errors, replacing the default error page. | + +#### Per-VIRTUAL_HOST configuration + +| File Path | Description | +|-----------|-------------| +| [`/etc/nginx/vhost.d/`](#per-virtual_host) | Per-`VIRTUAL_HOST` additional configuration. | +| [`/etc/nginx/vhost.d/default`](#per-virtual_host-default-configuration) | Per-`VIRTUAL_HOST` default additional configuration. | +| [`/etc/nginx/vhost.d/_location`](#per-virtual_host-location-configuration) | Per-`VIRTUAL_HOST` additional location configuration. | +| [`/etc/nginx/vhost.d/__location`](#per-virtual_path-location-configuration) | Per-`VIRTUAL_PATH` additional location configuration. | +| [`/etc/nginx/vhost.d/default_location`](#per-virtual_host-location-default-configuration) | Per-`VIRTUAL_HOST` default additional location configuration. | +| [`/etc/nginx/vhost.d/_location_override`](#overriding-location-blocks) | Per-`VIRTUAL_HOST` location configuration override. | +| [`/etc/nginx/vhost.d/__location_override`](#overriding-location-blocks) | Per-`VIRTUAL_PATH` location configuration override. | + +#### Authentication + +| File Path | Description | +|-----------|-------------| +| [`/etc/nginx/htpasswd/`](#basic-authentication-support) | Basic authentication for a specific `VIRTUAL_HOST`. | +| [`/etc/nginx/htpasswd/_`](#basic-authentication-support) | Basic authentication for a specific path within a `VIRTUAL_HOST`. | +| [`/etc/nginx/certs/.ca.crt`](#per-virtual_host-ca) | Per-`VIRTUAL_HOST` Certificate Authority (CA) certificate for mTLS client authentication. | +| [`/etc/nginx/certs/.crl.pem`](#per-virtual_host-crl) | Per-`VIRTUAL_HOST` Certificate Revocation List (CRL) for mTLS client authentication. | +| [`/etc/nginx/certs/ca.crt`](#global-ca) | Global Certificate Authority (CA) certificate for mTLS client authentication. | +| [`/etc/nginx/certs/ca.crl.pem`](#global-crl) | Global Certificate Revocation List (CRL) for mTLS client authentication. | + +#### SSL/TLS + +| File Path | Description | +|-----------|-------------| +| [`/etc/nginx/certs/.crt`](#ssl-support) | Per-`VIRTUAL_HOST` SSL/TLS certificate. | +| [`/etc/nginx/certs/.key`](#ssl-support) | Per-`VIRTUAL_HOST` SSL/TLS private key. | +| [`/etc/nginx/certs/.dhparam.pem`](#diffie-hellman-groups) | Per-`VIRTUAL_HOST` Diffie-Hellman parameters. | +| [`/etc/nginx/certs/dhparam.pem`](#diffie-hellman-groups) | Global Diffie-Hellman parameters. | + ⬆️ [back to table of contents](#table-of-contents) ## Troubleshooting From f2add3022bb2be82ee9708fe72fbde7607c7c91b Mon Sep 17 00:00:00 2001 From: Bob-le-pirate <84987272+Bob-le-pirate@users.noreply.github.com> Date: Wed, 20 Aug 2025 12:32:51 +0200 Subject: [PATCH 51/54] docs: add precisions to multi container compose file (#2650) Co-authored-by: Nicolas Duchon --- docker-compose-separate-containers.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docker-compose-separate-containers.yml b/docker-compose-separate-containers.yml index 3ceea451d..c36063feb 100644 --- a/docker-compose-separate-containers.yml +++ b/docker-compose-separate-containers.yml @@ -9,10 +9,14 @@ services: - "80:80" volumes: - nginx_conf:/etc/nginx/conf.d:ro + - network_internal.conf:/etc/nginx/network_internal.conf:ro dockergen: image: nginxproxy/docker-gen command: -notify-sighup nginx -watch /etc/docker-gen/templates/nginx.tmpl /etc/nginx/conf.d/default.conf + # the name "nginx" passed to the "-notify-sighup" flag must exactly match the container name used for the nginx container above. + environment: + # nginx-proxy environment variable (HTTP_PORT etc.) must be set on the docker-gen container (not the nginx container) volumes: - /var/run/docker.sock:/tmp/docker.sock:ro - ./nginx.tmpl:/etc/docker-gen/templates/nginx.tmpl From 9b5eec03c062668d9358732da32f4fef93dcc2c3 Mon Sep 17 00:00:00 2001 From: Nicolas Duchon Date: Wed, 20 Aug 2025 14:36:08 +0200 Subject: [PATCH 52/54] docs: add missing variable to config summary --- docs/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/README.md b/docs/README.md index aaba147ce..565f09e46 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1381,6 +1381,7 @@ Configuration available on each proxied container, either by environment variabl | [`HTTPS_METHOD`](#how-ssl-support-works) | n/a | global (proxy) value | | n/a | [`com.github.nginx-proxy.nginx-proxy.keepalive`](#upstream-server-http-keep-alive-support) | `auto` | | n/a | [`com.github.nginx-proxy.nginx-proxy.loadbalance`](#upstream-server-http-load-balancing-support) | no default value | +| [`NETWORK_ACCESS`](#internet-vs-local-network-access) | n/a | `external` | | n/a | [`com.github.nginx-proxy.nginx-proxy.non-get-redirect`](#how-ssl-support-works) | global (proxy) value | | [`SERVER_TOKENS`](#per-virtual_host-server_tokens-configuration) | n/a | no default value | | [`SSL_POLICY`](#how-ssl-support-works) | n/a | global (proxy) value | From 8e34e198e3beb27c42d894659d61bb52c3b774e7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Aug 2025 06:51:59 +0000 Subject: [PATCH 53/54] ci: bump requests from 2.32.4 to 2.32.5 in /test/requirements Bumps [requests](https://github.com/psf/requests) from 2.32.4 to 2.32.5. - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.4...v2.32.5) --- updated-dependencies: - dependency-name: requests dependency-version: 2.32.5 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- test/requirements/python-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/requirements/python-requirements.txt b/test/requirements/python-requirements.txt index cf0a1d74c..61c24733e 100644 --- a/test/requirements/python-requirements.txt +++ b/test/requirements/python-requirements.txt @@ -3,5 +3,5 @@ docker==7.1.0 packaging==25.0 pytest==8.4.1 pytest-ignore-flaky==2.2.1 -requests==2.32.4 +requests==2.32.5 urllib3==2.5.0 From ac3475bbe526240a29fe54bb0cf19aae0722f645 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 29 Aug 2025 04:02:05 +0000 Subject: [PATCH 54/54] build: bump nginxproxy/docker-gen from 0.15.0-debian to 0.15.1-debian Bumps nginxproxy/docker-gen from 0.15.0-debian to 0.15.1-debian. --- updated-dependencies: - dependency-name: nginxproxy/docker-gen dependency-version: 0.15.1-debian dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Dockerfile.alpine | 2 +- Dockerfile.debian | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile.alpine b/Dockerfile.alpine index a1e73e5d2..a588766b4 100644 --- a/Dockerfile.alpine +++ b/Dockerfile.alpine @@ -1,5 +1,5 @@ # syntax=docker/dockerfile:1 -FROM docker.io/nginxproxy/docker-gen:0.15.0 AS docker-gen +FROM docker.io/nginxproxy/docker-gen:0.15.1 AS docker-gen FROM docker.io/nginxproxy/forego:0.18.3 AS forego diff --git a/Dockerfile.debian b/Dockerfile.debian index 52f712c97..33b4ca0ef 100644 --- a/Dockerfile.debian +++ b/Dockerfile.debian @@ -1,5 +1,5 @@ # syntax=docker/dockerfile:1 -FROM docker.io/nginxproxy/docker-gen:0.15.0-debian AS docker-gen +FROM docker.io/nginxproxy/docker-gen:0.15.1-debian AS docker-gen FROM docker.io/nginxproxy/forego:0.18.3-debian AS forego