From a3fbaa59909c81d14ce7c29bdf1555bb5f4537bc Mon Sep 17 00:00:00 2001 From: Thomas LEVEIL Date: Tue, 21 Feb 2017 01:01:00 +0100 Subject: [PATCH 01/18] TESTS: add directory for tests featuring scenarios trying to make nginx-proxy fail --- test/stress_tests/README.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 test/stress_tests/README.md diff --git a/test/stress_tests/README.md b/test/stress_tests/README.md new file mode 100644 index 0000000..ca20cc1 --- /dev/null +++ b/test/stress_tests/README.md @@ -0,0 +1 @@ +This directory contains tests that showcase scenarios known to break the expected behavior of nginx-proxy. \ No newline at end of file From e25c78b00ad36c96b864dde2fef73a16e76482db Mon Sep 17 00:00:00 2001 From: Thomas LEVEIL Date: Tue, 21 Feb 2017 01:04:50 +0100 Subject: [PATCH 02/18] TESTS: add pytest `incremental` marker to mark tests as expected to fail if previous test failed see http://stackoverflow.com/a/12579625/107049 --- test/conftest.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/conftest.py b/test/conftest.py index fcff6ea..43f83bb 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -445,6 +445,18 @@ def pytest_runtest_logreport(report): report.longrepr.addsection('nginx-proxy conf', get_nginx_conf_from_container(container)) +# Py.test `incremental` marker, see http://stackoverflow.com/a/12579625/107049 +def pytest_runtest_makereport(item, call): + if "incremental" in item.keywords: + if call.excinfo is not None: + parent = item.parent + parent._previousfailed = item + + +def pytest_runtest_setup(item): + previousfailed = getattr(item.parent, "_previousfailed", None) + if previousfailed is not None: + pytest.xfail("previous test failed (%s)" % previousfailed.name) ############################################################################### # From 6dfc3f3f7026982570ea5f9e6d67414bd94da920 Mon Sep 17 00:00:00 2001 From: Thomas LEVEIL Date: Tue, 21 Feb 2017 01:05:42 +0100 Subject: [PATCH 03/18] TESTS: add stress test when a certificate file is missing --- test/stress_tests/test_deleted_cert/README.md | 5 ++ .../certs/web.nginx-proxy.crt | 70 ++++++++++++++++++ .../certs/web.nginx-proxy.key | 27 +++++++ .../test_deleted_cert/docker-compose.yml | 17 +++++ .../test_restart_while_missing_cert.py | 73 +++++++++++++++++++ .../test_deleted_cert/tmp_certs/.gitignore | 2 + 6 files changed, 194 insertions(+) create mode 100644 test/stress_tests/test_deleted_cert/README.md create mode 100644 test/stress_tests/test_deleted_cert/certs/web.nginx-proxy.crt create mode 100644 test/stress_tests/test_deleted_cert/certs/web.nginx-proxy.key create mode 100644 test/stress_tests/test_deleted_cert/docker-compose.yml create mode 100644 test/stress_tests/test_deleted_cert/test_restart_while_missing_cert.py create mode 100644 test/stress_tests/test_deleted_cert/tmp_certs/.gitignore diff --git a/test/stress_tests/test_deleted_cert/README.md b/test/stress_tests/test_deleted_cert/README.md new file mode 100644 index 0000000..9fac0b9 --- /dev/null +++ b/test/stress_tests/test_deleted_cert/README.md @@ -0,0 +1,5 @@ +Test the behavior of nginx-proxy when restarted after deleting a certificate file is was using. + +1. nginx-proxy is created with a virtual host having a certificate +1. while nginx-proxy is running, the certificate file is deleted +1. nginx-proxy is then restarted (without removing the container) diff --git a/test/stress_tests/test_deleted_cert/certs/web.nginx-proxy.crt b/test/stress_tests/test_deleted_cert/certs/web.nginx-proxy.crt new file mode 100644 index 0000000..2c92efe --- /dev/null +++ b/test/stress_tests/test_deleted_cert/certs/web.nginx-proxy.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: Feb 17 23:20:54 2017 GMT + Not After : Jul 5 23:20:54 2044 GMT + Subject: CN=web.nginx-proxy + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (2048 bit) + Modulus: + 00:b6:27:63:a5:c6:e8:f4:7a:94:0e:cc:a2:62:76: + 6d:5d:33:6f:cf:19:fc:e7:e5:bb:0e:0e:d0:7c:4f: + 73:4c:48:2b:17:d1:4d:d5:9f:42:08:73:84:54:8c: + 86:d2:c5:da:59:01:3f:42:22:e0:36:f0:dc:ab:de: + 0a:bd:26:2b:22:13:87:a6:1f:23:ef:0e:99:27:8b: + 15:4a:1b:ef:93:c9:6b:91:de:a0:02:0c:62:bb:cc: + 56:37:e8:25:92:c3:1f:f1:69:d8:7c:a8:33:e0:89: + ce:14:67:a0:39:77:88:91:e6:a3:07:97:90:22:88: + d0:79:18:63:fb:6f:7e:ee:2b:42:7e:23:f5:e7:da: + e9:ee:6a:fa:96:65:9f:e1:2b:15:49:c8:cd:2d:ce: + 86:4f:2c:2a:67:79:bf:41:30:14:cc:f6:0f:14:74: + 9e:b6:d3:d0:3b:f0:1b:b8:e8:19:2a:fd:d6:fd:dc: + 4b:4e:65:7d:9b:bf:37:7e:2d:35:22:2e:74:90:ce: + 41:35:3d:41:a0:99:db:97:1f:bf:3e:18:3c:48:fb: + da:df:c6:4e:4e:b9:67:b8:10:d5:a5:13:03:c4:b7: + 65:e7:aa:f0:14:4b:d3:4d:ea:fe:8f:69:cf:50:21: + 63:27:cf:9e:4c:67:15:7b:3f:3b:da:cb:17:80:61: + 1e:25 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Alternative Name: + DNS:web.nginx-proxy + Signature Algorithm: sha256WithRSAEncryption + 09:31:be:db:4e:b0:b6:68:da:ae:5b:16:51:29:fc:9f:61:b6: + 5a:2f:3c:35:ef:67:76:97:b0:34:4e:3b:b4:d6:88:19:4f:84: + 2e:73:d3:c0:3a:4c:41:54:6c:bb:67:89:67:ad:25:55:d7:d4: + 80:fe:a7:3f:3d:9e:f1:34:96:d8:da:5a:78:51:c0:63:f1:52: + 29:35:55:f4:7d:70:1c:d3:96:62:7f:64:86:81:52:27:c4:c6: + 10:13:c6:73:56:4d:32:d0:b3:c3:c8:2c:25:83:e4:2b:1d:d4: + 74:30:e5:85:af:2d:b6:a5:6b:fe:5d:d3:3c:00:58:94:f4:6a: + f5:a6:1d:cf:f9:ed:d5:27:ed:13:24:b2:4f:2b:f3:b8:e4:af: + 0c:1d:fe:e0:6a:01:5e:a2:44:ff:3e:96:fa:6c:39:a3:51:37: + f3:72:55:d8:2d:29:6e:de:95:b9:d8:e3:1e:65:a5:9c:0d:79: + 2d:39:ab:c7:ac:16:b6:a5:71:4b:35:a4:6c:72:47:1b:72:9c: + 67:58:c1:fc:f6:7f:a7:73:50:7b:d6:27:57:74:a1:31:38:a7: + 31:e3:b9:d4:c9:45:33:ec:ed:16:cf:c5:bd:d0:03:b1:45:3f: + 68:0d:91:5c:26:4e:37:05:74:ed:3e:75:5e:ca:5e:ee:e2:51: + 4b:da:08:99 +-----BEGIN CERTIFICATE----- +MIIC8zCCAdugAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwPzEfMB0GA1UECgwWbmdp +bngtcHJveHkgdGVzdCBzdWl0ZTEcMBoGA1UEAwwTd3d3Lm5naW54LXByb3h5LnRs +ZDAeFw0xNzAyMTcyMzIwNTRaFw00NDA3MDUyMzIwNTRaMBoxGDAWBgNVBAMMD3dl +Yi5uZ2lueC1wcm94eTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALYn +Y6XG6PR6lA7MomJ2bV0zb88Z/Ofluw4O0HxPc0xIKxfRTdWfQghzhFSMhtLF2lkB +P0Ii4Dbw3KveCr0mKyITh6YfI+8OmSeLFUob75PJa5HeoAIMYrvMVjfoJZLDH/Fp +2HyoM+CJzhRnoDl3iJHmoweXkCKI0HkYY/tvfu4rQn4j9efa6e5q+pZln+ErFUnI +zS3Ohk8sKmd5v0EwFMz2DxR0nrbT0DvwG7joGSr91v3cS05lfZu/N34tNSIudJDO +QTU9QaCZ25cfvz4YPEj72t/GTk65Z7gQ1aUTA8S3Zeeq8BRL003q/o9pz1AhYyfP +nkxnFXs/O9rLF4BhHiUCAwEAAaMeMBwwGgYDVR0RBBMwEYIPd2ViLm5naW54LXBy +b3h5MA0GCSqGSIb3DQEBCwUAA4IBAQAJMb7bTrC2aNquWxZRKfyfYbZaLzw172d2 +l7A0Tju01ogZT4Quc9PAOkxBVGy7Z4lnrSVV19SA/qc/PZ7xNJbY2lp4UcBj8VIp +NVX0fXAc05Zif2SGgVInxMYQE8ZzVk0y0LPDyCwlg+QrHdR0MOWFry22pWv+XdM8 +AFiU9Gr1ph3P+e3VJ+0TJLJPK/O45K8MHf7gagFeokT/Ppb6bDmjUTfzclXYLSlu +3pW52OMeZaWcDXktOavHrBa2pXFLNaRsckcbcpxnWMH89n+nc1B71idXdKExOKcx +47nUyUUz7O0Wz8W90AOxRT9oDZFcJk43BXTtPnVeyl7u4lFL2giZ +-----END CERTIFICATE----- diff --git a/test/stress_tests/test_deleted_cert/certs/web.nginx-proxy.key b/test/stress_tests/test_deleted_cert/certs/web.nginx-proxy.key new file mode 100644 index 0000000..dca1c99 --- /dev/null +++ b/test/stress_tests/test_deleted_cert/certs/web.nginx-proxy.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAtidjpcbo9HqUDsyiYnZtXTNvzxn85+W7Dg7QfE9zTEgrF9FN +1Z9CCHOEVIyG0sXaWQE/QiLgNvDcq94KvSYrIhOHph8j7w6ZJ4sVShvvk8lrkd6g +Agxiu8xWN+glksMf8WnYfKgz4InOFGegOXeIkeajB5eQIojQeRhj+29+7itCfiP1 +59rp7mr6lmWf4SsVScjNLc6GTywqZ3m/QTAUzPYPFHSettPQO/AbuOgZKv3W/dxL +TmV9m783fi01Ii50kM5BNT1BoJnblx+/Phg8SPva38ZOTrlnuBDVpRMDxLdl56rw +FEvTTer+j2nPUCFjJ8+eTGcVez872ssXgGEeJQIDAQABAoIBAGQCMFW+ZfyEqHGP +rMA+oUEAkqy0agSwPwky3QjDXlxNa0uCYSeebtTRB6CcHxHuCzm+04puN4gyqhW6 +rU64fAoTivCMPGBuNWxekmvD9r+/YM4P2u4E+th9EgFT9f0kII+dO30FpKXtQzY0 +xuWGWXcxl+T9M+eiEkPKPmq4BoqgTDo5ty7qDv0ZqksGotKFmdYbtSvgBAueJdwu +VWJvenI9F42ExBRKOW1aldiRiaYBCLiCVPKJtOg9iuOP9RHUL1SE8xy5I5mm78g3 +a13ji3BNq3yS+VhGjQ7zDy1V1jGupLoJw4I7OThu8hy+B8Vt8EN/iqakufOkjlTN +xTJ33CkCgYEA5Iymg0NTjWk6aEkFa9pERjfUWqdVp9sWSpFFZZgi55n7LOx6ohi3 +vuLim3is/gYfK2kU/kHGZZLPnT0Rdx0MbOB4XK0CAUlqtUd0IyO4jMZ06g4/kn3N +e2jLdCCIBoEQuLk4ELxj2mHsLQhEvDrg7nzU2WpTHHhvJbIbDWOAxhsCgYEAzAgv +rKpanF+QDf4yeKHxAj2rrwRksTw4Pe7ZK/bog/i+HIVDA70vMapqftHbual/IRrB +JL7hxskoJ/h9c1w4xkWDjqkSKz8/Ihr4dyPfWyGINWbx/rarT/m5MU5SarScoK7o +Xgb25x+W+61rtI+2JhVRGO86+JiAeT4LkAX88L8CgYAwHHug/jdEeXZWJakCfzwI +HBCT1M3vO+uBXvtg25ndb0i0uENIhDOJ93EEkW65Osis9r34mBgPocwaqZRXosHO +2aH8wF6/rpjL+HK2QvrCh7Rs4Pr494qeA/1wQLjhxaGjgToQK9hJTHvPLwJpLWvU +SGr2Ka+9Oo0LPmb7dorRKQKBgQCLsNcjOodLJMp2KiHYIdfmlt6itzlRd09yZ8Nc +rHHJWVagJEUbnD1hnbHIHlp3pSqbObwfMmlWNoc9xo3tm6hrZ1CJLgx4e5b3/Ms8 +ltznge/F0DPDFsH3wZwfu+YFlJ7gDKCfL9l/qEsxCS0CtJobPOEHV1NivNbJK8ey +1ca19QKBgDTdMOUsobAmDEkPQIpxfK1iqYAB7hpRLi79OOhLp23NKeyRNu8FH9fo +G3DZ4xUi6hP2bwiYugMXDyLKfvxbsXwQC84kGF8j+bGazKNhHqEC1OpYwmaTB3kg +qL9cHbjWySeRdIsRY/eWmiKjUwmiO54eAe1HWUdcsuz8yM3xf636 +-----END RSA PRIVATE KEY----- diff --git a/test/stress_tests/test_deleted_cert/docker-compose.yml b/test/stress_tests/test_deleted_cert/docker-compose.yml new file mode 100644 index 0000000..06a61b9 --- /dev/null +++ b/test/stress_tests/test_deleted_cert/docker-compose.yml @@ -0,0 +1,17 @@ +web: + image: web + expose: + - "81" + environment: + WEB_PORTS: 81 + VIRTUAL_HOST: web.nginx-proxy + + +reverseproxy: + image: jwilder/nginx-proxy:test + container_name: reverseproxy + environment: + DEBUG: "true" + volumes: + - /var/run/docker.sock:/tmp/docker.sock:ro + - ./tmp_certs:/etc/nginx/certs:ro \ No newline at end of file diff --git a/test/stress_tests/test_deleted_cert/test_restart_while_missing_cert.py b/test/stress_tests/test_deleted_cert/test_restart_while_missing_cert.py new file mode 100644 index 0000000..2b74acd --- /dev/null +++ b/test/stress_tests/test_deleted_cert/test_restart_while_missing_cert.py @@ -0,0 +1,73 @@ +import logging +import os +from os.path import join, isfile +from shutil import copy +from time import sleep + +import pytest +from requests import ConnectionError + +script_dir = os.path.dirname(__file__) + +pytestmark = pytest.mark.xfail() # TODO delete this marker once those issues are fixed + + +@pytest.yield_fixture(scope="module", autouse=True) +def certs(): + """ + pytest fixture that provides cert and key files into the tmp_certs directory + """ + file_names = ("web.nginx-proxy.crt", "web.nginx-proxy.key") + logging.info("copying server cert and key files into tmp_certs") + for f_name in file_names: + copy(join(script_dir, "certs", f_name), join(script_dir, "tmp_certs")) + yield + logging.info("cleaning up the tmp_cert directory") + for f_name in file_names: + if isfile(join(script_dir, "tmp_certs", f_name)): + os.remove(join(script_dir, "tmp_certs", f_name)) + +############################################################################### + + +def test_unknown_virtual_host_is_503(docker_compose, nginxproxy): + r = nginxproxy.get("http://foo.nginx-proxy/") + assert r.status_code == 503 + + +def test_http_web_is_301(docker_compose, nginxproxy): + r = nginxproxy.get("http://web.nginx-proxy/port", allow_redirects=False) + assert r.status_code == 301 + + +def test_https_web_is_200(docker_compose, nginxproxy): + r = nginxproxy.get("https://web.nginx-proxy/port") + assert r.status_code == 200 + assert 'answer from port 81\n' in r.text + + +@pytest.mark.incremental +def test_delete_cert_and_restart_reverseproxy(docker_compose): + os.remove(join(script_dir, "tmp_certs", "web.nginx-proxy.crt")) + docker_compose.containers.get("reverseproxy").restart() + sleep(3) # give time for the container to initialize + assert "running" == docker_compose.containers.get("reverseproxy").status + + +@pytest.mark.incremental +def test_unknown_virtual_host_is_still_503(nginxproxy): + r = nginxproxy.get("http://foo.nginx-proxy/") + assert r.status_code == 503 + + +@pytest.mark.incremental +def test_http_web_is_now_200(nginxproxy): + r = nginxproxy.get("http://web.nginx-proxy/port", allow_redirects=False) + assert r.status_code == 200 + assert "answer from port 81\n" == r.text + + +@pytest.mark.incremental +def test_https_web_is_now_broken_since_there_is_no_cert(nginxproxy): + with pytest.raises(ConnectionError): + nginxproxy.get("https://web.nginx-proxy/port") diff --git a/test/stress_tests/test_deleted_cert/tmp_certs/.gitignore b/test/stress_tests/test_deleted_cert/tmp_certs/.gitignore new file mode 100644 index 0000000..c96a04f --- /dev/null +++ b/test/stress_tests/test_deleted_cert/tmp_certs/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file From de2f057c10b1c18a323283910c293699e48143db Mon Sep 17 00:00:00 2001 From: Thomas LEVEIL Date: Tue, 21 Feb 2017 01:55:52 +0100 Subject: [PATCH 04/18] TESTS: add test for unreachable container resulting in an empty `upstream {}` block in the generated nginx config file --- .../test_unreachable_network/README.md | 59 +++++++++++++++++++ .../docker-compose.yml | 35 +++++++++++ .../test_unreachable_net.py | 35 +++++++++++ 3 files changed, 129 insertions(+) create mode 100644 test/stress_tests/test_unreachable_network/README.md create mode 100644 test/stress_tests/test_unreachable_network/docker-compose.yml create mode 100644 test/stress_tests/test_unreachable_network/test_unreachable_net.py diff --git a/test/stress_tests/test_unreachable_network/README.md b/test/stress_tests/test_unreachable_network/README.md new file mode 100644 index 0000000..aa09c4d --- /dev/null +++ b/test/stress_tests/test_unreachable_network/README.md @@ -0,0 +1,59 @@ +# nginx-proxy template is not considered when a container is not reachable + +Having a container with the `VIRTUAL_HOST` environment variable set but on a network not reachable from the nginx-proxy container will result in nginx-proxy serving the default nginx welcome page for all requests. + +Furthermore, if the nginx-proxy in such state is restarted, the nginx process will crash and the container stops. + +In the generated nginx config file, we can notice the presence of an empty `upstream {}` block. + +This can be fixed by merging [PR-585](https://github.com/jwilder/nginx-proxy/pull/585). + +## How to reproduce + +1. a first web container is created on network `netA` +1. a second web container is created on network `netB` +1. nginx-proxy is created with access to `netA` only + + +## Erratic behavior + +- nginx serves the default welcome page for all requests to `/` and error 404 for any other path +- nginx-container crash on restart + +Log shows: + +``` +webB_1 | starting a web server listening on port 82 +webA_1 | starting a web server listening on port 81 +reverseproxy | forego | starting dockergen.1 on port 5000 +reverseproxy | forego | starting nginx.1 on port 5100 +reverseproxy | dockergen.1 | 2017/02/20 01:10:24 Generated '/etc/nginx/conf.d/default.conf' from 3 containers +reverseproxy | dockergen.1 | 2017/02/20 01:10:24 Running 'nginx -s reload' +reverseproxy | dockergen.1 | 2017/02/20 01:10:24 Error running notify command: nginx -s reload, exit status 1 +reverseproxy | dockergen.1 | 2017/02/20 01:10:24 Watching docker events +reverseproxy | dockergen.1 | 2017/02/20 01:10:24 Contents of /etc/nginx/conf.d/default.conf did not change. Skipping notification 'nginx -s reload' +reverseproxy | reverseproxy | forego | starting dockergen.1 on port 5000 <---- nginx-proxy container restarted +reverseproxy | forego | starting nginx.1 on port 5100 +reverseproxy | dockergen.1 | 2017/02/20 01:10:24 Generated '/etc/nginx/conf.d/default.conf' from 3 containers +reverseproxy | dockergen.1 | 2017/02/20 01:10:24 Running 'nginx -s reload' +reverseproxy | dockergen.1 | 2017/02/20 01:10:24 Error running notify command: nginx -s reload, exit status 1 +reverseproxy | dockergen.1 | 2017/02/20 01:10:24 Watching docker events +reverseproxy | dockergen.1 | 2017/02/20 01:10:24 Contents of /etc/nginx/conf.d/default.conf did not change. Skipping notification 'nginx -s reload' +reverseproxy | forego | starting dockergen.1 on port 5000 +reverseproxy | forego | starting nginx.1 on port 5100 +reverseproxy | nginx.1 | 2017/02/20 01:11:02 [emerg] 17#17: no servers are inside upstream in /etc/nginx/conf.d/default.conf:64 +reverseproxy | forego | starting nginx.1 on port 5200 +reverseproxy | forego | sending SIGTERM to nginx.1 +reverseproxy | forego | sending SIGTERM to dockergen.1 +reverseproxy exited with code 0 +reverseproxy exited with code 0 + +``` + +## Expected behavior + +- no default nginx welcome page should be served +- nginx is able to forward requests to containers of `netA` +- nginx respond with error 503 for unknown virtual hosts +- nginx is not able to forward requests to containers of `netB` and responds with an error +- nginx should survive restarts diff --git a/test/stress_tests/test_unreachable_network/docker-compose.yml b/test/stress_tests/test_unreachable_network/docker-compose.yml new file mode 100644 index 0000000..0ca4f99 --- /dev/null +++ b/test/stress_tests/test_unreachable_network/docker-compose.yml @@ -0,0 +1,35 @@ +version: "2" + +networks: + netA: + netB: + +services: + reverseproxy: + container_name: reverseproxy + networks: + - netA + image: jwilder/nginx-proxy:test + volumes: + - /var/run/docker.sock:/tmp/docker.sock:ro + + webA: + networks: + - netA + image: web + expose: + - 81 + environment: + WEB_PORTS: 81 + VIRTUAL_HOST: webA.nginx-proxy + + webB: + networks: + - netB + image: web + expose: + - 82 + environment: + WEB_PORTS: 82 + VIRTUAL_HOST: webB.nginx-proxy + diff --git a/test/stress_tests/test_unreachable_network/test_unreachable_net.py b/test/stress_tests/test_unreachable_network/test_unreachable_net.py new file mode 100644 index 0000000..dbcfb14 --- /dev/null +++ b/test/stress_tests/test_unreachable_network/test_unreachable_net.py @@ -0,0 +1,35 @@ +from time import sleep + +import pytest +import requests + +pytestmark = pytest.mark.xfail() # TODO delete this marker once #585 is merged + + +def test_default_nginx_welcome_page_should_not_be_served(docker_compose, nginxproxy): + r = nginxproxy.get("http://whatever.nginx-proxy/", allow_redirects=False) + assert "Welcome to nginx!" not in r.text + + +def test_unknown_virtual_host_is_503(docker_compose, nginxproxy): + r = nginxproxy.get("http://unknown.nginx-proxy/", allow_redirects=False) + assert r.status_code == 503 + + +def test_http_web_a_is_forwarded(docker_compose, nginxproxy): + r = nginxproxy.get("http://webA.nginx-proxy/port", allow_redirects=False) + assert r.status_code == 200 + assert "answer from port 81\n" == r.text + + +def test_http_web_b_gets_an_error(docker_compose, nginxproxy): + r = nginxproxy.get("http://webB.nginx-proxy/", allow_redirects=False) + assert "Welcome to nginx!" not in r.text + with pytest.raises(requests.exceptions.HTTPError): + r.raise_for_status() + + +def test_reverseproxy_survive_restart(docker_compose): + docker_compose.containers.get("reverseproxy").restart() + sleep(2) # give time for the container to initialize + assert docker_compose.containers.get("reverseproxy").status == "running" From 6e9dc343cdcd3fd3e4091bac4a4eb5c542c549fb Mon Sep 17 00:00:00 2001 From: Sy Doveton Date: Sun, 19 Nov 2017 11:35:30 +0000 Subject: [PATCH 05/18] Changed the SSL stapling cert extension to pem from crt. SSL stapling was not working due to the incorrect file extension. --- nginx.tmpl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nginx.tmpl b/nginx.tmpl index 5147fee..ea6bec5 100644 --- a/nginx.tmpl +++ b/nginx.tmpl @@ -222,10 +222,10 @@ server { ssl_dhparam {{ printf "/etc/nginx/certs/%s.dhparam.pem" $cert }}; {{ end }} - {{ if (exists (printf "/etc/nginx/certs/%s.chain.crt" $cert)) }} + {{ if (exists (printf "/etc/nginx/certs/%s.chain.pem" $cert)) }} ssl_stapling on; ssl_stapling_verify on; - ssl_trusted_certificate {{ printf "/etc/nginx/certs/%s.chain.crt" $cert }}; + ssl_trusted_certificate {{ printf "/etc/nginx/certs/%s.chain.pem" $cert }}; {{ end }} {{ if (and (ne $https_method "noredirect") (ne $hsts "off")) }} From 32d42ffee720eb4bd848a00b46b630429c40516d Mon Sep 17 00:00:00 2001 From: Jason Wilder Date: Sun, 14 Jan 2018 15:28:46 -0700 Subject: [PATCH 06/18] Update docker-gen to 0.7.4 --- Dockerfile | 2 +- Dockerfile.alpine | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 149fb90..60f3b5b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,7 +18,7 @@ RUN echo "daemon off;" >> /etc/nginx/nginx.conf \ ADD https://github.com/jwilder/forego/releases/download/v0.16.1/forego /usr/local/bin/forego RUN chmod u+x /usr/local/bin/forego -ENV DOCKER_GEN_VERSION 0.7.3 +ENV DOCKER_GEN_VERSION 0.7.4 RUN wget https://github.com/jwilder/docker-gen/releases/download/$DOCKER_GEN_VERSION/docker-gen-linux-amd64-$DOCKER_GEN_VERSION.tar.gz \ && tar -C /usr/local/bin -xvzf docker-gen-linux-amd64-$DOCKER_GEN_VERSION.tar.gz \ diff --git a/Dockerfile.alpine b/Dockerfile.alpine index fce6aae..7089fd8 100644 --- a/Dockerfile.alpine +++ b/Dockerfile.alpine @@ -15,7 +15,7 @@ RUN echo "daemon off;" >> /etc/nginx/nginx.conf \ ADD https://github.com/jwilder/forego/releases/download/v0.16.1/forego /usr/local/bin/forego RUN chmod u+x /usr/local/bin/forego -ENV DOCKER_GEN_VERSION 0.7.3 +ENV DOCKER_GEN_VERSION 0.7.4 RUN wget --quiet https://github.com/jwilder/docker-gen/releases/download/$DOCKER_GEN_VERSION/docker-gen-alpine-linux-amd64-$DOCKER_GEN_VERSION.tar.gz \ && tar -C /usr/local/bin -xvzf docker-gen-alpine-linux-amd64-$DOCKER_GEN_VERSION.tar.gz \ From 7a769a6a22ac2ea28d084c11bb25b48881234b28 Mon Sep 17 00:00:00 2001 From: b1f6c1c4 Date: Tue, 20 Feb 2018 17:59:52 +0800 Subject: [PATCH 07/18] Add HSTS header regardless of status code See nginx [doc](http://nginx.org/en/docs/http/ngx_http_headers_module.html#add_header) and [blog](https://www.nginx.com/blog/http-strict-transport-security-hsts-and-nginx/). --- nginx.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nginx.tmpl b/nginx.tmpl index 726c74b..bbb9b37 100644 --- a/nginx.tmpl +++ b/nginx.tmpl @@ -260,7 +260,7 @@ server { {{ end }} {{ if (and (ne $https_method "noredirect") (ne $hsts "off")) }} - add_header Strict-Transport-Security "{{ trim $hsts }}"; + add_header Strict-Transport-Security "{{ trim $hsts }}" always; {{ end }} {{ if (exists (printf "/etc/nginx/vhost.d/%s" $host)) }} From 2f8ebe8d45af63e445c7266068d235e04460239b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20D=C3=B6ring?= Date: Wed, 7 Mar 2018 22:36:05 +0100 Subject: [PATCH 08/18] Enable NETWORK_ACCESS feature for alpine version This PR fixes a missing line in the alpine version. - Fixes #1076 - See #842 --- Dockerfile.alpine | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Dockerfile.alpine b/Dockerfile.alpine index fce6aae..18a486c 100644 --- a/Dockerfile.alpine +++ b/Dockerfile.alpine @@ -21,6 +21,8 @@ RUN wget --quiet https://github.com/jwilder/docker-gen/releases/download/$DOCKER && tar -C /usr/local/bin -xvzf docker-gen-alpine-linux-amd64-$DOCKER_GEN_VERSION.tar.gz \ && rm /docker-gen-alpine-linux-amd64-$DOCKER_GEN_VERSION.tar.gz +COPY network_internal.conf /etc/nginx/ + COPY . /app/ WORKDIR /app/ From 37714fa4f873fa7d0ca3e67d17bd4a4f4cd31396 Mon Sep 17 00:00:00 2001 From: Sergei Filippov Date: Fri, 9 Mar 2018 10:48:14 +1300 Subject: [PATCH 09/18] Grammar Police Tiny grammatical fix. --- nginx.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nginx.tmpl b/nginx.tmpl index 726c74b..6bf19d5 100644 --- a/nginx.tmpl +++ b/nginx.tmpl @@ -130,7 +130,7 @@ upstream {{ $upstream_name }} { {{ range $knownNetwork := $CurrentContainer.Networks }} {{ range $containerNetwork := $container.Networks }} {{ if (and (ne $containerNetwork.Name "ingress") (or (eq $knownNetwork.Name $containerNetwork.Name) (eq $knownNetwork.Name "host"))) }} - ## Can be connect with "{{ $containerNetwork.Name }}" network + ## Can be connected with "{{ $containerNetwork.Name }}" network {{/* If only 1 port exposed, use that */}} {{ if eq $addrLen 1 }} From b61c84192960d937a5e5b517264efe9653f8f899 Mon Sep 17 00:00:00 2001 From: Harald Wellmann Date: Thu, 22 Mar 2018 10:56:41 +0100 Subject: [PATCH 10/18] do not create an empty upstream entry for a container from an invisible Docker network --- nginx.tmpl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/nginx.tmpl b/nginx.tmpl index 6bf19d5..1d1d4b6 100644 --- a/nginx.tmpl +++ b/nginx.tmpl @@ -142,6 +142,9 @@ upstream {{ $upstream_name }} { {{ $address := where $container.Addresses "Port" $port | first }} {{ template "upstream" (dict "Container" $container "Address" $address "Network" $containerNetwork) }} {{ end }} + {{ else }} + # Cannot connect to network of this container + server 127.0.0.1 down; {{ end }} {{ end }} {{ end }} From 5266553e1bcb8bdcf47218d8a3c3d95e3272595c Mon Sep 17 00:00:00 2001 From: Jason Wilder Date: Fri, 23 Mar 2018 21:07:43 -0600 Subject: [PATCH 11/18] Add issue template/q&a links --- .github/ISSUE_TEMPLATE.md | 14 ++++++++++++++ README.md | 4 ++++ 2 files changed, 18 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE.md diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000..cfaa367 --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,14 @@ +# !!!PLEASE READ!!! + +## Questions + +If you have a question, DO NOT SUBMIT a new issue. Please ask the question on the Q&A Group: https://groups.google.com/forum/#!forum/nginx-proxy + +## Bugs or Features + +If you are logging a bug or feature request, please search the current open issues to see if there is already a bug or feature opened. + +For bugs, the easier you make it to reproduce the issue you see, the easier and faster it can get fixed. If you can provide a script or docker-compose file that reproduces the problems, that is very helpful. + +Thanks, +Jason diff --git a/README.md b/README.md index c44bf80..72185c4 100644 --- a/README.md +++ b/README.md @@ -416,3 +416,7 @@ If your system has the `make` command, you can automate those tasks by calling: You can learn more about how the test suite works and how to write new tests in the [test/README.md](test/README.md) file. + +### Need help? + +If you have questions on how to use the image, please ask them on the [Q&A Group](https://groups.google.com/forum/#!forum/nginx-proxy) From f8cd4483aca804ef0608d3b0a3b057f213d9518e Mon Sep 17 00:00:00 2001 From: Jason Wilder Date: Sun, 14 Jan 2018 15:29:16 -0700 Subject: [PATCH 12/18] Update version to 0.7.0 --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index c44bf80..d44ec82 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ +![latest 0.7.0](https://img.shields.io/badge/latest-0.7.0-green.svg?style=flat) ![nginx 1.13](https://img.shields.io/badge/nginx-1.13-brightgreen.svg) ![License MIT](https://img.shields.io/badge/license-MIT-blue.svg) [![Build Status](https://travis-ci.org/jwilder/nginx-proxy.svg?branch=master)](https://travis-ci.org/jwilder/nginx-proxy) [![](https://img.shields.io/docker/stars/jwilder/nginx-proxy.svg)](https://hub.docker.com/r/jwilder/nginx-proxy 'DockerHub') [![](https://img.shields.io/docker/pulls/jwilder/nginx-proxy.svg)](https://hub.docker.com/r/jwilder/nginx-proxy 'DockerHub') From c1ae91364c25a9722f19e66e5360eddf61642b8a Mon Sep 17 00:00:00 2001 From: Steve Kamerman Date: Mon, 26 Mar 2018 14:57:50 -0400 Subject: [PATCH 13/18] Added endpoint to allow testing alternate response codes --- test/requirements/web/webserver.py | 37 ++++++++++++++++++------------ 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/test/requirements/web/webserver.py b/test/requirements/web/webserver.py index 305c207..9334657 100755 --- a/test/requirements/web/webserver.py +++ b/test/requirements/web/webserver.py @@ -1,28 +1,35 @@ #!/usr/bin/env python3 -import os, sys +import os, sys, re import http.server import socketserver - class Handler(http.server.SimpleHTTPRequestHandler): def do_GET(self): - - self.send_response(200) + + response_body = "" + response_code = 200 + + if self.path == "/headers": + response_body += self.headers.as_string() + elif self.path == "/port": + response_body += "answer from port %s\n" % PORT + elif re.match("/status/(\d+)", self.path): + result = re.match("/status/(\d+)", self.path) + response_code = int(result.group(1)) + response_body += "answer with response code %s\n" % response_code + elif self.path == "/": + response_body += "I'm %s\n" % os.environ['HOSTNAME'] + else: + response_body += "No route for this path!\n" + response_code = 404 + + self.send_response(response_code) 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()) - elif self.path == "/": - response = "I'm %s\n" % os.environ['HOSTNAME'] - self.wfile.write(response.encode()) - else: - self.wfile.write("No route for this path!\n".encode()) - + if (len(response_body)): + self.wfile.write(response_body.encode()) if __name__ == '__main__': PORT = int(sys.argv[1]) From 3590c1bae00df9a00f0d258282668b36d0380eef Mon Sep 17 00:00:00 2001 From: Steve Kamerman Date: Mon, 26 Mar 2018 14:58:06 -0400 Subject: [PATCH 14/18] Added regression test to ensure HSTS works for errors --- test/test_ssl/test_hsts.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/test_ssl/test_hsts.py b/test/test_ssl/test_hsts.py index 180f274..554d79a 100644 --- a/test/test_ssl/test_hsts.py +++ b/test/test_ssl/test_hsts.py @@ -7,6 +7,13 @@ def test_web1_HSTS_default(docker_compose, nginxproxy): assert "Strict-Transport-Security" in r.headers assert "max-age=31536000" == r.headers["Strict-Transport-Security"] +# Regression test to ensure HSTS is enabled even when the upstream sends an error in response +# Issue #1073 https://github.com/jwilder/nginx-proxy/pull/1073 +def test_web1_HSTS_error(docker_compose, nginxproxy): + r = nginxproxy.get("https://web1.nginx-proxy.tld/status/500", allow_redirects=False) + assert "Strict-Transport-Security" in r.headers + assert "max-age=31536000" == r.headers["Strict-Transport-Security"] + def test_web2_HSTS_off(docker_compose, nginxproxy): r = nginxproxy.get("https://web2.nginx-proxy.tld/port", allow_redirects=False) assert "answer from port 81\n" in r.text From d7e939dc27ce5feeca3a9efce0e94264f6b7db52 Mon Sep 17 00:00:00 2001 From: Steve Kamerman Date: Wed, 28 Mar 2018 11:43:41 -0400 Subject: [PATCH 15/18] Added info on enabling OCSP Stapling --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index 18d65bc..054b4d1 100644 --- a/README.md +++ b/README.md @@ -237,6 +237,15 @@ to identify the certificate to be used. For example, a certificate for `*.foo.c could be named `shared.crt` and `shared.key`. A container running with `VIRTUAL_HOST=foo.bar.com` and `CERT_NAME=shared` will then use this shared cert. +#### OCSP Stapling +To enable OCSP Stapling for a domain, `nginx-proxy` looks for a PEM certificate containing the trusted +CA certificate chain at `/etc/nginx/certs/.chain.pem`, where `` is the domain name in +the `VIRTUAL_HOST` directive. The format of this file is a concatenation of the public PEM CA +certificates starting with the intermediate CA most near the SSL certificate, down to the root CA. This is +often referred to as the "SSL Certificate Chain". If found, this filename is passed to the NGINX +[`ssl_trusted_certificate` directive](http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_trusted_certificate) +and OCSP Stapling is enabled. + #### How SSL Support Works The default SSL cipher configuration is based on the [Mozilla intermediate profile](https://wiki.mozilla.org/Security/Server_Side_TLS#Intermediate_compatibility_.28default.29) which From c417813df916789fa29a83c3e31f196777e718d3 Mon Sep 17 00:00:00 2001 From: Steve Kamerman Date: Sun, 22 Apr 2018 16:03:43 -0400 Subject: [PATCH 16/18] Fixed out-of-scope variable --- test/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/conftest.py b/test/conftest.py index 43f83bb..54c3b08 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -257,7 +257,7 @@ def get_nginx_conf_from_container(container): strm, stat = container.get_archive('/etc/nginx/conf.d/default.conf') with tarfile.open(fileobj=StringIO(strm.read())) as tf: conffile = tf.extractfile('default.conf') - return conffile.read() + return conffile.read() def docker_compose_up(compose_file='docker-compose.yml'): From 9be2624d094a16b9a1d648888f29264713da48fd Mon Sep 17 00:00:00 2001 From: Steve Kamerman Date: Sun, 22 Apr 2018 16:08:29 -0400 Subject: [PATCH 17/18] Increased dependency versions to get around pip internal problem --- test/conftest.py | 4 ++-- test/requirements/python-requirements.txt | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/test/conftest.py b/test/conftest.py index 54c3b08..6bd172a 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -469,5 +469,5 @@ try: except docker.errors.ImageNotFound: pytest.exit("The docker image 'jwilder/nginx-proxy:test' is missing") -if docker.__version__ != "2.0.2": - pytest.exit("This test suite is meant to work with the python docker module v2.0.2") +if docker.__version__ != "2.1.0": + pytest.exit("This test suite is meant to work with the python docker module v2.1.0") diff --git a/test/requirements/python-requirements.txt b/test/requirements/python-requirements.txt index e868e14..ba95455 100644 --- a/test/requirements/python-requirements.txt +++ b/test/requirements/python-requirements.txt @@ -1,5 +1,5 @@ backoff==1.3.2 -docker-compose==1.11.1 -docker==2.0.2 +docker-compose==1.11.2 +docker==2.1.0 pytest==3.0.5 -requests==2.11.1 \ No newline at end of file +requests==2.11.1 From af266c0b83e1b769fd9d90a3fa34a982fec051b2 Mon Sep 17 00:00:00 2001 From: Steve Kamerman Date: Sun, 22 Apr 2018 16:43:00 -0400 Subject: [PATCH 18/18] Remove old docker.list to avoid getting unstable Docker version --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 268e275..7a1c66f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,8 @@ env: - TEST_TARGET: test-alpine before_install: - - sudo apt-get remove docker docker-engine + - sudo apt-get -y remove docker docker-engine docker-ce + - sudo rm /etc/apt/sources.list.d/docker.list - curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - - sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" - sudo apt-get update