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