diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0b3700d --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +**/__pycache__/ +**/.cache/ diff --git a/.travis.yml b/.travis.yml index 6bc9cd6..a453ac7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,8 +4,11 @@ services: - docker env: - global: - - DOCKER_VERSION=1.12.3-0~trusty + global: + - DOCKER_VERSION=1.13.1-0~ubuntu-trusty + matrix: + - TEST_TARGET: test-debian + - TEST_TARGET: test-alpine before_install: # list docker-engine versions @@ -14,15 +17,8 @@ before_install: - sudo apt-get -o Dpkg::Options::="--force-confnew" install -y --force-yes docker-engine=${DOCKER_VERSION} - docker version - docker info - - sudo add-apt-repository ppa:duggan/bats --yes - - sudo apt-get update -qq - - sudo apt-get install -qq bats + # prepare docker test requirements - make update-dependencies -matrix: - include: - - env: TEST_ID=test-debian - - env: TEST_ID=test-alpine - script: - - make $TEST_ID + - make $TEST_TARGET diff --git a/Dockerfile b/Dockerfile index 786db25..c3090f1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM nginx:1.11.8 +FROM nginx:1.11.10 MAINTAINER Jason Wilder mail@jasonwilder.com # Install wget and install/updates certificates diff --git a/Dockerfile.alpine b/Dockerfile.alpine index 25485d8..3e92200 100644 --- a/Dockerfile.alpine +++ b/Dockerfile.alpine @@ -1,4 +1,4 @@ -FROM nginx:1.11.8-alpine +FROM nginx:1.11.10-alpine MAINTAINER Jason Wilder mail@jasonwilder.com # Install wget and install/updates certificates diff --git a/Makefile b/Makefile index acb3386..5f965f7 100644 --- a/Makefile +++ b/Makefile @@ -1,21 +1,16 @@ .SILENT : -.PHONY : test +.PHONY : test-debian test-alpine test + update-dependencies: - docker pull jwilder/docker-gen:0.7.3 - docker pull nginx:1.11.6 - docker pull nginx:1.11.8-alpine - docker pull python:3 - docker pull rancher/socat-docker:latest - docker pull appropriate/curl:latest - docker pull docker:1.10 + test/requirements/build.sh -test-debian: - docker build -t jwilder/nginx-proxy:bats . - bats test +test-debian: update-dependencies + docker build -t jwilder/nginx-proxy:test . + test/pytest.sh -test-alpine: - docker build -f Dockerfile.alpine -t jwilder/nginx-proxy:bats . - bats test +test-alpine: update-dependencies + docker build -f Dockerfile.alpine -t jwilder/nginx-proxy:test . + test/pytest.sh test: test-debian test-alpine diff --git a/Procfile b/Procfile index 0fa56e7..29fe166 100644 --- a/Procfile +++ b/Procfile @@ -1,2 +1,2 @@ -nginx: nginx dockergen: docker-gen -watch -notify "nginx -s reload" /app/nginx.tmpl /etc/nginx/conf.d/default.conf +nginx: nginx diff --git a/README.md b/README.md index 8c45102..d09677c 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -![nginx 1.11.8](https://img.shields.io/badge/nginx-1.11.8-brightgreen.svg) ![License MIT](https://img.shields.io/badge/license-MIT-blue.svg) [![Build Status](https://travis-ci.org/jwilder/nginx-proxy.svg?branch=master)](https://travis-ci.org/jwilder/nginx-proxy) [![](https://img.shields.io/docker/stars/jwilder/nginx-proxy.svg)](https://hub.docker.com/r/jwilder/nginx-proxy 'DockerHub') [![](https://img.shields.io/docker/pulls/jwilder/nginx-proxy.svg)](https://hub.docker.com/r/jwilder/nginx-proxy 'DockerHub') +![nginx 1.11.10](https://img.shields.io/badge/nginx-1.11.10-brightgreen.svg) ![License MIT](https://img.shields.io/badge/license-MIT-blue.svg) [![Build Status](https://travis-ci.org/jwilder/nginx-proxy.svg?branch=master)](https://travis-ci.org/jwilder/nginx-proxy) [![](https://img.shields.io/docker/stars/jwilder/nginx-proxy.svg)](https://hub.docker.com/r/jwilder/nginx-proxy 'DockerHub') [![](https://img.shields.io/docker/pulls/jwilder/nginx-proxy.svg)](https://hub.docker.com/r/jwilder/nginx-proxy 'DockerHub') nginx-proxy sets up a container running nginx and [docker-gen][1]. docker-gen generates reverse proxy configs for nginx and reloads nginx when containers are started and stopped. @@ -15,10 +15,26 @@ Then start any containers you want proxied with an env var `VIRTUAL_HOST=subdoma $ docker run -e VIRTUAL_HOST=foo.bar.com ... -The containers being proxied must [expose](https://docs.docker.com/reference/run/#expose-incoming-ports) the port to be proxied, either by using the `EXPOSE` directive in their `Dockerfile` or by using the `--expose` flag to `docker run` or `docker create`. +The containers being proxied must [expose](https://docs.docker.com/engine/reference/run/#expose-incoming-ports) the port to be proxied, either by using the `EXPOSE` directive in their `Dockerfile` or by using the `--expose` flag to `docker run` or `docker create`. Provided your DNS is setup to forward foo.bar.com to the a host running nginx-proxy, the request will be routed to a container with the VIRTUAL_HOST env var set. +### Image variants + +The nginx-proxy images are available in two flavors. + +#### jwilder/nginx-proxy:latest + +This image uses the debian:jessie based nginx image. + + $ docker pull jwilder/nginx-proxy:latest + +#### jwilder/nginx-proxy:alpine + +This image is based on the nginx:alpine image. + + $ docker pull jwilder/nginx-proxy:alpine + ### Docker Compose ```yaml @@ -45,6 +61,12 @@ $ curl -H "Host: whoami.local" localhost I'm 5b129ab83266 ``` +### IPv6 support + +You can activate the IPv6 support for the nginx-proxy container by passing the value `true` to the `ENABLE_IPV6` environment variable: + + $ docker run -d -p 80:80 -e ENABLE_IPV6=true -v /var/run/docker.sock:/tmp/docker.sock:ro jwilder/nginx-proxy + ### Multiple Ports If your container exposes multiple ports, nginx-proxy will default to the service running on port 80. If you need to specify a different port, you can set a VIRTUAL_PORT env var to select a different one. If your container only exposes one port and it has a VIRTUAL_HOST env var set, that port will be selected. @@ -129,7 +151,7 @@ Finally, start your containers with `VIRTUAL_HOST` environment variables. $ docker run -e VIRTUAL_HOST=foo.bar.com ... ### SSL Support using letsencrypt -[letsencrypt-nginx-proxy-companion](https://github.com/JrCs/docker-letsencrypt-nginx-proxy-companion)is a lightweight companion container for the nginx-proxy. It allow the creation/renewal of Let's Encrypt certificates automatically. +[letsencrypt-nginx-proxy-companion](https://github.com/JrCs/docker-letsencrypt-nginx-proxy-companion) is a lightweight companion container for the nginx-proxy. It allow the creation/renewal of Let's Encrypt certificates automatically. ### SSL Support @@ -307,7 +329,7 @@ If you are using multiple hostnames for a single container (e.g. `VIRTUAL_HOST=e #### Per-VIRTUAL_HOST location default configuration If you want most of your virtual hosts to use a default single `location` block configuration and then override on a few specific ones, add those settings to the `/etc/nginx/vhost.d/default_location` file. This file -will be used on any virtual host which does not have a `/etc/nginx/vhost.d/{VIRTUAL_HOST}` file associated with it. +will be used on any virtual host which does not have a `/etc/nginx/vhost.d/{VIRTUAL_HOST}_location` file associated with it. ### Contributing @@ -315,6 +337,22 @@ Before submitting pull requests or issues, please check github to make sure an e #### Running Tests Locally -To run tests, you'll need to install [bats 0.4.0](https://github.com/sstephenson/bats). +To run tests, you need to prepare the docker image to test which must be tagged `jwilder/nginx-proxy:test`: + + docker build -t jwilder/nginx-proxy:test . # build the Debian variant image + +and call the [test/pytest.sh](test/pytest.sh) script. + +Then build the Alpine variant of the image: + + docker build -f Dockerfile.alpine -t jwilder/nginx-proxy:test . # build the Alpline variant image + +and call the [test/pytest.sh](test/pytest.sh) script again. + + +If your system has the `make` command, you can automate those tasks by calling: make test + + +You can learn more about how the test suite works and how to write new tests in the [test/README.md](test/README.md) file. diff --git a/docker-compose.yml b/docker-compose.yml index 044f022..b76f0c0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,4 +12,3 @@ services: image: jwilder/whoami environment: - VIRTUAL_HOST=whoami.local - diff --git a/nginx.tmpl b/nginx.tmpl index 66cd5c6..6802214 100644 --- a/nginx.tmpl +++ b/nginx.tmpl @@ -77,9 +77,13 @@ proxy_set_header X-Forwarded-Port $proxy_x_forwarded_port; proxy_set_header Proxy ""; {{ end }} +{{ $enable_ipv6 := eq (or ($.Env.ENABLE_IPV6) "") "true" }} server { server_name _; # This is just an invalid value which will never trigger on a real hostname. listen 80; + {{ if $enable_ipv6 }} + listen [::]:80; + {{ end }} access_log /var/log/nginx/access.log vhost; return 503; } @@ -88,6 +92,9 @@ server { server { server_name _; # This is just an invalid value which will never trigger on a real hostname. listen 443 ssl http2; + {{ if $enable_ipv6 }} + listen [::]:443 ssl http2; + {{ end }} access_log /var/log/nginx/access.log vhost; return 503; @@ -98,7 +105,8 @@ server { {{ end }} {{ range $host, $containers := groupByMulti $ "Env.VIRTUAL_HOST" "," }} -{{ $upstream_name := sha1 $host }} +{{ $is_regexp := hasPrefix "~" $host }} +{{ $upstream_name := when $is_regexp (sha1 $host) $host }} # {{ $host }} upstream {{ $upstream_name }} { {{ range $container := $containers }} @@ -155,6 +163,9 @@ upstream {{ $upstream_name }} { server { server_name {{ $host }}; listen 80 {{ $default_server }}; + {{ if $enable_ipv6 }} + listen [::]:80 {{ $default_server }}; + {{ end }} access_log /var/log/nginx/access.log vhost; return 301 https://$host$request_uri; } @@ -163,6 +174,9 @@ server { server { server_name {{ $host }}; listen 443 ssl http2 {{ $default_server }}; + {{ if $enable_ipv6 }} + listen [::]:443 ssl http2 {{ $default_server }}; + {{ end }} access_log /var/log/nginx/access.log vhost; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; @@ -216,6 +230,9 @@ server { server { server_name {{ $host }}; listen 80 {{ $default_server }}; + {{ if $enable_ipv6 }} + listen [::]:80 {{ $default_server }}; + {{ end }} access_log /var/log/nginx/access.log vhost; {{ if (exists (printf "/etc/nginx/vhost.d/%s" $host)) }} @@ -247,6 +264,9 @@ server { server { server_name {{ $host }}; listen 443 ssl http2 {{ $default_server }}; + {{ if $enable_ipv6 }} + listen [::]:443 ssl http2 {{ $default_server }}; + {{ end }} access_log /var/log/nginx/access.log vhost; return 500; diff --git a/test/README.md b/test/README.md index 721d436..9d7a57c 100644 --- a/test/README.md +++ b/test/README.md @@ -1,14 +1,107 @@ -Test suite -========== - -This test suite is implemented on top of the [Bats](https://github.com/sstephenson/bats/blob/master/README.md) test framework. - -It is intended to verify the correct behavior of the Docker image `jwilder/nginx-proxy:bats`. - -Running the test suite ----------------------- - -Make sure you have Bats installed, then run: - - docker build -t jwilder/nginx-proxy:bats . - bats test/ \ No newline at end of file +Nginx proxy test suite +====================== + +Install requirements +-------------------- + +You need [python 2.7](https://www.python.org/) and [pip](https://pip.pypa.io/en/stable/installing/) installed. Then run the commands: + + requirements/build.sh + pip install -r requirements/python-requirements.txt + +If you can't install those requirements on your computer, you can alternatively use the _pytest.sh_ script which will run the tests from a Docker container which has those requirements. + + +Prepare the nginx-proxy test image +---------------------------------- + + docker build -t jwilder/nginx-proxy:test .. + +or if you want to test the alpine flavor: + + docker build -t jwilder/nginx-proxy:test -f Dockerfile.alpine .. + +make sure to tag that test image exactly `jwilder/nginx-proxy:test` or the test suite won't work. + + +Run the test suite +------------------ + + pytest + +need more verbosity ? + + pytest -s + + +Run one single test module +-------------------------- + + pytest test_nominal.py + + +Write a test module +------------------- + +This test suite uses [pytest](http://doc.pytest.org/en/latest/). The [conftest.py](conftest.py) file will be automatically loaded by pytest and will provide you with two useful pytest [fixtures](http://doc.pytest.org/en/latest/fixture.html#fixture): + +- docker_compose +- nginxproxy + + +### docker_compose fixture + +When using the `docker_compose` fixture in a test, pytest will try to find a yml file named after your test module filename. For instance, if your test module is `test_example.py`, then the `docker_compose` fixture will try to load a `test_example.yml` [docker compose file](https://docs.docker.com/compose/compose-file/). + +Once the docker compose file found, the fixture will remove all containers, run `docker-compose up`, and finally your test will be executed. + +The fixture will run the _docker-compose_ command with the `-f` option to load the given compose file. So you can test your docker compose file syntax by running it yourself with: + + docker-compose -f test_example.yml up -d + +In the case you are running pytest from within a docker container, the `docker_compose` fixture will make sure the container running pytest is attached to all docker networks. That way, your test will be able to reach any of them. + +In your tests, you can use the `docker_compose` variable to query and command the docker daemon as it provides you with a [client from the docker python module](https://docker-py.readthedocs.io/en/2.0.2/client.html#client-reference). + +Also this fixture alters the way the python interpreter resolves domain names to IP addresses in the following ways: + +Any domain name containing the substring `nginx-proxy` will resolve to the IP address of the container that was created from the `jwilder/nginx-proxy:test` image. So all the following domain names will resolve to the nginx-proxy container in tests: +- `nginx-proxy` +- `nginx-proxy.com` +- `www.nginx-proxy.com` +- `www.nginx-proxy.test` +- `www.nginx-proxy` +- `whatever.nginx-proxyooooooo` +- ... + +Any domain name ending with `XXX.container.docker` will resolve to the IP address of the XXX container. +- `web1.container.docker` will resolve to the IP address of the `web1` container +- `f00.web1.container.docker` will resolve to the IP address of the `web1` container +- `anything.whatever.web2.container.docker` will resolve to the IP address of the `web2` container + +Otherwise, domain names are resoved as usual using your system DNS resolver. + + +### nginxproxy fixture + +The `nginxproxy` fixture will provide you with a replacement for the python [requests](https://pypi.python.org/pypi/requests/) module. This replacement will just repeat up to 30 times a requests if it receives the HTTP error 404 or 502. This error occurs when you try to send queries to nginx-proxy too early after the container creation. + +Also this requests replacement is preconfigured to use the Certificate Authority root certificate [certs/ca-root.crt](certs/) to validate https connections. + +Furthermore, the nginxproxy methods accept an additional keyword parameter: `ipv6` which forces requests made against containers to use the containers IPv6 address when set to `True`. If IPv6 is not supported by the system or docker, that particular test will be skipped. + + def test_forwards_to_web1_ipv6(docker_compose, nginxproxy): + r = nginxproxy.get("http://web1.nginx-proxy.tld/port", ipv6=True) + assert r.status_code == 200 + assert r.text == "answer from port 81\n" + + + +### The web docker image + +When you ran the `requirements/build.sh` script earlier, you built a [`web`](requirements/README.md) docker image which is convenient for running a small web server in a container. This image can produce containers that listens on multiple ports at the same time. + + +### Testing TLS + +If you need to create server certificates, use the [`certs/create_server_certificate.sh`](certs/) script. Pytest will be able to validate any certificate issued from this script. \ No newline at end of file diff --git a/test/certs/README.md b/test/certs/README.md new file mode 100644 index 0000000..343b53e --- /dev/null +++ b/test/certs/README.md @@ -0,0 +1,81 @@ +create_server_certificate.sh +============================ + +`create_server_certificate.sh` is a script helping with issuing server certificates that can be used to provide TLS on web servers. + +It also creates a Certificate Authority (CA) root key and certificate. This CA root certificate can be used to validate the server certificates it generates. + +For instance, with _curl_: + + curl --cacert /somewhere/ca-root.crt https://www.example.com/ + +or with _wget_: + + wget --certificate=/somewhere/ca-root.crt https://www.example.com/ + +or with the python _requests_ module: + + import requests + r = requests.get("https://www.example.com", verify="/somewhere/ca-root.crt") + +Usage +----- + +### Simple domain + +Create a server certificate for domain `www.example.com`: + + ./create_server_certificate.sh www.example.com + +Will produce: + - `www.example.com.key` + - `www.example.com.crt` + + +### Multiple domains + +Create a server certificate for main domain `www.example.com` and alternative domains `example.com`, `foo.com` and `bar.com`: + + ./create_server_certificate.sh www.example.com foo.com bar.com + +Will produce: + - `www.example.com.key` + - `www.example.com.crt` + +### Wildcard domain + +Create a server certificate for wildcard domain `*.example.com`: + + ./create_server_certificate.sh "*.example.com" + +Note that you need to use quotes around the domain string or the shell would expand `*`. + +Will produce: + - `*.example.com.key` + - `*.example.com.crt` + +Again, to prevent your shell from expanding `*`, use quotes. i.e.: `cat "*.example.com.crt"`. + +Such a server certificate would be valid for domains: +- `foo.example.com` +- `bar.example.com` + +but not for domains: +- `example.com` +- `foo.bar.example.com` + + +### Wildcard domain on multiple levels + +While you can technically create a server certificate for wildcard domain `*.example.com` and alternative name `*.*.example.com`, client implementations generally do not support multiple wildcards in a domain name. + +For instance, a python script using urllib3 would fail to validate domain `foo.bar.example.com` presenting a certificate with name `*.*.example.com`. It is advised to stay away from producing such certificates. + +If you want to give it a try: + + ./create_server_certificate.sh "*.example.com" "*.*.example.com" + +Such a server certificate would be valid for domains: +- `foo.example.com` +- `bar.example.com` +- `foo.bar.example.com` diff --git a/test/certs/ca-root.crt b/test/certs/ca-root.crt new file mode 100644 index 0000000..a707881 --- /dev/null +++ b/test/certs/ca-root.crt @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDZDCCAkygAwIBAgIJAJmW6Ju6iJNNMA0GCSqGSIb3DQEBCwUAMD8xHzAdBgNV +BAoMFm5naW54LXByb3h5IHRlc3Qgc3VpdGUxHDAaBgNVBAMME3d3dy5uZ2lueC1w +cm94eS50bGQwHhcNMTcwMTEwMDAwODUxWhcNMjcwMTA4MDAwODUxWjA/MR8wHQYD +VQQKDBZuZ2lueC1wcm94eSB0ZXN0IHN1aXRlMRwwGgYDVQQDDBN3d3cubmdpbngt +cHJveHkudGxkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAndjE3OPr +48hIOQigk/HejrowsQDLNfkkc6vej0J983rJitGTgBfxqq27fOPfqhE5bi1M5JDk +KkrOrSitxCJLgpq+4Ls9/RXg8skZFHRAQbNwuKBehaDkPdamJ0i3dv6e4kZy41oI +RqxQ/MKdminC4LShFZvPoKeh9ae7w1MgB2/4E68LO66bYiHlLNL7ENViSHhLyCmt +qIE7kdV9jgn2NuVJ37m6/6SNQ3GBiIjEW+ooRQ3HEVKBCismcwq80+BD5VS/yW18 +KqX8m4sBM+IgZbcOqrV+APMbGvd8iNJgQSSQC/r0Wscgt7UeggVYKDazjDSPvLUE +FUN5wEmydkP2AQIDAQABo2MwYTAdBgNVHQ4EFgQUJL59pHomt+8dUNxv8HgrYjKf +OA8wHwYDVR0jBBgwFoAUJL59pHomt+8dUNxv8HgrYjKfOA8wDwYDVR0TAQH/BAUw +AwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQADggEBABALxY96YqsZ +CL2crzY0FIGhfjLE7P3mtUGklUpFu7xyI6mGUyL1nJYSnHB5IEV6QLhVVUE/CojI +crXorQWBDkx26AgCt/eIOdvPYC0JDeXiIhH6sld3yH7JGwGqJkfXaUUfUkuwMae7 +mMIEG9e6vfSh/YNTRxs0KBjBcXHHl5K+Dz4h9r14OqnQFqVFZaR6T6td44tDDNhn +beW8iIfCWRqDsnvIcJzLa2QR4onmJSw5DaSeFFaKefhdHEzEBZntLfyFbjRYHT/O ++BRdewhg6rSDkGLcL8n/ZnRLOa+xmegjQ/Op94OmWO3TfXOITJAtkaO2YVZoyek8 +T6ckVovq4zU= +-----END CERTIFICATE----- diff --git a/test/certs/ca-root.key b/test/certs/ca-root.key new file mode 100644 index 0000000..28e86a9 --- /dev/null +++ b/test/certs/ca-root.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEAndjE3OPr48hIOQigk/HejrowsQDLNfkkc6vej0J983rJitGT +gBfxqq27fOPfqhE5bi1M5JDkKkrOrSitxCJLgpq+4Ls9/RXg8skZFHRAQbNwuKBe +haDkPdamJ0i3dv6e4kZy41oIRqxQ/MKdminC4LShFZvPoKeh9ae7w1MgB2/4E68L +O66bYiHlLNL7ENViSHhLyCmtqIE7kdV9jgn2NuVJ37m6/6SNQ3GBiIjEW+ooRQ3H +EVKBCismcwq80+BD5VS/yW18KqX8m4sBM+IgZbcOqrV+APMbGvd8iNJgQSSQC/r0 +Wscgt7UeggVYKDazjDSPvLUEFUN5wEmydkP2AQIDAQABAoIBAQCDM4zetix6lx1B +GuSuVFrTc/vJBInkgQRFiVRi67fZS/R+CJl73WsonWO7+YUNzWdZJxpE2hJs/OUx +lSBqaL8u/gUuszRhS3BBHdpU4BQRCF/ndpVaqVNN+z78ZDrrE9Vo63nPdCRw6gYf +MnzhiVjMghdq6Kn6NZwvno45WrzCsIbrrQ4zU+S2PhG8MTA53jzqqQ8mUSJX0lAl +6b9+1aWA0d0Jnk3M3doaFU/Dlnz3n6kkx0AdqNe8bdsFrPfwsrF+dwGx04SGgLmK +V2OjIDFYYGtiHp3PJ9IYIA32ij+UloSDDZ2BxXkma8Zilw04ytY5l8tlk2ZDWTD9 +U2MXxjmBAoGBAMmmI19I/asTPjljlqzrOsrdRkklJvnCHgy/yw9u3nMfkJ0lLGAp +mZoCqJIEsAqlLGM5bOjKy3KQ3n2SBX3mz7/RajnpJRTnNLeJIPAAXHN9TDyKcWRo +Los6xHN7YMSLYKs4HMihXp9Yu4Ms88/8nO/01nufjN0rTgFnWdL0WfxJAoGBAMhk +Qm92ukMmbrXSrV0WF+eFooHwgPmUWZ1oZY5ZHmO3FCuSBHiICGrWKmdbcG6H5zmZ +oFZ0unsvk2Yjl+/+tntxr/dwp6Q+chsqkLms8GE76NWEO8qn4hQNywkFgpKlPci3 +n5IqpuQ2DpJ1PAQkwgZD/5rSscNidNMezXO5Uvv5AoGBALR291kjXcJpKlr6AbMn +oipD9c8obMVBMNuAGh7pvjORoD7DMf+tu0XV8z8a6uHcCOmUTx/XvlP9yuDeegO/ +OVYV+NdzDDi04r0PAGdKK3NAQ6Y60Fhn1J/OLFqdpHDBu/X/9eKoaKJ7KvWumVUe +YuVtXTauB8c4JkujTwQ4ov/hAoGAHxvhbGhkFhSbT0K7gx3w7BJE3iM2AojTOKqC +SYzwOM6tJO5wHz4PAHbq8kyxsZcLgFenGoTYhlMmcM7JwYorThKiHKmyfL7s++ap +vQlp785bIPp8RcO2RyK1CFuAn79jTgujjA9vBTKXJIlqncIPFOXtgl1/FzPrqvK3 +NmXoyhECgYEAje9hM9RYO0jbfmTZoQh+onMRz34SM9XWLH+NQGgfvsGtjeRnrUKK +GuWQz/GQGJLy/Uc1KHIdrfPDjvQhZXmPL1v7pNfCrqyj+EnKCNDPPnYq5Zq4WLsB +x1hKPH0LmfEBkXOiFGrD3h3KAuBK5nb0/EFBDR4JuMaySC5CpbOds9o= +-----END RSA PRIVATE KEY----- diff --git a/test/certs/create_server_certificate.sh b/test/certs/create_server_certificate.sh new file mode 100755 index 0000000..833b97c --- /dev/null +++ b/test/certs/create_server_certificate.sh @@ -0,0 +1,183 @@ +#!/bin/bash +set -u +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +if [[ "$#" -eq 0 ]]; then + cat <<-EOF + + To generate a server certificate, provide the domain name as a parameter: + $(basename $0) www.my-domain.tdl + $(basename $0) www.my-domain.tdl alternate.domain.tld + + You can also create certificates for wildcard domains: + $(basename $0) '*.my-domain.tdl' + + EOF + exit 0 +else + DOMAIN="$1" + ALTERNATE_DOMAINS="DNS:$( echo "$@" | sed 's/ /,DNS:/g')" +fi + + +############################################################################### +# Create a nginx container (which conveniently provides the `openssl` command) +############################################################################### + +CONTAINER=$(docker run -d -v $DIR:/work -w /work -e SAN="$ALTERNATE_DOMAINS" nginx:1.11.8) +# Configure openssl +docker exec $CONTAINER bash -c ' + mkdir -p /ca/{certs,crl,private,newcerts} 2>/dev/null + echo 1000 > /ca/serial + touch /ca/index.txt + cat > /ca/openssl.cnf <<-"OESCRIPT" + [ ca ] + # `man ca` + default_ca = CA_default + + [ CA_default ] + # Directory and file locations. + dir = /ca + certs = $dir/certs + crl_dir = $dir/crl + new_certs_dir = $dir/newcerts + database = $dir/index.txt + serial = $dir/serial + RANDFILE = $dir/private/.rand + + # The root key and root certificate. + private_key = /work/ca-root.key + certificate = /work/ca-root.crt + + # SHA-1 is deprecated, so use SHA-2 instead. + default_md = sha256 + + name_opt = ca_default + cert_opt = ca_default + default_days = 10000 + preserve = no + policy = policy_loose + + [ policy_loose ] + countryName = optional + stateOrProvinceName = optional + localityName = optional + organizationName = optional + organizationalUnitName = optional + commonName = supplied + emailAddress = optional + + [ req ] + # Options for the `req` tool (`man req`). + default_bits = 2048 + distinguished_name = req_distinguished_name + string_mask = utf8only + + # SHA-1 is deprecated, so use SHA-2 instead. + default_md = sha256 + + # Extension to add when the -x509 option is used. + x509_extensions = v3_ca + + [ req_distinguished_name ] + # See . + countryName = Country Name (2 letter code) + stateOrProvinceName = State or Province Name + localityName = Locality Name + 0.organizationName = Organization Name + organizationalUnitName = Organizational Unit Name + commonName = Common Name + emailAddress = Email Address + + [ v3_ca ] + # Extensions for a typical CA (`man x509v3_config`). + subjectKeyIdentifier = hash + authorityKeyIdentifier = keyid:always,issuer + basicConstraints = critical, CA:true + keyUsage = critical, digitalSignature, cRLSign, keyCertSign + + [ server_cert ] + # Extensions for server certificates (`man x509v3_config`). + basicConstraints = CA:FALSE + nsCertType = server + nsComment = server certificate generated for test purpose (nginx-proxy test suite) + subjectKeyIdentifier = hash + authorityKeyIdentifier = keyid,issuer:always + keyUsage = critical, digitalSignature, keyEncipherment + extendedKeyUsage = serverAuth + + [ san_env ] + subjectAltName=${ENV::SAN} + OESCRIPT +' + +# shortcut for calling `openssl` inside the container +function openssl { + docker exec $CONTAINER openssl "$@" +} + +function exitfail { + echo + echo ERROR: "$@" + docker rm -f $CONTAINER + exit 1 +} + + +############################################################################### +# Setup Certificate authority +############################################################################### + +if ! [[ -f "$DIR/ca-root.key" ]]; then + echo + echo "> Create a Certificate Authority root key: $DIR/ca-root.key" + openssl genrsa -out ca-root.key 2048 + [[ $? -eq 0 ]] || exitfail failed to generate CA root key +fi + +# Create a CA root certificate +if ! [[ -f "$DIR/ca-root.crt" ]]; then + echo + echo "> Create a CA root certificate: $DIR/ca-root.crt" + openssl req -config /ca/openssl.cnf \ + -key ca-root.key \ + -new -x509 -days 3650 -subj "/O=nginx-proxy test suite/CN=www.nginx-proxy.tld" -extensions v3_ca \ + -out ca-root.crt + [[ $? -eq 0 ]] || exitfail failed to generate CA root certificate + + # Verify certificate + openssl x509 -noout -text -in ca-root.crt +fi + + +############################################################################### +# create server key and certificate signed by the certificate authority +############################################################################### + +echo +echo "> Create a host key: $DIR/$DOMAIN.key" +openssl genrsa -out "$DOMAIN.key" 2048 + +echo +echo "> Create a host certificate signing request" + +SAN="$ALTERNATE_DOMAINS" openssl req -config /ca/openssl.cnf \ + -key "$DOMAIN.key" \ + -new -out "/ca/$DOMAIN.csr" -days 1000 -extensions san_env -subj "/CN=$DOMAIN" + [[ $? -eq 0 ]] || exitfail failed to generate server certificate signing request + +echo +echo "> Create server certificate: $DIR/$DOMAIN.crt" +SAN="$ALTERNATE_DOMAINS" openssl ca -config /ca/openssl.cnf -batch \ + -extensions server_cert \ + -extensions san_env \ + -in "/ca/$DOMAIN.csr" \ + -out "$DOMAIN.crt" + [[ $? -eq 0 ]] || exitfail failed to generate server certificate + + +# Verify host certificate +#openssl x509 -noout -text -in "$DOMAIN.crt" + + +docker rm -f $CONTAINER >/dev/null diff --git a/test/conftest.py b/test/conftest.py new file mode 100644 index 0000000..fcff6ea --- /dev/null +++ b/test/conftest.py @@ -0,0 +1,461 @@ +from __future__ import print_function +import contextlib +import logging +import os +import shlex +import socket +import subprocess +import time +import re + +import backoff +import docker +import pytest +import requests +from _pytest._code.code import ReprExceptionInfo +from requests.packages.urllib3.util.connection import HAS_IPV6 + +logging.basicConfig(level=logging.INFO) +logging.getLogger('backoff').setLevel(logging.INFO) +logging.getLogger('DNS').setLevel(logging.DEBUG) +logging.getLogger('requests.packages.urllib3.connectionpool').setLevel(logging.WARN) + +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") +FORCE_CONTAINER_IPV6 = False # ugly global state to consider containers' IPv6 address instead of IPv4 + + +docker_client = docker.from_env() + + +############################################################################### +# +# utilities +# +############################################################################### + +@contextlib.contextmanager +def ipv6(force_ipv6=True): + """ + Meant to be used as a context manager to force IPv6 sockets: + + with ipv6(): + nginxproxy.get("http://something.nginx-proxy.local") # force use of IPv6 + + with ipv6(False): + nginxproxy.get("http://something.nginx-proxy.local") # legacy behavior + + + """ + global FORCE_CONTAINER_IPV6 + FORCE_CONTAINER_IPV6 = force_ipv6 + yield + FORCE_CONTAINER_IPV6 = False + + +class requests_for_docker(object): + """ + Proxy for calling methods of the requests module. + When a HTTP response failed due to HTTP Error 404 or 502, retry a few times. + Provides method `get_conf` to extract the nginx-proxy configuration content. + """ + def __init__(self): + self.session = requests.Session() + if os.path.isfile(CA_ROOT_CERTIFICATE): + self.session.verify = CA_ROOT_CERTIFICATE + + def get_conf(self): + """ + Return the nginx config file + """ + nginx_proxy_containers = docker_client.containers.list(filters={"ancestor": "jwilder/nginx-proxy:test"}) + if len(nginx_proxy_containers) > 1: + pytest.fail("Too many running jwilder/nginx-proxy:test containers", pytrace=False) + elif len(nginx_proxy_containers) == 0: + pytest.fail("No running jwilder/nginx-proxy:test container", pytrace=False) + return get_nginx_conf_from_container(nginx_proxy_containers[0]) + + def get(self, *args, **kwargs): + with ipv6(kwargs.pop('ipv6', False)): + @backoff.on_predicate(backoff.constant, lambda r: r.status_code in (404, 502), interval=.3, max_tries=30, jitter=None) + def _get(*args, **kwargs): + return self.session.get(*args, **kwargs) + return _get(*args, **kwargs) + + def post(self, *args, **kwargs): + with ipv6(kwargs.pop('ipv6', False)): + @backoff.on_predicate(backoff.constant, lambda r: r.status_code in (404, 502), interval=.3, max_tries=30, jitter=None) + def _post(*args, **kwargs): + return self.session.post(*args, **kwargs) + return _post(*args, **kwargs) + + def put(self, *args, **kwargs): + with ipv6(kwargs.pop('ipv6', False)): + @backoff.on_predicate(backoff.constant, lambda r: r.status_code in (404, 502), interval=.3, max_tries=30, jitter=None) + def _put(*args, **kwargs): + return self.session.put(*args, **kwargs) + return _put(*args, **kwargs) + + def head(self, *args, **kwargs): + with ipv6(kwargs.pop('ipv6', False)): + @backoff.on_predicate(backoff.constant, lambda r: r.status_code in (404, 502), interval=.3, max_tries=30, jitter=None) + def _head(*args, **kwargs): + return self.session.head(*args, **kwargs) + return _head(*args, **kwargs) + + def delete(self, *args, **kwargs): + with ipv6(kwargs.pop('ipv6', False)): + @backoff.on_predicate(backoff.constant, lambda r: r.status_code in (404, 502), interval=.3, max_tries=30, jitter=None) + def _delete(*args, **kwargs): + return self.session.delete(*args, **kwargs) + return _delete(*args, **kwargs) + + def options(self, *args, **kwargs): + with ipv6(kwargs.pop('ipv6', False)): + @backoff.on_predicate(backoff.constant, lambda r: r.status_code in (404, 502), interval=.3, max_tries=30, jitter=None) + def _options(*args, **kwargs): + return self.session.options(*args, **kwargs) + return _options(*args, **kwargs) + + def __getattr__(self, name): + return getattr(requests, name) + + +def container_ip(container): + """ + return the IP address of a container. + + If the global FORCE_CONTAINER_IPV6 flag is set, return the IPv6 address + """ + global FORCE_CONTAINER_IPV6 + if FORCE_CONTAINER_IPV6: + if not HAS_IPV6: + pytest.skip("This system does not support IPv6") + ip = container_ipv6(container) + if ip == '': + pytest.skip("Container %s has no IPv6 address" % container.name) + else: + return ip + else: + net_info = container.attrs["NetworkSettings"]["Networks"] + if "bridge" in net_info: + return net_info["bridge"]["IPAddress"] + + # not default bridge network, fallback on first network defined + network_name = net_info.keys()[0] + return net_info[network_name]["IPAddress"] + + +def container_ipv6(container): + """ + return the IPv6 address of a container. + """ + net_info = container.attrs["NetworkSettings"]["Networks"] + if "bridge" in net_info: + return net_info["bridge"]["GlobalIPv6Address"] + + # not default bridge network, fallback on first network defined + network_name = net_info.keys()[0] + return net_info[network_name]["GlobalIPv6Address"] + + +def nginx_proxy_dns_resolver(domain_name): + """ + if "nginx-proxy" if found in host, return the ip address of the docker container + issued from the docker image jwilder/nginx-proxy:test. + + :return: IP or None + """ + log = logging.getLogger('DNS') + log.debug("nginx_proxy_dns_resolver(%r)" % domain_name) + if 'nginx-proxy' in domain_name: + nginxproxy_containers = docker_client.containers.list(filters={"status": "running", "ancestor": "jwilder/nginx-proxy:test"}) + if len(nginxproxy_containers) == 0: + log.warn("no container found from image jwilder/nginx-proxy:test while resolving %r", domain_name) + return + nginxproxy_container = nginxproxy_containers[0] + ip = container_ip(nginxproxy_container) + log.info("resolving domain name %r as IP address %s of nginx-proxy container %s" % (domain_name, ip, nginxproxy_container.name)) + return ip + +def docker_container_dns_resolver(domain_name): + """ + if domain name is of the form "XXX.container.docker" or "anything.XXX.container.docker", return the ip address of the docker container + named XXX. + + :return: IP or None + """ + log = logging.getLogger('DNS') + log.debug("docker_container_dns_resolver(%r)" % domain_name) + + match = re.search('(^|.+\.)(?P[^.]+)\.container\.docker$', domain_name) + if not match: + log.debug("%r does not match" % domain_name) + return + + container_name = match.group('container') + log.debug("looking for container %r" % container_name) + try: + container = docker_client.containers.get(container_name) + except docker.errors.NotFound: + log.warn("container named %r not found while resolving %r" % (container_name, domain_name)) + return + log.debug("container %r found (%s)" % (container.name, container.short_id)) + + ip = container_ip(container) + log.info("resolving domain name %r as IP address %s of container %s" % (domain_name, ip, container.name)) + return ip + + +def monkey_patch_urllib_dns_resolver(): + """ + Alter the behavior of the urllib DNS resolver so that any domain name + containing substring 'nginx-proxy' will resolve to the IP address + of the container created from image 'jwilder/nginx-proxy:test'. + """ + prv_getaddrinfo = socket.getaddrinfo + dns_cache = {} + def new_getaddrinfo(*args): + logging.getLogger('DNS').debug("resolving domain name %s" % repr(args)) + _args = list(args) + + # custom DNS resolvers + ip = nginx_proxy_dns_resolver(args[0]) + if ip is None: + ip = docker_container_dns_resolver(args[0]) + if ip is not None: + _args[0] = ip + + # call on original DNS resolver, with eventually the original host changed to the wanted IP address + try: + return dns_cache[tuple(_args)] + except KeyError: + res = prv_getaddrinfo(*_args) + dns_cache[tuple(_args)] = res + return res + socket.getaddrinfo = new_getaddrinfo + return prv_getaddrinfo + +def restore_urllib_dns_resolver(getaddrinfo_func): + socket.getaddrinfo = getaddrinfo_func + + +def remove_all_containers(): + for container in docker_client.containers.list(all=True): + if I_AM_RUNNING_INSIDE_A_DOCKER_CONTAINER and container.id.startswith(socket.gethostname()): + continue # pytest is running within a Docker container, so we do not want to remove that particular container + logging.info("removing container %s" % container.name) + container.remove(v=True, force=True) + + +def get_nginx_conf_from_container(container): + """ + return the nginx /etc/nginx/conf.d/default.conf file content from a container + """ + import tarfile + from cStringIO import StringIO + strm, stat = container.get_archive('/etc/nginx/conf.d/default.conf') + with tarfile.open(fileobj=StringIO(strm.read())) as tf: + conffile = tf.extractfile('default.conf') + return conffile.read() + + +def docker_compose_up(compose_file='docker-compose.yml'): + logging.info('docker-compose -f %s up -d' % compose_file) + try: + subprocess.check_output(shlex.split('docker-compose -f %s up -d' % compose_file), stderr=subprocess.STDOUT) + except subprocess.CalledProcessError, e: + pytest.fail("Error while runninng 'docker-compose -f %s up -d':\n%s" % (compose_file, e.output), pytrace=False) + + +def docker_compose_down(compose_file='docker-compose.yml'): + logging.info('docker-compose -f %s down' % compose_file) + try: + subprocess.check_output(shlex.split('docker-compose -f %s down' % compose_file), stderr=subprocess.STDOUT) + except subprocess.CalledProcessError, e: + pytest.fail("Error while runninng 'docker-compose -f %s down':\n%s" % (compose_file, e.output), pytrace=False) + + +def wait_for_nginxproxy_to_be_ready(): + """ + If one (and only one) container started from image jwilder/nginx-proxy:test is found, + wait for its log to contain substring "Watching docker events" + """ + containers = docker_client.containers.list(filters={"ancestor": "jwilder/nginx-proxy:test"}) + if len(containers) != 1: + return + container = containers[0] + for line in container.logs(stream=True): + if "Watching docker events" in line: + logging.debug("nginx-proxy ready") + break + +def find_docker_compose_file(request): + """ + helper for fixture functions to figure out the name of the docker-compose file to consider. + + - if the test module provides a `docker_compose_file` variable, take that + - else, if a yaml file exists with the same name as the test module (but for the `.yml` extension), use that + - otherwise use `docker-compose.yml`. + """ + test_module_dir = os.path.dirname(request.module.__file__) + yml_file = os.path.join(test_module_dir, request.module.__name__ + '.yml') + yaml_file = os.path.join(test_module_dir, request.module.__name__ + '.yaml') + default_file = os.path.join(test_module_dir, 'docker-compose.yml') + + docker_compose_file_module_variable = getattr(request.module, "docker_compose_file", None) + if docker_compose_file_module_variable is not None: + docker_compose_file = os.path.join( test_module_dir, docker_compose_file_module_variable) + if not os.path.isfile(docker_compose_file): + raise ValueError("docker compose file %r could not be found. Check your test module `docker_compose_file` variable value." % docker_compose_file) + else: + if os.path.isfile(yml_file): + docker_compose_file = yml_file + elif os.path.isfile(yaml_file): + docker_compose_file = yaml_file + else: + docker_compose_file = default_file + + if not os.path.isfile(docker_compose_file): + logging.error("Could not find any docker-compose file named either '{0}.yml', '{0}.yaml' or 'docker-compose.yml'".format(request.module.__name__)) + + logging.debug("using docker compose file %s" % docker_compose_file) + return docker_compose_file + + +def connect_to_network(network): + """ + If we are running from a container, connect our container to the given network + + :return: the name of the network we were connected to, or None + """ + if I_AM_RUNNING_INSIDE_A_DOCKER_CONTAINER: + try: + my_container = docker_client.containers.get(socket.gethostname()) + except docker.errors.NotFound: + logging.warn("container %r not found" % socket.gethostname()) + return + + # figure out our container networks + my_networks = my_container.attrs["NetworkSettings"]["Networks"].keys() + + # make sure our container is connected to the nginx-proxy's network + if network not in my_networks: + logging.info("Connecting to docker network: %s" % network.name) + network.connect(my_container) + return network + + +def disconnect_from_network(network=None): + """ + If we are running from a container, disconnect our container from the given network. + + :param network: name of a docker network to disconnect from + """ + if I_AM_RUNNING_INSIDE_A_DOCKER_CONTAINER and network is not None: + try: + my_container = docker_client.containers.get(socket.gethostname()) + except docker.errors.NotFound: + logging.warn("container %r not found" % socket.gethostname()) + return + + # figure out our container networks + my_networks_names = my_container.attrs["NetworkSettings"]["Networks"].keys() + + # disconnect our container from the given network + if network.name in my_networks_names: + logging.info("Disconnecting from network %s" % network.name) + network.disconnect(my_container) + + +def connect_to_all_networks(): + """ + If we are running from a container, connect our container to all current docker networks. + + :return: a list of networks we connected to + """ + if not I_AM_RUNNING_INSIDE_A_DOCKER_CONTAINER: + return [] + else: + # find the list of docker networks + networks = filter(lambda network: len(network.containers) > 0 and network.name != 'bridge', docker_client.networks.list()) + return [connect_to_network(network) for network in networks] + + +############################################################################### +# +# Py.test fixtures +# +############################################################################### + +@pytest.yield_fixture(scope="module") +def docker_compose(request): + """ + pytest fixture providing containers described in a docker compose file. After the tests, remove the created containers + + A custom docker compose file name can be defined in a variable named `docker_compose_file`. + + Also, in the case where pytest is running from a docker container, this fixture makes sure + our container will be attached to all the docker networks. + """ + docker_compose_file = find_docker_compose_file(request) + original_dns_resolver = monkey_patch_urllib_dns_resolver() + remove_all_containers() + docker_compose_up(docker_compose_file) + networks = connect_to_all_networks() + wait_for_nginxproxy_to_be_ready() + time.sleep(3) # give time to containers to be ready + yield docker_client + for network in networks: + disconnect_from_network(network) + docker_compose_down(docker_compose_file) + restore_urllib_dns_resolver(original_dns_resolver) + + +@pytest.yield_fixture() +def nginxproxy(): + """ + Provides the `nginxproxy` object that can be used in the same way the requests module is: + + r = nginxproxy.get("http://foo.com") + + The difference is that in case an HTTP requests has status code 404 or 502 (which mostly + indicates that nginx has just reloaded), we retry up to 30 times the query. + + Also, the nginxproxy methods accept an additional keyword parameter: `ipv6` which forces requests + made against containers to use the containers IPv6 address when set to `True`. If IPv6 is not + supported by the system or docker, that particular test will be skipped. + """ + yield requests_for_docker() + + +############################################################################### +# +# Py.test hooks +# +############################################################################### + +# pytest hook to display additionnal stuff in test report +def pytest_runtest_logreport(report): + if report.failed: + if isinstance(report.longrepr, ReprExceptionInfo): + test_containers = docker_client.containers.list(all=True, filters={"ancestor": "jwilder/nginx-proxy:test"}) + for container in test_containers: + report.longrepr.addsection('nginx-proxy logs', container.logs()) + report.longrepr.addsection('nginx-proxy conf', get_nginx_conf_from_container(container)) + + + +############################################################################### +# +# Check requirements +# +############################################################################### + +try: + docker_client.images.get('jwilder/nginx-proxy:test') +except docker.errors.ImageNotFound: + pytest.exit("The docker image 'jwilder/nginx-proxy:test' is missing") + +if docker.__version__ != "2.0.2": + pytest.exit("This test suite is meant to work with the python docker module v2.0.2") diff --git a/test/default-host.bats b/test/default-host.bats deleted file mode 100644 index acdffc6..0000000 --- a/test/default-host.bats +++ /dev/null @@ -1,33 +0,0 @@ -#!/usr/bin/env bats -load test_helpers - -function setup { - # make sure to stop any web container before each test so we don't - # have any unexpected contaiener running with VIRTUAL_HOST or VIRUTAL_PORT set - stop_bats_containers web -} - - -@test "[$TEST_FILE] DEFAULT_HOST=web1.bats" { - SUT_CONTAINER=bats-nginx-proxy-${TEST_FILE}-1 - - # GIVEN a webserver with VIRTUAL_HOST set to web.bats - prepare_web_container bats-web 80 -e VIRTUAL_HOST=web.bats - - # WHEN nginx-proxy runs with DEFAULT_HOST set to web.bats - run nginxproxy $SUT_CONTAINER -v /var/run/docker.sock:/tmp/docker.sock:ro -e DEFAULT_HOST=web.bats - assert_success - docker_wait_for_log $SUT_CONTAINER 9 "Watching docker events" - - # THEN querying the proxy without Host header → 200 - run curl_container $SUT_CONTAINER / --head - assert_output -l 0 $'HTTP/1.1 200 OK\r' - - # THEN querying the proxy with any other Host header → 200 - run curl_container $SUT_CONTAINER / --head --header "Host: something.I.just.made.up" - assert_output -l 0 $'HTTP/1.1 200 OK\r' -} - -@test "[$TEST_FILE] stop all bats containers" { - stop_bats_containers -} diff --git a/test/docker.bats b/test/docker.bats deleted file mode 100644 index 0569dcb..0000000 --- a/test/docker.bats +++ /dev/null @@ -1,123 +0,0 @@ -#!/usr/bin/env bats -load test_helpers - - -@test "[$TEST_FILE] start 2 web containers" { - prepare_web_container bats-web1 81 -e VIRTUAL_HOST=web1.bats - prepare_web_container bats-web2 82 -e VIRTUAL_HOST=web2.bats -} - - -@test "[$TEST_FILE] -v /var/run/docker.sock:/tmp/docker.sock:ro" { - SUT_CONTAINER=bats-nginx-proxy-${TEST_FILE}-1 - - # WHEN nginx-proxy runs on our docker host using the default unix socket - run nginxproxy $SUT_CONTAINER -v /var/run/docker.sock:/tmp/docker.sock:ro - assert_success - docker_wait_for_log $SUT_CONTAINER 9 "Watching docker events" - - # THEN - assert_nginxproxy_behaves $SUT_CONTAINER -} - - -@test "[$TEST_FILE] -v /var/run/docker.sock:/f00.sock:ro -e DOCKER_HOST=unix:///f00.sock" { - SUT_CONTAINER=bats-nginx-proxy-${TEST_FILE}-2 - - # WHEN nginx-proxy runs on our docker host using a custom unix socket - run nginxproxy $SUT_CONTAINER -v /var/run/docker.sock:/f00.sock:ro -e DOCKER_HOST=unix:///f00.sock - assert_success - docker_wait_for_log $SUT_CONTAINER 9 "Watching docker events" - - # THEN - assert_nginxproxy_behaves $SUT_CONTAINER -} - - -@test "[$TEST_FILE] -e DOCKER_HOST=tcp://..." { - SUT_CONTAINER=bats-nginx-proxy-${TEST_FILE}-3 - # GIVEN a container exposing our docker host over TCP - run docker_tcp bats-docker-tcp - assert_success - sleep 1s - - # WHEN nginx-proxy runs on our docker host using tcp to connect to our docker host - run nginxproxy $SUT_CONTAINER -e DOCKER_HOST="tcp://bats-docker-tcp:2375" --link bats-docker-tcp:bats-docker-tcp - assert_success - docker_wait_for_log $SUT_CONTAINER 9 "Watching docker events" - - # THEN - assert_nginxproxy_behaves $SUT_CONTAINER -} - - -@test "[$TEST_FILE] separated containers (nginx + docker-gen + nginx.tmpl)" { - docker_clean bats-nginx - docker_clean bats-docker-gen - - # GIVEN a simple nginx container - run docker run -d \ - --label bats-type="nginx" \ - --name bats-nginx \ - -v /etc/nginx/conf.d/ \ - -v /etc/nginx/certs/ \ - nginx:latest - assert_success - run retry 5 1s docker run --label bats-type="curl" appropriate/curl --silent --fail --head http://$(docker_ip bats-nginx)/ - assert_output -l 0 $'HTTP/1.1 200 OK\r' - - # WHEN docker-gen runs on our docker host - run docker run -d \ - --label bats-type="docker-gen" \ - --name bats-docker-gen \ - -v /var/run/docker.sock:/tmp/docker.sock:ro \ - -v $BATS_TEST_DIRNAME/../nginx.tmpl:/etc/docker-gen/templates/nginx.tmpl:ro \ - --volumes-from bats-nginx \ - --expose 80 \ - jwilder/docker-gen:0.7.3 \ - -notify-sighup bats-nginx \ - -watch \ - -only-exposed \ - /etc/docker-gen/templates/nginx.tmpl \ - /etc/nginx/conf.d/default.conf - assert_success - docker_wait_for_log bats-docker-gen 9 "Watching docker events" - - # Give some time to the docker-gen container to notify bats-nginx so it - # reloads its config - sleep 2s - - run docker_running_state bats-nginx - assert_output "true" || { - docker logs bats-docker-gen - false - } >&2 - - # THEN - assert_nginxproxy_behaves bats-nginx -} - -@test "[$TEST_FILE] stop all bats containers" { - stop_bats_containers -} - - -# $1 nginx-proxy container -function assert_nginxproxy_behaves { - local -r container=$1 - - # Querying the proxy without Host header → 503 - run curl_container $container / --head - assert_output -l 0 $'HTTP/1.1 503 Service Temporarily Unavailable\r' - - # Querying the proxy with Host header → 200 - run curl_container $container /port --header "Host: web1.bats" - assert_output "answer from port 81" - - run curl_container $container /port --header "Host: web2.bats" - assert_output "answer from port 82" - - # Querying the proxy with unknown Host header → 503 - run curl_container $container /port --header "Host: webFOO.bats" --head - assert_output -l 0 $'HTTP/1.1 503 Service Temporarily Unavailable\r' -} diff --git a/test/headers.bats b/test/headers.bats deleted file mode 100644 index bc401fd..0000000 --- a/test/headers.bats +++ /dev/null @@ -1,139 +0,0 @@ -#!/usr/bin/env bats -load test_helpers -SUT_CONTAINER=bats-nginx-proxy-${TEST_FILE} - -function setup { - # make sure to stop any web container before each test so we don't - # have any unexpected container running with VIRTUAL_HOST or VIRUTAL_PORT set - stop_bats_containers web -} - - -@test "[$TEST_FILE] start a nginx-proxy container" { - # GIVEN - run nginxproxy $SUT_CONTAINER -v /var/run/docker.sock:/tmp/docker.sock:ro - assert_success - docker_wait_for_log $SUT_CONTAINER 9 "Watching docker events" -} - -@test "[$TEST_FILE] nginx-proxy passes arbitrary header" { - # WHEN - prepare_web_container bats-host-1 80 -e VIRTUAL_HOST=web.bats - dockergen_wait_for_event $SUT_CONTAINER start bats-host-1 - sleep 1 - - # THEN - run curl_container $SUT_CONTAINER /headers -H "Foo: Bar" -H "Host: web.bats" - assert_output -l 'Foo: Bar' -} - -##### Testing the handling of X-Forwarded-For ##### - -@test "[$TEST_FILE] nginx-proxy generates X-Forwarded-For" { - # WHEN - prepare_web_container bats-host-2 80 -e VIRTUAL_HOST=web.bats - dockergen_wait_for_event $SUT_CONTAINER start bats-host-2 - sleep 1 - - # THEN - run curl_container $SUT_CONTAINER /headers -H "Host: web.bats" - assert_output -p 'X-Forwarded-For:' -} - -@test "[$TEST_FILE] nginx-proxy passes X-Forwarded-For" { - # WHEN - prepare_web_container bats-host-3 80 -e VIRTUAL_HOST=web.bats - dockergen_wait_for_event $SUT_CONTAINER start bats-host-3 - sleep 1 - - # THEN - run curl_container $SUT_CONTAINER /headers -H "X-Forwarded-For: 1.2.3.4" -H "Host: web.bats" - assert_output -p 'X-Forwarded-For: 1.2.3.4, ' -} - -##### Testing the handling of X-Forwarded-Proto ##### - -@test "[$TEST_FILE] nginx-proxy generates X-Forwarded-Proto" { - # WHEN - prepare_web_container bats-host-4 80 -e VIRTUAL_HOST=web.bats - dockergen_wait_for_event $SUT_CONTAINER start bats-host-4 - sleep 1 - - # THEN - run curl_container $SUT_CONTAINER /headers -H "Host: web.bats" - assert_output -l 'X-Forwarded-Proto: http' -} - -@test "[$TEST_FILE] nginx-proxy passes X-Forwarded-Proto" { - # WHEN - prepare_web_container bats-host-5 80 -e VIRTUAL_HOST=web.bats - dockergen_wait_for_event $SUT_CONTAINER start bats-host-5 - sleep 1 - - # THEN - run curl_container $SUT_CONTAINER /headers -H "X-Forwarded-Proto: https" -H "Host: web.bats" - assert_output -l 'X-Forwarded-Proto: https' -} - -##### Testing the handling of X-Forwarded-Port ##### - -@test "[$TEST_FILE] nginx-proxy generates X-Forwarded-Port" { - # WHEN - prepare_web_container bats-host-6 80 -e VIRTUAL_HOST=web.bats - dockergen_wait_for_event $SUT_CONTAINER start bats-host-6 - sleep 1 - - # THEN - run curl_container $SUT_CONTAINER /headers -H "Host: web.bats" - assert_output -l 'X-Forwarded-Port: 80' -} - -@test "[$TEST_FILE] nginx-proxy passes X-Forwarded-Port" { - # WHEN - prepare_web_container bats-host-7 80 -e VIRTUAL_HOST=web.bats - dockergen_wait_for_event $SUT_CONTAINER start bats-host-7 - sleep 1 - - # THEN - run curl_container $SUT_CONTAINER /headers -H "X-Forwarded-Port: 1234" -H "Host: web.bats" - assert_output -l 'X-Forwarded-Port: 1234' -} - -##### Other headers - -@test "[$TEST_FILE] nginx-proxy generates X-Real-IP" { - # WHEN - prepare_web_container bats-host-8 80 -e VIRTUAL_HOST=web.bats - dockergen_wait_for_event $SUT_CONTAINER start bats-host-8 - sleep 1 - - # THEN - run curl_container $SUT_CONTAINER /headers -H "Host: web.bats" - assert_output -p 'X-Real-IP: ' -} - -@test "[$TEST_FILE] nginx-proxy passes Host" { - # WHEN - prepare_web_container bats-host-9 80 -e VIRTUAL_HOST=web.bats - dockergen_wait_for_event $SUT_CONTAINER start bats-host-9 - sleep 1 - - # THEN - run curl_container $SUT_CONTAINER /headers -H "Host: web.bats" - assert_output -l 'Host: web.bats' -} - -@test "[$TEST_FILE] nginx-proxy supresses Proxy for httpoxy protection" { - # WHEN - prepare_web_container bats-host-10 80 -e VIRTUAL_HOST=web.bats - dockergen_wait_for_event $SUT_CONTAINER start bats-host-10 - sleep 1 - - # THEN - run curl_container $SUT_CONTAINER /headers -H "Proxy: tcp://foo.com" -H "Host: web.bats" - refute_output -l 'Proxy: tcp://foo.com' -} - -@test "[$TEST_FILE] stop all bats containers" { - stop_bats_containers -} diff --git a/test/lib/README.md b/test/lib/README.md deleted file mode 100644 index 1021dc4..0000000 --- a/test/lib/README.md +++ /dev/null @@ -1,6 +0,0 @@ -bats lib -======== - -found on https://github.com/sstephenson/bats/pull/110 - -When that pull request will be merged, the `test/lib/bats` won't be necessary anymore. \ No newline at end of file diff --git a/test/lib/bats/batslib.bash b/test/lib/bats/batslib.bash deleted file mode 100644 index 003ada6..0000000 --- a/test/lib/bats/batslib.bash +++ /dev/null @@ -1,596 +0,0 @@ -# -# batslib.bash -# ------------ -# -# The Standard Library is a collection of test helpers intended to -# simplify testing. It contains the following types of test helpers. -# -# - Assertions are functions that perform a test and output relevant -# information on failure to help debugging. They return 1 on failure -# and 0 otherwise. -# -# All output is formatted for readability using the functions of -# `output.bash' and sent to the standard error. -# - -source "${BATS_LIB}/batslib/output.bash" - - -######################################################################## -# ASSERTIONS -######################################################################## - -# Fail and display a message. When no parameters are specified, the -# message is read from the standard input. Other functions use this to -# report failure. -# -# Globals: -# none -# Arguments: -# $@ - [=STDIN] message -# Returns: -# 1 - always -# Inputs: -# STDIN - [=$@] message -# Outputs: -# STDERR - message -fail() { - (( $# == 0 )) && batslib_err || batslib_err "$@" - return 1 -} - -# Fail and display details if the expression evaluates to false. Details -# include the expression, `$status' and `$output'. -# -# NOTE: The expression must be a simple command. Compound commands, such -# as `[[', can be used only when executed with `bash -c'. -# -# Globals: -# status -# output -# Arguments: -# $1 - expression -# Returns: -# 0 - expression evaluates to TRUE -# 1 - otherwise -# Outputs: -# STDERR - details, on failure -assert() { - if ! "$@"; then - { local -ar single=( - 'expression' "$*" - 'status' "$status" - ) - local -ar may_be_multi=( - 'output' "$output" - ) - local -ir width="$( batslib_get_max_single_line_key_width \ - "${single[@]}" "${may_be_multi[@]}" )" - batslib_print_kv_single "$width" "${single[@]}" - batslib_print_kv_single_or_multi "$width" "${may_be_multi[@]}" - } | batslib_decorate 'assertion failed' \ - | fail - fi -} - -# Fail and display details if the expected and actual values do not -# equal. Details include both values. -# -# Globals: -# none -# Arguments: -# $1 - actual value -# $2 - expected value -# Returns: -# 0 - values equal -# 1 - otherwise -# Outputs: -# STDERR - details, on failure -assert_equal() { - if [[ $1 != "$2" ]]; then - batslib_print_kv_single_or_multi 8 \ - 'expected' "$2" \ - 'actual' "$1" \ - | batslib_decorate 'values do not equal' \ - | fail - fi -} - -# Fail and display details if `$status' is not 0. Details include -# `$status' and `$output'. -# -# Globals: -# status -# output -# Arguments: -# none -# Returns: -# 0 - `$status' is 0 -# 1 - otherwise -# Outputs: -# STDERR - details, on failure -assert_success() { - if (( status != 0 )); then - { local -ir width=6 - batslib_print_kv_single "$width" 'status' "$status" - batslib_print_kv_single_or_multi "$width" 'output' "$output" - } | batslib_decorate 'command failed' \ - | fail - fi -} - -# Fail and display details if `$status' is 0. Details include `$output'. -# -# Optionally, when the expected status is specified, fail when it does -# not equal `$status'. In this case, details include the expected and -# actual status, and `$output'. -# -# Globals: -# status -# output -# Arguments: -# $1 - [opt] expected status -# Returns: -# 0 - `$status' is not 0, or -# `$status' equals the expected status -# 1 - otherwise -# Outputs: -# STDERR - details, on failure -assert_failure() { - (( $# > 0 )) && local -r expected="$1" - if (( status == 0 )); then - batslib_print_kv_single_or_multi 6 'output' "$output" \ - | batslib_decorate 'command succeeded, but it was expected to fail' \ - | fail - elif (( $# > 0 )) && (( status != expected )); then - { local -ir width=8 - batslib_print_kv_single "$width" \ - 'expected' "$expected" \ - 'actual' "$status" - batslib_print_kv_single_or_multi "$width" \ - 'output' "$output" - } | batslib_decorate 'command failed as expected, but status differs' \ - | fail - fi -} - -# Fail and display details if the expected does not match the actual -# output or a fragment of it. -# -# By default, the entire output is matched. The assertion fails if the -# expected output does not equal `$output'. Details include both values. -# -# When `-l ' is used, only the -th line is matched. The -# assertion fails if the expected line does not equal -# `${lines[}'. Details include the compared lines and . -# -# When `-l' is used without the argument, the output is searched -# for the expected line. The expected line is matched against each line -# in `${lines[@]}'. If no match is found the assertion fails. Details -# include the expected line and `$output'. -# -# By default, literal matching is performed. Options `-p' and `-r' -# enable partial (i.e. substring) and extended regular expression -# matching, respectively. Specifying an invalid extended regular -# expression with `-r' displays an error. -# -# Options `-p' and `-r' are mutually exclusive. When used -# simultaneously, an error is displayed. -# -# Globals: -# output -# lines -# Options: -# -l - match against the -th element of `${lines[@]}' -# -l - search `${lines[@]}' for the expected line -# -p - partial matching -# -r - extended regular expression matching -# Arguments: -# $1 - expected output -# Returns: -# 0 - expected matches the actual output -# 1 - otherwise -# Outputs: -# STDERR - details, on failure -# error message, on error -assert_output() { - local -i is_match_line=0 - local -i is_match_contained=0 - local -i is_mode_partial=0 - local -i is_mode_regex=0 - - # Handle options. - while (( $# > 0 )); do - case "$1" in - -l) - if (( $# > 2 )) && [[ $2 =~ ^([0-9]|[1-9][0-9]+)$ ]]; then - is_match_line=1 - local -ri idx="$2" - shift - else - is_match_contained=1; - fi - shift - ;; - -p) is_mode_partial=1; shift ;; - -r) is_mode_regex=1; shift ;; - --) break ;; - *) break ;; - esac - done - - if (( is_match_line )) && (( is_match_contained )); then - echo "\`-l' and \`-l ' are mutually exclusive" \ - | batslib_decorate 'ERROR: assert_output' \ - | fail - return $? - fi - - if (( is_mode_partial )) && (( is_mode_regex )); then - echo "\`-p' and \`-r' are mutually exclusive" \ - | batslib_decorate 'ERROR: assert_output' \ - | fail - return $? - fi - - # Arguments. - local -r expected="$1" - - if (( is_mode_regex == 1 )) && [[ '' =~ $expected ]] || (( $? == 2 )); then - echo "Invalid extended regular expression: \`$expected'" \ - | batslib_decorate 'ERROR: assert_output' \ - | fail - return $? - fi - - # Matching. - if (( is_match_contained )); then - # Line contained in output. - if (( is_mode_regex )); then - local -i idx - for (( idx = 0; idx < ${#lines[@]}; ++idx )); do - [[ ${lines[$idx]} =~ $expected ]] && return 0 - done - { local -ar single=( - 'regex' "$expected" - ) - local -ar may_be_multi=( - 'output' "$output" - ) - local -ir width="$( batslib_get_max_single_line_key_width \ - "${single[@]}" "${may_be_multi[@]}" )" - batslib_print_kv_single "$width" "${single[@]}" - batslib_print_kv_single_or_multi "$width" "${may_be_multi[@]}" - } | batslib_decorate 'no output line matches regular expression' \ - | fail - elif (( is_mode_partial )); then - local -i idx - for (( idx = 0; idx < ${#lines[@]}; ++idx )); do - [[ ${lines[$idx]} == *"$expected"* ]] && return 0 - done - { local -ar single=( - 'substring' "$expected" - ) - local -ar may_be_multi=( - 'output' "$output" - ) - local -ir width="$( batslib_get_max_single_line_key_width \ - "${single[@]}" "${may_be_multi[@]}" )" - batslib_print_kv_single "$width" "${single[@]}" - batslib_print_kv_single_or_multi "$width" "${may_be_multi[@]}" - } | batslib_decorate 'no output line contains substring' \ - | fail - else - local -i idx - for (( idx = 0; idx < ${#lines[@]}; ++idx )); do - [[ ${lines[$idx]} == "$expected" ]] && return 0 - done - { local -ar single=( - 'line' "$expected" - ) - local -ar may_be_multi=( - 'output' "$output" - ) - local -ir width="$( batslib_get_max_single_line_key_width \ - "${single[@]}" "${may_be_multi[@]}" )" - batslib_print_kv_single "$width" "${single[@]}" - batslib_print_kv_single_or_multi "$width" "${may_be_multi[@]}" - } | batslib_decorate 'output does not contain line' \ - | fail - fi - elif (( is_match_line )); then - # Specific line. - if (( is_mode_regex )); then - if ! [[ ${lines[$idx]} =~ $expected ]]; then - batslib_print_kv_single 5 \ - 'index' "$idx" \ - 'regex' "$expected" \ - 'line' "${lines[$idx]}" \ - | batslib_decorate 'regular expression does not match line' \ - | fail - fi - elif (( is_mode_partial )); then - if [[ ${lines[$idx]} != *"$expected"* ]]; then - batslib_print_kv_single 9 \ - 'index' "$idx" \ - 'substring' "$expected" \ - 'line' "${lines[$idx]}" \ - | batslib_decorate 'line does not contain substring' \ - | fail - fi - else - if [[ ${lines[$idx]} != "$expected" ]]; then - batslib_print_kv_single 8 \ - 'index' "$idx" \ - 'expected' "$expected" \ - 'actual' "${lines[$idx]}" \ - | batslib_decorate 'line differs' \ - | fail - fi - fi - else - # Entire output. - if (( is_mode_regex )); then - if ! [[ $output =~ $expected ]]; then - batslib_print_kv_single_or_multi 6 \ - 'regex' "$expected" \ - 'output' "$output" \ - | batslib_decorate 'regular expression does not match output' \ - | fail - fi - elif (( is_mode_partial )); then - if [[ $output != *"$expected"* ]]; then - batslib_print_kv_single_or_multi 9 \ - 'substring' "$expected" \ - 'output' "$output" \ - | batslib_decorate 'output does not contain substring' \ - | fail - fi - else - if [[ $output != "$expected" ]]; then - batslib_print_kv_single_or_multi 8 \ - 'expected' "$expected" \ - 'actual' "$output" \ - | batslib_decorate 'output differs' \ - | fail - fi - fi - fi -} - -# Fail and display details if the unexpected matches the actual output -# or a fragment of it. -# -# By default, the entire output is matched. The assertion fails if the -# unexpected output equals `$output'. Details include `$output'. -# -# When `-l ' is used, only the -th line is matched. The -# assertion fails if the unexpected line equals `${lines[}'. -# Details include the compared line and . -# -# When `-l' is used without the argument, the output is searched -# for the unexpected line. The unexpected line is matched against each -# line in `${lines[]}'. If a match is found the assertion fails. -# Details include the unexpected line, the index where it was found and -# `$output' (with the unexpected line highlighted in it if `$output` is -# longer than one line). -# -# By default, literal matching is performed. Options `-p' and `-r' -# enable partial (i.e. substring) and extended regular expression -# matching, respectively. On failure, the substring or the regular -# expression is added to the details (if not already displayed). -# Specifying an invalid extended regular expression with `-r' displays -# an error. -# -# Options `-p' and `-r' are mutually exclusive. When used -# simultaneously, an error is displayed. -# -# Globals: -# output -# lines -# Options: -# -l - match against the -th element of `${lines[@]}' -# -l - search `${lines[@]}' for the unexpected line -# -p - partial matching -# -r - extended regular expression matching -# Arguments: -# $1 - unexpected output -# Returns: -# 0 - unexpected matches the actual output -# 1 - otherwise -# Outputs: -# STDERR - details, on failure -# error message, on error -refute_output() { - local -i is_match_line=0 - local -i is_match_contained=0 - local -i is_mode_partial=0 - local -i is_mode_regex=0 - - # Handle options. - while (( $# > 0 )); do - case "$1" in - -l) - if (( $# > 2 )) && [[ $2 =~ ^([0-9]|[1-9][0-9]+)$ ]]; then - is_match_line=1 - local -ri idx="$2" - shift - else - is_match_contained=1; - fi - shift - ;; - -L) is_match_contained=1; shift ;; - -p) is_mode_partial=1; shift ;; - -r) is_mode_regex=1; shift ;; - --) break ;; - *) break ;; - esac - done - - if (( is_match_line )) && (( is_match_contained )); then - echo "\`-l' and \`-l ' are mutually exclusive" \ - | batslib_decorate 'ERROR: refute_output' \ - | fail - return $? - fi - - if (( is_mode_partial )) && (( is_mode_regex )); then - echo "\`-p' and \`-r' are mutually exclusive" \ - | batslib_decorate 'ERROR: refute_output' \ - | fail - return $? - fi - - # Arguments. - local -r unexpected="$1" - - if (( is_mode_regex == 1 )) && [[ '' =~ $unexpected ]] || (( $? == 2 )); then - echo "Invalid extended regular expression: \`$unexpected'" \ - | batslib_decorate 'ERROR: refute_output' \ - | fail - return $? - fi - - # Matching. - if (( is_match_contained )); then - # Line contained in output. - if (( is_mode_regex )); then - local -i idx - for (( idx = 0; idx < ${#lines[@]}; ++idx )); do - if [[ ${lines[$idx]} =~ $unexpected ]]; then - { local -ar single=( - 'regex' "$unexpected" - 'index' "$idx" - ) - local -a may_be_multi=( - 'output' "$output" - ) - local -ir width="$( batslib_get_max_single_line_key_width \ - "${single[@]}" "${may_be_multi[@]}" )" - batslib_print_kv_single "$width" "${single[@]}" - if batslib_is_single_line "${may_be_multi[1]}"; then - batslib_print_kv_single "$width" "${may_be_multi[@]}" - else - may_be_multi[1]="$( printf '%s' "${may_be_multi[1]}" \ - | batslib_prefix \ - | batslib_mark '>' "$idx" )" - batslib_print_kv_multi "${may_be_multi[@]}" - fi - } | batslib_decorate 'no line should match the regular expression' \ - | fail - return $? - fi - done - elif (( is_mode_partial )); then - local -i idx - for (( idx = 0; idx < ${#lines[@]}; ++idx )); do - if [[ ${lines[$idx]} == *"$unexpected"* ]]; then - { local -ar single=( - 'substring' "$unexpected" - 'index' "$idx" - ) - local -a may_be_multi=( - 'output' "$output" - ) - local -ir width="$( batslib_get_max_single_line_key_width \ - "${single[@]}" "${may_be_multi[@]}" )" - batslib_print_kv_single "$width" "${single[@]}" - if batslib_is_single_line "${may_be_multi[1]}"; then - batslib_print_kv_single "$width" "${may_be_multi[@]}" - else - may_be_multi[1]="$( printf '%s' "${may_be_multi[1]}" \ - | batslib_prefix \ - | batslib_mark '>' "$idx" )" - batslib_print_kv_multi "${may_be_multi[@]}" - fi - } | batslib_decorate 'no line should contain substring' \ - | fail - return $? - fi - done - else - local -i idx - for (( idx = 0; idx < ${#lines[@]}; ++idx )); do - if [[ ${lines[$idx]} == "$unexpected" ]]; then - { local -ar single=( - 'line' "$unexpected" - 'index' "$idx" - ) - local -a may_be_multi=( - 'output' "$output" - ) - local -ir width="$( batslib_get_max_single_line_key_width \ - "${single[@]}" "${may_be_multi[@]}" )" - batslib_print_kv_single "$width" "${single[@]}" - if batslib_is_single_line "${may_be_multi[1]}"; then - batslib_print_kv_single "$width" "${may_be_multi[@]}" - else - may_be_multi[1]="$( printf '%s' "${may_be_multi[1]}" \ - | batslib_prefix \ - | batslib_mark '>' "$idx" )" - batslib_print_kv_multi "${may_be_multi[@]}" - fi - } | batslib_decorate 'line should not be in output' \ - | fail - return $? - fi - done - fi - elif (( is_match_line )); then - # Specific line. - if (( is_mode_regex )); then - if [[ ${lines[$idx]} =~ $unexpected ]] || (( $? == 0 )); then - batslib_print_kv_single 5 \ - 'index' "$idx" \ - 'regex' "$unexpected" \ - 'line' "${lines[$idx]}" \ - | batslib_decorate 'regular expression should not match line' \ - | fail - fi - elif (( is_mode_partial )); then - if [[ ${lines[$idx]} == *"$unexpected"* ]]; then - batslib_print_kv_single 9 \ - 'index' "$idx" \ - 'substring' "$unexpected" \ - 'line' "${lines[$idx]}" \ - | batslib_decorate 'line should not contain substring' \ - | fail - fi - else - if [[ ${lines[$idx]} == "$unexpected" ]]; then - batslib_print_kv_single 5 \ - 'index' "$idx" \ - 'line' "${lines[$idx]}" \ - | batslib_decorate 'line should differ' \ - | fail - fi - fi - else - # Entire output. - if (( is_mode_regex )); then - if [[ $output =~ $unexpected ]] || (( $? == 0 )); then - batslib_print_kv_single_or_multi 6 \ - 'regex' "$unexpected" \ - 'output' "$output" \ - | batslib_decorate 'regular expression should not match output' \ - | fail - fi - elif (( is_mode_partial )); then - if [[ $output == *"$unexpected"* ]]; then - batslib_print_kv_single_or_multi 9 \ - 'substring' "$unexpected" \ - 'output' "$output" \ - | batslib_decorate 'output should not contain substring' \ - | fail - fi - else - if [[ $output == "$unexpected" ]]; then - batslib_print_kv_single_or_multi 6 \ - 'output' "$output" \ - | batslib_decorate 'output equals, but it was expected to differ' \ - | fail - fi - fi - fi -} \ No newline at end of file diff --git a/test/lib/bats/batslib/output.bash b/test/lib/bats/batslib/output.bash deleted file mode 100644 index aa9cb87..0000000 --- a/test/lib/bats/batslib/output.bash +++ /dev/null @@ -1,264 +0,0 @@ -# -# output.bash -# ----------- -# -# Private functions implementing output formatting. Used by public -# helper functions. -# - -# Print a message to the standard error. When no parameters are -# specified, the message is read from the standard input. -# -# Globals: -# none -# Arguments: -# $@ - [=STDIN] message -# Returns: -# none -# Inputs: -# STDIN - [=$@] message -# Outputs: -# STDERR - message -batslib_err() { - { if (( $# > 0 )); then - echo "$@" - else - cat - - fi - } >&2 -} - -# Count the number of lines in the given string. -# -# TODO(ztombol): Fix tests and remove this note after #93 is resolved! -# NOTE: Due to a bug in Bats, `batslib_count_lines "$output"' does not -# give the same result as `${#lines[@]}' when the output contains -# empty lines. -# See PR #93 (https://github.com/sstephenson/bats/pull/93). -# -# Globals: -# none -# Arguments: -# $1 - string -# Returns: -# none -# Outputs: -# STDOUT - number of lines -batslib_count_lines() { - local -i n_lines=0 - local line - while IFS='' read -r line || [[ -n $line ]]; do - (( ++n_lines )) - done < <(printf '%s' "$1") - echo "$n_lines" -} - -# Determine whether all strings are single-line. -# -# Globals: -# none -# Arguments: -# $@ - strings -# Returns: -# 0 - all strings are single-line -# 1 - otherwise -batslib_is_single_line() { - for string in "$@"; do - (( $(batslib_count_lines "$string") > 1 )) && return 1 - done - return 0 -} - -# Determine the length of the longest key that has a single-line value. -# -# This function is useful in determining the correct width of the key -# column in two-column format when some keys may have multi-line values -# and thus should be excluded. -# -# Globals: -# none -# Arguments: -# $odd - key -# $even - value of the previous key -# Returns: -# none -# Outputs: -# STDOUT - length of longest key -batslib_get_max_single_line_key_width() { - local -i max_len=-1 - while (( $# != 0 )); do - local -i key_len="${#1}" - batslib_is_single_line "$2" && (( key_len > max_len )) && max_len="$key_len" - shift 2 - done - echo "$max_len" -} - -# Print key-value pairs in two-column format. -# -# Keys are displayed in the first column, and their corresponding values -# in the second. To evenly line up values, the key column is fixed-width -# and its width is specified with the first parameter (possibly computed -# using `batslib_get_max_single_line_key_width'). -# -# Globals: -# none -# Arguments: -# $1 - width of key column -# $even - key -# $odd - value of the previous key -# Returns: -# none -# Outputs: -# STDOUT - formatted key-value pairs -batslib_print_kv_single() { - local -ir col_width="$1"; shift - while (( $# != 0 )); do - printf '%-*s : %s\n' "$col_width" "$1" "$2" - shift 2 - done -} - -# Print key-value pairs in multi-line format. -# -# The key is displayed first with the number of lines of its -# corresponding value in parenthesis. Next, starting on the next line, -# the value is displayed. For better readability, it is recommended to -# indent values using `batslib_prefix'. -# -# Globals: -# none -# Arguments: -# $odd - key -# $even - value of the previous key -# Returns: -# none -# Outputs: -# STDOUT - formatted key-value pairs -batslib_print_kv_multi() { - while (( $# != 0 )); do - printf '%s (%d lines):\n' "$1" "$( batslib_count_lines "$2" )" - printf '%s\n' "$2" - shift 2 - done -} - -# Print all key-value pairs in either two-column or multi-line format -# depending on whether all values are single-line. -# -# If all values are single-line, print all pairs in two-column format -# with the specified key column width (identical to using -# `batslib_print_kv_single'). -# -# Otherwise, print all pairs in multi-line format after indenting values -# with two spaces for readability (identical to using `batslib_prefix' -# and `batslib_print_kv_multi') -# -# Globals: -# none -# Arguments: -# $1 - width of key column (for two-column format) -# $even - key -# $odd - value of the previous key -# Returns: -# none -# Outputs: -# STDOUT - formatted key-value pairs -batslib_print_kv_single_or_multi() { - local -ir width="$1"; shift - local -a pairs=( "$@" ) - - local -a values=() - local -i i - for (( i=1; i < ${#pairs[@]}; i+=2 )); do - values+=( "${pairs[$i]}" ) - done - - if batslib_is_single_line "${values[@]}"; then - batslib_print_kv_single "$width" "${pairs[@]}" - else - local -i i - for (( i=1; i < ${#pairs[@]}; i+=2 )); do - pairs[$i]="$( batslib_prefix < <(printf '%s' "${pairs[$i]}") )" - done - batslib_print_kv_multi "${pairs[@]}" - fi -} - -# Prefix each line read from the standard input with the given string. -# -# Globals: -# none -# Arguments: -# $1 - [= ] prefix string -# Returns: -# none -# Inputs: -# STDIN - lines -# Outputs: -# STDOUT - prefixed lines -batslib_prefix() { - local -r prefix="${1:- }" - local line - while IFS='' read -r line || [[ -n $line ]]; do - printf '%s%s\n' "$prefix" "$line" - done -} - -# Mark select lines of the text read from the standard input by -# overwriting their beginning with the given string. -# -# Usually the input is indented by a few spaces using `batslib_prefix' -# first. -# -# Globals: -# none -# Arguments: -# $1 - marking string -# $@ - indices (zero-based) of lines to mark -# Returns: -# none -# Inputs: -# STDIN - lines -# Outputs: -# STDOUT - lines after marking -batslib_mark() { - local -r symbol="$1"; shift - # Sort line numbers. - set -- $( sort -nu <<< "$( printf '%d\n' "$@" )" ) - - local line - local -i idx=0 - while IFS='' read -r line || [[ -n $line ]]; do - if (( ${1:--1} == idx )); then - printf '%s\n' "${symbol}${line:${#symbol}}" - shift - else - printf '%s\n' "$line" - fi - (( ++idx )) - done -} - -# Enclose the input text in header and footer lines. -# -# The header contains the given string as title. The output is preceded -# and followed by an additional newline to make it stand out more. -# -# Globals: -# none -# Arguments: -# $1 - title -# Returns: -# none -# Inputs: -# STDIN - text -# Outputs: -# STDOUT - decorated text -batslib_decorate() { - echo - echo "-- $1 --" - cat - - echo '--' - echo -} \ No newline at end of file diff --git a/test/lib/docker_helpers.bash b/test/lib/docker_helpers.bash deleted file mode 100644 index 221234e..0000000 --- a/test/lib/docker_helpers.bash +++ /dev/null @@ -1,66 +0,0 @@ -## functions to help deal with docker - -# Removes container $1 -function docker_clean { - docker kill $1 &>/dev/null ||: - sleep .25s - docker rm -vf $1 &>/dev/null ||: - sleep .25s -} - -# get the ip of docker container $1 -function docker_ip { - docker inspect --format '{{ .NetworkSettings.IPAddress }}' $1 -} - -# get the ip of docker container $1 -function docker_id { - docker inspect --format '{{ .ID }}' $1 -} - -# get the running state of container $1 -# → true/false -# fails if the container does not exist -function docker_running_state { - docker inspect -f {{.State.Running}} $1 -} - -# get the docker container $1 PID -function docker_pid { - docker inspect --format {{.State.Pid}} $1 -} - -# asserts logs from container $1 contains $2 -function docker_assert_log { - local -r container=$1 - shift - run docker logs $container - assert_output -p "$*" -} - -# wait for a container to produce a given text in its log -# $1 container -# $2 timeout in second -# $* text to wait for -function docker_wait_for_log { - local -r container=$1 - local -ir timeout_sec=$2 - shift 2 - retry $(( $timeout_sec * 2 )) .5s docker_assert_log $container "$*" -} - -# Create a docker container named $1 which exposes the docker host unix -# socket over tcp on port 2375. -# -# $1 container name -function docker_tcp { - local container_name="$1" - docker_clean $container_name - docker run -d \ - --label bats-type="socat" \ - --name $container_name \ - --expose 2375 \ - -v /var/run/docker.sock:/var/run/docker.sock \ - rancher/socat-docker - docker run --label bats-type="docker" --link "$container_name:docker" docker:1.10 version -} diff --git a/test/lib/helpers.bash b/test/lib/helpers.bash deleted file mode 100644 index dffcd66..0000000 --- a/test/lib/helpers.bash +++ /dev/null @@ -1,22 +0,0 @@ -## add the retry function to bats - -# Retry a command $1 times until it succeeds. Wait $2 seconds between retries. -function retry { - local attempts=$1 - shift - local delay=$1 - shift - local i - - for ((i=0; i < attempts; i++)); do - run "$@" - if [ "$status" -eq 0 ]; then - echo "$output" - return 0 - fi - sleep $delay - done - - echo "Command \"$@\" failed $attempts times. Status: $status. Output: $output" >&2 - false -} diff --git a/test/lib/ssl/nginx-proxy.bats.crt b/test/lib/ssl/nginx-proxy.bats.crt deleted file mode 100644 index cf42bd7..0000000 --- a/test/lib/ssl/nginx-proxy.bats.crt +++ /dev/null @@ -1,24 +0,0 @@ ------BEGIN CERTIFICATE----- -MIID7TCCAtWgAwIBAgIJAOGkf5EnexJVMA0GCSqGSIb3DQEBCwUAMIGMMQswCQYD -VQQGEwJVUzERMA8GA1UECAwIVmlyZ2luaWExDzANBgNVBAcMBlJlc3RvbjERMA8G -A1UECgwIRmFrZSBPcmcxGzAZBgNVBAMMEioubmdpbngtcHJveHkuYmF0czEpMCcG -CSqGSIb3DQEJARYad2VibWFzdGVyQG5naW54LXByb3h5LmJhdHMwHhcNMTYwNDIw -MTUzOTUxWhcNMjYwNDE4MTUzOTUxWjCBjDELMAkGA1UEBhMCVVMxETAPBgNVBAgM -CFZpcmdpbmlhMQ8wDQYDVQQHDAZSZXN0b24xETAPBgNVBAoMCEZha2UgT3JnMRsw -GQYDVQQDDBIqLm5naW54LXByb3h5LmJhdHMxKTAnBgkqhkiG9w0BCQEWGndlYm1h -c3RlckBuZ2lueC1wcm94eS5iYXRzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB -CgKCAQEA0Amkj3iaQn8Z2CW6n24zSuWu2OoLCkHZAk8eprkI4kKoPBvjusynkm8E -phq65jebToHoldfuQ0wM61DzhD15bHwS3x9CrOVbShsmdnGALz+wdR0/4Likx50I -YZdecTOAlkoZudnX5FZ4ngOxjqcym7p5T8TrSS97a0fx99gitZY0p+Nu2tip4o3t -WBMs+SoPWTlQ1SrSmL8chC8O2knyBl/w1nHmDnMuR6FGcHdhLncApw9t5spgfv7p -OrMF4tQxJQNk10TnflmEMkGmy+pfk2e0cQ1Kwp3Nmzm7ECkggxxyjU3ihKiFK+09 -8aSCi7gDAY925+mV6LZ5oLMpO3KJvQIDAQABo1AwTjAdBgNVHQ4EFgQU+NvFo37z -9Dyq8Mu82SPtV7q1gYQwHwYDVR0jBBgwFoAU+NvFo37z9Dyq8Mu82SPtV7q1gYQw -DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAI1ityDV0UsCVHSpB2LN+ -QXlk8XS0ACIJ8Q0hbOj3BmYrdAVglG4P6upDEueaaxwsaBTagkTP8nxZ9dhfZHyZ -5YLNwYsiG5iqb8e0ecHx3uJT/0YiXn/8rBvxEZna4Fl8seGdp7BjOWUAS2Nv8tn4 -EJJvRdfX/O8XgPc95DM4lwQ/dvyWmavMI4lnl0n1IQV9WPGaIQhYPU9WEQK6iMUB -o1kx8YbOJQD0ZBRfqpriNt1/8ylkkSYYav8QT9JFvQFCWEvaX71QF+cuOwC7ZYBH -4ElXwEUrYBHKiPo0q0VsTtMvLh7h/T5czrIhG/NpfVJPtQOk8aVwNScL3/n+TGU8 -6g== ------END CERTIFICATE----- diff --git a/test/lib/ssl/nginx-proxy.bats.key b/test/lib/ssl/nginx-proxy.bats.key deleted file mode 100644 index 24d8dc8..0000000 --- a/test/lib/ssl/nginx-proxy.bats.key +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDQCaSPeJpCfxnY -JbqfbjNK5a7Y6gsKQdkCTx6muQjiQqg8G+O6zKeSbwSmGrrmN5tOgeiV1+5DTAzr -UPOEPXlsfBLfH0Ks5VtKGyZ2cYAvP7B1HT/guKTHnQhhl15xM4CWShm52dfkVnie -A7GOpzKbunlPxOtJL3trR/H32CK1ljSn427a2Knije1YEyz5Kg9ZOVDVKtKYvxyE -Lw7aSfIGX/DWceYOcy5HoUZwd2EudwCnD23mymB+/uk6swXi1DElA2TXROd+WYQy -QabL6l+TZ7RxDUrCnc2bObsQKSCDHHKNTeKEqIUr7T3xpIKLuAMBj3bn6ZXotnmg -syk7com9AgMBAAECggEAa7wCp3XqVPNjW+c1/ShhkbDeWmDhtL8i9aopkmeSbTHd -07sRtQQU56Vsf+Sp010KpZ5q52Z6cglpS1eRtHLtdbvPPhL/QXBJVVg4E/B1VIKk -DBJIqUSVuPXeiEOOWgs01R+ssO1ae1o4foQlKF33vGPWPPQacL0RKh6I9TPNzcD7 -n4rujlHk72N/bNydyK2rnyKB4vAI5TbZPLps+Xe123CmgZnW3JClcWV9B4foRmiu -a5Iq1WYAK2GYKbYwgqDRyYBC27m91a7U31pE4GQD+xQdlz6kcOlCU5hAcPK3h7j0 -fLQqn8g+YAtc0nBKKB4NZe3QEzTiVMorT0VitxI71QKBgQDnirardZaXOFzYGzB3 -j+FGB9BUW54hnHr5BxOYrfmEJ5umJjJWaGupfYrQsPArrJP1//WbqVZIPvdQParD -mQhLmSp1r/VNzGB6pISmzU1ZGDHsmBxYseh366om5YBQUFU2vmbil9VkrkM4fsJG -tcS9V/nVY/EM7Yp3PzjfLlhC1wKBgQDmA1YJmnZvIbLp3PoKqM69QiCLKztVm7nX -xpu3b3qbXEzXkt2sP5PHmr+s13hOPQFKRJ2hk4UN9WqpnFoHw5E5eWWhSa/peUZm -r10Y5XspiFtRHHiu6ABXB49eB4fen+vHEZHKyRJ4rFthKjjBHdNPC8bmwnT3jE85 -/8a26FLZiwKBgQDXEi8JZslBn9YF2oOTm28KCLoHka551AsaA+u892T8z3mxxGsf -fhD7N6TYonIEb2Jkr6OpOortwqcgvpc+5oghCJ27AX2fDUdUxDp/YdYF+wZsmQJD -lMW1lo7PYIBmmaf9mLCiq5xIz+GauYul+LNNmUl0YEgI1SC4EV63WCodswKBgDMX -GJxHd/kVViVGFTAa8NjvAEWJU8OfNHduQRZMp8IsjVDw6VYiRRP4Fo0wyyMtv8Sc -WxsRpmNEWO3VsdW5pd9LTLy3nmBQtMeIOjiWeHXwOMBaf5/yHmk2X6z2JULY6Mkt -6OFPKlAtkJqTg0m58z7Ckeqd1NdLjimG27+y+PwjAoGAFt0cbC1Ust2BE6YEspSX -ofpAnJsyKrbF9iVUyXDUP99sdqYQfPJ5uqPGkP59lJGkTLtebuitqi6FCyrsT6Fq -AWLiExbqebAqcuAZw2S+iuK27S4rrkjVGF53J7vH3rOzCBUXaRx6GKfTjUqedHdg -9Kw+LP6IFnMTb+EGLo+GqHs= ------END PRIVATE KEY----- diff --git a/test/multiple-hosts.bats b/test/multiple-hosts.bats deleted file mode 100644 index 8e14c11..0000000 --- a/test/multiple-hosts.bats +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env bats -load test_helpers -SUT_CONTAINER=bats-nginx-proxy-${TEST_FILE} - -function setup { - # make sure to stop any web container before each test so we don't - # have any unexpected contaiener running with VIRTUAL_HOST or VIRUTAL_PORT set - stop_bats_containers web -} - - -@test "[$TEST_FILE] start a nginx-proxy container" { - run nginxproxy $SUT_CONTAINER -v /var/run/docker.sock:/tmp/docker.sock:ro - assert_success - docker_wait_for_log $SUT_CONTAINER 9 "Watching docker events" -} - -@test "[$TEST_FILE] nginx-proxy forwards requests for 2 hosts" { - # WHEN a container runs a web server with VIRTUAL_HOST set for multiple hosts - prepare_web_container bats-multiple-hosts-1 80 -e VIRTUAL_HOST=multiple-hosts-1-A.bats,multiple-hosts-1-B.bats - dockergen_wait_for_event $SUT_CONTAINER start bats-multiple-hosts-1 - sleep 1 - - # THEN querying the proxy without Host header → 503 - run curl_container $SUT_CONTAINER / --head - assert_output -l 0 $'HTTP/1.1 503 Service Temporarily Unavailable\r' - - # THEN querying the proxy with unknown Host header → 503 - run curl_container $SUT_CONTAINER /port --header "Host: webFOO.bats" --head - assert_output -l 0 $'HTTP/1.1 503 Service Temporarily Unavailable\r' - - # THEN - run curl_container $SUT_CONTAINER /port --header 'Host: multiple-hosts-1-A.bats' - assert_output "answer from port 80" - - # THEN - run curl_container $SUT_CONTAINER /port --header 'Host: multiple-hosts-1-B.bats' - assert_output "answer from port 80" -} - -@test "[$TEST_FILE] stop all bats containers" { - stop_bats_containers -} diff --git a/test/multiple-ports.bats b/test/multiple-ports.bats deleted file mode 100644 index f3e670b..0000000 --- a/test/multiple-ports.bats +++ /dev/null @@ -1,64 +0,0 @@ -#!/usr/bin/env bats -load test_helpers -SUT_CONTAINER=bats-nginx-proxy-${TEST_FILE} - -function setup { - # make sure to stop any web container before each test so we don't - # have any unexpected contaiener running with VIRTUAL_HOST or VIRUTAL_PORT set - stop_bats_containers web -} - - -@test "[$TEST_FILE] start a nginx-proxy container" { - # GIVEN nginx-proxy - run nginxproxy $SUT_CONTAINER -v /var/run/docker.sock:/tmp/docker.sock:ro - assert_success - docker_wait_for_log $SUT_CONTAINER 9 "Watching docker events" -} - - -@test "[$TEST_FILE] nginx-proxy defaults to the service running on port 80" { - # WHEN - prepare_web_container bats-web-${TEST_FILE}-1 "80 90" -e VIRTUAL_HOST=web.bats - dockergen_wait_for_event $SUT_CONTAINER start bats-web-${TEST_FILE}-1 - sleep 1 - - # THEN - assert_response_is_from_port 80 -} - - -@test "[$TEST_FILE] VIRTUAL_PORT=90 while port 80 is also exposed" { - # GIVEN - prepare_web_container bats-web-${TEST_FILE}-2 "80 90" -e VIRTUAL_HOST=web.bats -e VIRTUAL_PORT=90 - dockergen_wait_for_event $SUT_CONTAINER start bats-web-${TEST_FILE}-2 - sleep 1 - - # THEN - assert_response_is_from_port 90 -} - - -@test "[$TEST_FILE] single exposed port != 80" { - # GIVEN - prepare_web_container bats-web-${TEST_FILE}-3 1234 -e VIRTUAL_HOST=web.bats - dockergen_wait_for_event $SUT_CONTAINER start bats-web-${TEST_FILE}-3 - sleep 1 - - # THEN - assert_response_is_from_port 1234 -} - -@test "[$TEST_FILE] stop all bats containers" { - stop_bats_containers -} - - -# assert querying nginx-proxy provides a response from the expected port of the web container -# $1 port we are expecting an response from -function assert_response_is_from_port { - local -r port=$1 - run curl_container $SUT_CONTAINER /port --header "Host: web.bats" - assert_output "answer from port $port" -} - diff --git a/test/pytest.ini b/test/pytest.ini new file mode 100644 index 0000000..30f3e19 --- /dev/null +++ b/test/pytest.ini @@ -0,0 +1,3 @@ +[pytest] +# disable the creation of the `.cache` folders +addopts = -p no:cacheprovider --ignore=requirements --ignore=certs -r s -v \ No newline at end of file diff --git a/test/pytest.sh b/test/pytest.sh new file mode 100755 index 0000000..a9745f5 --- /dev/null +++ b/test/pytest.sh @@ -0,0 +1,24 @@ +#!/bin/bash +############################################################################### +# # +# This script is meant to run the test suite from a Docker container. # +# # +# This is usefull when you want to run the test suite from Mac or # +# Docker Toolbox. # +# # +############################################################################### + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +ARGS="$@" + +# check requirements +echo "> Building nginx-proxy-tester image..." +docker build -t nginx-proxy-tester -f $DIR/requirements/Dockerfile-nginx-proxy-tester $DIR/requirements + +# 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. +exec docker run --rm -it \ + -v ${DIR}:/${DIR} \ + -w ${DIR} \ + -v /var/run/docker.sock:/var/run/docker.sock \ + nginx-proxy-tester ${ARGS} diff --git a/test/requirements/Dockerfile-nginx-proxy-tester b/test/requirements/Dockerfile-nginx-proxy-tester new file mode 100644 index 0000000..b403ed7 --- /dev/null +++ b/test/requirements/Dockerfile-nginx-proxy-tester @@ -0,0 +1,5 @@ +FROM python:2.7 +COPY python-requirements.txt /requirements.txt +RUN pip install -r /requirements.txt +WORKDIR /test +ENTRYPOINT ["pytest"] diff --git a/test/requirements/README.md b/test/requirements/README.md new file mode 100644 index 0000000..3a0c389 --- /dev/null +++ b/test/requirements/README.md @@ -0,0 +1,52 @@ +This directory contains resources to build Docker images tests depend on + +# Build images + + ./build.sh + + +# python-requirements.txt + +If you want to run the test suite from your computer, you need python and a few python modules. +The _python-requirements.txt_ file describes the python modules required. To install them, use +pip: + + pip install -r python-requirements.txt + +If you don't want to run the test from your computer, you can run the tests from a docker container, see the _pytest.sh_ script. + + +# Images + +## web + +This container will run one or many webservers, each of them listening on a single port. + +Ports are specified using the `WEB_PORTS` environment variable: + + docker run -d -e WEB_PORTS=80 web # will create a container running one webserver listening on port 80 + docker run -d -e WEB_PORTS="80 81" web # will create a container running two webservers, one listening on port 80 and a second one listening on port 81 + +The webserver answers on two paths: + +- `/headers` +- `/port` + +``` +$ docker run -d -e WEB_PORTS=80 -p 80:80 web +$ curl http://127.0.0.1:80/headers +Host: 127.0.0.1 +User-Agent: curl/7.47.0 +Accept: */* + +$ curl http://127.0.0.1:80/port +answer from port 80 + +``` + + +## nginx-proxy-tester + +This is an optional requirement which is usefull if you cannot (or don't want to) install pytest and its requirements on your computer. In this case, you can use the `nginx-proxy-tester` docker image to run the test suite from a Docker container. + +To use this image, it is mandatory to run the container using the `pytest.sh` shell script. The script will build the image and run a container from it with the appropriate volumes and settings. diff --git a/test/requirements/build.sh b/test/requirements/build.sh new file mode 100755 index 0000000..f29897a --- /dev/null +++ b/test/requirements/build.sh @@ -0,0 +1,6 @@ +#!/bin/bash +set -e + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +docker build -t web $DIR/web \ No newline at end of file diff --git a/test/requirements/python-requirements.txt b/test/requirements/python-requirements.txt new file mode 100644 index 0000000..e868e14 --- /dev/null +++ b/test/requirements/python-requirements.txt @@ -0,0 +1,5 @@ +backoff==1.3.2 +docker-compose==1.11.1 +docker==2.0.2 +pytest==3.0.5 +requests==2.11.1 \ No newline at end of file diff --git a/test/requirements/web/Dockerfile b/test/requirements/web/Dockerfile new file mode 100644 index 0000000..923ed79 --- /dev/null +++ b/test/requirements/web/Dockerfile @@ -0,0 +1,8 @@ +# Docker Image running one (or multiple) webservers listening on all given ports from WEB_PORTS environment variable + +FROM python:3 +COPY ./webserver.py / +COPY ./entrypoint.sh / +WORKDIR /opt +ENTRYPOINT ["/bin/bash", "/entrypoint.sh"] + diff --git a/test/requirements/web/entrypoint.sh b/test/requirements/web/entrypoint.sh new file mode 100644 index 0000000..3015c11 --- /dev/null +++ b/test/requirements/web/entrypoint.sh @@ -0,0 +1,15 @@ +#!/bin/bash +set -u + +trap '[ ${#PIDS[@]} -gt 0 ] && kill -TERM ${PIDS[@]}' TERM +declare -a PIDS + +for port in $WEB_PORTS; do + echo starting a web server listening on port $port; + /webserver.py $port & + PIDS+=($!) +done + +wait ${PIDS[@]} +trap - TERM +wait ${PIDS[@]} diff --git a/test/web_helpers/webserver.py b/test/requirements/web/webserver.py similarity index 72% rename from test/web_helpers/webserver.py rename to test/requirements/web/webserver.py index d94ed89..305c207 100755 --- a/test/web_helpers/webserver.py +++ b/test/requirements/web/webserver.py @@ -4,9 +4,9 @@ import os, sys import http.server import socketserver -class BatsHandler(http.server.SimpleHTTPRequestHandler): + +class Handler(http.server.SimpleHTTPRequestHandler): def do_GET(self): - root = os.getcwd() self.send_response(200) self.send_header("Content-Type", "text/plain") @@ -17,11 +17,15 @@ class BatsHandler(http.server.SimpleHTTPRequestHandler): elif self.path == "/port": response = "answer from port %s\n" % PORT self.wfile.write(response.encode()) + elif self.path == "/": + response = "I'm %s\n" % os.environ['HOSTNAME'] + self.wfile.write(response.encode()) else: self.wfile.write("No route for this path!\n".encode()) + if __name__ == '__main__': PORT = int(sys.argv[1]) socketserver.TCPServer.allow_reuse_address = True - httpd = socketserver.TCPServer(('0.0.0.0', PORT), BatsHandler) + httpd = socketserver.TCPServer(('0.0.0.0', PORT), Handler) httpd.serve_forever() diff --git a/test/ssl.bats b/test/ssl.bats deleted file mode 100644 index e7f8d8d..0000000 --- a/test/ssl.bats +++ /dev/null @@ -1,168 +0,0 @@ -#!/usr/bin/env bats -load test_helpers -SUT_CONTAINER=bats-nginx-proxy-${TEST_FILE} - -function setup { - # make sure to stop any web container before each test so we don't - # have any unexpected contaiener running with VIRTUAL_HOST or VIRUTAL_PORT set - stop_bats_containers web -} - - -@test "[$TEST_FILE] start a nginx-proxy container" { - run nginxproxy $SUT_CONTAINER -v /var/run/docker.sock:/tmp/docker.sock:ro -v ${DIR}/lib/ssl:/etc/nginx/certs:ro - assert_success - docker_wait_for_log $SUT_CONTAINER 9 "Watching docker events" -} - -@test "[$TEST_FILE] test SSL for VIRTUAL_HOST=*.nginx-proxy.bats" { - # WHEN - prepare_web_container bats-ssl-hosts-1 "80" \ - -e VIRTUAL_HOST=*.nginx-proxy.bats \ - -e CERT_NAME=nginx-proxy.bats - dockergen_wait_for_event $SUT_CONTAINER start bats-ssl-hosts-1 - sleep 1 - - # THEN - assert_301 test.nginx-proxy.bats - assert_200_https test.nginx-proxy.bats -} - -@test "[$TEST_FILE] test HTTPS_METHOD=nohttp" { - # WHEN - prepare_web_container bats-ssl-hosts-2 "80" \ - -e VIRTUAL_HOST=*.nginx-proxy.bats \ - -e CERT_NAME=nginx-proxy.bats \ - -e HTTPS_METHOD=nohttp - dockergen_wait_for_event $SUT_CONTAINER start bats-ssl-hosts-2 - sleep 1 - - # THEN - assert_503 test.nginx-proxy.bats - assert_200_https test.nginx-proxy.bats -} - -@test "[$TEST_FILE] test HTTPS_METHOD=noredirect" { - # WHEN - prepare_web_container bats-ssl-hosts-3 "80" \ - -e VIRTUAL_HOST=*.nginx-proxy.bats \ - -e CERT_NAME=nginx-proxy.bats \ - -e HTTPS_METHOD=noredirect - dockergen_wait_for_event $SUT_CONTAINER start bats-ssl-hosts-3 - sleep 1 - - # THEN - assert_200 test.nginx-proxy.bats - assert_200_https test.nginx-proxy.bats -} - -@test "[$TEST_FILE] test SSL Strict-Transport-Security" { - # WHEN - prepare_web_container bats-ssl-hosts-4 "80" \ - -e VIRTUAL_HOST=*.nginx-proxy.bats \ - -e CERT_NAME=nginx-proxy.bats - dockergen_wait_for_event $SUT_CONTAINER start bats-ssl-hosts-4 - sleep 1 - - # THEN - assert_301 test.nginx-proxy.bats - assert_200_https test.nginx-proxy.bats - assert_output -p "Strict-Transport-Security: max-age=31536000" -} - -@test "[$TEST_FILE] test HTTPS_METHOD=noredirect disables Strict-Transport-Security" { - # WHEN - prepare_web_container bats-ssl-hosts-5 "80" \ - -e VIRTUAL_HOST=*.nginx-proxy.bats \ - -e CERT_NAME=nginx-proxy.bats \ - -e HTTPS_METHOD=noredirect - dockergen_wait_for_event $SUT_CONTAINER start bats-ssl-hosts-5 - sleep 1 - - # THEN - assert_200 test.nginx-proxy.bats - assert_200_https test.nginx-proxy.bats - refute_output -p "Strict-Transport-Security: max-age=31536000" -} - -@test "[$TEST_FILE] test HTTPS_METHOD=nohttps" { - # WHEN - prepare_web_container bats-ssl-hosts-6 "80" \ - -e VIRTUAL_HOST=*.nginx-proxy.bats \ - -e CERT_NAME=nginx-proxy.bats \ - -e HTTPS_METHOD=nohttps - dockergen_wait_for_event $SUT_CONTAINER start bats-ssl-hosts-6 - sleep 1 - - # THEN - assert_down_https test.nginx-proxy.bats - assert_200 test.nginx-proxy.bats -} - -@test "[$TEST_FILE] stop all bats containers" { - stop_bats_containers -} - - -# assert that querying nginx-proxy with the given Host header produces a `HTTP 200` response -# $1 Host HTTP header to use when querying nginx-proxy -function assert_200 { - local -r host=$1 - - run curl_container $SUT_CONTAINER / --head --header "Host: $host" - assert_output -l 0 $'HTTP/1.1 200 OK\r' -} - -# assert that querying nginx-proxy with the given Host header produces a `HTTP 503` response -# $1 Host HTTP header to use when querying nginx-proxy -function assert_503 { - local -r host=$1 - - run curl_container $SUT_CONTAINER / --head --header "Host: $host" - assert_output -l 0 $'HTTP/1.1 503 Service Temporarily Unavailable\r' -} - -# assert that querying nginx-proxy with the given Host header produces a `HTTP 503` response -# $1 Host HTTP header to use when querying nginx-proxy -function assert_301 { - local -r host=$1 - - run curl_container $SUT_CONTAINER / --head --header "Host: $host" - assert_output -l 0 $'HTTP/1.1 301 Moved Permanently\r' -} - -# assert that querying nginx-proxy with the given Host header fails because the host is down -# $1 Host HTTP header to use when querying nginx-proxy -function assert_down_https { - local -r host=$1 - - run curl_container_https $SUT_CONTAINER / --head --header "Host: $host" - assert_failure -} - -# assert that querying nginx-proxy with the given Host header produces a `HTTP 200` response -# $1 Host HTTP header to use when querying nginx-proxy -function assert_200_https { - local -r host=$1 - - run curl_container_https $SUT_CONTAINER / --head --header "Host: $host" - assert_output -l 0 $'HTTP/1.1 200 OK\r' -} - -# assert that querying nginx-proxy with the given Host header produces a `HTTP 503` response -# $1 Host HTTP header to use when querying nginx-proxy -function assert_503_https { - local -r host=$1 - - run curl_container_https $SUT_CONTAINER / --head --header "Host: $host" - assert_output -l 0 $'HTTP/1.1 503 Service Temporarily Unavailable\r' -} - -# assert that querying nginx-proxy with the given Host header produces a `HTTP 503` response -# $1 Host HTTP header to use when querying nginx-proxy -function assert_301_https { - local -r host=$1 - - run curl_container_https $SUT_CONTAINER / --head --header "Host: $host" - assert_output -l 0 $'HTTP/1.1 301 Moved Permanently\r' -} diff --git a/test/test_DOCKER_HOST_unix_socket.py b/test/test_DOCKER_HOST_unix_socket.py new file mode 100644 index 0000000..b31da16 --- /dev/null +++ b/test/test_DOCKER_HOST_unix_socket.py @@ -0,0 +1,15 @@ +import pytest + +def test_unknown_virtual_host(docker_compose, nginxproxy): + r = nginxproxy.get("http://nginx-proxy/port") + assert r.status_code == 503 + +def test_forwards_to_web1(docker_compose, nginxproxy): + r = nginxproxy.get("http://web1.nginx-proxy.tld/port") + assert r.status_code == 200 + assert r.text == "answer from port 81\n" + +def test_forwards_to_web2(docker_compose, nginxproxy): + r = nginxproxy.get("http://web2.nginx-proxy.tld/port") + assert r.status_code == 200 + assert r.text == "answer from port 82\n" diff --git a/test/test_DOCKER_HOST_unix_socket.yml b/test/test_DOCKER_HOST_unix_socket.yml new file mode 100644 index 0000000..79b4baf --- /dev/null +++ b/test/test_DOCKER_HOST_unix_socket.yml @@ -0,0 +1,24 @@ +web1: + image: web + expose: + - "81" + environment: + WEB_PORTS: 81 + VIRTUAL_HOST: web1.nginx-proxy.tld + +web2: + image: web + expose: + - "82" + environment: + WEB_PORTS: 82 + VIRTUAL_HOST: web2.nginx-proxy.tld + + +sut: + image: jwilder/nginx-proxy:test + volumes: + - /var/run/docker.sock:/f00.sock:ro + environment: + DOCKER_HOST: unix:///f00.sock + diff --git a/test/test_composev2.py b/test/test_composev2.py new file mode 100644 index 0000000..88a4f80 --- /dev/null +++ b/test/test_composev2.py @@ -0,0 +1,10 @@ +import pytest + +def test_unknown_virtual_host(docker_compose, nginxproxy): + r = nginxproxy.get("http://nginx-proxy/") + assert r.status_code == 503 + +def test_forwards_to_whoami(docker_compose, nginxproxy): + r = nginxproxy.get("http://web.nginx-proxy.local/port") + assert r.status_code == 200 + assert r.text == "answer from port 81\n" diff --git a/test/test_composev2.yml b/test/test_composev2.yml new file mode 100644 index 0000000..5ffaf57 --- /dev/null +++ b/test/test_composev2.yml @@ -0,0 +1,14 @@ +version: '2' +services: + nginx-proxy: + image: jwilder/nginx-proxy:test + volumes: + - /var/run/docker.sock:/tmp/docker.sock:ro + + web: + image: web + expose: + - "81" + environment: + WEB_PORTS: 81 + VIRTUAL_HOST: web.nginx-proxy.local \ No newline at end of file diff --git a/test/test_custom/my_custom_proxy_settings.conf b/test/test_custom/my_custom_proxy_settings.conf new file mode 100644 index 0000000..8d8502d --- /dev/null +++ b/test/test_custom/my_custom_proxy_settings.conf @@ -0,0 +1 @@ +add_header X-test f00; \ No newline at end of file diff --git a/test/test_custom/my_custom_proxy_settings_bar.conf b/test/test_custom/my_custom_proxy_settings_bar.conf new file mode 100644 index 0000000..e8b0827 --- /dev/null +++ b/test/test_custom/my_custom_proxy_settings_bar.conf @@ -0,0 +1 @@ +add_header X-test bar; diff --git a/test/test_custom/test_defaults-location.py b/test/test_custom/test_defaults-location.py new file mode 100644 index 0000000..2b47f71 --- /dev/null +++ b/test/test_custom/test_defaults-location.py @@ -0,0 +1,28 @@ +import pytest + +def test_custom_default_conf_does_not_apply_to_unknown_vhost(docker_compose, nginxproxy): + r = nginxproxy.get("http://nginx-proxy/") + assert r.status_code == 503 + assert "X-test" not in r.headers + +def test_custom_default_conf_applies_to_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-test" in r.headers + assert "f00" == r.headers["X-test"] + +def test_custom_default_conf_applies_to_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-test" in r.headers + assert "f00" == r.headers["X-test"] + + +def test_custom_default_conf_is_overriden_for_web3(docker_compose, nginxproxy): + r = nginxproxy.get("http://web3.nginx-proxy.local/port") + assert r.status_code == 200 + assert r.text == "answer from port 83\n" + assert "X-test" in r.headers + assert "bar" == r.headers["X-test"] diff --git a/test/test_custom/test_defaults-location.yml b/test/test_custom/test_defaults-location.yml new file mode 100644 index 0000000..d2ac14a --- /dev/null +++ b/test/test_custom/test_defaults-location.yml @@ -0,0 +1,30 @@ +nginx-proxy: + image: jwilder/nginx-proxy:test + volumes: + - /var/run/docker.sock:/tmp/docker.sock:ro + - ./my_custom_proxy_settings.conf:/etc/nginx/vhost.d/default_location:ro + - ./my_custom_proxy_settings_bar.conf:/etc/nginx/vhost.d/web3.nginx-proxy.local_location:ro + +web1: + image: web + expose: + - "81" + environment: + WEB_PORTS: 81 + VIRTUAL_HOST: web1.nginx-proxy.local + +web2: + image: web + expose: + - "82" + environment: + WEB_PORTS: 82 + VIRTUAL_HOST: web2.nginx-proxy.local + +web3: + image: web + expose: + - "83" + environment: + WEB_PORTS: 83 + VIRTUAL_HOST: web3.nginx-proxy.local \ No newline at end of file diff --git a/test/test_custom/test_defaults.py b/test/test_custom/test_defaults.py new file mode 100644 index 0000000..c338628 --- /dev/null +++ b/test/test_custom/test_defaults.py @@ -0,0 +1,20 @@ +import pytest + +def test_custom_conf_does_not_apply_to_unknown_vhost(docker_compose, nginxproxy): + r = nginxproxy.get("http://nginx-proxy/") + assert r.status_code == 503 + assert "X-test" not in r.headers + +def test_custom_conf_applies_to_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-test" in r.headers + assert "f00" == r.headers["X-test"] + +def test_custom_conf_applies_to_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-test" in r.headers + assert "f00" == r.headers["X-test"] diff --git a/test/test_custom/test_defaults.yml b/test/test_custom/test_defaults.yml new file mode 100644 index 0000000..2b2f1bb --- /dev/null +++ b/test/test_custom/test_defaults.yml @@ -0,0 +1,23 @@ +version: '2' +services: + nginx-proxy: + image: jwilder/nginx-proxy:test + volumes: + - /var/run/docker.sock:/tmp/docker.sock:ro + - ./my_custom_proxy_settings.conf:/etc/nginx/proxy.conf:ro + + web1: + image: web + expose: + - "81" + environment: + WEB_PORTS: 81 + VIRTUAL_HOST: web1.nginx-proxy.local + + web2: + image: web + expose: + - "82" + environment: + WEB_PORTS: 82 + VIRTUAL_HOST: web2.nginx-proxy.local \ No newline at end of file diff --git a/test/test_custom/test_location-per-vhost.py b/test/test_custom/test_location-per-vhost.py new file mode 100644 index 0000000..b99996e --- /dev/null +++ b/test/test_custom/test_location-per-vhost.py @@ -0,0 +1,22 @@ +import pytest + +def test_custom_conf_does_not_apply_to_unknown_vhost(docker_compose, nginxproxy): + r = nginxproxy.get("http://nginx-proxy/") + assert r.status_code == 503 + assert "X-test" not in r.headers + +def test_custom_conf_applies_to_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-test" in r.headers + assert "f00" == r.headers["X-test"] + +def test_custom_conf_does_not_apply_to_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-test" not in r.headers + +def test_custom_block_is_present_in_nginx_generated_conf(docker_compose, nginxproxy): + assert "include /etc/nginx/vhost.d/web1.nginx-proxy.local_location;" in nginxproxy.get_conf() \ No newline at end of file diff --git a/test/test_custom/test_location-per-vhost.yml b/test/test_custom/test_location-per-vhost.yml new file mode 100644 index 0000000..7ec9992 --- /dev/null +++ b/test/test_custom/test_location-per-vhost.yml @@ -0,0 +1,23 @@ +version: '2' +services: + nginx-proxy: + image: jwilder/nginx-proxy:test + volumes: + - /var/run/docker.sock:/tmp/docker.sock:ro + - ./my_custom_proxy_settings.conf:/etc/nginx/vhost.d/web1.nginx-proxy.local_location:ro + + web1: + image: web + expose: + - "81" + environment: + WEB_PORTS: 81 + VIRTUAL_HOST: web1.nginx-proxy.local + + web2: + image: web + expose: + - "82" + environment: + WEB_PORTS: 82 + VIRTUAL_HOST: web2.nginx-proxy.local \ No newline at end of file diff --git a/test/test_custom/test_per-vhost.py b/test/test_custom/test_per-vhost.py new file mode 100644 index 0000000..57c3bca --- /dev/null +++ b/test/test_custom/test_per-vhost.py @@ -0,0 +1,19 @@ +import pytest + +def test_custom_conf_does_not_apply_to_unknown_vhost(docker_compose, nginxproxy): + r = nginxproxy.get("http://nginx-proxy/") + assert r.status_code == 503 + assert "X-test" not in r.headers + +def test_custom_conf_applies_to_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-test" in r.headers + assert "f00" == r.headers["X-test"] + +def test_custom_conf_does_not_apply_to_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-test" not in r.headers diff --git a/test/test_custom/test_per-vhost.yml b/test/test_custom/test_per-vhost.yml new file mode 100644 index 0000000..a99da1d --- /dev/null +++ b/test/test_custom/test_per-vhost.yml @@ -0,0 +1,23 @@ +version: '2' +services: + nginx-proxy: + image: jwilder/nginx-proxy:test + volumes: + - /var/run/docker.sock:/tmp/docker.sock:ro + - ./my_custom_proxy_settings.conf:/etc/nginx/vhost.d/web1.nginx-proxy.local:ro + + web1: + image: web + expose: + - "81" + environment: + WEB_PORTS: 81 + VIRTUAL_HOST: web1.nginx-proxy.local + + web2: + image: web + expose: + - "82" + environment: + WEB_PORTS: 82 + VIRTUAL_HOST: web2.nginx-proxy.local \ No newline at end of file diff --git a/test/test_custom/test_proxy-wide.py b/test/test_custom/test_proxy-wide.py new file mode 100644 index 0000000..c338628 --- /dev/null +++ b/test/test_custom/test_proxy-wide.py @@ -0,0 +1,20 @@ +import pytest + +def test_custom_conf_does_not_apply_to_unknown_vhost(docker_compose, nginxproxy): + r = nginxproxy.get("http://nginx-proxy/") + assert r.status_code == 503 + assert "X-test" not in r.headers + +def test_custom_conf_applies_to_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-test" in r.headers + assert "f00" == r.headers["X-test"] + +def test_custom_conf_applies_to_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-test" in r.headers + assert "f00" == r.headers["X-test"] diff --git a/test/test_custom/test_proxy-wide.yml b/test/test_custom/test_proxy-wide.yml new file mode 100644 index 0000000..3018131 --- /dev/null +++ b/test/test_custom/test_proxy-wide.yml @@ -0,0 +1,23 @@ +version: '2' +services: + nginx-proxy: + image: jwilder/nginx-proxy:test + volumes: + - /var/run/docker.sock:/tmp/docker.sock:ro + - ./my_custom_proxy_settings.conf:/etc/nginx/conf.d/my_custom_proxy_settings.conf:ro + + web1: + image: web + expose: + - "81" + environment: + WEB_PORTS: 81 + VIRTUAL_HOST: web1.nginx-proxy.local + + web2: + image: web + expose: + - "82" + environment: + WEB_PORTS: 82 + VIRTUAL_HOST: web2.nginx-proxy.local \ No newline at end of file diff --git a/test/test_default-host.py b/test/test_default-host.py new file mode 100644 index 0000000..90809a5 --- /dev/null +++ b/test/test_default-host.py @@ -0,0 +1,7 @@ +import pytest + + +def test_fallback_on_default(docker_compose, nginxproxy): + r = nginxproxy.get("http://unknown.nginx-proxy.tld/port") + assert r.status_code == 200 + assert r.text == "answer from port 81\n" \ No newline at end of file diff --git a/test/test_default-host.yml b/test/test_default-host.yml new file mode 100644 index 0000000..590dcaa --- /dev/null +++ b/test/test_default-host.yml @@ -0,0 +1,17 @@ +# GIVEN a webserver with VIRTUAL_HOST set to web1.tld +web1: + image: web + expose: + - "81" + environment: + WEB_PORTS: 81 + VIRTUAL_HOST: web1.tld + + +# WHEN nginx-proxy runs with DEFAULT_HOST set to web1.tld +sut: + image: jwilder/nginx-proxy:test + volumes: + - /var/run/docker.sock:/tmp/docker.sock:ro + environment: + DEFAULT_HOST: web1.tld diff --git a/test/test_dockergen/.gitignore b/test/test_dockergen/.gitignore new file mode 100644 index 0000000..98c0b06 --- /dev/null +++ b/test/test_dockergen/.gitignore @@ -0,0 +1 @@ +nginx.tmpl \ No newline at end of file diff --git a/test/test_dockergen/test_dockergen_v2.py b/test/test_dockergen/test_dockergen_v2.py new file mode 100644 index 0000000..c797d0c --- /dev/null +++ b/test/test_dockergen/test_dockergen_v2.py @@ -0,0 +1,38 @@ +import os +import docker +import logging +import pytest + + +@pytest.yield_fixture(scope="module") +def nginx_tmpl(): + """ + pytest fixture which extracts the the nginx config template from + the jwilder/nginx-proxy:test image + """ + script_dir = os.path.dirname(__file__) + logging.info("extracting nginx.tmpl from jwilder/nginx-proxy:test") + docker_client = docker.from_env() + print(docker_client.containers.run( + image='jwilder/nginx-proxy:test', + remove=True, + volumes=['{current_dir}:{current_dir}'.format(current_dir=script_dir)], + entrypoint='sh', + command='-xc "cp /app/nginx.tmpl {current_dir} && chmod 777 {current_dir}/nginx.tmpl"'.format( + current_dir=script_dir), + stderr=True)) + yield + logging.info("removing nginx.tmpl") + os.remove(os.path.join(script_dir, "nginx.tmpl")) + + +def test_unknown_virtual_host_is_503(nginx_tmpl, docker_compose, nginxproxy): + r = nginxproxy.get("http://unknown.nginx.container.docker/") + assert r.status_code == 503 + + +def test_forwards_to_whoami(nginx_tmpl, docker_compose, nginxproxy): + r = nginxproxy.get("http://whoami.nginx.container.docker/") + assert r.status_code == 200 + whoami_container = docker_compose.containers.get("whoami") + assert r.text == "I'm %s\n" % whoami_container.id[:12] diff --git a/test/test_dockergen/test_dockergen_v2.yml b/test/test_dockergen/test_dockergen_v2.yml new file mode 100644 index 0000000..0d2cab0 --- /dev/null +++ b/test/test_dockergen/test_dockergen_v2.yml @@ -0,0 +1,26 @@ +version: '2' + +services: + nginx: + image: nginx + container_name: nginx + volumes: + - /etc/nginx/conf.d + + dockergen: + image: jwilder/docker-gen + command: -notify-sighup nginx -watch /etc/docker-gen/templates/nginx.tmpl /etc/nginx/conf.d/default.conf + volumes_from: + - nginx + volumes: + - /var/run/docker.sock:/tmp/docker.sock:ro + - ./nginx.tmpl:/etc/docker-gen/templates/nginx.tmpl + + web: + image: web + container_name: whoami + expose: + - "80" + environment: + WEB_PORTS: 80 + VIRTUAL_HOST: whoami.nginx.container.docker \ No newline at end of file diff --git a/test/test_dockergen/test_dockergen_v3.py b/test/test_dockergen/test_dockergen_v3.py new file mode 100644 index 0000000..325d6db --- /dev/null +++ b/test/test_dockergen/test_dockergen_v3.py @@ -0,0 +1,47 @@ +import os +import docker +import logging +import pytest + + +def versiontuple(v): + return tuple(map(int, (v.split(".")))) + + +docker_version = docker.from_env().version()['Version'] +pytestmark = pytest.mark.skipif(versiontuple(docker_version) < versiontuple('1.13'), + reason="Docker compose syntax v3 requires docker engine v1.13") + + +@pytest.yield_fixture(scope="module") +def nginx_tmpl(): + """ + pytest fixture which extracts the the nginx config template from + the jwilder/nginx-proxy:test image + """ + script_dir = os.path.dirname(__file__) + logging.info("extracting nginx.tmpl from jwilder/nginx-proxy:test") + docker_client = docker.from_env() + print(docker_client.containers.run( + image='jwilder/nginx-proxy:test', + remove=True, + volumes=['{current_dir}:{current_dir}'.format(current_dir=script_dir)], + entrypoint='sh', + command='-xc "cp /app/nginx.tmpl {current_dir} && chmod 777 {current_dir}/nginx.tmpl"'.format( + current_dir=script_dir), + stderr=True)) + yield + logging.info("removing nginx.tmpl") + os.remove(os.path.join(script_dir, "nginx.tmpl")) + + +def test_unknown_virtual_host_is_503(nginx_tmpl, docker_compose, nginxproxy): + r = nginxproxy.get("http://unknown.nginx.container.docker/") + assert r.status_code == 503 + + +def test_forwards_to_whoami(nginx_tmpl, docker_compose, nginxproxy): + r = nginxproxy.get("http://whoami.nginx.container.docker/") + assert r.status_code == 200 + whoami_container = docker_compose.containers.get("whoami") + assert r.text == "I'm %s\n" % whoami_container.id[:12] diff --git a/test/test_dockergen/test_dockergen_v3.yml b/test/test_dockergen/test_dockergen_v3.yml new file mode 100644 index 0000000..643f49b --- /dev/null +++ b/test/test_dockergen/test_dockergen_v3.yml @@ -0,0 +1,27 @@ +version: '3' +services: + nginx: + image: nginx + container_name: nginx + volumes: + - nginx_conf:/etc/nginx/conf.d + + dockergen: + image: jwilder/docker-gen + command: -notify-sighup nginx -watch /etc/docker-gen/templates/nginx.tmpl /etc/nginx/conf.d/default.conf + volumes: + - /var/run/docker.sock:/tmp/docker.sock:ro + - ./nginx.tmpl:/etc/docker-gen/templates/nginx.tmpl + - nginx_conf:/etc/nginx/conf.d + + web: + image: web + container_name: whoami + expose: + - "80" + environment: + WEB_PORTS: 80 + VIRTUAL_HOST: whoami.nginx.container.docker + +volumes: + nginx_conf: {} \ No newline at end of file diff --git a/test/test_events.py b/test/test_events.py new file mode 100644 index 0000000..fa97f84 --- /dev/null +++ b/test/test_events.py @@ -0,0 +1,46 @@ +""" +Test that nginx-proxy detects new containers +""" +from time import sleep + +import pytest +from docker.errors import NotFound + + +@pytest.yield_fixture() +def web1(docker_compose): + """ + pytest fixture creating a web container with `VIRTUAL_HOST=web1.nginx-proxy` listening on port 81. + """ + container = docker_compose.containers.run( + name="web1", + image="web", + detach=True, + environment={ + "WEB_PORTS": "81", + "VIRTUAL_HOST": "web1.nginx-proxy" + }, + ports={"81/tcp": None} + ) + sleep(2) # give it some time to initialize and for docker-gen to detect it + yield container + try: + docker_compose.containers.get("web1").remove(force=True) + except NotFound: + pass + + +def test_nginx_proxy_behavior_when_alone(docker_compose, nginxproxy): + r = nginxproxy.get("http://nginx-proxy/") + assert r.status_code == 503 + + +def test_new_container_is_detected(web1, nginxproxy): + r = nginxproxy.get("http://web1.nginx-proxy/port") + assert r.status_code == 200 + assert "answer from port 81\n" == r.text + + web1.remove(force=True) + sleep(2) + r = nginxproxy.get("http://web1.nginx-proxy/port") + assert r.status_code == 503 diff --git a/test/test_events.yml b/test/test_events.yml new file mode 100644 index 0000000..d534870 --- /dev/null +++ b/test/test_events.yml @@ -0,0 +1,4 @@ +nginxproxy: + image: jwilder/nginx-proxy:test + volumes: + - /var/run/docker.sock:/tmp/docker.sock:ro diff --git a/test/test_headers/certs/web.nginx-proxy.tld.crt b/test/test_headers/certs/web.nginx-proxy.tld.crt new file mode 100644 index 0000000..aed9349 --- /dev/null +++ b/test/test_headers/certs/web.nginx-proxy.tld.crt @@ -0,0 +1,70 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 4096 (0x1000) + Signature Algorithm: sha256WithRSAEncryption + Issuer: O=nginx-proxy test suite, CN=www.nginx-proxy.tld + Validity + Not Before: Jan 13 03:06:39 2017 GMT + Not After : May 31 03:06:39 2044 GMT + Subject: CN=web.nginx-proxy.tld + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (2048 bit) + Modulus: + 00:95:56:c7:0d:48:a5:2b:3c:65:49:3f:26:e1:38: + 2b:61:30:56:e4:92:d7:63:e0:eb:ad:ac:f9:33:9b: + b2:31:f1:39:13:0b:e5:43:7b:c5:bd:8a:85:c8:d9: + 3d:d8:ac:71:ba:16:e7:81:96:b2:ab:ae:c6:c0:bd: + be:a7:d1:96:8f:b2:9b:df:ba:f9:4d:a1:3b:7e:21: + 4a:cd:b6:45:f9:6d:79:50:bf:24:8f:c1:6b:c1:09: + 19:5b:62:cb:96:e8:04:14:20:e8:d4:16:62:6a:f2: + 37:c1:96:e2:9d:53:05:0b:52:1d:e7:68:92:db:8b: + 36:68:cd:8d:5b:02:ff:12:f0:ac:5d:0c:c4:e0:7a: + 55:a2:49:60:9f:ff:47:1f:52:73:55:4d:d4:f2:d1: + 62:a2:f4:50:9d:c9:f6:f1:43:b3:dc:57:e1:31:76: + b4:e0:a4:69:7e:f2:6d:34:ae:b9:8d:74:26:7b:d9: + f6:07:00:ef:4b:36:61:b3:ef:7a:a1:36:3a:b6:d0: + 9e:f8:b8:a9:0d:4c:30:a2:ed:eb:ab:6b:eb:2e:e2: + 0b:28:be:f7:04:b1:e9:e0:84:d6:5d:31:77:7c:dc: + d2:1f:d4:1d:71:6f:6f:6c:6d:1b:bf:31:e2:5b:c3: + 52:d0:14:fc:8b:fb:45:ea:41:ec:ca:c7:3b:67:12: + c4:df + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Alternative Name: + DNS:web.nginx-proxy.tld + Signature Algorithm: sha256WithRSAEncryption + 4e:48:7d:81:66:ba:2f:50:3d:24:42:61:3f:1f:de:cf:ec:1b: + 1b:bd:0a:67:b6:62:c8:79:9d:31:a0:fd:a9:61:ce:ff:69:bf: + 0e:f4:f7:e6:15:2b:b0:f0:e4:f2:f4:d2:8f:74:02:b1:1e:4a: + a8:6f:26:0a:77:32:29:cf:dc:b5:61:82:3e:58:47:61:92:f0: + 0c:20:25:f8:41:4d:34:09:44:bc:39:9e:aa:82:06:83:13:8b: + 1e:2c:3d:cf:cd:1a:f7:77:39:38:e0:a3:a7:f3:09:da:02:8d: + 73:75:38:b4:dd:24:a7:f9:03:db:98:c6:88:54:87:dc:e0:65: + 4c:95:c5:39:9c:00:30:dc:f0:d3:2c:19:ca:f1:f4:6c:c6:d9: + b5:c4:4a:c7:bc:a1:2e:88:7b:b5:33:d0:ff:fb:48:5e:3e:29: + fa:58:e5:03:de:d8:17:de:ed:96:fc:7e:1f:fe:98:f6:be:99: + 38:87:51:c0:d3:b7:9a:0f:26:92:e5:53:1b:d6:25:4c:ac:48: + f3:29:fc:74:64:9d:07:6a:25:57:24:aa:a7:70:fa:8f:6c:a7: + 2b:b7:9d:81:46:10:32:93:b9:45:6d:0f:16:18:b2:21:1f:f3: + 30:24:62:3f:e1:6c:07:1d:71:28:cb:4c:bb:f5:39:05:f9:b2: + 5b:a0:05:1b +-----BEGIN CERTIFICATE----- +MIIC+zCCAeOgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwPzEfMB0GA1UECgwWbmdp +bngtcHJveHkgdGVzdCBzdWl0ZTEcMBoGA1UEAwwTd3d3Lm5naW54LXByb3h5LnRs +ZDAeFw0xNzAxMTMwMzA2MzlaFw00NDA1MzEwMzA2MzlaMB4xHDAaBgNVBAMME3dl +Yi5uZ2lueC1wcm94eS50bGQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +AQCVVscNSKUrPGVJPybhOCthMFbkktdj4OutrPkzm7Ix8TkTC+VDe8W9ioXI2T3Y +rHG6FueBlrKrrsbAvb6n0ZaPspvfuvlNoTt+IUrNtkX5bXlQvySPwWvBCRlbYsuW +6AQUIOjUFmJq8jfBluKdUwULUh3naJLbizZozY1bAv8S8KxdDMTgelWiSWCf/0cf +UnNVTdTy0WKi9FCdyfbxQ7PcV+ExdrTgpGl+8m00rrmNdCZ72fYHAO9LNmGz73qh +Njq20J74uKkNTDCi7eura+su4gsovvcEsenghNZdMXd83NIf1B1xb29sbRu/MeJb +w1LQFPyL+0XqQezKxztnEsTfAgMBAAGjIjAgMB4GA1UdEQQXMBWCE3dlYi5uZ2lu +eC1wcm94eS50bGQwDQYJKoZIhvcNAQELBQADggEBAE5IfYFmui9QPSRCYT8f3s/s +Gxu9Cme2Ysh5nTGg/alhzv9pvw709+YVK7Dw5PL00o90ArEeSqhvJgp3MinP3LVh +gj5YR2GS8AwgJfhBTTQJRLw5nqqCBoMTix4sPc/NGvd3OTjgo6fzCdoCjXN1OLTd +JKf5A9uYxohUh9zgZUyVxTmcADDc8NMsGcrx9GzG2bXESse8oS6Ie7Uz0P/7SF4+ +KfpY5QPe2Bfe7Zb8fh/+mPa+mTiHUcDTt5oPJpLlUxvWJUysSPMp/HRknQdqJVck +qqdw+o9spyu3nYFGEDKTuUVtDxYYsiEf8zAkYj/hbAcdcSjLTLv1OQX5slugBRs= +-----END CERTIFICATE----- diff --git a/test/test_headers/certs/web.nginx-proxy.tld.key b/test/test_headers/certs/web.nginx-proxy.tld.key new file mode 100644 index 0000000..8365ecf --- /dev/null +++ b/test/test_headers/certs/web.nginx-proxy.tld.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEAlVbHDUilKzxlST8m4TgrYTBW5JLXY+Drraz5M5uyMfE5Ewvl +Q3vFvYqFyNk92KxxuhbngZayq67GwL2+p9GWj7Kb37r5TaE7fiFKzbZF+W15UL8k +j8FrwQkZW2LLlugEFCDo1BZiavI3wZbinVMFC1Id52iS24s2aM2NWwL/EvCsXQzE +4HpVoklgn/9HH1JzVU3U8tFiovRQncn28UOz3FfhMXa04KRpfvJtNK65jXQme9n2 +BwDvSzZhs+96oTY6ttCe+LipDUwwou3rq2vrLuILKL73BLHp4ITWXTF3fNzSH9Qd +cW9vbG0bvzHiW8NS0BT8i/tF6kHsysc7ZxLE3wIDAQABAoIBAEmK7IecKMq7+V0y +3mC3GpXICmKR9cRX9XgX4LkLiZuSoXrBtuuevmhzGSMp6I0VjwQHV4a3wdFORs6Q +Ip3eVvj5Ck4Jc9BJAFVC6+WWR6tnwACFwOmSZRAw/O3GH2B3bdrDwiT/yQPFuLN7 +LKoxQiCrFdLp6rh3PBosb9pMBXU7k/HUazIdgmSKg6/JIoo/4Gwyid04TF/4MI2l +RscxtP5/ANtS8VgwBEqhgdafRJ4KnLEpgvswgIQvUKmduVhZQlzd0LMY8FbhKVqz +Utg8gsXaTyH6df/nmgUIInxLMz/MKPnMkv99fS6Sp/hvYlGpLZFWBJ6unMq3lKEr +LMbHfIECgYEAxB+5QWdVqG2r9loJlf8eeuNeMPml4P8Jmi5RKyJC7Cww6DMlMxOS +78ZJfl4b3ZrWuyvhjOfX/aTq7kQaF1BI9o3KJBH8k6EtO4gI8KeNmDONyQk9zsrn +ru8Zwr7hVbAo8fCXxCnmPzhDLsYg6f3BVOsQWoX2SFYKZ1GvkPfIReECgYEAwu6G +qtgFb57Vim10ecfWGM6vrPxvyfqP+zlH/p4nR+aQ+2sFbt27D0B1byWBRZe4KQyw +Vq6XiQ09Fk6MJr8E8iAr9GXPPHcqlYI6bbNc6YOP3jVSKut0tQdTUOHll4kYIY+h +RS3VA3+BA//ADpWpywu+7RZRbaIECA+U2a224r8CgYB5PCMIixgoRaNHZeEHF+1/ +iY1wOOKRcxY8eOU0BLnZxHd3EiasrCzoi2pi80nGczDKAxYqRCcAZDHVl8OJJdf0 +kTGjmnrHx5pucmkUWn7s1vGOlGfgrQ0K1kLWX6hrj7m/1Tn7yOrLqbvd7hvqiTI5 +jBVP3/+eN5G2zIf61TC4AQKBgCX2Q92jojNhsF58AHHy+/vqzIWYx8CC/mVDe4TX +kfjLqzJ7XhyAK/zFZdlWaX1/FYtRAEpxR+uV226rr1mgW7s3jrfS1/ADmRRyvyQ8 +CP0k9PCmW7EmF51lptEanRbMyRlIGnUZfuFmhF6eAO4WMXHsgKs1bHg4VCapuihG +T1aLAoGACRGn1UxFuBGqtsh2zhhsBZE7GvXKJSk/eP7QJeEXUNpNjCpgm8kIZM5K +GorpL7PSB8mwVlDl18TpMm3P7nz6YkJYte+HdjO7pg59H39Uvtg3tZnIrFxNxVNb +YF62/yHfk2AyTgjQZQUSmDS84jq1zUK4oS90lxr+u8qwELTniMs= +-----END RSA PRIVATE KEY----- diff --git a/test/test_headers/test_http.py b/test/test_headers/test_http.py new file mode 100644 index 0000000..2799262 --- /dev/null +++ b/test/test_headers/test_http.py @@ -0,0 +1,81 @@ +import pytest + +def test_arbitrary_headers_are_passed_on(docker_compose, nginxproxy): + r = nginxproxy.get("http://web.nginx-proxy.tld/headers", headers={'Foo': 'Bar'}) + assert r.status_code == 200 + assert "Foo: Bar\n" in r.text + + +##### Testing the handling of X-Forwarded-For ##### + +def test_X_Forwarded_For_is_generated(docker_compose, nginxproxy): + r = nginxproxy.get("http://web.nginx-proxy.tld/headers") + assert r.status_code == 200 + assert "X-Forwarded-For:" in r.text + +def test_X_Forwarded_For_is_passed_on(docker_compose, nginxproxy): + r = nginxproxy.get("http://web.nginx-proxy.tld/headers", headers={'X-Forwarded-For': '1.2.3.4'}) + assert r.status_code == 200 + assert "X-Forwarded-For: 1.2.3.4, " in r.text + + +##### Testing the handling of X-Forwarded-Proto ##### + +def test_X_Forwarded_Proto_is_generated(docker_compose, nginxproxy): + r = nginxproxy.get("http://web.nginx-proxy.tld/headers") + assert r.status_code == 200 + assert "X-Forwarded-Proto: http" in r.text + +def test_X_Forwarded_Proto_is_passed_on(docker_compose, nginxproxy): + r = nginxproxy.get("http://web.nginx-proxy.tld/headers", headers={'X-Forwarded-Proto': 'f00'}) + assert r.status_code == 200 + assert "X-Forwarded-Proto: f00\n" in r.text + + +##### Testing the handling of X-Forwarded-Port ##### + +def test_X_Forwarded_Port_is_generated(docker_compose, nginxproxy): + r = nginxproxy.get("http://web.nginx-proxy.tld/headers") + assert r.status_code == 200 + assert "X-Forwarded-Port: 80\n" in r.text + +def test_X_Forwarded_Port_is_passed_on(docker_compose, nginxproxy): + r = nginxproxy.get("http://web.nginx-proxy.tld/headers", headers={'X-Forwarded-Port': '1234'}) + assert r.status_code == 200 + assert "X-Forwarded-Port: 1234\n" in r.text + + +##### Testing the handling of X-Forwarded-Ssl ##### + +def test_X_Forwarded_Ssl_is_generated(docker_compose, nginxproxy): + r = nginxproxy.get("http://web.nginx-proxy.tld/headers") + assert r.status_code == 200 + assert "X-Forwarded-Ssl: off\n" in r.text + +def test_X_Forwarded_Ssl_is_overwritten(docker_compose, nginxproxy): + r = nginxproxy.get("http://web.nginx-proxy.tld/headers", headers={'X-Forwarded-Ssl': 'f00'}) + assert r.status_code == 200 + assert "X-Forwarded-Ssl: off\n" in r.text + + +##### Other headers + +def test_X_Real_IP_is_generated(docker_compose, nginxproxy): + r = nginxproxy.get("http://web.nginx-proxy.tld/headers") + assert r.status_code == 200 + assert "X-Real-IP: " in r.text + +def test_Host_is_passed_on(docker_compose, nginxproxy): + r = nginxproxy.get("http://web.nginx-proxy.tld/headers") + assert r.status_code == 200 + assert "Host: web.nginx-proxy.tld" in r.text + +def test_httpoxy_safe(docker_compose, nginxproxy): + """ + See https://httpoxy.org/ + nginx-proxy should suppress the `Proxy` header + """ + r = nginxproxy.get("http://web.nginx-proxy.tld/headers", headers={'Proxy': 'tcp://some.hacker.com'}) + assert r.status_code == 200 + assert "Proxy:" not in r.text + diff --git a/test/test_headers/test_http.yml b/test/test_headers/test_http.yml new file mode 100644 index 0000000..e3596be --- /dev/null +++ b/test/test_headers/test_http.yml @@ -0,0 +1,13 @@ +web: + image: web + expose: + - "80" + environment: + WEB_PORTS: 80 + VIRTUAL_HOST: web.nginx-proxy.tld + + +sut: + image: jwilder/nginx-proxy:test + volumes: + - /var/run/docker.sock:/tmp/docker.sock:ro \ No newline at end of file diff --git a/test/test_headers/test_https.py b/test/test_headers/test_https.py new file mode 100644 index 0000000..a1d434a --- /dev/null +++ b/test/test_headers/test_https.py @@ -0,0 +1,82 @@ +import pytest + + +def test_arbitrary_headers_are_passed_on(docker_compose, nginxproxy): + r = nginxproxy.get("https://web.nginx-proxy.tld/headers", headers={'Foo': 'Bar'}) + assert r.status_code == 200 + assert "Foo: Bar\n" in r.text + + +##### Testing the handling of X-Forwarded-For ##### + +def test_X_Forwarded_For_is_generated(docker_compose, nginxproxy): + r = nginxproxy.get("https://web.nginx-proxy.tld/headers") + assert r.status_code == 200 + assert "X-Forwarded-For:" in r.text + +def test_X_Forwarded_For_is_passed_on(docker_compose, nginxproxy): + r = nginxproxy.get("https://web.nginx-proxy.tld/headers", headers={'X-Forwarded-For': '1.2.3.4'}) + assert r.status_code == 200 + assert "X-Forwarded-For: 1.2.3.4, " in r.text + + +##### Testing the handling of X-Forwarded-Proto ##### + +def test_X_Forwarded_Proto_is_generated(docker_compose, nginxproxy): + r = nginxproxy.get("https://web.nginx-proxy.tld/headers") + assert r.status_code == 200 + assert "X-Forwarded-Proto: https" in r.text + +def test_X_Forwarded_Proto_is_passed_on(docker_compose, nginxproxy): + r = nginxproxy.get("https://web.nginx-proxy.tld/headers", headers={'X-Forwarded-Proto': 'f00'}) + assert r.status_code == 200 + assert "X-Forwarded-Proto: f00\n" in r.text + + +##### Testing the handling of X-Forwarded-Port ##### + +def test_X_Forwarded_Port_is_generated(docker_compose, nginxproxy): + r = nginxproxy.get("https://web.nginx-proxy.tld/headers") + assert r.status_code == 200 + assert "X-Forwarded-Port: 443\n" in r.text + +def test_X_Forwarded_Port_is_passed_on(docker_compose, nginxproxy): + r = nginxproxy.get("https://web.nginx-proxy.tld/headers", headers={'X-Forwarded-Port': '1234'}) + assert r.status_code == 200 + assert "X-Forwarded-Port: 1234\n" in r.text + + +##### Testing the handling of X-Forwarded-Ssl ##### + +def test_X_Forwarded_Ssl_is_generated(docker_compose, nginxproxy): + r = nginxproxy.get("https://web.nginx-proxy.tld/headers") + assert r.status_code == 200 + assert "X-Forwarded-Ssl: on\n" in r.text + +def test_X_Forwarded_Ssl_is_overwritten(docker_compose, nginxproxy): + r = nginxproxy.get("https://web.nginx-proxy.tld/headers", headers={'X-Forwarded-Ssl': 'f00'}) + assert r.status_code == 200 + assert "X-Forwarded-Ssl: on\n" in r.text + + +##### Other headers + +def test_X_Real_IP_is_generated(docker_compose, nginxproxy): + r = nginxproxy.get("https://web.nginx-proxy.tld/headers") + assert r.status_code == 200 + assert "X-Real-IP: " in r.text + +def test_Host_is_passed_on(docker_compose, nginxproxy): + r = nginxproxy.get("https://web.nginx-proxy.tld/headers") + assert r.status_code == 200 + assert "Host: web.nginx-proxy.tld" in r.text + +def test_httpoxy_safe(docker_compose, nginxproxy): + """ + See https://httpoxy.org/ + nginx-proxy should suppress the `Proxy` header + """ + r = nginxproxy.get("https://web.nginx-proxy.tld/headers", headers={'Proxy': 'tcp://some.hacker.com'}) + assert r.status_code == 200 + assert "Proxy:" not in r.text + diff --git a/test/test_headers/test_https.yml b/test/test_headers/test_https.yml new file mode 100644 index 0000000..8dc0744 --- /dev/null +++ b/test/test_headers/test_https.yml @@ -0,0 +1,15 @@ +web: + image: web + expose: + - "80" + environment: + WEB_PORTS: 80 + VIRTUAL_HOST: web.nginx-proxy.tld + + +sut: + image: jwilder/nginx-proxy:test + volumes: + - /var/run/docker.sock:/tmp/docker.sock:ro + - ./certs/web.nginx-proxy.tld.crt:/etc/nginx/certs/web.nginx-proxy.tld.crt:ro + - ./certs/web.nginx-proxy.tld.key:/etc/nginx/certs/web.nginx-proxy.tld.key:ro diff --git a/test/test_helpers.bash b/test/test_helpers.bash deleted file mode 100644 index 422df24..0000000 --- a/test/test_helpers.bash +++ /dev/null @@ -1,183 +0,0 @@ -# Test if requirements are met -( - type docker &>/dev/null || ( echo "docker is not available"; exit 1 ) -)>&2 - - -# set a few global variables -SUT_IMAGE=jwilder/nginx-proxy:bats -TEST_FILE=$(basename $BATS_TEST_FILENAME .bats) - - -# load the Bats stdlib (see https://github.com/sstephenson/bats/pull/110) -DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) -export BATS_LIB="${DIR}/lib/bats" -load "${BATS_LIB}/batslib.bash" - - -# load additional bats helpers -load ${DIR}/lib/helpers.bash -load ${DIR}/lib/docker_helpers.bash - - -# Define functions specific to our test suite - -# run the SUT docker container -# and makes sure it remains started -# and displays the nginx-proxy start logs -# -# $1 container name -# $@ other options for the `docker run` command -function nginxproxy { - local -r container_name=$1 - shift - docker_clean $container_name \ - && docker run -d \ - --label bats-type="nginx-proxy" \ - --name $container_name \ - -v $DIR/lib/ssl/dhparam.pem:/etc/nginx/dhparam/dhparam.pem:ro \ - "$@" \ - $SUT_IMAGE \ - && wait_for_nginxproxy_container_to_start $container_name \ - && docker logs $container_name -} - - -# wait until the nginx-proxy container is ready to operate -# -# $1 container name -function wait_for_nginxproxy_container_to_start { - local -r container_name=$1 - sleep .5s # give time to eventually fail to initialize - - function is_running { - run docker_running_state $container_name - assert_output "true" - } - retry 3 1 is_running -} - - -# Send a HTTP request to container $1 for path $2 and -# Additional curl options can be passed as $@ -# -# $1 container name -# $2 HTTP path to query -# $@ additional options to pass to the curl command -function curl_container { - local -r container=$1 - local -r path=$2 - shift 2 - docker run --label bats-type="curl" appropriate/curl --silent \ - --connect-timeout 5 \ - --max-time 20 \ - "$@" \ - http://$(docker_ip $container)${path} -} - -# Send a HTTPS request to container $1 for path $2 and -# Additional curl options can be passed as $@ -# -# $1 container name -# $2 HTTPS path to query -# $@ additional options to pass to the curl command -function curl_container_https { - local -r container=$1 - local -r path=$2 - shift 2 - docker run --label bats-type="curl" appropriate/curl --silent \ - --connect-timeout 5 \ - --max-time 20 \ - --insecure \ - "$@" \ - https://$(docker_ip $container)${path} -} - -# start a container running (one or multiple) webservers listening on given ports -# -# $1 container name -# $2 container port(s). If multiple ports, provide them as a string: "80 90" with a space as a separator -# $@ `docker run` additional options -function prepare_web_container { - local -r container_name=$1 - local -r ports=$2 - shift 2 - local -r options="$@" - - local expose_option="" - IFS=$' \t\n' # See https://github.com/sstephenson/bats/issues/89 - for port in $ports; do - expose_option="${expose_option}--expose=$port " - done - - ( # used for debugging purpose. Will be display if test fails - echo "container_name: $container_name" - echo "ports: $ports" - echo "options: $options" - echo "expose_option: $expose_option" - )>&2 - - docker_clean $container_name - - # GIVEN a container exposing 1 webserver on ports 1234 - run docker run -d \ - --label bats-type="web" \ - --name $container_name \ - $expose_option \ - -w /var/www/ \ - -v $DIR/web_helpers:/var/www:ro \ - $options \ - -e PYTHON_PORTS="$ports" \ - python:3 bash -c " - trap '[ \${#PIDS[@]} -gt 0 ] && kill -TERM \${PIDS[@]}' TERM - declare -a PIDS - for port in \$PYTHON_PORTS; do - echo starting a web server listening on port \$port; - ./webserver.py \$port & - PIDS+=(\$!) - done - wait \${PIDS[@]} - trap - TERM - wait \${PIDS[@]} - " - assert_success - - # THEN querying directly port works - IFS=$' \t\n' # See https://github.com/sstephenson/bats/issues/89 - for port in $ports; do - run retry 5 1s docker run --label bats-type="curl" appropriate/curl --silent --fail http://$(docker_ip $container_name):$port/port - assert_output "answer from port $port" - done -} - -# stop all containers with the "bats-type" label (matching the optionally supplied value) -# -# $1 optional label value -function stop_bats_containers { - local -r value=$1 - - if [ -z "$value" ]; then - CIDS=( $(docker ps -q --filter "label=bats-type") ) - else - CIDS=( $(docker ps -q --filter "label=bats-type=$value") ) - fi - - if [ ${#CIDS[@]} -gt 0 ]; then - docker stop ${CIDS[@]} >&2 - fi -} - -# wait for a docker-gen container to receive a specified event from a -# container with the specified ID/name -# -# $1 docker-gen container name -# $2 event -# $3 ID/name of container to receive event from -function dockergen_wait_for_event { - local -r container=$1 - local -r event=$2 - local -r other=$3 - local -r did=$(docker_id "$other") - docker_wait_for_log "$container" 9 "Received event $event for container ${did:0:12}" -} - diff --git a/test/test_ipv6.py b/test/test_ipv6.py new file mode 100644 index 0000000..36bf653 --- /dev/null +++ b/test/test_ipv6.py @@ -0,0 +1,35 @@ +import pytest + + +def test_unknown_virtual_host_ipv4(docker_compose, nginxproxy): + r = nginxproxy.get("http://nginx-proxy/port") + assert r.status_code == 503 + + +def test_forwards_to_web1_ipv4(docker_compose, nginxproxy): + r = nginxproxy.get("http://web1.nginx-proxy.tld/port") + assert r.status_code == 200 + assert r.text == "answer from port 81\n" + + +def test_forwards_to_web2_ipv4(docker_compose, nginxproxy): + r = nginxproxy.get("http://web2.nginx-proxy.tld/port") + assert r.status_code == 200 + assert r.text == "answer from port 82\n" + + +def test_unknown_virtual_host_ipv6(docker_compose, nginxproxy): + r = nginxproxy.get("http://nginx-proxy/port", ipv6=True) + assert r.status_code == 503 + + +def test_forwards_to_web1_ipv6(docker_compose, nginxproxy): + r = nginxproxy.get("http://web1.nginx-proxy.tld/port", ipv6=True) + assert r.status_code == 200 + assert r.text == "answer from port 81\n" + + +def test_forwards_to_web2_ipv6(docker_compose, nginxproxy): + r = nginxproxy.get("http://web2.nginx-proxy.tld/port", ipv6=True) + assert r.status_code == 200 + assert r.text == "answer from port 82\n" diff --git a/test/test_ipv6.yml b/test/test_ipv6.yml new file mode 100644 index 0000000..c734660 --- /dev/null +++ b/test/test_ipv6.yml @@ -0,0 +1,23 @@ +web1: + image: web + expose: + - "81" + environment: + WEB_PORTS: 81 + VIRTUAL_HOST: web1.nginx-proxy.tld + +web2: + image: web + expose: + - "82" + environment: + WEB_PORTS: 82 + VIRTUAL_HOST: web2.nginx-proxy.tld + + +sut: + image: jwilder/nginx-proxy:test + volumes: + - /var/run/docker.sock:/tmp/docker.sock:ro + environment: + ENABLE_IPV6: "true" diff --git a/test/test_multiple-hosts.py b/test/test_multiple-hosts.py new file mode 100644 index 0000000..76e7de6 --- /dev/null +++ b/test/test_multiple-hosts.py @@ -0,0 +1,16 @@ +import pytest + + +def test_unknown_virtual_host_is_503(docker_compose, nginxproxy): + r = nginxproxy.get("http://unknown.nginx-proxy.tld/port") + assert r.status_code == 503 + +def test_webA_is_forwarded(docker_compose, nginxproxy): + r = nginxproxy.get("http://webA.nginx-proxy.tld/port") + assert r.status_code == 200 + assert r.text == "answer from port 81\n" + +def test_webB_is_forwarded(docker_compose, nginxproxy): + r = nginxproxy.get("http://webB.nginx-proxy.tld/port") + assert r.status_code == 200 + assert r.text == "answer from port 81\n" diff --git a/test/test_multiple-hosts.yml b/test/test_multiple-hosts.yml new file mode 100644 index 0000000..95dcb4a --- /dev/null +++ b/test/test_multiple-hosts.yml @@ -0,0 +1,13 @@ +web: + image: web + expose: + - "81" + environment: + WEB_PORTS: 81 + VIRTUAL_HOST: webA.nginx-proxy.tld,webB.nginx-proxy.tld + + +sut: + image: jwilder/nginx-proxy:test + volumes: + - /var/run/docker.sock:/tmp/docker.sock:ro diff --git a/test/test_multiple-networks.py b/test/test_multiple-networks.py new file mode 100644 index 0000000..b9fa4c5 --- /dev/null +++ b/test/test_multiple-networks.py @@ -0,0 +1,15 @@ +import pytest + +def test_unknown_virtual_host(docker_compose, nginxproxy): + r = nginxproxy.get("http://nginx-proxy/") + assert r.status_code == 503 + +def test_forwards_to_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" + +def test_forwards_to_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" \ No newline at end of file diff --git a/test/test_multiple-networks.yml b/test/test_multiple-networks.yml new file mode 100644 index 0000000..c04a292 --- /dev/null +++ b/test/test_multiple-networks.yml @@ -0,0 +1,34 @@ +version: '2' + +networks: + net1: {} + net2: {} + +services: + nginx-proxy: + image: jwilder/nginx-proxy:test + volumes: + - /var/run/docker.sock:/tmp/docker.sock:ro + networks: + - net1 + - net2 + + web1: + image: web + expose: + - "81" + environment: + WEB_PORTS: 81 + VIRTUAL_HOST: web1.nginx-proxy.local + networks: + - net1 + + web2: + image: web + expose: + - "82" + environment: + WEB_PORTS: 82 + VIRTUAL_HOST: web2.nginx-proxy.local + networks: + - net2 \ No newline at end of file diff --git a/test/test_multiple-ports/test_VIRTUAL_PORT.py b/test/test_multiple-ports/test_VIRTUAL_PORT.py new file mode 100644 index 0000000..3c95ba6 --- /dev/null +++ b/test/test_multiple-ports/test_VIRTUAL_PORT.py @@ -0,0 +1,7 @@ +import pytest + + +def test_answer_is_served_from_chosen_port(docker_compose, nginxproxy): + r = nginxproxy.get("http://web.nginx-proxy.tld/port") + assert r.status_code == 200 + assert "answer from port 90\n" in r.text diff --git a/test/test_multiple-ports/test_VIRTUAL_PORT.yml b/test/test_multiple-ports/test_VIRTUAL_PORT.yml new file mode 100644 index 0000000..d61ac6f --- /dev/null +++ b/test/test_multiple-ports/test_VIRTUAL_PORT.yml @@ -0,0 +1,14 @@ +web: + image: web + expose: + - "80" + - "90" + environment: + WEB_PORTS: "80 90" + VIRTUAL_HOST: "web.nginx-proxy.tld" + VIRTUAL_PORT: 90 + +sut: + image: jwilder/nginx-proxy:test + volumes: + - /var/run/docker.sock:/tmp/docker.sock:ro diff --git a/test/test_multiple-ports/test_default-80.py b/test/test_multiple-ports/test_default-80.py new file mode 100644 index 0000000..74c2f9f --- /dev/null +++ b/test/test_multiple-ports/test_default-80.py @@ -0,0 +1,7 @@ +import pytest + + +def test_answer_is_served_from_port_80_by_default(docker_compose, nginxproxy): + r = nginxproxy.get("http://web.nginx-proxy.tld/port") + assert r.status_code == 200 + assert "answer from port 80\n" in r.text diff --git a/test/test_multiple-ports/test_default-80.yml b/test/test_multiple-ports/test_default-80.yml new file mode 100644 index 0000000..74916a6 --- /dev/null +++ b/test/test_multiple-ports/test_default-80.yml @@ -0,0 +1,13 @@ +web: + image: web + expose: + - "80" + - "81" + environment: + WEB_PORTS: "80 81" + VIRTUAL_HOST: "web.nginx-proxy.tld" + +sut: + image: jwilder/nginx-proxy:test + volumes: + - /var/run/docker.sock:/tmp/docker.sock:ro diff --git a/test/test_multiple-ports/test_single-port-not-80.py b/test/test_multiple-ports/test_single-port-not-80.py new file mode 100644 index 0000000..ee86eca --- /dev/null +++ b/test/test_multiple-ports/test_single-port-not-80.py @@ -0,0 +1,7 @@ +import pytest + + +def test_answer_is_served_from_exposed_port_even_if_not_80(docker_compose, nginxproxy): + r = nginxproxy.get("http://web.nginx-proxy.tld/port") + assert r.status_code == 200 + assert "answer from port 81\n" in r.text diff --git a/test/test_multiple-ports/test_single-port-not-80.yml b/test/test_multiple-ports/test_single-port-not-80.yml new file mode 100644 index 0000000..650dd07 --- /dev/null +++ b/test/test_multiple-ports/test_single-port-not-80.yml @@ -0,0 +1,13 @@ +web: + image: web + expose: + - "81" + environment: + WEB_PORTS: "81" + VIRTUAL_HOST: "web.nginx-proxy.tld" + + +sut: + image: jwilder/nginx-proxy:test + volumes: + - /var/run/docker.sock:/tmp/docker.sock:ro \ No newline at end of file diff --git a/test/test_nominal.py b/test/test_nominal.py new file mode 100644 index 0000000..cce7c94 --- /dev/null +++ b/test/test_nominal.py @@ -0,0 +1,24 @@ +import pytest +from requests import ConnectionError + + +def test_unknown_virtual_host(docker_compose, nginxproxy): + r = nginxproxy.get("http://nginx-proxy/port") + assert r.status_code == 503 + + +def test_forwards_to_web1(docker_compose, nginxproxy): + r = nginxproxy.get("http://web1.nginx-proxy.tld/port") + assert r.status_code == 200 + assert r.text == "answer from port 81\n" + + +def test_forwards_to_web2(docker_compose, nginxproxy): + r = nginxproxy.get("http://web2.nginx-proxy.tld/port") + assert r.status_code == 200 + assert r.text == "answer from port 82\n" + + +def test_ipv6_is_disabled_by_default(docker_compose, nginxproxy): + with pytest.raises(ConnectionError): + nginxproxy.get("http://nginx-proxy/port", ipv6=True) diff --git a/test/test_nominal.yml b/test/test_nominal.yml new file mode 100644 index 0000000..6a582a5 --- /dev/null +++ b/test/test_nominal.yml @@ -0,0 +1,21 @@ +web1: + image: web + expose: + - "81" + environment: + WEB_PORTS: 81 + VIRTUAL_HOST: web1.nginx-proxy.tld + +web2: + image: web + expose: + - "82" + environment: + WEB_PORTS: 82 + VIRTUAL_HOST: web2.nginx-proxy.tld + + +sut: + image: jwilder/nginx-proxy:test + volumes: + - /var/run/docker.sock:/tmp/docker.sock:ro diff --git a/test/test_ssl/certs/nginx-proxy.tld.crt b/test/test_ssl/certs/nginx-proxy.tld.crt new file mode 100644 index 0000000..cd7284b --- /dev/null +++ b/test/test_ssl/certs/nginx-proxy.tld.crt @@ -0,0 +1,70 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 4096 (0x1000) + Signature Algorithm: sha256WithRSAEncryption + Issuer: O=nginx-proxy test suite, CN=www.nginx-proxy.tld + Validity + Not Before: Jan 10 00:08:52 2017 GMT + Not After : May 28 00:08:52 2044 GMT + Subject: CN=*.nginx-proxy.tld + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (2048 bit) + Modulus: + 00:cb:45:f4:14:9b:fe:64:85:79:4a:36:8d:3d:d1: + 27:d0:7c:36:28:30:e6:73:80:6f:7c:49:23:d0:6c: + 17:e4:44:c0:77:4d:9a:c2:bc:24:84:e3:a5:4d:ba: + d2:da:51:7b:a1:2a:12:d4:c0:19:55:69:2c:22:27: + 2d:1a:f6:fc:4b:7f:e9:cb:a8:3c:e8:69:b8:d2:4f: + de:4e:50:e2:d0:74:30:7c:42:5a:ae:aa:85:a5:b1: + 71:4d:c9:7e:86:8b:62:8c:3e:0d:e3:3b:c3:f5:81: + 0b:8c:68:79:fe:bf:10:fb:ae:ec:11:49:6d:64:5e: + 1a:7d:b3:92:93:4e:96:19:3a:98:04:a7:66:b2:74: + 61:2d:41:13:0c:a4:54:0d:2c:78:fd:b4:a3:e8:37: + 78:9a:de:fa:bc:2e:a8:0f:67:14:58:ce:c3:87:d5: + 14:0e:8b:29:7d:48:19:b2:a9:f5:b4:e8:af:32:21: + 67:15:7e:43:52:8b:20:cf:9f:38:43:bf:fd:c8:24: + 7f:52:a3:88:f2:f1:4a:14:91:2a:6e:91:6f:fb:7d: + 6a:78:c6:6d:2e:dd:1e:4c:2b:63:bb:3a:43:9c:91: + f9:df:d3:08:13:63:86:7d:ce:e8:46:cf:f1:6c:1f: + ca:f7:4c:de:d8:4b:e0:da:bc:06:d9:87:0f:ff:96: + 45:85 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Alternative Name: + DNS:*.nginx-proxy.tld + Signature Algorithm: sha256WithRSAEncryption + 6e:a5:0e:e4:d3:cc:d5:b7:fc:34:75:89:4e:98:8c:e7:08:06: + a8:5b:ec:13:7d:83:99:a2:61:b8:d5:12:6e:c5:b4:53:4e:9a: + 22:cd:ad:14:30:6a:7d:58:d7:23:d9:a4:2a:96:a0:40:9e:50: + 9f:ce:f2:fe:8c:dd:9a:ac:99:39:5b:89:2d:ca:e5:3e:c3:bc: + 03:04:1c:12:d9:6e:b8:9f:f0:3a:be:12:44:7e:a4:21:86:73: + af:d5:00:51:3f:2c:56:70:34:8f:26:b0:7f:b0:cf:cf:7f:f9: + 40:6f:00:29:c4:cf:c3:b7:c2:49:3d:3f:b0:26:78:87:b9:c7: + 6c:1b:aa:6a:1a:dd:c5:eb:f2:69:ba:6d:46:0b:92:49:b5:11: + 3c:eb:48:c7:2f:fb:33:a6:6a:82:a2:ab:f8:1e:5f:7d:e3:b7: + f2:fd:f5:88:a5:09:4d:a0:bc:f4:3b:cd:d2:8b:d7:57:1f:86: + 3b:d2:3e:a4:92:21:b0:02:0b:e9:e0:c4:1c:f1:78:e2:58:a7: + 26:5f:4c:29:c8:23:f0:6e:12:3f:bd:ad:44:7b:0b:bd:db:ba: + 63:8d:07:c6:9d:dc:46:cc:63:40:ba:5e:45:82:dd:9a:e5:50: + e8:e7:d7:27:88:fc:6f:1d:8a:e7:5c:49:28:aa:10:29:75:28: + c7:52:de:f9 +-----BEGIN CERTIFICATE----- +MIIC9zCCAd+gAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwPzEfMB0GA1UECgwWbmdp +bngtcHJveHkgdGVzdCBzdWl0ZTEcMBoGA1UEAwwTd3d3Lm5naW54LXByb3h5LnRs +ZDAeFw0xNzAxMTAwMDA4NTJaFw00NDA1MjgwMDA4NTJaMBwxGjAYBgNVBAMMESou +bmdpbngtcHJveHkudGxkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA +y0X0FJv+ZIV5SjaNPdEn0Hw2KDDmc4BvfEkj0GwX5ETAd02awrwkhOOlTbrS2lF7 +oSoS1MAZVWksIictGvb8S3/py6g86Gm40k/eTlDi0HQwfEJarqqFpbFxTcl+hoti +jD4N4zvD9YELjGh5/r8Q+67sEUltZF4afbOSk06WGTqYBKdmsnRhLUETDKRUDSx4 +/bSj6Dd4mt76vC6oD2cUWM7Dh9UUDospfUgZsqn1tOivMiFnFX5DUosgz584Q7/9 +yCR/UqOI8vFKFJEqbpFv+31qeMZtLt0eTCtjuzpDnJH539MIE2OGfc7oRs/xbB/K +90ze2Evg2rwG2YcP/5ZFhQIDAQABoyAwHjAcBgNVHREEFTATghEqLm5naW54LXBy +b3h5LnRsZDANBgkqhkiG9w0BAQsFAAOCAQEAbqUO5NPM1bf8NHWJTpiM5wgGqFvs +E32DmaJhuNUSbsW0U06aIs2tFDBqfVjXI9mkKpagQJ5Qn87y/ozdmqyZOVuJLcrl +PsO8AwQcEtluuJ/wOr4SRH6kIYZzr9UAUT8sVnA0jyawf7DPz3/5QG8AKcTPw7fC +ST0/sCZ4h7nHbBuqahrdxevyabptRguSSbURPOtIxy/7M6ZqgqKr+B5ffeO38v31 +iKUJTaC89DvN0ovXVx+GO9I+pJIhsAIL6eDEHPF44linJl9MKcgj8G4SP72tRHsL +vdu6Y40Hxp3cRsxjQLpeRYLdmuVQ6OfXJ4j8bx2K51xJKKoQKXUox1Le+Q== +-----END CERTIFICATE----- diff --git a/test/test_ssl/certs/nginx-proxy.tld.key b/test/test_ssl/certs/nginx-proxy.tld.key new file mode 100644 index 0000000..91adb14 --- /dev/null +++ b/test/test_ssl/certs/nginx-proxy.tld.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAy0X0FJv+ZIV5SjaNPdEn0Hw2KDDmc4BvfEkj0GwX5ETAd02a +wrwkhOOlTbrS2lF7oSoS1MAZVWksIictGvb8S3/py6g86Gm40k/eTlDi0HQwfEJa +rqqFpbFxTcl+hotijD4N4zvD9YELjGh5/r8Q+67sEUltZF4afbOSk06WGTqYBKdm +snRhLUETDKRUDSx4/bSj6Dd4mt76vC6oD2cUWM7Dh9UUDospfUgZsqn1tOivMiFn +FX5DUosgz584Q7/9yCR/UqOI8vFKFJEqbpFv+31qeMZtLt0eTCtjuzpDnJH539MI +E2OGfc7oRs/xbB/K90ze2Evg2rwG2YcP/5ZFhQIDAQABAoIBAQCjAro2PNLJMfCO +fyjNRgmzu6iCmpR0U68T8GN0JPsT576g7e8J828l0pkhuIyW33lRSThIvLSUNf9a +dChL032H3lBTLduKVh4NKleQXnVFzaeEPoISSFVdButiAhAhPW4OIUVp0OfY3V+x +fac3j2nDLAfL5SKAtqZv363Py9m66EBYm5BmGTQqT/frQWeCEBvlErQef5RIaU8p +e2zMWgSNNojVai8U3nKNRvYHWeWXM6Ck7lCvkHhMF+RpbmCZuqhbEARVnehU/Jdn +QHJ3nxeA2OWpoWKXvAHtSnno49yxq1UIstiQvY+ng5C5i56UlB60UiU2NJ6doZkB +uQ7/1MaBAoGBAORdcFtgdgRALjXngFWhpCp0CseyUehn1KhxDCG+D1pJ142/ymcf +oJOzKJPMRNDdDUBMnR1GBfy7rmwvYevI/SMNy2Qs7ofcXPbdtwwvTCToZ1V9/54k +VfuPBFT+3QzWRvG1tjTV3E4L2VV3nrl2qNPhE5DlfIaU3nQq5Fl0HprJAoGBAOPf +MWOTGev61CdODO5KN3pLAoamiPs5lEUlz3kM3L1Q52YLITxNDjRj9hWBUATJZOS2 +pLOoYRwmhD7vrnimMc41+NuuFX+4T7hWPc8uSuOxX0VijYtULyNRK57mncG1Fq9M +RMLbOJ7FD+8jdXNsSMqpQ+pxLJRX/A10O2fOQnbdAoGAL5hV4YWSM0KZHvz332EI +ER0MXiCJN7HkPZMKH0I4eu3m8hEmAyYxVndBnsQ1F37q0xrkqAQ/HTSUntGlS/og +4Bxw5pkCwegoq/77tpto+ExDtSrEitYx4XMmSPyxX4qNULU5m3tzJgUML+b1etwD +Rd2kMU/TC02dq4KBAy/TbRkCgYAl1xN5iJz+XenLGR/2liZ+TWR+/bqzlU006mF4 +pZUmbv/uJxz+yYD5XDwqOA4UrWjuvhG9r9FoflDprp2XdWnB556KxG7XhcDfSJr9 +A5/2DadXe1Ur9O/a+oi2228JEsxQkea9QPA3FVxfBtFjOHEiDlez39VaUP4PMeUH +iO3qlQKBgFQhdTb7HeYnApYIDHLmd1PvjRvp8XKR1CpEN0nkw8HpHcT1q1MUjQCr +iT6FQupULEvGmO3frQsgVeRIQDbEdZK3C5xCtn6qOw70sYATVf361BbTtidmU9yV +THFxwDSVLiVZgFryoY/NtAc27sVdJnGsPRjjaeVgALAsLbmZ1K/H +-----END RSA PRIVATE KEY----- diff --git a/test/test_ssl/certs/web2.nginx-proxy.tld.crt b/test/test_ssl/certs/web2.nginx-proxy.tld.crt new file mode 100644 index 0000000..94562f4 --- /dev/null +++ b/test/test_ssl/certs/web2.nginx-proxy.tld.crt @@ -0,0 +1,71 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 4096 (0x1000) + Signature Algorithm: sha256WithRSAEncryption + Issuer: O=nginx-proxy test suite, CN=www.nginx-proxy.tld + Validity + Not Before: Jan 10 00:37:02 2017 GMT + Not After : May 28 00:37:02 2044 GMT + Subject: CN=web2.nginx-proxy.tld + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (2048 bit) + Modulus: + 00:da:ee:46:2d:44:7c:f1:e6:91:11:bf:34:d6:02: + 4e:fe:43:23:fb:6d:20:f7:8d:1b:c6:9c:cd:1c:1a: + 07:95:c2:ed:b9:23:73:c1:02:2b:50:51:3f:33:cf: + 8e:aa:f1:13:58:4c:ff:7f:7d:4a:87:fc:f0:0f:54: + af:8c:eb:ba:b4:0f:71:5e:12:1f:64:b1:3d:83:88: + dd:9c:09:50:2d:37:1d:03:3b:18:30:36:f4:82:94: + 87:7f:31:27:28:84:0c:99:6d:77:b5:b8:4f:ca:83: + 58:d5:d8:4e:36:73:1c:1a:5c:ed:05:ef:95:60:03: + 28:0c:9f:d8:6f:98:a8:cd:08:be:af:b1:95:5a:60: + 96:fe:2a:d0:98:74:9b:4a:c0:48:66:73:67:54:33: + 11:22:20:ea:11:05:ba:a6:ed:74:12:05:d8:de:4f: + 01:46:39:74:d8:34:1a:f2:2c:c2:df:6d:94:57:52: + c1:e4:2e:1b:8e:12:0e:43:e7:6f:1f:da:51:80:35: + c2:8a:9b:b6:2a:30:39:6b:a0:77:fa:37:11:b7:ec: + de:6e:8c:6f:93:81:5e:2d:90:69:1b:4b:a4:80:ca: + f4:e5:5b:c0:13:45:b9:76:70:ed:d3:4e:dd:66:98: + 99:9f:9d:f0:1e:fd:dd:04:4f:9a:55:bc:38:ad:42: + b9:dd + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Alternative Name: + DNS:web2.nginx-proxy.tld + Signature Algorithm: sha256WithRSAEncryption + 38:d6:8c:be:3c:5e:5d:61:02:77:eb:5b:6e:a7:1d:4f:69:0d: + 54:bd:dd:3a:1a:8e:9d:a0:c2:f3:a5:31:91:3e:ec:7a:69:48: + 40:27:45:a5:c6:b9:af:6d:d9:76:24:97:ec:c5:cf:4d:cc:49: + 93:ab:bc:4f:01:7e:7a:57:73:4d:27:62:a6:68:bf:4c:00:2c: + c0:f3:7b:b3:32:81:6b:96:20:0a:73:a0:85:b5:f8:07:10:88: + e8:62:85:41:63:df:43:c5:f9:4b:90:89:6a:16:d9:a6:85:4a: + 04:1b:5e:75:d8:84:ae:69:c7:62:8f:f1:53:c8:c6:31:71:6d: + 0c:05:2d:bf:6e:b8:b8:7a:8c:73:6f:17:bb:5c:5a:67:51:12: + af:e2:17:9c:40:0e:35:f6:59:93:69:45:14:74:33:c6:aa:04: + 76:8e:3c:a6:ac:f4:60:cb:53:eb:d6:63:1a:47:7b:be:11:8d: + 74:de:e8:e5:bc:de:1b:09:db:1b:87:89:b7:6a:20:0a:5e:fb: + 35:04:ab:b2:f7:4d:a1:86:65:1d:c7:c3:aa:30:15:3a:6e:66: + f8:43:33:63:ac:fc:c1:58:48:5b:ec:a0:00:bf:d4:f1:06:03: + 79:ca:d5:b6:f2:39:0b:62:b4:fd:27:27:e9:37:52:21:ce:a4: + 32:8a:bb:c7 +-----BEGIN CERTIFICATE----- +MIIC/TCCAeWgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwPzEfMB0GA1UECgwWbmdp +bngtcHJveHkgdGVzdCBzdWl0ZTEcMBoGA1UEAwwTd3d3Lm5naW54LXByb3h5LnRs +ZDAeFw0xNzAxMTAwMDM3MDJaFw00NDA1MjgwMDM3MDJaMB8xHTAbBgNVBAMMFHdl +YjIubmdpbngtcHJveHkudGxkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +AQEA2u5GLUR88eaREb801gJO/kMj+20g940bxpzNHBoHlcLtuSNzwQIrUFE/M8+O +qvETWEz/f31Kh/zwD1SvjOu6tA9xXhIfZLE9g4jdnAlQLTcdAzsYMDb0gpSHfzEn +KIQMmW13tbhPyoNY1dhONnMcGlztBe+VYAMoDJ/Yb5iozQi+r7GVWmCW/irQmHSb +SsBIZnNnVDMRIiDqEQW6pu10EgXY3k8BRjl02DQa8izC322UV1LB5C4bjhIOQ+dv +H9pRgDXCipu2KjA5a6B3+jcRt+zeboxvk4FeLZBpG0ukgMr05VvAE0W5dnDt007d +ZpiZn53wHv3dBE+aVbw4rUK53QIDAQABoyMwITAfBgNVHREEGDAWghR3ZWIyLm5n +aW54LXByb3h5LnRsZDANBgkqhkiG9w0BAQsFAAOCAQEAONaMvjxeXWECd+tbbqcd +T2kNVL3dOhqOnaDC86UxkT7semlIQCdFpca5r23ZdiSX7MXPTcxJk6u8TwF+eldz +TSdipmi/TAAswPN7szKBa5YgCnOghbX4BxCI6GKFQWPfQ8X5S5CJahbZpoVKBBte +ddiErmnHYo/xU8jGMXFtDAUtv264uHqMc28Xu1xaZ1ESr+IXnEAONfZZk2lFFHQz +xqoEdo48pqz0YMtT69ZjGkd7vhGNdN7o5bzeGwnbG4eJt2ogCl77NQSrsvdNoYZl +HcfDqjAVOm5m+EMzY6z8wVhIW+ygAL/U8QYDecrVtvI5C2K0/Scn6TdSIc6kMoq7 +xw== +-----END CERTIFICATE----- diff --git a/test/test_ssl/certs/web2.nginx-proxy.tld.key b/test/test_ssl/certs/web2.nginx-proxy.tld.key new file mode 100644 index 0000000..5cf1114 --- /dev/null +++ b/test/test_ssl/certs/web2.nginx-proxy.tld.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEA2u5GLUR88eaREb801gJO/kMj+20g940bxpzNHBoHlcLtuSNz +wQIrUFE/M8+OqvETWEz/f31Kh/zwD1SvjOu6tA9xXhIfZLE9g4jdnAlQLTcdAzsY +MDb0gpSHfzEnKIQMmW13tbhPyoNY1dhONnMcGlztBe+VYAMoDJ/Yb5iozQi+r7GV +WmCW/irQmHSbSsBIZnNnVDMRIiDqEQW6pu10EgXY3k8BRjl02DQa8izC322UV1LB +5C4bjhIOQ+dvH9pRgDXCipu2KjA5a6B3+jcRt+zeboxvk4FeLZBpG0ukgMr05VvA +E0W5dnDt007dZpiZn53wHv3dBE+aVbw4rUK53QIDAQABAoIBABeTCsl7E30017Ay +h6z3yKvGbQx43tDpR/FmFwwMnX555AFImQFSi3l1ljmtAu7TUML0X5rJ0gm8qdjs +xI6HH66d7xYzG2BLWZVdWoef1RtZUO11IpCmikO5XLHMiCvrtDOdPwO5WhYzeJBm +X12rnX4VPYyjFNGm5Vwepj62EI8rS9G3NG00pDYPmN/vUQJiV/iTRIlvXgFm4Hl2 +zJhVi+NhyiptFEycdg65JwVfLKtmUXRmwGFiSxQi1FX8YS5EdIV2S8yDwXlWSxmq +4R1eSID7pKxtzyRtBqZJggzfqtY8cMpoOC12FbLAvzagOavs4ntMgAVy5k2T15G2 +syQyLSECgYEA+1NIRF3CxNUaPvpcR8Y4PWhwDEzqn5ZscnXaFrUp1W72f3bpwSCa +/t9lXe9O53R5/yt4nCbwvVM0UWYPHGZGQr+5AS7WWDVWVcwkXX1NIjALi0TXQ0Ty +zeuViXDofUS31yhwFFmgGa52pd+edXaGRvxzGyPVdtwpSLZP/gBLQykCgYEA3wC9 +sHNPKMxi6vI2VBvmBXHoCSDAkuRLmQEGDmjjt0Ve4fmwGRbBJxniOlNtufNQRfRg +67qaeM4BTrP03cilZ71yXvLN5G2opY5c/I0dYTXRhV3IV6XwlCC+0xmn+ro7kB8x +J4wAj/h/tJ8T0+0TpnbyjmPH4KTJ9GjRTKwe65UCgYBszIHlbr2JXkONbe6S98GS +++o9uPJ9Aa6S4mf2Gpkwl2fIiF7rR0Ux/t2wC5AZ7Ld/en8tAkKHg0SL1GXIQpI6 +BSt+0prh9r0YSVaYzkyc9zWYJcYWjfuan1jN9f3/dMctMolKlf4UAA3HAwZjDVtV +0aW24w1e9jI9EweQCuqJ+QKBgBwZec18GiNn7abxMktS4J8bBUPxLpLT1XrIGD1E +lj0HrrcGwVvH9Dq7FjiHPrJJqHnIG1ZYwxIp0xxZrKctmzoBMyInsi3wa2nBEJJ6 +LZOMNoR5lr8El9XyclkjSHldchfs9kKnb4K0q1LVIKh5nRpCrrmmdQ8ndJMpigYB +QjwpAoGAWIhFrN0Acdq7Xc9pScqnAohPMWCTBUbeKrOm0ZwN4Q9D+lLeeggbWlWy +AqR4WHQMHc6B+p4Ncg3XBCFw0V62PkYhSCdaLNH3CPyFT0qoeY8VuMjmqS7yoKvp +uMMHfzMmyGg0dyplVGgANafb/it6Dp8T0pnCmzxhe57jf9xsVBo= +-----END RSA PRIVATE KEY----- diff --git a/test/test_ssl/certs/web3.nginx-proxy.tld.crt b/test/test_ssl/certs/web3.nginx-proxy.tld.crt new file mode 100644 index 0000000..453ef45 --- /dev/null +++ b/test/test_ssl/certs/web3.nginx-proxy.tld.crt @@ -0,0 +1,71 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 4096 (0x1000) + Signature Algorithm: sha256WithRSAEncryption + Issuer: O=nginx-proxy test suite, CN=www.nginx-proxy.tld + Validity + Not Before: Jan 10 00:58:11 2017 GMT + Not After : May 28 00:58:11 2044 GMT + Subject: CN=web3.nginx-proxy.tld + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (2048 bit) + Modulus: + 00:ac:9b:90:dc:2c:0d:c3:ae:f4:a0:cc:40:15:d2: + c4:c2:2c:8c:15:b6:70:28:cf:32:c4:03:ce:b3:87: + 30:5d:a6:12:96:69:7a:fe:67:29:1c:8c:24:bb:6a: + c8:86:13:19:91:00:3e:ef:00:67:50:b0:ea:c9:93: + c6:8a:73:82:d8:37:9f:8e:6e:12:13:ec:fa:08:0f: + ac:73:6e:42:96:67:9f:20:c5:1c:a3:b1:4a:83:36: + 0e:0a:31:93:76:b1:b6:37:4f:e0:88:3c:46:dc:c1: + 53:60:df:28:ae:3e:20:d8:d9:53:a2:be:09:38:f0: + ff:4a:91:45:cb:cb:b5:b3:3d:7d:09:98:47:dc:0d: + 5e:83:73:b6:c9:f3:fb:9a:f2:bb:b0:62:ca:aa:af: + 6b:42:e5:08:b2:14:87:f4:fc:f1:14:f8:cc:76:b3: + c0:49:df:66:c6:21:a0:bc:5e:0c:bb:de:e9:9c:e7: + fb:31:a1:cf:c4:e9:bb:bd:c3:5a:0d:23:52:c6:b3: + 84:77:f1:0c:3d:ca:c3:60:48:f9:7e:a6:dc:4f:f7: + d2:5b:7c:02:4d:38:09:64:33:7e:bb:b1:65:bb:e2: + 2b:1d:9a:49:d4:34:21:42:7a:49:3e:11:6c:10:66: + b4:91:db:bd:3a:c2:8d:f4:e4:03:b1:b4:6e:5c:98: + bf:7d + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Alternative Name: + DNS:web3.nginx-proxy.tld + Signature Algorithm: sha256WithRSAEncryption + 9a:f6:b9:c2:08:a4:b4:d7:e4:b2:d3:22:e9:fe:69:4a:e8:76: + 18:60:11:1b:3b:7c:4b:c3:72:66:95:b7:7c:de:c7:34:32:58: + aa:5d:e0:12:f0:df:27:b6:3f:dd:f1:8c:ed:ce:bd:73:50:fc: + 9b:e1:8c:c2:7f:ac:6b:54:9d:23:0a:d9:a6:25:cc:99:94:73: + 2b:69:e8:f7:07:40:37:d3:d4:0b:14:86:6a:a1:01:53:4b:ae: + 85:2d:12:13:bd:23:1e:09:cf:20:50:24:76:a6:5f:b4:d6:d6: + e1:34:40:93:4d:42:f7:d0:95:98:77:53:16:e7:ce:cc:4c:35: + b8:30:3b:62:95:e2:40:0c:a1:73:84:92:93:63:df:c6:21:d5: + eb:1d:a1:d0:f2:ec:a5:cf:d6:10:c9:8f:ac:11:16:39:cd:b0: + 38:04:29:cf:e1:1c:dd:21:df:1f:95:35:a5:a4:61:2b:3d:8b: + 8a:76:02:6d:31:a1:e8:6b:c5:3b:eb:90:40:34:16:5a:07:93: + 96:56:cd:8b:52:ca:65:51:78:d3:f2:af:40:44:43:ec:fe:a2: + c8:d4:6d:21:c7:1f:d2:45:28:0d:d2:51:0f:d1:a5:db:00:2a: + 3a:10:88:9e:5a:76:a2:f7:e2:f0:fe:14:a3:e8:ec:ef:00:f0: + 35:87:eb:7c +-----BEGIN CERTIFICATE----- +MIIC/TCCAeWgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwPzEfMB0GA1UECgwWbmdp +bngtcHJveHkgdGVzdCBzdWl0ZTEcMBoGA1UEAwwTd3d3Lm5naW54LXByb3h5LnRs +ZDAeFw0xNzAxMTAwMDU4MTFaFw00NDA1MjgwMDU4MTFaMB8xHTAbBgNVBAMMFHdl +YjMubmdpbngtcHJveHkudGxkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +AQEArJuQ3CwNw670oMxAFdLEwiyMFbZwKM8yxAPOs4cwXaYSlml6/mcpHIwku2rI +hhMZkQA+7wBnULDqyZPGinOC2Defjm4SE+z6CA+sc25ClmefIMUco7FKgzYOCjGT +drG2N0/giDxG3MFTYN8orj4g2NlTor4JOPD/SpFFy8u1sz19CZhH3A1eg3O2yfP7 +mvK7sGLKqq9rQuUIshSH9PzxFPjMdrPASd9mxiGgvF4Mu97pnOf7MaHPxOm7vcNa +DSNSxrOEd/EMPcrDYEj5fqbcT/fSW3wCTTgJZDN+u7Flu+IrHZpJ1DQhQnpJPhFs +EGa0kdu9OsKN9OQDsbRuXJi/fQIDAQABoyMwITAfBgNVHREEGDAWghR3ZWIzLm5n +aW54LXByb3h5LnRsZDANBgkqhkiG9w0BAQsFAAOCAQEAmva5wgiktNfkstMi6f5p +Suh2GGARGzt8S8NyZpW3fN7HNDJYql3gEvDfJ7Y/3fGM7c69c1D8m+GMwn+sa1Sd +IwrZpiXMmZRzK2no9wdAN9PUCxSGaqEBU0uuhS0SE70jHgnPIFAkdqZftNbW4TRA +k01C99CVmHdTFufOzEw1uDA7YpXiQAyhc4SSk2PfxiHV6x2h0PLspc/WEMmPrBEW +Oc2wOAQpz+Ec3SHfH5U1paRhKz2LinYCbTGh6GvFO+uQQDQWWgeTllbNi1LKZVF4 +0/KvQERD7P6iyNRtIccf0kUoDdJRD9Gl2wAqOhCInlp2ovfi8P4Uo+js7wDwNYfr +fA== +-----END CERTIFICATE----- diff --git a/test/test_ssl/certs/web3.nginx-proxy.tld.key b/test/test_ssl/certs/web3.nginx-proxy.tld.key new file mode 100644 index 0000000..773cd0b --- /dev/null +++ b/test/test_ssl/certs/web3.nginx-proxy.tld.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEArJuQ3CwNw670oMxAFdLEwiyMFbZwKM8yxAPOs4cwXaYSlml6 +/mcpHIwku2rIhhMZkQA+7wBnULDqyZPGinOC2Defjm4SE+z6CA+sc25ClmefIMUc +o7FKgzYOCjGTdrG2N0/giDxG3MFTYN8orj4g2NlTor4JOPD/SpFFy8u1sz19CZhH +3A1eg3O2yfP7mvK7sGLKqq9rQuUIshSH9PzxFPjMdrPASd9mxiGgvF4Mu97pnOf7 +MaHPxOm7vcNaDSNSxrOEd/EMPcrDYEj5fqbcT/fSW3wCTTgJZDN+u7Flu+IrHZpJ +1DQhQnpJPhFsEGa0kdu9OsKN9OQDsbRuXJi/fQIDAQABAoIBAA14DjPAFEriyiAK +EC4jxkrIox3GoLXuhS2ahnSn5fRI00Z9cKWNcz3RCcS+LmuX7fTMqhyIUYeQZqHY +MDP5k4o/vOmmWS7I3THn1zMitXt7FoW+G+ACI6hdfXb6K2GluGxUhVbcLUNoqpLy +lwARxQpm2wnl/l49IA63i1S9zq3vy5HSvxBv3jq8xp2PX3Sho33LhsvW+miCJe+R +etKSV4mAjvR62XVgUGJ40FciVMK3pzwwIKPLI7k9sa7WHZr6fNHeDxIWA6AsVBTH +O+2l8Ufd79KesOD6VqdHlxg2a79s3NoCOflQQAbOSSR9ioCE7Ykgvw0wVl57xNoB +WZVAY0ECgYEA2I6+a56NMzkEMxr0w9ZRqgocXCgbqxZx1v7newDyO5J3X4jYhmMJ +abNZtnVs1Pz0IAgCMCf+N4D+RAi9/ahYxq7jmhIkT/IUHseh93XLgd/x9Q9ZJOGm +9+NI3aIBgWOsy4orKxfwzRAVEakOCChYUCy/GUDDD1MPcjxC8ma5abUCgYEAzAua +15Nayr9Sg0QsHqNvgTVkVlA6u+TwN+vfI8cH5nmXIMm5ShwCc9+Pm8mpcFwUo4uE +vOzQ+NwG9CBbVu6/i1/aR+ZlbctdhpsW51v18B9eFVXSZUvEv1ONGdoJZhq0tW7W +V4Zjf+UwdTcrSZKVpd5woUbRkByROPdir/3Ie6kCgYBhf5LX3SBxSWBMqfw9F4bY +6YhvLVeXpZlHVKhfRsPIcl7wUio6Bui8ABWKAkAnfGNk8HYbvEXGM3tGojD3vQ2L +Fj4+paBXpgPM/9A6G3yuUmcbD/fwlO+Zd2jc8A2BdaDcWq6ozjSJ/o2dz+ETZyar +ohm/gtrPUXQI2HzDqeAcaQKBgEBMd+LvAHFbkPjkhrKw9fZViOTaK2gCYOB+Z7ay +hX7PWhxu9QCxiuRQ0sRY7BgILEjNMmsGhWOmklpjx+TBH4MgFX0K0XOj3jkIrlMB +26JrgA5hGQfqtHlGLvSyjLusNr3ly42RP9GRu490byOkGZxHWF66Hle3aNv2uRaU +dpThAoGBAMIPpf4E6xGzurhgYdXMit3jGAYD85BbNUIm9jOym2lxS63ipoF08QhH +NoMVRf/AUoS4VDGXsuABjMffTZbE8L+DaL1cWSuPJAoF9AXUXtz6A8LRc9Mn2WjS +L8BIs9xZCzrJ5XzY4PSnjjyAU81z6E80azWglkmh8rRDzi/o9O79 +-----END RSA PRIVATE KEY----- diff --git a/test/test_ssl/test_nohttp.py b/test/test_ssl/test_nohttp.py new file mode 100644 index 0000000..d7f0d92 --- /dev/null +++ b/test/test_ssl/test_nohttp.py @@ -0,0 +1,18 @@ +import pytest + + +def test_web2_http_is_not_forwarded(docker_compose, nginxproxy): + r = nginxproxy.get("http://web2.nginx-proxy.tld/", allow_redirects=False) + assert r.status_code == 503 + + +def test_web2_https_is_forwarded(docker_compose, nginxproxy): + r = nginxproxy.get("https://web2.nginx-proxy.tld/port", allow_redirects=False) + assert r.status_code == 200 + assert "answer from port 82\n" in r.text + + +def test_web2_HSTS_policy_is_active(docker_compose, nginxproxy): + r = nginxproxy.get("https://web2.nginx-proxy.tld/port", allow_redirects=False) + assert "answer from port 82\n" in r.text + assert "Strict-Transport-Security" in r.headers diff --git a/test/test_ssl/test_nohttp.yml b/test/test_ssl/test_nohttp.yml new file mode 100644 index 0000000..8486c5e --- /dev/null +++ b/test/test_ssl/test_nohttp.yml @@ -0,0 +1,15 @@ +web2: + image: web + expose: + - "82" + environment: + WEB_PORTS: "82" + VIRTUAL_HOST: "web2.nginx-proxy.tld" + HTTPS_METHOD: nohttp + + +sut: + image: jwilder/nginx-proxy:test + volumes: + - /var/run/docker.sock:/tmp/docker.sock:ro + - ./certs:/etc/nginx/certs:ro diff --git a/test/test_ssl/test_nohttps.py b/test/test_ssl/test_nohttps.py new file mode 100644 index 0000000..1cedf82 --- /dev/null +++ b/test/test_ssl/test_nohttps.py @@ -0,0 +1,12 @@ +import pytest +from requests import ConnectionError + +def test_http_is_forwarded(docker_compose, nginxproxy): + r = nginxproxy.get("http://web.nginx-proxy.tld/port", allow_redirects=False) + assert r.status_code == 200 + assert "answer from port 83\n" in r.text + + +def test_https_is_disabled(docker_compose, nginxproxy): + with pytest.raises(ConnectionError): + nginxproxy.get("https://web.nginx-proxy.tld/", allow_redirects=False) diff --git a/test/test_ssl/test_nohttps.yml b/test/test_ssl/test_nohttps.yml new file mode 100644 index 0000000..2e7623a --- /dev/null +++ b/test/test_ssl/test_nohttps.yml @@ -0,0 +1,14 @@ +web: + image: web + expose: + - "83" + environment: + WEB_PORTS: "83" + VIRTUAL_HOST: "web.nginx-proxy.tld" + HTTPS_METHOD: nohttps + + +sut: + image: jwilder/nginx-proxy:test + volumes: + - /var/run/docker.sock:/tmp/docker.sock:ro \ No newline at end of file diff --git a/test/test_ssl/test_noredirect.py b/test/test_ssl/test_noredirect.py new file mode 100644 index 0000000..62df28b --- /dev/null +++ b/test/test_ssl/test_noredirect.py @@ -0,0 +1,19 @@ +import pytest + + +def test_web3_http_is_forwarded(docker_compose, nginxproxy): + r = nginxproxy.get("http://web3.nginx-proxy.tld/port", allow_redirects=False) + assert r.status_code == 200 + assert "answer from port 83\n" in r.text + + +def test_web3_https_is_forwarded(docker_compose, nginxproxy): + r = nginxproxy.get("https://web3.nginx-proxy.tld/port", allow_redirects=False) + assert r.status_code == 200 + assert "answer from port 83\n" in r.text + + +def test_web2_HSTS_policy_is_inactive(docker_compose, nginxproxy): + r = nginxproxy.get("https://web3.nginx-proxy.tld/port", allow_redirects=False) + assert "answer from port 83\n" in r.text + assert "Strict-Transport-Security" not in r.headers \ No newline at end of file diff --git a/test/test_ssl/test_noredirect.yml b/test/test_ssl/test_noredirect.yml new file mode 100644 index 0000000..8d0b4b2 --- /dev/null +++ b/test/test_ssl/test_noredirect.yml @@ -0,0 +1,15 @@ +web3: + image: web + expose: + - "83" + environment: + WEB_PORTS: "83" + VIRTUAL_HOST: "web3.nginx-proxy.tld" + HTTPS_METHOD: noredirect + + +sut: + image: jwilder/nginx-proxy:test + volumes: + - /var/run/docker.sock:/tmp/docker.sock:ro + - ./certs:/etc/nginx/certs:ro diff --git a/test/test_ssl/test_wildcard.py b/test/test_ssl/test_wildcard.py new file mode 100644 index 0000000..9885d94 --- /dev/null +++ b/test/test_ssl/test_wildcard.py @@ -0,0 +1,23 @@ +import pytest + + +@pytest.mark.parametrize("subdomain", ["foo", "bar"]) +def test_web1_http_redirects_to_https(docker_compose, nginxproxy, subdomain): + r = nginxproxy.get("http://%s.nginx-proxy.tld/" % subdomain, allow_redirects=False) + assert r.status_code == 301 + assert "Location" in r.headers + assert "https://%s.nginx-proxy.tld/" % subdomain == r.headers['Location'] + + +@pytest.mark.parametrize("subdomain", ["foo", "bar"]) +def test_web1_https_is_forwarded(docker_compose, nginxproxy, subdomain): + r = nginxproxy.get("https://%s.nginx-proxy.tld/port" % subdomain, allow_redirects=False) + assert r.status_code == 200 + assert "answer from port 81\n" in r.text + + +@pytest.mark.parametrize("subdomain", ["foo", "bar"]) +def test_web1_HSTS_policy_is_active(docker_compose, nginxproxy, subdomain): + r = nginxproxy.get("https://%s.nginx-proxy.tld/port" % subdomain, allow_redirects=False) + assert "answer from port 81\n" in r.text + assert "Strict-Transport-Security" in r.headers diff --git a/test/test_ssl/test_wildcard.yml b/test/test_ssl/test_wildcard.yml new file mode 100644 index 0000000..27840d3 --- /dev/null +++ b/test/test_ssl/test_wildcard.yml @@ -0,0 +1,13 @@ +web1: + image: web + expose: + - "81" + environment: + WEB_PORTS: "81" + VIRTUAL_HOST: "*.nginx-proxy.tld" + +sut: + image: jwilder/nginx-proxy:test + volumes: + - /var/run/docker.sock:/tmp/docker.sock:ro + - ./certs:/etc/nginx/certs:ro diff --git a/test/test_wildcard_host.py b/test/test_wildcard_host.py new file mode 100644 index 0000000..eb8428e --- /dev/null +++ b/test/test_wildcard_host.py @@ -0,0 +1,32 @@ +import pytest + + +@pytest.mark.parametrize("host,expected_port", [ + ("f00.nginx-proxy.test", 81), + ("bar.nginx-proxy.test", 81), + ("test.nginx-proxy.f00", 82), + ("test.nginx-proxy.bar", 82), + ("web3.123.nginx-proxy.regexp", 83), + ("web3.ABC.nginx-proxy.regexp", 83), + ("web3.123.ABC.nginx-proxy.regexp", 83), + ("web3.123-ABC.nginx-proxy.regexp", 83), + ("web3.whatever.nginx-proxy.regexp-to-infinity-and-beyond", 83), + ("web4.123.nginx-proxy.regexp", 84), + ("web4.ABC.nginx-proxy.regexp", 84), + ("web4.123.ABC.nginx-proxy.regexp", 84), + ("web4.123-ABC.nginx-proxy.regexp", 84), + ("web4.whatever.nginx-proxy.regexp", 84), +]) +def test_wildcard_prefix(docker_compose, nginxproxy, host, expected_port): + r = nginxproxy.get("http://%s/port" % host) + assert r.status_code == 200 + assert r.text == "answer from port %s\n" % expected_port + + +@pytest.mark.parametrize("host", [ + "unexpected.nginx-proxy.tld", + "web4.whatever.nginx-proxy.regexp-to-infinity-and-beyond" +]) +def test_non_matching_host_is_503(docker_compose, nginxproxy, host): + r = nginxproxy.get("http://%s/port" % host) + assert r.status_code == 503, r.text diff --git a/test/test_wildcard_host.yml b/test/test_wildcard_host.yml new file mode 100644 index 0000000..a78ee3c --- /dev/null +++ b/test/test_wildcard_host.yml @@ -0,0 +1,37 @@ +web1: + image: web + expose: + - "81" + environment: + WEB_PORTS: "81" + VIRTUAL_HOST: "*.nginx-proxy.test" + +web2: + image: web + expose: + - "82" + environment: + WEB_PORTS: "82" + VIRTUAL_HOST: "test.nginx-proxy.*" + +web3: + image: web + expose: + - "83" + environment: + WEB_PORTS: "83" + VIRTUAL_HOST: ~^web3\..*\.nginx-proxy\.regexp + +web4: + image: web + expose: + - "84" + environment: + WEB_PORTS: "84" + VIRTUAL_HOST: ~^web4\..*\.nginx-proxy\.regexp$$ # we need to double the `$` because of docker-compose variable interpolation + + +sut: + image: jwilder/nginx-proxy:test + volumes: + - /var/run/docker.sock:/tmp/docker.sock:ro \ No newline at end of file diff --git a/test/wildcard-hosts.bats b/test/wildcard-hosts.bats deleted file mode 100644 index 826009e..0000000 --- a/test/wildcard-hosts.bats +++ /dev/null @@ -1,93 +0,0 @@ -#!/usr/bin/env bats -load test_helpers -SUT_CONTAINER=bats-nginx-proxy-${TEST_FILE} - -function setup { - # make sure to stop any web container before each test so we don't - # have any unexpected contaiener running with VIRTUAL_HOST or VIRUTAL_PORT set - stop_bats_containers web -} - - -@test "[$TEST_FILE] start a nginx-proxy container" { - # GIVEN - run nginxproxy $SUT_CONTAINER -v /var/run/docker.sock:/tmp/docker.sock:ro - assert_success - docker_wait_for_log $SUT_CONTAINER 9 "Watching docker events" -} - - -@test "[$TEST_FILE] VIRTUAL_HOST=*.wildcard.bats" { - # WHEN - prepare_web_container bats-wildcard-hosts-1 80 -e VIRTUAL_HOST=*.wildcard.bats - dockergen_wait_for_event $SUT_CONTAINER start bats-wildcard-hosts-1 - sleep 1 - - # THEN - assert_200 f00.wildcard.bats - assert_200 bar.wildcard.bats - assert_503 unexpected.host.bats -} - -@test "[$TEST_FILE] VIRTUAL_HOST=wildcard.bats.*" { - # WHEN - prepare_web_container bats-wildcard-hosts-2 80 -e VIRTUAL_HOST=wildcard.bats.* - dockergen_wait_for_event $SUT_CONTAINER start bats-wildcard-hosts-2 - sleep 1 - - # THEN - assert_200 wildcard.bats.f00 - assert_200 wildcard.bats.bar - assert_503 unexpected.host.bats -} - -@test "[$TEST_FILE] VIRTUAL_HOST=~^foo\.bar\..*\.bats" { - # WHEN - prepare_web_container bats-wildcard-hosts-3 80 -e VIRTUAL_HOST=~^foo\.bar\..*\.bats - dockergen_wait_for_event $SUT_CONTAINER start bats-wildcard-hosts-3 - sleep 1 - - # THEN - assert_200 foo.bar.whatever.bats - assert_200 foo.bar.why.not.bats - assert_200 foo.bar.why.not.bats-to-infinity-and-beyond - assert_503 unexpected.host.bats - -} - -@test "[$TEST_FILE] VIRTUAL_HOST=~^foo\.bar\..*\.bats$" { - # WHEN - prepare_web_container bats-wildcard-hosts-4 80 -e VIRTUAL_HOST=~^foo\.bar\..*\.bats$ - dockergen_wait_for_event $SUT_CONTAINER start bats-wildcard-hosts-4 - sleep 1 - - # THEN - assert_200 foo.bar.whatever.bats - assert_200 foo.bar.why.not.bats - assert_503 foo.bar.why.not.bats-to-infinity-and-beyond - assert_503 unexpected.host.bats - -} - -@test "[$TEST_FILE] stop all bats containers" { - stop_bats_containers -} - - -# assert that querying nginx-proxy with the given Host header produces a `HTTP 200` response -# $1 Host HTTP header to use when querying nginx-proxy -function assert_200 { - local -r host=$1 - - run curl_container $SUT_CONTAINER / --head --header "Host: $host" - assert_output -l 0 $'HTTP/1.1 200 OK\r' -} - -# assert that querying nginx-proxy with the given Host header produces a `HTTP 503` response -# $1 Host HTTP header to use when querying nginx-proxy -function assert_503 { - local -r host=$1 - - run curl_container $SUT_CONTAINER / --head --header "Host: $host" - assert_output -l 0 $'HTTP/1.1 503 Service Temporarily Unavailable\r' -}