From 197d793a250b4c828980dbab066816900a6d6581 Mon Sep 17 00:00:00 2001 From: Thomas LEVEIL Date: Sat, 10 Dec 2016 00:53:23 +0100 Subject: [PATCH] TESTS: rewrite tests using pytest and docker-compose Experimentation to see if it is worth the effort --- .gitignore | 2 + .travis.yml | 14 ++ Makefile | 8 +- test2/Makefile | 17 ++ test2/README.md | 84 +++++++ test2/certs/*.nginx-proxy.tld.crt | 70 ++++++ test2/certs/*.nginx-proxy.tld.key | 27 ++ test2/certs/README.md | 81 ++++++ test2/certs/ca-root.crt | 21 ++ test2/certs/ca-root.key | 27 ++ test2/certs/create_server_certificate.sh | 183 ++++++++++++++ test2/certs/web.nginx-proxy.tld.crt | 70 ++++++ test2/certs/web.nginx-proxy.tld.key | 27 ++ test2/certs/web2.nginx-proxy.tld.crt | 71 ++++++ test2/certs/web2.nginx-proxy.tld.key | 27 ++ test2/certs/web3.nginx-proxy.tld.crt | 71 ++++++ test2/certs/web3.nginx-proxy.tld.key | 27 ++ test2/conftest.py | 235 ++++++++++++++++++ test2/requirements.txt | 5 + test2/requirements/README.md | 35 +++ test2/requirements/build.sh | 6 + test2/requirements/web/Dockerfile | 8 + test2/requirements/web/entrypoint.sh | 15 ++ test2/requirements/web/webserver.py | 27 ++ test2/test_DOCKER_HOST_unix_socket.py | 15 ++ test2/test_DOCKER_HOST_unix_socket.yml | 24 ++ test2/test_default-host.py | 7 + test2/test_default-host.yml | 17 ++ test2/test_headers_http.py | 81 ++++++ test2/test_headers_http.yml | 13 + test2/test_headers_https.py | 82 ++++++ test2/test_headers_https.yml | 15 ++ test2/test_multiple-hosts.py | 16 ++ test2/test_multiple-hosts.yml | 13 + test2/test_multiple-ports-VIRTUAL_PORT.py | 7 + test2/test_multiple-ports-VIRTUAL_PORT.yml | 14 ++ test2/test_multiple-ports-default-80.py | 7 + test2/test_multiple-ports-default-80.yml | 13 + .../test_multiple-ports-single-port-not-80.py | 7 + ...test_multiple-ports-single-port-not-80.yml | 13 + test2/test_nominal.py | 15 ++ test2/test_nominal.yml | 21 ++ test2/test_ssl_nohttp.py | 19 ++ test2/test_ssl_nohttp.yml | 16 ++ test2/test_ssl_noredirect.py | 19 ++ test2/test_ssl_noredirect.yml | 16 ++ test2/test_ssl_wildcard.py | 24 ++ test2/test_ssl_wildcard.yml | 16 ++ test2/test_wildcard_host.py | 32 +++ test2/test_wildcard_host.yml | 37 +++ 50 files changed, 1716 insertions(+), 1 deletion(-) create mode 100644 .gitignore create mode 100644 test2/Makefile create mode 100644 test2/README.md create mode 100644 test2/certs/*.nginx-proxy.tld.crt create mode 100644 test2/certs/*.nginx-proxy.tld.key create mode 100644 test2/certs/README.md create mode 100644 test2/certs/ca-root.crt create mode 100644 test2/certs/ca-root.key create mode 100755 test2/certs/create_server_certificate.sh create mode 100644 test2/certs/web.nginx-proxy.tld.crt create mode 100644 test2/certs/web.nginx-proxy.tld.key create mode 100644 test2/certs/web2.nginx-proxy.tld.crt create mode 100644 test2/certs/web2.nginx-proxy.tld.key create mode 100644 test2/certs/web3.nginx-proxy.tld.crt create mode 100644 test2/certs/web3.nginx-proxy.tld.key create mode 100644 test2/conftest.py create mode 100644 test2/requirements.txt create mode 100644 test2/requirements/README.md create mode 100755 test2/requirements/build.sh create mode 100644 test2/requirements/web/Dockerfile create mode 100644 test2/requirements/web/entrypoint.sh create mode 100755 test2/requirements/web/webserver.py create mode 100644 test2/test_DOCKER_HOST_unix_socket.py create mode 100644 test2/test_DOCKER_HOST_unix_socket.yml create mode 100644 test2/test_default-host.py create mode 100644 test2/test_default-host.yml create mode 100644 test2/test_headers_http.py create mode 100644 test2/test_headers_http.yml create mode 100644 test2/test_headers_https.py create mode 100644 test2/test_headers_https.yml create mode 100644 test2/test_multiple-hosts.py create mode 100644 test2/test_multiple-hosts.yml create mode 100644 test2/test_multiple-ports-VIRTUAL_PORT.py create mode 100644 test2/test_multiple-ports-VIRTUAL_PORT.yml create mode 100644 test2/test_multiple-ports-default-80.py create mode 100644 test2/test_multiple-ports-default-80.yml create mode 100644 test2/test_multiple-ports-single-port-not-80.py create mode 100644 test2/test_multiple-ports-single-port-not-80.yml create mode 100644 test2/test_nominal.py create mode 100644 test2/test_nominal.yml create mode 100644 test2/test_ssl_nohttp.py create mode 100644 test2/test_ssl_nohttp.yml create mode 100644 test2/test_ssl_noredirect.py create mode 100644 test2/test_ssl_noredirect.yml create mode 100644 test2/test_ssl_wildcard.py create mode 100644 test2/test_ssl_wildcard.yml create mode 100644 test2/test_wildcard_host.py create mode 100644 test2/test_wildcard_host.yml 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..33c54db 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,6 +6,7 @@ services: env: global: - DOCKER_VERSION=1.12.3-0~trusty + - DOCKER_COMPOSE_VERSION=1.9.0 before_install: # list docker-engine versions @@ -14,15 +15,28 @@ before_install: - sudo apt-get -o Dpkg::Options::="--force-confnew" install -y --force-yes docker-engine=${DOCKER_VERSION} - docker version - docker info + # install docker-compose + - sudo rm /usr/local/bin/docker-compose ||true + - curl -L https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-`uname -s`-`uname -m` > docker-compose + - chmod +x docker-compose + - sudo mv docker-compose /usr/local/bin/docker-compose + - docker-compose --version + # install bats - sudo add-apt-repository ppa:duggan/bats --yes - sudo apt-get update -qq - sudo apt-get install -qq bats + # prepare docker images - make update-dependencies matrix: include: - env: TEST_ID=test-debian - env: TEST_ID=test-alpine + - env: TEST_ID=test2-debian + - env: TEST_ID=test2-alpine + allow_failures: + - env: TEST_ID=test2-debian + - env: TEST_ID=test2-alpine script: - make $TEST_ID diff --git a/Makefile b/Makefile index dd50a8c..f872bb6 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ .SILENT : -.PHONY : test +.PHONY : test test2 update-dependencies: docker pull jwilder/docker-gen:0.7.3 @@ -19,3 +19,9 @@ test-alpine: bats test test: test-debian test-alpine + +test2-debian: + $(MAKE) -C test2 test-debian + +test2-alpine: + $(MAKE) -C test2 test-alpine diff --git a/test2/Makefile b/test2/Makefile new file mode 100644 index 0000000..f2ff33a --- /dev/null +++ b/test2/Makefile @@ -0,0 +1,17 @@ +.SILENT : +.PHONY : test-debian test-alpine test + + +update-dependencies: + requirements/build.sh + pip install -U -r requirements.txt + +test-debian: update-dependencies + docker build -t jwilder/nginx-proxy:test .. + pytest + +test-alpine: update-dependencies + docker build -f ../Dockerfile.alpine -t jwilder/nginx-proxy:test .. + pytest + +test: test-debian test-alpine diff --git a/test2/README.md b/test2/README.md new file mode 100644 index 0000000..66bd237 --- /dev/null +++ b/test2/README.md @@ -0,0 +1,84 @@ +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.txt + + +Prepare the nginx-proxy test image +---------------------------------- + + docker build -t jwilder/nginx-proxy:test .. + +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 + +Also _conftest.py_ alters the way the python interpreter resolves domain names to IP addresses in such a way that 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` +- ... + + +### 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/). + +The only requirement within that compose file is to have a container declared from the docker image `jwilder/nginx-proxy:test`. + +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 + + +### 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. + + +### 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/test2/certs/*.nginx-proxy.tld.crt b/test2/certs/*.nginx-proxy.tld.crt new file mode 100644 index 0000000..cd7284b --- /dev/null +++ b/test2/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/test2/certs/*.nginx-proxy.tld.key b/test2/certs/*.nginx-proxy.tld.key new file mode 100644 index 0000000..91adb14 --- /dev/null +++ b/test2/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/test2/certs/README.md b/test2/certs/README.md new file mode 100644 index 0000000..343b53e --- /dev/null +++ b/test2/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/test2/certs/ca-root.crt b/test2/certs/ca-root.crt new file mode 100644 index 0000000..a707881 --- /dev/null +++ b/test2/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/test2/certs/ca-root.key b/test2/certs/ca-root.key new file mode 100644 index 0000000..28e86a9 --- /dev/null +++ b/test2/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/test2/certs/create_server_certificate.sh b/test2/certs/create_server_certificate.sh new file mode 100755 index 0000000..833b97c --- /dev/null +++ b/test2/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/test2/certs/web.nginx-proxy.tld.crt b/test2/certs/web.nginx-proxy.tld.crt new file mode 100644 index 0000000..aed9349 --- /dev/null +++ b/test2/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/test2/certs/web.nginx-proxy.tld.key b/test2/certs/web.nginx-proxy.tld.key new file mode 100644 index 0000000..8365ecf --- /dev/null +++ b/test2/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/test2/certs/web2.nginx-proxy.tld.crt b/test2/certs/web2.nginx-proxy.tld.crt new file mode 100644 index 0000000..94562f4 --- /dev/null +++ b/test2/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/test2/certs/web2.nginx-proxy.tld.key b/test2/certs/web2.nginx-proxy.tld.key new file mode 100644 index 0000000..5cf1114 --- /dev/null +++ b/test2/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/test2/certs/web3.nginx-proxy.tld.crt b/test2/certs/web3.nginx-proxy.tld.crt new file mode 100644 index 0000000..453ef45 --- /dev/null +++ b/test2/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/test2/certs/web3.nginx-proxy.tld.key b/test2/certs/web3.nginx-proxy.tld.key new file mode 100644 index 0000000..773cd0b --- /dev/null +++ b/test2/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/test2/conftest.py b/test2/conftest.py new file mode 100644 index 0000000..77e96c1 --- /dev/null +++ b/test2/conftest.py @@ -0,0 +1,235 @@ +from __future__ import print_function +import json +import logging +import os +import shlex +import socket +import subprocess +import time + +import backoff +import docker +import pytest +import requests + +logging.basicConfig(level=logging.WARNING) +logging.getLogger('backoff').setLevel(logging.INFO) +logging.getLogger('patched DNS').setLevel(logging.INFO) + + +CA_ROOT_CERTIFICATE = os.path.join(os.path.dirname(__file__), 'certs/ca-root.crt') + + +############################################################################### +# +# utilities +# +############################################################################### + +class requests_retry_on_error_502(object): + """ + Proxy for calling methods of the requests module. + When a HTTP response failed due to HTTP Error 404 or 502, retry up to 30 times. + """ + def __init__(self): + self.session = requests.Session() + if os.path.isfile(CA_ROOT_CERTIFICATE): + self.session.verify = CA_ROOT_CERTIFICATE + + def get(self, *args, **kwargs): + @backoff.on_predicate(backoff.constant, lambda r: r.status_code in (404, 502), interval=1, max_tries=30) + def _get(*args, **kwargs): + return self.session.get(*args, **kwargs) + return _get(*args, **kwargs) + + def post(self, *args, **kwargs): + @backoff.on_predicate(backoff.constant, lambda r: r.status_code in (404, 502), interval=1, max_tries=30) + def _post(*args, **kwargs): + return self.session.post(*args, **kwargs) + return _post(*args, **kwargs) + + def put(self, *args, **kwargs): + @backoff.on_predicate(backoff.constant, lambda r: r.status_code in (404, 502), interval=1, max_tries=30) + def _put(*args, **kwargs): + return self.session.put(*args, **kwargs) + return _put(*args, **kwargs) + + def head(self, *args, **kwargs): + @backoff.on_predicate(backoff.constant, lambda r: r.status_code in (404, 502), interval=1, max_tries=30) + def _head(*args, **kwargs): + return self.session.head(*args, **kwargs) + return _head(*args, **kwargs) + + def delete(self, *args, **kwargs): + @backoff.on_predicate(backoff.constant, lambda r: r.status_code in (404, 502), interval=1, max_tries=30) + def _delete(*args, **kwargs): + return self.session.delete(*args, **kwargs) + return _delete(*args, **kwargs) + + def options(self, *args, **kwargs): + @backoff.on_predicate(backoff.constant, lambda r: r.status_code in (404, 502), interval=1, max_tries=30) + def _options(*args, **kwargs): + return self.session.options(*args, **kwargs) + return _options(*args, **kwargs) + + def __getattr__(self, name): + return getattr(requests, name) + + +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'. + """ + log = logging.getLogger("patched DNS") + prv_getaddrinfo = socket.getaddrinfo + dns_cache = {} + def new_getaddrinfo(*args): + log.debug("resolving domain name %s" % repr(args)) + if 'nginx-proxy' in args[0]: + docker_client = docker.from_env() + ip = docker_client.containers(filters={"status": "running", "ancestor": "jwilder/nginx-proxy:test"})[0]["NetworkSettings"]["Networks"]["bridge"]["IPAddress"] + log.info("resolving domain name %r as IP address is %s" % (args[0], ip)) + return [ + (socket.AF_INET, socket.SOCK_STREAM, 6, '', (ip, args[1])), + (socket.AF_INET, socket.SOCK_DGRAM, 17, '', (ip, args[1])), + (socket.AF_INET, socket.SOCK_RAW, 0, '', (ip, args[1])) + ] + + try: + return dns_cache[args] + except KeyError: + res = prv_getaddrinfo(*args) + dns_cache[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(): + docker_client = docker.from_env() + for info in docker_client.containers(all=True): + docker_client.remove_container(info["Id"], v=True, force=True) + + +def get_nginx_conf_from_container(container_id): + """ + return the nginx /etc/nginx/conf.d/default.conf file content from a container + """ + import tarfile + from cStringIO import StringIO + docker_client = docker.from_env() + strm, stat = docker_client.get_archive(container_id, '/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'): + subprocess.check_output(shlex.split('docker-compose -f %s up -d' % compose_file)) + + +def wait_for_nginxproxy_to_be_ready(): + """ + If a one (and only one) container started from image jwilder/nginx-proxy:test is found, + wait for its log to contain substring "Watching docker events" + """ + docker_client = docker.from_env() + containers = docker_client.containers(filters={"ancestor": "jwilder/nginx-proxy:test"}) + if len(containers) != 1: + return + container = containers[0] + for line in docker_client.logs(container['Id'], 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.info("using docker compose file %s" % docker_compose_file) + return docker_compose_file + + +############################################################################### +# +# 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`. + """ + 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) + wait_for_nginxproxy_to_be_ready() + time.sleep(3) + yield + restore_urllib_dns_resolver(original_dns_resolver) + + +@pytest.fixture(scope="session") +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 + """ + return requests_retry_on_error_502() + + +############################################################################### +# +# Py.test hooks +# +############################################################################### + +# pytest hook to display additionnal stuff in test report +def pytest_runtest_logreport(report): + if report.failed: + docker_client = docker.from_env() + test_containers = docker_client.containers(all=True, filters={"ancestor": "jwilder/nginx-proxy:test"}) + for container in test_containers: + report.longrepr.addsection('nginx-proxy logs', docker_client.logs(container['Id'])) + report.longrepr.addsection('nginx-proxy conf', get_nginx_conf_from_container(container['Id'])) + diff --git a/test2/requirements.txt b/test2/requirements.txt new file mode 100644 index 0000000..fbd7a35 --- /dev/null +++ b/test2/requirements.txt @@ -0,0 +1,5 @@ +backoff==1.3.2 +docker-compose==1.9.0 +docker-py==1.10.6 +pytest==3.0.5 +requests==2.11.1 \ No newline at end of file diff --git a/test2/requirements/README.md b/test2/requirements/README.md new file mode 100644 index 0000000..79195d6 --- /dev/null +++ b/test2/requirements/README.md @@ -0,0 +1,35 @@ +This directory contains ressources to build Docker images tests depend on + +# Build images + + ./build.sh + + +# 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 answer for 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 + +``` + diff --git a/test2/requirements/build.sh b/test2/requirements/build.sh new file mode 100755 index 0000000..8741d3c --- /dev/null +++ b/test2/requirements/build.sh @@ -0,0 +1,6 @@ +#!/bin/bash +set -e + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +docker build -t web $DIR/web diff --git a/test2/requirements/web/Dockerfile b/test2/requirements/web/Dockerfile new file mode 100644 index 0000000..923ed79 --- /dev/null +++ b/test2/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/test2/requirements/web/entrypoint.sh b/test2/requirements/web/entrypoint.sh new file mode 100644 index 0000000..3015c11 --- /dev/null +++ b/test2/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/test2/requirements/web/webserver.py b/test2/requirements/web/webserver.py new file mode 100755 index 0000000..d94ed89 --- /dev/null +++ b/test2/requirements/web/webserver.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 + +import os, sys +import http.server +import socketserver + +class BatsHandler(http.server.SimpleHTTPRequestHandler): + def do_GET(self): + root = os.getcwd() + + self.send_response(200) + self.send_header("Content-Type", "text/plain") + self.end_headers() + + if self.path == "/headers": + self.wfile.write(self.headers.as_string().encode()) + elif self.path == "/port": + response = "answer from port %s\n" % PORT + 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.serve_forever() diff --git a/test2/test_DOCKER_HOST_unix_socket.py b/test2/test_DOCKER_HOST_unix_socket.py new file mode 100644 index 0000000..b31da16 --- /dev/null +++ b/test2/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/test2/test_DOCKER_HOST_unix_socket.yml b/test2/test_DOCKER_HOST_unix_socket.yml new file mode 100644 index 0000000..79b4baf --- /dev/null +++ b/test2/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/test2/test_default-host.py b/test2/test_default-host.py new file mode 100644 index 0000000..90809a5 --- /dev/null +++ b/test2/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/test2/test_default-host.yml b/test2/test_default-host.yml new file mode 100644 index 0000000..590dcaa --- /dev/null +++ b/test2/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/test2/test_headers_http.py b/test2/test_headers_http.py new file mode 100644 index 0000000..2799262 --- /dev/null +++ b/test2/test_headers_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/test2/test_headers_http.yml b/test2/test_headers_http.yml new file mode 100644 index 0000000..e3596be --- /dev/null +++ b/test2/test_headers_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/test2/test_headers_https.py b/test2/test_headers_https.py new file mode 100644 index 0000000..a1d434a --- /dev/null +++ b/test2/test_headers_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/test2/test_headers_https.yml b/test2/test_headers_https.yml new file mode 100644 index 0000000..8dc0744 --- /dev/null +++ b/test2/test_headers_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/test2/test_multiple-hosts.py b/test2/test_multiple-hosts.py new file mode 100644 index 0000000..76e7de6 --- /dev/null +++ b/test2/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/test2/test_multiple-hosts.yml b/test2/test_multiple-hosts.yml new file mode 100644 index 0000000..95dcb4a --- /dev/null +++ b/test2/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/test2/test_multiple-ports-VIRTUAL_PORT.py b/test2/test_multiple-ports-VIRTUAL_PORT.py new file mode 100644 index 0000000..3c95ba6 --- /dev/null +++ b/test2/test_multiple-ports-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/test2/test_multiple-ports-VIRTUAL_PORT.yml b/test2/test_multiple-ports-VIRTUAL_PORT.yml new file mode 100644 index 0000000..d61ac6f --- /dev/null +++ b/test2/test_multiple-ports-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/test2/test_multiple-ports-default-80.py b/test2/test_multiple-ports-default-80.py new file mode 100644 index 0000000..74c2f9f --- /dev/null +++ b/test2/test_multiple-ports-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/test2/test_multiple-ports-default-80.yml b/test2/test_multiple-ports-default-80.yml new file mode 100644 index 0000000..74916a6 --- /dev/null +++ b/test2/test_multiple-ports-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/test2/test_multiple-ports-single-port-not-80.py b/test2/test_multiple-ports-single-port-not-80.py new file mode 100644 index 0000000..ee86eca --- /dev/null +++ b/test2/test_multiple-ports-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/test2/test_multiple-ports-single-port-not-80.yml b/test2/test_multiple-ports-single-port-not-80.yml new file mode 100644 index 0000000..650dd07 --- /dev/null +++ b/test2/test_multiple-ports-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/test2/test_nominal.py b/test2/test_nominal.py new file mode 100644 index 0000000..b31da16 --- /dev/null +++ b/test2/test_nominal.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/test2/test_nominal.yml b/test2/test_nominal.yml new file mode 100644 index 0000000..6a582a5 --- /dev/null +++ b/test2/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/test2/test_ssl_nohttp.py b/test2/test_ssl_nohttp.py new file mode 100644 index 0000000..ad44b4a --- /dev/null +++ b/test2/test_ssl_nohttp.py @@ -0,0 +1,19 @@ +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/test2/test_ssl_nohttp.yml b/test2/test_ssl_nohttp.yml new file mode 100644 index 0000000..b945e4a --- /dev/null +++ b/test2/test_ssl_nohttp.yml @@ -0,0 +1,16 @@ +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/web2.nginx-proxy.tld.crt:/etc/nginx/certs/web2.nginx-proxy.tld.crt:ro + - ./certs/web2.nginx-proxy.tld.key:/etc/nginx/certs/web2.nginx-proxy.tld.key:ro \ No newline at end of file diff --git a/test2/test_ssl_noredirect.py b/test2/test_ssl_noredirect.py new file mode 100644 index 0000000..62df28b --- /dev/null +++ b/test2/test_ssl_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/test2/test_ssl_noredirect.yml b/test2/test_ssl_noredirect.yml new file mode 100644 index 0000000..5209d72 --- /dev/null +++ b/test2/test_ssl_noredirect.yml @@ -0,0 +1,16 @@ +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/web3.nginx-proxy.tld.crt:/etc/nginx/certs/web3.nginx-proxy.tld.crt:ro + - ./certs/web3.nginx-proxy.tld.key:/etc/nginx/certs/web3.nginx-proxy.tld.key:ro \ No newline at end of file diff --git a/test2/test_ssl_wildcard.py b/test2/test_ssl_wildcard.py new file mode 100644 index 0000000..2bea1d5 --- /dev/null +++ b/test2/test_ssl_wildcard.py @@ -0,0 +1,24 @@ +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/test2/test_ssl_wildcard.yml b/test2/test_ssl_wildcard.yml new file mode 100644 index 0000000..16a0758 --- /dev/null +++ b/test2/test_ssl_wildcard.yml @@ -0,0 +1,16 @@ +web1: + image: web + expose: + - "81" + environment: + WEB_PORTS: "81" + VIRTUAL_HOST: "*.nginx-proxy.tld" + CERT_NAME: "*.nginx-proxy.tld" + + +sut: + image: jwilder/nginx-proxy:test + volumes: + - /var/run/docker.sock:/tmp/docker.sock:ro + - ./certs/*.nginx-proxy.tld.crt:/etc/nginx/certs/*.nginx-proxy.tld.crt:ro + - ./certs/*.nginx-proxy.tld.key:/etc/nginx/certs/*.nginx-proxy.tld.key:ro \ No newline at end of file diff --git a/test2/test_wildcard_host.py b/test2/test_wildcard_host.py new file mode 100644 index 0000000..eb8428e --- /dev/null +++ b/test2/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/test2/test_wildcard_host.yml b/test2/test_wildcard_host.yml new file mode 100644 index 0000000..a78ee3c --- /dev/null +++ b/test2/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