├── test
├── test_dockergen
│ ├── .gitignore
│ ├── test_dockergen_v2.yml
│ ├── test_dockergen_v3.yml
│ ├── test_dockergen_v2.py
│ └── test_dockergen_v3.py
├── test_virtual-path
│ ├── foo.conf
│ ├── bar.conf
│ ├── host.conf
│ ├── path.conf
│ ├── default.conf
│ ├── alternate.conf
│ ├── test_forwarding.yml
│ ├── test_forwarding.py
│ ├── test_virtual_paths.yml
│ ├── test_location_precedence.yml
│ ├── test_location_precedence.py
│ ├── test_custom_conf.yml
│ ├── test_custom_conf.py
│ └── test_virtual_paths.py
├── stress_tests
│ ├── test_deleted_cert
│ │ ├── tmp_certs
│ │ │ └── .gitignore
│ │ ├── README.md
│ │ ├── docker-compose.yml
│ │ ├── certs
│ │ │ ├── web.nginx-proxy.key
│ │ │ └── web.nginx-proxy.crt
│ │ └── test_restart_while_missing_cert.py
│ ├── README.md
│ └── test_unreachable_network
│ │ ├── docker-compose.yml
│ │ ├── test_unreachable_net.py
│ │ └── README.md
├── test_custom
│ ├── my_custom_proxy_settings.conf
│ ├── my_custom_proxy_settings_bar.conf
│ ├── test_defaults.yml
│ ├── test_per-vhost.yml
│ ├── test_proxy-wide.yml
│ ├── test_location-per-vhost.yml
│ ├── test_defaults-location.yml
│ ├── test_per-vhost.py
│ ├── test_defaults.py
│ ├── test_proxy-wide.py
│ ├── test_location-per-vhost.py
│ └── test_defaults-location.py
├── test_events.yml
├── requirements
│ ├── python-requirements.txt
│ ├── Dockerfile-nginx-proxy-tester
│ ├── web
│ │ ├── Dockerfile
│ │ ├── entrypoint.sh
│ │ └── webserver.py
│ └── README.md
├── pytest.ini
├── test_default-host.py
├── test_multiple-ports
│ ├── test_VIRTUAL_PORT.py
│ ├── test_default-80.py
│ ├── test_single-port-not-80.py
│ ├── test_single-port-not-80.yml
│ ├── test_default-80.yml
│ ├── test_VIRTUAL_PORT.yml
│ ├── test_VIRTUAL_PORT-single-different-from-single-port.yml
│ └── test_VIRTUAL_PORT-single-different-from-single-port.py
├── test_upstream-name
│ ├── test_predictable-name.py
│ ├── test_predictable-name.yml
│ ├── test_sha1-name.yml
│ └── test_sha1-name.py
├── test_server-down
│ ├── test_no-server-down.yml
│ ├── test_server-down.yml
│ ├── test_server-down.py
│ ├── test_no-server-down.py
│ ├── test_load-balancing.py
│ └── test_load-balancing.yml
├── test_multiple-hosts.yml
├── test_internal
│ ├── network_internal.conf
│ ├── test_internal-per-vhost.yml
│ ├── test_internal-per-vhost.py
│ ├── test_internal-per-vpath.py
│ └── test_internal-per-vpath.yml
├── test_http_port.yml
├── test_ssl
│ ├── wildcard_cert_and_nohttps
│ │ ├── README.md
│ │ ├── docker-compose.yml
│ │ ├── test_wildcard_cert_nohttps.py
│ │ └── certs
│ │ │ ├── default.key
│ │ │ ├── web.nginx-proxy.tld.key
│ │ │ ├── default.crt
│ │ │ └── web.nginx-proxy.tld.crt
│ ├── test_nohttps.yml
│ ├── test_wildcard.yml
│ ├── test_nohttp.yml
│ ├── test_noredirect.yml
│ ├── test_https_port.yml
│ ├── certs
│ │ ├── web2.nginx-proxy.tld.dhparam.pem
│ │ ├── nginx-proxy.tld.key
│ │ ├── web2.nginx-proxy.tld.key
│ │ ├── web3.nginx-proxy.tld.key
│ │ ├── nginx-proxy.tld.crt
│ │ ├── web2.nginx-proxy.tld.crt
│ │ └── web3.nginx-proxy.tld.crt
│ ├── test_nohttps.py
│ ├── test_virtual_path.yml
│ ├── test_nohttp.py
│ ├── test_https_port.py
│ ├── test_virtual_path.py
│ ├── test_noredirect.py
│ ├── test_hsts.yml
│ ├── test_wildcard.py
│ ├── test_hsts.py
│ ├── test_dhparam.yml
│ └── test_dhparam.py
├── test_composev2.yml
├── test_http_port.py
├── test_composev2.py
├── test_default-host.yml
├── test_DOCKER_HOST_unix_socket.yml
├── test_headers
│ ├── test_http.yml
│ ├── test_https.yml
│ ├── certs
│ │ ├── web.nginx-proxy.tld.key
│ │ ├── web-server-tokens-off.nginx-proxy.tld.key
│ │ ├── web.nginx-proxy.tld.crt
│ │ └── web-server-tokens-off.nginx-proxy.tld.crt
│ ├── test_http.py
│ └── test_https.py
├── test_debug
│ ├── test_server-debug-flag.py
│ ├── test_server-debug-flag.yml
│ ├── test_proxy-debug-flag.yml
│ └── test_proxy-debug-flag.py
├── test_multiple-networks.py
├── test_raw-ip-vhost.py
├── test_DOCKER_HOST_unix_socket.py
├── test_multiple-hosts.py
├── test_multiple-networks.yml
├── test_nominal.yml
├── test_ipv6.yml
├── test_wildcard_host.yml
├── test_nominal.py
├── test_raw-ip-vhost.yml
├── test_ipv6.py
├── test_wildcard_host.py
├── certs
│ ├── ca-root.crt
│ ├── ca-root.key
│ ├── README.md
│ └── create_server_certificate.sh
├── pytest.sh
├── test_events.py
└── README.md
├── .gitignore
├── app
├── Procfile
├── dhparam
│ ├── ffdhe2048.pem
│ ├── ffdhe3072.pem
│ └── ffdhe4096.pem
└── docker-entrypoint.sh
├── .dockerignore
├── network_internal.conf
├── docker-compose.yml
├── .github
├── dependabot.yml
├── workflows
│ ├── test.yml
│ └── dockerhub.yml
└── ISSUE_TEMPLATE.md
├── Makefile
├── docker-compose-separate-containers.yml
├── LICENSE
├── Dockerfile.alpine
└── Dockerfile
/test/test_dockergen/.gitignore:
--------------------------------------------------------------------------------
1 | nginx.tmpl
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | **/__pycache__/
2 | **/.cache/
3 | .idea/
4 |
--------------------------------------------------------------------------------
/test/test_virtual-path/foo.conf:
--------------------------------------------------------------------------------
1 | add_header X-test f00;
--------------------------------------------------------------------------------
/test/test_virtual-path/bar.conf:
--------------------------------------------------------------------------------
1 | add_header X-test bar;
2 |
--------------------------------------------------------------------------------
/test/test_virtual-path/host.conf:
--------------------------------------------------------------------------------
1 | add_header X-test-host true;
2 |
--------------------------------------------------------------------------------
/test/test_virtual-path/path.conf:
--------------------------------------------------------------------------------
1 | add_header X-test-path true;
2 |
--------------------------------------------------------------------------------
/test/stress_tests/test_deleted_cert/tmp_certs/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
--------------------------------------------------------------------------------
/test/test_custom/my_custom_proxy_settings.conf:
--------------------------------------------------------------------------------
1 | add_header X-test f00;
--------------------------------------------------------------------------------
/test/test_virtual-path/default.conf:
--------------------------------------------------------------------------------
1 | add_header X-test-default true;
2 |
--------------------------------------------------------------------------------
/test/test_virtual-path/alternate.conf:
--------------------------------------------------------------------------------
1 | rewrite ^/(web3|alt)/(.*) /$2 break;
2 |
--------------------------------------------------------------------------------
/test/test_custom/my_custom_proxy_settings_bar.conf:
--------------------------------------------------------------------------------
1 | add_header X-test bar;
2 |
--------------------------------------------------------------------------------
/test/stress_tests/README.md:
--------------------------------------------------------------------------------
1 | This directory contains tests that showcase scenarios known to break the expected behavior of nginx-proxy.
--------------------------------------------------------------------------------
/app/Procfile:
--------------------------------------------------------------------------------
1 | dockergen: docker-gen -watch -notify "nginx -s reload" /app/nginx.tmpl /etc/nginx/conf.d/default.conf
2 | nginx: nginx
3 |
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | .git
2 | .github
3 | test
4 | .dockerignore
5 | .gitignore
6 | *.yml
7 | Dockerfile*
8 | Makefile
9 | README.md
10 |
--------------------------------------------------------------------------------
/test/test_events.yml:
--------------------------------------------------------------------------------
1 | nginxproxy:
2 | image: nginxproxy/nginx-proxy:test
3 | volumes:
4 | - /var/run/docker.sock:/tmp/docker.sock:ro
5 |
--------------------------------------------------------------------------------
/test/requirements/python-requirements.txt:
--------------------------------------------------------------------------------
1 | backoff==1.11.1
2 | docker-compose==1.29.2
3 | docker==5.0.3
4 | pytest==7.1.2
5 | requests==2.27.1
6 |
--------------------------------------------------------------------------------
/test/pytest.ini:
--------------------------------------------------------------------------------
1 | [pytest]
2 | # disable the creation of the `.cache` folders
3 | addopts = -p no:cacheprovider --ignore=requirements --ignore=certs -r s -v
4 | markers =
5 | incremental: mark a test as incremental.
--------------------------------------------------------------------------------
/network_internal.conf:
--------------------------------------------------------------------------------
1 | # Only allow traffic from internal clients
2 | allow 127.0.0.0/8;
3 | allow 10.0.0.0/8;
4 | allow 192.168.0.0/16;
5 | allow 172.16.0.0/12;
6 | allow fc00::/7; # IPv6 local address range
7 | deny all;
8 |
--------------------------------------------------------------------------------
/test/test_default-host.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 |
4 | def test_fallback_on_default(docker_compose, nginxproxy):
5 | r = nginxproxy.get("http://unknown.nginx-proxy.tld/port")
6 | assert r.status_code == 200
7 | assert r.text == "answer from port 81\n"
--------------------------------------------------------------------------------
/test/requirements/Dockerfile-nginx-proxy-tester:
--------------------------------------------------------------------------------
1 | FROM python:3.9
2 |
3 | ENV PYTEST_RUNNING_IN_CONTAINER=1
4 |
5 | COPY python-requirements.txt /requirements.txt
6 | RUN pip install -r /requirements.txt
7 |
8 | WORKDIR /test
9 | ENTRYPOINT ["pytest"]
10 |
--------------------------------------------------------------------------------
/test/requirements/web/Dockerfile:
--------------------------------------------------------------------------------
1 | # Docker Image running one (or multiple) webservers listening on all given ports from WEB_PORTS environment variable
2 |
3 | FROM python:3
4 | COPY ./webserver.py /
5 | COPY ./entrypoint.sh /
6 | WORKDIR /opt
7 | ENTRYPOINT ["/bin/bash", "/entrypoint.sh"]
8 |
9 |
--------------------------------------------------------------------------------
/test/test_multiple-ports/test_VIRTUAL_PORT.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 |
4 | def test_answer_is_served_from_chosen_port(docker_compose, nginxproxy):
5 | r = nginxproxy.get("http://web.nginx-proxy.tld/port")
6 | assert r.status_code == 200
7 | assert "answer from port 90\n" in r.text
8 |
--------------------------------------------------------------------------------
/test/test_multiple-ports/test_default-80.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 |
4 | def test_answer_is_served_from_port_80_by_default(docker_compose, nginxproxy):
5 | r = nginxproxy.get("http://web.nginx-proxy.tld/port")
6 | assert r.status_code == 200
7 | assert "answer from port 80\n" in r.text
8 |
--------------------------------------------------------------------------------
/test/test_upstream-name/test_predictable-name.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | import re
3 |
4 |
5 | def test_predictable_upstream_is_present_in_nginx_generated_conf(docker_compose, nginxproxy):
6 | conf = nginxproxy.get_conf().decode('ASCII')
7 | assert re.search(r"upstream web\.nginx-proxy\.tld \{", conf)
8 |
--------------------------------------------------------------------------------
/test/test_multiple-ports/test_single-port-not-80.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 |
4 | def test_answer_is_served_from_exposed_port_even_if_not_80(docker_compose, nginxproxy):
5 | r = nginxproxy.get("http://web.nginx-proxy.tld/port")
6 | assert r.status_code == 200
7 | assert "answer from port 81\n" in r.text
8 |
--------------------------------------------------------------------------------
/test/test_server-down/test_no-server-down.yml:
--------------------------------------------------------------------------------
1 | web:
2 | image: web
3 | expose:
4 | - "81"
5 | environment:
6 | WEB_PORTS: 81
7 | VIRTUAL_HOST: web.nginx-proxy.tld
8 |
9 | sut:
10 | image: nginxproxy/nginx-proxy:test
11 | volumes:
12 | - /var/run/docker.sock:/tmp/docker.sock:ro
13 |
--------------------------------------------------------------------------------
/test/test_multiple-hosts.yml:
--------------------------------------------------------------------------------
1 | web:
2 | image: web
3 | expose:
4 | - "81"
5 | environment:
6 | WEB_PORTS: 81
7 | VIRTUAL_HOST: webA.nginx-proxy.tld,webB.nginx-proxy.tld
8 |
9 |
10 | sut:
11 | image: nginxproxy/nginx-proxy:test
12 | volumes:
13 | - /var/run/docker.sock:/tmp/docker.sock:ro
14 |
--------------------------------------------------------------------------------
/test/test_internal/network_internal.conf:
--------------------------------------------------------------------------------
1 | # Only allow traffic from internal clients
2 | allow 127.0.0.0/8;
3 | allow 10.0.0.0/8;
4 | allow 192.168.0.0/16;
5 | allow 172.16.0.0/12;
6 | allow fc00::/7; # IPv6 local address range
7 | deny all;
8 |
9 | # Dummy header for testing
10 | add_header X-network internal;
11 |
12 |
--------------------------------------------------------------------------------
/test/test_server-down/test_server-down.yml:
--------------------------------------------------------------------------------
1 | web:
2 | image: web
3 | expose:
4 | - "81"
5 | environment:
6 | WEB_PORTS: 81
7 | VIRTUAL_HOST: web.nginx-proxy.tld
8 | net: "none"
9 |
10 | sut:
11 | image: nginxproxy/nginx-proxy:test
12 | volumes:
13 | - /var/run/docker.sock:/tmp/docker.sock:ro
14 |
--------------------------------------------------------------------------------
/test/test_http_port.yml:
--------------------------------------------------------------------------------
1 | web1:
2 | image: web
3 | expose:
4 | - "81"
5 | environment:
6 | WEB_PORTS: "81"
7 | VIRTUAL_HOST: "*.nginx-proxy.tld"
8 |
9 | sut:
10 | image: nginxproxy/nginx-proxy:test
11 | volumes:
12 | - /var/run/docker.sock:/tmp/docker.sock:ro
13 | environment:
14 | HTTP_PORT: 8080
--------------------------------------------------------------------------------
/test/test_multiple-ports/test_single-port-not-80.yml:
--------------------------------------------------------------------------------
1 | web:
2 | image: web
3 | expose:
4 | - "81"
5 | environment:
6 | WEB_PORTS: "81"
7 | VIRTUAL_HOST: "web.nginx-proxy.tld"
8 |
9 |
10 | sut:
11 | image: nginxproxy/nginx-proxy:test
12 | volumes:
13 | - /var/run/docker.sock:/tmp/docker.sock:ro
14 |
--------------------------------------------------------------------------------
/test/test_ssl/wildcard_cert_and_nohttps/README.md:
--------------------------------------------------------------------------------
1 | In this scenario, we have a wildcard certificate for `*.web.nginx-proxy.tld` and 3 web containers:
2 | - 1.web.nginx-proxy.tld
3 | - 2.web.nginx-proxy.tld
4 | - 3.web.nginx-proxy.tld
5 |
6 | We want web containers 1 and 2 to support SSL, but 3 should not (using `HTTPS_METHOD=nohttps`)
--------------------------------------------------------------------------------
/test/test_multiple-ports/test_default-80.yml:
--------------------------------------------------------------------------------
1 | web:
2 | image: web
3 | expose:
4 | - "80"
5 | - "81"
6 | environment:
7 | WEB_PORTS: "80 81"
8 | VIRTUAL_HOST: "web.nginx-proxy.tld"
9 |
10 | sut:
11 | image: nginxproxy/nginx-proxy:test
12 | volumes:
13 | - /var/run/docker.sock:/tmp/docker.sock:ro
14 |
--------------------------------------------------------------------------------
/test/test_server-down/test_server-down.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | def test_web_has_server_down(docker_compose, nginxproxy):
4 | conf = nginxproxy.get_conf().decode('ASCII')
5 | r = nginxproxy.get("http://web.nginx-proxy.tld/port")
6 | assert r.status_code in [502, 503]
7 | assert conf.count("server 127.0.0.1 down;") == 1
8 |
--------------------------------------------------------------------------------
/test/test_ssl/test_nohttps.yml:
--------------------------------------------------------------------------------
1 | web:
2 | image: web
3 | expose:
4 | - "83"
5 | environment:
6 | WEB_PORTS: "83"
7 | VIRTUAL_HOST: "web.nginx-proxy.tld"
8 | HTTPS_METHOD: nohttps
9 |
10 |
11 | sut:
12 | image: nginxproxy/nginx-proxy:test
13 | volumes:
14 | - /var/run/docker.sock:/tmp/docker.sock:ro
15 |
--------------------------------------------------------------------------------
/test/test_ssl/test_wildcard.yml:
--------------------------------------------------------------------------------
1 | web1:
2 | image: web
3 | expose:
4 | - "81"
5 | environment:
6 | WEB_PORTS: "81"
7 | VIRTUAL_HOST: "*.nginx-proxy.tld"
8 |
9 | sut:
10 | image: nginxproxy/nginx-proxy:test
11 | volumes:
12 | - /var/run/docker.sock:/tmp/docker.sock:ro
13 | - ./certs:/etc/nginx/certs:ro
14 |
--------------------------------------------------------------------------------
/test/stress_tests/test_deleted_cert/README.md:
--------------------------------------------------------------------------------
1 | Test the behavior of nginx-proxy when restarted after deleting a certificate file is was using.
2 |
3 | 1. nginx-proxy is created with a virtual host having a certificate
4 | 1. while nginx-proxy is running, the certificate file is deleted
5 | 1. nginx-proxy is then restarted (without removing the container)
6 |
--------------------------------------------------------------------------------
/test/test_composev2.yml:
--------------------------------------------------------------------------------
1 | version: '2'
2 | services:
3 | nginx-proxy:
4 | image: nginxproxy/nginx-proxy:test
5 | volumes:
6 | - /var/run/docker.sock:/tmp/docker.sock:ro
7 |
8 | web:
9 | image: web
10 | expose:
11 | - "81"
12 | environment:
13 | WEB_PORTS: 81
14 | VIRTUAL_HOST: web.nginx-proxy.local
15 |
--------------------------------------------------------------------------------
/test/test_http_port.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 |
4 | @pytest.mark.parametrize("subdomain", ["foo", "bar"])
5 | def test_web1_http_custom_port(docker_compose, nginxproxy, subdomain):
6 | r = nginxproxy.get("http://%s.nginx-proxy.tld:8080/port" % subdomain, allow_redirects=False)
7 | assert r.status_code == 200
8 | assert "answer from port 81\n" in r.text
--------------------------------------------------------------------------------
/test/test_multiple-ports/test_VIRTUAL_PORT.yml:
--------------------------------------------------------------------------------
1 | web:
2 | image: web
3 | expose:
4 | - "80"
5 | - "90"
6 | environment:
7 | WEB_PORTS: "80 90"
8 | VIRTUAL_HOST: "web.nginx-proxy.tld"
9 | VIRTUAL_PORT: 90
10 |
11 | sut:
12 | image: nginxproxy/nginx-proxy:test
13 | volumes:
14 | - /var/run/docker.sock:/tmp/docker.sock:ro
15 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '2'
2 | services:
3 | nginx-proxy:
4 | image: nginxproxy/nginx-proxy
5 | container_name: nginx-proxy
6 | ports:
7 | - "80:80"
8 | volumes:
9 | - /var/run/docker.sock:/tmp/docker.sock:ro
10 |
11 | whoami:
12 | image: jwilder/whoami
13 | environment:
14 | - VIRTUAL_HOST=whoami.local
15 |
--------------------------------------------------------------------------------
/test/requirements/web/entrypoint.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -u
3 |
4 | trap '[ ${#PIDS[@]} -gt 0 ] && kill -TERM ${PIDS[@]}' TERM
5 | declare -a PIDS
6 |
7 | for port in $WEB_PORTS; do
8 | echo starting a web server listening on port $port;
9 | /webserver.py $port &
10 | PIDS+=($!)
11 | done
12 |
13 | wait ${PIDS[@]}
14 | trap - TERM
15 | wait ${PIDS[@]}
16 |
--------------------------------------------------------------------------------
/test/test_ssl/test_nohttp.yml:
--------------------------------------------------------------------------------
1 | web2:
2 | image: web
3 | expose:
4 | - "82"
5 | environment:
6 | WEB_PORTS: "82"
7 | VIRTUAL_HOST: "web2.nginx-proxy.tld"
8 | HTTPS_METHOD: nohttp
9 |
10 |
11 | sut:
12 | image: nginxproxy/nginx-proxy:test
13 | volumes:
14 | - /var/run/docker.sock:/tmp/docker.sock:ro
15 | - ./certs:/etc/nginx/certs:ro
16 |
--------------------------------------------------------------------------------
/test/test_upstream-name/test_predictable-name.yml:
--------------------------------------------------------------------------------
1 | version: '2'
2 |
3 | services:
4 | web:
5 | image: web
6 | expose:
7 | - "80"
8 | environment:
9 | WEB_PORTS: 80
10 | VIRTUAL_HOST: web.nginx-proxy.tld
11 |
12 | sut:
13 | image: nginxproxy/nginx-proxy:test
14 | volumes:
15 | - /var/run/docker.sock:/tmp/docker.sock:ro
16 |
--------------------------------------------------------------------------------
/test/test_multiple-ports/test_VIRTUAL_PORT-single-different-from-single-port.yml:
--------------------------------------------------------------------------------
1 | web:
2 | image: web
3 | expose:
4 | - "81"
5 | environment:
6 | WEB_PORTS: "81"
7 | VIRTUAL_HOST: "web.nginx-proxy.tld"
8 | VIRTUAL_PORT: "90"
9 |
10 |
11 | sut:
12 | image: nginxproxy/nginx-proxy:test
13 | volumes:
14 | - /var/run/docker.sock:/tmp/docker.sock:ro
15 |
--------------------------------------------------------------------------------
/test/test_server-down/test_no-server-down.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | def test_web_has_no_server_down(docker_compose, nginxproxy):
4 | conf = nginxproxy.get_conf().decode('ASCII')
5 | r = nginxproxy.get("http://web.nginx-proxy.tld/port")
6 | assert r.status_code == 200
7 | assert r.text == "answer from port 81\n"
8 | assert conf.count("server 127.0.0.1 down;") == 0
9 |
--------------------------------------------------------------------------------
/test/test_ssl/test_noredirect.yml:
--------------------------------------------------------------------------------
1 | web3:
2 | image: web
3 | expose:
4 | - "83"
5 | environment:
6 | WEB_PORTS: "83"
7 | VIRTUAL_HOST: "web3.nginx-proxy.tld"
8 | HTTPS_METHOD: noredirect
9 |
10 |
11 | sut:
12 | image: nginxproxy/nginx-proxy:test
13 | volumes:
14 | - /var/run/docker.sock:/tmp/docker.sock:ro
15 | - ./certs:/etc/nginx/certs:ro
16 |
--------------------------------------------------------------------------------
/test/test_ssl/test_https_port.yml:
--------------------------------------------------------------------------------
1 | web1:
2 | image: web
3 | expose:
4 | - "81"
5 | environment:
6 | WEB_PORTS: "81"
7 | VIRTUAL_HOST: "*.nginx-proxy.tld"
8 |
9 | sut:
10 | image: nginxproxy/nginx-proxy:test
11 | volumes:
12 | - /var/run/docker.sock:/tmp/docker.sock:ro
13 | - ./certs:/etc/nginx/certs:ro
14 | environment:
15 | HTTP_PORT: 8080
16 | HTTPS_PORT: 8443
--------------------------------------------------------------------------------
/test/test_composev2.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | def test_unknown_virtual_host(docker_compose, nginxproxy):
4 | r = nginxproxy.get("http://nginx-proxy/")
5 | assert r.status_code == 503
6 |
7 | def test_forwards_to_whoami(docker_compose, nginxproxy):
8 | r = nginxproxy.get("http://web.nginx-proxy.local/port")
9 | assert r.status_code == 200
10 | assert r.text == "answer from port 81\n"
11 |
--------------------------------------------------------------------------------
/test/test_multiple-ports/test_VIRTUAL_PORT-single-different-from-single-port.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | import re
3 |
4 |
5 | def test_answer_is_served_from_virtual_port_which_is_ureachable(docker_compose, nginxproxy):
6 | r = nginxproxy.get("http://web.nginx-proxy.tld/port")
7 | assert r.status_code == 502
8 | assert re.search(r"\n\s+server \d+\.\d+\.\d+\.\d+:90;\n", nginxproxy.get_conf().decode('ASCII'))
9 |
--------------------------------------------------------------------------------
/test/test_server-down/test_load-balancing.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | def test_web_has_no_server_down(docker_compose, nginxproxy):
4 | conf = nginxproxy.get_conf().decode('ASCII')
5 | r = nginxproxy.get("http://web.nginx-proxy.tld/port")
6 | assert r.status_code == 200
7 | assert (r.text == "answer from port 81\n") or (r.text == "answer from port 82\n")
8 | assert conf.count("server 127.0.0.1 down;") == 0
9 |
--------------------------------------------------------------------------------
/test/test_upstream-name/test_sha1-name.yml:
--------------------------------------------------------------------------------
1 | version: '2'
2 |
3 | services:
4 | web:
5 | image: web
6 | expose:
7 | - "80"
8 | environment:
9 | WEB_PORTS: 80
10 | VIRTUAL_HOST: web.nginx-proxy.tld
11 |
12 | sut:
13 | image: nginxproxy/nginx-proxy:test
14 | volumes:
15 | - /var/run/docker.sock:/tmp/docker.sock:ro
16 | environment:
17 | SHA1_UPSTREAM_NAME: "true"
18 |
--------------------------------------------------------------------------------
/test/stress_tests/test_deleted_cert/docker-compose.yml:
--------------------------------------------------------------------------------
1 | web:
2 | image: web
3 | expose:
4 | - "81"
5 | environment:
6 | WEB_PORTS: 81
7 | VIRTUAL_HOST: web.nginx-proxy
8 |
9 |
10 | reverseproxy:
11 | image: nginxproxy/nginx-proxy:test
12 | container_name: reverseproxy
13 | environment:
14 | DEBUG: "true"
15 | volumes:
16 | - /var/run/docker.sock:/tmp/docker.sock:ro
17 | - ./tmp_certs:/etc/nginx/certs:ro
--------------------------------------------------------------------------------
/test/test_default-host.yml:
--------------------------------------------------------------------------------
1 | # GIVEN a webserver with VIRTUAL_HOST set to web1.tld
2 | web1:
3 | image: web
4 | expose:
5 | - "81"
6 | environment:
7 | WEB_PORTS: 81
8 | VIRTUAL_HOST: web1.tld
9 |
10 |
11 | # WHEN nginx-proxy runs with DEFAULT_HOST set to web1.tld
12 | sut:
13 | image: nginxproxy/nginx-proxy:test
14 | volumes:
15 | - /var/run/docker.sock:/tmp/docker.sock:ro
16 | environment:
17 | DEFAULT_HOST: web1.tld
18 |
--------------------------------------------------------------------------------
/app/dhparam/ffdhe2048.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN DH PARAMETERS-----
2 | MIIBCAKCAQEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz
3 | +8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a
4 | 87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7
5 | YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi
6 | 7MA0BM0oNC9hkXL+nOmFg/+OTxIy7vKBg8P+OxtMb61zO7X8vC7CIAXFjvGDfRaD
7 | ssbzSibBsu/6iGtCOGEoXJf//////////wIBAg==
8 | -----END DH PARAMETERS-----
--------------------------------------------------------------------------------
/test/test_virtual-path/test_forwarding.yml:
--------------------------------------------------------------------------------
1 | web1:
2 | image: web
3 | expose:
4 | - "81"
5 | environment:
6 | WEB_PORTS: "81"
7 | VIRTUAL_HOST: "www.nginx-proxy.tld"
8 | VIRTUAL_PATH: "/web1/"
9 | VIRTUAL_DEST: "/"
10 |
11 | sut:
12 | image: nginxproxy/nginx-proxy:test
13 | volumes:
14 | - /var/run/docker.sock:/tmp/docker.sock:ro
15 | - ./certs:/etc/nginx/certs:ro
16 | environment:
17 | - DEFAULT_ROOT=301 http://$$host/web1$$request_uri
18 |
--------------------------------------------------------------------------------
/test/test_ssl/certs/web2.nginx-proxy.tld.dhparam.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN DH PARAMETERS-----
2 | MIIBCAKCAQEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz
3 | +8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a
4 | 87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7
5 | YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi
6 | 7MA0BM0oNC9hkXL+nOmFg/+OTxIy7vKBg8P+OxtMb61zO7X8vC7CIAXFjvGDfRaD
7 | ssbzSibBsu/6iGtCOGEoXJf//////////wIBAg==
8 | -----END DH PARAMETERS-----
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 |
4 | # Maintain dependencies for Docker
5 | - package-ecosystem: "docker"
6 | directory: "/"
7 | schedule:
8 | interval: "daily"
9 | labels:
10 | - "type/build"
11 | - "scope/dockerfile"
12 |
13 | # Maintain Python dependencies (test suite)
14 | - package-ecosystem: "pip"
15 | directory: "/test/requirements"
16 | schedule:
17 | interval: "daily"
18 | labels:
19 | - "type/ci"
20 |
--------------------------------------------------------------------------------
/test/test_DOCKER_HOST_unix_socket.yml:
--------------------------------------------------------------------------------
1 | web1:
2 | image: web
3 | expose:
4 | - "81"
5 | environment:
6 | WEB_PORTS: 81
7 | VIRTUAL_HOST: web1.nginx-proxy.tld
8 |
9 | web2:
10 | image: web
11 | expose:
12 | - "82"
13 | environment:
14 | WEB_PORTS: 82
15 | VIRTUAL_HOST: web2.nginx-proxy.tld
16 |
17 |
18 | sut:
19 | image: nginxproxy/nginx-proxy:test
20 | volumes:
21 | - /var/run/docker.sock:/f00.sock:ro
22 | environment:
23 | DOCKER_HOST: unix:///f00.sock
24 |
--------------------------------------------------------------------------------
/test/test_headers/test_http.yml:
--------------------------------------------------------------------------------
1 | web:
2 | image: web
3 | expose:
4 | - "80"
5 | environment:
6 | WEB_PORTS: 80
7 | VIRTUAL_HOST: web.nginx-proxy.tld
8 |
9 | web-server-tokens-off:
10 | image: web
11 | expose:
12 | - "80"
13 | environment:
14 | WEB_PORTS: 80
15 | VIRTUAL_HOST: web-server-tokens-off.nginx-proxy.tld
16 | SERVER_TOKENS: "off"
17 |
18 |
19 | sut:
20 | image: nginxproxy/nginx-proxy:test
21 | volumes:
22 | - /var/run/docker.sock:/tmp/docker.sock:ro
23 |
--------------------------------------------------------------------------------
/test/test_debug/test_server-debug-flag.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | import re
3 |
4 | def test_debug_info_is_present_in_nginx_generated_conf(docker_compose, nginxproxy):
5 | conf = nginxproxy.get_conf().decode('ASCII')
6 | assert re.search(r"# Exposed ports: \[\{\d+\.\d+\.\d+\.\d+\s+80\s+tcp \} \{\d+\.\d+\.\d+\.\d+\s+81\s+tcp \}\]", conf) or \
7 | re.search(r"# Exposed ports: \[\{\d+\.\d+\.\d+\.\d+\s+81\s+tcp \} \{\d+\.\d+\.\d+\.\d+\s+80\s+tcp \}\]", conf)
8 | assert conf.count("# Exposed ports: [{") == 1
9 |
--------------------------------------------------------------------------------
/test/test_ssl/test_nohttps.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | from requests import ConnectionError
3 |
4 | def test_http_is_forwarded(docker_compose, nginxproxy):
5 | r = nginxproxy.get("http://web.nginx-proxy.tld/port", allow_redirects=False)
6 | assert r.status_code == 200
7 | assert "answer from port 83\n" in r.text
8 |
9 |
10 | def test_https_is_disabled(docker_compose, nginxproxy):
11 | with pytest.raises(ConnectionError):
12 | nginxproxy.get("https://web.nginx-proxy.tld/", allow_redirects=False)
13 |
--------------------------------------------------------------------------------
/test/test_upstream-name/test_sha1-name.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | import re
3 |
4 |
5 | def test_sha1_upstream_is_present_in_nginx_generated_conf(docker_compose, nginxproxy):
6 | conf = nginxproxy.get_conf().decode('ASCII')
7 | assert re.search(r"upstream 3e837201a6255962094cd6d8f61e22b07d3cc8ed \{", conf)
8 |
9 | def test_sha1_upstream_forwards_correctly(docker_compose, nginxproxy):
10 | r = nginxproxy.get("http://web.nginx-proxy.tld/port")
11 | assert r.status_code == 200
12 | assert r.text == "answer from port 80\n"
13 |
--------------------------------------------------------------------------------
/test/test_internal/test_internal-per-vhost.yml:
--------------------------------------------------------------------------------
1 | web1:
2 | image: web
3 | expose:
4 | - "81"
5 | environment:
6 | WEB_PORTS: 81
7 | VIRTUAL_HOST: web1.nginx-proxy.local
8 | NETWORK_ACCESS: internal
9 |
10 | web2:
11 | image: web
12 | expose:
13 | - "82"
14 | environment:
15 | WEB_PORTS: 82
16 | VIRTUAL_HOST: web2.nginx-proxy.local
17 |
18 | sut:
19 | image: nginxproxy/nginx-proxy:test
20 | volumes:
21 | - /var/run/docker.sock:/tmp/docker.sock:ro
22 | - ./network_internal.conf:/etc/nginx/network_internal.conf:ro
23 |
24 |
--------------------------------------------------------------------------------
/test/test_debug/test_server-debug-flag.yml:
--------------------------------------------------------------------------------
1 | web1:
2 | image: web
3 | expose:
4 | - "80"
5 | - "81"
6 | environment:
7 | WEB_PORTS: "80 81"
8 | VIRTUAL_HOST: "web1.nginx-proxy.tld"
9 | VIRTUAL_PORT: "82"
10 | DEBUG: "true"
11 |
12 | web2:
13 | image: web
14 | expose:
15 | - "82"
16 | - "83"
17 | environment:
18 | WEB_PORTS: "82 83"
19 | VIRTUAL_HOST: "web2.nginx-proxy.tld"
20 | VIRTUAL_PORT: "82"
21 |
22 | sut:
23 | image: nginxproxy/nginx-proxy:test
24 | volumes:
25 | - /var/run/docker.sock:/tmp/docker.sock:ro
26 |
--------------------------------------------------------------------------------
/test/test_custom/test_defaults.yml:
--------------------------------------------------------------------------------
1 | version: '2'
2 | services:
3 | nginx-proxy:
4 | image: nginxproxy/nginx-proxy:test
5 | volumes:
6 | - /var/run/docker.sock:/tmp/docker.sock:ro
7 | - ./my_custom_proxy_settings.conf:/etc/nginx/proxy.conf:ro
8 |
9 | web1:
10 | image: web
11 | expose:
12 | - "81"
13 | environment:
14 | WEB_PORTS: 81
15 | VIRTUAL_HOST: web1.nginx-proxy.local
16 |
17 | web2:
18 | image: web
19 | expose:
20 | - "82"
21 | environment:
22 | WEB_PORTS: 82
23 | VIRTUAL_HOST: web2.nginx-proxy.local
24 |
--------------------------------------------------------------------------------
/test/test_debug/test_proxy-debug-flag.yml:
--------------------------------------------------------------------------------
1 | web1:
2 | image: web
3 | expose:
4 | - "80"
5 | - "81"
6 | environment:
7 | WEB_PORTS: "80 81"
8 | VIRTUAL_HOST: "web1.nginx-proxy.tld"
9 | VIRTUAL_PORT: "82"
10 |
11 | web2:
12 | image: web
13 | expose:
14 | - "82"
15 | - "83"
16 | environment:
17 | WEB_PORTS: "82 83"
18 | VIRTUAL_HOST: "web2.nginx-proxy.tld"
19 | VIRTUAL_PORT: "82"
20 |
21 | sut:
22 | image: nginxproxy/nginx-proxy:test
23 | volumes:
24 | - /var/run/docker.sock:/tmp/docker.sock:ro
25 | environment:
26 | DEBUG: "true"
27 |
--------------------------------------------------------------------------------
/test/test_custom/test_per-vhost.yml:
--------------------------------------------------------------------------------
1 | version: '2'
2 | services:
3 | nginx-proxy:
4 | image: nginxproxy/nginx-proxy:test
5 | volumes:
6 | - /var/run/docker.sock:/tmp/docker.sock:ro
7 | - ./my_custom_proxy_settings.conf:/etc/nginx/vhost.d/web1.nginx-proxy.local:ro
8 |
9 | web1:
10 | image: web
11 | expose:
12 | - "81"
13 | environment:
14 | WEB_PORTS: 81
15 | VIRTUAL_HOST: web1.nginx-proxy.local
16 |
17 | web2:
18 | image: web
19 | expose:
20 | - "82"
21 | environment:
22 | WEB_PORTS: 82
23 | VIRTUAL_HOST: web2.nginx-proxy.local
24 |
--------------------------------------------------------------------------------
/test/test_ssl/test_virtual_path.yml:
--------------------------------------------------------------------------------
1 | web1:
2 | image: web
3 | expose:
4 | - "81"
5 | environment:
6 | WEB_PORTS: "81"
7 | VIRTUAL_HOST: "www.nginx-proxy.tld"
8 | VIRTUAL_PATH: "/web1/"
9 | VIRTUAL_DEST: "/"
10 |
11 | web2:
12 | image: web
13 | expose:
14 | - "82"
15 | environment:
16 | WEB_PORTS: "82"
17 | VIRTUAL_HOST: "www.nginx-proxy.tld"
18 | VIRTUAL_PATH: "/web2/"
19 | VIRTUAL_DEST: "/"
20 |
21 | sut:
22 | image: nginxproxy/nginx-proxy:test
23 | volumes:
24 | - /var/run/docker.sock:/tmp/docker.sock:ro
25 | - ./certs:/etc/nginx/certs:ro
26 |
27 |
--------------------------------------------------------------------------------
/test/test_custom/test_proxy-wide.yml:
--------------------------------------------------------------------------------
1 | version: '2'
2 | services:
3 | nginx-proxy:
4 | image: nginxproxy/nginx-proxy:test
5 | volumes:
6 | - /var/run/docker.sock:/tmp/docker.sock:ro
7 | - ./my_custom_proxy_settings.conf:/etc/nginx/conf.d/my_custom_proxy_settings.conf:ro
8 |
9 | web1:
10 | image: web
11 | expose:
12 | - "81"
13 | environment:
14 | WEB_PORTS: 81
15 | VIRTUAL_HOST: web1.nginx-proxy.local
16 |
17 | web2:
18 | image: web
19 | expose:
20 | - "82"
21 | environment:
22 | WEB_PORTS: 82
23 | VIRTUAL_HOST: web2.nginx-proxy.local
24 |
--------------------------------------------------------------------------------
/test/test_internal/test_internal-per-vhost.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | def test_network_web1(docker_compose, nginxproxy):
4 | r = nginxproxy.get("http://web1.nginx-proxy.local/port")
5 | assert r.status_code == 200
6 | assert r.text == "answer from port 81\n"
7 | assert "X-network" in r.headers
8 | assert "internal" == r.headers["X-network"]
9 |
10 | def test_network_web2(docker_compose, nginxproxy):
11 | r = nginxproxy.get("http://web2.nginx-proxy.local/port")
12 | assert r.status_code == 200
13 | assert r.text == "answer from port 82\n"
14 | assert "X-network" not in r.headers
15 |
--------------------------------------------------------------------------------
/test/test_internal/test_internal-per-vpath.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | def test_network_web1(docker_compose, nginxproxy):
4 | r = nginxproxy.get("http://nginx-proxy.local/web1/port")
5 | assert r.status_code == 200
6 | assert r.text == "answer from port 81\n"
7 | assert "X-network" in r.headers
8 | assert "internal" == r.headers["X-network"]
9 |
10 | def test_network_web2(docker_compose, nginxproxy):
11 | r = nginxproxy.get("http://nginx-proxy.local/web2/port")
12 | assert r.status_code == 200
13 | assert r.text == "answer from port 82\n"
14 | assert "X-network" not in r.headers
15 |
--------------------------------------------------------------------------------
/test/test_server-down/test_load-balancing.yml:
--------------------------------------------------------------------------------
1 | web1:
2 | image: web
3 | expose:
4 | - "81"
5 | environment:
6 | WEB_PORTS: 81
7 | VIRTUAL_HOST: web.nginx-proxy.tld
8 |
9 | web2:
10 | image: web
11 | expose:
12 | - "82"
13 | environment:
14 | WEB_PORTS: 83
15 | VIRTUAL_HOST: web.nginx-proxy.tld
16 |
17 | web3:
18 | image: web
19 | expose:
20 | - "83"
21 | environment:
22 | WEB_PORTS: 83
23 | VIRTUAL_HOST: web.nginx-proxy.tld
24 | net: "none"
25 |
26 | sut:
27 | image: nginxproxy/nginx-proxy:test
28 | volumes:
29 | - /var/run/docker.sock:/tmp/docker.sock:ro
30 |
--------------------------------------------------------------------------------
/test/test_multiple-networks.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | def test_unknown_virtual_host(docker_compose, nginxproxy):
4 | r = nginxproxy.get("http://nginx-proxy/")
5 | assert r.status_code == 503
6 |
7 | def test_forwards_to_web1(docker_compose, nginxproxy):
8 | r = nginxproxy.get("http://web1.nginx-proxy.local/port")
9 | assert r.status_code == 200
10 | assert r.text == "answer from port 81\n"
11 |
12 | def test_forwards_to_web2(docker_compose, nginxproxy):
13 | r = nginxproxy.get("http://web2.nginx-proxy.local/port")
14 | assert r.status_code == 200
15 | assert r.text == "answer from port 82\n"
--------------------------------------------------------------------------------
/test/test_custom/test_location-per-vhost.yml:
--------------------------------------------------------------------------------
1 | version: '2'
2 | services:
3 | nginx-proxy:
4 | image: nginxproxy/nginx-proxy:test
5 | volumes:
6 | - /var/run/docker.sock:/tmp/docker.sock:ro
7 | - ./my_custom_proxy_settings.conf:/etc/nginx/vhost.d/web1.nginx-proxy.local_location:ro
8 |
9 | web1:
10 | image: web
11 | expose:
12 | - "81"
13 | environment:
14 | WEB_PORTS: 81
15 | VIRTUAL_HOST: web1.nginx-proxy.local
16 |
17 | web2:
18 | image: web
19 | expose:
20 | - "82"
21 | environment:
22 | WEB_PORTS: 82
23 | VIRTUAL_HOST: web2.nginx-proxy.local
24 |
--------------------------------------------------------------------------------
/test/test_raw-ip-vhost.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 |
4 | def test_raw_ipv4_vhost_forwards_to_web1(docker_compose, nginxproxy):
5 | r = nginxproxy.get("http://172.20.0.4")
6 | assert r.status_code == 200
7 | web1_container = docker_compose.containers.get("web1")
8 | assert r.text == f"I'm {web1_container.id[:12]}\n"
9 |
10 |
11 | def test_raw_ipv6_vhost_forwards_to_web2(docker_compose, nginxproxy):
12 | r = nginxproxy.get("http://[fd00::4]", ipv6=True)
13 | assert r.status_code == 200
14 | web2_container = docker_compose.containers.get("web2")
15 | assert r.text == f"I'm {web2_container.id[:12]}\n"
16 |
--------------------------------------------------------------------------------
/test/test_DOCKER_HOST_unix_socket.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | def test_unknown_virtual_host(docker_compose, nginxproxy):
4 | r = nginxproxy.get("http://nginx-proxy/port")
5 | assert r.status_code == 503
6 |
7 | def test_forwards_to_web1(docker_compose, nginxproxy):
8 | r = nginxproxy.get("http://web1.nginx-proxy.tld/port")
9 | assert r.status_code == 200
10 | assert r.text == "answer from port 81\n"
11 |
12 | def test_forwards_to_web2(docker_compose, nginxproxy):
13 | r = nginxproxy.get("http://web2.nginx-proxy.tld/port")
14 | assert r.status_code == 200
15 | assert r.text == "answer from port 82\n"
16 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | .SILENT :
2 | .PHONY : test-debian test-alpine test
3 |
4 |
5 | build-webserver:
6 | docker build -t web test/requirements/web
7 |
8 | build-nginx-proxy-test-debian:
9 | docker build --build-arg NGINX_PROXY_VERSION="test" -t nginxproxy/nginx-proxy:test .
10 |
11 | build-nginx-proxy-test-alpine:
12 | docker build --build-arg NGINX_PROXY_VERSION="test" -f Dockerfile.alpine -t nginxproxy/nginx-proxy:test .
13 |
14 | test-debian: build-webserver build-nginx-proxy-test-debian
15 | test/pytest.sh
16 |
17 | test-alpine: build-webserver build-nginx-proxy-test-alpine
18 | test/pytest.sh
19 |
20 | test: test-debian test-alpine
21 |
--------------------------------------------------------------------------------
/app/dhparam/ffdhe3072.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN DH PARAMETERS-----
2 | MIIBiAKCAYEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz
3 | +8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a
4 | 87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7
5 | YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi
6 | 7MA0BM0oNC9hkXL+nOmFg/+OTxIy7vKBg8P+OxtMb61zO7X8vC7CIAXFjvGDfRaD
7 | ssbzSibBsu/6iGtCOGEfz9zeNVs7ZRkDW7w09N75nAI4YbRvydbmyQd62R0mkff3
8 | 7lmMsPrBhtkcrv4TCYUTknC0EwyTvEN5RPT9RFLi103TZPLiHnH1S/9croKrnJ32
9 | nuhtK8UiNjoNq8Uhl5sN6todv5pC1cRITgq80Gv6U93vPBsg7j/VnXwl5B0rZsYu
10 | N///////////AgEC
11 | -----END DH PARAMETERS-----
--------------------------------------------------------------------------------
/test/test_multiple-hosts.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 |
4 | def test_unknown_virtual_host_is_503(docker_compose, nginxproxy):
5 | r = nginxproxy.get("http://unknown.nginx-proxy.tld/port")
6 | assert r.status_code == 503
7 |
8 | def test_webA_is_forwarded(docker_compose, nginxproxy):
9 | r = nginxproxy.get("http://webA.nginx-proxy.tld/port")
10 | assert r.status_code == 200
11 | assert r.text == "answer from port 81\n"
12 |
13 | def test_webB_is_forwarded(docker_compose, nginxproxy):
14 | r = nginxproxy.get("http://webB.nginx-proxy.tld/port")
15 | assert r.status_code == 200
16 | assert r.text == "answer from port 81\n"
17 |
--------------------------------------------------------------------------------
/test/test_internal/test_internal-per-vpath.yml:
--------------------------------------------------------------------------------
1 | web1:
2 | image: web
3 | expose:
4 | - "81"
5 | environment:
6 | WEB_PORTS: 81
7 | VIRTUAL_HOST: nginx-proxy.local
8 | VIRTUAL_PATH: /web1/
9 | VIRTUAL_DEST: /
10 | NETWORK_ACCESS: internal
11 |
12 | web2:
13 | image: web
14 | expose:
15 | - "82"
16 | environment:
17 | WEB_PORTS: 82
18 | VIRTUAL_HOST: nginx-proxy.local
19 | VIRTUAL_PATH: /web2/
20 | VIRTUAL_DEST: /
21 |
22 | sut:
23 | image: nginxproxy/nginx-proxy:test
24 | volumes:
25 | - /var/run/docker.sock:/tmp/docker.sock:ro
26 | - ./network_internal.conf:/etc/nginx/network_internal.conf:ro
27 |
28 |
--------------------------------------------------------------------------------
/docker-compose-separate-containers.yml:
--------------------------------------------------------------------------------
1 | version: '2'
2 | services:
3 | nginx:
4 | image: nginx
5 | container_name: nginx
6 | ports:
7 | - "80:80"
8 | volumes:
9 | - /etc/nginx/conf.d
10 |
11 | dockergen:
12 | image: nginxproxy/docker-gen
13 | command: -notify-sighup nginx -watch /etc/docker-gen/templates/nginx.tmpl
14 | /etc/nginx/conf.d/default.conf
15 | volumes_from:
16 | - nginx
17 | volumes:
18 | - /var/run/docker.sock:/tmp/docker.sock:ro
19 | - ./nginx.tmpl:/etc/docker-gen/templates/nginx.tmpl
20 |
21 | whoami:
22 | image: jwilder/whoami
23 | environment:
24 | - VIRTUAL_HOST=whoami.local
25 |
--------------------------------------------------------------------------------
/test/test_multiple-networks.yml:
--------------------------------------------------------------------------------
1 | version: '2'
2 |
3 | networks:
4 | net1: {}
5 | net2: {}
6 |
7 | services:
8 | nginx-proxy:
9 | image: nginxproxy/nginx-proxy:test
10 | volumes:
11 | - /var/run/docker.sock:/tmp/docker.sock:ro
12 | networks:
13 | - net1
14 | - net2
15 |
16 | web1:
17 | image: web
18 | expose:
19 | - "81"
20 | environment:
21 | WEB_PORTS: 81
22 | VIRTUAL_HOST: web1.nginx-proxy.local
23 | networks:
24 | - net1
25 |
26 | web2:
27 | image: web
28 | expose:
29 | - "82"
30 | environment:
31 | WEB_PORTS: 82
32 | VIRTUAL_HOST: web2.nginx-proxy.local
33 | networks:
34 | - net2
35 |
--------------------------------------------------------------------------------
/test/test_dockergen/test_dockergen_v2.yml:
--------------------------------------------------------------------------------
1 | version: '2'
2 |
3 | services:
4 | nginx:
5 | image: nginx
6 | container_name: nginx
7 | volumes:
8 | - /etc/nginx/conf.d
9 |
10 | dockergen:
11 | image: nginxproxy/docker-gen
12 | command: -notify-sighup nginx -watch /etc/docker-gen/templates/nginx.tmpl /etc/nginx/conf.d/default.conf
13 | volumes_from:
14 | - nginx
15 | volumes:
16 | - /var/run/docker.sock:/tmp/docker.sock:ro
17 | - ./nginx.tmpl:/etc/docker-gen/templates/nginx.tmpl
18 |
19 | web:
20 | image: web
21 | container_name: whoami
22 | expose:
23 | - "80"
24 | environment:
25 | WEB_PORTS: 80
26 | VIRTUAL_HOST: whoami.nginx.container.docker
27 |
--------------------------------------------------------------------------------
/test/stress_tests/test_unreachable_network/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "2"
2 |
3 | networks:
4 | netA:
5 | netB:
6 |
7 | services:
8 | reverseproxy:
9 | container_name: reverseproxy
10 | networks:
11 | - netA
12 | image: nginxproxy/nginx-proxy:test
13 | volumes:
14 | - /var/run/docker.sock:/tmp/docker.sock:ro
15 |
16 | webA:
17 | networks:
18 | - netA
19 | image: web
20 | expose:
21 | - 81
22 | environment:
23 | WEB_PORTS: 81
24 | VIRTUAL_HOST: webA.nginx-proxy
25 |
26 | webB:
27 | networks:
28 | - netB
29 | image: web
30 | expose:
31 | - 82
32 | environment:
33 | WEB_PORTS: 82
34 | VIRTUAL_HOST: webB.nginx-proxy
35 |
36 |
--------------------------------------------------------------------------------
/test/test_nominal.yml:
--------------------------------------------------------------------------------
1 | version: '2'
2 |
3 | networks:
4 | net1:
5 | enable_ipv6: true
6 | ipam:
7 | config:
8 | - subnet: fd00:1::/80
9 |
10 | services:
11 | web1:
12 | image: web
13 | expose:
14 | - "81"
15 | environment:
16 | WEB_PORTS: 81
17 | VIRTUAL_HOST: web1.nginx-proxy.tld
18 | networks:
19 | - net1
20 |
21 | web2:
22 | image: web
23 | expose:
24 | - "82"
25 | environment:
26 | WEB_PORTS: 82
27 | VIRTUAL_HOST: web2.nginx-proxy.tld
28 | networks:
29 | - net1
30 |
31 |
32 | sut:
33 | image: nginxproxy/nginx-proxy:test
34 | volumes:
35 | - /var/run/docker.sock:/tmp/docker.sock:ro
36 | networks:
37 | - net1
38 |
--------------------------------------------------------------------------------
/test/test_ssl/test_nohttp.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 |
4 | def test_web2_http_is_not_forwarded(docker_compose, nginxproxy):
5 | r = nginxproxy.get("http://web2.nginx-proxy.tld/", allow_redirects=False)
6 | assert r.status_code == 503
7 |
8 |
9 | def test_web2_https_is_forwarded(docker_compose, nginxproxy):
10 | r = nginxproxy.get("https://web2.nginx-proxy.tld/port", allow_redirects=False)
11 | assert r.status_code == 200
12 | assert "answer from port 82\n" in r.text
13 |
14 |
15 | def test_web2_HSTS_policy_is_active(docker_compose, nginxproxy):
16 | r = nginxproxy.get("https://web2.nginx-proxy.tld/port", allow_redirects=False)
17 | assert "answer from port 82\n" in r.text
18 | assert "Strict-Transport-Security" in r.headers
19 |
--------------------------------------------------------------------------------
/test/test_ssl/test_https_port.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | @pytest.mark.parametrize("subdomain", ["foo", "bar"])
4 | def test_web1_http_redirects_to_https(docker_compose, nginxproxy, subdomain):
5 | r = nginxproxy.get("http://%s.nginx-proxy.tld:8080/" % subdomain, allow_redirects=False)
6 | assert r.status_code == 301
7 | assert "Location" in r.headers
8 | assert "https://%s.nginx-proxy.tld:8443/" % subdomain == r.headers['Location']
9 |
10 | @pytest.mark.parametrize("subdomain", ["foo", "bar"])
11 | def test_web1_https_is_forwarded(docker_compose, nginxproxy, subdomain):
12 | r = nginxproxy.get("https://%s.nginx-proxy.tld:8443/port" % subdomain, allow_redirects=False)
13 | assert r.status_code == 200
14 | assert "answer from port 81\n" in r.text
--------------------------------------------------------------------------------
/test/test_dockergen/test_dockergen_v3.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 | services:
3 | nginx:
4 | image: nginx
5 | container_name: nginx
6 | volumes:
7 | - nginx_conf:/etc/nginx/conf.d
8 |
9 | dockergen:
10 | image: nginxproxy/docker-gen
11 | command: -notify-sighup nginx -watch /etc/docker-gen/templates/nginx.tmpl /etc/nginx/conf.d/default.conf
12 | volumes:
13 | - /var/run/docker.sock:/tmp/docker.sock:ro
14 | - ./nginx.tmpl:/etc/docker-gen/templates/nginx.tmpl
15 | - nginx_conf:/etc/nginx/conf.d
16 |
17 | web:
18 | image: web
19 | container_name: whoami
20 | expose:
21 | - "80"
22 | environment:
23 | WEB_PORTS: 80
24 | VIRTUAL_HOST: whoami.nginx.container.docker
25 |
26 | volumes:
27 | nginx_conf: {}
28 |
--------------------------------------------------------------------------------
/test/test_ssl/test_virtual_path.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | @pytest.mark.parametrize("path", ["web1", "web2"])
4 | def test_web1_http_redirects_to_https(docker_compose, nginxproxy, path):
5 | r = nginxproxy.get("http://www.nginx-proxy.tld/%s/port" % path, allow_redirects=False)
6 | assert r.status_code == 301
7 | assert "Location" in r.headers
8 | assert "https://www.nginx-proxy.tld/%s/port" % path == r.headers['Location']
9 |
10 | @pytest.mark.parametrize("path,port", [("web1", 81), ("web2", 82)])
11 | def test_web1_https_is_forwarded(docker_compose, nginxproxy, path, port):
12 | r = nginxproxy.get("https://www.nginx-proxy.tld/%s/port" % path, allow_redirects=False)
13 | assert r.status_code == 200
14 | assert "answer from port %d\n" % port in r.text
15 |
16 |
--------------------------------------------------------------------------------
/test/test_custom/test_defaults-location.yml:
--------------------------------------------------------------------------------
1 | nginx-proxy:
2 | image: nginxproxy/nginx-proxy:test
3 | volumes:
4 | - /var/run/docker.sock:/tmp/docker.sock:ro
5 | - ./my_custom_proxy_settings.conf:/etc/nginx/vhost.d/default_location:ro
6 | - ./my_custom_proxy_settings_bar.conf:/etc/nginx/vhost.d/web3.nginx-proxy.local_location:ro
7 |
8 | web1:
9 | image: web
10 | expose:
11 | - "81"
12 | environment:
13 | WEB_PORTS: 81
14 | VIRTUAL_HOST: web1.nginx-proxy.local
15 |
16 | web2:
17 | image: web
18 | expose:
19 | - "82"
20 | environment:
21 | WEB_PORTS: 82
22 | VIRTUAL_HOST: web2.nginx-proxy.local
23 |
24 | web3:
25 | image: web
26 | expose:
27 | - "83"
28 | environment:
29 | WEB_PORTS: 83
30 | VIRTUAL_HOST: web3.nginx-proxy.local
31 |
--------------------------------------------------------------------------------
/test/test_ipv6.yml:
--------------------------------------------------------------------------------
1 | version: '2'
2 |
3 | networks:
4 | net1:
5 | enable_ipv6: true
6 | ipam:
7 | config:
8 | - subnet: fd00:1::/80
9 |
10 | services:
11 | web1:
12 | image: web
13 | expose:
14 | - "81"
15 | environment:
16 | WEB_PORTS: 81
17 | VIRTUAL_HOST: web1.nginx-proxy.tld
18 | networks:
19 | - net1
20 |
21 | web2:
22 | image: web
23 | expose:
24 | - "82"
25 | environment:
26 | WEB_PORTS: 82
27 | VIRTUAL_HOST: web2.nginx-proxy.tld
28 | networks:
29 | - net1
30 |
31 |
32 | sut:
33 | image: nginxproxy/nginx-proxy:test
34 | volumes:
35 | - /var/run/docker.sock:/tmp/docker.sock:ro
36 | environment:
37 | ENABLE_IPV6: "true"
38 | networks:
39 | - net1
40 |
--------------------------------------------------------------------------------
/test/test_ssl/wildcard_cert_and_nohttps/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "3"
2 |
3 | services:
4 |
5 | proxy:
6 | image: nginxproxy/nginx-proxy:test
7 | volumes:
8 | - /var/run/docker.sock:/tmp/docker.sock:ro
9 | - ./certs:/etc/nginx/certs:ro
10 |
11 | web1:
12 | image: web
13 | expose:
14 | - "81"
15 | environment:
16 | WEB_PORTS: "81"
17 | VIRTUAL_HOST: "1.web.nginx-proxy.tld"
18 | web2:
19 | image: web
20 | expose:
21 | - "82"
22 | environment:
23 | WEB_PORTS: "82"
24 | VIRTUAL_HOST: "2.web.nginx-proxy.tld"
25 |
26 | web3_nohttps:
27 | image: web
28 | expose:
29 | - "83"
30 | environment:
31 | WEB_PORTS: "83"
32 | VIRTUAL_HOST: "3.web.nginx-proxy.tld"
33 | HTTPS_METHOD: nohttps
34 |
--------------------------------------------------------------------------------
/test/test_ssl/test_noredirect.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 |
4 | def test_web3_http_is_forwarded(docker_compose, nginxproxy):
5 | r = nginxproxy.get("http://web3.nginx-proxy.tld/port", allow_redirects=False)
6 | assert r.status_code == 200
7 | assert "answer from port 83\n" in r.text
8 |
9 |
10 | def test_web3_https_is_forwarded(docker_compose, nginxproxy):
11 | r = nginxproxy.get("https://web3.nginx-proxy.tld/port", allow_redirects=False)
12 | assert r.status_code == 200
13 | assert "answer from port 83\n" in r.text
14 |
15 |
16 | def test_web2_HSTS_policy_is_inactive(docker_compose, nginxproxy):
17 | r = nginxproxy.get("https://web3.nginx-proxy.tld/port", allow_redirects=False)
18 | assert "answer from port 83\n" in r.text
19 | assert "Strict-Transport-Security" not in r.headers
--------------------------------------------------------------------------------
/app/dhparam/ffdhe4096.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN DH PARAMETERS-----
2 | MIICCAKCAgEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz
3 | +8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a
4 | 87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7
5 | YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi
6 | 7MA0BM0oNC9hkXL+nOmFg/+OTxIy7vKBg8P+OxtMb61zO7X8vC7CIAXFjvGDfRaD
7 | ssbzSibBsu/6iGtCOGEfz9zeNVs7ZRkDW7w09N75nAI4YbRvydbmyQd62R0mkff3
8 | 7lmMsPrBhtkcrv4TCYUTknC0EwyTvEN5RPT9RFLi103TZPLiHnH1S/9croKrnJ32
9 | nuhtK8UiNjoNq8Uhl5sN6todv5pC1cRITgq80Gv6U93vPBsg7j/VnXwl5B0rZp4e
10 | 8W5vUsMWTfT7eTDp5OWIV7asfV9C1p9tGHdjzx1VA0AEh/VbpX4xzHpxNciG77Qx
11 | iu1qHgEtnmgyqQdgCpGBMMRtx3j5ca0AOAkpmaMzy4t6Gh25PXFAADwqTs6p+Y0K
12 | zAqCkc3OyX3Pjsm1Wn+IpGtNtahR9EGC4caKAH5eZV9q//////////8CAQI=
13 | -----END DH PARAMETERS-----
--------------------------------------------------------------------------------
/test/test_virtual-path/test_forwarding.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | def test_root_redirects_to_web1(docker_compose, nginxproxy):
4 | r = nginxproxy.get("http://www.nginx-proxy.tld/port", allow_redirects=False)
5 | assert r.status_code == 301
6 | assert "Location" in r.headers
7 | assert "http://www.nginx-proxy.tld/web1/port" == r.headers['Location']
8 |
9 | def test_direct_access(docker_compose, nginxproxy):
10 | r = nginxproxy.get("http://www.nginx-proxy.tld/web1/port", allow_redirects=False)
11 | assert r.status_code == 200
12 | assert "answer from port 81\n" in r.text
13 |
14 | def test_root_is_forwarded(docker_compose, nginxproxy):
15 | r = nginxproxy.get("http://www.nginx-proxy.tld/port", allow_redirects=True)
16 | assert r.status_code == 200
17 | assert "answer from port 81\n" in r.text
18 |
19 |
--------------------------------------------------------------------------------
/test/test_custom/test_per-vhost.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | def test_custom_conf_does_not_apply_to_unknown_vhost(docker_compose, nginxproxy):
4 | r = nginxproxy.get("http://nginx-proxy/")
5 | assert r.status_code == 503
6 | assert "X-test" not in r.headers
7 |
8 | def test_custom_conf_applies_to_web1(docker_compose, nginxproxy):
9 | r = nginxproxy.get("http://web1.nginx-proxy.local/port")
10 | assert r.status_code == 200
11 | assert r.text == "answer from port 81\n"
12 | assert "X-test" in r.headers
13 | assert "f00" == r.headers["X-test"]
14 |
15 | def test_custom_conf_does_not_apply_to_web2(docker_compose, nginxproxy):
16 | r = nginxproxy.get("http://web2.nginx-proxy.local/port")
17 | assert r.status_code == 200
18 | assert r.text == "answer from port 82\n"
19 | assert "X-test" not in r.headers
20 |
--------------------------------------------------------------------------------
/test/test_wildcard_host.yml:
--------------------------------------------------------------------------------
1 | web1:
2 | image: web
3 | expose:
4 | - "81"
5 | environment:
6 | WEB_PORTS: "81"
7 | VIRTUAL_HOST: "*.nginx-proxy.test"
8 |
9 | web2:
10 | image: web
11 | expose:
12 | - "82"
13 | environment:
14 | WEB_PORTS: "82"
15 | VIRTUAL_HOST: "test.nginx-proxy.*"
16 |
17 | web3:
18 | image: web
19 | expose:
20 | - "83"
21 | environment:
22 | WEB_PORTS: "83"
23 | VIRTUAL_HOST: ~^web3\..*\.nginx-proxy\.regexp
24 |
25 | web4:
26 | image: web
27 | expose:
28 | - "84"
29 | environment:
30 | WEB_PORTS: "84"
31 | VIRTUAL_HOST: ~^web4\..*\.nginx-proxy\.regexp$$ # we need to double the `$` because of docker-compose variable interpolation
32 |
33 |
34 | sut:
35 | image: nginxproxy/nginx-proxy:test
36 | volumes:
37 | - /var/run/docker.sock:/tmp/docker.sock:ro
38 |
--------------------------------------------------------------------------------
/test/test_custom/test_defaults.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | def test_custom_conf_does_not_apply_to_unknown_vhost(docker_compose, nginxproxy):
4 | r = nginxproxy.get("http://nginx-proxy/")
5 | assert r.status_code == 503
6 | assert "X-test" not in r.headers
7 |
8 | def test_custom_conf_applies_to_web1(docker_compose, nginxproxy):
9 | r = nginxproxy.get("http://web1.nginx-proxy.local/port")
10 | assert r.status_code == 200
11 | assert r.text == "answer from port 81\n"
12 | assert "X-test" in r.headers
13 | assert "f00" == r.headers["X-test"]
14 |
15 | def test_custom_conf_applies_to_web2(docker_compose, nginxproxy):
16 | r = nginxproxy.get("http://web2.nginx-proxy.local/port")
17 | assert r.status_code == 200
18 | assert r.text == "answer from port 82\n"
19 | assert "X-test" in r.headers
20 | assert "f00" == r.headers["X-test"]
21 |
--------------------------------------------------------------------------------
/test/test_debug/test_proxy-debug-flag.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | import re
3 |
4 | def test_debug_info_is_present_in_nginx_generated_conf(docker_compose, nginxproxy):
5 | conf = nginxproxy.get_conf().decode('ASCII')
6 | assert re.search(r"# Exposed ports: \[\{\d+\.\d+\.\d+\.\d+\s+80\s+tcp \} \{\d+\.\d+\.\d+\.\d+\s+81\s+tcp \}\]", conf) or \
7 | re.search(r"# Exposed ports: \[\{\d+\.\d+\.\d+\.\d+\s+81\s+tcp \} \{\d+\.\d+\.\d+\.\d+\s+80\s+tcp \}\]", conf)
8 | assert re.search(r"# Exposed ports: \[\{\d+\.\d+\.\d+\.\d+\s+82\s+tcp \} \{\d+\.\d+\.\d+\.\d+\s+83\s+tcp \}\]", conf) or \
9 | re.search(r"# Exposed ports: \[\{\d+\.\d+\.\d+\.\d+\s+83\s+tcp \} \{\d+\.\d+\.\d+\.\d+\s+82\s+tcp \}\]", conf)
10 | assert "# Default virtual port: 80" in conf
11 | assert "# VIRTUAL_PORT: 82" in conf
12 | assert conf.count("# /!\\ Virtual port not exposed") == 1
13 |
--------------------------------------------------------------------------------
/test/test_custom/test_proxy-wide.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | def test_custom_conf_does_not_apply_to_unknown_vhost(docker_compose, nginxproxy):
4 | r = nginxproxy.get("http://nginx-proxy/")
5 | assert r.status_code == 503
6 | assert "X-test" not in r.headers
7 |
8 | def test_custom_conf_applies_to_web1(docker_compose, nginxproxy):
9 | r = nginxproxy.get("http://web1.nginx-proxy.local/port")
10 | assert r.status_code == 200
11 | assert r.text == "answer from port 81\n"
12 | assert "X-test" in r.headers
13 | assert "f00" == r.headers["X-test"]
14 |
15 | def test_custom_conf_applies_to_web2(docker_compose, nginxproxy):
16 | r = nginxproxy.get("http://web2.nginx-proxy.local/port")
17 | assert r.status_code == 200
18 | assert r.text == "answer from port 82\n"
19 | assert "X-test" in r.headers
20 | assert "f00" == r.headers["X-test"]
21 |
--------------------------------------------------------------------------------
/test/test_virtual-path/test_virtual_paths.yml:
--------------------------------------------------------------------------------
1 |
2 | foo:
3 | image: web
4 | expose:
5 | - "42"
6 | environment:
7 | WEB_PORTS: "42"
8 | VIRTUAL_HOST: "foo.nginx-proxy.test"
9 |
10 | web1:
11 | image: web
12 | expose:
13 | - "81"
14 | environment:
15 | WEB_PORTS: "81"
16 | VIRTUAL_HOST: "nginx-proxy.test"
17 | VIRTUAL_PATH: "/web1/"
18 | VIRTUAL_DEST: "/"
19 |
20 | web2:
21 | image: web
22 | expose:
23 | - "82"
24 | environment:
25 | WEB_PORTS: "82"
26 | VIRTUAL_HOST: "nginx-proxy.test"
27 | VIRTUAL_PATH: "/web2/"
28 | VIRTUAL_DEST: "/"
29 |
30 | web3:
31 | image: web
32 | expose:
33 | - "83"
34 | environment:
35 | WEB_PORTS: "83"
36 | VIRTUAL_HOST: "nginx-proxy.test"
37 | VIRTUAL_PATH: "/"
38 |
39 | sut:
40 | image: nginxproxy/nginx-proxy:test
41 | volumes:
42 | - /var/run/docker.sock:/tmp/docker.sock:ro
43 |
--------------------------------------------------------------------------------
/test/test_ssl/test_hsts.yml:
--------------------------------------------------------------------------------
1 | web1:
2 | image: web
3 | expose:
4 | - "81"
5 | environment:
6 | WEB_PORTS: "81"
7 | VIRTUAL_HOST: "web1.nginx-proxy.tld"
8 |
9 | web2:
10 | image: web
11 | expose:
12 | - "81"
13 | environment:
14 | WEB_PORTS: "81"
15 | VIRTUAL_HOST: "web2.nginx-proxy.tld"
16 | HSTS: "off"
17 |
18 | web3:
19 | image: web
20 | expose:
21 | - "81"
22 | environment:
23 | WEB_PORTS: "81"
24 | VIRTUAL_HOST: "web3.nginx-proxy.tld"
25 | HSTS: "max-age=86400; includeSubDomains; preload"
26 |
27 | web4:
28 | image: web
29 | expose:
30 | - "81"
31 | environment:
32 | WEB_PORTS: "81"
33 | VIRTUAL_HOST: "web4.nginx-proxy.tld"
34 | HSTS: "off"
35 | HTTPS_METHOD: "noredirect"
36 |
37 | sut:
38 | image: nginxproxy/nginx-proxy:test
39 | volumes:
40 | - /var/run/docker.sock:/tmp/docker.sock:ro
41 | - ./certs:/etc/nginx/certs:ro
42 |
--------------------------------------------------------------------------------
/test/test_virtual-path/test_location_precedence.yml:
--------------------------------------------------------------------------------
1 | web1:
2 | image: web
3 | expose:
4 | - "81"
5 | environment:
6 | WEB_PORTS: "81"
7 | VIRTUAL_HOST: "foo.nginx-proxy.test"
8 | VIRTUAL_PATH: "/web1/"
9 | VIRTUAL_DEST: "/"
10 |
11 | web2:
12 | image: web
13 | expose:
14 | - "82"
15 | environment:
16 | WEB_PORTS: "82"
17 | VIRTUAL_HOST: "bar.nginx-proxy.test"
18 | VIRTUAL_PATH: "/web2/"
19 | VIRTUAL_DEST: "/"
20 |
21 | web3:
22 | image: web
23 | expose:
24 | - "83"
25 | environment:
26 | WEB_PORTS: "83"
27 | VIRTUAL_HOST: "bar.nginx-proxy.test"
28 | VIRTUAL_PATH: "/web3/"
29 | VIRTUAL_DEST: "/"
30 |
31 | sut:
32 | image: nginxproxy/nginx-proxy:test
33 | volumes:
34 | - /var/run/docker.sock:/tmp/docker.sock:ro
35 | - ./default.conf:/etc/nginx/vhost.d/default_location:ro
36 | - ./host.conf:/etc/nginx/vhost.d/bar.nginx-proxy.test_location:ro
37 | - ./path.conf:/etc/nginx/vhost.d/bar.nginx-proxy.test_99f2db0ed8aa95dbb5b87fca79c7eff2ff6bb8bd_location:ro
38 |
--------------------------------------------------------------------------------
/test/test_custom/test_location-per-vhost.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | def test_custom_conf_does_not_apply_to_unknown_vhost(docker_compose, nginxproxy):
4 | r = nginxproxy.get("http://nginx-proxy/")
5 | assert r.status_code == 503
6 | assert "X-test" not in r.headers
7 |
8 | def test_custom_conf_applies_to_web1(docker_compose, nginxproxy):
9 | r = nginxproxy.get("http://web1.nginx-proxy.local/port")
10 | assert r.status_code == 200
11 | assert r.text == "answer from port 81\n"
12 | assert "X-test" in r.headers
13 | assert "f00" == r.headers["X-test"]
14 |
15 | def test_custom_conf_does_not_apply_to_web2(docker_compose, nginxproxy):
16 | r = nginxproxy.get("http://web2.nginx-proxy.local/port")
17 | assert r.status_code == 200
18 | assert r.text == "answer from port 82\n"
19 | assert "X-test" not in r.headers
20 |
21 | def test_custom_block_is_present_in_nginx_generated_conf(docker_compose, nginxproxy):
22 | assert b"include /etc/nginx/vhost.d/web1.nginx-proxy.local_location;" in nginxproxy.get_conf()
--------------------------------------------------------------------------------
/test/test_nominal.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | from requests import ConnectionError
3 |
4 |
5 | def test_unknown_virtual_host(docker_compose, nginxproxy):
6 | r = nginxproxy.get("http://nginx-proxy/port")
7 | assert r.status_code == 503
8 |
9 |
10 | def test_forwards_to_web1(docker_compose, nginxproxy):
11 | r = nginxproxy.get("http://web1.nginx-proxy.tld/port")
12 | assert r.status_code == 200
13 | assert r.text == "answer from port 81\n"
14 |
15 |
16 | def test_forwards_to_web2(docker_compose, nginxproxy):
17 | r = nginxproxy.get("http://web2.nginx-proxy.tld/port")
18 | assert r.status_code == 200
19 | assert r.text == "answer from port 82\n"
20 |
21 |
22 | def test_ipv6_is_disabled_by_default(docker_compose, nginxproxy):
23 | with pytest.raises(ConnectionError):
24 | nginxproxy.get("http://nginx-proxy/port", ipv6=True)
25 |
26 |
27 | def test_container_version_is_displayed(docker_compose, nginxproxy):
28 | conf = nginxproxy.get_conf().decode('ASCII')
29 | assert "# nginx-proxy version : test" in conf
30 |
--------------------------------------------------------------------------------
/test/test_headers/test_https.yml:
--------------------------------------------------------------------------------
1 | web:
2 | image: web
3 | expose:
4 | - "80"
5 | environment:
6 | WEB_PORTS: 80
7 | VIRTUAL_HOST: web.nginx-proxy.tld
8 |
9 | web-server-tokens-off:
10 | image: web
11 | expose:
12 | - "80"
13 | environment:
14 | WEB_PORTS: 80
15 | VIRTUAL_HOST: web-server-tokens-off.nginx-proxy.tld
16 | SERVER_TOKENS: "off"
17 |
18 |
19 | sut:
20 | image: nginxproxy/nginx-proxy:test
21 | volumes:
22 | - /var/run/docker.sock:/tmp/docker.sock:ro
23 | - ./certs/web.nginx-proxy.tld.crt:/etc/nginx/certs/default.crt:ro
24 | - ./certs/web.nginx-proxy.tld.key:/etc/nginx/certs/default.key:ro
25 | - ./certs/web.nginx-proxy.tld.crt:/etc/nginx/certs/web.nginx-proxy.tld.crt:ro
26 | - ./certs/web.nginx-proxy.tld.key:/etc/nginx/certs/web.nginx-proxy.tld.key:ro
27 | - ./certs/web-server-tokens-off.nginx-proxy.tld.crt:/etc/nginx/certs/web-server-tokens-off.nginx-proxy.tld.crt:ro
28 | - ./certs/web-server-tokens-off.nginx-proxy.tld.key:/etc/nginx/certs/web-server-tokens-off.nginx-proxy.tld.key:ro
29 |
--------------------------------------------------------------------------------
/test/test_raw-ip-vhost.yml:
--------------------------------------------------------------------------------
1 | version: '2'
2 |
3 | networks:
4 | net1:
5 | enable_ipv6: true
6 | ipam:
7 | config:
8 | - subnet: 172.20.0.0/16
9 | - subnet: fd00::/80
10 |
11 | services:
12 | web1:
13 | container_name: web1
14 | image: web
15 | expose:
16 | - "81"
17 | environment:
18 | WEB_PORTS: 81
19 | VIRTUAL_HOST: "172.20.0.4"
20 | networks:
21 | net1:
22 | ipv4_address: 172.20.0.2
23 | ipv6_address: fd00::2
24 |
25 | web2:
26 | container_name: web2
27 | image: web
28 | expose:
29 | - "82"
30 | environment:
31 | WEB_PORTS: 82
32 | VIRTUAL_HOST: "[fd00::4]"
33 | networks:
34 | net1:
35 | ipv4_address: 172.20.0.3
36 | ipv6_address: fd00::3
37 |
38 | sut:
39 | image: nginxproxy/nginx-proxy:test
40 | environment:
41 | ENABLE_IPV6: "true"
42 | volumes:
43 | - /var/run/docker.sock:/tmp/docker.sock:ro
44 | networks:
45 | net1:
46 | ipv4_address: 172.20.0.4
47 | ipv6_address: fd00::4
48 |
--------------------------------------------------------------------------------
/test/test_ssl/test_wildcard.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 |
4 | @pytest.mark.parametrize("subdomain", ["foo", "bar"])
5 | def test_web1_http_redirects_to_https(docker_compose, nginxproxy, subdomain):
6 | r = nginxproxy.get(f"http://{subdomain}.nginx-proxy.tld/", allow_redirects=False)
7 | assert r.status_code == 301
8 | assert "Location" in r.headers
9 | assert f"https://{subdomain}.nginx-proxy.tld/" == r.headers['Location']
10 |
11 |
12 | @pytest.mark.parametrize("subdomain", ["foo", "bar"])
13 | def test_web1_https_is_forwarded(docker_compose, nginxproxy, subdomain):
14 | r = nginxproxy.get(f"https://{subdomain}.nginx-proxy.tld/port", allow_redirects=False)
15 | assert r.status_code == 200
16 | assert "answer from port 81\n" in r.text
17 |
18 |
19 | @pytest.mark.parametrize("subdomain", ["foo", "bar"])
20 | def test_web1_HSTS_policy_is_active(docker_compose, nginxproxy, subdomain):
21 | r = nginxproxy.get(f"https://{subdomain}.nginx-proxy.tld/port", allow_redirects=False)
22 | assert "answer from port 81\n" in r.text
23 | assert "Strict-Transport-Security" in r.headers
24 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: Tests
2 |
3 | on:
4 | workflow_dispatch:
5 | push:
6 | paths-ignore:
7 | - 'LICENSE'
8 | - '**.md'
9 | pull_request:
10 | paths-ignore:
11 | - 'LICENSE'
12 | - '**.md'
13 |
14 | jobs:
15 | unit:
16 | name: Unit Tests
17 | runs-on: ubuntu-latest
18 |
19 | strategy:
20 | matrix:
21 | base_docker_image: [alpine, debian]
22 |
23 | steps:
24 | - uses: actions/checkout@v2
25 |
26 | - name: Set up Python 3.9
27 | uses: actions/setup-python@v2
28 | with:
29 | python-version: 3.9
30 |
31 | - name: Install dependencies
32 | run: |
33 | python -m pip install --upgrade pip
34 | pip install -r python-requirements.txt
35 | working-directory: test/requirements
36 |
37 | - name: Build Docker web server image
38 | run: make build-webserver
39 |
40 | - name: Build Docker nginx proxy test image
41 | run: make build-nginx-proxy-test-${{ matrix.base_docker_image }}
42 |
43 | - name: Run tests
44 | run: pytest
45 | working-directory: test
46 |
--------------------------------------------------------------------------------
/test/test_virtual-path/test_location_precedence.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | def test_location_precedence_case1(docker_compose, nginxproxy):
4 | r = nginxproxy.get(f"http://foo.nginx-proxy.test/web1/port")
5 | assert r.status_code == 200
6 |
7 | assert "X-test-default" in r.headers
8 | assert "X-test-host" not in r.headers
9 | assert "X-test-path" not in r.headers
10 |
11 | assert r.headers["X-test-default"] == "true"
12 |
13 | def test_location_precedence_case2(docker_compose, nginxproxy):
14 | r = nginxproxy.get(f"http://bar.nginx-proxy.test/web2/port")
15 | assert r.status_code == 200
16 |
17 | assert "X-test-default" not in r.headers
18 | assert "X-test-host" in r.headers
19 | assert "X-test-path" not in r.headers
20 |
21 | assert r.headers["X-test-host"] == "true"
22 |
23 | def test_location_precedence_case3(docker_compose, nginxproxy):
24 | r = nginxproxy.get(f"http://bar.nginx-proxy.test/web3/port")
25 | assert r.status_code == 200
26 |
27 | assert "X-test-default" not in r.headers
28 | assert "X-test-host" not in r.headers
29 | assert "X-test-path" in r.headers
30 |
31 | assert r.headers["X-test-path"] == "true"
32 |
33 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014-2020 Jason Wilder
4 | Copyright (c) 2021-2022 Nicolas Duchon
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 |
--------------------------------------------------------------------------------
/test/test_custom/test_defaults-location.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | def test_custom_default_conf_does_not_apply_to_unknown_vhost(docker_compose, nginxproxy):
4 | r = nginxproxy.get("http://nginx-proxy/")
5 | assert r.status_code == 503
6 | assert "X-test" not in r.headers
7 |
8 | def test_custom_default_conf_applies_to_web1(docker_compose, nginxproxy):
9 | r = nginxproxy.get("http://web1.nginx-proxy.local/port")
10 | assert r.status_code == 200
11 | assert r.text == "answer from port 81\n"
12 | assert "X-test" in r.headers
13 | assert "f00" == r.headers["X-test"]
14 |
15 | def test_custom_default_conf_applies_to_web2(docker_compose, nginxproxy):
16 | r = nginxproxy.get("http://web2.nginx-proxy.local/port")
17 | assert r.status_code == 200
18 | assert r.text == "answer from port 82\n"
19 | assert "X-test" in r.headers
20 | assert "f00" == r.headers["X-test"]
21 |
22 |
23 | def test_custom_default_conf_is_overriden_for_web3(docker_compose, nginxproxy):
24 | r = nginxproxy.get("http://web3.nginx-proxy.local/port")
25 | assert r.status_code == 200
26 | assert r.text == "answer from port 83\n"
27 | assert "X-test" in r.headers
28 | assert "bar" == r.headers["X-test"]
29 |
--------------------------------------------------------------------------------
/test/test_virtual-path/test_custom_conf.yml:
--------------------------------------------------------------------------------
1 |
2 | foo:
3 | image: web
4 | expose:
5 | - "42"
6 | environment:
7 | WEB_PORTS: "42"
8 | VIRTUAL_HOST: "foo.nginx-proxy.test"
9 |
10 | web1:
11 | image: web
12 | expose:
13 | - "81"
14 | environment:
15 | WEB_PORTS: "81"
16 | VIRTUAL_HOST: "nginx-proxy.test"
17 | VIRTUAL_PATH: "/web1/"
18 | VIRTUAL_DEST: "/"
19 |
20 | web2:
21 | image: web
22 | expose:
23 | - "82"
24 | environment:
25 | WEB_PORTS: "82"
26 | VIRTUAL_HOST: "nginx-proxy.test"
27 | VIRTUAL_PATH: "/web2/"
28 | VIRTUAL_DEST: "/"
29 |
30 | web3:
31 | image: web
32 | expose:
33 | - "83"
34 | environment:
35 | WEB_PORTS: "83"
36 | VIRTUAL_HOST: "nginx-proxy.test"
37 | VIRTUAL_PATH: "~ ^/(web3|alt)/"
38 |
39 | sut:
40 | image: nginxproxy/nginx-proxy:test
41 | environment:
42 | DEFAULT_ROOT: 418
43 | volumes:
44 | - /var/run/docker.sock:/tmp/docker.sock:ro
45 | - ./foo.conf:/etc/nginx/vhost.d/foo.nginx-proxy.test:ro
46 | - ./bar.conf:/etc/nginx/vhost.d/nginx-proxy.test_918d687a929083edd0c7224ee2293e0e7c062ab4_location:ro
47 | - ./alternate.conf:/etc/nginx/vhost.d/nginx-proxy.test_7fb22b74bbdf907425dbbad18e4462565cada230_location:ro
48 |
49 |
--------------------------------------------------------------------------------
/test/test_ipv6.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 |
4 | def test_unknown_virtual_host_ipv4(docker_compose, nginxproxy):
5 | r = nginxproxy.get("http://nginx-proxy/port")
6 | assert r.status_code == 503
7 |
8 |
9 | def test_forwards_to_web1_ipv4(docker_compose, nginxproxy):
10 | r = nginxproxy.get("http://web1.nginx-proxy.tld/port")
11 | assert r.status_code == 200
12 | assert r.text == "answer from port 81\n"
13 |
14 |
15 | def test_forwards_to_web2_ipv4(docker_compose, nginxproxy):
16 | r = nginxproxy.get("http://web2.nginx-proxy.tld/port")
17 | assert r.status_code == 200
18 | assert r.text == "answer from port 82\n"
19 |
20 |
21 | def test_unknown_virtual_host_ipv6(docker_compose, nginxproxy):
22 | r = nginxproxy.get("http://nginx-proxy/port", ipv6=True)
23 | assert r.status_code == 503
24 |
25 |
26 | def test_forwards_to_web1_ipv6(docker_compose, nginxproxy):
27 | r = nginxproxy.get("http://web1.nginx-proxy.tld/port", ipv6=True)
28 | assert r.status_code == 200
29 | assert r.text == "answer from port 81\n"
30 |
31 |
32 | def test_forwards_to_web2_ipv6(docker_compose, nginxproxy):
33 | r = nginxproxy.get("http://web2.nginx-proxy.tld/port", ipv6=True)
34 | assert r.status_code == 200
35 | assert r.text == "answer from port 82\n"
36 |
--------------------------------------------------------------------------------
/test/test_wildcard_host.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 |
4 | @pytest.mark.parametrize("host,expected_port", [
5 | ("f00.nginx-proxy.test", 81),
6 | ("bar.nginx-proxy.test", 81),
7 | ("test.nginx-proxy.f00", 82),
8 | ("test.nginx-proxy.bar", 82),
9 | ("web3.123.nginx-proxy.regexp", 83),
10 | ("web3.ABC.nginx-proxy.regexp", 83),
11 | ("web3.123.ABC.nginx-proxy.regexp", 83),
12 | ("web3.123-ABC.nginx-proxy.regexp", 83),
13 | ("web3.whatever.nginx-proxy.regexp-to-infinity-and-beyond", 83),
14 | ("web4.123.nginx-proxy.regexp", 84),
15 | ("web4.ABC.nginx-proxy.regexp", 84),
16 | ("web4.123.ABC.nginx-proxy.regexp", 84),
17 | ("web4.123-ABC.nginx-proxy.regexp", 84),
18 | ("web4.whatever.nginx-proxy.regexp", 84),
19 | ])
20 | def test_wildcard_prefix(docker_compose, nginxproxy, host, expected_port):
21 | r = nginxproxy.get(f"http://{host}/port")
22 | assert r.status_code == 200
23 | assert r.text == f"answer from port {expected_port}\n"
24 |
25 |
26 | @pytest.mark.parametrize("host", [
27 | "unexpected.nginx-proxy.tld",
28 | "web4.whatever.nginx-proxy.regexp-to-infinity-and-beyond"
29 | ])
30 | def test_non_matching_host_is_503(docker_compose, nginxproxy, host):
31 | r = nginxproxy.get(f"http://{host}/port")
32 | assert r.status_code == 503, r.text
33 |
--------------------------------------------------------------------------------
/test/certs/ca-root.crt:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIDZDCCAkygAwIBAgIJAJmW6Ju6iJNNMA0GCSqGSIb3DQEBCwUAMD8xHzAdBgNV
3 | BAoMFm5naW54LXByb3h5IHRlc3Qgc3VpdGUxHDAaBgNVBAMME3d3dy5uZ2lueC1w
4 | cm94eS50bGQwHhcNMTcwMTEwMDAwODUxWhcNMjcwMTA4MDAwODUxWjA/MR8wHQYD
5 | VQQKDBZuZ2lueC1wcm94eSB0ZXN0IHN1aXRlMRwwGgYDVQQDDBN3d3cubmdpbngt
6 | cHJveHkudGxkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAndjE3OPr
7 | 48hIOQigk/HejrowsQDLNfkkc6vej0J983rJitGTgBfxqq27fOPfqhE5bi1M5JDk
8 | KkrOrSitxCJLgpq+4Ls9/RXg8skZFHRAQbNwuKBehaDkPdamJ0i3dv6e4kZy41oI
9 | RqxQ/MKdminC4LShFZvPoKeh9ae7w1MgB2/4E68LO66bYiHlLNL7ENViSHhLyCmt
10 | qIE7kdV9jgn2NuVJ37m6/6SNQ3GBiIjEW+ooRQ3HEVKBCismcwq80+BD5VS/yW18
11 | KqX8m4sBM+IgZbcOqrV+APMbGvd8iNJgQSSQC/r0Wscgt7UeggVYKDazjDSPvLUE
12 | FUN5wEmydkP2AQIDAQABo2MwYTAdBgNVHQ4EFgQUJL59pHomt+8dUNxv8HgrYjKf
13 | OA8wHwYDVR0jBBgwFoAUJL59pHomt+8dUNxv8HgrYjKfOA8wDwYDVR0TAQH/BAUw
14 | AwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQADggEBABALxY96YqsZ
15 | CL2crzY0FIGhfjLE7P3mtUGklUpFu7xyI6mGUyL1nJYSnHB5IEV6QLhVVUE/CojI
16 | crXorQWBDkx26AgCt/eIOdvPYC0JDeXiIhH6sld3yH7JGwGqJkfXaUUfUkuwMae7
17 | mMIEG9e6vfSh/YNTRxs0KBjBcXHHl5K+Dz4h9r14OqnQFqVFZaR6T6td44tDDNhn
18 | beW8iIfCWRqDsnvIcJzLa2QR4onmJSw5DaSeFFaKefhdHEzEBZntLfyFbjRYHT/O
19 | +BRdewhg6rSDkGLcL8n/ZnRLOa+xmegjQ/Op94OmWO3TfXOITJAtkaO2YVZoyek8
20 | T6ckVovq4zU=
21 | -----END CERTIFICATE-----
22 |
--------------------------------------------------------------------------------
/test/stress_tests/test_unreachable_network/test_unreachable_net.py:
--------------------------------------------------------------------------------
1 | from time import sleep
2 |
3 | import pytest
4 | import requests
5 |
6 |
7 | def test_default_nginx_welcome_page_should_not_be_served(docker_compose, nginxproxy):
8 | r = nginxproxy.get("http://whatever.nginx-proxy/", allow_redirects=False)
9 | assert "
Welcome to nginx!" not in r.text
10 |
11 |
12 | def test_unknown_virtual_host_is_503(docker_compose, nginxproxy):
13 | r = nginxproxy.get("http://unknown.nginx-proxy/", allow_redirects=False)
14 | assert r.status_code == 503
15 |
16 |
17 | def test_http_web_a_is_forwarded(docker_compose, nginxproxy):
18 | r = nginxproxy.get("http://webA.nginx-proxy/port", allow_redirects=False)
19 | assert r.status_code == 200
20 | assert "answer from port 81\n" == r.text
21 |
22 |
23 | def test_http_web_b_gets_an_error(docker_compose, nginxproxy):
24 | r = nginxproxy.get("http://webB.nginx-proxy/", allow_redirects=False)
25 | assert "Welcome to nginx!" not in r.text
26 | with pytest.raises(requests.exceptions.HTTPError):
27 | r.raise_for_status()
28 |
29 |
30 | def test_reverseproxy_survive_restart(docker_compose):
31 | docker_compose.containers.get("reverseproxy").restart()
32 | sleep(2) # give time for the container to initialize
33 | assert docker_compose.containers.get("reverseproxy").status == "running"
34 |
--------------------------------------------------------------------------------
/test/pytest.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | ###############################################################################
3 | # #
4 | # This script is meant to run the test suite from a Docker container. #
5 | # #
6 | # This is usefull when you want to run the test suite from Mac or #
7 | # Docker Toolbox. #
8 | # #
9 | ###############################################################################
10 |
11 | # Returns the absolute directory path to this script
12 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
13 | ARGS=("$@")
14 |
15 | # check requirements
16 | echo "> Building nginx-proxy-tester image..."
17 | docker build -t nginx-proxy-tester -f "${DIR}/requirements/Dockerfile-nginx-proxy-tester" "${DIR}/requirements"
18 |
19 | # run the nginx-proxy-tester container setting the correct value for the working dir in order for
20 | # docker-compose to work properly when run from within that container.
21 | exec docker run --rm -it --name "nginx-proxy-pytest" \
22 | --volume "/var/run/docker.sock:/var/run/docker.sock" \
23 | --volume "${DIR}:${DIR}" \
24 | --workdir "${DIR}" \
25 | nginx-proxy-tester "${ARGS[@]}"
--------------------------------------------------------------------------------
/test/requirements/web/webserver.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import os, sys, re
4 | import http.server
5 | import socketserver
6 |
7 | class Handler(http.server.SimpleHTTPRequestHandler):
8 | def do_GET(self):
9 |
10 | response_body = ""
11 | response_code = 200
12 |
13 | if self.path == "/headers":
14 | response_body += self.headers.as_string()
15 | elif self.path == "/port":
16 | response_body += f"answer from port {PORT}\n"
17 | elif re.match("/status/(\d+)", self.path):
18 | result = re.match("/status/(\d+)", self.path)
19 | response_code = int(result.group(1))
20 | response_body += f"answer with response code {response_code}\n"
21 | elif self.path == "/":
22 | response_body += f"I'm {os.environ['HOSTNAME']}\n"
23 | else:
24 | response_body += "No route for this path!\n"
25 | response_code = 404
26 |
27 | self.send_response(response_code)
28 | self.send_header("Content-Type", "text/plain")
29 | self.end_headers()
30 |
31 | if (len(response_body)):
32 | self.wfile.write(response_body.encode())
33 |
34 | if __name__ == '__main__':
35 | PORT = int(sys.argv[1])
36 | socketserver.TCPServer.allow_reuse_address = True
37 | httpd = socketserver.TCPServer(('0.0.0.0', PORT), Handler)
38 | httpd.serve_forever()
39 |
--------------------------------------------------------------------------------
/test/test_virtual-path/test_custom_conf.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | def test_default_root_response(docker_compose, nginxproxy):
4 | r = nginxproxy.get("http://nginx-proxy.test/")
5 | assert r.status_code == 418
6 |
7 | @pytest.mark.parametrize("stub,header", [
8 | ("nginx-proxy.test/web1", "bar"),
9 | ("foo.nginx-proxy.test", "f00"),
10 | ])
11 | def test_custom_applies(docker_compose, nginxproxy, stub, header):
12 | r = nginxproxy.get(f"http://{stub}/port")
13 | assert r.status_code == 200
14 | assert "X-test" in r.headers
15 | assert header == r.headers["X-test"]
16 |
17 | @pytest.mark.parametrize("stub,code", [
18 | ("nginx-proxy.test/foo", 418),
19 | ("nginx-proxy.test/web2", 200),
20 | ("nginx-proxy.test/web3", 200),
21 | ("bar.nginx-proxy.test", 503),
22 | ])
23 | def test_custom_does_not_apply(docker_compose, nginxproxy, stub, code):
24 | r = nginxproxy.get(f"http://{stub}/port")
25 | assert r.status_code == code
26 | assert "X-test" not in r.headers
27 |
28 | @pytest.mark.parametrize("stub,port", [
29 | ("nginx-proxy.test/web1", 81),
30 | ("nginx-proxy.test/web2", 82),
31 | ("nginx-proxy.test/web3", 83),
32 | ("nginx-proxy.test/alt", 83),
33 | ])
34 | def test_alternate(docker_compose, nginxproxy, stub, port):
35 | r = nginxproxy.get(f"http://{stub}/port")
36 | assert r.status_code == 200
37 | assert r.text == f"answer from port {port}\n"
38 |
39 |
--------------------------------------------------------------------------------
/test/test_dockergen/test_dockergen_v2.py:
--------------------------------------------------------------------------------
1 | import os
2 | import docker
3 | import logging
4 | import pytest
5 |
6 |
7 | @pytest.fixture(scope="module")
8 | def nginx_tmpl():
9 | """
10 | pytest fixture which extracts the the nginx config template from
11 | the nginxproxy/nginx-proxy:test image
12 | """
13 | script_dir = os.path.dirname(__file__)
14 | logging.info("extracting nginx.tmpl from nginxproxy/nginx-proxy:test")
15 | docker_client = docker.from_env()
16 | print(
17 | docker_client.containers.run(
18 | image="nginxproxy/nginx-proxy:test",
19 | remove=True,
20 | volumes=["{current_dir}:{current_dir}".format(current_dir=script_dir)],
21 | entrypoint="sh",
22 | command='-xc "cp /app/nginx.tmpl {current_dir} && chmod 777 {current_dir}/nginx.tmpl"'.format(
23 | current_dir=script_dir
24 | ),
25 | stderr=True,
26 | )
27 | )
28 | yield
29 | logging.info("removing nginx.tmpl")
30 | os.remove(os.path.join(script_dir, "nginx.tmpl"))
31 |
32 |
33 | def test_unknown_virtual_host_is_503(nginx_tmpl, docker_compose, nginxproxy):
34 | r = nginxproxy.get("http://unknown.nginx.container.docker/")
35 | assert r.status_code == 503
36 |
37 |
38 | def test_forwards_to_whoami(nginx_tmpl, docker_compose, nginxproxy):
39 | r = nginxproxy.get("http://whoami.nginx.container.docker/")
40 | assert r.status_code == 200
41 | whoami_container = docker_compose.containers.get("whoami")
42 | assert r.text == f"I'm {whoami_container.id[:12]}\n"
43 |
--------------------------------------------------------------------------------
/test/test_ssl/wildcard_cert_and_nohttps/test_wildcard_cert_nohttps.py:
--------------------------------------------------------------------------------
1 | import pytest
2 | from ssl import CertificateError
3 | from requests.exceptions import SSLError
4 |
5 |
6 | @pytest.mark.parametrize("subdomain,should_redirect_to_https", [
7 | (1, True),
8 | (2, True),
9 | (3, False),
10 | ])
11 | def test_http_redirects_to_https(docker_compose, nginxproxy, subdomain, should_redirect_to_https):
12 | r = nginxproxy.get(f"http://{subdomain}.web.nginx-proxy.tld/port")
13 | if should_redirect_to_https:
14 | assert len(r.history) > 0
15 | assert r.history[0].is_redirect
16 | assert r.history[0].headers.get("Location") == f"https://{subdomain}.web.nginx-proxy.tld/port"
17 | assert f"answer from port 8{subdomain}\n" == r.text
18 |
19 |
20 | @pytest.mark.parametrize("subdomain", [1, 2])
21 | def test_https_get_served(docker_compose, nginxproxy, subdomain):
22 | r = nginxproxy.get(f"https://{subdomain}.web.nginx-proxy.tld/port", allow_redirects=False)
23 | assert r.status_code == 200
24 | assert f"answer from port 8{subdomain}\n" == r.text
25 |
26 | @pytest.mark.filterwarnings('ignore::urllib3.exceptions.InsecureRequestWarning')
27 | def test_web3_https_is_500_and_SSL_validation_fails(docker_compose, nginxproxy):
28 | with pytest.raises( (CertificateError, SSLError) ) as excinfo:
29 | nginxproxy.get("https://3.web.nginx-proxy.tld/port")
30 | assert """hostname '3.web.nginx-proxy.tld' doesn't match 'nginx-proxy.tld'""" in str(excinfo.value)
31 |
32 | r = nginxproxy.get("https://3.web.nginx-proxy.tld/port", verify=False)
33 | assert r.status_code == 500
34 |
--------------------------------------------------------------------------------
/test/certs/ca-root.key:
--------------------------------------------------------------------------------
1 | -----BEGIN RSA PRIVATE KEY-----
2 | MIIEpQIBAAKCAQEAndjE3OPr48hIOQigk/HejrowsQDLNfkkc6vej0J983rJitGT
3 | gBfxqq27fOPfqhE5bi1M5JDkKkrOrSitxCJLgpq+4Ls9/RXg8skZFHRAQbNwuKBe
4 | haDkPdamJ0i3dv6e4kZy41oIRqxQ/MKdminC4LShFZvPoKeh9ae7w1MgB2/4E68L
5 | O66bYiHlLNL7ENViSHhLyCmtqIE7kdV9jgn2NuVJ37m6/6SNQ3GBiIjEW+ooRQ3H
6 | EVKBCismcwq80+BD5VS/yW18KqX8m4sBM+IgZbcOqrV+APMbGvd8iNJgQSSQC/r0
7 | Wscgt7UeggVYKDazjDSPvLUEFUN5wEmydkP2AQIDAQABAoIBAQCDM4zetix6lx1B
8 | GuSuVFrTc/vJBInkgQRFiVRi67fZS/R+CJl73WsonWO7+YUNzWdZJxpE2hJs/OUx
9 | lSBqaL8u/gUuszRhS3BBHdpU4BQRCF/ndpVaqVNN+z78ZDrrE9Vo63nPdCRw6gYf
10 | MnzhiVjMghdq6Kn6NZwvno45WrzCsIbrrQ4zU+S2PhG8MTA53jzqqQ8mUSJX0lAl
11 | 6b9+1aWA0d0Jnk3M3doaFU/Dlnz3n6kkx0AdqNe8bdsFrPfwsrF+dwGx04SGgLmK
12 | V2OjIDFYYGtiHp3PJ9IYIA32ij+UloSDDZ2BxXkma8Zilw04ytY5l8tlk2ZDWTD9
13 | U2MXxjmBAoGBAMmmI19I/asTPjljlqzrOsrdRkklJvnCHgy/yw9u3nMfkJ0lLGAp
14 | mZoCqJIEsAqlLGM5bOjKy3KQ3n2SBX3mz7/RajnpJRTnNLeJIPAAXHN9TDyKcWRo
15 | Los6xHN7YMSLYKs4HMihXp9Yu4Ms88/8nO/01nufjN0rTgFnWdL0WfxJAoGBAMhk
16 | Qm92ukMmbrXSrV0WF+eFooHwgPmUWZ1oZY5ZHmO3FCuSBHiICGrWKmdbcG6H5zmZ
17 | oFZ0unsvk2Yjl+/+tntxr/dwp6Q+chsqkLms8GE76NWEO8qn4hQNywkFgpKlPci3
18 | n5IqpuQ2DpJ1PAQkwgZD/5rSscNidNMezXO5Uvv5AoGBALR291kjXcJpKlr6AbMn
19 | oipD9c8obMVBMNuAGh7pvjORoD7DMf+tu0XV8z8a6uHcCOmUTx/XvlP9yuDeegO/
20 | OVYV+NdzDDi04r0PAGdKK3NAQ6Y60Fhn1J/OLFqdpHDBu/X/9eKoaKJ7KvWumVUe
21 | YuVtXTauB8c4JkujTwQ4ov/hAoGAHxvhbGhkFhSbT0K7gx3w7BJE3iM2AojTOKqC
22 | SYzwOM6tJO5wHz4PAHbq8kyxsZcLgFenGoTYhlMmcM7JwYorThKiHKmyfL7s++ap
23 | vQlp785bIPp8RcO2RyK1CFuAn79jTgujjA9vBTKXJIlqncIPFOXtgl1/FzPrqvK3
24 | NmXoyhECgYEAje9hM9RYO0jbfmTZoQh+onMRz34SM9XWLH+NQGgfvsGtjeRnrUKK
25 | GuWQz/GQGJLy/Uc1KHIdrfPDjvQhZXmPL1v7pNfCrqyj+EnKCNDPPnYq5Zq4WLsB
26 | x1hKPH0LmfEBkXOiFGrD3h3KAuBK5nb0/EFBDR4JuMaySC5CpbOds9o=
27 | -----END RSA PRIVATE KEY-----
28 |
--------------------------------------------------------------------------------
/test/test_ssl/certs/nginx-proxy.tld.key:
--------------------------------------------------------------------------------
1 | -----BEGIN RSA PRIVATE KEY-----
2 | MIIEowIBAAKCAQEAy0X0FJv+ZIV5SjaNPdEn0Hw2KDDmc4BvfEkj0GwX5ETAd02a
3 | wrwkhOOlTbrS2lF7oSoS1MAZVWksIictGvb8S3/py6g86Gm40k/eTlDi0HQwfEJa
4 | rqqFpbFxTcl+hotijD4N4zvD9YELjGh5/r8Q+67sEUltZF4afbOSk06WGTqYBKdm
5 | snRhLUETDKRUDSx4/bSj6Dd4mt76vC6oD2cUWM7Dh9UUDospfUgZsqn1tOivMiFn
6 | FX5DUosgz584Q7/9yCR/UqOI8vFKFJEqbpFv+31qeMZtLt0eTCtjuzpDnJH539MI
7 | E2OGfc7oRs/xbB/K90ze2Evg2rwG2YcP/5ZFhQIDAQABAoIBAQCjAro2PNLJMfCO
8 | fyjNRgmzu6iCmpR0U68T8GN0JPsT576g7e8J828l0pkhuIyW33lRSThIvLSUNf9a
9 | dChL032H3lBTLduKVh4NKleQXnVFzaeEPoISSFVdButiAhAhPW4OIUVp0OfY3V+x
10 | fac3j2nDLAfL5SKAtqZv363Py9m66EBYm5BmGTQqT/frQWeCEBvlErQef5RIaU8p
11 | e2zMWgSNNojVai8U3nKNRvYHWeWXM6Ck7lCvkHhMF+RpbmCZuqhbEARVnehU/Jdn
12 | QHJ3nxeA2OWpoWKXvAHtSnno49yxq1UIstiQvY+ng5C5i56UlB60UiU2NJ6doZkB
13 | uQ7/1MaBAoGBAORdcFtgdgRALjXngFWhpCp0CseyUehn1KhxDCG+D1pJ142/ymcf
14 | oJOzKJPMRNDdDUBMnR1GBfy7rmwvYevI/SMNy2Qs7ofcXPbdtwwvTCToZ1V9/54k
15 | VfuPBFT+3QzWRvG1tjTV3E4L2VV3nrl2qNPhE5DlfIaU3nQq5Fl0HprJAoGBAOPf
16 | MWOTGev61CdODO5KN3pLAoamiPs5lEUlz3kM3L1Q52YLITxNDjRj9hWBUATJZOS2
17 | pLOoYRwmhD7vrnimMc41+NuuFX+4T7hWPc8uSuOxX0VijYtULyNRK57mncG1Fq9M
18 | RMLbOJ7FD+8jdXNsSMqpQ+pxLJRX/A10O2fOQnbdAoGAL5hV4YWSM0KZHvz332EI
19 | ER0MXiCJN7HkPZMKH0I4eu3m8hEmAyYxVndBnsQ1F37q0xrkqAQ/HTSUntGlS/og
20 | 4Bxw5pkCwegoq/77tpto+ExDtSrEitYx4XMmSPyxX4qNULU5m3tzJgUML+b1etwD
21 | Rd2kMU/TC02dq4KBAy/TbRkCgYAl1xN5iJz+XenLGR/2liZ+TWR+/bqzlU006mF4
22 | pZUmbv/uJxz+yYD5XDwqOA4UrWjuvhG9r9FoflDprp2XdWnB556KxG7XhcDfSJr9
23 | A5/2DadXe1Ur9O/a+oi2228JEsxQkea9QPA3FVxfBtFjOHEiDlez39VaUP4PMeUH
24 | iO3qlQKBgFQhdTb7HeYnApYIDHLmd1PvjRvp8XKR1CpEN0nkw8HpHcT1q1MUjQCr
25 | iT6FQupULEvGmO3frQsgVeRIQDbEdZK3C5xCtn6qOw70sYATVf361BbTtidmU9yV
26 | THFxwDSVLiVZgFryoY/NtAc27sVdJnGsPRjjaeVgALAsLbmZ1K/H
27 | -----END RSA PRIVATE KEY-----
28 |
--------------------------------------------------------------------------------
/test/test_ssl/test_hsts.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 |
4 | def test_web1_HSTS_default(docker_compose, nginxproxy):
5 | r = nginxproxy.get("https://web1.nginx-proxy.tld/port", allow_redirects=False)
6 | assert "answer from port 81\n" in r.text
7 | assert "Strict-Transport-Security" in r.headers
8 | assert "max-age=31536000" == r.headers["Strict-Transport-Security"]
9 |
10 | # Regression test to ensure HSTS is enabled even when the upstream sends an error in response
11 | # Issue #1073 https://github.com/nginx-proxy/nginx-proxy/pull/1073
12 | def test_web1_HSTS_error(docker_compose, nginxproxy):
13 | r = nginxproxy.get("https://web1.nginx-proxy.tld/status/500", allow_redirects=False)
14 | assert "Strict-Transport-Security" in r.headers
15 | assert "max-age=31536000" == r.headers["Strict-Transport-Security"]
16 |
17 | def test_web2_HSTS_off(docker_compose, nginxproxy):
18 | r = nginxproxy.get("https://web2.nginx-proxy.tld/port", allow_redirects=False)
19 | assert "answer from port 81\n" in r.text
20 | assert "Strict-Transport-Security" not in r.headers
21 |
22 | def test_web3_HSTS_custom(docker_compose, nginxproxy):
23 | r = nginxproxy.get("https://web3.nginx-proxy.tld/port", allow_redirects=False)
24 | assert "answer from port 81\n" in r.text
25 | assert "Strict-Transport-Security" in r.headers
26 | assert "max-age=86400; includeSubDomains; preload" == r.headers["Strict-Transport-Security"]
27 |
28 | # Regression test for issue 1080
29 | # https://github.com/nginx-proxy/nginx-proxy/issues/1080
30 | def test_web4_HSTS_off_noredirect(docker_compose, nginxproxy):
31 | r = nginxproxy.get("https://web4.nginx-proxy.tld/port", allow_redirects=False)
32 | assert "answer from port 81\n" in r.text
33 | assert "Strict-Transport-Security" not in r.headers
34 |
--------------------------------------------------------------------------------
/test/test_headers/certs/web.nginx-proxy.tld.key:
--------------------------------------------------------------------------------
1 | -----BEGIN RSA PRIVATE KEY-----
2 | MIIEogIBAAKCAQEAlVbHDUilKzxlST8m4TgrYTBW5JLXY+Drraz5M5uyMfE5Ewvl
3 | Q3vFvYqFyNk92KxxuhbngZayq67GwL2+p9GWj7Kb37r5TaE7fiFKzbZF+W15UL8k
4 | j8FrwQkZW2LLlugEFCDo1BZiavI3wZbinVMFC1Id52iS24s2aM2NWwL/EvCsXQzE
5 | 4HpVoklgn/9HH1JzVU3U8tFiovRQncn28UOz3FfhMXa04KRpfvJtNK65jXQme9n2
6 | BwDvSzZhs+96oTY6ttCe+LipDUwwou3rq2vrLuILKL73BLHp4ITWXTF3fNzSH9Qd
7 | cW9vbG0bvzHiW8NS0BT8i/tF6kHsysc7ZxLE3wIDAQABAoIBAEmK7IecKMq7+V0y
8 | 3mC3GpXICmKR9cRX9XgX4LkLiZuSoXrBtuuevmhzGSMp6I0VjwQHV4a3wdFORs6Q
9 | Ip3eVvj5Ck4Jc9BJAFVC6+WWR6tnwACFwOmSZRAw/O3GH2B3bdrDwiT/yQPFuLN7
10 | LKoxQiCrFdLp6rh3PBosb9pMBXU7k/HUazIdgmSKg6/JIoo/4Gwyid04TF/4MI2l
11 | RscxtP5/ANtS8VgwBEqhgdafRJ4KnLEpgvswgIQvUKmduVhZQlzd0LMY8FbhKVqz
12 | Utg8gsXaTyH6df/nmgUIInxLMz/MKPnMkv99fS6Sp/hvYlGpLZFWBJ6unMq3lKEr
13 | LMbHfIECgYEAxB+5QWdVqG2r9loJlf8eeuNeMPml4P8Jmi5RKyJC7Cww6DMlMxOS
14 | 78ZJfl4b3ZrWuyvhjOfX/aTq7kQaF1BI9o3KJBH8k6EtO4gI8KeNmDONyQk9zsrn
15 | ru8Zwr7hVbAo8fCXxCnmPzhDLsYg6f3BVOsQWoX2SFYKZ1GvkPfIReECgYEAwu6G
16 | qtgFb57Vim10ecfWGM6vrPxvyfqP+zlH/p4nR+aQ+2sFbt27D0B1byWBRZe4KQyw
17 | Vq6XiQ09Fk6MJr8E8iAr9GXPPHcqlYI6bbNc6YOP3jVSKut0tQdTUOHll4kYIY+h
18 | RS3VA3+BA//ADpWpywu+7RZRbaIECA+U2a224r8CgYB5PCMIixgoRaNHZeEHF+1/
19 | iY1wOOKRcxY8eOU0BLnZxHd3EiasrCzoi2pi80nGczDKAxYqRCcAZDHVl8OJJdf0
20 | kTGjmnrHx5pucmkUWn7s1vGOlGfgrQ0K1kLWX6hrj7m/1Tn7yOrLqbvd7hvqiTI5
21 | jBVP3/+eN5G2zIf61TC4AQKBgCX2Q92jojNhsF58AHHy+/vqzIWYx8CC/mVDe4TX
22 | kfjLqzJ7XhyAK/zFZdlWaX1/FYtRAEpxR+uV226rr1mgW7s3jrfS1/ADmRRyvyQ8
23 | CP0k9PCmW7EmF51lptEanRbMyRlIGnUZfuFmhF6eAO4WMXHsgKs1bHg4VCapuihG
24 | T1aLAoGACRGn1UxFuBGqtsh2zhhsBZE7GvXKJSk/eP7QJeEXUNpNjCpgm8kIZM5K
25 | GorpL7PSB8mwVlDl18TpMm3P7nz6YkJYte+HdjO7pg59H39Uvtg3tZnIrFxNxVNb
26 | YF62/yHfk2AyTgjQZQUSmDS84jq1zUK4oS90lxr+u8qwELTniMs=
27 | -----END RSA PRIVATE KEY-----
28 |
--------------------------------------------------------------------------------
/test/test_ssl/certs/web2.nginx-proxy.tld.key:
--------------------------------------------------------------------------------
1 | -----BEGIN RSA PRIVATE KEY-----
2 | MIIEogIBAAKCAQEA2u5GLUR88eaREb801gJO/kMj+20g940bxpzNHBoHlcLtuSNz
3 | wQIrUFE/M8+OqvETWEz/f31Kh/zwD1SvjOu6tA9xXhIfZLE9g4jdnAlQLTcdAzsY
4 | MDb0gpSHfzEnKIQMmW13tbhPyoNY1dhONnMcGlztBe+VYAMoDJ/Yb5iozQi+r7GV
5 | WmCW/irQmHSbSsBIZnNnVDMRIiDqEQW6pu10EgXY3k8BRjl02DQa8izC322UV1LB
6 | 5C4bjhIOQ+dvH9pRgDXCipu2KjA5a6B3+jcRt+zeboxvk4FeLZBpG0ukgMr05VvA
7 | E0W5dnDt007dZpiZn53wHv3dBE+aVbw4rUK53QIDAQABAoIBABeTCsl7E30017Ay
8 | h6z3yKvGbQx43tDpR/FmFwwMnX555AFImQFSi3l1ljmtAu7TUML0X5rJ0gm8qdjs
9 | xI6HH66d7xYzG2BLWZVdWoef1RtZUO11IpCmikO5XLHMiCvrtDOdPwO5WhYzeJBm
10 | X12rnX4VPYyjFNGm5Vwepj62EI8rS9G3NG00pDYPmN/vUQJiV/iTRIlvXgFm4Hl2
11 | zJhVi+NhyiptFEycdg65JwVfLKtmUXRmwGFiSxQi1FX8YS5EdIV2S8yDwXlWSxmq
12 | 4R1eSID7pKxtzyRtBqZJggzfqtY8cMpoOC12FbLAvzagOavs4ntMgAVy5k2T15G2
13 | syQyLSECgYEA+1NIRF3CxNUaPvpcR8Y4PWhwDEzqn5ZscnXaFrUp1W72f3bpwSCa
14 | /t9lXe9O53R5/yt4nCbwvVM0UWYPHGZGQr+5AS7WWDVWVcwkXX1NIjALi0TXQ0Ty
15 | zeuViXDofUS31yhwFFmgGa52pd+edXaGRvxzGyPVdtwpSLZP/gBLQykCgYEA3wC9
16 | sHNPKMxi6vI2VBvmBXHoCSDAkuRLmQEGDmjjt0Ve4fmwGRbBJxniOlNtufNQRfRg
17 | 67qaeM4BTrP03cilZ71yXvLN5G2opY5c/I0dYTXRhV3IV6XwlCC+0xmn+ro7kB8x
18 | J4wAj/h/tJ8T0+0TpnbyjmPH4KTJ9GjRTKwe65UCgYBszIHlbr2JXkONbe6S98GS
19 | ++o9uPJ9Aa6S4mf2Gpkwl2fIiF7rR0Ux/t2wC5AZ7Ld/en8tAkKHg0SL1GXIQpI6
20 | BSt+0prh9r0YSVaYzkyc9zWYJcYWjfuan1jN9f3/dMctMolKlf4UAA3HAwZjDVtV
21 | 0aW24w1e9jI9EweQCuqJ+QKBgBwZec18GiNn7abxMktS4J8bBUPxLpLT1XrIGD1E
22 | lj0HrrcGwVvH9Dq7FjiHPrJJqHnIG1ZYwxIp0xxZrKctmzoBMyInsi3wa2nBEJJ6
23 | LZOMNoR5lr8El9XyclkjSHldchfs9kKnb4K0q1LVIKh5nRpCrrmmdQ8ndJMpigYB
24 | QjwpAoGAWIhFrN0Acdq7Xc9pScqnAohPMWCTBUbeKrOm0ZwN4Q9D+lLeeggbWlWy
25 | AqR4WHQMHc6B+p4Ncg3XBCFw0V62PkYhSCdaLNH3CPyFT0qoeY8VuMjmqS7yoKvp
26 | uMMHfzMmyGg0dyplVGgANafb/it6Dp8T0pnCmzxhe57jf9xsVBo=
27 | -----END RSA PRIVATE KEY-----
28 |
--------------------------------------------------------------------------------
/test/test_ssl/certs/web3.nginx-proxy.tld.key:
--------------------------------------------------------------------------------
1 | -----BEGIN RSA PRIVATE KEY-----
2 | MIIEowIBAAKCAQEArJuQ3CwNw670oMxAFdLEwiyMFbZwKM8yxAPOs4cwXaYSlml6
3 | /mcpHIwku2rIhhMZkQA+7wBnULDqyZPGinOC2Defjm4SE+z6CA+sc25ClmefIMUc
4 | o7FKgzYOCjGTdrG2N0/giDxG3MFTYN8orj4g2NlTor4JOPD/SpFFy8u1sz19CZhH
5 | 3A1eg3O2yfP7mvK7sGLKqq9rQuUIshSH9PzxFPjMdrPASd9mxiGgvF4Mu97pnOf7
6 | MaHPxOm7vcNaDSNSxrOEd/EMPcrDYEj5fqbcT/fSW3wCTTgJZDN+u7Flu+IrHZpJ
7 | 1DQhQnpJPhFsEGa0kdu9OsKN9OQDsbRuXJi/fQIDAQABAoIBAA14DjPAFEriyiAK
8 | EC4jxkrIox3GoLXuhS2ahnSn5fRI00Z9cKWNcz3RCcS+LmuX7fTMqhyIUYeQZqHY
9 | MDP5k4o/vOmmWS7I3THn1zMitXt7FoW+G+ACI6hdfXb6K2GluGxUhVbcLUNoqpLy
10 | lwARxQpm2wnl/l49IA63i1S9zq3vy5HSvxBv3jq8xp2PX3Sho33LhsvW+miCJe+R
11 | etKSV4mAjvR62XVgUGJ40FciVMK3pzwwIKPLI7k9sa7WHZr6fNHeDxIWA6AsVBTH
12 | O+2l8Ufd79KesOD6VqdHlxg2a79s3NoCOflQQAbOSSR9ioCE7Ykgvw0wVl57xNoB
13 | WZVAY0ECgYEA2I6+a56NMzkEMxr0w9ZRqgocXCgbqxZx1v7newDyO5J3X4jYhmMJ
14 | abNZtnVs1Pz0IAgCMCf+N4D+RAi9/ahYxq7jmhIkT/IUHseh93XLgd/x9Q9ZJOGm
15 | 9+NI3aIBgWOsy4orKxfwzRAVEakOCChYUCy/GUDDD1MPcjxC8ma5abUCgYEAzAua
16 | 15Nayr9Sg0QsHqNvgTVkVlA6u+TwN+vfI8cH5nmXIMm5ShwCc9+Pm8mpcFwUo4uE
17 | vOzQ+NwG9CBbVu6/i1/aR+ZlbctdhpsW51v18B9eFVXSZUvEv1ONGdoJZhq0tW7W
18 | V4Zjf+UwdTcrSZKVpd5woUbRkByROPdir/3Ie6kCgYBhf5LX3SBxSWBMqfw9F4bY
19 | 6YhvLVeXpZlHVKhfRsPIcl7wUio6Bui8ABWKAkAnfGNk8HYbvEXGM3tGojD3vQ2L
20 | Fj4+paBXpgPM/9A6G3yuUmcbD/fwlO+Zd2jc8A2BdaDcWq6ozjSJ/o2dz+ETZyar
21 | ohm/gtrPUXQI2HzDqeAcaQKBgEBMd+LvAHFbkPjkhrKw9fZViOTaK2gCYOB+Z7ay
22 | hX7PWhxu9QCxiuRQ0sRY7BgILEjNMmsGhWOmklpjx+TBH4MgFX0K0XOj3jkIrlMB
23 | 26JrgA5hGQfqtHlGLvSyjLusNr3ly42RP9GRu490byOkGZxHWF66Hle3aNv2uRaU
24 | dpThAoGBAMIPpf4E6xGzurhgYdXMit3jGAYD85BbNUIm9jOym2lxS63ipoF08QhH
25 | NoMVRf/AUoS4VDGXsuABjMffTZbE8L+DaL1cWSuPJAoF9AXUXtz6A8LRc9Mn2WjS
26 | L8BIs9xZCzrJ5XzY4PSnjjyAU81z6E80azWglkmh8rRDzi/o9O79
27 | -----END RSA PRIVATE KEY-----
28 |
--------------------------------------------------------------------------------
/test/stress_tests/test_deleted_cert/certs/web.nginx-proxy.key:
--------------------------------------------------------------------------------
1 | -----BEGIN RSA PRIVATE KEY-----
2 | MIIEowIBAAKCAQEAtidjpcbo9HqUDsyiYnZtXTNvzxn85+W7Dg7QfE9zTEgrF9FN
3 | 1Z9CCHOEVIyG0sXaWQE/QiLgNvDcq94KvSYrIhOHph8j7w6ZJ4sVShvvk8lrkd6g
4 | Agxiu8xWN+glksMf8WnYfKgz4InOFGegOXeIkeajB5eQIojQeRhj+29+7itCfiP1
5 | 59rp7mr6lmWf4SsVScjNLc6GTywqZ3m/QTAUzPYPFHSettPQO/AbuOgZKv3W/dxL
6 | TmV9m783fi01Ii50kM5BNT1BoJnblx+/Phg8SPva38ZOTrlnuBDVpRMDxLdl56rw
7 | FEvTTer+j2nPUCFjJ8+eTGcVez872ssXgGEeJQIDAQABAoIBAGQCMFW+ZfyEqHGP
8 | rMA+oUEAkqy0agSwPwky3QjDXlxNa0uCYSeebtTRB6CcHxHuCzm+04puN4gyqhW6
9 | rU64fAoTivCMPGBuNWxekmvD9r+/YM4P2u4E+th9EgFT9f0kII+dO30FpKXtQzY0
10 | xuWGWXcxl+T9M+eiEkPKPmq4BoqgTDo5ty7qDv0ZqksGotKFmdYbtSvgBAueJdwu
11 | VWJvenI9F42ExBRKOW1aldiRiaYBCLiCVPKJtOg9iuOP9RHUL1SE8xy5I5mm78g3
12 | a13ji3BNq3yS+VhGjQ7zDy1V1jGupLoJw4I7OThu8hy+B8Vt8EN/iqakufOkjlTN
13 | xTJ33CkCgYEA5Iymg0NTjWk6aEkFa9pERjfUWqdVp9sWSpFFZZgi55n7LOx6ohi3
14 | vuLim3is/gYfK2kU/kHGZZLPnT0Rdx0MbOB4XK0CAUlqtUd0IyO4jMZ06g4/kn3N
15 | e2jLdCCIBoEQuLk4ELxj2mHsLQhEvDrg7nzU2WpTHHhvJbIbDWOAxhsCgYEAzAgv
16 | rKpanF+QDf4yeKHxAj2rrwRksTw4Pe7ZK/bog/i+HIVDA70vMapqftHbual/IRrB
17 | JL7hxskoJ/h9c1w4xkWDjqkSKz8/Ihr4dyPfWyGINWbx/rarT/m5MU5SarScoK7o
18 | Xgb25x+W+61rtI+2JhVRGO86+JiAeT4LkAX88L8CgYAwHHug/jdEeXZWJakCfzwI
19 | HBCT1M3vO+uBXvtg25ndb0i0uENIhDOJ93EEkW65Osis9r34mBgPocwaqZRXosHO
20 | 2aH8wF6/rpjL+HK2QvrCh7Rs4Pr494qeA/1wQLjhxaGjgToQK9hJTHvPLwJpLWvU
21 | SGr2Ka+9Oo0LPmb7dorRKQKBgQCLsNcjOodLJMp2KiHYIdfmlt6itzlRd09yZ8Nc
22 | rHHJWVagJEUbnD1hnbHIHlp3pSqbObwfMmlWNoc9xo3tm6hrZ1CJLgx4e5b3/Ms8
23 | ltznge/F0DPDFsH3wZwfu+YFlJ7gDKCfL9l/qEsxCS0CtJobPOEHV1NivNbJK8ey
24 | 1ca19QKBgDTdMOUsobAmDEkPQIpxfK1iqYAB7hpRLi79OOhLp23NKeyRNu8FH9fo
25 | G3DZ4xUi6hP2bwiYugMXDyLKfvxbsXwQC84kGF8j+bGazKNhHqEC1OpYwmaTB3kg
26 | qL9cHbjWySeRdIsRY/eWmiKjUwmiO54eAe1HWUdcsuz8yM3xf636
27 | -----END RSA PRIVATE KEY-----
28 |
--------------------------------------------------------------------------------
/test/test_ssl/wildcard_cert_and_nohttps/certs/default.key:
--------------------------------------------------------------------------------
1 | -----BEGIN RSA PRIVATE KEY-----
2 | MIIEpQIBAAKCAQEA8v15cJkM2mNcgShyMQFi6WjXy43GlfnsJjQcCMZt3q3YsMCu
3 | SANzdms/xTWGxkKRUzyqhYmEkmeS76lb8tQEczQCNdRq+sLakUqpcIclOIQdk5k8
4 | 1wNhpm0zb4NFBK9PlmIewXmHydVM6Y+F4sgbW/y4Av97bTRMXUBzRJ7FH1/gD4mI
5 | xDUrBFOMjqB8fJcWIMJPocDdv9UTLWQlA/LY1ScBcMn0NzM2fntIVOw3K4E9UDzU
6 | XwUZ4gu6dvYsOyNLgnhf6eNX/DlKXEKCcsijr7ezkeQBnCxHXv+qrWMc55wuoqxd
7 | UTCDZ274Wu0LcORo1Olep/VehzvoMa0ABPh72QIDAQABAoIBAQDqcaW5/fFoxHV8
8 | KIoEvlGw4ndS7nesPHacZaqmzM01DIcGAuIkmS/OEax1mi9vGsschGwCa6x9lXEv
9 | yzfsEqQ4gvWe+lQ9ncNEa8UPzVUcMlxXDIKm8ZxF9xapgP4Whw9DCWijQ57AHg0X
10 | TGLhbDD5j9v7CIUN2GfVkVml24pVuUoeXqv7ZLzTJKZ+Q/eqxyeIikjFheXzaQxb
11 | bUHbEHIXJtHMYULXmfc5WCxuobHqal3z0ymCijoZVXV8hp8dtDP34tRV9MID9wck
12 | lRUVqboFCIXxmLLRTZgyCbiFLkCIu2nmgNobWCNfkHN7QQhToPEecSFMZzYtmo6/
13 | T1fHE3ABAoGBAP1J1Izfc4CF9t2iPGzXyn8oNkXHLMPKtFQ2Rb8XwBryUOOrAHqT
14 | FIZ2FsDJr0VvS1ihFs1kbO+WAY5W5GytwiiVXvztHz3/f5JnGgvMCeUcEmaj90vq
15 | sTyfHc2OKFjumIjGe87uav3bgac7nOWLO+RIJ/ua6UO7/8psqwryxY4FAoGBAPWX
16 | a502kT56VwI3Gf8hb37PZ/PD+gOzgzVcMn13yLZ4gC9xoP4TKUBHSz4wO8asjKk5
17 | 1RD/DITXYKelyRXynOtMW+2j2s5bVBpOshN/n9jRC1haoGJZYb2JVP6+8WoZKQOF
18 | NwgNlI4he32kSFw59fjkdG64iw7KY8ZYUatkrgrFAoGBAPozTjUCHfRdYOi6c/oI
19 | h81oCYSQJVYbDFsLaYZEjc2Qg/sBVm2+kE3qpLs3/10VfVZFemLVyw44Hb1fdDEu
20 | y1aPhs9N5Mi3dGtIUWBJ45RgUIT3fzeM1BtQCn6c6JpAxoiFmJNmzGWLyd1Kc8gD
21 | 69uqs2RFOBtiwGBTS/p6qk+JAoGBAM1QkpnzFYf69SSX9jbRuAl20Xv8GdbgS0/f
22 | zSIRcw4BPYDsaOAgGrtvHttVrZORi2KqQ5Ma9ldUS6y8L5kWo9MemjfYZUNhHLWF
23 | luAwMO0tDmQGF9FA0jKHTjROYzsE38Heq7wixk/wc/H81rWrixRRwXkS9MYfszwN
24 | d/FmkQ3VAoGAXHZrDEygUmf4q0LwjLVF0TPzElh530qVmyhPa0OBs/hVh9Mwv/i6
25 | fj3+k7uYWgKDzcaVXSMOFGt515F8qy0AUEY9r+IjAn01KTLKO4ZuPiSpxliqDbCs
26 | gzsX9CWVSVgTN+TY15QCoJNpzLiyrXe3uldAP5JEBQSnjt9OfSJQ5IU=
27 | -----END RSA PRIVATE KEY-----
28 |
--------------------------------------------------------------------------------
/test/test_headers/certs/web-server-tokens-off.nginx-proxy.tld.key:
--------------------------------------------------------------------------------
1 | -----BEGIN RSA PRIVATE KEY-----
2 | MIIEowIBAAKCAQEAtPqdinQ/F+qZHEVxGJDrkjU415AhgQqRBUHPtYc0vdh7f30G
3 | M/iUZ47kB1R/t2LFdmx/fBklGSw2miZUji2XAngxxhPTrfMxYubPlq5jN929c8tO
4 | +z+bZWeX2FpdDnKxEasO1yOptyLeI3R+iHwomKluAPS+jGnqPzOLGZfaG6ZltVqS
5 | ATw6E2sAAuGYeNPa6qacM7AdnwLE8dDW3nr3QhJLMfvt6dfYFehOGJF8nb8PsBLW
6 | 4oCLeu8XcFH0PLdDy1Zhr2F6Tp1sXtgnDDvXpB0vDaCZj7VxkyG0h760HHeguc2R
7 | vZzQuYFQEmPSCqlhBZEZJ/fqnY5IZS4a5/3xtwIDAQABAoIBAAaBi/BSRYJimKZ/
8 | iJVNgGp9J1H4iHvPGW+K8iCgf7Dje20V3Yc4xH0EkgYBb6X0Ew0y0VJwxPimsj/Q
9 | aPHDic446/Em/VEfkQLxMT1Ff6OegRUMlgZKPxfiJX9NoFLIpLzx3VK2oX9H7Zxw
10 | r6vQatUyIhY+tiruE9G51KJS5zBfN388ErfRUI8ByBaDGH0huA6kTBcNffhCfZr5
11 | 9naWSIIcuBe8v7z6nAaeYL00q1q3vuWPmuQduSgsmef7QuN71CIxuOAqXTJl8koS
12 | LYNbj8yvIy3nOF90D+uZD/Pa2Y0kB6aum09hbUP15K0QFKulbKLRQ60IuvRcw3Qv
13 | MM177OECgYEA5Rw3qUcoTDfsx+nu2BxECj62uyNVZfX/QMf7dvzCqjXuOhij+KBB
14 | U9xnNfuLc4HfCXx/rMg5dGExEBbD2iHAo0nvnCSxzLJmF6i66Uves0VWISXcv2Au
15 | L0TWMhhsbDFoqkWuxXr69oNwKyl9yFRFWEY3p3G+aBAEqWZ1lOkU8O0CgYEAyjhC
16 | bN4mJJYhvX+cXhv+89Z+JIDAvtvQ5Vy7kxvhQUTx2By6rWKKrBPdTnzsxBGKqQwv
17 | lXzfgj/MlIr6A6QDReGwU3ZXTJqSGEuT8Ra9SbjczQgaGOrPCrWhnbeZ18iM67pJ
18 | LPfLgdRdkh3XgbOOKcDhpg2KybbbyXx6Q2xb7LMCgYEAzKHKWUh0BreApgIcUSvV
19 | 3ayr+zOQ5/Oy24KC6IDTwcFPmNY/RiakkqluCfo1UKKzuj5XrtRa9MaGUs9yeJbi
20 | /zVfbQAdSi4hH4qV/x/Dtiz8w7iUlN3sAk4iXjYQSQZMbKC2fC3ej2VQP0zcypvy
21 | H+j/dnASV9HOyBr6dFlGWfUCgYB3gfYntsXd+2fnQOJdb7glzM5xrjG62dfDpSEp
22 | mGFwHFm8+YWNcF45weeZOhUG7sL+krgQZWMF68RwyQ1mV2ijxPRa7uY63GKYvxmo
23 | cmLdjcXX2gDqVuKTFrJzrgzaTKiTq10RmUQI70N5Ve+FtGLA5D+2zewGt+1+TvVG
24 | oWRWJwKBgAUpJ/NXOB82ie9RtwfAeuiD0yDPM3gNFVe0udAyG/71nXyHiW5aHn/w
25 | H+QSliw7gqir4u6bcrprFQMcwiowtCfeDkcXoQCOBx6TvL2zZTrG7J/68yDHfHGg
26 | w3eFN7ac8FsliRpT+UVKM97zJXcWFkai5Q+R7oKsWXRVXQUZZxg9
27 | -----END RSA PRIVATE KEY-----
28 |
--------------------------------------------------------------------------------
/test/test_ssl/wildcard_cert_and_nohttps/certs/web.nginx-proxy.tld.key:
--------------------------------------------------------------------------------
1 | -----BEGIN RSA PRIVATE KEY-----
2 | MIIEowIBAAKCAQEAzit0E7Ia1XJcPhD3YwEi3+jZzwuKP0B1Ylh4J56vM9KhGWrh
3 | t1fb2Y8FcMI1XfFEDVFidHPld9m7xtAzekOI6eY8LdQ5nWE0WhnzwZbgvSZbaRim
4 | TIwhBNj6ViLsVQ26SU2OJ2l/gunn6cS3h3DX10tJ0cGMsFoTYtvewZQx0cl0xGMB
5 | UBBwQnNnxHYy+9K3kS/ozzqWSu6ODRN0cxvkdIPnZtaNgRlUW9hHPju1/TWi3/N9
6 | HJ5n7lDaKJwCCq11jQT3KB8EiROs7ak0Jtz3+R9yIdVy+wnZy0DADTY8wHcOmvdB
7 | 8TvdtgWrE2DF/cZf9QXEQgC6te/73GSY2U0rBwIDAQABAoIBAGVkDVPaVUP/V8nW
8 | QjNYTbRcKTGfdT+iDZht9blWWsdboIqFe7fU53PY2E4Z1HD8xADgs1Cd5o3IcIZX
9 | wdkw+VY+Of43zpXNRhfBh5T/BEtBX9cRnkcq6todcw+FYUB63dBK6cwMH/9b1Qes
10 | DK35GszwY79aNjxMMBiAFM6SeOW4EElPsV8wd9ldX/ndiZuwkZ6k9PfyWrfeeaF+
11 | EwVf/HaT0bV7cHQ73tYqzKjMpdbzIyaMzuAMGZDwPfLK+O1rEsWvLvK0ypl2Omzw
12 | ndon8U3z0JPNmBGoq+SFS2qtCeOezNX3lPz+TWxG05R5iiFtuK83zJ5qGqCgCNZ6
13 | qzpZsOECgYEA/NvWqT5MdZS1fdL2wROzFMTH4OBdUGr1Gh/DsNZj4qFVSFl969mA
14 | 7Vntm+koNLFsJt2EB67kC3ZWjozLXomHJ55/uKNnJ5LrLxczQ9x4l52CsTzrlvFq
15 | crYjQZDmeN3B4Z+8RSi2icq6j1PeaCZRTvcz6eBjNYj/v/O0SmiXIp8CgYEA0Lsh
16 | fZWuw23a8UXS2YUrXXqfIEdisVMnLRu3Zi0Y1R4lIpuwn5+2n+TxnuWcY1q+ZTMw
17 | dcmGPi6aRj81kEN/Kw5raKoVb6YywTNB4/Dwz7PRQH386FrjfivGXGEEINgbPQ09
18 | 2u0QV2Cr9yMGZ5qNXut70RYewkxjF7+s6L8+RpkCgYB9ikBHgtC/R/fb4pP0RG2T
19 | ECgUtBBgTtomAENOVwL8kBEhfJ0SLcjfDtjzoYz+rF//49cbYW+DaVuMJscJxso9
20 | l2neJ/KdKUpu9NvVA280B1XN3WsyY+Xv0hIrCWAD/kW2WXJF+/K08twxMPipSOzx
21 | gbZalbdr6vrfOIX4s3jmDQKBgDiXA3Vw53jEh99x9sBSgndNj2bI89DvomdwZECn
22 | aVweWCMR4sjkHDctcvSJe+TT7VqyjijhAixJpjn1WShLpGaf+i7eLgGfJZOLugl6
23 | gU9OiSTbA35bZeIHLDhPdTcSYBAlTufT7eJCq1zNeicMl9dsMJ13Sc+TtinyJYbU
24 | kqXBAoGBAL9gRa1PkNkpCJ5F9aYSohCAXB7DaAgYvVyvOTQ8Bw2uACPgdnpHmxQd
25 | /sT7qJ1h8ZCtn89Ug/4yx79eUcOImugoCRIUVtq1xhyXUdVl55Tuy5bKBSSAe/Vh
26 | T7sAmryCkzn9ihRziY2j84vK0mdMkCU5AoatPg5l0g1adn5zcY6q
27 | -----END RSA PRIVATE KEY-----
28 |
--------------------------------------------------------------------------------
/test/requirements/README.md:
--------------------------------------------------------------------------------
1 | This directory contains resources to build Docker images tests depend on
2 |
3 | # Build images
4 |
5 | make build-webserver
6 |
7 |
8 | # python-requirements.txt
9 |
10 | If you want to run the test suite from your computer, you need python and a few python modules.
11 | The _python-requirements.txt_ file describes the python modules required. To install them, use
12 | pip:
13 |
14 | pip install -r python-requirements.txt
15 |
16 | If you don't want to run the test from your computer, you can run the tests from a docker container, see the _pytest.sh_ script.
17 |
18 |
19 | # Images
20 |
21 | ## web
22 |
23 | This container will run one or many webservers, each of them listening on a single port.
24 |
25 | Ports are specified using the `WEB_PORTS` environment variable:
26 |
27 | docker run -d -e WEB_PORTS=80 web # will create a container running one webserver listening on port 80
28 | docker run -d -e WEB_PORTS="80 81" web # will create a container running two webservers, one listening on port 80 and a second one listening on port 81
29 |
30 | The webserver answers on two paths:
31 |
32 | - `/headers`
33 | - `/port`
34 |
35 | ```
36 | $ docker run -d -e WEB_PORTS=80 -p 80:80 web
37 | $ curl http://127.0.0.1:80/headers
38 | Host: 127.0.0.1
39 | User-Agent: curl/7.47.0
40 | Accept: */*
41 |
42 | $ curl http://127.0.0.1:80/port
43 | answer from port 80
44 |
45 | ```
46 |
47 |
48 | ## nginx-proxy-tester
49 |
50 | This is an optional requirement which is usefull if you cannot (or don't want to) install pytest and its requirements on your computer. In this case, you can use the `nginx-proxy-tester` docker image to run the test suite from a Docker container.
51 |
52 | To use this image, it is mandatory to run the container using the `pytest.sh` shell script. The script will build the image and run a container from it with the appropriate volumes and settings.
53 |
--------------------------------------------------------------------------------
/test/test_dockergen/test_dockergen_v3.py:
--------------------------------------------------------------------------------
1 | import os
2 | import docker
3 | import logging
4 | import pytest
5 | import re
6 | from distutils.version import LooseVersion
7 |
8 |
9 | raw_version = docker.from_env().version()["Version"]
10 | pytestmark = pytest.mark.skipif(
11 | LooseVersion(raw_version) < LooseVersion("1.13"),
12 | reason="Docker compose syntax v3 requires docker engine v1.13 or later (got {raw_version})"
13 | )
14 |
15 |
16 | @pytest.fixture(scope="module")
17 | def nginx_tmpl():
18 | """
19 | pytest fixture which extracts the the nginx config template from
20 | the nginxproxy/nginx-proxy:test image
21 | """
22 | script_dir = os.path.dirname(__file__)
23 | logging.info("extracting nginx.tmpl from nginxproxy/nginx-proxy:test")
24 | docker_client = docker.from_env()
25 | print(
26 | docker_client.containers.run(
27 | image="nginxproxy/nginx-proxy:test",
28 | remove=True,
29 | volumes=["{current_dir}:{current_dir}".format(current_dir=script_dir)],
30 | entrypoint="sh",
31 | command='-xc "cp /app/nginx.tmpl {current_dir} && chmod 777 {current_dir}/nginx.tmpl"'.format(
32 | current_dir=script_dir
33 | ),
34 | stderr=True,
35 | )
36 | )
37 | yield
38 | logging.info("removing nginx.tmpl")
39 | os.remove(os.path.join(script_dir, "nginx.tmpl"))
40 |
41 |
42 | def test_unknown_virtual_host_is_503(nginx_tmpl, docker_compose, nginxproxy):
43 | r = nginxproxy.get("http://unknown.nginx.container.docker/")
44 | assert r.status_code == 503
45 |
46 |
47 | def test_forwards_to_whoami(nginx_tmpl, docker_compose, nginxproxy):
48 | r = nginxproxy.get("http://whoami.nginx.container.docker/")
49 | assert r.status_code == 200
50 | whoami_container = docker_compose.containers.get("whoami")
51 | assert r.text == f"I'm {whoami_container.id[:12]}\n"
52 |
53 |
54 | if __name__ == "__main__":
55 | import doctest
56 | doctest.testmod()
57 |
--------------------------------------------------------------------------------
/test/test_ssl/test_dhparam.yml:
--------------------------------------------------------------------------------
1 | web5:
2 | image: web
3 | expose:
4 | - "85"
5 | environment:
6 | WEB_PORTS: "85"
7 | VIRTUAL_HOST: "web5.nginx-proxy.tld"
8 |
9 | # Intended for testing with `dh-file` container.
10 | # VIRTUAL_HOST is paired with site-specific DH param file.
11 | # DEFAULT_HOST is required to avoid defaulting to web2,
12 | # if not specifying FQDN (`-servername`) in openssl queries.
13 | web2:
14 | image: web
15 | expose:
16 | - "85"
17 | environment:
18 | WEB_PORTS: "85"
19 | VIRTUAL_HOST: "web2.nginx-proxy.tld"
20 |
21 |
22 | # sut - System Under Test
23 | # `docker.sock` required for functionality
24 | # `certs` required to enable HTTPS via template
25 | with_default_group:
26 | container_name: dh-default
27 | image: &img-nginxproxy nginxproxy/nginx-proxy:test
28 | environment: &env-common
29 | - &default-host DEFAULT_HOST=web5.nginx-proxy.tld
30 | volumes: &vols-common
31 | - &docker-sock /var/run/docker.sock:/tmp/docker.sock:ro
32 | - &nginx-certs ./certs:/etc/nginx/certs:ro
33 |
34 | with_alternative_group:
35 | container_name: dh-env
36 | environment:
37 | - DHPARAM_BITS=3072
38 | - *default-host
39 | image: *img-nginxproxy
40 | volumes: *vols-common
41 |
42 | with_invalid_group:
43 | container_name: invalid-group-1024
44 | environment:
45 | - DHPARAM_BITS=1024
46 | - *default-host
47 | image: *img-nginxproxy
48 | volumes: *vols-common
49 |
50 | with_custom_file:
51 | container_name: dh-file
52 | image: *img-nginxproxy
53 | environment: *env-common
54 | volumes:
55 | - *docker-sock
56 | - *nginx-certs
57 | - ../../app/dhparam/ffdhe3072.pem:/etc/nginx/dhparam/dhparam.pem:ro
58 |
59 | with_skip:
60 | container_name: dh-skip
61 | environment:
62 | - DHPARAM_SKIP=true
63 | - *default-host
64 | image: *img-nginxproxy
65 | volumes: *vols-common
66 |
67 | with_skip_backward:
68 | container_name: dh-skip-backward
69 | environment:
70 | - DHPARAM_GENERATION=false
71 | - *default-host
72 | image: *img-nginxproxy
73 | volumes: *vols-common
74 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | # ⚠️ PLEASE READ ⚠️
2 |
3 | ## Questions or Features
4 |
5 | If you have a question or want to request a feature, please **DO NOT SUBMIT** a new issue.
6 |
7 | Instead please use the relevant Discussions section's category:
8 | - 🙏 [Ask a question](https://github.com/nginx-proxy/nginx-proxy/discussions/categories/q-a)
9 | - 💡 [Request a feature](https://github.com/nginx-proxy/nginx-proxy/discussions/categories/ideas)
10 |
11 | ## Bugs
12 |
13 | If you are logging a bug, please search the current open issues first to see if there is already a bug opened.
14 |
15 | 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.
16 |
17 | Please at least provide:
18 | - 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).
19 | - 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.
20 | - generated nginx configuration obtained with `docker exec nameofyournginxproxycontainer nginx -T`
21 |
22 | If you can provide a script or docker-compose file that reproduces the problems, that is very helpful.
23 |
24 | ## General advice about `latest`
25 |
26 | Do not use the `latest` tag for production setups.
27 |
28 | `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.
29 |
30 | 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.
31 |
32 | This recommendation stands for pretty much every Docker image in existence, not just nginx-proxy's ones.
33 |
34 | Thanks,
35 | Nicolas
36 |
--------------------------------------------------------------------------------
/test/test_virtual-path/test_virtual_paths.py:
--------------------------------------------------------------------------------
1 | from time import sleep
2 |
3 | import pytest
4 | from docker.errors import NotFound
5 |
6 | @pytest.mark.parametrize("stub,expected_port", [
7 | ("nginx-proxy.test/web1", 81),
8 | ("nginx-proxy.test/web2", 82),
9 | ("nginx-proxy.test", 83),
10 | ("foo.nginx-proxy.test", 42),
11 | ])
12 | def test_valid_path(docker_compose, nginxproxy, stub, expected_port):
13 | r = nginxproxy.get(f"http://{stub}/port")
14 | assert r.status_code == 200
15 | assert r.text == f"answer from port {expected_port}\n"
16 |
17 | @pytest.mark.parametrize("stub", [
18 | "nginx-proxy.test/foo",
19 | "bar.nginx-proxy.test",
20 | ])
21 | def test_invalid_path(docker_compose, nginxproxy, stub):
22 | r = nginxproxy.get(f"http://{stub}/port")
23 | assert r.status_code in [404, 503]
24 |
25 | @pytest.fixture()
26 | def web4(docker_compose):
27 | """
28 | pytest fixture creating a web container with `VIRTUAL_HOST=nginx-proxy.test`, `VIRTUAL_PATH=/web4/` and `VIRTUAL_DEST=/` listening on port 84.
29 | """
30 | container = docker_compose.containers.run(
31 | name="web4",
32 | image="web",
33 | detach=True,
34 | environment={
35 | "WEB_PORTS": "84",
36 | "VIRTUAL_HOST": "nginx-proxy.test",
37 | "VIRTUAL_PATH": "/web4/",
38 | "VIRTUAL_DEST": "/",
39 | },
40 | ports={"84/tcp": None}
41 | )
42 | sleep(2) # give it some time to initialize and for docker-gen to detect it
43 | yield container
44 | try:
45 | docker_compose.containers.get("web4").remove(force=True)
46 | except NotFound:
47 | pass
48 |
49 | """
50 | Test if we can add and remove a single virtual_path from multiple ones on the same subdomain.
51 | """
52 | def test_container_hotplug(web4, nginxproxy):
53 | r = nginxproxy.get(f"http://nginx-proxy.test/web4/port")
54 | assert r.status_code == 200
55 | assert r.text == f"answer from port 84\n"
56 | web4.remove(force=True)
57 | sleep(2)
58 | r = nginxproxy.get(f"http://nginx-proxy.test/web4/port")
59 | assert r.status_code == 404
60 |
--------------------------------------------------------------------------------
/Dockerfile.alpine:
--------------------------------------------------------------------------------
1 | # setup build arguments for version of dependencies to use
2 | ARG DOCKER_GEN_VERSION=0.9.0
3 | ARG FOREGO_VERSION=v0.17.0
4 |
5 | # Use a specific version of golang to build both binaries
6 | FROM golang:1.18.1-alpine as gobuilder
7 | RUN apk add --no-cache git musl-dev
8 |
9 | # Build docker-gen from scratch
10 | FROM gobuilder as dockergen
11 |
12 | ARG DOCKER_GEN_VERSION
13 |
14 | RUN git clone https://github.com/nginx-proxy/docker-gen \
15 | && cd /go/docker-gen \
16 | && git -c advice.detachedHead=false checkout $DOCKER_GEN_VERSION \
17 | && go mod download \
18 | && CGO_ENABLED=0 go build -ldflags "-X main.buildVersion=${DOCKER_GEN_VERSION}" ./cmd/docker-gen \
19 | && go clean -cache \
20 | && mv docker-gen /usr/local/bin/ \
21 | && cd - \
22 | && rm -rf /go/docker-gen
23 |
24 | # Build forego from scratch
25 | FROM gobuilder as forego
26 |
27 | ARG FOREGO_VERSION
28 |
29 | RUN git clone https://github.com/nginx-proxy/forego/ \
30 | && cd /go/forego \
31 | && git -c advice.detachedHead=false checkout $FOREGO_VERSION \
32 | && go mod download \
33 | && CGO_ENABLED=0 go build -o forego . \
34 | && go clean -cache \
35 | && mv forego /usr/local/bin/ \
36 | && cd - \
37 | && rm -rf /go/forego
38 |
39 | # Build the final image
40 | FROM nginx:1.21.6-alpine
41 |
42 | ARG NGINX_PROXY_VERSION
43 | # Add DOCKER_GEN_VERSION environment variable
44 | # Because some external projects rely on it
45 | ARG DOCKER_GEN_VERSION
46 | ENV NGINX_PROXY_VERSION=${NGINX_PROXY_VERSION} \
47 | DOCKER_GEN_VERSION=${DOCKER_GEN_VERSION} \
48 | DOCKER_HOST=unix:///tmp/docker.sock
49 |
50 | # Install wget and install/updates certificates
51 | RUN apk add --no-cache --virtual .run-deps \
52 | ca-certificates bash wget openssl \
53 | && update-ca-certificates
54 |
55 | # Configure Nginx
56 | RUN echo "daemon off;" >> /etc/nginx/nginx.conf \
57 | && sed -i 's/worker_processes 1/worker_processes auto/' /etc/nginx/nginx.conf \
58 | && sed -i 's/worker_connections 1024/worker_connections 10240/' /etc/nginx/nginx.conf \
59 | && mkdir -p '/etc/nginx/dhparam'
60 |
61 | # Install Forego + docker-gen
62 | COPY --from=forego /usr/local/bin/forego /usr/local/bin/forego
63 | COPY --from=dockergen /usr/local/bin/docker-gen /usr/local/bin/docker-gen
64 |
65 | COPY network_internal.conf /etc/nginx/
66 |
67 | COPY app nginx.tmpl LICENSE /app/
68 | WORKDIR /app/
69 |
70 | ENTRYPOINT ["/app/docker-entrypoint.sh"]
71 | CMD ["forego", "start", "-r"]
72 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | # setup build arguments for version of dependencies to use
2 | ARG DOCKER_GEN_VERSION=0.9.0
3 | ARG FOREGO_VERSION=v0.17.0
4 |
5 | # Use a specific version of golang to build both binaries
6 | FROM golang:1.18.1 as gobuilder
7 |
8 | # Build docker-gen from scratch
9 | FROM gobuilder as dockergen
10 |
11 | ARG DOCKER_GEN_VERSION
12 |
13 | RUN git clone https://github.com/nginx-proxy/docker-gen \
14 | && cd /go/docker-gen \
15 | && git -c advice.detachedHead=false checkout $DOCKER_GEN_VERSION \
16 | && go mod download \
17 | && CGO_ENABLED=0 GOOS=linux go build -ldflags "-X main.buildVersion=${DOCKER_GEN_VERSION}" ./cmd/docker-gen \
18 | && go clean -cache \
19 | && mv docker-gen /usr/local/bin/ \
20 | && cd - \
21 | && rm -rf /go/docker-gen
22 |
23 | # Build forego from scratch
24 | FROM gobuilder as forego
25 |
26 | ARG FOREGO_VERSION
27 |
28 | RUN git clone https://github.com/nginx-proxy/forego/ \
29 | && cd /go/forego \
30 | && git -c advice.detachedHead=false checkout $FOREGO_VERSION \
31 | && go mod download \
32 | && CGO_ENABLED=0 GOOS=linux go build -o forego . \
33 | && go clean -cache \
34 | && mv forego /usr/local/bin/ \
35 | && cd - \
36 | && rm -rf /go/forego
37 |
38 | # Build the final image
39 | FROM nginx:1.21.6
40 |
41 | ARG NGINX_PROXY_VERSION
42 | # Add DOCKER_GEN_VERSION environment variable
43 | # Because some external projects rely on it
44 | ARG DOCKER_GEN_VERSION
45 | ENV NGINX_PROXY_VERSION=${NGINX_PROXY_VERSION} \
46 | DOCKER_GEN_VERSION=${DOCKER_GEN_VERSION} \
47 | DOCKER_HOST=unix:///tmp/docker.sock
48 |
49 | # Install wget and install/updates certificates
50 | RUN apt-get update \
51 | && apt-get install -y -q --no-install-recommends \
52 | ca-certificates \
53 | wget \
54 | && apt-get clean \
55 | && rm -r /var/lib/apt/lists/*
56 |
57 |
58 | # Configure Nginx
59 | RUN echo "daemon off;" >> /etc/nginx/nginx.conf \
60 | && sed -i 's/worker_processes 1/worker_processes auto/' /etc/nginx/nginx.conf \
61 | && sed -i 's/worker_connections 1024/worker_connections 10240/' /etc/nginx/nginx.conf \
62 | && mkdir -p '/etc/nginx/dhparam'
63 |
64 | # Install Forego + docker-gen
65 | COPY --from=forego /usr/local/bin/forego /usr/local/bin/forego
66 | COPY --from=dockergen /usr/local/bin/docker-gen /usr/local/bin/docker-gen
67 |
68 | COPY network_internal.conf /etc/nginx/
69 |
70 | COPY app nginx.tmpl LICENSE /app/
71 | WORKDIR /app/
72 |
73 | ENTRYPOINT ["/app/docker-entrypoint.sh"]
74 | CMD ["forego", "start", "-r"]
75 |
--------------------------------------------------------------------------------
/test/test_events.py:
--------------------------------------------------------------------------------
1 | """
2 | Test that nginx-proxy detects new containers
3 | """
4 | from time import sleep
5 |
6 | import pytest
7 | from docker.errors import NotFound
8 |
9 |
10 | @pytest.fixture()
11 | def web1(docker_compose):
12 | """
13 | pytest fixture creating a web container with `VIRTUAL_HOST=web1.nginx-proxy` listening on port 81.
14 | """
15 | container = docker_compose.containers.run(
16 | name="web1",
17 | image="web",
18 | detach=True,
19 | environment={
20 | "WEB_PORTS": "81",
21 | "VIRTUAL_HOST": "web1.nginx-proxy"
22 | },
23 | ports={"81/tcp": None}
24 | )
25 | sleep(2) # give it some time to initialize and for docker-gen to detect it
26 | yield container
27 | try:
28 | docker_compose.containers.get("web1").remove(force=True)
29 | except NotFound:
30 | pass
31 |
32 | @pytest.fixture()
33 | def web2(docker_compose):
34 | """
35 | pytest fixture creating a web container with `VIRTUAL_HOST=nginx-proxy`, `VIRTUAL_PATH=/web2/` and `VIRTUAL_DEST=/` listening on port 82.
36 | """
37 | container = docker_compose.containers.run(
38 | name="web2",
39 | image="web",
40 | detach=True,
41 | environment={
42 | "WEB_PORTS": "82",
43 | "VIRTUAL_HOST": "nginx-proxy",
44 | "VIRTUAL_PATH": "/web2/",
45 | "VIRTUAL_DEST": "/",
46 | },
47 | ports={"82/tcp": None}
48 | )
49 | sleep(2) # give it some time to initialize and for docker-gen to detect it
50 | yield container
51 | try:
52 | docker_compose.containers.get("web2").remove(force=True)
53 | except NotFound:
54 | pass
55 |
56 | def test_nginx_proxy_behavior_when_alone(docker_compose, nginxproxy):
57 | r = nginxproxy.get("http://nginx-proxy/")
58 | assert r.status_code == 503
59 |
60 |
61 | def test_new_container_is_detected_vhost(web1, nginxproxy):
62 | r = nginxproxy.get("http://web1.nginx-proxy/port")
63 | assert r.status_code == 200
64 | assert "answer from port 81\n" == r.text
65 |
66 | web1.remove(force=True)
67 | sleep(2)
68 | r = nginxproxy.get("http://web1.nginx-proxy/port")
69 | assert r.status_code == 503
70 |
71 | def test_new_container_is_detected_vpath(web2, nginxproxy):
72 | r = nginxproxy.get("http://nginx-proxy/web2/port")
73 | assert r.status_code == 200
74 | assert "answer from port 82\n" == r.text
75 | r = nginxproxy.get("http://nginx-proxy/port")
76 | assert r.status_code in [404, 503]
77 |
78 | web2.remove(force=True)
79 | sleep(2)
80 | r = nginxproxy.get("http://nginx-proxy/web2/port")
81 | assert r.status_code == 503
82 |
83 |
--------------------------------------------------------------------------------
/test/certs/README.md:
--------------------------------------------------------------------------------
1 | create_server_certificate.sh
2 | ============================
3 |
4 | `create_server_certificate.sh` is a script helping with issuing server certificates that can be used to provide TLS on web servers.
5 |
6 | It also creates a Certificate Authority (CA) root key and certificate. This CA root certificate can be used to validate the server certificates it generates.
7 |
8 | For instance, with _curl_:
9 |
10 | curl --cacert /somewhere/ca-root.crt https://www.example.com/
11 |
12 | or with _wget_:
13 |
14 | wget --certificate=/somewhere/ca-root.crt https://www.example.com/
15 |
16 | or with the python _requests_ module:
17 |
18 | import requests
19 | r = requests.get("https://www.example.com", verify="/somewhere/ca-root.crt")
20 |
21 | Usage
22 | -----
23 |
24 | ### Simple domain
25 |
26 | Create a server certificate for domain `www.example.com`:
27 |
28 | ./create_server_certificate.sh www.example.com
29 |
30 | Will produce:
31 | - `www.example.com.key`
32 | - `www.example.com.crt`
33 |
34 |
35 | ### Multiple domains
36 |
37 | Create a server certificate for main domain `www.example.com` and alternative domains `example.com`, `foo.com` and `bar.com`:
38 |
39 | ./create_server_certificate.sh www.example.com foo.com bar.com
40 |
41 | Will produce:
42 | - `www.example.com.key`
43 | - `www.example.com.crt`
44 |
45 | ### Wildcard domain
46 |
47 | Create a server certificate for wildcard domain `*.example.com`:
48 |
49 | ./create_server_certificate.sh "*.example.com"
50 |
51 | Note that you need to use quotes around the domain string or the shell would expand `*`.
52 |
53 | Will produce:
54 | - `*.example.com.key`
55 | - `*.example.com.crt`
56 |
57 | Again, to prevent your shell from expanding `*`, use quotes. i.e.: `cat "*.example.com.crt"`.
58 |
59 | Such a server certificate would be valid for domains:
60 | - `foo.example.com`
61 | - `bar.example.com`
62 |
63 | but not for domains:
64 | - `example.com`
65 | - `foo.bar.example.com`
66 |
67 |
68 | ### Wildcard domain on multiple levels
69 |
70 | While you can technically create a server certificate for wildcard domain `*.example.com` and alternative name `*.*.example.com`, client implementations generally do not support multiple wildcards in a domain name.
71 |
72 | For instance, a python script using urllib3 would fail to validate domain `foo.bar.example.com` presenting a certificate with name `*.*.example.com`. It is advised to stay away from producing such certificates.
73 |
74 | If you want to give it a try:
75 |
76 | ./create_server_certificate.sh "*.example.com" "*.*.example.com"
77 |
78 | Such a server certificate would be valid for domains:
79 | - `foo.example.com`
80 | - `bar.example.com`
81 | - `foo.bar.example.com`
82 |
--------------------------------------------------------------------------------
/test/stress_tests/test_deleted_cert/test_restart_while_missing_cert.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import os
3 | from os.path import join, isfile
4 | from shutil import copy
5 | from time import sleep
6 |
7 | import pytest
8 | from requests import ConnectionError
9 |
10 | script_dir = os.path.dirname(__file__)
11 |
12 | pytestmark = pytest.mark.xfail() # TODO delete this marker once those issues are fixed
13 |
14 | @pytest.fixture(scope="module", autouse=True)
15 | def certs():
16 | """
17 | pytest fixture that provides cert and key files into the tmp_certs directory
18 | """
19 | file_names = ("web.nginx-proxy.crt", "web.nginx-proxy.key")
20 | logging.info("copying server cert and key files into tmp_certs")
21 | for f_name in file_names:
22 | copy(join(script_dir, "certs", f_name), join(script_dir, "tmp_certs"))
23 | yield
24 | logging.info("cleaning up the tmp_cert directory")
25 | for f_name in file_names:
26 | if isfile(join(script_dir, "tmp_certs", f_name)):
27 | os.remove(join(script_dir, "tmp_certs", f_name))
28 |
29 | ###############################################################################
30 |
31 |
32 | def test_unknown_virtual_host_is_503(docker_compose, nginxproxy):
33 | r = nginxproxy.get("http://foo.nginx-proxy/")
34 | assert r.status_code == 503
35 |
36 |
37 | def test_http_web_is_301(docker_compose, nginxproxy):
38 | r = nginxproxy.get("http://web.nginx-proxy/port", allow_redirects=False)
39 | assert r.status_code == 301
40 |
41 |
42 | def test_https_web_is_200(docker_compose, nginxproxy):
43 | r = nginxproxy.get("https://web.nginx-proxy/port")
44 | assert r.status_code == 200
45 | assert "answer from port 81\n" in r.text
46 |
47 |
48 | @pytest.mark.incremental
49 | def test_delete_cert_and_restart_reverseproxy(docker_compose):
50 | os.remove(join(script_dir, "tmp_certs", "web.nginx-proxy.crt"))
51 | docker_compose.containers.get("reverseproxy").restart()
52 | sleep(3) # give time for the container to initialize
53 | assert "running" == docker_compose.containers.get("reverseproxy").status
54 |
55 |
56 | @pytest.mark.incremental
57 | def test_unknown_virtual_host_is_still_503(nginxproxy):
58 | r = nginxproxy.get("http://foo.nginx-proxy/")
59 | assert r.status_code == 503
60 |
61 |
62 | @pytest.mark.incremental
63 | def test_http_web_is_now_200(nginxproxy):
64 | r = nginxproxy.get("http://web.nginx-proxy/port", allow_redirects=False)
65 | assert r.status_code == 200
66 | assert "answer from port 81\n" == r.text
67 |
68 |
69 | @pytest.mark.incremental
70 | def test_https_web_is_now_broken_since_there_is_no_cert(nginxproxy):
71 | with pytest.raises(ConnectionError):
72 | nginxproxy.get("https://web.nginx-proxy/port")
73 |
--------------------------------------------------------------------------------
/test/stress_tests/test_unreachable_network/README.md:
--------------------------------------------------------------------------------
1 | # nginx-proxy template is not considered when a container is not reachable
2 |
3 | Having a container with the `VIRTUAL_HOST` environment variable set but on a network not reachable from the nginx-proxy container will result in nginx-proxy serving the default nginx welcome page for all requests.
4 |
5 | Furthermore, if the nginx-proxy in such state is restarted, the nginx process will crash and the container stops.
6 |
7 | In the generated nginx config file, we can notice the presence of an empty `upstream {}` block.
8 |
9 | This can be fixed by merging [PR-585](https://github.com/nginx-proxy/nginx-proxy/pull/585).
10 |
11 | ## How to reproduce
12 |
13 | 1. a first web container is created on network `netA`
14 | 1. a second web container is created on network `netB`
15 | 1. nginx-proxy is created with access to `netA` only
16 |
17 |
18 | ## Erratic behavior
19 |
20 | - nginx serves the default welcome page for all requests to `/` and error 404 for any other path
21 | - nginx-container crash on restart
22 |
23 | Log shows:
24 |
25 | ```
26 | webB_1 | starting a web server listening on port 82
27 | webA_1 | starting a web server listening on port 81
28 | reverseproxy | forego | starting dockergen.1 on port 5000
29 | reverseproxy | forego | starting nginx.1 on port 5100
30 | reverseproxy | dockergen.1 | 2017/02/20 01:10:24 Generated '/etc/nginx/conf.d/default.conf' from 3 containers
31 | reverseproxy | dockergen.1 | 2017/02/20 01:10:24 Running 'nginx -s reload'
32 | reverseproxy | dockergen.1 | 2017/02/20 01:10:24 Error running notify command: nginx -s reload, exit status 1
33 | reverseproxy | dockergen.1 | 2017/02/20 01:10:24 Watching docker events
34 | reverseproxy | dockergen.1 | 2017/02/20 01:10:24 Contents of /etc/nginx/conf.d/default.conf did not change. Skipping notification 'nginx -s reload'
35 | reverseproxy | reverseproxy | forego | starting dockergen.1 on port 5000 <---- nginx-proxy container restarted
36 | reverseproxy | forego | starting nginx.1 on port 5100
37 | reverseproxy | dockergen.1 | 2017/02/20 01:10:24 Generated '/etc/nginx/conf.d/default.conf' from 3 containers
38 | reverseproxy | dockergen.1 | 2017/02/20 01:10:24 Running 'nginx -s reload'
39 | reverseproxy | dockergen.1 | 2017/02/20 01:10:24 Error running notify command: nginx -s reload, exit status 1
40 | reverseproxy | dockergen.1 | 2017/02/20 01:10:24 Watching docker events
41 | reverseproxy | dockergen.1 | 2017/02/20 01:10:24 Contents of /etc/nginx/conf.d/default.conf did not change. Skipping notification 'nginx -s reload'
42 | reverseproxy | forego | starting dockergen.1 on port 5000
43 | reverseproxy | forego | starting nginx.1 on port 5100
44 | reverseproxy | nginx.1 | 2017/02/20 01:11:02 [emerg] 17#17: no servers are inside upstream in /etc/nginx/conf.d/default.conf:64
45 | reverseproxy | forego | starting nginx.1 on port 5200
46 | reverseproxy | forego | sending SIGTERM to nginx.1
47 | reverseproxy | forego | sending SIGTERM to dockergen.1
48 | reverseproxy exited with code 0
49 | reverseproxy exited with code 0
50 |
51 | ```
52 |
53 | ## Expected behavior
54 |
55 | - no default nginx welcome page should be served
56 | - nginx is able to forward requests to containers of `netA`
57 | - nginx respond with error 503 for unknown virtual hosts
58 | - nginx is not able to forward requests to containers of `netB` and responds with an error
59 | - nginx should survive restarts
60 |
--------------------------------------------------------------------------------
/app/docker-entrypoint.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | function _parse_true() {
5 | case "$1" in
6 |
7 | true | True | TRUE | 1)
8 | return 0
9 | ;;
10 |
11 | *)
12 | return 1
13 | ;;
14 |
15 | esac
16 | }
17 |
18 | function _parse_false() {
19 | case "$1" in
20 |
21 | false | False | FALSE | 0)
22 | return 0
23 | ;;
24 |
25 | *)
26 | return 1
27 | ;;
28 |
29 | esac
30 | }
31 |
32 | function _print_version {
33 | if [[ -n "${NGINX_PROXY_VERSION:-}" ]]; then
34 | echo "Info: running nginx-proxy version ${NGINX_PROXY_VERSION}"
35 | fi
36 | }
37 |
38 | function _check_unix_socket() {
39 | # Warn if the DOCKER_HOST socket does not exist
40 | if [[ ${DOCKER_HOST} == unix://* ]]; then
41 | local SOCKET_FILE="${DOCKER_HOST#unix://}"
42 |
43 | if [[ ! -S ${SOCKET_FILE} ]]; then
44 | cat >&2 <<-EOT
45 | ERROR: you need to share your Docker host socket with a volume at ${SOCKET_FILE}
46 | Typically you should run your nginxproxy/nginx-proxy with: \`-v /var/run/docker.sock:${SOCKET_FILE}:ro\`
47 | See the documentation at: https://github.com/nginx-proxy/nginx-proxy/#usage
48 | EOT
49 |
50 | exit 1
51 | fi
52 | fi
53 | }
54 |
55 | function _resolvers() {
56 | # Compute the DNS resolvers for use in the templates - if the IP contains ":", it's IPv6 and must be enclosed in []
57 | RESOLVERS=$(awk '$1 == "nameserver" {print ($2 ~ ":")? "["$2"]": $2}' ORS=' ' /etc/resolv.conf | sed 's/ *$//g'); export RESOLVERS
58 |
59 | SCOPED_IPV6_REGEX='\[fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}\]'
60 |
61 | if [[ -z ${RESOLVERS} ]]; then
62 | echo 'Warning: unable to determine DNS resolvers for nginx' >&2
63 | unset RESOLVERS
64 | elif [[ ${RESOLVERS} =~ ${SCOPED_IPV6_REGEX} ]]; then
65 | echo -n 'Warning: Scoped IPv6 addresses removed from resolvers: ' >&2
66 | echo "${RESOLVERS}" | grep -Eo "$SCOPED_IPV6_REGEX" | paste -s -d ' ' >&2
67 | RESOLVERS=$(echo "${RESOLVERS}" | sed -r "s/${SCOPED_IPV6_REGEX}//g" | xargs echo -n); export RESOLVERS
68 | fi
69 | }
70 |
71 | function _setup_dhparam() {
72 | # DH params will be supplied for nginx here:
73 | local DHPARAM_FILE='/etc/nginx/dhparam/dhparam.pem'
74 |
75 | # Should be 2048, 3072, or 4096 (default):
76 | local FFDHE_GROUP="${DHPARAM_BITS:=4096}"
77 |
78 | # DH params may be provided by the user (rarely necessary)
79 | if [[ -f ${DHPARAM_FILE} ]]; then
80 | echo 'Warning: A custom dhparam.pem file was provided. Best practice is to use standardized RFC7919 DHE groups instead.' >&2
81 | return 0
82 | elif _parse_true "${DHPARAM_SKIP:=false}"; then
83 | echo 'Skipping Diffie-Hellman parameters setup.'
84 | return 0
85 | elif _parse_false "${DHPARAM_GENERATION:=true}"; then
86 | echo 'Warning: The DHPARAM_GENERATION environment variable is deprecated, please consider using DHPARAM_SKIP set to true instead.' >&2
87 | echo 'Skipping Diffie-Hellman parameters setup.'
88 | return 0
89 | elif [[ ! ${DHPARAM_BITS} =~ ^(2048|3072|4096)$ ]]; then
90 | echo "ERROR: Unsupported DHPARAM_BITS size: ${DHPARAM_BITS}. Use: 2048, 3072, or 4096 (default)." >&2
91 | exit 1
92 | fi
93 |
94 | echo 'Setting up DH Parameters..'
95 |
96 | # Use an existing pre-generated DH group from RFC7919 (https://datatracker.ietf.org/doc/html/rfc7919#appendix-A):
97 | local RFC7919_DHPARAM_FILE="/app/dhparam/ffdhe${FFDHE_GROUP}.pem"
98 |
99 | # Provide the DH params file to nginx:
100 | cp "${RFC7919_DHPARAM_FILE}" "${DHPARAM_FILE}"
101 | }
102 |
103 | # Run the init logic if the default CMD was provided
104 | if [[ $* == 'forego start -r' ]]; then
105 | _print_version
106 |
107 | _check_unix_socket
108 |
109 | _resolvers
110 |
111 | _setup_dhparam
112 | fi
113 |
114 | exec "$@"
115 |
--------------------------------------------------------------------------------
/test/test_ssl/certs/nginx-proxy.tld.crt:
--------------------------------------------------------------------------------
1 | Certificate:
2 | Data:
3 | Version: 3 (0x2)
4 | Serial Number: 4096 (0x1000)
5 | Signature Algorithm: sha256WithRSAEncryption
6 | Issuer: O=nginx-proxy test suite, CN=www.nginx-proxy.tld
7 | Validity
8 | Not Before: Jan 10 00:08:52 2017 GMT
9 | Not After : May 28 00:08:52 2044 GMT
10 | Subject: CN=*.nginx-proxy.tld
11 | Subject Public Key Info:
12 | Public Key Algorithm: rsaEncryption
13 | Public-Key: (2048 bit)
14 | Modulus:
15 | 00:cb:45:f4:14:9b:fe:64:85:79:4a:36:8d:3d:d1:
16 | 27:d0:7c:36:28:30:e6:73:80:6f:7c:49:23:d0:6c:
17 | 17:e4:44:c0:77:4d:9a:c2:bc:24:84:e3:a5:4d:ba:
18 | d2:da:51:7b:a1:2a:12:d4:c0:19:55:69:2c:22:27:
19 | 2d:1a:f6:fc:4b:7f:e9:cb:a8:3c:e8:69:b8:d2:4f:
20 | de:4e:50:e2:d0:74:30:7c:42:5a:ae:aa:85:a5:b1:
21 | 71:4d:c9:7e:86:8b:62:8c:3e:0d:e3:3b:c3:f5:81:
22 | 0b:8c:68:79:fe:bf:10:fb:ae:ec:11:49:6d:64:5e:
23 | 1a:7d:b3:92:93:4e:96:19:3a:98:04:a7:66:b2:74:
24 | 61:2d:41:13:0c:a4:54:0d:2c:78:fd:b4:a3:e8:37:
25 | 78:9a:de:fa:bc:2e:a8:0f:67:14:58:ce:c3:87:d5:
26 | 14:0e:8b:29:7d:48:19:b2:a9:f5:b4:e8:af:32:21:
27 | 67:15:7e:43:52:8b:20:cf:9f:38:43:bf:fd:c8:24:
28 | 7f:52:a3:88:f2:f1:4a:14:91:2a:6e:91:6f:fb:7d:
29 | 6a:78:c6:6d:2e:dd:1e:4c:2b:63:bb:3a:43:9c:91:
30 | f9:df:d3:08:13:63:86:7d:ce:e8:46:cf:f1:6c:1f:
31 | ca:f7:4c:de:d8:4b:e0:da:bc:06:d9:87:0f:ff:96:
32 | 45:85
33 | Exponent: 65537 (0x10001)
34 | X509v3 extensions:
35 | X509v3 Subject Alternative Name:
36 | DNS:*.nginx-proxy.tld
37 | Signature Algorithm: sha256WithRSAEncryption
38 | 6e:a5:0e:e4:d3:cc:d5:b7:fc:34:75:89:4e:98:8c:e7:08:06:
39 | a8:5b:ec:13:7d:83:99:a2:61:b8:d5:12:6e:c5:b4:53:4e:9a:
40 | 22:cd:ad:14:30:6a:7d:58:d7:23:d9:a4:2a:96:a0:40:9e:50:
41 | 9f:ce:f2:fe:8c:dd:9a:ac:99:39:5b:89:2d:ca:e5:3e:c3:bc:
42 | 03:04:1c:12:d9:6e:b8:9f:f0:3a:be:12:44:7e:a4:21:86:73:
43 | af:d5:00:51:3f:2c:56:70:34:8f:26:b0:7f:b0:cf:cf:7f:f9:
44 | 40:6f:00:29:c4:cf:c3:b7:c2:49:3d:3f:b0:26:78:87:b9:c7:
45 | 6c:1b:aa:6a:1a:dd:c5:eb:f2:69:ba:6d:46:0b:92:49:b5:11:
46 | 3c:eb:48:c7:2f:fb:33:a6:6a:82:a2:ab:f8:1e:5f:7d:e3:b7:
47 | f2:fd:f5:88:a5:09:4d:a0:bc:f4:3b:cd:d2:8b:d7:57:1f:86:
48 | 3b:d2:3e:a4:92:21:b0:02:0b:e9:e0:c4:1c:f1:78:e2:58:a7:
49 | 26:5f:4c:29:c8:23:f0:6e:12:3f:bd:ad:44:7b:0b:bd:db:ba:
50 | 63:8d:07:c6:9d:dc:46:cc:63:40:ba:5e:45:82:dd:9a:e5:50:
51 | e8:e7:d7:27:88:fc:6f:1d:8a:e7:5c:49:28:aa:10:29:75:28:
52 | c7:52:de:f9
53 | -----BEGIN CERTIFICATE-----
54 | MIIC9zCCAd+gAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwPzEfMB0GA1UECgwWbmdp
55 | bngtcHJveHkgdGVzdCBzdWl0ZTEcMBoGA1UEAwwTd3d3Lm5naW54LXByb3h5LnRs
56 | ZDAeFw0xNzAxMTAwMDA4NTJaFw00NDA1MjgwMDA4NTJaMBwxGjAYBgNVBAMMESou
57 | bmdpbngtcHJveHkudGxkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
58 | y0X0FJv+ZIV5SjaNPdEn0Hw2KDDmc4BvfEkj0GwX5ETAd02awrwkhOOlTbrS2lF7
59 | oSoS1MAZVWksIictGvb8S3/py6g86Gm40k/eTlDi0HQwfEJarqqFpbFxTcl+hoti
60 | jD4N4zvD9YELjGh5/r8Q+67sEUltZF4afbOSk06WGTqYBKdmsnRhLUETDKRUDSx4
61 | /bSj6Dd4mt76vC6oD2cUWM7Dh9UUDospfUgZsqn1tOivMiFnFX5DUosgz584Q7/9
62 | yCR/UqOI8vFKFJEqbpFv+31qeMZtLt0eTCtjuzpDnJH539MIE2OGfc7oRs/xbB/K
63 | 90ze2Evg2rwG2YcP/5ZFhQIDAQABoyAwHjAcBgNVHREEFTATghEqLm5naW54LXBy
64 | b3h5LnRsZDANBgkqhkiG9w0BAQsFAAOCAQEAbqUO5NPM1bf8NHWJTpiM5wgGqFvs
65 | E32DmaJhuNUSbsW0U06aIs2tFDBqfVjXI9mkKpagQJ5Qn87y/ozdmqyZOVuJLcrl
66 | PsO8AwQcEtluuJ/wOr4SRH6kIYZzr9UAUT8sVnA0jyawf7DPz3/5QG8AKcTPw7fC
67 | ST0/sCZ4h7nHbBuqahrdxevyabptRguSSbURPOtIxy/7M6ZqgqKr+B5ffeO38v31
68 | iKUJTaC89DvN0ovXVx+GO9I+pJIhsAIL6eDEHPF44linJl9MKcgj8G4SP72tRHsL
69 | vdu6Y40Hxp3cRsxjQLpeRYLdmuVQ6OfXJ4j8bx2K51xJKKoQKXUox1Le+Q==
70 | -----END CERTIFICATE-----
71 |
--------------------------------------------------------------------------------
/test/test_ssl/wildcard_cert_and_nohttps/certs/default.crt:
--------------------------------------------------------------------------------
1 | Certificate:
2 | Data:
3 | Version: 3 (0x2)
4 | Serial Number: 4096 (0x1000)
5 | Signature Algorithm: sha256WithRSAEncryption
6 | Issuer: O=nginx-proxy test suite, CN=www.nginx-proxy.tld
7 | Validity
8 | Not Before: Mar 15 00:17:52 2017 GMT
9 | Not After : Jul 31 00:17:52 2044 GMT
10 | Subject: CN=nginx-proxy.tld
11 | Subject Public Key Info:
12 | Public Key Algorithm: rsaEncryption
13 | Public-Key: (2048 bit)
14 | Modulus:
15 | 00:f2:fd:79:70:99:0c:da:63:5c:81:28:72:31:01:
16 | 62:e9:68:d7:cb:8d:c6:95:f9:ec:26:34:1c:08:c6:
17 | 6d:de:ad:d8:b0:c0:ae:48:03:73:76:6b:3f:c5:35:
18 | 86:c6:42:91:53:3c:aa:85:89:84:92:67:92:ef:a9:
19 | 5b:f2:d4:04:73:34:02:35:d4:6a:fa:c2:da:91:4a:
20 | a9:70:87:25:38:84:1d:93:99:3c:d7:03:61:a6:6d:
21 | 33:6f:83:45:04:af:4f:96:62:1e:c1:79:87:c9:d5:
22 | 4c:e9:8f:85:e2:c8:1b:5b:fc:b8:02:ff:7b:6d:34:
23 | 4c:5d:40:73:44:9e:c5:1f:5f:e0:0f:89:88:c4:35:
24 | 2b:04:53:8c:8e:a0:7c:7c:97:16:20:c2:4f:a1:c0:
25 | dd:bf:d5:13:2d:64:25:03:f2:d8:d5:27:01:70:c9:
26 | f4:37:33:36:7e:7b:48:54:ec:37:2b:81:3d:50:3c:
27 | d4:5f:05:19:e2:0b:ba:76:f6:2c:3b:23:4b:82:78:
28 | 5f:e9:e3:57:fc:39:4a:5c:42:82:72:c8:a3:af:b7:
29 | b3:91:e4:01:9c:2c:47:5e:ff:aa:ad:63:1c:e7:9c:
30 | 2e:a2:ac:5d:51:30:83:67:6e:f8:5a:ed:0b:70:e4:
31 | 68:d4:e9:5e:a7:f5:5e:87:3b:e8:31:ad:00:04:f8:
32 | 7b:d9
33 | Exponent: 65537 (0x10001)
34 | X509v3 extensions:
35 | X509v3 Subject Alternative Name:
36 | DNS:nginx-proxy.tld
37 | Signature Algorithm: sha256WithRSAEncryption
38 | 39:d4:cc:78:a3:5e:64:e9:ab:9d:a9:89:3b:9e:18:01:98:cb:
39 | e2:0c:ef:e9:2b:50:34:ed:63:ed:e6:0e:53:59:30:80:e0:3b:
40 | 5e:08:ca:09:55:da:e3:3e:c2:01:d8:d6:ca:92:2a:0b:ee:2c:
41 | a1:93:18:7b:15:28:8d:2a:17:25:76:eb:ef:70:e0:d7:02:d3:
42 | ad:81:33:47:9b:fb:d8:52:87:69:a4:3a:20:a4:9a:2d:3f:40:
43 | 5f:52:bf:0b:96:e3:52:c3:59:55:dc:5a:37:f3:e6:d6:16:46:
44 | 64:e4:20:32:5d:cd:4b:da:2b:ef:e9:85:af:00:a1:ca:a1:08:
45 | ed:0f:f4:65:dc:2a:c9:b3:4e:cc:f3:82:d7:69:3a:4d:fc:8e:
46 | db:10:95:28:20:07:55:f0:d1:11:1f:c5:00:74:88:c6:c9:94:
47 | 15:90:93:3a:de:90:85:fb:72:9c:d8:57:58:05:7d:bb:6a:36:
48 | eb:d8:12:22:41:0e:fc:c9:24:79:c0:28:4f:4f:1b:4b:59:f9:
49 | e4:c6:97:be:b1:94:74:de:a7:65:d3:cb:0a:56:3b:d3:63:fc:
50 | b2:05:fc:e7:ec:bb:45:04:91:9f:21:f9:05:3b:5d:4c:af:8e:
51 | 84:04:f5:25:fb:4d:ab:db:23:56:74:7e:4f:b3:da:bb:27:e7:
52 | ea:fb:bd:00
53 | -----BEGIN CERTIFICATE-----
54 | MIIC8zCCAdugAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwPzEfMB0GA1UECgwWbmdp
55 | bngtcHJveHkgdGVzdCBzdWl0ZTEcMBoGA1UEAwwTd3d3Lm5naW54LXByb3h5LnRs
56 | ZDAeFw0xNzAzMTUwMDE3NTJaFw00NDA3MzEwMDE3NTJaMBoxGDAWBgNVBAMMD25n
57 | aW54LXByb3h5LnRsZDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPL9
58 | eXCZDNpjXIEocjEBYulo18uNxpX57CY0HAjGbd6t2LDArkgDc3ZrP8U1hsZCkVM8
59 | qoWJhJJnku+pW/LUBHM0AjXUavrC2pFKqXCHJTiEHZOZPNcDYaZtM2+DRQSvT5Zi
60 | HsF5h8nVTOmPheLIG1v8uAL/e200TF1Ac0SexR9f4A+JiMQ1KwRTjI6gfHyXFiDC
61 | T6HA3b/VEy1kJQPy2NUnAXDJ9DczNn57SFTsNyuBPVA81F8FGeILunb2LDsjS4J4
62 | X+njV/w5SlxCgnLIo6+3s5HkAZwsR17/qq1jHOecLqKsXVEwg2du+FrtC3DkaNTp
63 | Xqf1Xoc76DGtAAT4e9kCAwEAAaMeMBwwGgYDVR0RBBMwEYIPbmdpbngtcHJveHku
64 | dGxkMA0GCSqGSIb3DQEBCwUAA4IBAQA51Mx4o15k6audqYk7nhgBmMviDO/pK1A0
65 | 7WPt5g5TWTCA4DteCMoJVdrjPsIB2NbKkioL7iyhkxh7FSiNKhclduvvcODXAtOt
66 | gTNHm/vYUodppDogpJotP0BfUr8LluNSw1lV3Fo38+bWFkZk5CAyXc1L2ivv6YWv
67 | AKHKoQjtD/Rl3CrJs07M84LXaTpN/I7bEJUoIAdV8NERH8UAdIjGyZQVkJM63pCF
68 | +3Kc2FdYBX27ajbr2BIiQQ78ySR5wChPTxtLWfnkxpe+sZR03qdl08sKVjvTY/yy
69 | Bfzn7LtFBJGfIfkFO11Mr46EBPUl+02r2yNWdH5Ps9q7J+fq+70A
70 | -----END CERTIFICATE-----
71 |
--------------------------------------------------------------------------------
/test/stress_tests/test_deleted_cert/certs/web.nginx-proxy.crt:
--------------------------------------------------------------------------------
1 | Certificate:
2 | Data:
3 | Version: 3 (0x2)
4 | Serial Number: 4096 (0x1000)
5 | Signature Algorithm: sha256WithRSAEncryption
6 | Issuer: O=nginx-proxy test suite, CN=www.nginx-proxy.tld
7 | Validity
8 | Not Before: Feb 17 23:20:54 2017 GMT
9 | Not After : Jul 5 23:20:54 2044 GMT
10 | Subject: CN=web.nginx-proxy
11 | Subject Public Key Info:
12 | Public Key Algorithm: rsaEncryption
13 | Public-Key: (2048 bit)
14 | Modulus:
15 | 00:b6:27:63:a5:c6:e8:f4:7a:94:0e:cc:a2:62:76:
16 | 6d:5d:33:6f:cf:19:fc:e7:e5:bb:0e:0e:d0:7c:4f:
17 | 73:4c:48:2b:17:d1:4d:d5:9f:42:08:73:84:54:8c:
18 | 86:d2:c5:da:59:01:3f:42:22:e0:36:f0:dc:ab:de:
19 | 0a:bd:26:2b:22:13:87:a6:1f:23:ef:0e:99:27:8b:
20 | 15:4a:1b:ef:93:c9:6b:91:de:a0:02:0c:62:bb:cc:
21 | 56:37:e8:25:92:c3:1f:f1:69:d8:7c:a8:33:e0:89:
22 | ce:14:67:a0:39:77:88:91:e6:a3:07:97:90:22:88:
23 | d0:79:18:63:fb:6f:7e:ee:2b:42:7e:23:f5:e7:da:
24 | e9:ee:6a:fa:96:65:9f:e1:2b:15:49:c8:cd:2d:ce:
25 | 86:4f:2c:2a:67:79:bf:41:30:14:cc:f6:0f:14:74:
26 | 9e:b6:d3:d0:3b:f0:1b:b8:e8:19:2a:fd:d6:fd:dc:
27 | 4b:4e:65:7d:9b:bf:37:7e:2d:35:22:2e:74:90:ce:
28 | 41:35:3d:41:a0:99:db:97:1f:bf:3e:18:3c:48:fb:
29 | da:df:c6:4e:4e:b9:67:b8:10:d5:a5:13:03:c4:b7:
30 | 65:e7:aa:f0:14:4b:d3:4d:ea:fe:8f:69:cf:50:21:
31 | 63:27:cf:9e:4c:67:15:7b:3f:3b:da:cb:17:80:61:
32 | 1e:25
33 | Exponent: 65537 (0x10001)
34 | X509v3 extensions:
35 | X509v3 Subject Alternative Name:
36 | DNS:web.nginx-proxy
37 | Signature Algorithm: sha256WithRSAEncryption
38 | 09:31:be:db:4e:b0:b6:68:da:ae:5b:16:51:29:fc:9f:61:b6:
39 | 5a:2f:3c:35:ef:67:76:97:b0:34:4e:3b:b4:d6:88:19:4f:84:
40 | 2e:73:d3:c0:3a:4c:41:54:6c:bb:67:89:67:ad:25:55:d7:d4:
41 | 80:fe:a7:3f:3d:9e:f1:34:96:d8:da:5a:78:51:c0:63:f1:52:
42 | 29:35:55:f4:7d:70:1c:d3:96:62:7f:64:86:81:52:27:c4:c6:
43 | 10:13:c6:73:56:4d:32:d0:b3:c3:c8:2c:25:83:e4:2b:1d:d4:
44 | 74:30:e5:85:af:2d:b6:a5:6b:fe:5d:d3:3c:00:58:94:f4:6a:
45 | f5:a6:1d:cf:f9:ed:d5:27:ed:13:24:b2:4f:2b:f3:b8:e4:af:
46 | 0c:1d:fe:e0:6a:01:5e:a2:44:ff:3e:96:fa:6c:39:a3:51:37:
47 | f3:72:55:d8:2d:29:6e:de:95:b9:d8:e3:1e:65:a5:9c:0d:79:
48 | 2d:39:ab:c7:ac:16:b6:a5:71:4b:35:a4:6c:72:47:1b:72:9c:
49 | 67:58:c1:fc:f6:7f:a7:73:50:7b:d6:27:57:74:a1:31:38:a7:
50 | 31:e3:b9:d4:c9:45:33:ec:ed:16:cf:c5:bd:d0:03:b1:45:3f:
51 | 68:0d:91:5c:26:4e:37:05:74:ed:3e:75:5e:ca:5e:ee:e2:51:
52 | 4b:da:08:99
53 | -----BEGIN CERTIFICATE-----
54 | MIIC8zCCAdugAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwPzEfMB0GA1UECgwWbmdp
55 | bngtcHJveHkgdGVzdCBzdWl0ZTEcMBoGA1UEAwwTd3d3Lm5naW54LXByb3h5LnRs
56 | ZDAeFw0xNzAyMTcyMzIwNTRaFw00NDA3MDUyMzIwNTRaMBoxGDAWBgNVBAMMD3dl
57 | Yi5uZ2lueC1wcm94eTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALYn
58 | Y6XG6PR6lA7MomJ2bV0zb88Z/Ofluw4O0HxPc0xIKxfRTdWfQghzhFSMhtLF2lkB
59 | P0Ii4Dbw3KveCr0mKyITh6YfI+8OmSeLFUob75PJa5HeoAIMYrvMVjfoJZLDH/Fp
60 | 2HyoM+CJzhRnoDl3iJHmoweXkCKI0HkYY/tvfu4rQn4j9efa6e5q+pZln+ErFUnI
61 | zS3Ohk8sKmd5v0EwFMz2DxR0nrbT0DvwG7joGSr91v3cS05lfZu/N34tNSIudJDO
62 | QTU9QaCZ25cfvz4YPEj72t/GTk65Z7gQ1aUTA8S3Zeeq8BRL003q/o9pz1AhYyfP
63 | nkxnFXs/O9rLF4BhHiUCAwEAAaMeMBwwGgYDVR0RBBMwEYIPd2ViLm5naW54LXBy
64 | b3h5MA0GCSqGSIb3DQEBCwUAA4IBAQAJMb7bTrC2aNquWxZRKfyfYbZaLzw172d2
65 | l7A0Tju01ogZT4Quc9PAOkxBVGy7Z4lnrSVV19SA/qc/PZ7xNJbY2lp4UcBj8VIp
66 | NVX0fXAc05Zif2SGgVInxMYQE8ZzVk0y0LPDyCwlg+QrHdR0MOWFry22pWv+XdM8
67 | AFiU9Gr1ph3P+e3VJ+0TJLJPK/O45K8MHf7gagFeokT/Ppb6bDmjUTfzclXYLSlu
68 | 3pW52OMeZaWcDXktOavHrBa2pXFLNaRsckcbcpxnWMH89n+nc1B71idXdKExOKcx
69 | 47nUyUUz7O0Wz8W90AOxRT9oDZFcJk43BXTtPnVeyl7u4lFL2giZ
70 | -----END CERTIFICATE-----
71 |
--------------------------------------------------------------------------------
/test/test_headers/certs/web.nginx-proxy.tld.crt:
--------------------------------------------------------------------------------
1 | Certificate:
2 | Data:
3 | Version: 3 (0x2)
4 | Serial Number: 4096 (0x1000)
5 | Signature Algorithm: sha256WithRSAEncryption
6 | Issuer: O=nginx-proxy test suite, CN=www.nginx-proxy.tld
7 | Validity
8 | Not Before: Jan 13 03:06:39 2017 GMT
9 | Not After : May 31 03:06:39 2044 GMT
10 | Subject: CN=web.nginx-proxy.tld
11 | Subject Public Key Info:
12 | Public Key Algorithm: rsaEncryption
13 | Public-Key: (2048 bit)
14 | Modulus:
15 | 00:95:56:c7:0d:48:a5:2b:3c:65:49:3f:26:e1:38:
16 | 2b:61:30:56:e4:92:d7:63:e0:eb:ad:ac:f9:33:9b:
17 | b2:31:f1:39:13:0b:e5:43:7b:c5:bd:8a:85:c8:d9:
18 | 3d:d8:ac:71:ba:16:e7:81:96:b2:ab:ae:c6:c0:bd:
19 | be:a7:d1:96:8f:b2:9b:df:ba:f9:4d:a1:3b:7e:21:
20 | 4a:cd:b6:45:f9:6d:79:50:bf:24:8f:c1:6b:c1:09:
21 | 19:5b:62:cb:96:e8:04:14:20:e8:d4:16:62:6a:f2:
22 | 37:c1:96:e2:9d:53:05:0b:52:1d:e7:68:92:db:8b:
23 | 36:68:cd:8d:5b:02:ff:12:f0:ac:5d:0c:c4:e0:7a:
24 | 55:a2:49:60:9f:ff:47:1f:52:73:55:4d:d4:f2:d1:
25 | 62:a2:f4:50:9d:c9:f6:f1:43:b3:dc:57:e1:31:76:
26 | b4:e0:a4:69:7e:f2:6d:34:ae:b9:8d:74:26:7b:d9:
27 | f6:07:00:ef:4b:36:61:b3:ef:7a:a1:36:3a:b6:d0:
28 | 9e:f8:b8:a9:0d:4c:30:a2:ed:eb:ab:6b:eb:2e:e2:
29 | 0b:28:be:f7:04:b1:e9:e0:84:d6:5d:31:77:7c:dc:
30 | d2:1f:d4:1d:71:6f:6f:6c:6d:1b:bf:31:e2:5b:c3:
31 | 52:d0:14:fc:8b:fb:45:ea:41:ec:ca:c7:3b:67:12:
32 | c4:df
33 | Exponent: 65537 (0x10001)
34 | X509v3 extensions:
35 | X509v3 Subject Alternative Name:
36 | DNS:web.nginx-proxy.tld
37 | Signature Algorithm: sha256WithRSAEncryption
38 | 4e:48:7d:81:66:ba:2f:50:3d:24:42:61:3f:1f:de:cf:ec:1b:
39 | 1b:bd:0a:67:b6:62:c8:79:9d:31:a0:fd:a9:61:ce:ff:69:bf:
40 | 0e:f4:f7:e6:15:2b:b0:f0:e4:f2:f4:d2:8f:74:02:b1:1e:4a:
41 | a8:6f:26:0a:77:32:29:cf:dc:b5:61:82:3e:58:47:61:92:f0:
42 | 0c:20:25:f8:41:4d:34:09:44:bc:39:9e:aa:82:06:83:13:8b:
43 | 1e:2c:3d:cf:cd:1a:f7:77:39:38:e0:a3:a7:f3:09:da:02:8d:
44 | 73:75:38:b4:dd:24:a7:f9:03:db:98:c6:88:54:87:dc:e0:65:
45 | 4c:95:c5:39:9c:00:30:dc:f0:d3:2c:19:ca:f1:f4:6c:c6:d9:
46 | b5:c4:4a:c7:bc:a1:2e:88:7b:b5:33:d0:ff:fb:48:5e:3e:29:
47 | fa:58:e5:03:de:d8:17:de:ed:96:fc:7e:1f:fe:98:f6:be:99:
48 | 38:87:51:c0:d3:b7:9a:0f:26:92:e5:53:1b:d6:25:4c:ac:48:
49 | f3:29:fc:74:64:9d:07:6a:25:57:24:aa:a7:70:fa:8f:6c:a7:
50 | 2b:b7:9d:81:46:10:32:93:b9:45:6d:0f:16:18:b2:21:1f:f3:
51 | 30:24:62:3f:e1:6c:07:1d:71:28:cb:4c:bb:f5:39:05:f9:b2:
52 | 5b:a0:05:1b
53 | -----BEGIN CERTIFICATE-----
54 | MIIC+zCCAeOgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwPzEfMB0GA1UECgwWbmdp
55 | bngtcHJveHkgdGVzdCBzdWl0ZTEcMBoGA1UEAwwTd3d3Lm5naW54LXByb3h5LnRs
56 | ZDAeFw0xNzAxMTMwMzA2MzlaFw00NDA1MzEwMzA2MzlaMB4xHDAaBgNVBAMME3dl
57 | Yi5uZ2lueC1wcm94eS50bGQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
58 | AQCVVscNSKUrPGVJPybhOCthMFbkktdj4OutrPkzm7Ix8TkTC+VDe8W9ioXI2T3Y
59 | rHG6FueBlrKrrsbAvb6n0ZaPspvfuvlNoTt+IUrNtkX5bXlQvySPwWvBCRlbYsuW
60 | 6AQUIOjUFmJq8jfBluKdUwULUh3naJLbizZozY1bAv8S8KxdDMTgelWiSWCf/0cf
61 | UnNVTdTy0WKi9FCdyfbxQ7PcV+ExdrTgpGl+8m00rrmNdCZ72fYHAO9LNmGz73qh
62 | Njq20J74uKkNTDCi7eura+su4gsovvcEsenghNZdMXd83NIf1B1xb29sbRu/MeJb
63 | w1LQFPyL+0XqQezKxztnEsTfAgMBAAGjIjAgMB4GA1UdEQQXMBWCE3dlYi5uZ2lu
64 | eC1wcm94eS50bGQwDQYJKoZIhvcNAQELBQADggEBAE5IfYFmui9QPSRCYT8f3s/s
65 | Gxu9Cme2Ysh5nTGg/alhzv9pvw709+YVK7Dw5PL00o90ArEeSqhvJgp3MinP3LVh
66 | gj5YR2GS8AwgJfhBTTQJRLw5nqqCBoMTix4sPc/NGvd3OTjgo6fzCdoCjXN1OLTd
67 | JKf5A9uYxohUh9zgZUyVxTmcADDc8NMsGcrx9GzG2bXESse8oS6Ie7Uz0P/7SF4+
68 | KfpY5QPe2Bfe7Zb8fh/+mPa+mTiHUcDTt5oPJpLlUxvWJUysSPMp/HRknQdqJVck
69 | qqdw+o9spyu3nYFGEDKTuUVtDxYYsiEf8zAkYj/hbAcdcSjLTLv1OQX5slugBRs=
70 | -----END CERTIFICATE-----
71 |
--------------------------------------------------------------------------------
/test/test_ssl/certs/web2.nginx-proxy.tld.crt:
--------------------------------------------------------------------------------
1 | Certificate:
2 | Data:
3 | Version: 3 (0x2)
4 | Serial Number: 4096 (0x1000)
5 | Signature Algorithm: sha256WithRSAEncryption
6 | Issuer: O=nginx-proxy test suite, CN=www.nginx-proxy.tld
7 | Validity
8 | Not Before: Jan 10 00:37:02 2017 GMT
9 | Not After : May 28 00:37:02 2044 GMT
10 | Subject: CN=web2.nginx-proxy.tld
11 | Subject Public Key Info:
12 | Public Key Algorithm: rsaEncryption
13 | Public-Key: (2048 bit)
14 | Modulus:
15 | 00:da:ee:46:2d:44:7c:f1:e6:91:11:bf:34:d6:02:
16 | 4e:fe:43:23:fb:6d:20:f7:8d:1b:c6:9c:cd:1c:1a:
17 | 07:95:c2:ed:b9:23:73:c1:02:2b:50:51:3f:33:cf:
18 | 8e:aa:f1:13:58:4c:ff:7f:7d:4a:87:fc:f0:0f:54:
19 | af:8c:eb:ba:b4:0f:71:5e:12:1f:64:b1:3d:83:88:
20 | dd:9c:09:50:2d:37:1d:03:3b:18:30:36:f4:82:94:
21 | 87:7f:31:27:28:84:0c:99:6d:77:b5:b8:4f:ca:83:
22 | 58:d5:d8:4e:36:73:1c:1a:5c:ed:05:ef:95:60:03:
23 | 28:0c:9f:d8:6f:98:a8:cd:08:be:af:b1:95:5a:60:
24 | 96:fe:2a:d0:98:74:9b:4a:c0:48:66:73:67:54:33:
25 | 11:22:20:ea:11:05:ba:a6:ed:74:12:05:d8:de:4f:
26 | 01:46:39:74:d8:34:1a:f2:2c:c2:df:6d:94:57:52:
27 | c1:e4:2e:1b:8e:12:0e:43:e7:6f:1f:da:51:80:35:
28 | c2:8a:9b:b6:2a:30:39:6b:a0:77:fa:37:11:b7:ec:
29 | de:6e:8c:6f:93:81:5e:2d:90:69:1b:4b:a4:80:ca:
30 | f4:e5:5b:c0:13:45:b9:76:70:ed:d3:4e:dd:66:98:
31 | 99:9f:9d:f0:1e:fd:dd:04:4f:9a:55:bc:38:ad:42:
32 | b9:dd
33 | Exponent: 65537 (0x10001)
34 | X509v3 extensions:
35 | X509v3 Subject Alternative Name:
36 | DNS:web2.nginx-proxy.tld
37 | Signature Algorithm: sha256WithRSAEncryption
38 | 38:d6:8c:be:3c:5e:5d:61:02:77:eb:5b:6e:a7:1d:4f:69:0d:
39 | 54:bd:dd:3a:1a:8e:9d:a0:c2:f3:a5:31:91:3e:ec:7a:69:48:
40 | 40:27:45:a5:c6:b9:af:6d:d9:76:24:97:ec:c5:cf:4d:cc:49:
41 | 93:ab:bc:4f:01:7e:7a:57:73:4d:27:62:a6:68:bf:4c:00:2c:
42 | c0:f3:7b:b3:32:81:6b:96:20:0a:73:a0:85:b5:f8:07:10:88:
43 | e8:62:85:41:63:df:43:c5:f9:4b:90:89:6a:16:d9:a6:85:4a:
44 | 04:1b:5e:75:d8:84:ae:69:c7:62:8f:f1:53:c8:c6:31:71:6d:
45 | 0c:05:2d:bf:6e:b8:b8:7a:8c:73:6f:17:bb:5c:5a:67:51:12:
46 | af:e2:17:9c:40:0e:35:f6:59:93:69:45:14:74:33:c6:aa:04:
47 | 76:8e:3c:a6:ac:f4:60:cb:53:eb:d6:63:1a:47:7b:be:11:8d:
48 | 74:de:e8:e5:bc:de:1b:09:db:1b:87:89:b7:6a:20:0a:5e:fb:
49 | 35:04:ab:b2:f7:4d:a1:86:65:1d:c7:c3:aa:30:15:3a:6e:66:
50 | f8:43:33:63:ac:fc:c1:58:48:5b:ec:a0:00:bf:d4:f1:06:03:
51 | 79:ca:d5:b6:f2:39:0b:62:b4:fd:27:27:e9:37:52:21:ce:a4:
52 | 32:8a:bb:c7
53 | -----BEGIN CERTIFICATE-----
54 | MIIC/TCCAeWgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwPzEfMB0GA1UECgwWbmdp
55 | bngtcHJveHkgdGVzdCBzdWl0ZTEcMBoGA1UEAwwTd3d3Lm5naW54LXByb3h5LnRs
56 | ZDAeFw0xNzAxMTAwMDM3MDJaFw00NDA1MjgwMDM3MDJaMB8xHTAbBgNVBAMMFHdl
57 | YjIubmdpbngtcHJveHkudGxkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
58 | AQEA2u5GLUR88eaREb801gJO/kMj+20g940bxpzNHBoHlcLtuSNzwQIrUFE/M8+O
59 | qvETWEz/f31Kh/zwD1SvjOu6tA9xXhIfZLE9g4jdnAlQLTcdAzsYMDb0gpSHfzEn
60 | KIQMmW13tbhPyoNY1dhONnMcGlztBe+VYAMoDJ/Yb5iozQi+r7GVWmCW/irQmHSb
61 | SsBIZnNnVDMRIiDqEQW6pu10EgXY3k8BRjl02DQa8izC322UV1LB5C4bjhIOQ+dv
62 | H9pRgDXCipu2KjA5a6B3+jcRt+zeboxvk4FeLZBpG0ukgMr05VvAE0W5dnDt007d
63 | ZpiZn53wHv3dBE+aVbw4rUK53QIDAQABoyMwITAfBgNVHREEGDAWghR3ZWIyLm5n
64 | aW54LXByb3h5LnRsZDANBgkqhkiG9w0BAQsFAAOCAQEAONaMvjxeXWECd+tbbqcd
65 | T2kNVL3dOhqOnaDC86UxkT7semlIQCdFpca5r23ZdiSX7MXPTcxJk6u8TwF+eldz
66 | TSdipmi/TAAswPN7szKBa5YgCnOghbX4BxCI6GKFQWPfQ8X5S5CJahbZpoVKBBte
67 | ddiErmnHYo/xU8jGMXFtDAUtv264uHqMc28Xu1xaZ1ESr+IXnEAONfZZk2lFFHQz
68 | xqoEdo48pqz0YMtT69ZjGkd7vhGNdN7o5bzeGwnbG4eJt2ogCl77NQSrsvdNoYZl
69 | HcfDqjAVOm5m+EMzY6z8wVhIW+ygAL/U8QYDecrVtvI5C2K0/Scn6TdSIc6kMoq7
70 | xw==
71 | -----END CERTIFICATE-----
72 |
--------------------------------------------------------------------------------
/test/test_ssl/certs/web3.nginx-proxy.tld.crt:
--------------------------------------------------------------------------------
1 | Certificate:
2 | Data:
3 | Version: 3 (0x2)
4 | Serial Number: 4096 (0x1000)
5 | Signature Algorithm: sha256WithRSAEncryption
6 | Issuer: O=nginx-proxy test suite, CN=www.nginx-proxy.tld
7 | Validity
8 | Not Before: Jan 10 00:58:11 2017 GMT
9 | Not After : May 28 00:58:11 2044 GMT
10 | Subject: CN=web3.nginx-proxy.tld
11 | Subject Public Key Info:
12 | Public Key Algorithm: rsaEncryption
13 | Public-Key: (2048 bit)
14 | Modulus:
15 | 00:ac:9b:90:dc:2c:0d:c3:ae:f4:a0:cc:40:15:d2:
16 | c4:c2:2c:8c:15:b6:70:28:cf:32:c4:03:ce:b3:87:
17 | 30:5d:a6:12:96:69:7a:fe:67:29:1c:8c:24:bb:6a:
18 | c8:86:13:19:91:00:3e:ef:00:67:50:b0:ea:c9:93:
19 | c6:8a:73:82:d8:37:9f:8e:6e:12:13:ec:fa:08:0f:
20 | ac:73:6e:42:96:67:9f:20:c5:1c:a3:b1:4a:83:36:
21 | 0e:0a:31:93:76:b1:b6:37:4f:e0:88:3c:46:dc:c1:
22 | 53:60:df:28:ae:3e:20:d8:d9:53:a2:be:09:38:f0:
23 | ff:4a:91:45:cb:cb:b5:b3:3d:7d:09:98:47:dc:0d:
24 | 5e:83:73:b6:c9:f3:fb:9a:f2:bb:b0:62:ca:aa:af:
25 | 6b:42:e5:08:b2:14:87:f4:fc:f1:14:f8:cc:76:b3:
26 | c0:49:df:66:c6:21:a0:bc:5e:0c:bb:de:e9:9c:e7:
27 | fb:31:a1:cf:c4:e9:bb:bd:c3:5a:0d:23:52:c6:b3:
28 | 84:77:f1:0c:3d:ca:c3:60:48:f9:7e:a6:dc:4f:f7:
29 | d2:5b:7c:02:4d:38:09:64:33:7e:bb:b1:65:bb:e2:
30 | 2b:1d:9a:49:d4:34:21:42:7a:49:3e:11:6c:10:66:
31 | b4:91:db:bd:3a:c2:8d:f4:e4:03:b1:b4:6e:5c:98:
32 | bf:7d
33 | Exponent: 65537 (0x10001)
34 | X509v3 extensions:
35 | X509v3 Subject Alternative Name:
36 | DNS:web3.nginx-proxy.tld
37 | Signature Algorithm: sha256WithRSAEncryption
38 | 9a:f6:b9:c2:08:a4:b4:d7:e4:b2:d3:22:e9:fe:69:4a:e8:76:
39 | 18:60:11:1b:3b:7c:4b:c3:72:66:95:b7:7c:de:c7:34:32:58:
40 | aa:5d:e0:12:f0:df:27:b6:3f:dd:f1:8c:ed:ce:bd:73:50:fc:
41 | 9b:e1:8c:c2:7f:ac:6b:54:9d:23:0a:d9:a6:25:cc:99:94:73:
42 | 2b:69:e8:f7:07:40:37:d3:d4:0b:14:86:6a:a1:01:53:4b:ae:
43 | 85:2d:12:13:bd:23:1e:09:cf:20:50:24:76:a6:5f:b4:d6:d6:
44 | e1:34:40:93:4d:42:f7:d0:95:98:77:53:16:e7:ce:cc:4c:35:
45 | b8:30:3b:62:95:e2:40:0c:a1:73:84:92:93:63:df:c6:21:d5:
46 | eb:1d:a1:d0:f2:ec:a5:cf:d6:10:c9:8f:ac:11:16:39:cd:b0:
47 | 38:04:29:cf:e1:1c:dd:21:df:1f:95:35:a5:a4:61:2b:3d:8b:
48 | 8a:76:02:6d:31:a1:e8:6b:c5:3b:eb:90:40:34:16:5a:07:93:
49 | 96:56:cd:8b:52:ca:65:51:78:d3:f2:af:40:44:43:ec:fe:a2:
50 | c8:d4:6d:21:c7:1f:d2:45:28:0d:d2:51:0f:d1:a5:db:00:2a:
51 | 3a:10:88:9e:5a:76:a2:f7:e2:f0:fe:14:a3:e8:ec:ef:00:f0:
52 | 35:87:eb:7c
53 | -----BEGIN CERTIFICATE-----
54 | MIIC/TCCAeWgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwPzEfMB0GA1UECgwWbmdp
55 | bngtcHJveHkgdGVzdCBzdWl0ZTEcMBoGA1UEAwwTd3d3Lm5naW54LXByb3h5LnRs
56 | ZDAeFw0xNzAxMTAwMDU4MTFaFw00NDA1MjgwMDU4MTFaMB8xHTAbBgNVBAMMFHdl
57 | YjMubmdpbngtcHJveHkudGxkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
58 | AQEArJuQ3CwNw670oMxAFdLEwiyMFbZwKM8yxAPOs4cwXaYSlml6/mcpHIwku2rI
59 | hhMZkQA+7wBnULDqyZPGinOC2Defjm4SE+z6CA+sc25ClmefIMUco7FKgzYOCjGT
60 | drG2N0/giDxG3MFTYN8orj4g2NlTor4JOPD/SpFFy8u1sz19CZhH3A1eg3O2yfP7
61 | mvK7sGLKqq9rQuUIshSH9PzxFPjMdrPASd9mxiGgvF4Mu97pnOf7MaHPxOm7vcNa
62 | DSNSxrOEd/EMPcrDYEj5fqbcT/fSW3wCTTgJZDN+u7Flu+IrHZpJ1DQhQnpJPhFs
63 | EGa0kdu9OsKN9OQDsbRuXJi/fQIDAQABoyMwITAfBgNVHREEGDAWghR3ZWIzLm5n
64 | aW54LXByb3h5LnRsZDANBgkqhkiG9w0BAQsFAAOCAQEAmva5wgiktNfkstMi6f5p
65 | Suh2GGARGzt8S8NyZpW3fN7HNDJYql3gEvDfJ7Y/3fGM7c69c1D8m+GMwn+sa1Sd
66 | IwrZpiXMmZRzK2no9wdAN9PUCxSGaqEBU0uuhS0SE70jHgnPIFAkdqZftNbW4TRA
67 | k01C99CVmHdTFufOzEw1uDA7YpXiQAyhc4SSk2PfxiHV6x2h0PLspc/WEMmPrBEW
68 | Oc2wOAQpz+Ec3SHfH5U1paRhKz2LinYCbTGh6GvFO+uQQDQWWgeTllbNi1LKZVF4
69 | 0/KvQERD7P6iyNRtIccf0kUoDdJRD9Gl2wAqOhCInlp2ovfi8P4Uo+js7wDwNYfr
70 | fA==
71 | -----END CERTIFICATE-----
72 |
--------------------------------------------------------------------------------
/test/test_ssl/wildcard_cert_and_nohttps/certs/web.nginx-proxy.tld.crt:
--------------------------------------------------------------------------------
1 | Certificate:
2 | Data:
3 | Version: 3 (0x2)
4 | Serial Number: 4096 (0x1000)
5 | Signature Algorithm: sha256WithRSAEncryption
6 | Issuer: O=nginx-proxy test suite, CN=www.nginx-proxy.tld
7 | Validity
8 | Not Before: Mar 14 23:19:36 2017 GMT
9 | Not After : Jul 30 23:19:36 2044 GMT
10 | Subject: CN=*.web.nginx-proxy.tld
11 | Subject Public Key Info:
12 | Public Key Algorithm: rsaEncryption
13 | Public-Key: (2048 bit)
14 | Modulus:
15 | 00:ce:2b:74:13:b2:1a:d5:72:5c:3e:10:f7:63:01:
16 | 22:df:e8:d9:cf:0b:8a:3f:40:75:62:58:78:27:9e:
17 | af:33:d2:a1:19:6a:e1:b7:57:db:d9:8f:05:70:c2:
18 | 35:5d:f1:44:0d:51:62:74:73:e5:77:d9:bb:c6:d0:
19 | 33:7a:43:88:e9:e6:3c:2d:d4:39:9d:61:34:5a:19:
20 | f3:c1:96:e0:bd:26:5b:69:18:a6:4c:8c:21:04:d8:
21 | fa:56:22:ec:55:0d:ba:49:4d:8e:27:69:7f:82:e9:
22 | e7:e9:c4:b7:87:70:d7:d7:4b:49:d1:c1:8c:b0:5a:
23 | 13:62:db:de:c1:94:31:d1:c9:74:c4:63:01:50:10:
24 | 70:42:73:67:c4:76:32:fb:d2:b7:91:2f:e8:cf:3a:
25 | 96:4a:ee:8e:0d:13:74:73:1b:e4:74:83:e7:66:d6:
26 | 8d:81:19:54:5b:d8:47:3e:3b:b5:fd:35:a2:df:f3:
27 | 7d:1c:9e:67:ee:50:da:28:9c:02:0a:ad:75:8d:04:
28 | f7:28:1f:04:89:13:ac:ed:a9:34:26:dc:f7:f9:1f:
29 | 72:21:d5:72:fb:09:d9:cb:40:c0:0d:36:3c:c0:77:
30 | 0e:9a:f7:41:f1:3b:dd:b6:05:ab:13:60:c5:fd:c6:
31 | 5f:f5:05:c4:42:00:ba:b5:ef:fb:dc:64:98:d9:4d:
32 | 2b:07
33 | Exponent: 65537 (0x10001)
34 | X509v3 extensions:
35 | X509v3 Subject Alternative Name:
36 | DNS:*.web.nginx-proxy.tld
37 | Signature Algorithm: sha256WithRSAEncryption
38 | 9b:78:39:b3:90:8f:31:8c:7d:02:aa:6f:46:3d:8c:f5:93:86:
39 | 03:e2:d8:9b:73:d1:e7:70:f1:d6:e6:3c:41:41:8c:76:c9:29:
40 | a4:83:47:c7:10:fd:d0:8b:fa:60:26:a8:36:41:a4:69:89:81:
41 | ec:bf:fd:33:72:bb:83:ea:42:e4:59:3f:10:df:d1:de:e2:bb:
42 | eb:fa:97:44:fe:f4:55:29:69:ca:a5:88:b2:94:60:58:5a:1a:
43 | 19:16:fb:9f:42:4c:7c:d3:6b:21:45:22:56:5c:76:07:97:35:
44 | 27:8f:46:d2:77:5b:65:1b:94:99:cb:73:37:ae:cf:61:6c:7a:
45 | 5c:b3:3b:19:f2:9f:99:8f:89:eb:98:0b:74:0d:30:f5:49:19:
46 | d6:41:32:4e:c9:fc:59:2a:4a:53:2c:83:89:3d:e8:89:ed:37:
47 | d0:b4:f1:09:49:b5:0b:76:fd:a5:75:23:fb:01:c8:bb:59:02:
48 | 5c:e4:8e:9c:f9:5b:85:5f:67:fb:04:40:de:bc:e8:c3:15:2f:
49 | ba:00:5c:36:57:47:e3:1a:95:44:5f:f4:10:55:b0:c4:af:12:
50 | dc:0e:6c:18:4a:70:9e:73:90:8d:55:37:73:a5:1a:41:7f:00:
51 | 79:96:34:01:6b:10:2d:e9:61:3d:8f:8a:9a:c8:b6:bc:0f:57:
52 | 91:84:7c:26
53 | -----BEGIN CERTIFICATE-----
54 | MIIC/zCCAeegAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwPzEfMB0GA1UECgwWbmdp
55 | bngtcHJveHkgdGVzdCBzdWl0ZTEcMBoGA1UEAwwTd3d3Lm5naW54LXByb3h5LnRs
56 | ZDAeFw0xNzAzMTQyMzE5MzZaFw00NDA3MzAyMzE5MzZaMCAxHjAcBgNVBAMMFSou
57 | d2ViLm5naW54LXByb3h5LnRsZDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
58 | ggEBAM4rdBOyGtVyXD4Q92MBIt/o2c8Lij9AdWJYeCeerzPSoRlq4bdX29mPBXDC
59 | NV3xRA1RYnRz5XfZu8bQM3pDiOnmPC3UOZ1hNFoZ88GW4L0mW2kYpkyMIQTY+lYi
60 | 7FUNuklNjidpf4Lp5+nEt4dw19dLSdHBjLBaE2Lb3sGUMdHJdMRjAVAQcEJzZ8R2
61 | MvvSt5Ev6M86lkrujg0TdHMb5HSD52bWjYEZVFvYRz47tf01ot/zfRyeZ+5Q2iic
62 | AgqtdY0E9ygfBIkTrO2pNCbc9/kfciHVcvsJ2ctAwA02PMB3Dpr3QfE73bYFqxNg
63 | xf3GX/UFxEIAurXv+9xkmNlNKwcCAwEAAaMkMCIwIAYDVR0RBBkwF4IVKi53ZWIu
64 | bmdpbngtcHJveHkudGxkMA0GCSqGSIb3DQEBCwUAA4IBAQCbeDmzkI8xjH0Cqm9G
65 | PYz1k4YD4tibc9HncPHW5jxBQYx2ySmkg0fHEP3Qi/pgJqg2QaRpiYHsv/0zcruD
66 | 6kLkWT8Q39He4rvr+pdE/vRVKWnKpYiylGBYWhoZFvufQkx802shRSJWXHYHlzUn
67 | j0bSd1tlG5SZy3M3rs9hbHpcszsZ8p+Zj4nrmAt0DTD1SRnWQTJOyfxZKkpTLIOJ
68 | PeiJ7TfQtPEJSbULdv2ldSP7Aci7WQJc5I6c+VuFX2f7BEDevOjDFS+6AFw2V0fj
69 | GpVEX/QQVbDErxLcDmwYSnCec5CNVTdzpRpBfwB5ljQBaxAt6WE9j4qayLa8D1eR
70 | hHwm
71 | -----END CERTIFICATE-----
72 |
--------------------------------------------------------------------------------
/test/test_headers/test_http.py:
--------------------------------------------------------------------------------
1 | def test_arbitrary_headers_are_passed_on(docker_compose, nginxproxy):
2 | r = nginxproxy.get("http://web.nginx-proxy.tld/headers", headers={'Foo': 'Bar'})
3 | assert r.status_code == 200
4 | assert "Foo: Bar\n" in r.text
5 |
6 |
7 | ##### Testing the handling of X-Forwarded-For #####
8 |
9 | def test_X_Forwarded_For_is_generated(docker_compose, nginxproxy):
10 | r = nginxproxy.get("http://web.nginx-proxy.tld/headers")
11 | assert r.status_code == 200
12 | assert "X-Forwarded-For:" in r.text
13 |
14 | def test_X_Forwarded_For_is_passed_on(docker_compose, nginxproxy):
15 | r = nginxproxy.get("http://web.nginx-proxy.tld/headers", headers={'X-Forwarded-For': '1.2.3.4'})
16 | assert r.status_code == 200
17 | assert "X-Forwarded-For: 1.2.3.4, " in r.text
18 |
19 |
20 | ##### Testing the handling of X-Forwarded-Proto #####
21 |
22 | def test_X_Forwarded_Proto_is_generated(docker_compose, nginxproxy):
23 | r = nginxproxy.get("http://web.nginx-proxy.tld/headers")
24 | assert r.status_code == 200
25 | assert "X-Forwarded-Proto: http" in r.text
26 |
27 | def test_X_Forwarded_Proto_is_passed_on(docker_compose, nginxproxy):
28 | r = nginxproxy.get("http://web.nginx-proxy.tld/headers", headers={'X-Forwarded-Proto': 'f00'})
29 | assert r.status_code == 200
30 | assert "X-Forwarded-Proto: f00\n" in r.text
31 |
32 |
33 | ##### Testing the handling of X-Forwarded-Port #####
34 |
35 | def test_X_Forwarded_Port_is_generated(docker_compose, nginxproxy):
36 | r = nginxproxy.get("http://web.nginx-proxy.tld/headers")
37 | assert r.status_code == 200
38 | assert "X-Forwarded-Port: 80\n" in r.text
39 |
40 | def test_X_Forwarded_Port_is_passed_on(docker_compose, nginxproxy):
41 | r = nginxproxy.get("http://web.nginx-proxy.tld/headers", headers={'X-Forwarded-Port': '1234'})
42 | assert r.status_code == 200
43 | assert "X-Forwarded-Port: 1234\n" in r.text
44 |
45 |
46 | ##### Testing the handling of X-Forwarded-Ssl #####
47 |
48 | def test_X_Forwarded_Ssl_is_generated(docker_compose, nginxproxy):
49 | r = nginxproxy.get("http://web.nginx-proxy.tld/headers")
50 | assert r.status_code == 200
51 | assert "X-Forwarded-Ssl: off\n" in r.text
52 |
53 | def test_X_Forwarded_Ssl_is_overwritten(docker_compose, nginxproxy):
54 | r = nginxproxy.get("http://web.nginx-proxy.tld/headers", headers={'X-Forwarded-Ssl': 'f00'})
55 | assert r.status_code == 200
56 | assert "X-Forwarded-Ssl: off\n" in r.text
57 |
58 |
59 | ##### Other headers
60 |
61 | def test_X_Real_IP_is_generated(docker_compose, nginxproxy):
62 | r = nginxproxy.get("http://web.nginx-proxy.tld/headers")
63 | assert r.status_code == 200
64 | assert "X-Real-IP: " in r.text
65 |
66 | def test_Host_is_passed_on(docker_compose, nginxproxy):
67 | r = nginxproxy.get("http://web.nginx-proxy.tld/headers")
68 | assert r.status_code == 200
69 | assert "Host: web.nginx-proxy.tld" in r.text
70 |
71 | def test_httpoxy_safe(docker_compose, nginxproxy):
72 | """
73 | See https://httpoxy.org/
74 | nginx-proxy should suppress the `Proxy` header
75 | """
76 | r = nginxproxy.get("http://web.nginx-proxy.tld/headers", headers={'Proxy': 'tcp://some.hacker.com'})
77 | assert r.status_code == 200
78 | assert "Proxy:" not in r.text
79 |
80 |
81 | def test_no_host_server_tokens_off(docker_compose, nginxproxy):
82 | ip = nginxproxy.get_ip()
83 | r = nginxproxy.get(f"http://{ip}/headers")
84 | assert r.status_code == 503
85 | assert r.headers["Server"] == "nginx"
86 |
87 |
88 | def test_server_tokens_on(docker_compose, nginxproxy):
89 | r = nginxproxy.get("http://web.nginx-proxy.tld/headers")
90 | assert r.status_code == 200
91 | assert "Host: web.nginx-proxy.tld" in r.text
92 | assert r.headers["Server"].startswith("nginx/")
93 |
94 |
95 | def test_server_tokens_off(docker_compose, nginxproxy):
96 | r = nginxproxy.get("http://web-server-tokens-off.nginx-proxy.tld/headers")
97 | assert r.status_code == 200
98 | assert "Host: web-server-tokens-off.nginx-proxy.tld" in r.text
99 | assert r.headers["Server"] == "nginx"
100 |
--------------------------------------------------------------------------------
/test/test_headers/certs/web-server-tokens-off.nginx-proxy.tld.crt:
--------------------------------------------------------------------------------
1 | Certificate:
2 | Data:
3 | Version: 3 (0x2)
4 | Serial Number: 4096 (0x1000)
5 | Signature Algorithm: sha256WithRSAEncryption
6 | Issuer: O=nginx-proxy test suite, CN=www.nginx-proxy.tld
7 | Validity
8 | Not Before: May 11 18:25:49 2021 GMT
9 | Not After : Sep 26 18:25:49 2048 GMT
10 | Subject: CN=web-server-tokens-off.nginx-proxy.tld
11 | Subject Public Key Info:
12 | Public Key Algorithm: rsaEncryption
13 | RSA Public-Key: (2048 bit)
14 | Modulus:
15 | 00:b4:fa:9d:8a:74:3f:17:ea:99:1c:45:71:18:90:
16 | eb:92:35:38:d7:90:21:81:0a:91:05:41:cf:b5:87:
17 | 34:bd:d8:7b:7f:7d:06:33:f8:94:67:8e:e4:07:54:
18 | 7f:b7:62:c5:76:6c:7f:7c:19:25:19:2c:36:9a:26:
19 | 54:8e:2d:97:02:78:31:c6:13:d3:ad:f3:31:62:e6:
20 | cf:96:ae:63:37:dd:bd:73:cb:4e:fb:3f:9b:65:67:
21 | 97:d8:5a:5d:0e:72:b1:11:ab:0e:d7:23:a9:b7:22:
22 | de:23:74:7e:88:7c:28:98:a9:6e:00:f4:be:8c:69:
23 | ea:3f:33:8b:19:97:da:1b:a6:65:b5:5a:92:01:3c:
24 | 3a:13:6b:00:02:e1:98:78:d3:da:ea:a6:9c:33:b0:
25 | 1d:9f:02:c4:f1:d0:d6:de:7a:f7:42:12:4b:31:fb:
26 | ed:e9:d7:d8:15:e8:4e:18:91:7c:9d:bf:0f:b0:12:
27 | d6:e2:80:8b:7a:ef:17:70:51:f4:3c:b7:43:cb:56:
28 | 61:af:61:7a:4e:9d:6c:5e:d8:27:0c:3b:d7:a4:1d:
29 | 2f:0d:a0:99:8f:b5:71:93:21:b4:87:be:b4:1c:77:
30 | a0:b9:cd:91:bd:9c:d0:b9:81:50:12:63:d2:0a:a9:
31 | 61:05:91:19:27:f7:ea:9d:8e:48:65:2e:1a:e7:fd:
32 | f1:b7
33 | Exponent: 65537 (0x10001)
34 | X509v3 extensions:
35 | X509v3 Subject Alternative Name:
36 | DNS:web-server-tokens-off.nginx-proxy.tld
37 | Signature Algorithm: sha256WithRSAEncryption
38 | 5b:b7:74:ad:07:08:65:3c:8e:02:50:a9:b6:f4:8d:47:95:6f:
39 | e0:ba:5a:8c:ae:5c:32:88:8b:45:04:48:ce:3d:72:45:d7:7e:
40 | 1e:d7:75:17:30:98:90:21:4c:67:e2:57:1d:c9:fa:03:f4:81:
41 | 64:cf:d2:b3:85:71:be:53:b9:2a:fd:89:04:a6:b1:88:0a:0a:
42 | f1:5c:93:9b:fb:4f:86:0e:c5:4d:6a:ff:54:7b:07:f1:7e:d1:
43 | 8a:6b:fa:3b:f3:5c:d2:1b:2c:86:05:4c:e0:b4:04:0d:c7:db:
44 | 0b:89:b4:33:09:b6:1a:f0:cb:d4:ae:2c:05:63:a4:18:19:52:
45 | c7:15:21:ac:ae:9e:15:b9:b0:58:0c:96:df:7b:77:46:ef:59:
46 | a7:96:56:da:f6:f6:81:9f:10:7d:5a:48:68:0c:28:02:5d:7b:
47 | 69:4d:89:41:e2:88:6d:c6:22:45:6a:34:1b:ba:9b:6f:d6:2d:
48 | c2:55:b1:73:b4:bb:f5:06:d6:5f:ed:01:d1:3c:51:8b:e2:6c:
49 | 31:d7:6b:a5:bd:05:e3:9a:97:15:40:bf:bb:8f:81:e5:bf:bc:
50 | 06:66:47:84:fe:f7:06:fb:5d:35:9e:04:26:0d:aa:3d:b5:92:
51 | 6b:90:c2:1c:17:ac:c1:95:d9:6b:f1:5d:0a:09:9f:a7:a6:ca:
52 | 3b:45:a4:59
53 | -----BEGIN CERTIFICATE-----
54 | MIIDHzCCAgegAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwPzEfMB0GA1UECgwWbmdp
55 | bngtcHJveHkgdGVzdCBzdWl0ZTEcMBoGA1UEAwwTd3d3Lm5naW54LXByb3h5LnRs
56 | ZDAeFw0yMTA1MTExODI1NDlaFw00ODA5MjYxODI1NDlaMDAxLjAsBgNVBAMMJXdl
57 | Yi1zZXJ2ZXItdG9rZW5zLW9mZi5uZ2lueC1wcm94eS50bGQwggEiMA0GCSqGSIb3
58 | DQEBAQUAA4IBDwAwggEKAoIBAQC0+p2KdD8X6pkcRXEYkOuSNTjXkCGBCpEFQc+1
59 | hzS92Ht/fQYz+JRnjuQHVH+3YsV2bH98GSUZLDaaJlSOLZcCeDHGE9Ot8zFi5s+W
60 | rmM33b1zy077P5tlZ5fYWl0OcrERqw7XI6m3It4jdH6IfCiYqW4A9L6Maeo/M4sZ
61 | l9obpmW1WpIBPDoTawAC4Zh409rqppwzsB2fAsTx0NbeevdCEksx++3p19gV6E4Y
62 | kXydvw+wEtbigIt67xdwUfQ8t0PLVmGvYXpOnWxe2CcMO9ekHS8NoJmPtXGTIbSH
63 | vrQcd6C5zZG9nNC5gVASY9IKqWEFkRkn9+qdjkhlLhrn/fG3AgMBAAGjNDAyMDAG
64 | A1UdEQQpMCeCJXdlYi1zZXJ2ZXItdG9rZW5zLW9mZi5uZ2lueC1wcm94eS50bGQw
65 | DQYJKoZIhvcNAQELBQADggEBAFu3dK0HCGU8jgJQqbb0jUeVb+C6WoyuXDKIi0UE
66 | SM49ckXXfh7XdRcwmJAhTGfiVx3J+gP0gWTP0rOFcb5TuSr9iQSmsYgKCvFck5v7
67 | T4YOxU1q/1R7B/F+0Ypr+jvzXNIbLIYFTOC0BA3H2wuJtDMJthrwy9SuLAVjpBgZ
68 | UscVIayunhW5sFgMlt97d0bvWaeWVtr29oGfEH1aSGgMKAJde2lNiUHiiG3GIkVq
69 | NBu6m2/WLcJVsXO0u/UG1l/tAdE8UYvibDHXa6W9BeOalxVAv7uPgeW/vAZmR4T+
70 | 9wb7XTWeBCYNqj21kmuQwhwXrMGV2WvxXQoJn6emyjtFpFk=
71 | -----END CERTIFICATE-----
72 |
--------------------------------------------------------------------------------
/test/test_headers/test_https.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 |
4 | def test_arbitrary_headers_are_passed_on(docker_compose, nginxproxy):
5 | r = nginxproxy.get("https://web.nginx-proxy.tld/headers", headers={'Foo': 'Bar'})
6 | assert r.status_code == 200
7 | assert "Foo: Bar\n" in r.text
8 |
9 |
10 | ##### Testing the handling of X-Forwarded-For #####
11 |
12 | def test_X_Forwarded_For_is_generated(docker_compose, nginxproxy):
13 | r = nginxproxy.get("https://web.nginx-proxy.tld/headers")
14 | assert r.status_code == 200
15 | assert "X-Forwarded-For:" in r.text
16 |
17 | def test_X_Forwarded_For_is_passed_on(docker_compose, nginxproxy):
18 | r = nginxproxy.get("https://web.nginx-proxy.tld/headers", headers={'X-Forwarded-For': '1.2.3.4'})
19 | assert r.status_code == 200
20 | assert "X-Forwarded-For: 1.2.3.4, " in r.text
21 |
22 |
23 | ##### Testing the handling of X-Forwarded-Proto #####
24 |
25 | def test_X_Forwarded_Proto_is_generated(docker_compose, nginxproxy):
26 | r = nginxproxy.get("https://web.nginx-proxy.tld/headers")
27 | assert r.status_code == 200
28 | assert "X-Forwarded-Proto: https" in r.text
29 |
30 | def test_X_Forwarded_Proto_is_passed_on(docker_compose, nginxproxy):
31 | r = nginxproxy.get("https://web.nginx-proxy.tld/headers", headers={'X-Forwarded-Proto': 'f00'})
32 | assert r.status_code == 200
33 | assert "X-Forwarded-Proto: f00\n" in r.text
34 |
35 |
36 | ##### Testing the handling of X-Forwarded-Port #####
37 |
38 | def test_X_Forwarded_Port_is_generated(docker_compose, nginxproxy):
39 | r = nginxproxy.get("https://web.nginx-proxy.tld/headers")
40 | assert r.status_code == 200
41 | assert "X-Forwarded-Port: 443\n" in r.text
42 |
43 | def test_X_Forwarded_Port_is_passed_on(docker_compose, nginxproxy):
44 | r = nginxproxy.get("https://web.nginx-proxy.tld/headers", headers={'X-Forwarded-Port': '1234'})
45 | assert r.status_code == 200
46 | assert "X-Forwarded-Port: 1234\n" in r.text
47 |
48 |
49 | ##### Testing the handling of X-Forwarded-Ssl #####
50 |
51 | def test_X_Forwarded_Ssl_is_generated(docker_compose, nginxproxy):
52 | r = nginxproxy.get("https://web.nginx-proxy.tld/headers")
53 | assert r.status_code == 200
54 | assert "X-Forwarded-Ssl: on\n" in r.text
55 |
56 | def test_X_Forwarded_Ssl_is_overwritten(docker_compose, nginxproxy):
57 | r = nginxproxy.get("https://web.nginx-proxy.tld/headers", headers={'X-Forwarded-Ssl': 'f00'})
58 | assert r.status_code == 200
59 | assert "X-Forwarded-Ssl: on\n" in r.text
60 |
61 |
62 | ##### Other headers
63 |
64 | def test_X_Real_IP_is_generated(docker_compose, nginxproxy):
65 | r = nginxproxy.get("https://web.nginx-proxy.tld/headers")
66 | assert r.status_code == 200
67 | assert "X-Real-IP: " in r.text
68 |
69 | def test_Host_is_passed_on(docker_compose, nginxproxy):
70 | r = nginxproxy.get("https://web.nginx-proxy.tld/headers")
71 | assert r.status_code == 200
72 | assert "Host: web.nginx-proxy.tld" in r.text
73 |
74 | def test_httpoxy_safe(docker_compose, nginxproxy):
75 | """
76 | See https://httpoxy.org/
77 | nginx-proxy should suppress the `Proxy` header
78 | """
79 | r = nginxproxy.get("https://web.nginx-proxy.tld/headers", headers={'Proxy': 'tcp://some.hacker.com'})
80 | assert r.status_code == 200
81 | assert "Proxy:" not in r.text
82 |
83 |
84 | @pytest.mark.filterwarnings('ignore::urllib3.exceptions.InsecureRequestWarning')
85 | def test_no_host_server_tokens_off(docker_compose, nginxproxy):
86 | ip = nginxproxy.get_ip()
87 | r = nginxproxy.get(f"https://{ip}/headers", verify=False)
88 | assert r.status_code == 503
89 | assert r.headers["Server"] == "nginx"
90 |
91 |
92 | def test_server_tokens_on(docker_compose, nginxproxy):
93 | r = nginxproxy.get("https://web.nginx-proxy.tld/headers")
94 | assert r.status_code == 200
95 | assert "Host: web.nginx-proxy.tld" in r.text
96 | assert r.headers["Server"].startswith("nginx/")
97 |
98 |
99 | def test_server_tokens_off(docker_compose, nginxproxy):
100 | r = nginxproxy.get("https://web-server-tokens-off.nginx-proxy.tld/headers")
101 | assert r.status_code == 200
102 | assert "Host: web-server-tokens-off.nginx-proxy.tld" in r.text
103 | assert r.headers["Server"] == "nginx"
104 |
--------------------------------------------------------------------------------
/test/README.md:
--------------------------------------------------------------------------------
1 | Nginx proxy test suite
2 | ======================
3 |
4 | Install requirements
5 | --------------------
6 |
7 | You need [python 3.9](https://www.python.org/) and [pip](https://pip.pypa.io/en/stable/installing/) installed. Then run the commands:
8 |
9 | pip install -r requirements/python-requirements.txt
10 |
11 |
12 |
13 | Prepare the nginx-proxy test image
14 | ----------------------------------
15 |
16 | make build-nginx-proxy-test-debian
17 |
18 | or if you want to test the alpine flavor:
19 |
20 | make build-nginx-proxy-test-alpine
21 |
22 | Run the test suite
23 | ------------------
24 |
25 | pytest
26 |
27 | need more verbosity ?
28 |
29 | pytest -s
30 |
31 |
32 | Run one single test module
33 | --------------------------
34 |
35 | pytest test_nominal.py
36 |
37 |
38 | Write a test module
39 | -------------------
40 |
41 | This test suite uses [pytest](http://doc.pytest.org/en/latest/). The [conftest.py](conftest.py) file will be automatically loaded by pytest and will provide you with two useful pytest [fixtures](https://docs.pytest.org/en/latest/explanation/fixtures.html):
42 |
43 | - docker_compose
44 | - nginxproxy
45 |
46 |
47 | ### docker_compose fixture
48 |
49 | When using the `docker_compose` fixture in a test, pytest will try to find a yml file named after your test module filename. For instance, if your test module is `test_example.py`, then the `docker_compose` fixture will try to load a `test_example.yml` [docker compose file](https://docs.docker.com/compose/compose-file/).
50 |
51 | Once the docker compose file found, the fixture will remove all containers, run `docker-compose up`, and finally your test will be executed.
52 |
53 | The fixture will run the _docker-compose_ command with the `-f` option to load the given compose file. So you can test your docker compose file syntax by running it yourself with:
54 |
55 | docker-compose -f test_example.yml up -d
56 |
57 | In the case you are running pytest from within a docker container, the `docker_compose` fixture will make sure the container running pytest is attached to all docker networks. That way, your test will be able to reach any of them.
58 |
59 | In your tests, you can use the `docker_compose` variable to query and command the docker daemon as it provides you with a [client from the docker python module](https://docker-py.readthedocs.io/en/4.4.4/client.html#client-reference).
60 |
61 | Also this fixture alters the way the python interpreter resolves domain names to IP addresses in the following ways:
62 |
63 | Any domain name containing the substring `nginx-proxy` will resolve to the IP address of the container that was created from the `nginxproxy/nginx-proxy:test` image. So all the following domain names will resolve to the nginx-proxy container in tests:
64 | - `nginx-proxy`
65 | - `nginx-proxy.com`
66 | - `www.nginx-proxy.com`
67 | - `www.nginx-proxy.test`
68 | - `www.nginx-proxy`
69 | - `whatever.nginx-proxyooooooo`
70 | - ...
71 |
72 | Any domain name ending with `XXX.container.docker` will resolve to the IP address of the XXX container.
73 | - `web1.container.docker` will resolve to the IP address of the `web1` container
74 | - `f00.web1.container.docker` will resolve to the IP address of the `web1` container
75 | - `anything.whatever.web2.container.docker` will resolve to the IP address of the `web2` container
76 |
77 | Otherwise, domain names are resoved as usual using your system DNS resolver.
78 |
79 |
80 | ### nginxproxy fixture
81 |
82 | The `nginxproxy` fixture will provide you with a replacement for the python [requests](https://pypi.python.org/pypi/requests/) module. This replacement will just repeat up to 30 times a requests if it receives the HTTP error 404 or 502. This error occurs when you try to send queries to nginx-proxy too early after the container creation.
83 |
84 | Also this requests replacement is preconfigured to use the Certificate Authority root certificate [certs/ca-root.crt](certs/) to validate https connections.
85 |
86 | Furthermore, the nginxproxy methods accept an additional keyword parameter: `ipv6` which forces requests made against containers to use the containers IPv6 address when set to `True`. If IPv6 is not supported by the system or docker, that particular test will be skipped.
87 |
88 | def test_forwards_to_web1_ipv6(docker_compose, nginxproxy):
89 | r = nginxproxy.get("http://web1.nginx-proxy.tld/port", ipv6=True)
90 | assert r.status_code == 200
91 | assert r.text == "answer from port 81\n"
92 |
93 |
94 |
95 | ### The web docker image
96 |
97 | When you run the `make build-webserver` command, you built a [`web`](requirements/README.md) docker image which is convenient for running a small web server in a container. This image can produce containers that listens on multiple ports at the same time.
98 |
99 | ### Testing TLS
100 |
101 | If you need to create server certificates, use the [`certs/create_server_certificate.sh`](certs/) script. Pytest will be able to validate any certificate issued from this script.
102 |
--------------------------------------------------------------------------------
/.github/workflows/dockerhub.yml:
--------------------------------------------------------------------------------
1 | name: DockerHub
2 |
3 | on:
4 | workflow_dispatch:
5 | schedule:
6 | - cron: '0 0 * * 1'
7 | push:
8 | branches:
9 | - main
10 | tags:
11 | - '*.*.*'
12 | paths-ignore:
13 | - 'test/*'
14 | - '.gitignore'
15 | - 'docker-compose-separate-containers.yml'
16 | - 'docker-compose.yml'
17 | - 'LICENSE'
18 | - 'Makefile'
19 | - '*.md'
20 |
21 | jobs:
22 | multiarch-build-debian:
23 | runs-on: ubuntu-latest
24 | steps:
25 |
26 | - name: Checkout
27 | uses: actions/checkout@v2
28 | with:
29 | fetch-depth: 0
30 |
31 | - name: Retrieve version
32 | run: echo "GIT_DESCRIBE=$(git describe --tags)" >> $GITHUB_ENV
33 |
34 | - name: Get Docker tags for Debian based image
35 | id: docker_meta_debian
36 | uses: docker/metadata-action@v3
37 | with:
38 | images: |
39 | ghcr.io/nginx-proxy/nginx-proxy
40 | nginxproxy/nginx-proxy
41 | jwilder/nginx-proxy
42 | tags: |
43 | type=semver,pattern={{version}}
44 | type=semver,pattern={{major}}.{{minor}}
45 | type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }}
46 | labels: |
47 | org.opencontainers.image.authors=Nicolas Duchon (@buchdag), Jason Wilder
48 | org.opencontainers.image.version=${{ env.GIT_DESCRIBE }}
49 |
50 | - name: Set up QEMU
51 | uses: docker/setup-qemu-action@v1
52 |
53 | - name: Set up Docker Buildx
54 | uses: docker/setup-buildx-action@v1
55 |
56 | - name: Login to DockerHub
57 | uses: docker/login-action@v1
58 | with:
59 | username: ${{ secrets.DOCKERHUB_USERNAME }}
60 | password: ${{ secrets.DOCKERHUB_TOKEN }}
61 |
62 | - name: Log in to GitHub Container Registry
63 | uses: docker/login-action@v1
64 | with:
65 | registry: ghcr.io
66 | username: ${{ github.actor }}
67 | password: ${{ secrets.GITHUB_TOKEN }}
68 |
69 | - name: Build and push the Debian based image
70 | id: docker_build_debian
71 | uses: docker/build-push-action@v2
72 | with:
73 | context: .
74 | file: Dockerfile
75 | build-args: NGINX_PROXY_VERSION=${{ env.GIT_DESCRIBE }}
76 | platforms: linux/amd64,linux/arm64,linux/arm/v7
77 | push: true
78 | tags: ${{ steps.docker_meta_debian.outputs.tags }}
79 | labels: ${{ steps.docker_meta_debian.outputs.labels }}
80 |
81 | - name: Images digests
82 | run: echo ${{ steps.docker_build_debian.outputs.digest }}
83 |
84 | multiarch-build-alpine:
85 | runs-on: ubuntu-latest
86 | steps:
87 |
88 | - name: Checkout
89 | uses: actions/checkout@v2
90 | with:
91 | fetch-depth: 0
92 |
93 | - name: Retrieve version
94 | run: echo "GIT_DESCRIBE=$(git describe --tags)" >> $GITHUB_ENV
95 |
96 | - name: Get Docker tags for Alpine based image
97 | id: docker_meta_alpine
98 | uses: docker/metadata-action@v3
99 | with:
100 | images: |
101 | ghcr.io/nginx-proxy/nginx-proxy
102 | nginxproxy/nginx-proxy
103 | jwilder/nginx-proxy
104 | tags: |
105 | type=semver,suffix=-alpine,pattern={{version}}
106 | type=semver,suffix=-alpine,pattern={{major}}.{{minor}}
107 | type=raw,value=alpine,enable=${{ github.ref == 'refs/heads/main' }}
108 | labels: |
109 | org.opencontainers.image.authors=Nicolas Duchon (@buchdag), Jason Wilder
110 | org.opencontainers.image.version=${{ env.GIT_DESCRIBE }}
111 | flavor: latest=false
112 |
113 | - name: Set up QEMU
114 | uses: docker/setup-qemu-action@v1
115 |
116 | - name: Set up Docker Buildx
117 | uses: docker/setup-buildx-action@v1
118 |
119 | - name: Login to DockerHub
120 | uses: docker/login-action@v1
121 | with:
122 | username: ${{ secrets.DOCKERHUB_USERNAME }}
123 | password: ${{ secrets.DOCKERHUB_TOKEN }}
124 |
125 | - name: Log in to GitHub Container Registry
126 | uses: docker/login-action@v1
127 | with:
128 | registry: ghcr.io
129 | username: ${{ github.actor }}
130 | password: ${{ secrets.GITHUB_TOKEN }}
131 |
132 | - name: Build and push the Alpine based image
133 | id: docker_build_alpine
134 | uses: docker/build-push-action@v2
135 | with:
136 | context: .
137 | file: Dockerfile.alpine
138 | build-args: NGINX_PROXY_VERSION=${{ env.GIT_DESCRIBE }}
139 | platforms: linux/amd64,linux/arm64,linux/arm/v7
140 | push: true
141 | tags: ${{ steps.docker_meta_alpine.outputs.tags }}
142 | labels: ${{ steps.docker_meta_alpine.outputs.labels }}
143 |
144 | - name: Images digests
145 | run: echo ${{ steps.docker_build_alpine.outputs.digest }}
146 |
--------------------------------------------------------------------------------
/test/certs/create_server_certificate.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -u
3 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
4 |
5 | if [[ "$#" -eq 0 ]]; then
6 | cat <<-EOF
7 |
8 | To generate a server certificate, provide the domain name as a parameter:
9 | $(basename $0) www.my-domain.tdl
10 | $(basename $0) www.my-domain.tdl alternate.domain.tld
11 |
12 | You can also create certificates for wildcard domains:
13 | $(basename $0) '*.my-domain.tdl'
14 |
15 | EOF
16 | exit 0
17 | else
18 | DOMAIN="$1"
19 | ALTERNATE_DOMAINS="DNS:$( echo "$@" | sed 's/ /,DNS:/g')"
20 | fi
21 |
22 |
23 | ###############################################################################
24 | # Create a nginx container (which conveniently provides the `openssl` command)
25 | ###############################################################################
26 |
27 | CONTAINER=$(docker run -d -v $DIR:/work -w /work -e SAN="$ALTERNATE_DOMAINS" nginx:1.19.10)
28 | # Configure openssl
29 | docker exec $CONTAINER bash -c '
30 | mkdir -p /ca/{certs,crl,private,newcerts} 2>/dev/null
31 | echo 1000 > /ca/serial
32 | touch /ca/index.txt
33 | cat > /ca/openssl.cnf <<-"OESCRIPT"
34 | [ ca ]
35 | # `man ca`
36 | default_ca = CA_default
37 |
38 | [ CA_default ]
39 | # Directory and file locations.
40 | dir = /ca
41 | certs = $dir/certs
42 | crl_dir = $dir/crl
43 | new_certs_dir = $dir/newcerts
44 | database = $dir/index.txt
45 | serial = $dir/serial
46 | RANDFILE = $dir/private/.rand
47 |
48 | # The root key and root certificate.
49 | private_key = /work/ca-root.key
50 | certificate = /work/ca-root.crt
51 |
52 | # SHA-1 is deprecated, so use SHA-2 instead.
53 | default_md = sha256
54 |
55 | name_opt = ca_default
56 | cert_opt = ca_default
57 | default_days = 10000
58 | preserve = no
59 | policy = policy_loose
60 |
61 | [ policy_loose ]
62 | countryName = optional
63 | stateOrProvinceName = optional
64 | localityName = optional
65 | organizationName = optional
66 | organizationalUnitName = optional
67 | commonName = supplied
68 | emailAddress = optional
69 |
70 | [ req ]
71 | # Options for the `req` tool (`man req`).
72 | default_bits = 2048
73 | distinguished_name = req_distinguished_name
74 | string_mask = utf8only
75 |
76 | # SHA-1 is deprecated, so use SHA-2 instead.
77 | default_md = sha256
78 |
79 | # Extension to add when the -x509 option is used.
80 | x509_extensions = v3_ca
81 |
82 | [ req_distinguished_name ]
83 | # See .
84 | countryName = Country Name (2 letter code)
85 | stateOrProvinceName = State or Province Name
86 | localityName = Locality Name
87 | 0.organizationName = Organization Name
88 | organizationalUnitName = Organizational Unit Name
89 | commonName = Common Name
90 | emailAddress = Email Address
91 |
92 | [ v3_ca ]
93 | # Extensions for a typical CA (`man x509v3_config`).
94 | subjectKeyIdentifier = hash
95 | authorityKeyIdentifier = keyid:always,issuer
96 | basicConstraints = critical, CA:true
97 | keyUsage = critical, digitalSignature, cRLSign, keyCertSign
98 |
99 | [ server_cert ]
100 | # Extensions for server certificates (`man x509v3_config`).
101 | basicConstraints = CA:FALSE
102 | nsCertType = server
103 | nsComment = server certificate generated for test purpose (nginx-proxy test suite)
104 | subjectKeyIdentifier = hash
105 | authorityKeyIdentifier = keyid,issuer:always
106 | keyUsage = critical, digitalSignature, keyEncipherment
107 | extendedKeyUsage = serverAuth
108 |
109 | [ san_env ]
110 | subjectAltName=${ENV::SAN}
111 | OESCRIPT
112 | '
113 |
114 | # shortcut for calling `openssl` inside the container
115 | function openssl {
116 | docker exec $CONTAINER openssl "$@"
117 | }
118 |
119 | function exitfail {
120 | echo
121 | echo ERROR: "$@"
122 | docker rm -f $CONTAINER
123 | exit 1
124 | }
125 |
126 |
127 | ###############################################################################
128 | # Setup Certificate authority
129 | ###############################################################################
130 |
131 | if ! [[ -f "$DIR/ca-root.key" ]]; then
132 | echo
133 | echo "> Create a Certificate Authority root key: $DIR/ca-root.key"
134 | openssl genrsa -out ca-root.key 2048
135 | [[ $? -eq 0 ]] || exitfail failed to generate CA root key
136 | fi
137 |
138 | # Create a CA root certificate
139 | if ! [[ -f "$DIR/ca-root.crt" ]]; then
140 | echo
141 | echo "> Create a CA root certificate: $DIR/ca-root.crt"
142 | openssl req -config /ca/openssl.cnf \
143 | -key ca-root.key \
144 | -new -x509 -days 3650 -subj "/O=nginx-proxy test suite/CN=www.nginx-proxy.tld" -extensions v3_ca \
145 | -out ca-root.crt
146 | [[ $? -eq 0 ]] || exitfail failed to generate CA root certificate
147 |
148 | # Verify certificate
149 | openssl x509 -noout -text -in ca-root.crt
150 | fi
151 |
152 |
153 | ###############################################################################
154 | # create server key and certificate signed by the certificate authority
155 | ###############################################################################
156 |
157 | echo
158 | echo "> Create a host key: $DIR/$DOMAIN.key"
159 | openssl genrsa -out "$DOMAIN.key" 2048
160 |
161 | echo
162 | echo "> Create a host certificate signing request"
163 |
164 | SAN="$ALTERNATE_DOMAINS" openssl req -config /ca/openssl.cnf \
165 | -key "$DOMAIN.key" \
166 | -new -out "/ca/$DOMAIN.csr" -days 1000 -extensions san_env -subj "/CN=$DOMAIN"
167 | [[ $? -eq 0 ]] || exitfail failed to generate server certificate signing request
168 |
169 | echo
170 | echo "> Create server certificate: $DIR/$DOMAIN.crt"
171 | SAN="$ALTERNATE_DOMAINS" openssl ca -config /ca/openssl.cnf -batch \
172 | -extensions server_cert \
173 | -extensions san_env \
174 | -in "/ca/$DOMAIN.csr" \
175 | -out "$DOMAIN.crt"
176 | [[ $? -eq 0 ]] || exitfail failed to generate server certificate
177 |
178 |
179 | # Verify host certificate
180 | #openssl x509 -noout -text -in "$DOMAIN.crt"
181 |
182 |
183 | docker rm -f $CONTAINER >/dev/null
184 |
--------------------------------------------------------------------------------
/test/test_ssl/test_dhparam.py:
--------------------------------------------------------------------------------
1 | import re
2 | import subprocess
3 | import os
4 |
5 | import backoff
6 | import docker
7 | import pytest
8 |
9 | docker_client = docker.from_env()
10 |
11 |
12 | ###############################################################################
13 | #
14 | # Tests helpers
15 | #
16 | ###############################################################################
17 |
18 | @backoff.on_exception(backoff.constant, AssertionError, interval=2, max_tries=15, jitter=None)
19 | def assert_log_contains(expected_log_line, container_name="nginxproxy"):
20 | """
21 | Check that the nginx-proxy container log contains a given string.
22 | The backoff decorator will retry the check 15 times with a 2 seconds delay.
23 |
24 | :param expected_log_line: string to search for
25 | :return: None
26 | :raises: AssertError if the expected string is not found in the log
27 | """
28 | sut_container = docker_client.containers.get(container_name)
29 | docker_logs = sut_container.logs(stdout=True, stderr=True, stream=False, follow=False)
30 | assert bytes(expected_log_line, encoding="utf8") in docker_logs
31 |
32 |
33 | def require_openssl(required_version):
34 | """
35 | This function checks that the required version of OpenSSL is present, and skips the test if not.
36 | Use it as a test function decorator:
37 |
38 | @require_openssl("2.3.4")
39 | def test_something():
40 | ...
41 |
42 | :param required_version: minimal required version as a string: "1.2.3"
43 | """
44 |
45 | def versiontuple(v):
46 | clean_v = re.sub(r"[^\d\.]", "", v)
47 | return tuple(map(int, (clean_v.split("."))))
48 |
49 | try:
50 | command_output = subprocess.check_output(["openssl", "version"])
51 | except OSError:
52 | return pytest.mark.skip("openssl command is not available in test environment")
53 | else:
54 | if not command_output:
55 | raise Exception("Could not get openssl version")
56 | openssl_version = str(command_output.split()[1])
57 | return pytest.mark.skipif(
58 | versiontuple(openssl_version) < versiontuple(required_version),
59 | reason=f"openssl v{openssl_version} is less than required version {required_version}")
60 |
61 |
62 | @require_openssl("1.0.2")
63 | def negotiate_cipher(sut_container, additional_params='', grep='Cipher is'):
64 | host = f"{sut_container.attrs['NetworkSettings']['IPAddress']}:443"
65 |
66 | try:
67 | # Enforce TLS 1.2 as newer versions don't support custom dhparam or ciphersuite preference.
68 | # The empty `echo` is to provide `openssl` user input, so that the process exits: https://stackoverflow.com/a/28567565
69 | # `shell=True` enables using a single string to execute as a shell command.
70 | # `text=True` prevents the need to compare against byte strings.
71 | # `stderr=subprocess.PIPE` removes the output to stderr being interleaved with test case status (output during exceptions).
72 | return subprocess.check_output(
73 | f"echo '' | openssl s_client -connect {host} -tls1_2 {additional_params} | grep '{grep}'",
74 | shell=True,
75 | text=True,
76 | stderr=subprocess.PIPE,
77 | )
78 | except subprocess.CalledProcessError as e:
79 | # Output a more helpful error, the original exception in this case isn't that helpful.
80 | # `from None` to ignore undesired output from exception chaining.
81 | raise Exception("Failed to process CLI request:\n" + e.stderr) from None
82 |
83 |
84 | # The default `dh_bits` can vary due to configuration.
85 | # `additional_params` allows for adjusting the request to a specific `VIRTUAL_HOST`,
86 | # where DH size can differ from the configured global default DH size.
87 | def can_negotiate_dhe_ciphersuite(sut_container, dh_bits=4096, additional_params=''):
88 | openssl_params = f"-cipher 'EDH' {additional_params}"
89 |
90 | r = negotiate_cipher(sut_container, openssl_params)
91 | assert "New, TLSv1.2, Cipher is DHE-RSA-AES256-GCM-SHA384\n" == r
92 |
93 | r2 = negotiate_cipher(sut_container, openssl_params, "Server Temp Key")
94 | assert f"Server Temp Key: DH, {dh_bits} bits" in r2
95 |
96 |
97 | def cannot_negotiate_dhe_ciphersuite(sut_container):
98 | # Fail to negotiate a DHE cipher suite:
99 | r = negotiate_cipher(sut_container, "-cipher 'EDH'")
100 | assert "New, (NONE), Cipher is (NONE)\n" == r
101 |
102 | # Correctly establish a connection (TLS 1.2):
103 | r2 = negotiate_cipher(sut_container)
104 | assert "New, TLSv1.2, Cipher is ECDHE-RSA-AES256-GCM-SHA384\n" == r2
105 |
106 | r3 = negotiate_cipher(sut_container, grep="Server Temp Key")
107 | assert "X25519" in r3
108 |
109 |
110 | # To verify self-signed certificates, the file path to their CA cert must be provided.
111 | # Use the `fqdn` arg to specify the `VIRTUAL_HOST` to request for verification for that cert.
112 | #
113 | # Resolves the following stderr warnings regarding self-signed cert verification and missing SNI:
114 | # `Can't use SSL_get_servername`
115 | # `verify error:num=20:unable to get local issuer certificate`
116 | # `verify error:num=21:unable to verify the first certificate`
117 | #
118 | # The stderr output is hidden due to running the openssl command with `stderr=subprocess.PIPE`.
119 | def can_verify_chain_of_trust(sut_container, ca_cert, fqdn):
120 | openssl_params = f"-CAfile '{ca_cert}' -servername '{fqdn}'"
121 |
122 | r = negotiate_cipher(sut_container, openssl_params, "Verify return code")
123 | assert "Verify return code: 0 (ok)" in r
124 |
125 |
126 | def should_be_equivalent_content(sut_container, expected, actual):
127 | expected_checksum = sut_container.exec_run(f"md5sum {expected}").output.split()[0]
128 | actual_checksum = sut_container.exec_run(f"md5sum {actual}").output.split()[0]
129 |
130 | assert expected_checksum == actual_checksum
131 |
132 |
133 | # Parse array of container ENV, splitting at the `=` and returning the value, otherwise `None`
134 | def get_env(sut_container, var):
135 | env = sut_container.attrs['Config']['Env']
136 |
137 | for e in env:
138 | if e.startswith(var):
139 | return e.split('=')[1]
140 |
141 | return None
142 |
143 |
144 | ###############################################################################
145 | #
146 | # Tests
147 | #
148 | ###############################################################################
149 |
150 | def test_default_dhparam_is_ffdhe4096(docker_compose):
151 | container_name="dh-default"
152 | sut_container = docker_client.containers.get(container_name)
153 | assert sut_container.status == "running"
154 |
155 | assert_log_contains("Setting up DH Parameters..", container_name)
156 |
157 | # `dhparam.pem` contents should match the default (ffdhe4096.pem):
158 | should_be_equivalent_content(
159 | sut_container,
160 | "/app/dhparam/ffdhe4096.pem",
161 | "/etc/nginx/dhparam/dhparam.pem"
162 | )
163 |
164 | can_negotiate_dhe_ciphersuite(sut_container, 4096)
165 |
166 |
167 | # Overrides default DH group via ENV `DHPARAM_BITS=3072`:
168 | def test_can_change_dhparam_group(docker_compose):
169 | container_name="dh-env"
170 | sut_container = docker_client.containers.get(container_name)
171 | assert sut_container.status == "running"
172 |
173 | assert_log_contains("Setting up DH Parameters..", container_name)
174 |
175 | # `dhparam.pem` contents should not match the default (ffdhe4096.pem):
176 | should_be_equivalent_content(
177 | sut_container,
178 | "/app/dhparam/ffdhe3072.pem",
179 | "/etc/nginx/dhparam/dhparam.pem"
180 | )
181 |
182 | can_negotiate_dhe_ciphersuite(sut_container, 3072)
183 |
184 |
185 | def test_fail_if_dhparam_group_not_supported(docker_compose):
186 | container_name="invalid-group-1024"
187 | sut_container = docker_client.containers.get(container_name)
188 | assert sut_container.status == "exited"
189 |
190 | DHPARAM_BITS = get_env(sut_container, "DHPARAM_BITS")
191 | assert DHPARAM_BITS == "1024"
192 |
193 | assert_log_contains(
194 | f"ERROR: Unsupported DHPARAM_BITS size: {DHPARAM_BITS}. Use: 2048, 3072, or 4096 (default).",
195 | container_name
196 | )
197 |
198 |
199 | # Overrides default DH group by providing a custom `/etc/nginx/dhparam/dhparam.pem`:
200 | def test_custom_dhparam_is_supported(docker_compose):
201 | container_name="dh-file"
202 | sut_container = docker_client.containers.get(container_name)
203 | assert sut_container.status == "running"
204 |
205 | assert_log_contains(
206 | "Warning: A custom dhparam.pem file was provided. Best practice is to use standardized RFC7919 DHE groups instead.",
207 | container_name
208 | )
209 |
210 | # `dhparam.pem` contents should not match the default (ffdhe4096.pem):
211 | should_be_equivalent_content(
212 | sut_container,
213 | "/app/dhparam/ffdhe3072.pem",
214 | "/etc/nginx/dhparam/dhparam.pem"
215 | )
216 |
217 | can_negotiate_dhe_ciphersuite(sut_container, 3072)
218 |
219 |
220 | # Only `web2` has a site-specific DH param file (which overrides all other DH config)
221 | # Other tests here use `web5` explicitly, or implicitly (via ENV `DEFAULT_HOST`, otherwise first HTTPS server)
222 | def test_custom_dhparam_is_supported_per_site(docker_compose):
223 | container_name="dh-file"
224 | sut_container = docker_client.containers.get(container_name)
225 | assert sut_container.status == "running"
226 |
227 | # A site specific `dhparam.pem` with DH group size of 2048-bit.
228 | # DH group size should not match the:
229 | # - 4096-bit default.
230 | # - 3072-bit default, overriden by file.
231 | should_be_equivalent_content(
232 | sut_container,
233 | "/app/dhparam/ffdhe2048.pem",
234 | "/etc/nginx/certs/web2.nginx-proxy.tld.dhparam.pem"
235 | )
236 |
237 | # `-servername` required for nginx-proxy to respond with site-specific DH params used:
238 | can_negotiate_dhe_ciphersuite(sut_container, 2048, '-servername web2.nginx-proxy.tld')
239 |
240 | # --Unrelated to DH support--
241 | # - `web5` is missing a certificate, but falls back to available `/etc/nginx/certs/nginx-proxy.tld.crt` via `nginx.tmpl` "closest" result.
242 | # - `web2` has it's own cert provisioned at `/etc/nginx/certs/web2.nginx-proxy.tld.crt`.
243 | can_verify_chain_of_trust(
244 | sut_container,
245 | ca_cert = f"{os.getcwd()}/certs/ca-root.crt",
246 | fqdn = 'web2.nginx-proxy.tld'
247 | )
248 |
249 |
250 | # NOTE: These two tests will fail without the ENV `DEFAULT_HOST` to prevent
251 | # accidentally falling back to `web2` as the default server, which has explicit DH params configured.
252 | # Only copying DH params is skipped, not explicit usage via user providing custom files.
253 | def test_can_skip_dhparam(docker_compose):
254 | container_name="dh-skip"
255 | sut_container = docker_client.containers.get(container_name)
256 | assert sut_container.status == "running"
257 |
258 | assert_log_contains("Skipping Diffie-Hellman parameters setup.", container_name)
259 |
260 | cannot_negotiate_dhe_ciphersuite(sut_container)
261 |
262 |
263 | def test_can_skip_dhparam_backward_compatibility(docker_compose):
264 | container_name="dh-skip-backward"
265 | sut_container = docker_client.containers.get(container_name)
266 | assert sut_container.status == "running"
267 |
268 | assert_log_contains("Warning: The DHPARAM_GENERATION environment variable is deprecated, please consider using DHPARAM_SKIP set to true instead.", container_name)
269 | assert_log_contains("Skipping Diffie-Hellman parameters setup.", container_name)
270 |
271 | cannot_negotiate_dhe_ciphersuite(sut_container)
272 |
273 |
274 | def test_web5_https_works(docker_compose, nginxproxy):
275 | r = nginxproxy.get("https://web5.nginx-proxy.tld/port", allow_redirects=False)
276 | assert r.status_code == 200
277 | assert "answer from port 85\n" in r.text
278 |
--------------------------------------------------------------------------------