Merge branch 'nginx-proxy:main' into master
This commit is contained in:
commit
dbf596efdf
48 changed files with 964 additions and 223 deletions
|
@ -1,6 +1,9 @@
|
||||||
.git
|
.git
|
||||||
|
.github
|
||||||
|
test
|
||||||
.dockerignore
|
.dockerignore
|
||||||
circle.yml
|
.gitignore
|
||||||
|
*.yml
|
||||||
|
Dockerfile*
|
||||||
Makefile
|
Makefile
|
||||||
README.md
|
README.md
|
||||||
test
|
|
||||||
|
|
35
.github/ISSUE_TEMPLATE.md
vendored
35
.github/ISSUE_TEMPLATE.md
vendored
|
@ -1,16 +1,35 @@
|
||||||
# !!!PLEASE READ!!!
|
# ⚠️ PLEASE READ ⚠️
|
||||||
|
|
||||||
## Questions
|
## Questions or Features
|
||||||
|
|
||||||
If you have a question, DO NOT SUBMIT a new issue.
|
If you have a question or want to request a feature, please **DO NOT SUBMIT** a new issue.
|
||||||
|
|
||||||
Please ask the question on the Discussions section: https://github.com/nginx-proxy/nginx-proxy/discussions
|
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 or Features
|
## Bugs
|
||||||
|
|
||||||
If you are logging a bug or feature request, please search the current open issues to see if there is already a bug or feature opened.
|
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, the easier and faster it can get fixed. If you can provide a script or docker-compose file that reproduces the problems, that is very helpful.
|
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,
|
Thanks,
|
||||||
Jason
|
Nicolas
|
||||||
|
|
39
.github/workflows/dockerhub.yml
vendored
39
.github/workflows/dockerhub.yml
vendored
|
@ -12,7 +12,6 @@ on:
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- 'test/*'
|
- 'test/*'
|
||||||
- '.gitignore'
|
- '.gitignore'
|
||||||
- '.travis.yml'
|
|
||||||
- 'docker-compose-separate-containers.yml'
|
- 'docker-compose-separate-containers.yml'
|
||||||
- 'docker-compose.yml'
|
- 'docker-compose.yml'
|
||||||
- 'LICENSE'
|
- 'LICENSE'
|
||||||
|
@ -29,17 +28,24 @@ jobs:
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Retrieve version
|
||||||
|
run: echo "GIT_DESCRIBE=$(git describe --tags)" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Get Docker tags for Debian based image
|
- name: Get Docker tags for Debian based image
|
||||||
id: docker_meta_debian
|
id: docker_meta_debian
|
||||||
uses: crazy-max/ghaction-docker-meta@v2
|
uses: docker/metadata-action@v3
|
||||||
with:
|
with:
|
||||||
images: |
|
images: |
|
||||||
|
ghcr.io/nginx-proxy/nginx-proxy
|
||||||
nginxproxy/nginx-proxy
|
nginxproxy/nginx-proxy
|
||||||
jwilder/nginx-proxy
|
jwilder/nginx-proxy
|
||||||
tags: |
|
tags: |
|
||||||
type=semver,pattern={{version}}
|
type=semver,pattern={{version}}
|
||||||
type=semver,pattern={{major}}.{{minor}}
|
type=semver,pattern={{major}}.{{minor}}
|
||||||
type=raw,value=latest,enable=${{ endsWith(github.ref, github.event.repository.default_branch) }}
|
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }}
|
||||||
|
labels: |
|
||||||
|
org.opencontainers.image.authors=Nicolas Duchon <nicolas.duchon@gmail.com> (@buchdag), Jason Wilder
|
||||||
|
org.opencontainers.image.version=${{ env.GIT_DESCRIBE }}
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v1
|
uses: docker/setup-qemu-action@v1
|
||||||
|
@ -53,12 +59,20 @@ jobs:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Log in to GitHub Container Registry
|
||||||
|
uses: docker/login-action@v1
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Build and push the Debian based image
|
- name: Build and push the Debian based image
|
||||||
id: docker_build_debian
|
id: docker_build_debian
|
||||||
uses: docker/build-push-action@v2
|
uses: docker/build-push-action@v2
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: Dockerfile
|
file: Dockerfile
|
||||||
|
build-args: NGINX_PROXY_VERSION=${{ env.GIT_DESCRIBE }}
|
||||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||||
push: true
|
push: true
|
||||||
tags: ${{ steps.docker_meta_debian.outputs.tags }}
|
tags: ${{ steps.docker_meta_debian.outputs.tags }}
|
||||||
|
@ -76,17 +90,24 @@ jobs:
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Retrieve version
|
||||||
|
run: echo "GIT_DESCRIBE=$(git describe --tags)" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Get Docker tags for Alpine based image
|
- name: Get Docker tags for Alpine based image
|
||||||
id: docker_meta_alpine
|
id: docker_meta_alpine
|
||||||
uses: crazy-max/ghaction-docker-meta@v2
|
uses: docker/metadata-action@v3
|
||||||
with:
|
with:
|
||||||
images: |
|
images: |
|
||||||
|
ghcr.io/nginx-proxy/nginx-proxy
|
||||||
nginxproxy/nginx-proxy
|
nginxproxy/nginx-proxy
|
||||||
jwilder/nginx-proxy
|
jwilder/nginx-proxy
|
||||||
tags: |
|
tags: |
|
||||||
type=semver,suffix=-alpine,pattern={{version}}
|
type=semver,suffix=-alpine,pattern={{version}}
|
||||||
type=semver,suffix=-alpine,pattern={{major}}.{{minor}}
|
type=semver,suffix=-alpine,pattern={{major}}.{{minor}}
|
||||||
type=raw,value=alpine,enable=${{ endsWith(github.ref, github.event.repository.default_branch) }}
|
type=raw,value=alpine,enable=${{ github.ref == 'refs/heads/main' }}
|
||||||
|
labels: |
|
||||||
|
org.opencontainers.image.authors=Nicolas Duchon <nicolas.duchon@gmail.com> (@buchdag), Jason Wilder
|
||||||
|
org.opencontainers.image.version=${{ env.GIT_DESCRIBE }}
|
||||||
flavor: latest=false
|
flavor: latest=false
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
|
@ -101,12 +122,20 @@ jobs:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Log in to GitHub Container Registry
|
||||||
|
uses: docker/login-action@v1
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Build and push the Alpine based image
|
- name: Build and push the Alpine based image
|
||||||
id: docker_build_alpine
|
id: docker_build_alpine
|
||||||
uses: docker/build-push-action@v2
|
uses: docker/build-push-action@v2
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: Dockerfile.alpine
|
file: Dockerfile.alpine
|
||||||
|
build-args: NGINX_PROXY_VERSION=${{ env.GIT_DESCRIBE }}
|
||||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||||
push: true
|
push: true
|
||||||
tags: ${{ steps.docker_meta_alpine.outputs.tags }}
|
tags: ${{ steps.docker_meta_alpine.outputs.tags }}
|
||||||
|
|
28
Dockerfile
28
Dockerfile
|
@ -1,16 +1,16 @@
|
||||||
# setup build arguments for version of dependencies to use
|
# setup build arguments for version of dependencies to use
|
||||||
ARG DOCKER_GEN_VERSION=0.7.7
|
ARG DOCKER_GEN_VERSION=0.9.0
|
||||||
ARG FOREGO_VERSION=v0.17.0
|
ARG FOREGO_VERSION=v0.17.0
|
||||||
|
|
||||||
# Use a specific version of golang to build both binaries
|
# Use a specific version of golang to build both binaries
|
||||||
FROM golang:1.16.7 as gobuilder
|
FROM golang:1.18.1 as gobuilder
|
||||||
|
|
||||||
# Build docker-gen from scratch
|
# Build docker-gen from scratch
|
||||||
FROM gobuilder as dockergen
|
FROM gobuilder as dockergen
|
||||||
|
|
||||||
ARG DOCKER_GEN_VERSION
|
ARG DOCKER_GEN_VERSION
|
||||||
|
|
||||||
RUN git clone https://github.com/jwilder/docker-gen \
|
RUN git clone https://github.com/nginx-proxy/docker-gen \
|
||||||
&& cd /go/docker-gen \
|
&& cd /go/docker-gen \
|
||||||
&& git -c advice.detachedHead=false checkout $DOCKER_GEN_VERSION \
|
&& git -c advice.detachedHead=false checkout $DOCKER_GEN_VERSION \
|
||||||
&& go mod download \
|
&& go mod download \
|
||||||
|
@ -36,8 +36,15 @@ RUN git clone https://github.com/nginx-proxy/forego/ \
|
||||||
&& rm -rf /go/forego
|
&& rm -rf /go/forego
|
||||||
|
|
||||||
# Build the final image
|
# Build the final image
|
||||||
FROM nginx:1.21.4
|
FROM nginx:1.21.6
|
||||||
LABEL maintainer="Nicolas Duchon <nicolas.duchon@gmail.com> (@buchdag)"
|
|
||||||
|
ARG NGINX_PROXY_VERSION
|
||||||
|
# Add DOCKER_GEN_VERSION environment variable
|
||||||
|
# Because some external projects rely on it
|
||||||
|
ARG DOCKER_GEN_VERSION
|
||||||
|
ENV NGINX_PROXY_VERSION=${NGINX_PROXY_VERSION} \
|
||||||
|
DOCKER_GEN_VERSION=${DOCKER_GEN_VERSION} \
|
||||||
|
DOCKER_HOST=unix:///tmp/docker.sock
|
||||||
|
|
||||||
# Install wget and install/updates certificates
|
# Install wget and install/updates certificates
|
||||||
RUN apt-get update \
|
RUN apt-get update \
|
||||||
|
@ -48,7 +55,7 @@ RUN apt-get update \
|
||||||
&& rm -r /var/lib/apt/lists/*
|
&& rm -r /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
|
||||||
# Configure Nginx and apply fix for very long server names
|
# Configure Nginx
|
||||||
RUN echo "daemon off;" >> /etc/nginx/nginx.conf \
|
RUN echo "daemon off;" >> /etc/nginx/nginx.conf \
|
||||||
&& sed -i 's/worker_processes 1/worker_processes auto/' /etc/nginx/nginx.conf \
|
&& sed -i 's/worker_processes 1/worker_processes auto/' /etc/nginx/nginx.conf \
|
||||||
&& sed -i 's/worker_connections 1024/worker_connections 10240/' /etc/nginx/nginx.conf \
|
&& sed -i 's/worker_connections 1024/worker_connections 10240/' /etc/nginx/nginx.conf \
|
||||||
|
@ -58,17 +65,10 @@ RUN echo "daemon off;" >> /etc/nginx/nginx.conf \
|
||||||
COPY --from=forego /usr/local/bin/forego /usr/local/bin/forego
|
COPY --from=forego /usr/local/bin/forego /usr/local/bin/forego
|
||||||
COPY --from=dockergen /usr/local/bin/docker-gen /usr/local/bin/docker-gen
|
COPY --from=dockergen /usr/local/bin/docker-gen /usr/local/bin/docker-gen
|
||||||
|
|
||||||
# Add DOCKER_GEN_VERSION environment variable
|
|
||||||
# Because some external projects rely on it
|
|
||||||
ARG DOCKER_GEN_VERSION
|
|
||||||
ENV DOCKER_GEN_VERSION=${DOCKER_GEN_VERSION}
|
|
||||||
|
|
||||||
COPY network_internal.conf /etc/nginx/
|
COPY network_internal.conf /etc/nginx/
|
||||||
|
|
||||||
COPY . /app/
|
COPY app nginx.tmpl LICENSE /app/
|
||||||
WORKDIR /app/
|
WORKDIR /app/
|
||||||
|
|
||||||
ENV DOCKER_HOST unix:///tmp/docker.sock
|
|
||||||
|
|
||||||
ENTRYPOINT ["/app/docker-entrypoint.sh"]
|
ENTRYPOINT ["/app/docker-entrypoint.sh"]
|
||||||
CMD ["forego", "start", "-r"]
|
CMD ["forego", "start", "-r"]
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
# setup build arguments for version of dependencies to use
|
# setup build arguments for version of dependencies to use
|
||||||
ARG DOCKER_GEN_VERSION=0.7.7
|
ARG DOCKER_GEN_VERSION=0.9.0
|
||||||
ARG FOREGO_VERSION=v0.17.0
|
ARG FOREGO_VERSION=v0.17.0
|
||||||
|
|
||||||
# Use a specific version of golang to build both binaries
|
# Use a specific version of golang to build both binaries
|
||||||
FROM golang:1.16.7-alpine as gobuilder
|
FROM golang:1.18.1-alpine as gobuilder
|
||||||
RUN apk add --no-cache git musl-dev
|
RUN apk add --no-cache git musl-dev
|
||||||
|
|
||||||
# Build docker-gen from scratch
|
# Build docker-gen from scratch
|
||||||
|
@ -11,7 +11,7 @@ FROM gobuilder as dockergen
|
||||||
|
|
||||||
ARG DOCKER_GEN_VERSION
|
ARG DOCKER_GEN_VERSION
|
||||||
|
|
||||||
RUN git clone https://github.com/jwilder/docker-gen \
|
RUN git clone https://github.com/nginx-proxy/docker-gen \
|
||||||
&& cd /go/docker-gen \
|
&& cd /go/docker-gen \
|
||||||
&& git -c advice.detachedHead=false checkout $DOCKER_GEN_VERSION \
|
&& git -c advice.detachedHead=false checkout $DOCKER_GEN_VERSION \
|
||||||
&& go mod download \
|
&& go mod download \
|
||||||
|
@ -37,15 +37,22 @@ RUN git clone https://github.com/nginx-proxy/forego/ \
|
||||||
&& rm -rf /go/forego
|
&& rm -rf /go/forego
|
||||||
|
|
||||||
# Build the final image
|
# Build the final image
|
||||||
FROM nginx:1.21.4-alpine
|
FROM nginx:1.21.6-alpine
|
||||||
LABEL maintainer="Nicolas Duchon <nicolas.duchon@gmail.com> (@buchdag)"
|
|
||||||
|
ARG NGINX_PROXY_VERSION
|
||||||
|
# Add DOCKER_GEN_VERSION environment variable
|
||||||
|
# Because some external projects rely on it
|
||||||
|
ARG DOCKER_GEN_VERSION
|
||||||
|
ENV NGINX_PROXY_VERSION=${NGINX_PROXY_VERSION} \
|
||||||
|
DOCKER_GEN_VERSION=${DOCKER_GEN_VERSION} \
|
||||||
|
DOCKER_HOST=unix:///tmp/docker.sock
|
||||||
|
|
||||||
# Install wget and install/updates certificates
|
# Install wget and install/updates certificates
|
||||||
RUN apk add --no-cache --virtual .run-deps \
|
RUN apk add --no-cache --virtual .run-deps \
|
||||||
ca-certificates bash wget openssl \
|
ca-certificates bash wget openssl \
|
||||||
&& update-ca-certificates
|
&& update-ca-certificates
|
||||||
|
|
||||||
# Configure Nginx and apply fix for very long server names
|
# Configure Nginx
|
||||||
RUN echo "daemon off;" >> /etc/nginx/nginx.conf \
|
RUN echo "daemon off;" >> /etc/nginx/nginx.conf \
|
||||||
&& sed -i 's/worker_processes 1/worker_processes auto/' /etc/nginx/nginx.conf \
|
&& sed -i 's/worker_processes 1/worker_processes auto/' /etc/nginx/nginx.conf \
|
||||||
&& sed -i 's/worker_connections 1024/worker_connections 10240/' /etc/nginx/nginx.conf \
|
&& sed -i 's/worker_connections 1024/worker_connections 10240/' /etc/nginx/nginx.conf \
|
||||||
|
@ -55,17 +62,10 @@ RUN echo "daemon off;" >> /etc/nginx/nginx.conf \
|
||||||
COPY --from=forego /usr/local/bin/forego /usr/local/bin/forego
|
COPY --from=forego /usr/local/bin/forego /usr/local/bin/forego
|
||||||
COPY --from=dockergen /usr/local/bin/docker-gen /usr/local/bin/docker-gen
|
COPY --from=dockergen /usr/local/bin/docker-gen /usr/local/bin/docker-gen
|
||||||
|
|
||||||
# Add DOCKER_GEN_VERSION environment variable
|
|
||||||
# Because some external projects rely on it
|
|
||||||
ARG DOCKER_GEN_VERSION
|
|
||||||
ENV DOCKER_GEN_VERSION=${DOCKER_GEN_VERSION}
|
|
||||||
|
|
||||||
COPY network_internal.conf /etc/nginx/
|
COPY network_internal.conf /etc/nginx/
|
||||||
|
|
||||||
COPY . /app/
|
COPY app nginx.tmpl LICENSE /app/
|
||||||
WORKDIR /app/
|
WORKDIR /app/
|
||||||
|
|
||||||
ENV DOCKER_HOST unix:///tmp/docker.sock
|
|
||||||
|
|
||||||
ENTRYPOINT ["/app/docker-entrypoint.sh"]
|
ENTRYPOINT ["/app/docker-entrypoint.sh"]
|
||||||
CMD ["forego", "start", "-r"]
|
CMD ["forego", "start", "-r"]
|
||||||
|
|
3
LICENSE
3
LICENSE
|
@ -1,6 +1,7 @@
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2014 Jason Wilder
|
Copyright (c) 2014-2020 Jason Wilder
|
||||||
|
Copyright (c) 2021-2022 Nicolas Duchon
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|
4
Makefile
4
Makefile
|
@ -6,10 +6,10 @@ build-webserver:
|
||||||
docker build -t web test/requirements/web
|
docker build -t web test/requirements/web
|
||||||
|
|
||||||
build-nginx-proxy-test-debian:
|
build-nginx-proxy-test-debian:
|
||||||
docker build -t nginxproxy/nginx-proxy:test .
|
docker build --build-arg NGINX_PROXY_VERSION="test" -t nginxproxy/nginx-proxy:test .
|
||||||
|
|
||||||
build-nginx-proxy-test-alpine:
|
build-nginx-proxy-test-alpine:
|
||||||
docker build -f Dockerfile.alpine -t nginxproxy/nginx-proxy:test .
|
docker build --build-arg NGINX_PROXY_VERSION="test" -f Dockerfile.alpine -t nginxproxy/nginx-proxy:test .
|
||||||
|
|
||||||
test-debian: build-webserver build-nginx-proxy-test-debian
|
test-debian: build-webserver build-nginx-proxy-test-debian
|
||||||
test/pytest.sh
|
test/pytest.sh
|
||||||
|
|
59
README.md
59
README.md
|
@ -1,6 +1,6 @@
|
||||||
[](https://github.com/nginx-proxy/nginx-proxy/actions/workflows/test.yml)
|
[](https://github.com/nginx-proxy/nginx-proxy/actions/workflows/test.yml)
|
||||||
[](https://github.com/nginx-proxy/nginx-proxy/releases)
|
[](https://github.com/nginx-proxy/nginx-proxy/releases)
|
||||||

|

|
||||||
[](https://hub.docker.com/r/nginxproxy/nginx-proxy "Click to view the image on Docker Hub")
|
[](https://hub.docker.com/r/nginxproxy/nginx-proxy "Click to view the image on Docker Hub")
|
||||||
[](https://hub.docker.com/r/nginxproxy/nginx-proxy 'DockerHub')
|
[](https://hub.docker.com/r/nginxproxy/nginx-proxy 'DockerHub')
|
||||||
[](https://hub.docker.com/r/nginxproxy/nginx-proxy 'DockerHub')
|
[](https://hub.docker.com/r/nginxproxy/nginx-proxy 'DockerHub')
|
||||||
|
@ -115,7 +115,51 @@ For each host defined into `VIRTUAL_HOST`, the associated virtual port is retrie
|
||||||
|
|
||||||
### Wildcard Hosts
|
### Wildcard Hosts
|
||||||
|
|
||||||
You can also use wildcards at the beginning and the end of host name, like `*.bar.com` or `foo.bar.*`. Or even a regular expression, which can be very useful in conjunction with a wildcard DNS service like [xip.io](http://xip.io), using `~^foo\.bar\..*\.xip\.io` will match `foo.bar.127.0.0.1.xip.io`, `foo.bar.10.0.2.2.xip.io` and all other given IPs. More information about this topic can be found in the nginx documentation about [`server_names`](http://nginx.org/en/docs/http/server_names.html).
|
You can also use wildcards at the beginning and the end of host name, like `*.bar.com` or `foo.bar.*`. Or even a regular expression, which can be very useful in conjunction with a wildcard DNS service like [nip.io](https://nip.io) or [sslip.io](https://sslip.io), using `~^foo\.bar\..*\.nip\.io` will match `foo.bar.127.0.0.1.nip.io`, `foo.bar.10.0.2.2.nip.io` and all other given IPs. More information about this topic can be found in the nginx documentation about [`server_names`](http://nginx.org/en/docs/http/server_names.html).
|
||||||
|
|
||||||
|
### Path-based Routing
|
||||||
|
|
||||||
|
You can have multiple containers proxied by the same `VIRTUAL_HOST` by adding a `VIRTUAL_PATH` environment variable containing the absolute path to where the container should be mounted. For example with `VIRTUAL_HOST=foo.example.com` and `VIRTUAL_PATH=/api/v2/service`, then requests to http://foo.example.com/api/v2/service will be routed to the container. If you wish to have a container serve the root while other containers serve other paths, give the root container a `VIRTUAL_PATH` of `/`. Unmatched paths will be served by the container at `/` or will return the default nginx error page if no container has been assigned `/`.
|
||||||
|
It is also possible to specify multiple paths with regex locations like `VIRTUAL_PATH=~^/(app1|alternative1)/`. For further details see the nginx documentation on location blocks. This is not compatible with `VIRTUAL_DEST`.
|
||||||
|
|
||||||
|
The full request URI will be forwarded to the serving container in the `X-Forwarded-Path` header.
|
||||||
|
|
||||||
|
**NOTE**: Your application needs to be able to generate links starting with `VIRTUAL_PATH`. This can be achieved by it being natively on this path or having an option to prepend this path. The application does not need to expect this path in the request.
|
||||||
|
|
||||||
|
#### VIRTUAL_DEST
|
||||||
|
|
||||||
|
This environment variable can be used to rewrite the `VIRTUAL_PATH` part of the requested URL to proxied application. The default value is empty (off).
|
||||||
|
Make sure that your settings won't result in the slash missing or being doubled. Both these versions can cause troubles.
|
||||||
|
|
||||||
|
If the application runs natively on this sub-path or has a setting to do so, `VIRTUAL_DEST` should not be set or empty.
|
||||||
|
If the requests are expected to not contain a sub-path and the generated links contain the sub-path, `VIRTUAL_DEST=/` should be used.
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker run -d -e VIRTUAL_HOST=example.tld -e VIRTUAL_PATH=/app1/ -e VIRTUAL_DEST=/ --name app1 app
|
||||||
|
```
|
||||||
|
|
||||||
|
In this example, the incoming request `http://example.tld/app1/foo` will be proxied as `http://app1/foo` instead of `http://app1/app1/foo`.
|
||||||
|
|
||||||
|
#### Per-VIRTUAL_PATH location configuration
|
||||||
|
|
||||||
|
The same options as from [Per-VIRTUAL_HOST location configuration](#Per-VIRTUAL_HOST-location-configuration) are available on a `VIRTUAL_PATH` basis.
|
||||||
|
The only difference is that the filename gets an additional block `HASH=$(echo -n $VIRTUAL_PATH | sha1sum | awk '{ print $1 }')`. This is the sha1-hash of the `VIRTUAL_PATH` (no newline). This is done filename sanitization purposes.
|
||||||
|
The used filename is `${VIRTUAL_HOST}_${HASH}_location`
|
||||||
|
|
||||||
|
The filename of the previous example would be `example.tld_8610f6c344b4096614eab6e09d58885349f42faf_location`.
|
||||||
|
|
||||||
|
#### DEFAULT_ROOT
|
||||||
|
|
||||||
|
This environment variable of the nginx proxy container can be used to customize the return error page if no matching path is found. Furthermore it is possible to use anything which is compatible with the `return` statement of nginx.
|
||||||
|
|
||||||
|
For example `DEFAUL_ROOT=418` will return a 418 error page instead of the normal 404 one.
|
||||||
|
Another example is `DEFAULT_ROOT="301 https://github.com/nginx-proxy/nginx-proxy/blob/main/README.md"` which would redirect an invalid request to this documentation.
|
||||||
|
Nginx variables such as $scheme, $host, and $request_uri can be used. However, care must be taken to make sure the $ signs are escaped properly.
|
||||||
|
If you want to use `301 $scheme://$host/myapp1$request_uri` you should use:
|
||||||
|
|
||||||
|
* Bash: `DEFAULT_ROOT='301 $scheme://$host/myapp1$request_uri'`
|
||||||
|
* Docker Compose yaml: `- DEFAULT_ROOT: 301 $$scheme://$$host/myapp1$$request_uri`
|
||||||
|
|
||||||
|
|
||||||
### Virtual Host Aliases
|
### Virtual Host Aliases
|
||||||
|
|
||||||
|
@ -218,7 +262,7 @@ docker run -d -e VIRTUAL_HOST=foo.bar.com nginx
|
||||||
|
|
||||||
### Separate Containers
|
### Separate Containers
|
||||||
|
|
||||||
nginx-proxy can also be run as two separate containers using the [jwilder/docker-gen](https://hub.docker.com/r/jwilder/docker-gen) image and the official [nginx](https://registry.hub.docker.com/_/nginx/) image.
|
nginx-proxy can also be run as two separate containers using the [nginxproxy/docker-gen](https://hub.docker.com/r/nginxproxy/docker-gen) image and the official [nginx](https://registry.hub.docker.com/_/nginx/) image.
|
||||||
|
|
||||||
You may want to do this to prevent having the docker socket bound to a publicly exposed container service.
|
You may want to do this to prevent having the docker socket bound to a publicly exposed container service.
|
||||||
|
|
||||||
|
@ -249,7 +293,7 @@ Then start the docker-gen container with the shared volume and template:
|
||||||
docker run --volumes-from nginx \
|
docker run --volumes-from nginx \
|
||||||
-v /var/run/docker.sock:/tmp/docker.sock:ro \
|
-v /var/run/docker.sock:/tmp/docker.sock:ro \
|
||||||
-v $(pwd):/etc/docker-gen/templates \
|
-v $(pwd):/etc/docker-gen/templates \
|
||||||
-t jwilder/docker-gen -notify-sighup nginx -watch /etc/docker-gen/templates/nginx.tmpl /etc/nginx/conf.d/default.conf
|
-t 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.
|
Finally, start your containers with `VIRTUAL_HOST` environment variables.
|
||||||
|
@ -284,7 +328,7 @@ To use custom `dhparam.pem` files per-virtual-host, the files should be named af
|
||||||
|
|
||||||
> COMPATIBILITY WARNING: The default generated `dhparam.pem` key is 4096 bits for A+ security. Some older clients (like Java 6 and 7) do not support DH keys with over 1024 bits. In order to support these clients, you must provide your own `dhparam.pem`.
|
> COMPATIBILITY WARNING: The default generated `dhparam.pem` key is 4096 bits for A+ security. Some older clients (like Java 6 and 7) do not support DH keys with over 1024 bits. In order to support these clients, you must provide your own `dhparam.pem`.
|
||||||
|
|
||||||
In the separate container setup, no pre-generated key will be available and neither the [jwilder/docker-gen](https://hub.docker.com/r/jwilder/docker-gen) image, nor the offical [nginx](https://registry.hub.docker.com/_/nginx/) image will provide one. If you still want A+ security in a separate container setup, you should mount an RFC7919 DH key file to the nginx container at `/etc/nginx/dhparam/dhparam.pem`.
|
In the separate container setup, no pre-generated key will be available and neither the [nginxproxy/docker-gen](https://hub.docker.com/r/nginxproxy/docker-gen) image, nor the offical [nginx](https://registry.hub.docker.com/_/nginx/) image will provide one. If you still want A+ security in a separate container setup, you should mount an RFC7919 DH key file to the nginx container at `/etc/nginx/dhparam/dhparam.pem`.
|
||||||
|
|
||||||
Set `DHPARAM_SKIP` environment variable to `true` to disable using default Diffie-Hellman parameters. The default value is `false`.
|
Set `DHPARAM_SKIP` environment variable to `true` to disable using default Diffie-Hellman parameters. The default value is `false`.
|
||||||
|
|
||||||
|
@ -362,6 +406,7 @@ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
|
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
|
||||||
proxy_set_header X-Forwarded-Ssl $proxy_x_forwarded_ssl;
|
proxy_set_header X-Forwarded-Ssl $proxy_x_forwarded_ssl;
|
||||||
proxy_set_header X-Forwarded-Port $proxy_x_forwarded_port;
|
proxy_set_header X-Forwarded-Port $proxy_x_forwarded_port;
|
||||||
|
proxy_set_header X-Forwarded-Path $request_uri;
|
||||||
|
|
||||||
# Mitigate httpoxy attack (see README for details)
|
# Mitigate httpoxy attack (see README for details)
|
||||||
proxy_set_header Proxy "";
|
proxy_set_header Proxy "";
|
||||||
|
@ -448,10 +493,10 @@ Please note that using regular expressions in `VIRTUAL_HOST` will always result
|
||||||
|
|
||||||
### Troubleshooting
|
### Troubleshooting
|
||||||
|
|
||||||
In case you can't access your VIRTUAL_HOST, set `DEBUG=true` in the client container's environment and have a look at the generated nginx configuration file `/etc/nginx/conf.d/default`:
|
In case you can't access your VIRTUAL_HOST, set `DEBUG=true` in the client container's environment and have a look at the generated nginx configuration file `/etc/nginx/conf.d/default.conf`:
|
||||||
|
|
||||||
```console
|
```console
|
||||||
docker exec <nginx-proxy-instance> cat /etc/nginx/conf.d/default
|
docker exec <nginx-proxy-instance> cat /etc/nginx/conf.d/default.conf
|
||||||
```
|
```
|
||||||
Especially at `upstream` definition blocks which should look like:
|
Especially at `upstream` definition blocks which should look like:
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,12 @@ function _parse_false() {
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _print_version {
|
||||||
|
if [[ -n "${NGINX_PROXY_VERSION:-}" ]]; then
|
||||||
|
echo "Info: running nginx-proxy version ${NGINX_PROXY_VERSION}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
function _check_unix_socket() {
|
function _check_unix_socket() {
|
||||||
# Warn if the DOCKER_HOST socket does not exist
|
# Warn if the DOCKER_HOST socket does not exist
|
||||||
if [[ ${DOCKER_HOST} == unix://* ]]; then
|
if [[ ${DOCKER_HOST} == unix://* ]]; then
|
||||||
|
@ -96,6 +102,8 @@ function _setup_dhparam() {
|
||||||
|
|
||||||
# Run the init logic if the default CMD was provided
|
# Run the init logic if the default CMD was provided
|
||||||
if [[ $* == 'forego start -r' ]]; then
|
if [[ $* == 'forego start -r' ]]; then
|
||||||
|
_print_version
|
||||||
|
|
||||||
_check_unix_socket
|
_check_unix_socket
|
||||||
|
|
||||||
_resolvers
|
_resolvers
|
|
@ -9,8 +9,9 @@ services:
|
||||||
- /etc/nginx/conf.d
|
- /etc/nginx/conf.d
|
||||||
|
|
||||||
dockergen:
|
dockergen:
|
||||||
image: jwilder/docker-gen
|
image: nginxproxy/docker-gen
|
||||||
command: -notify-sighup nginx -watch /etc/docker-gen/templates/nginx.tmpl /etc/nginx/conf.d/default.conf
|
command: -notify-sighup nginx -watch /etc/docker-gen/templates/nginx.tmpl
|
||||||
|
/etc/nginx/conf.d/default.conf
|
||||||
volumes_from:
|
volumes_from:
|
||||||
- nginx
|
- nginx
|
||||||
volumes:
|
volumes:
|
||||||
|
|
|
@ -3,4 +3,5 @@ allow 127.0.0.0/8;
|
||||||
allow 10.0.0.0/8;
|
allow 10.0.0.0/8;
|
||||||
allow 192.168.0.0/16;
|
allow 192.168.0.0/16;
|
||||||
allow 172.16.0.0/12;
|
allow 172.16.0.0/12;
|
||||||
|
allow fc00::/7; # IPv6 local address range
|
||||||
deny all;
|
deny all;
|
||||||
|
|
264
nginx.tmpl
264
nginx.tmpl
|
@ -1,9 +1,11 @@
|
||||||
{{ $CurrentContainer := where $ "ID" .Docker.CurrentContainerID | first }}
|
{{ $CurrentContainer := where $ "ID" .Docker.CurrentContainerID | first }}
|
||||||
|
|
||||||
|
{{ $nginx_proxy_version := coalesce $.Env.NGINX_PROXY_VERSION "" }}
|
||||||
{{ $external_http_port := coalesce $.Env.HTTP_PORT "80" }}
|
{{ $external_http_port := coalesce $.Env.HTTP_PORT "80" }}
|
||||||
{{ $external_https_port := coalesce $.Env.HTTPS_PORT "443" }}
|
{{ $external_https_port := coalesce $.Env.HTTPS_PORT "443" }}
|
||||||
{{ $debug_all := $.Env.DEBUG }}
|
{{ $debug_all := $.Env.DEBUG }}
|
||||||
{{ $sha1_upstream_name := parseBool (coalesce $.Env.SHA1_UPSTREAM_NAME "false") }}
|
{{ $sha1_upstream_name := parseBool (coalesce $.Env.SHA1_UPSTREAM_NAME "false") }}
|
||||||
|
{{ $default_root_response := coalesce $.Env.DEFAULT_ROOT "404" }}
|
||||||
|
|
||||||
{{ define "ssl_policy" }}
|
{{ define "ssl_policy" }}
|
||||||
{{ if eq .ssl_policy "Mozilla-Modern" }}
|
{{ if eq .ssl_policy "Mozilla-Modern" }}
|
||||||
|
@ -48,6 +50,103 @@
|
||||||
{{ end }}
|
{{ end }}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
|
{{ define "location" }}
|
||||||
|
location {{ .Path }} {
|
||||||
|
{{ if eq .NetworkTag "internal" }}
|
||||||
|
# Only allow traffic from internal clients
|
||||||
|
include /etc/nginx/network_internal.conf;
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
{{ if eq .Proto "uwsgi" }}
|
||||||
|
include uwsgi_params;
|
||||||
|
uwsgi_pass {{ trim .Proto }}://{{ trim .Upstream }};
|
||||||
|
{{ else if eq .Proto "fastcgi" }}
|
||||||
|
root {{ trim .VhostRoot }};
|
||||||
|
include fastcgi_params;
|
||||||
|
fastcgi_pass {{ trim .Upstream }};
|
||||||
|
{{ else if eq .Proto "grpc" }}
|
||||||
|
grpc_pass {{ trim .Proto }}://{{ trim .Upstream }};
|
||||||
|
{{ else }}
|
||||||
|
proxy_pass {{ trim .Proto }}://{{ trim .Upstream }}{{ trim .Dest }};
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
{{ if (exists (printf "/etc/nginx/htpasswd/%s" .Host)) }}
|
||||||
|
auth_basic "Restricted {{ .Host }}";
|
||||||
|
auth_basic_user_file {{ (printf "/etc/nginx/htpasswd/%s" .Host) }};
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
{{ if (exists (printf "/etc/nginx/vhost.d/%s_%s_location" .Host (sha1 .Path) )) }}
|
||||||
|
include {{ printf "/etc/nginx/vhost.d/%s_%s_location" .Host (sha1 .Path) }};
|
||||||
|
{{ else if (exists (printf "/etc/nginx/vhost.d/%s_location" .Host)) }}
|
||||||
|
include {{ printf "/etc/nginx/vhost.d/%s_location" .Host}};
|
||||||
|
{{ else if (exists "/etc/nginx/vhost.d/default_location") }}
|
||||||
|
include /etc/nginx/vhost.d/default_location;
|
||||||
|
{{ end }}
|
||||||
|
}
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
{{ define "upstream" }}
|
||||||
|
{{ $networks := .Networks }}
|
||||||
|
{{ $debug_all := .Debug }}
|
||||||
|
upstream {{ .Upstream }} {
|
||||||
|
{{ $server_found := "false" }}
|
||||||
|
{{ range $container := .Containers }}
|
||||||
|
{{ $debug := (eq (coalesce $container.Env.DEBUG $debug_all "false") "true") }}
|
||||||
|
{{/* If only 1 port exposed, use that as a default, else 80 */}}
|
||||||
|
{{ $defaultPort := (when (eq (len $container.Addresses) 1) (first $container.Addresses) (dict "Port" "80")).Port }}
|
||||||
|
{{ $port := (coalesce $container.Env.VIRTUAL_PORT $defaultPort) }}
|
||||||
|
{{ $address := where $container.Addresses "Port" $port | first }}
|
||||||
|
{{ if $debug }}
|
||||||
|
# Exposed ports: {{ $container.Addresses }}
|
||||||
|
# Default virtual port: {{ $defaultPort }}
|
||||||
|
# VIRTUAL_PORT: {{ $container.Env.VIRTUAL_PORT }}
|
||||||
|
{{ if not $address }}
|
||||||
|
# /!\ Virtual port not exposed
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
{{ range $knownNetwork := $networks }}
|
||||||
|
{{ range $containerNetwork := $container.Networks }}
|
||||||
|
{{ if (and (ne $containerNetwork.Name "ingress") (or (eq $knownNetwork.Name $containerNetwork.Name) (eq $knownNetwork.Name "host"))) }}
|
||||||
|
## Can be connected with "{{ $containerNetwork.Name }}" network
|
||||||
|
{{ if $address }}
|
||||||
|
{{/* If we got the containers from swarm and this container's port is published to host, use host IP:PORT */}}
|
||||||
|
{{ if and $container.Node.ID $address.HostPort }}
|
||||||
|
{{ $server_found = "true" }}
|
||||||
|
# {{ $container.Node.Name }}/{{ $container.Name }}
|
||||||
|
server {{ $container.Node.Address.IP }}:{{ $address.HostPort }};
|
||||||
|
{{/* If there is no swarm node or the port is not published on host, use container's IP:PORT */}}
|
||||||
|
{{ else if $containerNetwork }}
|
||||||
|
{{ $server_found = "true" }}
|
||||||
|
# {{ $container.Name }}
|
||||||
|
server {{ $containerNetwork.IP }}:{{ $address.Port }};
|
||||||
|
{{ end }}
|
||||||
|
{{ else if $containerNetwork }}
|
||||||
|
# {{ $container.Name }}
|
||||||
|
{{ if $containerNetwork.IP }}
|
||||||
|
{{ $server_found = "true" }}
|
||||||
|
server {{ $containerNetwork.IP }}:{{ $port }};
|
||||||
|
{{ else }}
|
||||||
|
# /!\ No IP for this network!
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
{{ else }}
|
||||||
|
# Cannot connect to network '{{ $containerNetwork.Name }}' of this container
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
{{/* nginx-proxy/nginx-proxy#1105 */}}
|
||||||
|
{{ if (eq $server_found "false") }}
|
||||||
|
# Fallback entry
|
||||||
|
server 127.0.0.1 down;
|
||||||
|
{{ end }}
|
||||||
|
}
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
{{ if ne $nginx_proxy_version "" }}
|
||||||
|
# nginx-proxy version : {{ $nginx_proxy_version }}
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
# If we receive X-Forwarded-Proto, pass it through; otherwise, pass along the
|
# If we receive X-Forwarded-Proto, pass it through; otherwise, pass along the
|
||||||
# scheme used to connect to this server
|
# scheme used to connect to this server
|
||||||
map $http_x_forwarded_proto $proxy_x_forwarded_proto {
|
map $http_x_forwarded_proto $proxy_x_forwarded_proto {
|
||||||
|
@ -95,6 +194,7 @@ access_log off;
|
||||||
{{/* Get the SSL_POLICY defined by this container, falling back to "Mozilla-Intermediate" */}}
|
{{/* Get the SSL_POLICY defined by this container, falling back to "Mozilla-Intermediate" */}}
|
||||||
{{ $ssl_policy := or ($.Env.SSL_POLICY) "Mozilla-Intermediate" }}
|
{{ $ssl_policy := or ($.Env.SSL_POLICY) "Mozilla-Intermediate" }}
|
||||||
{{ template "ssl_policy" (dict "ssl_policy" $ssl_policy) }}
|
{{ template "ssl_policy" (dict "ssl_policy" $ssl_policy) }}
|
||||||
|
error_log /dev/stderr;
|
||||||
|
|
||||||
{{ if $.Env.RESOLVERS }}
|
{{ if $.Env.RESOLVERS }}
|
||||||
resolver {{ $.Env.RESOLVERS }};
|
resolver {{ $.Env.RESOLVERS }};
|
||||||
|
@ -114,6 +214,7 @@ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
|
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
|
||||||
proxy_set_header X-Forwarded-Ssl $proxy_x_forwarded_ssl;
|
proxy_set_header X-Forwarded-Ssl $proxy_x_forwarded_ssl;
|
||||||
proxy_set_header X-Forwarded-Port $proxy_x_forwarded_port;
|
proxy_set_header X-Forwarded-Port $proxy_x_forwarded_port;
|
||||||
|
proxy_set_header X-Original-URI $request_uri;
|
||||||
|
|
||||||
# Mitigate httpoxy attack (see README for details)
|
# Mitigate httpoxy attack (see README for details)
|
||||||
proxy_set_header Proxy "";
|
proxy_set_header Proxy "";
|
||||||
|
@ -157,73 +258,27 @@ server {
|
||||||
{{ $is_regexp := hasPrefix "~" $host }}
|
{{ $is_regexp := hasPrefix "~" $host }}
|
||||||
{{ $upstream_name := when (or $is_regexp $sha1_upstream_name) (sha1 $host) $host }}
|
{{ $upstream_name := when (or $is_regexp $sha1_upstream_name) (sha1 $host) $host }}
|
||||||
|
|
||||||
# {{ $host }}
|
{{ $paths := groupBy $containers "Env.VIRTUAL_PATH" }}
|
||||||
upstream {{ $upstream_name }} {
|
{{ $nPaths := len $paths }}
|
||||||
|
|
||||||
{{ $server_found := "false" }}
|
{{ if eq $nPaths 0 }}
|
||||||
{{ range $container := $containers }}
|
# {{ $host }}
|
||||||
{{ $debug := (eq (coalesce $container.Env.DEBUG $debug_all "false") "true") }}
|
{{ template "upstream" (dict "Upstream" $upstream_name "Containers" $containers "Networks" $CurrentContainer.Networks "Debug" $debug_all) }}
|
||||||
{{/* If only 1 port exposed, use that as a default, else 80 */}}
|
|
||||||
{{ $defaultPort := (when (eq (len $container.Addresses) 1) (first $container.Addresses) (dict "Port" "80")).Port }}
|
|
||||||
{{ $port := (coalesce $container.Env.VIRTUAL_PORT $defaultPort) }}
|
|
||||||
{{ $address := where $container.Addresses "Port" $port | first }}
|
|
||||||
{{ if $debug }}
|
|
||||||
# Exposed ports: {{ $container.Addresses }}
|
|
||||||
# Default virtual port: {{ $defaultPort }}
|
|
||||||
# VIRTUAL_PORT: {{ $container.Env.VIRTUAL_PORT }}
|
|
||||||
{{ if not $address }}
|
|
||||||
# /!\ Virtual port not exposed
|
|
||||||
{{ end }}
|
|
||||||
{{ end }}
|
|
||||||
{{ range $knownNetwork := $CurrentContainer.Networks }}
|
|
||||||
{{ range $containerNetwork := $container.Networks }}
|
|
||||||
{{ if (and (ne $containerNetwork.Name "ingress") (or (eq $knownNetwork.Name $containerNetwork.Name) (eq $knownNetwork.Name "host"))) }}
|
|
||||||
## Can be connected with "{{ $containerNetwork.Name }}" network
|
|
||||||
{{ if $address }}
|
|
||||||
{{/* If we got the containers from swarm and this container's port is published to host, use host IP:PORT */}}
|
|
||||||
{{ if and $container.Node.ID $address.HostPort }}
|
|
||||||
{{ $server_found = "true" }}
|
|
||||||
# {{ $container.Node.Name }}/{{ $container.Name }}
|
|
||||||
server {{ $container.Node.Address.IP }}:{{ $address.HostPort }};
|
|
||||||
{{/* If there is no swarm node or the port is not published on host, use container's IP:PORT */}}
|
|
||||||
{{ else if $containerNetwork }}
|
|
||||||
{{ $server_found = "true" }}
|
|
||||||
# {{ $container.Name }}
|
|
||||||
server {{ $containerNetwork.IP }}:{{ $address.Port }};
|
|
||||||
{{ end }}
|
|
||||||
{{ else if $containerNetwork }}
|
|
||||||
# {{ $container.Name }}
|
|
||||||
{{ if $containerNetwork.IP }}
|
|
||||||
{{ $server_found = "true" }}
|
|
||||||
server {{ $containerNetwork.IP }}:{{ $port }};
|
|
||||||
{{ else }}
|
{{ else }}
|
||||||
# /!\ No IP for this network!
|
{{ range $path, $containers := $paths }}
|
||||||
|
{{ $sum := sha1 $path }}
|
||||||
|
{{ $upstream := printf "%s-%s" $upstream_name $sum }}
|
||||||
|
# {{ $host }}{{ $path }}
|
||||||
|
{{ template "upstream" (dict "Upstream" $upstream "Containers" $containers "Networks" $CurrentContainer.Networks "Debug" $debug_all) }}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
{{ else }}
|
|
||||||
# Cannot connect to network '{{ $containerNetwork.Name }}' of this container
|
|
||||||
{{ end }}
|
|
||||||
{{ end }}
|
|
||||||
{{ end }}
|
|
||||||
{{ end }}
|
|
||||||
{{/* nginx-proxy/nginx-proxy#1105 */}}
|
|
||||||
{{ if (eq $server_found "false") }}
|
|
||||||
# Fallback entry
|
|
||||||
server 127.0.0.1 down;
|
|
||||||
{{ end }}
|
|
||||||
}
|
|
||||||
|
|
||||||
{{ $default_host := or ($.Env.DEFAULT_HOST) "" }}
|
{{ $default_host := or ($.Env.DEFAULT_HOST) "" }}
|
||||||
{{ $default_server := index (dict $host "" $default_host "default_server") $host }}
|
{{ $default_server := index (dict $host "" $default_host "default_server") $host }}
|
||||||
|
|
||||||
{{/* Get the VIRTUAL_PROTO defined by containers w/ the same vhost, falling back to "http" */}}
|
|
||||||
{{ $proto := trim (or (first (groupByKeys $containers "Env.VIRTUAL_PROTO")) "http") }}
|
|
||||||
|
|
||||||
{{/* Get the SERVER_TOKENS defined by containers w/ the same vhost, falling back to "" */}}
|
{{/* Get the SERVER_TOKENS defined by containers w/ the same vhost, falling back to "" */}}
|
||||||
{{ $server_tokens := trim (or (first (groupByKeys $containers "Env.SERVER_TOKENS")) "") }}
|
{{ $server_tokens := trim (or (first (groupByKeys $containers "Env.SERVER_TOKENS")) "") }}
|
||||||
|
|
||||||
{{/* Get the NETWORK_ACCESS defined by containers w/ the same vhost, falling back to "external" */}}
|
|
||||||
{{ $network_tag := or (first (groupByKeys $containers "Env.NETWORK_ACCESS")) "external" }}
|
|
||||||
|
|
||||||
{{/* Get the HTTPS_METHOD defined by containers w/ the same vhost, falling back to "redirect" */}}
|
{{/* Get the HTTPS_METHOD defined by containers w/ the same vhost, falling back to "redirect" */}}
|
||||||
{{ $https_method := or (first (groupByKeys $containers "Env.HTTPS_METHOD")) (or $.Env.HTTPS_METHOD "redirect") }}
|
{{ $https_method := or (first (groupByKeys $containers "Env.HTTPS_METHOD")) (or $.Env.HTTPS_METHOD "redirect") }}
|
||||||
|
@ -298,11 +353,6 @@ server {
|
||||||
{{ end }}
|
{{ end }}
|
||||||
{{ $access_log }}
|
{{ $access_log }}
|
||||||
|
|
||||||
{{ if eq $network_tag "internal" }}
|
|
||||||
# Only allow traffic from internal clients
|
|
||||||
include /etc/nginx/network_internal.conf;
|
|
||||||
{{ end }}
|
|
||||||
|
|
||||||
{{ template "ssl_policy" (dict "ssl_policy" $ssl_policy) }}
|
{{ template "ssl_policy" (dict "ssl_policy" $ssl_policy) }}
|
||||||
|
|
||||||
ssl_session_timeout 5m;
|
ssl_session_timeout 5m;
|
||||||
|
@ -332,30 +382,31 @@ server {
|
||||||
include /etc/nginx/vhost.d/default;
|
include /etc/nginx/vhost.d/default;
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
location / {
|
{{ if eq $nPaths 0 }}
|
||||||
{{ if eq $proto "uwsgi" }}
|
{{/* Get the VIRTUAL_PROTO defined by containers w/ the same vhost, falling back to "http" */}}
|
||||||
include uwsgi_params;
|
{{ $proto := trim (or (first (groupByKeys $containers "Env.VIRTUAL_PROTO")) "http") }}
|
||||||
uwsgi_pass {{ trim $proto }}://{{ trim $upstream_name }};
|
|
||||||
{{ else if eq $proto "fastcgi" }}
|
|
||||||
root {{ trim $vhost_root }};
|
|
||||||
include fastcgi_params;
|
|
||||||
fastcgi_pass {{ trim $upstream_name }};
|
|
||||||
{{ else if eq $proto "grpc" }}
|
|
||||||
grpc_pass {{ trim $proto }}://{{ trim $upstream_name }};
|
|
||||||
{{ else }}
|
|
||||||
proxy_pass {{ trim $proto }}://{{ trim $upstream_name }};
|
|
||||||
{{ end }}
|
|
||||||
|
|
||||||
{{ if (exists (printf "/etc/nginx/htpasswd/%s" $host)) }}
|
{{/* Get the NETWORK_ACCESS defined by containers w/ the same vhost, falling back to "external" */}}
|
||||||
auth_basic "Restricted {{ $host }}";
|
{{ $network_tag := or (first (groupByKeys $containers "Env.NETWORK_ACCESS")) "external" }}
|
||||||
auth_basic_user_file {{ (printf "/etc/nginx/htpasswd/%s" $host) }};
|
{{ template "location" (dict "Path" "/" "Proto" $proto "Upstream" $upstream_name "Host" $host "VhostRoot" $vhost_root "Dest" "" "NetworkTag" $network_tag) }}
|
||||||
{{ end }}
|
{{ else }}
|
||||||
{{ if (exists (printf "/etc/nginx/vhost.d/%s_location" $host)) }}
|
{{ range $path, $container := $paths }}
|
||||||
include {{ printf "/etc/nginx/vhost.d/%s_location" $host}};
|
{{/* Get the VIRTUAL_PROTO defined by containers w/ the same vhost-vpath, falling back to "http" */}}
|
||||||
{{ else if (exists "/etc/nginx/vhost.d/default_location") }}
|
{{ $proto := trim (or (first (groupByKeys $container "Env.VIRTUAL_PROTO")) "http") }}
|
||||||
include /etc/nginx/vhost.d/default_location;
|
|
||||||
|
{{/* Get the NETWORK_ACCESS defined by containers w/ the same vhost, falling back to "external" */}}
|
||||||
|
{{ $network_tag := or (first (groupByKeys $container "Env.NETWORK_ACCESS")) "external" }}
|
||||||
|
{{ $sum := sha1 $path }}
|
||||||
|
{{ $upstream := printf "%s-%s" $upstream_name $sum }}
|
||||||
|
{{ $dest := (or (first (groupByKeys $container "Env.VIRTUAL_DEST")) "") }}
|
||||||
|
{{ template "location" (dict "Path" $path "Proto" $proto "Upstream" $upstream "Host" $host "VhostRoot" $vhost_root "Dest" $dest "NetworkTag" $network_tag) }}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
{{ if (not (contains $paths "/")) }}
|
||||||
|
location / {
|
||||||
|
return {{ $default_root_response }};
|
||||||
}
|
}
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
}
|
}
|
||||||
|
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
@ -369,44 +420,41 @@ server {
|
||||||
{{ end }}
|
{{ end }}
|
||||||
listen {{ $external_http_port }} {{ $default_server }};
|
listen {{ $external_http_port }} {{ $default_server }};
|
||||||
{{ if $enable_ipv6 }}
|
{{ if $enable_ipv6 }}
|
||||||
listen [::]:80 {{ $default_server }};
|
listen [::]:{{ $external_http_port }} {{ $default_server }};
|
||||||
{{ end }}
|
{{ end }}
|
||||||
{{ $access_log }}
|
{{ $access_log }}
|
||||||
|
|
||||||
{{ if eq $network_tag "internal" }}
|
|
||||||
# Only allow traffic from internal clients
|
|
||||||
include /etc/nginx/network_internal.conf;
|
|
||||||
{{ end }}
|
|
||||||
|
|
||||||
{{ if (exists (printf "/etc/nginx/vhost.d/%s" $host)) }}
|
{{ if (exists (printf "/etc/nginx/vhost.d/%s" $host)) }}
|
||||||
include {{ printf "/etc/nginx/vhost.d/%s" $host }};
|
include {{ printf "/etc/nginx/vhost.d/%s" $host }};
|
||||||
{{ else if (exists "/etc/nginx/vhost.d/default") }}
|
{{ else if (exists "/etc/nginx/vhost.d/default") }}
|
||||||
include /etc/nginx/vhost.d/default;
|
include /etc/nginx/vhost.d/default;
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
location / {
|
{{ if eq $nPaths 0 }}
|
||||||
{{ if eq $proto "uwsgi" }}
|
{{/* Get the VIRTUAL_PROTO defined by containers w/ the same vhost, falling back to "http" */}}
|
||||||
include uwsgi_params;
|
{{ $proto := trim (or (first (groupByKeys $containers "Env.VIRTUAL_PROTO")) "http") }}
|
||||||
uwsgi_pass {{ trim $proto }}://{{ trim $upstream_name }};
|
|
||||||
{{ else if eq $proto "fastcgi" }}
|
{{/* Get the NETWORK_ACCESS defined by containers w/ the same vhost, falling back to "external" */}}
|
||||||
root {{ trim $vhost_root }};
|
{{ $network_tag := or (first (groupByKeys $containers "Env.NETWORK_ACCESS")) "external" }}
|
||||||
include fastcgi_params;
|
{{ template "location" (dict "Path" "/" "Proto" $proto "Upstream" $upstream_name "Host" $host "VhostRoot" $vhost_root "Dest" "" "NetworkTag" $network_tag) }}
|
||||||
fastcgi_pass {{ trim $upstream_name }};
|
|
||||||
{{ else if eq $proto "grpc" }}
|
|
||||||
grpc_pass {{ trim $proto }}://{{ trim $upstream_name }};
|
|
||||||
{{ else }}
|
{{ else }}
|
||||||
proxy_pass {{ trim $proto }}://{{ trim $upstream_name }};
|
{{ range $path, $container := $paths }}
|
||||||
{{ end }}
|
{{/* Get the VIRTUAL_PROTO defined by containers w/ the same vhost-vpath, falling back to "http" */}}
|
||||||
{{ if (exists (printf "/etc/nginx/htpasswd/%s" $host)) }}
|
{{ $proto := trim (or (first (groupByKeys $container "Env.VIRTUAL_PROTO")) "http") }}
|
||||||
auth_basic "Restricted {{ $host }}";
|
|
||||||
auth_basic_user_file {{ (printf "/etc/nginx/htpasswd/%s" $host) }};
|
{{/* Get the NETWORK_ACCESS defined by containers w/ the same vhost, falling back to "external" */}}
|
||||||
{{ end }}
|
{{ $network_tag := or (first (groupByKeys $container "Env.NETWORK_ACCESS")) "external" }}
|
||||||
{{ if (exists (printf "/etc/nginx/vhost.d/%s_location" $host)) }}
|
{{ $sum := sha1 $path }}
|
||||||
include {{ printf "/etc/nginx/vhost.d/%s_location" $host}};
|
{{ $upstream := printf "%s-%s" $upstream_name $sum }}
|
||||||
{{ else if (exists "/etc/nginx/vhost.d/default_location") }}
|
{{ $dest := (or (first (groupByKeys $container "Env.VIRTUAL_DEST")) "") }}
|
||||||
include /etc/nginx/vhost.d/default_location;
|
{{ template "location" (dict "Path" $path "Proto" $proto "Upstream" $upstream "Host" $host "VhostRoot" $vhost_root "Dest" $dest "NetworkTag" $network_tag) }}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
{{ if (not (contains $paths "/")) }}
|
||||||
|
location / {
|
||||||
|
return {{ $default_root_response }};
|
||||||
}
|
}
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
}
|
}
|
||||||
|
|
||||||
{{ if (and (not $is_https) (exists "/etc/nginx/certs/default.crt") (exists "/etc/nginx/certs/default.key")) }}
|
{{ if (and (not $is_https) (exists "/etc/nginx/certs/default.crt") (exists "/etc/nginx/certs/default.key")) }}
|
||||||
|
|
|
@ -23,12 +23,15 @@ logging.getLogger('DNS').setLevel(logging.DEBUG)
|
||||||
logging.getLogger('requests.packages.urllib3.connectionpool').setLevel(logging.WARN)
|
logging.getLogger('requests.packages.urllib3.connectionpool').setLevel(logging.WARN)
|
||||||
|
|
||||||
CA_ROOT_CERTIFICATE = os.path.join(os.path.dirname(__file__), 'certs/ca-root.crt')
|
CA_ROOT_CERTIFICATE = os.path.join(os.path.dirname(__file__), 'certs/ca-root.crt')
|
||||||
I_AM_RUNNING_INSIDE_A_DOCKER_CONTAINER = os.path.isfile("/.dockerenv")
|
PYTEST_RUNNING_IN_CONTAINER = os.environ.get('PYTEST_RUNNING_IN_CONTAINER') == "1"
|
||||||
FORCE_CONTAINER_IPV6 = False # ugly global state to consider containers' IPv6 address instead of IPv4
|
FORCE_CONTAINER_IPV6 = False # ugly global state to consider containers' IPv6 address instead of IPv4
|
||||||
|
|
||||||
|
|
||||||
docker_client = docker.from_env()
|
docker_client = docker.from_env()
|
||||||
|
|
||||||
|
# Name of pytest container to reference if it's being used for running tests
|
||||||
|
test_container = 'nginx-proxy-pytest'
|
||||||
|
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
#
|
#
|
||||||
|
@ -189,6 +192,10 @@ def nginx_proxy_dns_resolver(domain_name):
|
||||||
nginxproxy_containers = docker_client.containers.list(filters={"status": "running", "ancestor": "nginxproxy/nginx-proxy:test"})
|
nginxproxy_containers = docker_client.containers.list(filters={"status": "running", "ancestor": "nginxproxy/nginx-proxy:test"})
|
||||||
if len(nginxproxy_containers) == 0:
|
if len(nginxproxy_containers) == 0:
|
||||||
log.warn(f"no container found from image nginxproxy/nginx-proxy:test while resolving {domain_name!r}")
|
log.warn(f"no container found from image nginxproxy/nginx-proxy:test while resolving {domain_name!r}")
|
||||||
|
exited_nginxproxy_containers = docker_client.containers.list(filters={"status": "exited", "ancestor": "nginxproxy/nginx-proxy:test"})
|
||||||
|
if len(exited_nginxproxy_containers) > 0:
|
||||||
|
exited_nginxproxy_container_logs = exited_nginxproxy_containers[0].logs()
|
||||||
|
log.warn(f"nginxproxy/nginx-proxy:test container might have exited unexpectedly. Container logs: " + "\n" + exited_nginxproxy_container_logs.decode())
|
||||||
return
|
return
|
||||||
nginxproxy_container = nginxproxy_containers[0]
|
nginxproxy_container = nginxproxy_containers[0]
|
||||||
ip = container_ip(nginxproxy_container)
|
ip = container_ip(nginxproxy_container)
|
||||||
|
@ -236,6 +243,11 @@ def monkey_patch_urllib_dns_resolver():
|
||||||
logging.getLogger('DNS').debug(f"resolving domain name {repr(args)}")
|
logging.getLogger('DNS').debug(f"resolving domain name {repr(args)}")
|
||||||
_args = list(args)
|
_args = list(args)
|
||||||
|
|
||||||
|
# Fail early when querying IP directly and it is forced ipv6 when not supported,
|
||||||
|
# Otherwise a pytest container not using the host network fails to pass `test_raw-ip-vhost`.
|
||||||
|
if FORCE_CONTAINER_IPV6 and not HAS_IPV6:
|
||||||
|
pytest.skip("This system does not support IPv6")
|
||||||
|
|
||||||
# custom DNS resolvers
|
# custom DNS resolvers
|
||||||
ip = nginx_proxy_dns_resolver(args[0])
|
ip = nginx_proxy_dns_resolver(args[0])
|
||||||
if ip is None:
|
if ip is None:
|
||||||
|
@ -259,7 +271,7 @@ def restore_urllib_dns_resolver(getaddrinfo_func):
|
||||||
|
|
||||||
def remove_all_containers():
|
def remove_all_containers():
|
||||||
for container in docker_client.containers.list(all=True):
|
for container in docker_client.containers.list(all=True):
|
||||||
if I_AM_RUNNING_INSIDE_A_DOCKER_CONTAINER and container.id.startswith(socket.gethostname()):
|
if PYTEST_RUNNING_IN_CONTAINER and container.name == test_container:
|
||||||
continue # pytest is running within a Docker container, so we do not want to remove that particular container
|
continue # pytest is running within a Docker container, so we do not want to remove that particular container
|
||||||
logging.info(f"removing container {container.name}")
|
logging.info(f"removing container {container.name}")
|
||||||
container.remove(v=True, force=True)
|
container.remove(v=True, force=True)
|
||||||
|
@ -349,18 +361,23 @@ def connect_to_network(network):
|
||||||
|
|
||||||
:return: the name of the network we were connected to, or None
|
:return: the name of the network we were connected to, or None
|
||||||
"""
|
"""
|
||||||
if I_AM_RUNNING_INSIDE_A_DOCKER_CONTAINER:
|
if PYTEST_RUNNING_IN_CONTAINER:
|
||||||
try:
|
try:
|
||||||
my_container = docker_client.containers.get(socket.gethostname())
|
my_container = docker_client.containers.get(test_container)
|
||||||
except docker.errors.NotFound:
|
except docker.errors.NotFound:
|
||||||
logging.warn(f"container {socket.gethostname()!r} not found")
|
logging.warn(f"container {test_container} not found")
|
||||||
return
|
return
|
||||||
|
|
||||||
# figure out our container networks
|
# figure out our container networks
|
||||||
my_networks = list(my_container.attrs["NetworkSettings"]["Networks"].keys())
|
my_networks = list(my_container.attrs["NetworkSettings"]["Networks"].keys())
|
||||||
|
|
||||||
# make sure our container is connected to the nginx-proxy's network
|
# If the pytest container is using host networking, it cannot connect to container networks (not required with host network)
|
||||||
if network not in my_networks:
|
if 'host' in my_networks:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Make sure our container is connected to the nginx-proxy's network,
|
||||||
|
# but avoid connecting to `none` network (not valid) with `test_server-down` tests
|
||||||
|
if network.name not in my_networks and network.name != 'none':
|
||||||
logging.info(f"Connecting to docker network: {network.name}")
|
logging.info(f"Connecting to docker network: {network.name}")
|
||||||
network.connect(my_container)
|
network.connect(my_container)
|
||||||
return network
|
return network
|
||||||
|
@ -372,11 +389,11 @@ def disconnect_from_network(network=None):
|
||||||
|
|
||||||
:param network: name of a docker network to disconnect from
|
:param network: name of a docker network to disconnect from
|
||||||
"""
|
"""
|
||||||
if I_AM_RUNNING_INSIDE_A_DOCKER_CONTAINER and network is not None:
|
if PYTEST_RUNNING_IN_CONTAINER and network is not None:
|
||||||
try:
|
try:
|
||||||
my_container = docker_client.containers.get(socket.gethostname())
|
my_container = docker_client.containers.get(test_container)
|
||||||
except docker.errors.NotFound:
|
except docker.errors.NotFound:
|
||||||
logging.warn(f"container {socket.gethostname()!r} not found")
|
logging.warn(f"container {test_container} not found")
|
||||||
return
|
return
|
||||||
|
|
||||||
# figure out our container networks
|
# figure out our container networks
|
||||||
|
@ -394,11 +411,11 @@ def connect_to_all_networks():
|
||||||
|
|
||||||
:return: a list of networks we connected to
|
:return: a list of networks we connected to
|
||||||
"""
|
"""
|
||||||
if not I_AM_RUNNING_INSIDE_A_DOCKER_CONTAINER:
|
if not PYTEST_RUNNING_IN_CONTAINER:
|
||||||
return []
|
return []
|
||||||
else:
|
else:
|
||||||
# find the list of docker networks
|
# find the list of docker networks
|
||||||
networks = [network for network in docker_client.networks.list() if len(network.containers) > 0 and network.name != 'bridge']
|
networks = [network for network in docker_client.networks.list(greedy=True) if len(network.containers) > 0 and network.name != 'bridge']
|
||||||
return [connect_to_network(network) for network in networks]
|
return [connect_to_network(network) for network in networks]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -18,8 +18,8 @@ docker build -t nginx-proxy-tester -f "${DIR}/requirements/Dockerfile-nginx-prox
|
||||||
|
|
||||||
# run the nginx-proxy-tester container setting the correct value for the working dir in order for
|
# run the nginx-proxy-tester container setting the correct value for the working dir in order for
|
||||||
# docker-compose to work properly when run from within that container.
|
# docker-compose to work properly when run from within that container.
|
||||||
exec docker run --rm -it \
|
exec docker run --rm -it --name "nginx-proxy-pytest" \
|
||||||
--volume /var/run/docker.sock:/var/run/docker.sock \
|
--volume "/var/run/docker.sock:/var/run/docker.sock" \
|
||||||
--volume "${DIR}:${DIR}" \
|
--volume "${DIR}:${DIR}" \
|
||||||
--workdir "${DIR}" \
|
--workdir "${DIR}" \
|
||||||
nginx-proxy-tester "${ARGS[@]}"
|
nginx-proxy-tester "${ARGS[@]}"
|
|
@ -1,5 +1,7 @@
|
||||||
FROM python:3.9
|
FROM python:3.9
|
||||||
|
|
||||||
|
ENV PYTEST_RUNNING_IN_CONTAINER=1
|
||||||
|
|
||||||
COPY python-requirements.txt /requirements.txt
|
COPY python-requirements.txt /requirements.txt
|
||||||
RUN pip install -r /requirements.txt
|
RUN pip install -r /requirements.txt
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
backoff==1.11.1
|
backoff==1.11.1
|
||||||
docker-compose==1.29.2
|
docker-compose==1.29.2
|
||||||
docker==5.0.3
|
docker==5.0.3
|
||||||
pytest==6.2.5
|
pytest==7.1.2
|
||||||
requests==2.26.0
|
requests==2.27.1
|
||||||
|
|
|
@ -8,7 +8,7 @@ services:
|
||||||
- /etc/nginx/conf.d
|
- /etc/nginx/conf.d
|
||||||
|
|
||||||
dockergen:
|
dockergen:
|
||||||
image: jwilder/docker-gen
|
image: nginxproxy/docker-gen
|
||||||
command: -notify-sighup nginx -watch /etc/docker-gen/templates/nginx.tmpl /etc/nginx/conf.d/default.conf
|
command: -notify-sighup nginx -watch /etc/docker-gen/templates/nginx.tmpl /etc/nginx/conf.d/default.conf
|
||||||
volumes_from:
|
volumes_from:
|
||||||
- nginx
|
- nginx
|
||||||
|
|
|
@ -7,7 +7,7 @@ services:
|
||||||
- nginx_conf:/etc/nginx/conf.d
|
- nginx_conf:/etc/nginx/conf.d
|
||||||
|
|
||||||
dockergen:
|
dockergen:
|
||||||
image: jwilder/docker-gen
|
image: nginxproxy/docker-gen
|
||||||
command: -notify-sighup nginx -watch /etc/docker-gen/templates/nginx.tmpl /etc/nginx/conf.d/default.conf
|
command: -notify-sighup nginx -watch /etc/docker-gen/templates/nginx.tmpl /etc/nginx/conf.d/default.conf
|
||||||
volumes:
|
volumes:
|
||||||
- /var/run/docker.sock:/tmp/docker.sock:ro
|
- /var/run/docker.sock:/tmp/docker.sock:ro
|
||||||
|
|
|
@ -29,13 +29,36 @@ def web1(docker_compose):
|
||||||
except NotFound:
|
except NotFound:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def web2(docker_compose):
|
||||||
|
"""
|
||||||
|
pytest fixture creating a web container with `VIRTUAL_HOST=nginx-proxy`, `VIRTUAL_PATH=/web2/` and `VIRTUAL_DEST=/` listening on port 82.
|
||||||
|
"""
|
||||||
|
container = docker_compose.containers.run(
|
||||||
|
name="web2",
|
||||||
|
image="web",
|
||||||
|
detach=True,
|
||||||
|
environment={
|
||||||
|
"WEB_PORTS": "82",
|
||||||
|
"VIRTUAL_HOST": "nginx-proxy",
|
||||||
|
"VIRTUAL_PATH": "/web2/",
|
||||||
|
"VIRTUAL_DEST": "/",
|
||||||
|
},
|
||||||
|
ports={"82/tcp": None}
|
||||||
|
)
|
||||||
|
sleep(2) # give it some time to initialize and for docker-gen to detect it
|
||||||
|
yield container
|
||||||
|
try:
|
||||||
|
docker_compose.containers.get("web2").remove(force=True)
|
||||||
|
except NotFound:
|
||||||
|
pass
|
||||||
|
|
||||||
def test_nginx_proxy_behavior_when_alone(docker_compose, nginxproxy):
|
def test_nginx_proxy_behavior_when_alone(docker_compose, nginxproxy):
|
||||||
r = nginxproxy.get("http://nginx-proxy/")
|
r = nginxproxy.get("http://nginx-proxy/")
|
||||||
assert r.status_code == 503
|
assert r.status_code == 503
|
||||||
|
|
||||||
|
|
||||||
def test_new_container_is_detected(web1, nginxproxy):
|
def test_new_container_is_detected_vhost(web1, nginxproxy):
|
||||||
r = nginxproxy.get("http://web1.nginx-proxy/port")
|
r = nginxproxy.get("http://web1.nginx-proxy/port")
|
||||||
assert r.status_code == 200
|
assert r.status_code == 200
|
||||||
assert "answer from port 81\n" == r.text
|
assert "answer from port 81\n" == r.text
|
||||||
|
@ -44,3 +67,16 @@ def test_new_container_is_detected(web1, nginxproxy):
|
||||||
sleep(2)
|
sleep(2)
|
||||||
r = nginxproxy.get("http://web1.nginx-proxy/port")
|
r = nginxproxy.get("http://web1.nginx-proxy/port")
|
||||||
assert r.status_code == 503
|
assert r.status_code == 503
|
||||||
|
|
||||||
|
def test_new_container_is_detected_vpath(web2, nginxproxy):
|
||||||
|
r = nginxproxy.get("http://nginx-proxy/web2/port")
|
||||||
|
assert r.status_code == 200
|
||||||
|
assert "answer from port 82\n" == r.text
|
||||||
|
r = nginxproxy.get("http://nginx-proxy/port")
|
||||||
|
assert r.status_code in [404, 503]
|
||||||
|
|
||||||
|
web2.remove(force=True)
|
||||||
|
sleep(2)
|
||||||
|
r = nginxproxy.get("http://nginx-proxy/web2/port")
|
||||||
|
assert r.status_code == 503
|
||||||
|
|
||||||
|
|
11
test/test_internal/network_internal.conf
Normal file
11
test/test_internal/network_internal.conf
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
# Only allow traffic from internal clients
|
||||||
|
allow 127.0.0.0/8;
|
||||||
|
allow 10.0.0.0/8;
|
||||||
|
allow 192.168.0.0/16;
|
||||||
|
allow 172.16.0.0/12;
|
||||||
|
allow fc00::/7; # IPv6 local address range
|
||||||
|
deny all;
|
||||||
|
|
||||||
|
# Dummy header for testing
|
||||||
|
add_header X-network internal;
|
||||||
|
|
14
test/test_internal/test_internal-per-vhost.py
Normal file
14
test/test_internal/test_internal-per-vhost.py
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
def test_network_web1(docker_compose, nginxproxy):
|
||||||
|
r = nginxproxy.get("http://web1.nginx-proxy.local/port")
|
||||||
|
assert r.status_code == 200
|
||||||
|
assert r.text == "answer from port 81\n"
|
||||||
|
assert "X-network" in r.headers
|
||||||
|
assert "internal" == r.headers["X-network"]
|
||||||
|
|
||||||
|
def test_network_web2(docker_compose, nginxproxy):
|
||||||
|
r = nginxproxy.get("http://web2.nginx-proxy.local/port")
|
||||||
|
assert r.status_code == 200
|
||||||
|
assert r.text == "answer from port 82\n"
|
||||||
|
assert "X-network" not in r.headers
|
23
test/test_internal/test_internal-per-vhost.yml
Normal file
23
test/test_internal/test_internal-per-vhost.yml
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
web1:
|
||||||
|
image: web
|
||||||
|
expose:
|
||||||
|
- "81"
|
||||||
|
environment:
|
||||||
|
WEB_PORTS: 81
|
||||||
|
VIRTUAL_HOST: web1.nginx-proxy.local
|
||||||
|
NETWORK_ACCESS: internal
|
||||||
|
|
||||||
|
web2:
|
||||||
|
image: web
|
||||||
|
expose:
|
||||||
|
- "82"
|
||||||
|
environment:
|
||||||
|
WEB_PORTS: 82
|
||||||
|
VIRTUAL_HOST: web2.nginx-proxy.local
|
||||||
|
|
||||||
|
sut:
|
||||||
|
image: nginxproxy/nginx-proxy:test
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/tmp/docker.sock:ro
|
||||||
|
- ./network_internal.conf:/etc/nginx/network_internal.conf:ro
|
||||||
|
|
14
test/test_internal/test_internal-per-vpath.py
Normal file
14
test/test_internal/test_internal-per-vpath.py
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
def test_network_web1(docker_compose, nginxproxy):
|
||||||
|
r = nginxproxy.get("http://nginx-proxy.local/web1/port")
|
||||||
|
assert r.status_code == 200
|
||||||
|
assert r.text == "answer from port 81\n"
|
||||||
|
assert "X-network" in r.headers
|
||||||
|
assert "internal" == r.headers["X-network"]
|
||||||
|
|
||||||
|
def test_network_web2(docker_compose, nginxproxy):
|
||||||
|
r = nginxproxy.get("http://nginx-proxy.local/web2/port")
|
||||||
|
assert r.status_code == 200
|
||||||
|
assert r.text == "answer from port 82\n"
|
||||||
|
assert "X-network" not in r.headers
|
27
test/test_internal/test_internal-per-vpath.yml
Normal file
27
test/test_internal/test_internal-per-vpath.yml
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
web1:
|
||||||
|
image: web
|
||||||
|
expose:
|
||||||
|
- "81"
|
||||||
|
environment:
|
||||||
|
WEB_PORTS: 81
|
||||||
|
VIRTUAL_HOST: nginx-proxy.local
|
||||||
|
VIRTUAL_PATH: /web1/
|
||||||
|
VIRTUAL_DEST: /
|
||||||
|
NETWORK_ACCESS: internal
|
||||||
|
|
||||||
|
web2:
|
||||||
|
image: web
|
||||||
|
expose:
|
||||||
|
- "82"
|
||||||
|
environment:
|
||||||
|
WEB_PORTS: 82
|
||||||
|
VIRTUAL_HOST: nginx-proxy.local
|
||||||
|
VIRTUAL_PATH: /web2/
|
||||||
|
VIRTUAL_DEST: /
|
||||||
|
|
||||||
|
sut:
|
||||||
|
image: nginxproxy/nginx-proxy:test
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/tmp/docker.sock:ro
|
||||||
|
- ./network_internal.conf:/etc/nginx/network_internal.conf:ro
|
||||||
|
|
|
@ -22,3 +22,8 @@ def test_forwards_to_web2(docker_compose, nginxproxy):
|
||||||
def test_ipv6_is_disabled_by_default(docker_compose, nginxproxy):
|
def test_ipv6_is_disabled_by_default(docker_compose, nginxproxy):
|
||||||
with pytest.raises(ConnectionError):
|
with pytest.raises(ConnectionError):
|
||||||
nginxproxy.get("http://nginx-proxy/port", ipv6=True)
|
nginxproxy.get("http://nginx-proxy/port", ipv6=True)
|
||||||
|
|
||||||
|
|
||||||
|
def test_container_version_is_displayed(docker_compose, nginxproxy):
|
||||||
|
conf = nginxproxy.get_conf().decode('ASCII')
|
||||||
|
assert "# nginx-proxy version : test" in conf
|
||||||
|
|
8
test/test_ssl/certs/web2.nginx-proxy.tld.dhparam.pem
Normal file
8
test/test_ssl/certs/web2.nginx-proxy.tld.dhparam.pem
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
-----BEGIN DH PARAMETERS-----
|
||||||
|
MIIBCAKCAQEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz
|
||||||
|
+8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a
|
||||||
|
87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7
|
||||||
|
YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi
|
||||||
|
7MA0BM0oNC9hkXL+nOmFg/+OTxIy7vKBg8P+OxtMb61zO7X8vC7CIAXFjvGDfRaD
|
||||||
|
ssbzSibBsu/6iGtCOGEoXJf//////////wIBAg==
|
||||||
|
-----END DH PARAMETERS-----
|
|
@ -1,5 +1,6 @@
|
||||||
import re
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import os
|
||||||
|
|
||||||
import backoff
|
import backoff
|
||||||
import docker
|
import docker
|
||||||
|
@ -80,12 +81,17 @@ def negotiate_cipher(sut_container, additional_params='', grep='Cipher is'):
|
||||||
raise Exception("Failed to process CLI request:\n" + e.stderr) from None
|
raise Exception("Failed to process CLI request:\n" + e.stderr) from None
|
||||||
|
|
||||||
|
|
||||||
def can_negotiate_dhe_ciphersuite(sut_container):
|
# The default `dh_bits` can vary due to configuration.
|
||||||
r = negotiate_cipher(sut_container, "-cipher 'EDH'")
|
# `additional_params` allows for adjusting the request to a specific `VIRTUAL_HOST`,
|
||||||
|
# where DH size can differ from the configured global default DH size.
|
||||||
|
def can_negotiate_dhe_ciphersuite(sut_container, dh_bits=4096, additional_params=''):
|
||||||
|
openssl_params = f"-cipher 'EDH' {additional_params}"
|
||||||
|
|
||||||
|
r = negotiate_cipher(sut_container, openssl_params)
|
||||||
assert "New, TLSv1.2, Cipher is DHE-RSA-AES256-GCM-SHA384\n" == r
|
assert "New, TLSv1.2, Cipher is DHE-RSA-AES256-GCM-SHA384\n" == r
|
||||||
|
|
||||||
r2 = negotiate_cipher(sut_container, "-cipher 'EDH'", "Server Temp Key")
|
r2 = negotiate_cipher(sut_container, openssl_params, "Server Temp Key")
|
||||||
assert "DH" in r2
|
assert f"Server Temp Key: DH, {dh_bits} bits" in r2
|
||||||
|
|
||||||
|
|
||||||
def cannot_negotiate_dhe_ciphersuite(sut_container):
|
def cannot_negotiate_dhe_ciphersuite(sut_container):
|
||||||
|
@ -101,6 +107,29 @@ def cannot_negotiate_dhe_ciphersuite(sut_container):
|
||||||
assert "X25519" in r3
|
assert "X25519" in r3
|
||||||
|
|
||||||
|
|
||||||
|
# To verify self-signed certificates, the file path to their CA cert must be provided.
|
||||||
|
# Use the `fqdn` arg to specify the `VIRTUAL_HOST` to request for verification for that cert.
|
||||||
|
#
|
||||||
|
# Resolves the following stderr warnings regarding self-signed cert verification and missing SNI:
|
||||||
|
# `Can't use SSL_get_servername`
|
||||||
|
# `verify error:num=20:unable to get local issuer certificate`
|
||||||
|
# `verify error:num=21:unable to verify the first certificate`
|
||||||
|
#
|
||||||
|
# The stderr output is hidden due to running the openssl command with `stderr=subprocess.PIPE`.
|
||||||
|
def can_verify_chain_of_trust(sut_container, ca_cert, fqdn):
|
||||||
|
openssl_params = f"-CAfile '{ca_cert}' -servername '{fqdn}'"
|
||||||
|
|
||||||
|
r = negotiate_cipher(sut_container, openssl_params, "Verify return code")
|
||||||
|
assert "Verify return code: 0 (ok)" in r
|
||||||
|
|
||||||
|
|
||||||
|
def should_be_equivalent_content(sut_container, expected, actual):
|
||||||
|
expected_checksum = sut_container.exec_run(f"md5sum {expected}").output.split()[0]
|
||||||
|
actual_checksum = sut_container.exec_run(f"md5sum {actual}").output.split()[0]
|
||||||
|
|
||||||
|
assert expected_checksum == actual_checksum
|
||||||
|
|
||||||
|
|
||||||
# Parse array of container ENV, splitting at the `=` and returning the value, otherwise `None`
|
# Parse array of container ENV, splitting at the `=` and returning the value, otherwise `None`
|
||||||
def get_env(sut_container, var):
|
def get_env(sut_container, var):
|
||||||
env = sut_container.attrs['Config']['Env']
|
env = sut_container.attrs['Config']['Env']
|
||||||
|
@ -125,14 +154,17 @@ def test_default_dhparam_is_ffdhe4096(docker_compose):
|
||||||
|
|
||||||
assert_log_contains("Setting up DH Parameters..", container_name)
|
assert_log_contains("Setting up DH Parameters..", container_name)
|
||||||
|
|
||||||
# Make sure the dhparam file used is the default ffdhe4096.pem:
|
# `dhparam.pem` contents should match the default (ffdhe4096.pem):
|
||||||
default_checksum = sut_container.exec_run("md5sum /app/dhparam/ffdhe4096.pem").output.split()
|
should_be_equivalent_content(
|
||||||
current_checksum = sut_container.exec_run("md5sum /etc/nginx/dhparam/dhparam.pem").output.split()
|
sut_container,
|
||||||
assert default_checksum[0] == current_checksum[0]
|
"/app/dhparam/ffdhe4096.pem",
|
||||||
|
"/etc/nginx/dhparam/dhparam.pem"
|
||||||
|
)
|
||||||
|
|
||||||
can_negotiate_dhe_ciphersuite(sut_container)
|
can_negotiate_dhe_ciphersuite(sut_container, 4096)
|
||||||
|
|
||||||
|
|
||||||
|
# Overrides default DH group via ENV `DHPARAM_BITS=3072`:
|
||||||
def test_can_change_dhparam_group(docker_compose):
|
def test_can_change_dhparam_group(docker_compose):
|
||||||
container_name="dh-env"
|
container_name="dh-env"
|
||||||
sut_container = docker_client.containers.get(container_name)
|
sut_container = docker_client.containers.get(container_name)
|
||||||
|
@ -140,12 +172,14 @@ def test_can_change_dhparam_group(docker_compose):
|
||||||
|
|
||||||
assert_log_contains("Setting up DH Parameters..", container_name)
|
assert_log_contains("Setting up DH Parameters..", container_name)
|
||||||
|
|
||||||
# Make sure the dhparam file used is ffdhe2048.pem, not the default (ffdhe4096.pem):
|
# `dhparam.pem` contents should not match the default (ffdhe4096.pem):
|
||||||
default_checksum = sut_container.exec_run("md5sum /app/dhparam/ffdhe2048.pem").output.split()
|
should_be_equivalent_content(
|
||||||
current_checksum = sut_container.exec_run("md5sum /etc/nginx/dhparam/dhparam.pem").output.split()
|
sut_container,
|
||||||
assert default_checksum[0] == current_checksum[0]
|
"/app/dhparam/ffdhe3072.pem",
|
||||||
|
"/etc/nginx/dhparam/dhparam.pem"
|
||||||
|
)
|
||||||
|
|
||||||
can_negotiate_dhe_ciphersuite(sut_container)
|
can_negotiate_dhe_ciphersuite(sut_container, 3072)
|
||||||
|
|
||||||
|
|
||||||
def test_fail_if_dhparam_group_not_supported(docker_compose):
|
def test_fail_if_dhparam_group_not_supported(docker_compose):
|
||||||
|
@ -162,6 +196,7 @@ def test_fail_if_dhparam_group_not_supported(docker_compose):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Overrides default DH group by providing a custom `/etc/nginx/dhparam/dhparam.pem`:
|
||||||
def test_custom_dhparam_is_supported(docker_compose):
|
def test_custom_dhparam_is_supported(docker_compose):
|
||||||
container_name="dh-file"
|
container_name="dh-file"
|
||||||
sut_container = docker_client.containers.get(container_name)
|
sut_container = docker_client.containers.get(container_name)
|
||||||
|
@ -172,14 +207,49 @@ def test_custom_dhparam_is_supported(docker_compose):
|
||||||
container_name
|
container_name
|
||||||
)
|
)
|
||||||
|
|
||||||
# Make sure the dhparam file used is not the default (ffdhe4096.pem):
|
# `dhparam.pem` contents should not match the default (ffdhe4096.pem):
|
||||||
default_checksum = sut_container.exec_run("md5sum /app/dhparam/ffdhe4096.pem").output.split()
|
should_be_equivalent_content(
|
||||||
current_checksum = sut_container.exec_run("md5sum /etc/nginx/dhparam/dhparam.pem").output.split()
|
sut_container,
|
||||||
assert default_checksum[0] != current_checksum[0]
|
"/app/dhparam/ffdhe3072.pem",
|
||||||
|
"/etc/nginx/dhparam/dhparam.pem"
|
||||||
|
)
|
||||||
|
|
||||||
can_negotiate_dhe_ciphersuite(sut_container)
|
can_negotiate_dhe_ciphersuite(sut_container, 3072)
|
||||||
|
|
||||||
|
|
||||||
|
# Only `web2` has a site-specific DH param file (which overrides all other DH config)
|
||||||
|
# Other tests here use `web5` explicitly, or implicitly (via ENV `DEFAULT_HOST`, otherwise first HTTPS server)
|
||||||
|
def test_custom_dhparam_is_supported_per_site(docker_compose):
|
||||||
|
container_name="dh-file"
|
||||||
|
sut_container = docker_client.containers.get(container_name)
|
||||||
|
assert sut_container.status == "running"
|
||||||
|
|
||||||
|
# A site specific `dhparam.pem` with DH group size of 2048-bit.
|
||||||
|
# DH group size should not match the:
|
||||||
|
# - 4096-bit default.
|
||||||
|
# - 3072-bit default, overriden by file.
|
||||||
|
should_be_equivalent_content(
|
||||||
|
sut_container,
|
||||||
|
"/app/dhparam/ffdhe2048.pem",
|
||||||
|
"/etc/nginx/certs/web2.nginx-proxy.tld.dhparam.pem"
|
||||||
|
)
|
||||||
|
|
||||||
|
# `-servername` required for nginx-proxy to respond with site-specific DH params used:
|
||||||
|
can_negotiate_dhe_ciphersuite(sut_container, 2048, '-servername web2.nginx-proxy.tld')
|
||||||
|
|
||||||
|
# --Unrelated to DH support--
|
||||||
|
# - `web5` is missing a certificate, but falls back to available `/etc/nginx/certs/nginx-proxy.tld.crt` via `nginx.tmpl` "closest" result.
|
||||||
|
# - `web2` has it's own cert provisioned at `/etc/nginx/certs/web2.nginx-proxy.tld.crt`.
|
||||||
|
can_verify_chain_of_trust(
|
||||||
|
sut_container,
|
||||||
|
ca_cert = f"{os.getcwd()}/certs/ca-root.crt",
|
||||||
|
fqdn = 'web2.nginx-proxy.tld'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# NOTE: These two tests will fail without the ENV `DEFAULT_HOST` to prevent
|
||||||
|
# accidentally falling back to `web2` as the default server, which has explicit DH params configured.
|
||||||
|
# Only copying DH params is skipped, not explicit usage via user providing custom files.
|
||||||
def test_can_skip_dhparam(docker_compose):
|
def test_can_skip_dhparam(docker_compose):
|
||||||
container_name="dh-skip"
|
container_name="dh-skip"
|
||||||
sut_container = docker_client.containers.get(container_name)
|
sut_container = docker_client.containers.get(container_name)
|
||||||
|
@ -189,6 +259,7 @@ def test_can_skip_dhparam(docker_compose):
|
||||||
|
|
||||||
cannot_negotiate_dhe_ciphersuite(sut_container)
|
cannot_negotiate_dhe_ciphersuite(sut_container)
|
||||||
|
|
||||||
|
|
||||||
def test_can_skip_dhparam_backward_compatibility(docker_compose):
|
def test_can_skip_dhparam_backward_compatibility(docker_compose):
|
||||||
container_name="dh-skip-backward"
|
container_name="dh-skip-backward"
|
||||||
sut_container = docker_client.containers.get(container_name)
|
sut_container = docker_client.containers.get(container_name)
|
||||||
|
|
|
@ -6,12 +6,27 @@ web5:
|
||||||
WEB_PORTS: "85"
|
WEB_PORTS: "85"
|
||||||
VIRTUAL_HOST: "web5.nginx-proxy.tld"
|
VIRTUAL_HOST: "web5.nginx-proxy.tld"
|
||||||
|
|
||||||
|
# Intended for testing with `dh-file` container.
|
||||||
|
# VIRTUAL_HOST is paired with site-specific DH param file.
|
||||||
|
# DEFAULT_HOST is required to avoid defaulting to web2,
|
||||||
|
# if not specifying FQDN (`-servername`) in openssl queries.
|
||||||
|
web2:
|
||||||
|
image: web
|
||||||
|
expose:
|
||||||
|
- "85"
|
||||||
|
environment:
|
||||||
|
WEB_PORTS: "85"
|
||||||
|
VIRTUAL_HOST: "web2.nginx-proxy.tld"
|
||||||
|
|
||||||
|
|
||||||
# sut - System Under Test
|
# sut - System Under Test
|
||||||
# `docker.sock` required for functionality
|
# `docker.sock` required for functionality
|
||||||
# `certs` required to enable HTTPS via template
|
# `certs` required to enable HTTPS via template
|
||||||
with_default_group:
|
with_default_group:
|
||||||
container_name: dh-default
|
container_name: dh-default
|
||||||
image: &img-nginxproxy nginxproxy/nginx-proxy:test
|
image: &img-nginxproxy nginxproxy/nginx-proxy:test
|
||||||
|
environment: &env-common
|
||||||
|
- &default-host DEFAULT_HOST=web5.nginx-proxy.tld
|
||||||
volumes: &vols-common
|
volumes: &vols-common
|
||||||
- &docker-sock /var/run/docker.sock:/tmp/docker.sock:ro
|
- &docker-sock /var/run/docker.sock:/tmp/docker.sock:ro
|
||||||
- &nginx-certs ./certs:/etc/nginx/certs:ro
|
- &nginx-certs ./certs:/etc/nginx/certs:ro
|
||||||
|
@ -19,7 +34,8 @@ with_default_group:
|
||||||
with_alternative_group:
|
with_alternative_group:
|
||||||
container_name: dh-env
|
container_name: dh-env
|
||||||
environment:
|
environment:
|
||||||
- DHPARAM_BITS=2048
|
- DHPARAM_BITS=3072
|
||||||
|
- *default-host
|
||||||
image: *img-nginxproxy
|
image: *img-nginxproxy
|
||||||
volumes: *vols-common
|
volumes: *vols-common
|
||||||
|
|
||||||
|
@ -27,21 +43,24 @@ with_invalid_group:
|
||||||
container_name: invalid-group-1024
|
container_name: invalid-group-1024
|
||||||
environment:
|
environment:
|
||||||
- DHPARAM_BITS=1024
|
- DHPARAM_BITS=1024
|
||||||
|
- *default-host
|
||||||
image: *img-nginxproxy
|
image: *img-nginxproxy
|
||||||
volumes: *vols-common
|
volumes: *vols-common
|
||||||
|
|
||||||
with_custom_file:
|
with_custom_file:
|
||||||
container_name: dh-file
|
container_name: dh-file
|
||||||
image: *img-nginxproxy
|
image: *img-nginxproxy
|
||||||
|
environment: *env-common
|
||||||
volumes:
|
volumes:
|
||||||
- *docker-sock
|
- *docker-sock
|
||||||
- *nginx-certs
|
- *nginx-certs
|
||||||
- ../../dhparam/ffdhe3072.pem:/etc/nginx/dhparam/dhparam.pem:ro
|
- ../../app/dhparam/ffdhe3072.pem:/etc/nginx/dhparam/dhparam.pem:ro
|
||||||
|
|
||||||
with_skip:
|
with_skip:
|
||||||
container_name: dh-skip
|
container_name: dh-skip
|
||||||
environment:
|
environment:
|
||||||
- DHPARAM_SKIP=true
|
- DHPARAM_SKIP=true
|
||||||
|
- *default-host
|
||||||
image: *img-nginxproxy
|
image: *img-nginxproxy
|
||||||
volumes: *vols-common
|
volumes: *vols-common
|
||||||
|
|
||||||
|
@ -49,5 +68,6 @@ with_skip_backward:
|
||||||
container_name: dh-skip-backward
|
container_name: dh-skip-backward
|
||||||
environment:
|
environment:
|
||||||
- DHPARAM_GENERATION=false
|
- DHPARAM_GENERATION=false
|
||||||
|
- *default-host
|
||||||
image: *img-nginxproxy
|
image: *img-nginxproxy
|
||||||
volumes: *vols-common
|
volumes: *vols-common
|
15
test/test_ssl/test_virtual_path.py
Normal file
15
test/test_ssl/test_virtual_path.py
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("path", ["web1", "web2"])
|
||||||
|
def test_web1_http_redirects_to_https(docker_compose, nginxproxy, path):
|
||||||
|
r = nginxproxy.get("http://www.nginx-proxy.tld/%s/port" % path, allow_redirects=False)
|
||||||
|
assert r.status_code == 301
|
||||||
|
assert "Location" in r.headers
|
||||||
|
assert "https://www.nginx-proxy.tld/%s/port" % path == r.headers['Location']
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("path,port", [("web1", 81), ("web2", 82)])
|
||||||
|
def test_web1_https_is_forwarded(docker_compose, nginxproxy, path, port):
|
||||||
|
r = nginxproxy.get("https://www.nginx-proxy.tld/%s/port" % path, allow_redirects=False)
|
||||||
|
assert r.status_code == 200
|
||||||
|
assert "answer from port %d\n" % port in r.text
|
||||||
|
|
26
test/test_ssl/test_virtual_path.yml
Normal file
26
test/test_ssl/test_virtual_path.yml
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
web1:
|
||||||
|
image: web
|
||||||
|
expose:
|
||||||
|
- "81"
|
||||||
|
environment:
|
||||||
|
WEB_PORTS: "81"
|
||||||
|
VIRTUAL_HOST: "www.nginx-proxy.tld"
|
||||||
|
VIRTUAL_PATH: "/web1/"
|
||||||
|
VIRTUAL_DEST: "/"
|
||||||
|
|
||||||
|
web2:
|
||||||
|
image: web
|
||||||
|
expose:
|
||||||
|
- "82"
|
||||||
|
environment:
|
||||||
|
WEB_PORTS: "82"
|
||||||
|
VIRTUAL_HOST: "www.nginx-proxy.tld"
|
||||||
|
VIRTUAL_PATH: "/web2/"
|
||||||
|
VIRTUAL_DEST: "/"
|
||||||
|
|
||||||
|
sut:
|
||||||
|
image: nginxproxy/nginx-proxy:test
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/tmp/docker.sock:ro
|
||||||
|
- ./certs:/etc/nginx/certs:ro
|
||||||
|
|
1
test/test_virtual-path/alternate.conf
Normal file
1
test/test_virtual-path/alternate.conf
Normal file
|
@ -0,0 +1 @@
|
||||||
|
rewrite ^/(web3|alt)/(.*) /$2 break;
|
1
test/test_virtual-path/bar.conf
Normal file
1
test/test_virtual-path/bar.conf
Normal file
|
@ -0,0 +1 @@
|
||||||
|
add_header X-test bar;
|
1
test/test_virtual-path/default.conf
Normal file
1
test/test_virtual-path/default.conf
Normal file
|
@ -0,0 +1 @@
|
||||||
|
add_header X-test-default true;
|
1
test/test_virtual-path/foo.conf
Normal file
1
test/test_virtual-path/foo.conf
Normal file
|
@ -0,0 +1 @@
|
||||||
|
add_header X-test f00;
|
1
test/test_virtual-path/host.conf
Normal file
1
test/test_virtual-path/host.conf
Normal file
|
@ -0,0 +1 @@
|
||||||
|
add_header X-test-host true;
|
1
test/test_virtual-path/path.conf
Normal file
1
test/test_virtual-path/path.conf
Normal file
|
@ -0,0 +1 @@
|
||||||
|
add_header X-test-path true;
|
38
test/test_virtual-path/test_custom_conf.py
Normal file
38
test/test_virtual-path/test_custom_conf.py
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
def test_default_root_response(docker_compose, nginxproxy):
|
||||||
|
r = nginxproxy.get("http://nginx-proxy.test/")
|
||||||
|
assert r.status_code == 418
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("stub,header", [
|
||||||
|
("nginx-proxy.test/web1", "bar"),
|
||||||
|
("foo.nginx-proxy.test", "f00"),
|
||||||
|
])
|
||||||
|
def test_custom_applies(docker_compose, nginxproxy, stub, header):
|
||||||
|
r = nginxproxy.get(f"http://{stub}/port")
|
||||||
|
assert r.status_code == 200
|
||||||
|
assert "X-test" in r.headers
|
||||||
|
assert header == r.headers["X-test"]
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("stub,code", [
|
||||||
|
("nginx-proxy.test/foo", 418),
|
||||||
|
("nginx-proxy.test/web2", 200),
|
||||||
|
("nginx-proxy.test/web3", 200),
|
||||||
|
("bar.nginx-proxy.test", 503),
|
||||||
|
])
|
||||||
|
def test_custom_does_not_apply(docker_compose, nginxproxy, stub, code):
|
||||||
|
r = nginxproxy.get(f"http://{stub}/port")
|
||||||
|
assert r.status_code == code
|
||||||
|
assert "X-test" not in r.headers
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("stub,port", [
|
||||||
|
("nginx-proxy.test/web1", 81),
|
||||||
|
("nginx-proxy.test/web2", 82),
|
||||||
|
("nginx-proxy.test/web3", 83),
|
||||||
|
("nginx-proxy.test/alt", 83),
|
||||||
|
])
|
||||||
|
def test_alternate(docker_compose, nginxproxy, stub, port):
|
||||||
|
r = nginxproxy.get(f"http://{stub}/port")
|
||||||
|
assert r.status_code == 200
|
||||||
|
assert r.text == f"answer from port {port}\n"
|
||||||
|
|
48
test/test_virtual-path/test_custom_conf.yml
Normal file
48
test/test_virtual-path/test_custom_conf.yml
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
|
||||||
|
foo:
|
||||||
|
image: web
|
||||||
|
expose:
|
||||||
|
- "42"
|
||||||
|
environment:
|
||||||
|
WEB_PORTS: "42"
|
||||||
|
VIRTUAL_HOST: "foo.nginx-proxy.test"
|
||||||
|
|
||||||
|
web1:
|
||||||
|
image: web
|
||||||
|
expose:
|
||||||
|
- "81"
|
||||||
|
environment:
|
||||||
|
WEB_PORTS: "81"
|
||||||
|
VIRTUAL_HOST: "nginx-proxy.test"
|
||||||
|
VIRTUAL_PATH: "/web1/"
|
||||||
|
VIRTUAL_DEST: "/"
|
||||||
|
|
||||||
|
web2:
|
||||||
|
image: web
|
||||||
|
expose:
|
||||||
|
- "82"
|
||||||
|
environment:
|
||||||
|
WEB_PORTS: "82"
|
||||||
|
VIRTUAL_HOST: "nginx-proxy.test"
|
||||||
|
VIRTUAL_PATH: "/web2/"
|
||||||
|
VIRTUAL_DEST: "/"
|
||||||
|
|
||||||
|
web3:
|
||||||
|
image: web
|
||||||
|
expose:
|
||||||
|
- "83"
|
||||||
|
environment:
|
||||||
|
WEB_PORTS: "83"
|
||||||
|
VIRTUAL_HOST: "nginx-proxy.test"
|
||||||
|
VIRTUAL_PATH: "~ ^/(web3|alt)/"
|
||||||
|
|
||||||
|
sut:
|
||||||
|
image: nginxproxy/nginx-proxy:test
|
||||||
|
environment:
|
||||||
|
DEFAULT_ROOT: 418
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/tmp/docker.sock:ro
|
||||||
|
- ./foo.conf:/etc/nginx/vhost.d/foo.nginx-proxy.test:ro
|
||||||
|
- ./bar.conf:/etc/nginx/vhost.d/nginx-proxy.test_918d687a929083edd0c7224ee2293e0e7c062ab4_location:ro
|
||||||
|
- ./alternate.conf:/etc/nginx/vhost.d/nginx-proxy.test_7fb22b74bbdf907425dbbad18e4462565cada230_location:ro
|
||||||
|
|
18
test/test_virtual-path/test_forwarding.py
Normal file
18
test/test_virtual-path/test_forwarding.py
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
def test_root_redirects_to_web1(docker_compose, nginxproxy):
|
||||||
|
r = nginxproxy.get("http://www.nginx-proxy.tld/port", allow_redirects=False)
|
||||||
|
assert r.status_code == 301
|
||||||
|
assert "Location" in r.headers
|
||||||
|
assert "http://www.nginx-proxy.tld/web1/port" == r.headers['Location']
|
||||||
|
|
||||||
|
def test_direct_access(docker_compose, nginxproxy):
|
||||||
|
r = nginxproxy.get("http://www.nginx-proxy.tld/web1/port", allow_redirects=False)
|
||||||
|
assert r.status_code == 200
|
||||||
|
assert "answer from port 81\n" in r.text
|
||||||
|
|
||||||
|
def test_root_is_forwarded(docker_compose, nginxproxy):
|
||||||
|
r = nginxproxy.get("http://www.nginx-proxy.tld/port", allow_redirects=True)
|
||||||
|
assert r.status_code == 200
|
||||||
|
assert "answer from port 81\n" in r.text
|
||||||
|
|
17
test/test_virtual-path/test_forwarding.yml
Normal file
17
test/test_virtual-path/test_forwarding.yml
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
web1:
|
||||||
|
image: web
|
||||||
|
expose:
|
||||||
|
- "81"
|
||||||
|
environment:
|
||||||
|
WEB_PORTS: "81"
|
||||||
|
VIRTUAL_HOST: "www.nginx-proxy.tld"
|
||||||
|
VIRTUAL_PATH: "/web1/"
|
||||||
|
VIRTUAL_DEST: "/"
|
||||||
|
|
||||||
|
sut:
|
||||||
|
image: nginxproxy/nginx-proxy:test
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/tmp/docker.sock:ro
|
||||||
|
- ./certs:/etc/nginx/certs:ro
|
||||||
|
environment:
|
||||||
|
- DEFAULT_ROOT=301 http://$$host/web1$$request_uri
|
32
test/test_virtual-path/test_location_precedence.py
Normal file
32
test/test_virtual-path/test_location_precedence.py
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
def test_location_precedence_case1(docker_compose, nginxproxy):
|
||||||
|
r = nginxproxy.get(f"http://foo.nginx-proxy.test/web1/port")
|
||||||
|
assert r.status_code == 200
|
||||||
|
|
||||||
|
assert "X-test-default" in r.headers
|
||||||
|
assert "X-test-host" not in r.headers
|
||||||
|
assert "X-test-path" not in r.headers
|
||||||
|
|
||||||
|
assert r.headers["X-test-default"] == "true"
|
||||||
|
|
||||||
|
def test_location_precedence_case2(docker_compose, nginxproxy):
|
||||||
|
r = nginxproxy.get(f"http://bar.nginx-proxy.test/web2/port")
|
||||||
|
assert r.status_code == 200
|
||||||
|
|
||||||
|
assert "X-test-default" not in r.headers
|
||||||
|
assert "X-test-host" in r.headers
|
||||||
|
assert "X-test-path" not in r.headers
|
||||||
|
|
||||||
|
assert r.headers["X-test-host"] == "true"
|
||||||
|
|
||||||
|
def test_location_precedence_case3(docker_compose, nginxproxy):
|
||||||
|
r = nginxproxy.get(f"http://bar.nginx-proxy.test/web3/port")
|
||||||
|
assert r.status_code == 200
|
||||||
|
|
||||||
|
assert "X-test-default" not in r.headers
|
||||||
|
assert "X-test-host" not in r.headers
|
||||||
|
assert "X-test-path" in r.headers
|
||||||
|
|
||||||
|
assert r.headers["X-test-path"] == "true"
|
||||||
|
|
37
test/test_virtual-path/test_location_precedence.yml
Normal file
37
test/test_virtual-path/test_location_precedence.yml
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
web1:
|
||||||
|
image: web
|
||||||
|
expose:
|
||||||
|
- "81"
|
||||||
|
environment:
|
||||||
|
WEB_PORTS: "81"
|
||||||
|
VIRTUAL_HOST: "foo.nginx-proxy.test"
|
||||||
|
VIRTUAL_PATH: "/web1/"
|
||||||
|
VIRTUAL_DEST: "/"
|
||||||
|
|
||||||
|
web2:
|
||||||
|
image: web
|
||||||
|
expose:
|
||||||
|
- "82"
|
||||||
|
environment:
|
||||||
|
WEB_PORTS: "82"
|
||||||
|
VIRTUAL_HOST: "bar.nginx-proxy.test"
|
||||||
|
VIRTUAL_PATH: "/web2/"
|
||||||
|
VIRTUAL_DEST: "/"
|
||||||
|
|
||||||
|
web3:
|
||||||
|
image: web
|
||||||
|
expose:
|
||||||
|
- "83"
|
||||||
|
environment:
|
||||||
|
WEB_PORTS: "83"
|
||||||
|
VIRTUAL_HOST: "bar.nginx-proxy.test"
|
||||||
|
VIRTUAL_PATH: "/web3/"
|
||||||
|
VIRTUAL_DEST: "/"
|
||||||
|
|
||||||
|
sut:
|
||||||
|
image: nginxproxy/nginx-proxy:test
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/tmp/docker.sock:ro
|
||||||
|
- ./default.conf:/etc/nginx/vhost.d/default_location:ro
|
||||||
|
- ./host.conf:/etc/nginx/vhost.d/bar.nginx-proxy.test_location:ro
|
||||||
|
- ./path.conf:/etc/nginx/vhost.d/bar.nginx-proxy.test_99f2db0ed8aa95dbb5b87fca79c7eff2ff6bb8bd_location:ro
|
59
test/test_virtual-path/test_virtual_paths.py
Normal file
59
test/test_virtual-path/test_virtual_paths.py
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
from time import sleep
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from docker.errors import NotFound
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("stub,expected_port", [
|
||||||
|
("nginx-proxy.test/web1", 81),
|
||||||
|
("nginx-proxy.test/web2", 82),
|
||||||
|
("nginx-proxy.test", 83),
|
||||||
|
("foo.nginx-proxy.test", 42),
|
||||||
|
])
|
||||||
|
def test_valid_path(docker_compose, nginxproxy, stub, expected_port):
|
||||||
|
r = nginxproxy.get(f"http://{stub}/port")
|
||||||
|
assert r.status_code == 200
|
||||||
|
assert r.text == f"answer from port {expected_port}\n"
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("stub", [
|
||||||
|
"nginx-proxy.test/foo",
|
||||||
|
"bar.nginx-proxy.test",
|
||||||
|
])
|
||||||
|
def test_invalid_path(docker_compose, nginxproxy, stub):
|
||||||
|
r = nginxproxy.get(f"http://{stub}/port")
|
||||||
|
assert r.status_code in [404, 503]
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def web4(docker_compose):
|
||||||
|
"""
|
||||||
|
pytest fixture creating a web container with `VIRTUAL_HOST=nginx-proxy.test`, `VIRTUAL_PATH=/web4/` and `VIRTUAL_DEST=/` listening on port 84.
|
||||||
|
"""
|
||||||
|
container = docker_compose.containers.run(
|
||||||
|
name="web4",
|
||||||
|
image="web",
|
||||||
|
detach=True,
|
||||||
|
environment={
|
||||||
|
"WEB_PORTS": "84",
|
||||||
|
"VIRTUAL_HOST": "nginx-proxy.test",
|
||||||
|
"VIRTUAL_PATH": "/web4/",
|
||||||
|
"VIRTUAL_DEST": "/",
|
||||||
|
},
|
||||||
|
ports={"84/tcp": None}
|
||||||
|
)
|
||||||
|
sleep(2) # give it some time to initialize and for docker-gen to detect it
|
||||||
|
yield container
|
||||||
|
try:
|
||||||
|
docker_compose.containers.get("web4").remove(force=True)
|
||||||
|
except NotFound:
|
||||||
|
pass
|
||||||
|
|
||||||
|
"""
|
||||||
|
Test if we can add and remove a single virtual_path from multiple ones on the same subdomain.
|
||||||
|
"""
|
||||||
|
def test_container_hotplug(web4, nginxproxy):
|
||||||
|
r = nginxproxy.get(f"http://nginx-proxy.test/web4/port")
|
||||||
|
assert r.status_code == 200
|
||||||
|
assert r.text == f"answer from port 84\n"
|
||||||
|
web4.remove(force=True)
|
||||||
|
sleep(2)
|
||||||
|
r = nginxproxy.get(f"http://nginx-proxy.test/web4/port")
|
||||||
|
assert r.status_code == 404
|
42
test/test_virtual-path/test_virtual_paths.yml
Normal file
42
test/test_virtual-path/test_virtual_paths.yml
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
|
||||||
|
foo:
|
||||||
|
image: web
|
||||||
|
expose:
|
||||||
|
- "42"
|
||||||
|
environment:
|
||||||
|
WEB_PORTS: "42"
|
||||||
|
VIRTUAL_HOST: "foo.nginx-proxy.test"
|
||||||
|
|
||||||
|
web1:
|
||||||
|
image: web
|
||||||
|
expose:
|
||||||
|
- "81"
|
||||||
|
environment:
|
||||||
|
WEB_PORTS: "81"
|
||||||
|
VIRTUAL_HOST: "nginx-proxy.test"
|
||||||
|
VIRTUAL_PATH: "/web1/"
|
||||||
|
VIRTUAL_DEST: "/"
|
||||||
|
|
||||||
|
web2:
|
||||||
|
image: web
|
||||||
|
expose:
|
||||||
|
- "82"
|
||||||
|
environment:
|
||||||
|
WEB_PORTS: "82"
|
||||||
|
VIRTUAL_HOST: "nginx-proxy.test"
|
||||||
|
VIRTUAL_PATH: "/web2/"
|
||||||
|
VIRTUAL_DEST: "/"
|
||||||
|
|
||||||
|
web3:
|
||||||
|
image: web
|
||||||
|
expose:
|
||||||
|
- "83"
|
||||||
|
environment:
|
||||||
|
WEB_PORTS: "83"
|
||||||
|
VIRTUAL_HOST: "nginx-proxy.test"
|
||||||
|
VIRTUAL_PATH: "/"
|
||||||
|
|
||||||
|
sut:
|
||||||
|
image: nginxproxy/nginx-proxy:test
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/tmp/docker.sock:ro
|
Loading…
Reference in a new issue