浏览代码

TESTS: rewrite tests using pytest and docker-compose

Experimentation to see if it is worth the effort
Thomas LEVEIL 8 年之前
父节点
当前提交
197d793a25
共有 50 个文件被更改,包括 1716 次插入1 次删除
  1. 2 0
      .gitignore
  2. 14 0
      .travis.yml
  3. 7 1
      Makefile
  4. 17 0
      test2/Makefile
  5. 84 0
      test2/README.md
  6. 70 0
      test2/certs/*.nginx-proxy.tld.crt
  7. 27 0
      test2/certs/*.nginx-proxy.tld.key
  8. 81 0
      test2/certs/README.md
  9. 21 0
      test2/certs/ca-root.crt
  10. 27 0
      test2/certs/ca-root.key
  11. 183 0
      test2/certs/create_server_certificate.sh
  12. 70 0
      test2/certs/web.nginx-proxy.tld.crt
  13. 27 0
      test2/certs/web.nginx-proxy.tld.key
  14. 71 0
      test2/certs/web2.nginx-proxy.tld.crt
  15. 27 0
      test2/certs/web2.nginx-proxy.tld.key
  16. 71 0
      test2/certs/web3.nginx-proxy.tld.crt
  17. 27 0
      test2/certs/web3.nginx-proxy.tld.key
  18. 235 0
      test2/conftest.py
  19. 5 0
      test2/requirements.txt
  20. 35 0
      test2/requirements/README.md
  21. 6 0
      test2/requirements/build.sh
  22. 8 0
      test2/requirements/web/Dockerfile
  23. 15 0
      test2/requirements/web/entrypoint.sh
  24. 27 0
      test2/requirements/web/webserver.py
  25. 15 0
      test2/test_DOCKER_HOST_unix_socket.py
  26. 24 0
      test2/test_DOCKER_HOST_unix_socket.yml
  27. 7 0
      test2/test_default-host.py
  28. 17 0
      test2/test_default-host.yml
  29. 81 0
      test2/test_headers_http.py
  30. 13 0
      test2/test_headers_http.yml
  31. 82 0
      test2/test_headers_https.py
  32. 15 0
      test2/test_headers_https.yml
  33. 16 0
      test2/test_multiple-hosts.py
  34. 13 0
      test2/test_multiple-hosts.yml
  35. 7 0
      test2/test_multiple-ports-VIRTUAL_PORT.py
  36. 14 0
      test2/test_multiple-ports-VIRTUAL_PORT.yml
  37. 7 0
      test2/test_multiple-ports-default-80.py
  38. 13 0
      test2/test_multiple-ports-default-80.yml
  39. 7 0
      test2/test_multiple-ports-single-port-not-80.py
  40. 13 0
      test2/test_multiple-ports-single-port-not-80.yml
  41. 15 0
      test2/test_nominal.py
  42. 21 0
      test2/test_nominal.yml
  43. 19 0
      test2/test_ssl_nohttp.py
  44. 16 0
      test2/test_ssl_nohttp.yml
  45. 19 0
      test2/test_ssl_noredirect.py
  46. 16 0
      test2/test_ssl_noredirect.yml
  47. 24 0
      test2/test_ssl_wildcard.py
  48. 16 0
      test2/test_ssl_wildcard.yml
  49. 32 0
      test2/test_wildcard_host.py
  50. 37 0
      test2/test_wildcard_host.yml

+ 2 - 0
.gitignore

@@ -0,0 +1,2 @@
+**/__pycache__/
+**/.cache/

+ 14 - 0
.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

+ 7 - 1
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

+ 17 - 0
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

+ 84 - 0
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.

+ 70 - 0
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-----

+ 27 - 0
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-----

+ 81 - 0
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`

+ 21 - 0
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-----

+ 27 - 0
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-----

+ 183 - 0
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 <https://en.wikipedia.org/wiki/Certificate_signing_request>.
+		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

+ 70 - 0
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-----

+ 27 - 0
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-----

+ 71 - 0
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-----

+ 27 - 0
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-----

+ 71 - 0
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-----

+ 27 - 0
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-----

+ 235 - 0
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']))
+

+ 5 - 0
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

+ 35 - 0
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
+
+```
+

+ 6 - 0
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 

+ 8 - 0
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"]
+

+ 15 - 0
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[@]}

+ 27 - 0
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()

+ 15 - 0
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" 

+ 24 - 0
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
+

+ 7 - 0
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"

+ 17 - 0
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

+ 81 - 0
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
+    

+ 13 - 0
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

+ 82 - 0
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
+    

+ 15 - 0
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

+ 16 - 0
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"

+ 13 - 0
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

+ 7 - 0
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

+ 14 - 0
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

+ 7 - 0
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

+ 13 - 0
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

+ 7 - 0
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

+ 13 - 0
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

+ 15 - 0
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" 

+ 21 - 0
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

+ 19 - 0
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
+

+ 16 - 0
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

+ 19 - 0
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

+ 16 - 0
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

+ 24 - 0
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
+

+ 16 - 0
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

+ 32 - 0
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

+ 37 - 0
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