From b6e9cdc065a19ce7e7cb0dcf2c11cd7c9fe1d063 Mon Sep 17 00:00:00 2001 From: Nicolas Duchon Date: Tue, 4 May 2021 11:03:27 +0200 Subject: [PATCH 01/23] ci: use docker-gen main on dev branch tests --- .github/workflows/test.yml | 20 ++++++++++++++++---- Makefile | 6 ++++++ 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6be93bd..8c4a173 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -3,13 +3,16 @@ name: Tests on: workflow_dispatch: push: + branches: + - main + - dev paths-ignore: - - 'LICENSE' - - '**.md' + - 'LICENSE' + - '**.md' pull_request: paths-ignore: - - 'LICENSE' - - '**.md' + - 'LICENSE' + - '**.md' jobs: unit: @@ -39,6 +42,15 @@ jobs: - name: Build Docker nginx proxy test image run: make build-nginx-proxy-test-${{ matrix.base_docker_image }} + if: | + ( github.event_name == 'push' && github.ref != 'refs/heads/dev' ) || + ( github.event_name == 'pull_request' && github.base_ref != 'dev' ) + + - name: Build Docker nginx proxy dev test image + run: make build-nginx-proxy-test-${{ matrix.base_docker_image }}-dev + if: | + ( github.event_name == 'push' && github.ref == 'refs/heads/dev' ) || + ( github.event_name == 'pull_request' && github.base_ref == 'dev' ) - name: Run tests run: pytest diff --git a/Makefile b/Makefile index ab44880..60406b6 100644 --- a/Makefile +++ b/Makefile @@ -11,6 +11,12 @@ build-nginx-proxy-test-debian: build-nginx-proxy-test-alpine: docker build --build-arg NGINX_PROXY_VERSION="test" -f Dockerfile.alpine -t nginxproxy/nginx-proxy:test . +build-nginx-proxy-test-debian-dev: + docker build --build-arg DOCKER_GEN_VERSION=main -t nginxproxy/nginx-proxy:test . + +build-nginx-proxy-test-alpine-dev: + docker build -f Dockerfile.alpine --build-arg DOCKER_GEN_VERSION=main -t nginxproxy/nginx-proxy:test . + test-debian: build-webserver build-nginx-proxy-test-debian test/pytest.sh From 2901b917a0cee641b58e66338d8855366b60d1c2 Mon Sep 17 00:00:00 2001 From: Greg Symons Date: Sun, 23 May 2021 22:52:57 +0200 Subject: [PATCH 02/23] feat: support for path-based routing Co-authored-by: Josh Trow Co-authored-by: Adrian Co-authored-by: Rodrigo Aguilera Co-authored-by: Alexander Lieret --- nginx.tmpl | 212 +++++++++++++++++++++++++++++------------------------ 1 file changed, 115 insertions(+), 97 deletions(-) diff --git a/nginx.tmpl b/nginx.tmpl index 2414633..fc6f540 100644 --- a/nginx.tmpl +++ b/nginx.tmpl @@ -49,6 +49,92 @@ {{ end }} {{ end }} +{{ define "location" }} +location {{ .Path }} { + {{ if eq .Proto "uwsgi" }} + include uwsgi_params; + uwsgi_pass {{ trim .Proto }}://{{ trim .Upstream }}; + {{ else if eq .Proto "fastcgi" }} + root {{ trim .Vhostroot }}; + include fastcgi.conf; + fastcgi_pass {{ trim .Upstream }}; + {{ else if eq .Proto "grpc" }} + grpc_pass {{ trim .Proto }}://{{ trim .Upstream }}; + {{ else }} + proxy_pass {{ trim .Proto }}://{{ trim .Upstream }}/; + {{ end }} + + {{ if (exists (printf "/etc/nginx/htpasswd/%s" .Host)) }} + auth_basic "Restricted {{ .Host }}"; + auth_basic_user_file {{ (printf "/etc/nginx/htpasswd/%s" .Host) }}; + {{ end }} + + {{ if (exists (printf "/etc/nginx/vhost.d/%s_location" .Host)) }} + include {{ printf "/etc/nginx/vhost.d/%s_location" .Host}}; + {{ else if (exists "/etc/nginx/vhost.d/default_location") }} + include /etc/nginx/vhost.d/default_location; + {{ end }} +} +{{ end }} + +{{ define "upstream-definition" }} + {{ $networks := .Networks }} + {{ $debug_all := .Debug }} + upstream {{ .Upstream }} { + {{ $server_found := "false" }} + {{ range $container := .Containers }} + {{ $debug := (eq (coalesce $container.Env.DEBUG $debug_all "false") "true") }} + {{/* If only 1 port exposed, use that as a default, else 80 */}} + {{ $defaultPort := (when (eq (len $container.Addresses) 1) (first $container.Addresses) (dict "Port" "80")).Port }} + {{ $port := (coalesce $container.Env.VIRTUAL_PORT $defaultPort) }} + {{ $address := where $container.Addresses "Port" $port | first }} + {{ if $debug }} + # Exposed ports: {{ $container.Addresses }} + # Default virtual port: {{ $defaultPort }} + # VIRTUAL_PORT: {{ $container.Env.VIRTUAL_PORT }} + {{ if not $address }} + # /!\ Virtual port not exposed + {{ end }} + {{ end }} + {{ range $knownNetwork := $networks }} + {{ range $containerNetwork := $container.Networks }} + {{ if (and (ne $containerNetwork.Name "ingress") (or (eq $knownNetwork.Name $containerNetwork.Name) (eq $knownNetwork.Name "host"))) }} + ## Can be connected with "{{ $containerNetwork.Name }}" network + {{ if $address }} + {{/* If we got the containers from swarm and this container's port is published to host, use host IP:PORT */}} + {{ if and $container.Node.ID $address.HostPort }} + {{ $server_found = "true" }} + # {{ $container.Node.Name }}/{{ $container.Name }} + server {{ $container.Node.Address.IP }}:{{ $address.HostPort }}; + {{/* If there is no swarm node or the port is not published on host, use container's IP:PORT */}} + {{ else if $containerNetwork }} + {{ $server_found = "true" }} + # {{ $container.Name }} + server {{ $containerNetwork.IP }}:{{ $address.Port }}; + {{ end }} + {{ else if $containerNetwork }} + # {{ $container.Name }} + {{ if $containerNetwork.IP }} + {{ $server_found = "true" }} + server {{ $containerNetwork.IP }}:{{ $port }}; + {{ else }} + # /!\ No IP for this network! + {{ end }} + {{ end }} + {{ else }} + # Cannot connect to network '{{ $containerNetwork.Name }}' of this container + {{ end }} + {{ end }} + {{ end }} + {{ end }} + {{/* nginx-proxy/nginx-proxy#1105 */}} + {{ if (eq $server_found "false") }} + # Fallback entry + server 127.0.0.1 down; + {{ end }} + } +{{ end }} + {{ if ne $nginx_proxy_version "" }} # nginx-proxy version : {{ $nginx_proxy_version }} {{ end }} @@ -100,6 +186,7 @@ access_log off; {{/* Get the SSL_POLICY defined by this container, falling back to "Mozilla-Intermediate" */}} {{ $ssl_policy := or ($.Env.SSL_POLICY) "Mozilla-Intermediate" }} {{ template "ssl_policy" (dict "ssl_policy" $ssl_policy) }} +error_log /dev/stderr; {{ if $.Env.RESOLVERS }} resolver {{ $.Env.RESOLVERS }}; @@ -119,6 +206,7 @@ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto; proxy_set_header X-Forwarded-Ssl $proxy_x_forwarded_ssl; proxy_set_header X-Forwarded-Port $proxy_x_forwarded_port; +proxy_set_header X-Original-URI $request_uri; # Mitigate httpoxy attack (see README for details) proxy_set_header Proxy ""; @@ -162,61 +250,20 @@ server { {{ $is_regexp := hasPrefix "~" $host }} {{ $upstream_name := when (or $is_regexp $sha1_upstream_name) (sha1 $host) $host }} -# {{ $host }} -upstream {{ $upstream_name }} { +{{ $paths := groupBy $containers "Env.VIRTUAL_PATH" }} +{{ $nPaths := len $paths }} -{{ $server_found := "false" }} -{{ range $container := $containers }} - {{ $debug := (eq (coalesce $container.Env.DEBUG $debug_all "false") "true") }} - {{/* If only 1 port exposed, use that as a default, else 80 */}} - {{ $defaultPort := (when (eq (len $container.Addresses) 1) (first $container.Addresses) (dict "Port" "80")).Port }} - {{ $port := (coalesce $container.Env.VIRTUAL_PORT $defaultPort) }} - {{ $address := where $container.Addresses "Port" $port | first }} - {{ if $debug }} - # Exposed ports: {{ $container.Addresses }} - # Default virtual port: {{ $defaultPort }} - # VIRTUAL_PORT: {{ $container.Env.VIRTUAL_PORT }} - {{ if not $address }} - # /!\ Virtual port not exposed - {{ end }} - {{ end }} - {{ range $knownNetwork := $CurrentContainer.Networks }} - {{ range $containerNetwork := $container.Networks }} - {{ if (and (ne $containerNetwork.Name "ingress") (or (eq $knownNetwork.Name $containerNetwork.Name) (eq $knownNetwork.Name "host"))) }} - ## Can be connected with "{{ $containerNetwork.Name }}" network - {{ if $address }} - {{/* If we got the containers from swarm and this container's port is published to host, use host IP:PORT */}} - {{ if and $container.Node.ID $address.HostPort }} - {{ $server_found = "true" }} - # {{ $container.Node.Name }}/{{ $container.Name }} - server {{ $container.Node.Address.IP }}:{{ $address.HostPort }}; - {{/* If there is no swarm node or the port is not published on host, use container's IP:PORT */}} - {{ else if $containerNetwork }} - {{ $server_found = "true" }} - # {{ $container.Name }} - server {{ $containerNetwork.IP }}:{{ $address.Port }}; - {{ end }} - {{ else if $containerNetwork }} - # {{ $container.Name }} - {{ if $containerNetwork.IP }} - {{ $server_found = "true" }} - server {{ $containerNetwork.IP }}:{{ $port }}; - {{ else }} - # /!\ No IP for this network! - {{ end }} - {{ end }} - {{ else }} - # Cannot connect to network '{{ $containerNetwork.Name }}' of this container - {{ end }} - {{ end }} +{{ if eq $nPaths 0 }} + # {{ $host }} + {{ template "upstream-definition" (dict "Upstream" $upstream_name "Containers" $containers "Networks" $CurrentContainer.Networks "Debug" $debug_all) }} +{{ else }} + {{ range $path, $containers := $paths }} + {{ $sum := sha1 $path }} + {{ $upstream := printf "%s-%s" $upstream_name $sum }} + # {{ $host }}{{ $path }} + {{ template "upstream-definition" (dict "Upstream" $upstream "Containers" $containers "Networks" $CurrentContainer.Networks "Debug" $debug_all) }} {{ end }} {{ end }} -{{/* nginx-proxy/nginx-proxy#1105 */}} -{{ if (eq $server_found "false") }} - # Fallback entry - server 127.0.0.1 down; -{{ end }} -} {{ $default_host := or ($.Env.DEFAULT_HOST) "" }} {{ $default_server := index (dict $host "" $default_host "default_server") $host }} @@ -337,30 +384,15 @@ server { include /etc/nginx/vhost.d/default; {{ end }} - location / { - {{ if eq $proto "uwsgi" }} - include uwsgi_params; - uwsgi_pass {{ trim $proto }}://{{ trim $upstream_name }}; - {{ else if eq $proto "fastcgi" }} - root {{ trim $vhost_root }}; - include fastcgi_params; - fastcgi_pass {{ trim $upstream_name }}; - {{ else if eq $proto "grpc" }} - grpc_pass {{ trim $proto }}://{{ trim $upstream_name }}; - {{ else }} - proxy_pass {{ trim $proto }}://{{ trim $upstream_name }}; + {{ if eq $nPaths 0 }} + {{ template "location" (dict "Path" "/" "Proto" $proto "Upstream" $upstream_name "Host" $host "Vhostroot" $vhost_root) }} + {{ else }} + {{ range $path, $container := $paths }} + {{ $sum := sha1 $path }} + {{ $upstream := printf "%s-%s" $host $sum }} + {{ template "location" (dict "Path" $path "Proto" $proto "Upstream" $upstream "Host" $host "Vhostroot" $vhost_root) }} {{ end }} - - {{ if (exists (printf "/etc/nginx/htpasswd/%s" $host)) }} - auth_basic "Restricted {{ $host }}"; - auth_basic_user_file {{ (printf "/etc/nginx/htpasswd/%s" $host) }}; - {{ end }} - {{ if (exists (printf "/etc/nginx/vhost.d/%s_location" $host)) }} - include {{ printf "/etc/nginx/vhost.d/%s_location" $host}}; - {{ else if (exists "/etc/nginx/vhost.d/default_location") }} - include /etc/nginx/vhost.d/default_location; - {{ end }} - } + {{ end }} } {{ end }} @@ -389,29 +421,15 @@ server { include /etc/nginx/vhost.d/default; {{ end }} - location / { - {{ if eq $proto "uwsgi" }} - include uwsgi_params; - uwsgi_pass {{ trim $proto }}://{{ trim $upstream_name }}; - {{ else if eq $proto "fastcgi" }} - root {{ trim $vhost_root }}; - include fastcgi_params; - fastcgi_pass {{ trim $upstream_name }}; - {{ else if eq $proto "grpc" }} - grpc_pass {{ trim $proto }}://{{ trim $upstream_name }}; - {{ else }} - proxy_pass {{ trim $proto }}://{{ trim $upstream_name }}; + {{ if eq $nPaths 0 }} + {{ template "location" (dict "Path" "/" "Proto" $proto "Upstream" $upstream_name "Host" $host "Vhostroot" $vhost_root) }} + {{ else }} + {{ range $path, $container := $paths }} + {{ $sum := sha1 $path }} + {{ $upstream := printf "%s-%s" $upstream_name $sum }} + {{ template "location" (dict "Path" $path "Proto" $proto "Upstream" $upstream "Host" $host "Vhostroot" $vhost_root) }} {{ end }} - {{ if (exists (printf "/etc/nginx/htpasswd/%s" $host)) }} - auth_basic "Restricted {{ $host }}"; - auth_basic_user_file {{ (printf "/etc/nginx/htpasswd/%s" $host) }}; - {{ end }} - {{ if (exists (printf "/etc/nginx/vhost.d/%s_location" $host)) }} - include {{ printf "/etc/nginx/vhost.d/%s_location" $host}}; - {{ else if (exists "/etc/nginx/vhost.d/default_location") }} - include /etc/nginx/vhost.d/default_location; - {{ end }} - } + {{ end }} } {{ if (and (not $is_https) (exists "/etc/nginx/certs/default.crt") (exists "/etc/nginx/certs/default.key")) }} From fc4c4e17cab24f84f6b2b9691a43cd4bb90d688b Mon Sep 17 00:00:00 2001 From: Alexander Lieret Date: Tue, 6 Jul 2021 14:40:21 +0200 Subject: [PATCH 03/23] ci: Add tests for the virtual-path routing @gregsymons test cases were too outdated to be ported easily. The new tests should include the coverage of the old ones. --- test/test_events.py | 38 +++++++++++- test/test_virtual-path/test_virtual_paths.py | 59 +++++++++++++++++++ test/test_virtual-path/test_virtual_paths.yml | 42 +++++++++++++ 3 files changed, 138 insertions(+), 1 deletion(-) create mode 100644 test/test_virtual-path/test_virtual_paths.py create mode 100644 test/test_virtual-path/test_virtual_paths.yml diff --git a/test/test_events.py b/test/test_events.py index 201917f..b5da3dd 100644 --- a/test/test_events.py +++ b/test/test_events.py @@ -29,13 +29,36 @@ def web1(docker_compose): except NotFound: pass +@pytest.fixture() +def web2(docker_compose): + """ + pytest fixture creating a web container with `VIRTUAL_HOST=nginx-proxy`, `VIRTUAL_PATH=/web2/` and `VIRTUAL_DEST=/` listening on port 82. + """ + container = docker_compose.containers.run( + name="web2", + image="web", + detach=True, + environment={ + "WEB_PORTS": "82", + "VIRTUAL_HOST": "nginx-proxy", + "VIRTUAL_PATH": "/web2/", + "VIRTUAL_DEST": "/", + }, + ports={"82/tcp": None} + ) + sleep(2) # give it some time to initialize and for docker-gen to detect it + yield container + try: + docker_compose.containers.get("web2").remove(force=True) + except NotFound: + pass def test_nginx_proxy_behavior_when_alone(docker_compose, nginxproxy): r = nginxproxy.get("http://nginx-proxy/") assert r.status_code == 503 -def test_new_container_is_detected(web1, nginxproxy): +def test_new_container_is_detected_vhost(web1, nginxproxy): r = nginxproxy.get("http://web1.nginx-proxy/port") assert r.status_code == 200 assert "answer from port 81\n" == r.text @@ -44,3 +67,16 @@ def test_new_container_is_detected(web1, nginxproxy): sleep(2) r = nginxproxy.get("http://web1.nginx-proxy/port") assert r.status_code == 503 + +def test_new_container_is_detected_vpath(web2, nginxproxy): + r = nginxproxy.get("http://nginx-proxy/web2/port") + assert r.status_code == 200 + assert "answer from port 82\n" == r.text + r = nginxproxy.get("http://nginx-proxy/port") + assert r.status_code in [404, 503] + + web2.remove(force=True) + sleep(2) + r = nginxproxy.get("http://nginx-proxy/web2/port") + assert r.status_code == 503 + diff --git a/test/test_virtual-path/test_virtual_paths.py b/test/test_virtual-path/test_virtual_paths.py new file mode 100644 index 0000000..115d47f --- /dev/null +++ b/test/test_virtual-path/test_virtual_paths.py @@ -0,0 +1,59 @@ +from time import sleep + +import pytest +from docker.errors import NotFound + +@pytest.mark.parametrize("stub,expected_port", [ + ("nginx-proxy.test/web1", 81), + ("nginx-proxy.test/web2", 82), + ("nginx-proxy.test", 83), + ("foo.nginx-proxy.test", 42), +]) +def test_valid_path(docker_compose, nginxproxy, stub, expected_port): + r = nginxproxy.get(f"http://{stub}/port") + assert r.status_code == 200 + assert r.text == f"answer from port {expected_port}\n" + +@pytest.mark.parametrize("stub", [ + "nginx-proxy.test/foo", + "bar.nginx-proxy.test", +]) +def test_invalid_path(docker_compose, nginxproxy, stub): + r = nginxproxy.get(f"http://{stub}/port") + assert r.status_code in [404, 503] + +@pytest.fixture() +def web4(docker_compose): + """ + pytest fixture creating a web container with `VIRTUAL_HOST=nginx-proxy.test`, `VIRTUAL_PATH=/web4/` and `VIRTUAL_DEST=/` listening on port 84. + """ + container = docker_compose.containers.run( + name="web4", + image="web", + detach=True, + environment={ + "WEB_PORTS": "84", + "VIRTUAL_HOST": "nginx-proxy.test", + "VIRTUAL_PATH": "/web4/", + "VIRTUAL_DEST": "/", + }, + ports={"84/tcp": None} + ) + sleep(2) # give it some time to initialize and for docker-gen to detect it + yield container + try: + docker_compose.containers.get("web4").remove(force=True) + except NotFound: + pass + +""" +Test if we can add and remove a single virtual_path from multiple ones on the same subdomain. +""" +def test_container_hotplug(web4, nginxproxy): + r = nginxproxy.get(f"http://nginx-proxy.test/web4/port") + assert r.status_code == 200 + assert r.text == f"answer from port 84\n" + web4.remove(force=True) + sleep(2) + r = nginxproxy.get(f"http://nginx-proxy.test/web4/port") + assert r.status_code == 404 diff --git a/test/test_virtual-path/test_virtual_paths.yml b/test/test_virtual-path/test_virtual_paths.yml new file mode 100644 index 0000000..ca688eb --- /dev/null +++ b/test/test_virtual-path/test_virtual_paths.yml @@ -0,0 +1,42 @@ + +foo: + image: web + expose: + - "42" + environment: + WEB_PORTS: "42" + VIRTUAL_HOST: "foo.nginx-proxy.test" + +web1: + image: web + expose: + - "81" + environment: + WEB_PORTS: "81" + VIRTUAL_HOST: "nginx-proxy.test" + VIRTUAL_PATH: "/web1/" + +web2: + image: web + expose: + - "82" + environment: + WEB_PORTS: "82" + VIRTUAL_HOST: "nginx-proxy.test" + VIRTUAL_PATH: "/web2/" + +web3: + image: web + expose: + - "83" + environment: + WEB_PORTS: "83" + VIRTUAL_HOST: "nginx-proxy.test" + VIRTUAL_PATH: "/" + +sut: + image: nginxproxy/nginx-proxy:test + volumes: + - /var/run/docker.sock:/tmp/docker.sock:ro + - ../lib/ssl/dhparam.pem:/etc/nginx/dhparam/dhparam.pem:ro + From e0e1732842b7c4f90847a4a9f7ca1747a8e17558 Mon Sep 17 00:00:00 2001 From: Greg Symons Date: Tue, 6 Jul 2021 15:02:09 +0200 Subject: [PATCH 04/23] docs: Add documentation for path-based routing Co-authored-by: Josh Trow Co-authored-by: Adrian Co-authored-by: Rodrigo Aguilera Co-authored-by: Alexander Lieret --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 556cf5c..9e248fa 100644 --- a/README.md +++ b/README.md @@ -117,6 +117,12 @@ For each host defined into `VIRTUAL_HOST`, the associated virtual port is retrie You can also use wildcards at the beginning and the end of host name, like `*.bar.com` or `foo.bar.*`. Or even a regular expression, which can be very useful in conjunction with a wildcard DNS service like [nip.io](https://nip.io) or [sslip.io](https://sslip.io), using `~^foo\.bar\..*\.nip\.io` will match `foo.bar.127.0.0.1.nip.io`, `foo.bar.10.0.2.2.nip.io` and all other given IPs. More information about this topic can be found in the nginx documentation about [`server_names`](http://nginx.org/en/docs/http/server_names.html). +### Path-based Routing + +You can have multiple containers proxied by the same `VIRTUAL_HOST` by adding a `VIRTUAL_PATH` environment variable containing the absolute path to where the container should be mounted. For example with `VIRTUAL_HOST=foo.example.com` and `VIRTUAL_PATH=/api/v2/service`, then requests to http://foo.example.com/api/v2/service will be routed to the container. If you wish to have a container serve the root while other containers serve other paths, make give the root container a `VIRTUAL_PATH` of `/`. Unmatched paths will be served by the container at `/` or will return the default nginx error page if no container has been assigned `/`. + +The full request URI will be forwarded to the serving container in the `X-Forwarded-Path` header. + ### Multiple Networks With the addition of [overlay networking](https://docs.docker.com/engine/userguide/networking/get-started-overlay/) in Docker 1.9, your `nginx-proxy` container may need to connect to backend containers on multiple networks. By default, if you don't pass the `--net` flag when your `nginx-proxy` container is created, it will only be attached to the default `bridge` network. This means that it will not be able to connect to containers on networks other than `bridge`. @@ -337,6 +343,7 @@ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto; proxy_set_header X-Forwarded-Ssl $proxy_x_forwarded_ssl; proxy_set_header X-Forwarded-Port $proxy_x_forwarded_port; +proxy_set_header X-Forwarded-Path $request_uri; # Mitigate httpoxy attack (see README for details) proxy_set_header Proxy ""; From 9cd85f61d5165f5307cae1dfad5c165bc9821c75 Mon Sep 17 00:00:00 2001 From: Nicolas Duchon Date: Thu, 15 Jul 2021 21:47:03 +0200 Subject: [PATCH 05/23] build: build and push the dev branch to Dockerhub --- .github/workflows/dockerhub.yml | 43 +++++++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/.github/workflows/dockerhub.yml b/.github/workflows/dockerhub.yml index cb3635e..74283b7 100644 --- a/.github/workflows/dockerhub.yml +++ b/.github/workflows/dockerhub.yml @@ -7,6 +7,7 @@ on: push: branches: - main + - dev tags: - '*.*.*' paths-ignore: @@ -42,7 +43,8 @@ jobs: tags: | type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} - type=raw,value=latest,enable=${{ endsWith(github.ref, github.event.repository.default_branch) }} + type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }} + type=raw,value=dev,enable=${{ github.ref == 'refs/heads/dev' }} labels: | org.opencontainers.image.authors=Nicolas Duchon (@buchdag), Jason Wilder org.opencontainers.image.version=${{ env.GIT_DESCRIBE }} @@ -60,6 +62,7 @@ jobs: password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Build and push the Debian based image + if: github.ref == 'refs/heads/main' id: docker_build_debian uses: docker/build-push-action@v2 with: @@ -72,8 +75,25 @@ jobs: labels: ${{ steps.docker_meta_debian.outputs.labels }} - name: Images digests + if: github.ref == 'refs/heads/main' run: echo ${{ steps.docker_build_debian.outputs.digest }} + - name: Build and push the Debian based dev image + if: github.ref == 'refs/heads/dev' + id: docker_build_debian_dev + uses: docker/build-push-action@v2 + with: + file: Dockerfile + build-args: DOCKER_GEN_VERSION=main + platforms: linux/amd64,linux/arm64,linux/arm/v7 + push: true + tags: ${{ steps.docker_meta_debian.outputs.tags }} + labels: ${{ steps.docker_meta_debian.outputs.labels }} + + - name: Images digests + if: github.ref == 'refs/heads/dev' + run: echo ${{ steps.docker_build_debian_dev.outputs.digest }} + multiarch-build-alpine: runs-on: ubuntu-latest steps: @@ -96,7 +116,8 @@ jobs: tags: | type=semver,suffix=-alpine,pattern={{version}} type=semver,suffix=-alpine,pattern={{major}}.{{minor}} - type=raw,value=alpine,enable=${{ endsWith(github.ref, github.event.repository.default_branch) }} + type=raw,value=alpine,enable=${{ github.ref == 'refs/heads/main' }} + type=raw,value=dev-alpine,enable=${{ github.ref == 'refs/heads/dev' }} labels: | org.opencontainers.image.authors=Nicolas Duchon (@buchdag), Jason Wilder org.opencontainers.image.version=${{ env.GIT_DESCRIBE }} @@ -115,6 +136,7 @@ jobs: password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Build and push the Alpine based image + if: github.ref == 'refs/heads/main' id: docker_build_alpine uses: docker/build-push-action@v2 with: @@ -127,4 +149,21 @@ jobs: labels: ${{ steps.docker_meta_alpine.outputs.labels }} - name: Images digests + if: github.ref == 'refs/heads/main' run: echo ${{ steps.docker_build_alpine.outputs.digest }} + + - name: Build and push the Alpine based dev image + if: github.ref == 'refs/heads/dev' + id: docker_build_alpine_dev + uses: docker/build-push-action@v2 + with: + file: Dockerfile.alpine + build-args: DOCKER_GEN_VERSION=main + platforms: linux/amd64,linux/arm64,linux/arm/v7 + push: true + tags: ${{ steps.docker_meta_alpine.outputs.tags }} + labels: ${{ steps.docker_meta_alpine.outputs.labels }} + + - name: Images digests + if: github.ref == 'refs/heads/dev' + run: echo ${{ steps.docker_build_alpine_dev.outputs.digest }} From dad4a2d7bfcf71d517b35a4fe1f7f46c74b4fea8 Mon Sep 17 00:00:00 2001 From: Rafael Kraut Date: Tue, 20 Jul 2021 10:28:39 +0200 Subject: [PATCH 06/23] docs: remove unnecessary word --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9e248fa..262147c 100644 --- a/README.md +++ b/README.md @@ -119,7 +119,7 @@ You can also use wildcards at the beginning and the end of host name, like `*.ba ### Path-based Routing -You can have multiple containers proxied by the same `VIRTUAL_HOST` by adding a `VIRTUAL_PATH` environment variable containing the absolute path to where the container should be mounted. For example with `VIRTUAL_HOST=foo.example.com` and `VIRTUAL_PATH=/api/v2/service`, then requests to http://foo.example.com/api/v2/service will be routed to the container. If you wish to have a container serve the root while other containers serve other paths, make give the root container a `VIRTUAL_PATH` of `/`. Unmatched paths will be served by the container at `/` or will return the default nginx error page if no container has been assigned `/`. +You can have multiple containers proxied by the same `VIRTUAL_HOST` by adding a `VIRTUAL_PATH` environment variable containing the absolute path to where the container should be mounted. For example with `VIRTUAL_HOST=foo.example.com` and `VIRTUAL_PATH=/api/v2/service`, then requests to http://foo.example.com/api/v2/service will be routed to the container. If you wish to have a container serve the root while other containers serve other paths, give the root container a `VIRTUAL_PATH` of `/`. Unmatched paths will be served by the container at `/` or will return the default nginx error page if no container has been assigned `/`. The full request URI will be forwarded to the serving container in the `X-Forwarded-Path` header. From 28c73e5b52d281964812e411c0a411dabf1a387e Mon Sep 17 00:00:00 2001 From: Nicolas Duchon Date: Wed, 11 Aug 2021 18:04:53 +0200 Subject: [PATCH 07/23] fix: non working https with virtual path --- nginx.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nginx.tmpl b/nginx.tmpl index fc6f540..73fba26 100644 --- a/nginx.tmpl +++ b/nginx.tmpl @@ -389,7 +389,7 @@ server { {{ else }} {{ range $path, $container := $paths }} {{ $sum := sha1 $path }} - {{ $upstream := printf "%s-%s" $host $sum }} + {{ $upstream := printf "%s-%s" $upstream_name $sum }} {{ template "location" (dict "Path" $path "Proto" $proto "Upstream" $upstream "Host" $host "Vhostroot" $vhost_root) }} {{ end }} {{ end }} From 9df330e51ebb405966c942a4f5241faec16a462e Mon Sep 17 00:00:00 2001 From: Alexander Lieret Date: Tue, 6 Jul 2021 15:26:02 +0200 Subject: [PATCH 08/23] feat: Add user customizable default root response --- Dockerfile | 2 +- Dockerfile.alpine | 2 +- README.md | 10 +++++++++ nginx.tmpl | 11 ++++++++++ test/test_virtual-path/test_custom_conf.py | 6 ++++++ test/test_virtual-path/test_custom_conf.yml | 24 +++++++++++++++++++++ 6 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 test/test_virtual-path/test_custom_conf.py create mode 100644 test/test_virtual-path/test_custom_conf.yml diff --git a/Dockerfile b/Dockerfile index d5c71bc..bc4093d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # setup build arguments for version of dependencies to use -ARG DOCKER_GEN_VERSION=0.7.7 +ARG DOCKER_GEN_VERSION=main ARG FOREGO_VERSION=v0.17.0 # Use a specific version of golang to build both binaries diff --git a/Dockerfile.alpine b/Dockerfile.alpine index 2552615..98e9bc5 100644 --- a/Dockerfile.alpine +++ b/Dockerfile.alpine @@ -1,5 +1,5 @@ # setup build arguments for version of dependencies to use -ARG DOCKER_GEN_VERSION=0.7.7 +ARG DOCKER_GEN_VERSION=main ARG FOREGO_VERSION=v0.17.0 # Use a specific version of golang to build both binaries diff --git a/README.md b/README.md index 262147c..aa1b79b 100644 --- a/README.md +++ b/README.md @@ -123,6 +123,16 @@ You can have multiple containers proxied by the same `VIRTUAL_HOST` by adding a The full request URI will be forwarded to the serving container in the `X-Forwarded-Path` header. +**NOTE**: Your application needs to be able to generate links starting with `VIRTUAL_PATH`. This can be achieved by it being natively on this path or havin an option to prepend this path. The application does not need to expect this path in the request. + +#### DEFAULT_ROOT + +This environment variable of the nginx proxy container can be used to customize the return error page if no matching path is found. Furthermore it is possible to use anything which is compatible with the `return` statement of nginx. + +For example `DEFAUL_ROOT=418` will return a 418 error page instead of the normal 404 one. +Another example is `DEFAULT_ROOT="301 https://github.com/nginx-proxy/nginx-proxy/blob/main/README.md"` which would redirect an invalid request to this documentation. + + ### Multiple Networks With the addition of [overlay networking](https://docs.docker.com/engine/userguide/networking/get-started-overlay/) in Docker 1.9, your `nginx-proxy` container may need to connect to backend containers on multiple networks. By default, if you don't pass the `--net` flag when your `nginx-proxy` container is created, it will only be attached to the default `bridge` network. This means that it will not be able to connect to containers on networks other than `bridge`. diff --git a/nginx.tmpl b/nginx.tmpl index 73fba26..85ccbda 100644 --- a/nginx.tmpl +++ b/nginx.tmpl @@ -5,6 +5,7 @@ {{ $external_https_port := coalesce $.Env.HTTPS_PORT "443" }} {{ $debug_all := $.Env.DEBUG }} {{ $sha1_upstream_name := parseBool (coalesce $.Env.SHA1_UPSTREAM_NAME "false") }} +{{ $default_root_response := coalesce $.Env.DEFAULT_ROOT "404" }} {{ define "ssl_policy" }} {{ if eq .ssl_policy "Mozilla-Modern" }} @@ -392,6 +393,11 @@ server { {{ $upstream := printf "%s-%s" $upstream_name $sum }} {{ template "location" (dict "Path" $path "Proto" $proto "Upstream" $upstream "Host" $host "Vhostroot" $vhost_root) }} {{ end }} + {{ if (not (contains $paths "/")) }} + location / { + return {{ $default_root_response }}; + } + {{ end }} {{ end }} } @@ -429,6 +435,11 @@ server { {{ $upstream := printf "%s-%s" $upstream_name $sum }} {{ template "location" (dict "Path" $path "Proto" $proto "Upstream" $upstream "Host" $host "Vhostroot" $vhost_root) }} {{ end }} + {{ if (not (contains $paths "/")) }} + location / { + return {{ $default_root_response }}; + } + {{ end }} {{ end }} } diff --git a/test/test_virtual-path/test_custom_conf.py b/test/test_virtual-path/test_custom_conf.py new file mode 100644 index 0000000..68ecd3a --- /dev/null +++ b/test/test_virtual-path/test_custom_conf.py @@ -0,0 +1,6 @@ +import pytest + +def test_default_root_response(docker_compose, nginxproxy): + r = nginxproxy.get("http://nginx-proxy.test/") + assert r.status_code == 418 + diff --git a/test/test_virtual-path/test_custom_conf.yml b/test/test_virtual-path/test_custom_conf.yml new file mode 100644 index 0000000..2fffd65 --- /dev/null +++ b/test/test_virtual-path/test_custom_conf.yml @@ -0,0 +1,24 @@ +web1: + image: web + expose: + - "81" + environment: + WEB_PORTS: "81" + VIRTUAL_HOST: "nginx-proxy.test" + VIRTUAL_PATH: "/web1/" + +web2: + image: web + expose: + - "82" + environment: + WEB_PORTS: "82" + VIRTUAL_HOST: "nginx-proxy.test" + VIRTUAL_PATH: "/web2/" +sut: + image: nginxproxy/nginx-proxy:test + environment: + DEFAULT_ROOT: 418 + volumes: + - /var/run/docker.sock:/tmp/docker.sock:ro + - ../lib/ssl/dhparam.pem:/etc/nginx/dhparam/dhparam.pem:ro From 4b85e9582450ff07dcf205ad8ab5f5b1131599ef Mon Sep 17 00:00:00 2001 From: Alexander Lieret Date: Tue, 6 Jul 2021 15:36:06 +0200 Subject: [PATCH 09/23] feat: Replace path stripping with variable This commit removes the automatic path stripping and replaces it with a user configurable environment variable. This can be set individually for each container. --- README.md | 14 ++++++++++++++ nginx.tmpl | 12 +++++++----- test/test_virtual-path/test_custom_conf.yml | 3 +++ test/test_virtual-path/test_virtual_paths.yml | 2 ++ 4 files changed, 26 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index aa1b79b..33ec073 100644 --- a/README.md +++ b/README.md @@ -125,6 +125,20 @@ The full request URI will be forwarded to the serving container in the `X-Forwar **NOTE**: Your application needs to be able to generate links starting with `VIRTUAL_PATH`. This can be achieved by it being natively on this path or havin an option to prepend this path. The application does not need to expect this path in the request. +#### VIRTUAL_DEST + +This environment variable can be used to rewrite the `VIRTUAL_PATH` part of the requested URL to proxied application. The default value is empty (off). +Make sure that your settings won't result in the slash missing or being doubled. Both these versions can cause troubles. + +If the application runs natively on this sub-path or has a setting to do so, `VIRTUAL_DEST` should not be set or empty. +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 +``` + +In this example, the incoming request `http://example.tld/app1/foo` will be proxied as `http://app1/foo` instead of `http://app1/app1/foo`. + #### DEFAULT_ROOT This environment variable of the nginx proxy container can be used to customize the return error page if no matching path is found. Furthermore it is possible to use anything which is compatible with the `return` statement of nginx. diff --git a/nginx.tmpl b/nginx.tmpl index 85ccbda..b1e6249 100644 --- a/nginx.tmpl +++ b/nginx.tmpl @@ -62,7 +62,7 @@ location {{ .Path }} { {{ else if eq .Proto "grpc" }} grpc_pass {{ trim .Proto }}://{{ trim .Upstream }}; {{ else }} - proxy_pass {{ trim .Proto }}://{{ trim .Upstream }}/; + proxy_pass {{ trim .Proto }}://{{ trim .Upstream }}{{ trim .Dest }}; {{ end }} {{ if (exists (printf "/etc/nginx/htpasswd/%s" .Host)) }} @@ -386,12 +386,13 @@ server { {{ end }} {{ if eq $nPaths 0 }} - {{ template "location" (dict "Path" "/" "Proto" $proto "Upstream" $upstream_name "Host" $host "Vhostroot" $vhost_root) }} + {{ template "location" (dict "Path" "/" "Proto" $proto "Upstream" $upstream_name "Host" $host "Vhostroot" $vhost_root "Dest" "") }} {{ else }} {{ range $path, $container := $paths }} {{ $sum := sha1 $path }} {{ $upstream := printf "%s-%s" $upstream_name $sum }} - {{ template "location" (dict "Path" $path "Proto" $proto "Upstream" $upstream "Host" $host "Vhostroot" $vhost_root) }} + {{ $dest := (or (first (groupByKeys $container "Env.VIRTUAL_DEST")) "") }} + {{ template "location" (dict "Path" $path "Proto" $proto "Upstream" $upstream "Host" $host "Vhostroot" $vhost_root "Dest" $dest) }} {{ end }} {{ if (not (contains $paths "/")) }} location / { @@ -428,12 +429,13 @@ server { {{ end }} {{ if eq $nPaths 0 }} - {{ template "location" (dict "Path" "/" "Proto" $proto "Upstream" $upstream_name "Host" $host "Vhostroot" $vhost_root) }} + {{ template "location" (dict "Path" "/" "Proto" $proto "Upstream" $upstream_name "Host" $host "Vhostroot" $vhost_root "Dest" "") }} {{ else }} {{ range $path, $container := $paths }} {{ $sum := sha1 $path }} {{ $upstream := printf "%s-%s" $upstream_name $sum }} - {{ template "location" (dict "Path" $path "Proto" $proto "Upstream" $upstream "Host" $host "Vhostroot" $vhost_root) }} + {{ $dest := (or (first (groupByKeys $container "Env.VIRTUAL_DEST")) "") }} + {{ template "location" (dict "Path" $path "Proto" $proto "Upstream" $upstream "Host" $host "Vhostroot" $vhost_root "Dest" $dest) }} {{ end }} {{ if (not (contains $paths "/")) }} location / { diff --git a/test/test_virtual-path/test_custom_conf.yml b/test/test_virtual-path/test_custom_conf.yml index 2fffd65..2369bac 100644 --- a/test/test_virtual-path/test_custom_conf.yml +++ b/test/test_virtual-path/test_custom_conf.yml @@ -6,6 +6,7 @@ web1: WEB_PORTS: "81" VIRTUAL_HOST: "nginx-proxy.test" VIRTUAL_PATH: "/web1/" + VIRTUAL_DEST: "/" web2: image: web @@ -15,6 +16,8 @@ web2: WEB_PORTS: "82" VIRTUAL_HOST: "nginx-proxy.test" VIRTUAL_PATH: "/web2/" + VIRTUAL_DEST: "/" + sut: image: nginxproxy/nginx-proxy:test environment: diff --git a/test/test_virtual-path/test_virtual_paths.yml b/test/test_virtual-path/test_virtual_paths.yml index ca688eb..b335c3f 100644 --- a/test/test_virtual-path/test_virtual_paths.yml +++ b/test/test_virtual-path/test_virtual_paths.yml @@ -15,6 +15,7 @@ web1: WEB_PORTS: "81" VIRTUAL_HOST: "nginx-proxy.test" VIRTUAL_PATH: "/web1/" + VIRTUAL_DEST: "/" web2: image: web @@ -24,6 +25,7 @@ web2: WEB_PORTS: "82" VIRTUAL_HOST: "nginx-proxy.test" VIRTUAL_PATH: "/web2/" + VIRTUAL_DEST: "/" web3: image: web From 33eab70d321b727b245f5d59056b8a990048eb68 Mon Sep 17 00:00:00 2001 From: Alexander Lieret Date: Tue, 6 Jul 2021 15:41:14 +0200 Subject: [PATCH 10/23] feat: Add custom location block to virtual paths This features allows the custom location blocks to be added to the virtual path based routing. The custom config can be specified for each container individually. --- README.md | 9 ++++++ nginx.tmpl | 2 ++ test/test_virtual-path/alternate.conf | 1 + test/test_virtual-path/bar.conf | 1 + test/test_virtual-path/foo.conf | 1 + test/test_virtual-path/test_custom_conf.py | 32 +++++++++++++++++++++ test/test_virtual-path/test_custom_conf.yml | 22 ++++++++++++++ 7 files changed, 68 insertions(+) create mode 100644 test/test_virtual-path/alternate.conf create mode 100644 test/test_virtual-path/bar.conf create mode 100644 test/test_virtual-path/foo.conf diff --git a/README.md b/README.md index 33ec073..2f00d1c 100644 --- a/README.md +++ b/README.md @@ -120,6 +120,7 @@ You can also use wildcards at the beginning and the end of host name, like `*.ba ### Path-based Routing You can have multiple containers proxied by the same `VIRTUAL_HOST` by adding a `VIRTUAL_PATH` environment variable containing the absolute path to where the container should be mounted. For example with `VIRTUAL_HOST=foo.example.com` and `VIRTUAL_PATH=/api/v2/service`, then requests to http://foo.example.com/api/v2/service will be routed to the container. If you wish to have a container serve the root while other containers serve other paths, give the root container a `VIRTUAL_PATH` of `/`. Unmatched paths will be served by the container at `/` or will return the default nginx error page if no container has been assigned `/`. +It is also possible to specify multiple paths with regex locations like `VIRTUAL_PATH=~^/(app1|alternative1)/`. For further details see the nginx documentation on location blocks. This is not compatible with `VIRTUAL_DEST`. The full request URI will be forwarded to the serving container in the `X-Forwarded-Path` header. @@ -139,6 +140,14 @@ $ docker run -d -e VIRTUAL_HOST=example.tld -e VIRTUAL_PATH=/app1/ -e VIRTUAL_DE In this example, the incoming request `http://example.tld/app1/foo` will be proxied as `http://app1/foo` instead of `http://app1/app1/foo`. +#### Per-VIRTUAL_PATH location configuration + +The same options as from [Per-VIRTUAL_HOST location configuration](#Per-VIRTUAL_HOST-location-configuration) are available on a `VIRTUAL_PATH` basis. +The only difference is that the filename gets an additional block `HASH=$(echo -n $VIRTUAL_PATH | sha1sum | awk '{ print $1 }')`. This is the sha1-hash of the `VIRTUAL_PATH` (no newline). This is done filename sanitization purposes. +The used filename is `${VIRTUAL_HOST}_${HASH}_location` + +The filename of the previous example would be `example.tld_8610f6c344b4096614eab6e09d58885349f42faf_location`. + #### DEFAULT_ROOT This environment variable of the nginx proxy container can be used to customize the return error page if no matching path is found. Furthermore it is possible to use anything which is compatible with the `return` statement of nginx. diff --git a/nginx.tmpl b/nginx.tmpl index b1e6249..dd48d4b 100644 --- a/nginx.tmpl +++ b/nginx.tmpl @@ -72,6 +72,8 @@ location {{ .Path }} { {{ if (exists (printf "/etc/nginx/vhost.d/%s_location" .Host)) }} include {{ printf "/etc/nginx/vhost.d/%s_location" .Host}}; + {{ else 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) }}; {{ else if (exists "/etc/nginx/vhost.d/default_location") }} include /etc/nginx/vhost.d/default_location; {{ end }} diff --git a/test/test_virtual-path/alternate.conf b/test/test_virtual-path/alternate.conf new file mode 100644 index 0000000..541332e --- /dev/null +++ b/test/test_virtual-path/alternate.conf @@ -0,0 +1 @@ +rewrite ^/(web3|alt)/(.*) /$2 break; diff --git a/test/test_virtual-path/bar.conf b/test/test_virtual-path/bar.conf new file mode 100644 index 0000000..e8b0827 --- /dev/null +++ b/test/test_virtual-path/bar.conf @@ -0,0 +1 @@ +add_header X-test bar; diff --git a/test/test_virtual-path/foo.conf b/test/test_virtual-path/foo.conf new file mode 100644 index 0000000..8d8502d --- /dev/null +++ b/test/test_virtual-path/foo.conf @@ -0,0 +1 @@ +add_header X-test f00; \ No newline at end of file diff --git a/test/test_virtual-path/test_custom_conf.py b/test/test_virtual-path/test_custom_conf.py index 68ecd3a..eec149f 100644 --- a/test/test_virtual-path/test_custom_conf.py +++ b/test/test_virtual-path/test_custom_conf.py @@ -4,3 +4,35 @@ def test_default_root_response(docker_compose, nginxproxy): r = nginxproxy.get("http://nginx-proxy.test/") assert r.status_code == 418 +@pytest.mark.parametrize("stub,header", [ + ("nginx-proxy.test/web1", "bar"), + ("foo.nginx-proxy.test", "f00"), +]) +def test_custom_applies(docker_compose, nginxproxy, stub, header): + r = nginxproxy.get(f"http://{stub}/port") + assert r.status_code == 200 + assert "X-test" in r.headers + assert header == r.headers["X-test"] + +@pytest.mark.parametrize("stub,code", [ + ("nginx-proxy.test/foo", 418), + ("nginx-proxy.test/web2", 200), + ("nginx-proxy.test/web3", 200), + ("bar.nginx-proxy.test", 503), +]) +def test_custom_does_not_apply(docker_compose, nginxproxy, stub, code): + r = nginxproxy.get(f"http://{stub}/port") + assert r.status_code == code + assert "X-test" not in r.headers + +@pytest.mark.parametrize("stub,port", [ + ("nginx-proxy.test/web1", 81), + ("nginx-proxy.test/web2", 82), + ("nginx-proxy.test/web3", 83), + ("nginx-proxy.test/alt", 83), +]) +def test_alternate(docker_compose, nginxproxy, stub, port): + r = nginxproxy.get(f"http://{stub}/port") + assert r.status_code == 200 + assert r.text == f"answer from port {port}\n" + diff --git a/test/test_virtual-path/test_custom_conf.yml b/test/test_virtual-path/test_custom_conf.yml index 2369bac..abd9d0c 100644 --- a/test/test_virtual-path/test_custom_conf.yml +++ b/test/test_virtual-path/test_custom_conf.yml @@ -1,3 +1,12 @@ + +foo: + image: web + expose: + - "42" + environment: + WEB_PORTS: "42" + VIRTUAL_HOST: "foo.nginx-proxy.test" + web1: image: web expose: @@ -18,6 +27,15 @@ web2: VIRTUAL_PATH: "/web2/" VIRTUAL_DEST: "/" +web3: + image: web + expose: + - "83" + environment: + WEB_PORTS: "83" + VIRTUAL_HOST: "nginx-proxy.test" + VIRTUAL_PATH: "~ ^/(web3|alt)/" + sut: image: nginxproxy/nginx-proxy:test environment: @@ -25,3 +43,7 @@ sut: volumes: - /var/run/docker.sock:/tmp/docker.sock:ro - ../lib/ssl/dhparam.pem:/etc/nginx/dhparam/dhparam.pem:ro + - ./foo.conf:/etc/nginx/vhost.d/foo.nginx-proxy.test:ro + - ./bar.conf:/etc/nginx/vhost.d/nginx-proxy.test_918d687a929083edd0c7224ee2293e0e7c062ab4_location:ro + - ./alternate.conf:/etc/nginx/vhost.d/nginx-proxy.test_7fb22b74bbdf907425dbbad18e4462565cada230_location:ro + From c75622db87abf0aee6eb97affa95474ea0b2573b Mon Sep 17 00:00:00 2001 From: Nicolas Duchon Date: Fri, 13 Aug 2021 10:54:12 +0200 Subject: [PATCH 11/23] docs: fix typo in README.md Co-authored-by: Jonathan Underwood --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2f00d1c..9a69d1c 100644 --- a/README.md +++ b/README.md @@ -124,7 +124,7 @@ It is also possible to specify multiple paths with regex locations like `VIRTUAL The full request URI will be forwarded to the serving container in the `X-Forwarded-Path` header. -**NOTE**: Your application needs to be able to generate links starting with `VIRTUAL_PATH`. This can be achieved by it being natively on this path or havin an option to prepend this path. The application does not need to expect this path in the request. +**NOTE**: Your application needs to be able to generate links starting with `VIRTUAL_PATH`. This can be achieved by it being natively on this path or having an option to prepend this path. The application does not need to expect this path in the request. #### VIRTUAL_DEST From efb250da0120854e4f399d58e4a4c2e2db82333e Mon Sep 17 00:00:00 2001 From: Nicolas Duchon Date: Sat, 14 Aug 2021 21:38:13 +0200 Subject: [PATCH 12/23] fix: use most specific custom location config first Co-authored-by: Jonathan Underwood --- nginx.tmpl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nginx.tmpl b/nginx.tmpl index dd48d4b..c0f6db5 100644 --- a/nginx.tmpl +++ b/nginx.tmpl @@ -70,10 +70,10 @@ location {{ .Path }} { auth_basic_user_file {{ (printf "/etc/nginx/htpasswd/%s" .Host) }}; {{ end }} - {{ if (exists (printf "/etc/nginx/vhost.d/%s_location" .Host)) }} - include {{ printf "/etc/nginx/vhost.d/%s_location" .Host}}; - {{ else if (exists (printf "/etc/nginx/vhost.d/%s_%s_location" .Host (sha1 .Path) )) }} + {{ 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) }}; + {{ else if (exists (printf "/etc/nginx/vhost.d/%s_location" .Host)) }} + include {{ printf "/etc/nginx/vhost.d/%s_location" .Host}}; {{ else if (exists "/etc/nginx/vhost.d/default_location") }} include /etc/nginx/vhost.d/default_location; {{ end }} From 12887a977b3570e27775f19c13563d68ded4314e Mon Sep 17 00:00:00 2001 From: Nicolas Duchon Date: Sat, 14 Aug 2021 21:40:25 +0200 Subject: [PATCH 13/23] docs: update DEFAULT_ROOT documentation Co-authored-by: Jonathan Underwood --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 9a69d1c..880d0e8 100644 --- a/README.md +++ b/README.md @@ -154,6 +154,11 @@ This environment variable of the nginx proxy container can be used to customize For example `DEFAUL_ROOT=418` will return a 418 error page instead of the normal 404 one. Another example is `DEFAULT_ROOT="301 https://github.com/nginx-proxy/nginx-proxy/blob/main/README.md"` which would redirect an invalid request to this documentation. +Nginx variables such as $scheme, $host, and $request_uri can be used. However, care must be taken to make sure the $ signs are escaped properly. +If you want to use `301 $scheme://$host/myapp1$request_uri` you should use: + +* Bash: `DEFAULT_ROOT='301 $scheme://$host/myapp1$request_uri'` +* Docker Compose yaml: `- DEFAULT_ROOT: 301 $$scheme://$$host/myapp1$$request_uri` ### Multiple Networks From e08b3487c95b38cd3f094e86f9638a39c12bbf84 Mon Sep 17 00:00:00 2001 From: Alexander Lieret Date: Tue, 17 Aug 2021 10:51:32 +0200 Subject: [PATCH 14/23] test: Add test to cover SSL of path-based routing --- test/test_ssl/test_virtual_path.py | 15 +++++++++++++++ test/test_ssl/test_virtual_path.yml | 27 +++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 test/test_ssl/test_virtual_path.py create mode 100644 test/test_ssl/test_virtual_path.yml diff --git a/test/test_ssl/test_virtual_path.py b/test/test_ssl/test_virtual_path.py new file mode 100644 index 0000000..508653f --- /dev/null +++ b/test/test_ssl/test_virtual_path.py @@ -0,0 +1,15 @@ +import pytest + +@pytest.mark.parametrize("path", ["web1", "web2"]) +def test_web1_http_redirects_to_https(docker_compose, nginxproxy, path): + r = nginxproxy.get("http://www.nginx-proxy.tld/%s/port" % path, allow_redirects=False) + assert r.status_code == 301 + assert "Location" in r.headers + assert "https://www.nginx-proxy.tld/%s/port" % path == r.headers['Location'] + +@pytest.mark.parametrize("path,port", [("web1", 81), ("web2", 82)]) +def test_web1_https_is_forwarded(docker_compose, nginxproxy, path, port): + r = nginxproxy.get("https://www.nginx-proxy.tld/%s/port" % path, allow_redirects=False) + assert r.status_code == 200 + assert "answer from port %d\n" % port in r.text + diff --git a/test/test_ssl/test_virtual_path.yml b/test/test_ssl/test_virtual_path.yml new file mode 100644 index 0000000..07175ac --- /dev/null +++ b/test/test_ssl/test_virtual_path.yml @@ -0,0 +1,27 @@ +web1: + image: web + expose: + - "81" + environment: + WEB_PORTS: "81" + VIRTUAL_HOST: "www.nginx-proxy.tld" + VIRTUAL_PATH: "/web1/" + VIRTUAL_DEST: "/" + +web2: + image: web + expose: + - "82" + environment: + WEB_PORTS: "82" + VIRTUAL_HOST: "www.nginx-proxy.tld" + VIRTUAL_PATH: "/web2/" + VIRTUAL_DEST: "/" + +sut: + image: nginxproxy/nginx-proxy:test + volumes: + - /var/run/docker.sock:/tmp/docker.sock:ro + - ../lib/ssl/dhparam.pem:/etc/nginx/dhparam/dhparam.pem:ro + - ./certs:/etc/nginx/certs:ro + From 4099fcd61872fe4093323771090ccad570a80e77 Mon Sep 17 00:00:00 2001 From: Alexander Lieret Date: Tue, 17 Aug 2021 11:08:56 +0200 Subject: [PATCH 15/23] test: Add test case for default app redirect Co-authored-by: Jonathan Underwood --- test/test_virtual-path/test_forwarding.py | 18 ++++++++++++++++++ test/test_virtual-path/test_forwarding.yml | 18 ++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 test/test_virtual-path/test_forwarding.py create mode 100644 test/test_virtual-path/test_forwarding.yml diff --git a/test/test_virtual-path/test_forwarding.py b/test/test_virtual-path/test_forwarding.py new file mode 100644 index 0000000..062dd6c --- /dev/null +++ b/test/test_virtual-path/test_forwarding.py @@ -0,0 +1,18 @@ +import pytest + +def test_root_redirects_to_web1(docker_compose, nginxproxy): + r = nginxproxy.get("http://www.nginx-proxy.tld/port", allow_redirects=False) + assert r.status_code == 301 + assert "Location" in r.headers + assert "http://www.nginx-proxy.tld/web1/port" == r.headers['Location'] + +def test_direct_access(docker_compose, nginxproxy): + r = nginxproxy.get("http://www.nginx-proxy.tld/web1/port", allow_redirects=False) + assert r.status_code == 200 + assert "answer from port 81\n" in r.text + +def test_root_is_forwarded(docker_compose, nginxproxy): + r = nginxproxy.get("http://www.nginx-proxy.tld/port", allow_redirects=True) + assert r.status_code == 200 + assert "answer from port 81\n" in r.text + diff --git a/test/test_virtual-path/test_forwarding.yml b/test/test_virtual-path/test_forwarding.yml new file mode 100644 index 0000000..78662d8 --- /dev/null +++ b/test/test_virtual-path/test_forwarding.yml @@ -0,0 +1,18 @@ +web1: + image: web + expose: + - "81" + environment: + WEB_PORTS: "81" + VIRTUAL_HOST: "www.nginx-proxy.tld" + VIRTUAL_PATH: "/web1/" + VIRTUAL_DEST: "/" + +sut: + image: nginxproxy/nginx-proxy:test + volumes: + - /var/run/docker.sock:/tmp/docker.sock:ro + - ../lib/ssl/dhparam.pem:/etc/nginx/dhparam/dhparam.pem:ro + - ./certs:/etc/nginx/certs:ro + environment: + - DEFAULT_ROOT=301 http://$$host/web1$$request_uri From 6a580ad66435b7d2b81a3df0cfb857636db46ef3 Mon Sep 17 00:00:00 2001 From: Alexander Lieret Date: Wed, 18 Aug 2021 15:34:30 +0200 Subject: [PATCH 16/23] test: Add test case for location config priority --- test/test_virtual-path/default.conf | 1 + test/test_virtual-path/host.conf | 1 + test/test_virtual-path/path.conf | 1 + .../test_location_precedence.py | 32 ++++++++++++++++ .../test_location_precedence.yml | 38 +++++++++++++++++++ 5 files changed, 73 insertions(+) create mode 100644 test/test_virtual-path/default.conf create mode 100644 test/test_virtual-path/host.conf create mode 100644 test/test_virtual-path/path.conf create mode 100644 test/test_virtual-path/test_location_precedence.py create mode 100644 test/test_virtual-path/test_location_precedence.yml diff --git a/test/test_virtual-path/default.conf b/test/test_virtual-path/default.conf new file mode 100644 index 0000000..087e66c --- /dev/null +++ b/test/test_virtual-path/default.conf @@ -0,0 +1 @@ +add_header X-test-default true; diff --git a/test/test_virtual-path/host.conf b/test/test_virtual-path/host.conf new file mode 100644 index 0000000..fe05265 --- /dev/null +++ b/test/test_virtual-path/host.conf @@ -0,0 +1 @@ +add_header X-test-host true; diff --git a/test/test_virtual-path/path.conf b/test/test_virtual-path/path.conf new file mode 100644 index 0000000..6c23b9a --- /dev/null +++ b/test/test_virtual-path/path.conf @@ -0,0 +1 @@ +add_header X-test-path true; diff --git a/test/test_virtual-path/test_location_precedence.py b/test/test_virtual-path/test_location_precedence.py new file mode 100644 index 0000000..415c6c1 --- /dev/null +++ b/test/test_virtual-path/test_location_precedence.py @@ -0,0 +1,32 @@ +import pytest + +def test_location_precedence_case1(docker_compose, nginxproxy): + r = nginxproxy.get(f"http://foo.nginx-proxy.test/web1/port") + assert r.status_code == 200 + + assert "X-test-default" in r.headers + assert "X-test-host" not in r.headers + assert "X-test-path" not in r.headers + + assert r.headers["X-test-default"] == "true" + +def test_location_precedence_case2(docker_compose, nginxproxy): + r = nginxproxy.get(f"http://bar.nginx-proxy.test/web2/port") + assert r.status_code == 200 + + assert "X-test-default" not in r.headers + assert "X-test-host" in r.headers + assert "X-test-path" not in r.headers + + assert r.headers["X-test-host"] == "true" + +def test_location_precedence_case3(docker_compose, nginxproxy): + r = nginxproxy.get(f"http://bar.nginx-proxy.test/web3/port") + assert r.status_code == 200 + + assert "X-test-default" not in r.headers + assert "X-test-host" not in r.headers + assert "X-test-path" in r.headers + + assert r.headers["X-test-path"] == "true" + diff --git a/test/test_virtual-path/test_location_precedence.yml b/test/test_virtual-path/test_location_precedence.yml new file mode 100644 index 0000000..be93b58 --- /dev/null +++ b/test/test_virtual-path/test_location_precedence.yml @@ -0,0 +1,38 @@ +web1: + image: web + expose: + - "81" + environment: + WEB_PORTS: "81" + VIRTUAL_HOST: "foo.nginx-proxy.test" + VIRTUAL_PATH: "/web1/" + VIRTUAL_DEST: "/" + +web2: + image: web + expose: + - "82" + environment: + WEB_PORTS: "82" + VIRTUAL_HOST: "bar.nginx-proxy.test" + VIRTUAL_PATH: "/web2/" + VIRTUAL_DEST: "/" + +web3: + image: web + expose: + - "83" + environment: + WEB_PORTS: "83" + VIRTUAL_HOST: "bar.nginx-proxy.test" + VIRTUAL_PATH: "/web3/" + VIRTUAL_DEST: "/" + +sut: + image: nginxproxy/nginx-proxy:test + volumes: + - /var/run/docker.sock:/tmp/docker.sock:ro + - ../lib/ssl/dhparam.pem:/etc/nginx/dhparam/dhparam.pem:ro + - ./default.conf:/etc/nginx/vhost.d/default_location:ro + - ./host.conf:/etc/nginx/vhost.d/bar.nginx-proxy.test_location:ro + - ./path.conf:/etc/nginx/vhost.d/bar.nginx-proxy.test_99f2db0ed8aa95dbb5b87fca79c7eff2ff6bb8bd_location:ro From 28c74e8daeb0eb7f5bcace7db4c5b570af6b5436 Mon Sep 17 00:00:00 2001 From: Alexander Lieret Date: Tue, 24 Aug 2021 15:51:39 +0200 Subject: [PATCH 17/23] fix: Move NETWORK_ACCESS to location block --- network_internal.conf | 1 + nginx.tmpl | 33 +++++++++++++++++---------------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/network_internal.conf b/network_internal.conf index cdf3c9c..bacceb1 100644 --- a/network_internal.conf +++ b/network_internal.conf @@ -3,4 +3,5 @@ allow 127.0.0.0/8; allow 10.0.0.0/8; allow 192.168.0.0/16; allow 172.16.0.0/12; +allow fc00::/7; # IPv6 local address range deny all; diff --git a/nginx.tmpl b/nginx.tmpl index c0f6db5..13ca7a4 100644 --- a/nginx.tmpl +++ b/nginx.tmpl @@ -52,6 +52,11 @@ {{ define "location" }} location {{ .Path }} { + {{ if eq .NetworkTag "internal" }} + # Only allow traffic from internal clients + include /etc/nginx/network_internal.conf; + {{ end }} + {{ if eq .Proto "uwsgi" }} include uwsgi_params; uwsgi_pass {{ trim .Proto }}://{{ trim .Upstream }}; @@ -277,8 +282,6 @@ server { {{/* Get the SERVER_TOKENS defined by containers w/ the same vhost, falling back to "" */}} {{ $server_tokens := trim (or (first (groupByKeys $containers "Env.SERVER_TOKENS")) "") }} -{{/* Get the NETWORK_ACCESS defined by containers w/ the same vhost, falling back to "external" */}} -{{ $network_tag := or (first (groupByKeys $containers "Env.NETWORK_ACCESS")) "external" }} {{/* Get the HTTPS_METHOD defined by containers w/ the same vhost, falling back to "redirect" */}} {{ $https_method := or (first (groupByKeys $containers "Env.HTTPS_METHOD")) (or $.Env.HTTPS_METHOD "redirect") }} @@ -353,11 +356,6 @@ server { {{ end }} {{ $access_log }} - {{ if eq $network_tag "internal" }} - # Only allow traffic from internal clients - include /etc/nginx/network_internal.conf; - {{ end }} - {{ template "ssl_policy" (dict "ssl_policy" $ssl_policy) }} ssl_session_timeout 5m; @@ -388,13 +386,17 @@ server { {{ end }} {{ if eq $nPaths 0 }} - {{ template "location" (dict "Path" "/" "Proto" $proto "Upstream" $upstream_name "Host" $host "Vhostroot" $vhost_root "Dest" "") }} + {{/* Get the NETWORK_ACCESS defined by containers w/ the same vhost, falling back to "external" */}} + {{ $network_tag := or (first (groupByKeys $containers "Env.NETWORK_ACCESS")) "external" }} + {{ template "location" (dict "Path" "/" "Proto" $proto "Upstream" $upstream_name "Host" $host "Vhostroot" $vhost_root "Dest" "" "NetworkTag" $network_tag) }} {{ else }} {{ range $path, $container := $paths }} + {{/* Get the NETWORK_ACCESS defined by containers w/ the same vhost, falling back to "external" */}} + {{ $network_tag := or (first (groupByKeys $container "Env.NETWORK_ACCESS")) "external" }} {{ $sum := sha1 $path }} {{ $upstream := printf "%s-%s" $upstream_name $sum }} {{ $dest := (or (first (groupByKeys $container "Env.VIRTUAL_DEST")) "") }} - {{ template "location" (dict "Path" $path "Proto" $proto "Upstream" $upstream "Host" $host "Vhostroot" $vhost_root "Dest" $dest) }} + {{ template "location" (dict "Path" $path "Proto" $proto "Upstream" $upstream "Host" $host "Vhostroot" $vhost_root "Dest" $dest "NetworkTag" $network_tag) }} {{ end }} {{ if (not (contains $paths "/")) }} location / { @@ -419,11 +421,6 @@ server { {{ end }} {{ $access_log }} - {{ if eq $network_tag "internal" }} - # Only allow traffic from internal clients - include /etc/nginx/network_internal.conf; - {{ end }} - {{ if (exists (printf "/etc/nginx/vhost.d/%s" $host)) }} include {{ printf "/etc/nginx/vhost.d/%s" $host }}; {{ else if (exists "/etc/nginx/vhost.d/default") }} @@ -431,13 +428,17 @@ server { {{ end }} {{ if eq $nPaths 0 }} - {{ template "location" (dict "Path" "/" "Proto" $proto "Upstream" $upstream_name "Host" $host "Vhostroot" $vhost_root "Dest" "") }} + {{/* Get the NETWORK_ACCESS defined by containers w/ the same vhost, falling back to "external" */}} + {{ $network_tag := or (first (groupByKeys $containers "Env.NETWORK_ACCESS")) "external" }} + {{ template "location" (dict "Path" "/" "Proto" $proto "Upstream" $upstream_name "Host" $host "Vhostroot" $vhost_root "Dest" "" "NetworkTag" $network_tag) }} {{ else }} {{ range $path, $container := $paths }} + {{/* Get the NETWORK_ACCESS defined by containers w/ the same vhost, falling back to "external" */}} + {{ $network_tag := or (first (groupByKeys $container "Env.NETWORK_ACCESS")) "external" }} {{ $sum := sha1 $path }} {{ $upstream := printf "%s-%s" $upstream_name $sum }} {{ $dest := (or (first (groupByKeys $container "Env.VIRTUAL_DEST")) "") }} - {{ template "location" (dict "Path" $path "Proto" $proto "Upstream" $upstream "Host" $host "Vhostroot" $vhost_root "Dest" $dest) }} + {{ template "location" (dict "Path" $path "Proto" $proto "Upstream" $upstream "Host" $host "Vhostroot" $vhost_root "Dest" $dest "NetworkTag" $network_tag) }} {{ end }} {{ if (not (contains $paths "/")) }} location / { From 2509fc1076d50832e124955a829c7a94634be98e Mon Sep 17 00:00:00 2001 From: Alexander Lieret Date: Mon, 30 Aug 2021 12:19:10 +0200 Subject: [PATCH 18/23] test: Add test cases for NETWORK_ACCESS=internal --- test/test_internal/network_internal.conf | 11 ++++++++++ test/test_internal/test_per-vhost.py | 14 ++++++++++++ test/test_internal/test_per-vhost.yml | 24 ++++++++++++++++++++ test/test_internal/test_per-vpath.py | 14 ++++++++++++ test/test_internal/test_per-vpath.yml | 28 ++++++++++++++++++++++++ 5 files changed, 91 insertions(+) create mode 100644 test/test_internal/network_internal.conf create mode 100644 test/test_internal/test_per-vhost.py create mode 100644 test/test_internal/test_per-vhost.yml create mode 100644 test/test_internal/test_per-vpath.py create mode 100644 test/test_internal/test_per-vpath.yml diff --git a/test/test_internal/network_internal.conf b/test/test_internal/network_internal.conf new file mode 100644 index 0000000..496e569 --- /dev/null +++ b/test/test_internal/network_internal.conf @@ -0,0 +1,11 @@ +# Only allow traffic from internal clients +allow 127.0.0.0/8; +allow 10.0.0.0/8; +allow 192.168.0.0/16; +allow 172.16.0.0/12; +allow fc00::/7; # IPv6 local address range +deny all; + +# Dummy header for testing +add_header X-network internal; + diff --git a/test/test_internal/test_per-vhost.py b/test/test_internal/test_per-vhost.py new file mode 100644 index 0000000..4586cc0 --- /dev/null +++ b/test/test_internal/test_per-vhost.py @@ -0,0 +1,14 @@ +import pytest + +def test_network_web1(docker_compose, nginxproxy): + r = nginxproxy.get("http://web1.nginx-proxy.local/port") + assert r.status_code == 200 + assert r.text == "answer from port 81\n" + assert "X-network" in r.headers + assert "internal" == r.headers["X-network"] + +def test_network_web2(docker_compose, nginxproxy): + r = nginxproxy.get("http://web2.nginx-proxy.local/port") + assert r.status_code == 200 + assert r.text == "answer from port 82\n" + assert "X-network" not in r.headers diff --git a/test/test_internal/test_per-vhost.yml b/test/test_internal/test_per-vhost.yml new file mode 100644 index 0000000..a935e53 --- /dev/null +++ b/test/test_internal/test_per-vhost.yml @@ -0,0 +1,24 @@ +web1: + image: web + expose: + - "81" + environment: + WEB_PORTS: 81 + VIRTUAL_HOST: web1.nginx-proxy.local + NETWORK_ACCESS: internal + +web2: + image: web + expose: + - "82" + environment: + WEB_PORTS: 82 + VIRTUAL_HOST: web2.nginx-proxy.local + +sut: + image: nginxproxy/nginx-proxy:test + volumes: + - /var/run/docker.sock:/tmp/docker.sock:ro + - ../lib/ssl/dhparam.pem:/etc/nginx/dhparam/dhparam.pem:ro + - ./network_internal.conf:/etc/nginx/network_internal.conf:ro + diff --git a/test/test_internal/test_per-vpath.py b/test/test_internal/test_per-vpath.py new file mode 100644 index 0000000..e95fe00 --- /dev/null +++ b/test/test_internal/test_per-vpath.py @@ -0,0 +1,14 @@ +import pytest + +def test_network_web1(docker_compose, nginxproxy): + r = nginxproxy.get("http://nginx-proxy.local/web1/port") + assert r.status_code == 200 + assert r.text == "answer from port 81\n" + assert "X-network" in r.headers + assert "internal" == r.headers["X-network"] + +def test_network_web2(docker_compose, nginxproxy): + r = nginxproxy.get("http://nginx-proxy.local/web2/port") + assert r.status_code == 200 + assert r.text == "answer from port 82\n" + assert "X-network" not in r.headers diff --git a/test/test_internal/test_per-vpath.yml b/test/test_internal/test_per-vpath.yml new file mode 100644 index 0000000..1ea470f --- /dev/null +++ b/test/test_internal/test_per-vpath.yml @@ -0,0 +1,28 @@ +web1: + image: web + expose: + - "81" + environment: + WEB_PORTS: 81 + VIRTUAL_HOST: nginx-proxy.local + VIRTUAL_PATH: /web1/ + VIRTUAL_DEST: / + NETWORK_ACCESS: internal + +web2: + image: web + expose: + - "82" + environment: + WEB_PORTS: 82 + VIRTUAL_HOST: nginx-proxy.local + VIRTUAL_PATH: /web2/ + VIRTUAL_DEST: / + +sut: + image: nginxproxy/nginx-proxy:test + volumes: + - /var/run/docker.sock:/tmp/docker.sock:ro + - ../lib/ssl/dhparam.pem:/etc/nginx/dhparam/dhparam.pem:ro + - ./network_internal.conf:/etc/nginx/network_internal.conf:ro + From 7ede0fa4b97840c02aeac5498c43d616c74b6221 Mon Sep 17 00:00:00 2001 From: Alexander Lieret Date: Mon, 30 Aug 2021 14:37:28 +0200 Subject: [PATCH 19/23] test: fix: Rename new test files --- .../{test_per-vhost.py => test_internal-per-vhost.py} | 0 .../{test_per-vhost.yml => test_internal-per-vhost.yml} | 0 .../{test_per-vpath.py => test_internal-per-vpath.py} | 0 .../{test_per-vpath.yml => test_internal-per-vpath.yml} | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename test/test_internal/{test_per-vhost.py => test_internal-per-vhost.py} (100%) rename test/test_internal/{test_per-vhost.yml => test_internal-per-vhost.yml} (100%) rename test/test_internal/{test_per-vpath.py => test_internal-per-vpath.py} (100%) rename test/test_internal/{test_per-vpath.yml => test_internal-per-vpath.yml} (100%) diff --git a/test/test_internal/test_per-vhost.py b/test/test_internal/test_internal-per-vhost.py similarity index 100% rename from test/test_internal/test_per-vhost.py rename to test/test_internal/test_internal-per-vhost.py diff --git a/test/test_internal/test_per-vhost.yml b/test/test_internal/test_internal-per-vhost.yml similarity index 100% rename from test/test_internal/test_per-vhost.yml rename to test/test_internal/test_internal-per-vhost.yml diff --git a/test/test_internal/test_per-vpath.py b/test/test_internal/test_internal-per-vpath.py similarity index 100% rename from test/test_internal/test_per-vpath.py rename to test/test_internal/test_internal-per-vpath.py diff --git a/test/test_internal/test_per-vpath.yml b/test/test_internal/test_internal-per-vpath.yml similarity index 100% rename from test/test_internal/test_per-vpath.yml rename to test/test_internal/test_internal-per-vpath.yml From 08c9586346703dfa7035404ca7ab724afd3a0e54 Mon Sep 17 00:00:00 2001 From: Alexander Lieret Date: Tue, 21 Sep 2021 14:07:41 +0200 Subject: [PATCH 20/23] fix: Handle VIRTUAL_PROTO on virtual path basis --- nginx.tmpl | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/nginx.tmpl b/nginx.tmpl index 13ca7a4..0eeeac9 100644 --- a/nginx.tmpl +++ b/nginx.tmpl @@ -276,9 +276,6 @@ server { {{ $default_host := or ($.Env.DEFAULT_HOST) "" }} {{ $default_server := index (dict $host "" $default_host "default_server") $host }} -{{/* Get the VIRTUAL_PROTO defined by containers w/ the same vhost, falling back to "http" */}} -{{ $proto := trim (or (first (groupByKeys $containers "Env.VIRTUAL_PROTO")) "http") }} - {{/* Get the SERVER_TOKENS defined by containers w/ the same vhost, falling back to "" */}} {{ $server_tokens := trim (or (first (groupByKeys $containers "Env.SERVER_TOKENS")) "") }} @@ -386,11 +383,17 @@ server { {{ end }} {{ if eq $nPaths 0 }} + {{/* Get the VIRTUAL_PROTO defined by containers w/ the same vhost, falling back to "http" */}} + {{ $proto := trim (or (first (groupByKeys $containers "Env.VIRTUAL_PROTO")) "http") }} + {{/* Get the NETWORK_ACCESS defined by containers w/ the same vhost, falling back to "external" */}} {{ $network_tag := or (first (groupByKeys $containers "Env.NETWORK_ACCESS")) "external" }} {{ template "location" (dict "Path" "/" "Proto" $proto "Upstream" $upstream_name "Host" $host "Vhostroot" $vhost_root "Dest" "" "NetworkTag" $network_tag) }} {{ else }} {{ range $path, $container := $paths }} + {{/* Get the VIRTUAL_PROTO defined by containers w/ the same vhost-vpath, falling back to "http" */}} + {{ $proto := trim (or (first (groupByKeys $container "Env.VIRTUAL_PROTO")) "http") }} + {{/* Get the NETWORK_ACCESS defined by containers w/ the same vhost, falling back to "external" */}} {{ $network_tag := or (first (groupByKeys $container "Env.NETWORK_ACCESS")) "external" }} {{ $sum := sha1 $path }} @@ -428,11 +431,17 @@ server { {{ end }} {{ if eq $nPaths 0 }} + {{/* Get the VIRTUAL_PROTO defined by containers w/ the same vhost, falling back to "http" */}} + {{ $proto := trim (or (first (groupByKeys $containers "Env.VIRTUAL_PROTO")) "http") }} + {{/* Get the NETWORK_ACCESS defined by containers w/ the same vhost, falling back to "external" */}} {{ $network_tag := or (first (groupByKeys $containers "Env.NETWORK_ACCESS")) "external" }} {{ template "location" (dict "Path" "/" "Proto" $proto "Upstream" $upstream_name "Host" $host "Vhostroot" $vhost_root "Dest" "" "NetworkTag" $network_tag) }} {{ else }} {{ range $path, $container := $paths }} + {{/* Get the VIRTUAL_PROTO defined by containers w/ the same vhost-vpath, falling back to "http" */}} + {{ $proto := trim (or (first (groupByKeys $container "Env.VIRTUAL_PROTO")) "http") }} + {{/* Get the NETWORK_ACCESS defined by containers w/ the same vhost, falling back to "external" */}} {{ $network_tag := or (first (groupByKeys $container "Env.NETWORK_ACCESS")) "external" }} {{ $sum := sha1 $path }} From 01446472dddb3c7ebbe7f69c7269b3461daa9aec Mon Sep 17 00:00:00 2001 From: Nicolas Duchon Date: Thu, 24 Feb 2022 15:08:45 +0100 Subject: [PATCH 21/23] Revert "ci: use docker-gen main on dev branch tests" This reverts commit b6e9cdc065a19ce7e7cb0dcf2c11cd7c9fe1d063. --- .github/workflows/test.yml | 20 ++++---------------- Makefile | 6 ------ 2 files changed, 4 insertions(+), 22 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8c4a173..6be93bd 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -3,16 +3,13 @@ name: Tests on: workflow_dispatch: push: - branches: - - main - - dev paths-ignore: - - 'LICENSE' - - '**.md' + - 'LICENSE' + - '**.md' pull_request: paths-ignore: - - 'LICENSE' - - '**.md' + - 'LICENSE' + - '**.md' jobs: unit: @@ -42,15 +39,6 @@ jobs: - name: Build Docker nginx proxy test image run: make build-nginx-proxy-test-${{ matrix.base_docker_image }} - if: | - ( github.event_name == 'push' && github.ref != 'refs/heads/dev' ) || - ( github.event_name == 'pull_request' && github.base_ref != 'dev' ) - - - name: Build Docker nginx proxy dev test image - run: make build-nginx-proxy-test-${{ matrix.base_docker_image }}-dev - if: | - ( github.event_name == 'push' && github.ref == 'refs/heads/dev' ) || - ( github.event_name == 'pull_request' && github.base_ref == 'dev' ) - name: Run tests run: pytest diff --git a/Makefile b/Makefile index 60406b6..ab44880 100644 --- a/Makefile +++ b/Makefile @@ -11,12 +11,6 @@ build-nginx-proxy-test-debian: build-nginx-proxy-test-alpine: docker build --build-arg NGINX_PROXY_VERSION="test" -f Dockerfile.alpine -t nginxproxy/nginx-proxy:test . -build-nginx-proxy-test-debian-dev: - docker build --build-arg DOCKER_GEN_VERSION=main -t nginxproxy/nginx-proxy:test . - -build-nginx-proxy-test-alpine-dev: - docker build -f Dockerfile.alpine --build-arg DOCKER_GEN_VERSION=main -t nginxproxy/nginx-proxy:test . - test-debian: build-webserver build-nginx-proxy-test-debian test/pytest.sh From b6b7133a2eba412749a6e90856b98292ce09c3af Mon Sep 17 00:00:00 2001 From: Nicolas Duchon Date: Thu, 24 Feb 2022 15:17:47 +0100 Subject: [PATCH 22/23] fix: minor fixes on nginx template --- nginx.tmpl | 70 +++++++++++++++++++++++++++--------------------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/nginx.tmpl b/nginx.tmpl index 0eeeac9..351cf88 100644 --- a/nginx.tmpl +++ b/nginx.tmpl @@ -61,8 +61,8 @@ location {{ .Path }} { include uwsgi_params; uwsgi_pass {{ trim .Proto }}://{{ trim .Upstream }}; {{ else if eq .Proto "fastcgi" }} - root {{ trim .Vhostroot }}; - include fastcgi.conf; + root {{ trim .VhostRoot }}; + include fastcgi_params; fastcgi_pass {{ trim .Upstream }}; {{ else if eq .Proto "grpc" }} grpc_pass {{ trim .Proto }}://{{ trim .Upstream }}; @@ -85,12 +85,12 @@ location {{ .Path }} { } {{ end }} -{{ define "upstream-definition" }} +{{ define "upstream" }} {{ $networks := .Networks }} {{ $debug_all := .Debug }} upstream {{ .Upstream }} { - {{ $server_found := "false" }} - {{ range $container := .Containers }} + {{ $server_found := "false" }} + {{ range $container := .Containers }} {{ $debug := (eq (coalesce $container.Env.DEBUG $debug_all "false") "true") }} {{/* If only 1 port exposed, use that as a default, else 80 */}} {{ $defaultPort := (when (eq (len $container.Addresses) 1) (first $container.Addresses) (dict "Port" "80")).Port }} @@ -100,46 +100,46 @@ location {{ .Path }} { # Exposed ports: {{ $container.Addresses }} # Default virtual port: {{ $defaultPort }} # VIRTUAL_PORT: {{ $container.Env.VIRTUAL_PORT }} - {{ if not $address }} + {{ if not $address }} # /!\ Virtual port not exposed - {{ end }} + {{ end }} {{ end }} - {{ range $knownNetwork := $networks }} - {{ range $containerNetwork := $container.Networks }} - {{ if (and (ne $containerNetwork.Name "ingress") (or (eq $knownNetwork.Name $containerNetwork.Name) (eq $knownNetwork.Name "host"))) }} + {{ range $knownNetwork := $networks }} + {{ range $containerNetwork := $container.Networks }} + {{ if (and (ne $containerNetwork.Name "ingress") (or (eq $knownNetwork.Name $containerNetwork.Name) (eq $knownNetwork.Name "host"))) }} ## Can be connected with "{{ $containerNetwork.Name }}" network - {{ if $address }} - {{/* If we got the containers from swarm and this container's port is published to host, use host IP:PORT */}} - {{ if and $container.Node.ID $address.HostPort }} - {{ $server_found = "true" }} + {{ if $address }} + {{/* If we got the containers from swarm and this container's port is published to host, use host IP:PORT */}} + {{ if and $container.Node.ID $address.HostPort }} + {{ $server_found = "true" }} # {{ $container.Node.Name }}/{{ $container.Name }} server {{ $container.Node.Address.IP }}:{{ $address.HostPort }}; - {{/* If there is no swarm node or the port is not published on host, use container's IP:PORT */}} - {{ else if $containerNetwork }} - {{ $server_found = "true" }} + {{/* If there is no swarm node or the port is not published on host, use container's IP:PORT */}} + {{ else if $containerNetwork }} + {{ $server_found = "true" }} # {{ $container.Name }} server {{ $containerNetwork.IP }}:{{ $address.Port }}; - {{ end }} - {{ else if $containerNetwork }} + {{ end }} + {{ else if $containerNetwork }} # {{ $container.Name }} - {{ if $containerNetwork.IP }} - {{ $server_found = "true" }} + {{ if $containerNetwork.IP }} + {{ $server_found = "true" }} server {{ $containerNetwork.IP }}:{{ $port }}; - {{ else }} + {{ else }} # /!\ No IP for this network! - {{ end }} - {{ end }} - {{ else }} - # Cannot connect to network '{{ $containerNetwork.Name }}' of this container + {{ end }} {{ end }} + {{ else }} + # Cannot connect to network '{{ $containerNetwork.Name }}' of this container {{ end }} {{ end }} {{ end }} - {{/* nginx-proxy/nginx-proxy#1105 */}} - {{ if (eq $server_found "false") }} + {{ end }} + {{/* nginx-proxy/nginx-proxy#1105 */}} + {{ if (eq $server_found "false") }} # Fallback entry server 127.0.0.1 down; - {{ end }} + {{ end }} } {{ end }} @@ -263,13 +263,13 @@ server { {{ if eq $nPaths 0 }} # {{ $host }} - {{ template "upstream-definition" (dict "Upstream" $upstream_name "Containers" $containers "Networks" $CurrentContainer.Networks "Debug" $debug_all) }} + {{ template "upstream" (dict "Upstream" $upstream_name "Containers" $containers "Networks" $CurrentContainer.Networks "Debug" $debug_all) }} {{ else }} {{ range $path, $containers := $paths }} {{ $sum := sha1 $path }} {{ $upstream := printf "%s-%s" $upstream_name $sum }} # {{ $host }}{{ $path }} - {{ template "upstream-definition" (dict "Upstream" $upstream "Containers" $containers "Networks" $CurrentContainer.Networks "Debug" $debug_all) }} + {{ template "upstream" (dict "Upstream" $upstream "Containers" $containers "Networks" $CurrentContainer.Networks "Debug" $debug_all) }} {{ end }} {{ end }} @@ -388,7 +388,7 @@ server { {{/* Get the NETWORK_ACCESS defined by containers w/ the same vhost, falling back to "external" */}} {{ $network_tag := or (first (groupByKeys $containers "Env.NETWORK_ACCESS")) "external" }} - {{ template "location" (dict "Path" "/" "Proto" $proto "Upstream" $upstream_name "Host" $host "Vhostroot" $vhost_root "Dest" "" "NetworkTag" $network_tag) }} + {{ template "location" (dict "Path" "/" "Proto" $proto "Upstream" $upstream_name "Host" $host "VhostRoot" $vhost_root "Dest" "" "NetworkTag" $network_tag) }} {{ else }} {{ range $path, $container := $paths }} {{/* Get the VIRTUAL_PROTO defined by containers w/ the same vhost-vpath, falling back to "http" */}} @@ -399,7 +399,7 @@ server { {{ $sum := sha1 $path }} {{ $upstream := printf "%s-%s" $upstream_name $sum }} {{ $dest := (or (first (groupByKeys $container "Env.VIRTUAL_DEST")) "") }} - {{ template "location" (dict "Path" $path "Proto" $proto "Upstream" $upstream "Host" $host "Vhostroot" $vhost_root "Dest" $dest "NetworkTag" $network_tag) }} + {{ template "location" (dict "Path" $path "Proto" $proto "Upstream" $upstream "Host" $host "VhostRoot" $vhost_root "Dest" $dest "NetworkTag" $network_tag) }} {{ end }} {{ if (not (contains $paths "/")) }} location / { @@ -436,7 +436,7 @@ server { {{/* Get the NETWORK_ACCESS defined by containers w/ the same vhost, falling back to "external" */}} {{ $network_tag := or (first (groupByKeys $containers "Env.NETWORK_ACCESS")) "external" }} - {{ template "location" (dict "Path" "/" "Proto" $proto "Upstream" $upstream_name "Host" $host "Vhostroot" $vhost_root "Dest" "" "NetworkTag" $network_tag) }} + {{ template "location" (dict "Path" "/" "Proto" $proto "Upstream" $upstream_name "Host" $host "VhostRoot" $vhost_root "Dest" "" "NetworkTag" $network_tag) }} {{ else }} {{ range $path, $container := $paths }} {{/* Get the VIRTUAL_PROTO defined by containers w/ the same vhost-vpath, falling back to "http" */}} @@ -447,7 +447,7 @@ server { {{ $sum := sha1 $path }} {{ $upstream := printf "%s-%s" $upstream_name $sum }} {{ $dest := (or (first (groupByKeys $container "Env.VIRTUAL_DEST")) "") }} - {{ template "location" (dict "Path" $path "Proto" $proto "Upstream" $upstream "Host" $host "Vhostroot" $vhost_root "Dest" $dest "NetworkTag" $network_tag) }} + {{ template "location" (dict "Path" $path "Proto" $proto "Upstream" $upstream "Host" $host "VhostRoot" $vhost_root "Dest" $dest "NetworkTag" $network_tag) }} {{ end }} {{ if (not (contains $paths "/")) }} location / { From 0185a2971c6538afa72021acefd9bfdc1552034d Mon Sep 17 00:00:00 2001 From: Nicolas Duchon Date: Thu, 24 Feb 2022 15:21:14 +0100 Subject: [PATCH 23/23] tests: fix virtual path tests for new dhparam --- test/test_internal/test_internal-per-vhost.yml | 1 - test/test_internal/test_internal-per-vpath.yml | 1 - test/test_ssl/test_virtual_path.yml | 1 - test/test_virtual-path/test_custom_conf.yml | 1 - test/test_virtual-path/test_forwarding.yml | 1 - test/test_virtual-path/test_location_precedence.yml | 1 - test/test_virtual-path/test_virtual_paths.yml | 2 -- 7 files changed, 8 deletions(-) diff --git a/test/test_internal/test_internal-per-vhost.yml b/test/test_internal/test_internal-per-vhost.yml index a935e53..5c732ee 100644 --- a/test/test_internal/test_internal-per-vhost.yml +++ b/test/test_internal/test_internal-per-vhost.yml @@ -19,6 +19,5 @@ sut: image: nginxproxy/nginx-proxy:test volumes: - /var/run/docker.sock:/tmp/docker.sock:ro - - ../lib/ssl/dhparam.pem:/etc/nginx/dhparam/dhparam.pem:ro - ./network_internal.conf:/etc/nginx/network_internal.conf:ro diff --git a/test/test_internal/test_internal-per-vpath.yml b/test/test_internal/test_internal-per-vpath.yml index 1ea470f..f5bac55 100644 --- a/test/test_internal/test_internal-per-vpath.yml +++ b/test/test_internal/test_internal-per-vpath.yml @@ -23,6 +23,5 @@ sut: image: nginxproxy/nginx-proxy:test volumes: - /var/run/docker.sock:/tmp/docker.sock:ro - - ../lib/ssl/dhparam.pem:/etc/nginx/dhparam/dhparam.pem:ro - ./network_internal.conf:/etc/nginx/network_internal.conf:ro diff --git a/test/test_ssl/test_virtual_path.yml b/test/test_ssl/test_virtual_path.yml index 07175ac..2260321 100644 --- a/test/test_ssl/test_virtual_path.yml +++ b/test/test_ssl/test_virtual_path.yml @@ -22,6 +22,5 @@ sut: image: nginxproxy/nginx-proxy:test volumes: - /var/run/docker.sock:/tmp/docker.sock:ro - - ../lib/ssl/dhparam.pem:/etc/nginx/dhparam/dhparam.pem:ro - ./certs:/etc/nginx/certs:ro diff --git a/test/test_virtual-path/test_custom_conf.yml b/test/test_virtual-path/test_custom_conf.yml index abd9d0c..40ab512 100644 --- a/test/test_virtual-path/test_custom_conf.yml +++ b/test/test_virtual-path/test_custom_conf.yml @@ -42,7 +42,6 @@ sut: DEFAULT_ROOT: 418 volumes: - /var/run/docker.sock:/tmp/docker.sock:ro - - ../lib/ssl/dhparam.pem:/etc/nginx/dhparam/dhparam.pem:ro - ./foo.conf:/etc/nginx/vhost.d/foo.nginx-proxy.test:ro - ./bar.conf:/etc/nginx/vhost.d/nginx-proxy.test_918d687a929083edd0c7224ee2293e0e7c062ab4_location:ro - ./alternate.conf:/etc/nginx/vhost.d/nginx-proxy.test_7fb22b74bbdf907425dbbad18e4462565cada230_location:ro diff --git a/test/test_virtual-path/test_forwarding.yml b/test/test_virtual-path/test_forwarding.yml index 78662d8..ee87e8d 100644 --- a/test/test_virtual-path/test_forwarding.yml +++ b/test/test_virtual-path/test_forwarding.yml @@ -12,7 +12,6 @@ sut: image: nginxproxy/nginx-proxy:test volumes: - /var/run/docker.sock:/tmp/docker.sock:ro - - ../lib/ssl/dhparam.pem:/etc/nginx/dhparam/dhparam.pem:ro - ./certs:/etc/nginx/certs:ro environment: - DEFAULT_ROOT=301 http://$$host/web1$$request_uri diff --git a/test/test_virtual-path/test_location_precedence.yml b/test/test_virtual-path/test_location_precedence.yml index be93b58..be3248c 100644 --- a/test/test_virtual-path/test_location_precedence.yml +++ b/test/test_virtual-path/test_location_precedence.yml @@ -32,7 +32,6 @@ sut: image: nginxproxy/nginx-proxy:test volumes: - /var/run/docker.sock:/tmp/docker.sock:ro - - ../lib/ssl/dhparam.pem:/etc/nginx/dhparam/dhparam.pem:ro - ./default.conf:/etc/nginx/vhost.d/default_location:ro - ./host.conf:/etc/nginx/vhost.d/bar.nginx-proxy.test_location:ro - ./path.conf:/etc/nginx/vhost.d/bar.nginx-proxy.test_99f2db0ed8aa95dbb5b87fca79c7eff2ff6bb8bd_location:ro diff --git a/test/test_virtual-path/test_virtual_paths.yml b/test/test_virtual-path/test_virtual_paths.yml index b335c3f..9f6a54f 100644 --- a/test/test_virtual-path/test_virtual_paths.yml +++ b/test/test_virtual-path/test_virtual_paths.yml @@ -40,5 +40,3 @@ sut: image: nginxproxy/nginx-proxy:test volumes: - /var/run/docker.sock:/tmp/docker.sock:ro - - ../lib/ssl/dhparam.pem:/etc/nginx/dhparam/dhparam.pem:ro -