1
0
mirror of https://github.com/thib8956/nginx-proxy synced 2024-12-24 03:26:31 +00:00

TESTS: rewrite tests using pytest and docker-compose

Experimentation to see if it is worth the effort
This commit is contained in:
Thomas LEVEIL 2016-12-10 00:53:23 +01:00
parent 985c46d8b5
commit 197d793a25
50 changed files with 1716 additions and 1 deletions

2
.gitignore vendored Normal file
View File

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

View File

@ -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

View File

@ -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
test2/Makefile Normal file
View File

@ -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
test2/README.md Normal file
View File

@ -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.

View File

@ -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-----

View File

@ -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
test2/certs/README.md Normal file
View File

@ -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
test2/certs/ca-root.crt Normal file
View File

@ -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
test2/certs/ca-root.key Normal file
View File

@ -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-----

View File

@ -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

View File

@ -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-----

View File

@ -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-----

View File

@ -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-----

View File

@ -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-----

View File

@ -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-----

View File

@ -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
test2/conftest.py Normal file
View File

@ -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
test2/requirements.txt Normal file
View File

@ -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

View File

@ -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
test2/requirements/build.sh Executable file
View File

@ -0,0 +1,6 @@
#!/bin/bash
set -e
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
docker build -t web $DIR/web

View File

@ -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"]

View File

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

View File

@ -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()

View File

@ -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"

View File

@ -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

View File

@ -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"

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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"

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
test2/test_nominal.py Normal file
View File

@ -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
test2/test_nominal.yml Normal file
View File

@ -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
test2/test_ssl_nohttp.py Normal file
View File

@ -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
test2/test_ssl_nohttp.yml Normal file
View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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