Merge branch 'master' of https://github.com/jwilder/nginx-proxy
This commit is contained in:
commit
db468e1c1b
20 changed files with 412 additions and 28 deletions
14
.github/ISSUE_TEMPLATE.md
vendored
Normal file
14
.github/ISSUE_TEMPLATE.md
vendored
Normal file
|
@ -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
|
|
@ -7,7 +7,8 @@ env:
|
||||||
- TEST_TARGET: test-alpine
|
- TEST_TARGET: test-alpine
|
||||||
|
|
||||||
before_install:
|
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 -
|
- 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 add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
|
||||||
- sudo apt-get update
|
- sudo apt-get update
|
||||||
|
|
|
@ -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
|
ADD https://github.com/jwilder/forego/releases/download/v0.16.1/forego /usr/local/bin/forego
|
||||||
RUN chmod u+x /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 \
|
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 \
|
&& tar -C /usr/local/bin -xvzf docker-gen-linux-amd64-$DOCKER_GEN_VERSION.tar.gz \
|
||||||
|
|
|
@ -15,12 +15,14 @@ RUN echo "daemon off;" >> /etc/nginx/nginx.conf \
|
||||||
ADD https://github.com/jwilder/forego/releases/download/v0.16.1/forego /usr/local/bin/forego
|
ADD https://github.com/jwilder/forego/releases/download/v0.16.1/forego /usr/local/bin/forego
|
||||||
RUN chmod u+x /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 \
|
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 \
|
&& 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
|
&& rm /docker-gen-alpine-linux-amd64-$DOCKER_GEN_VERSION.tar.gz
|
||||||
|
|
||||||
|
COPY network_internal.conf /etc/nginx/
|
||||||
|
|
||||||
COPY . /app/
|
COPY . /app/
|
||||||
WORKDIR /app/
|
WORKDIR /app/
|
||||||
|
|
||||||
|
|
14
README.md
14
README.md
|
@ -1,3 +1,4 @@
|
||||||
|

|
||||||
  [](https://travis-ci.org/jwilder/nginx-proxy) [](https://hub.docker.com/r/jwilder/nginx-proxy 'DockerHub') [](https://hub.docker.com/r/jwilder/nginx-proxy 'DockerHub')
|
  [](https://travis-ci.org/jwilder/nginx-proxy) [](https://hub.docker.com/r/jwilder/nginx-proxy 'DockerHub') [](https://hub.docker.com/r/jwilder/nginx-proxy 'DockerHub')
|
||||||
|
|
||||||
|
|
||||||
|
@ -245,6 +246,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`
|
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.
|
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/<domain>.chain.pem`, where `<domain>` 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
|
#### 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
|
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
|
||||||
|
@ -425,3 +435,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.
|
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)
|
||||||
|
|
11
nginx.tmpl
11
nginx.tmpl
|
@ -130,7 +130,7 @@ upstream {{ $upstream_name }} {
|
||||||
{{ range $knownNetwork := $CurrentContainer.Networks }}
|
{{ range $knownNetwork := $CurrentContainer.Networks }}
|
||||||
{{ range $containerNetwork := $container.Networks }}
|
{{ range $containerNetwork := $container.Networks }}
|
||||||
{{ if (and (ne $containerNetwork.Name "ingress") (or (eq $knownNetwork.Name $containerNetwork.Name) (eq $knownNetwork.Name "host"))) }}
|
{{ 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 only 1 port exposed, use that */}}
|
||||||
{{ if eq $addrLen 1 }}
|
{{ if eq $addrLen 1 }}
|
||||||
|
@ -142,6 +142,9 @@ upstream {{ $upstream_name }} {
|
||||||
{{ $address := where $container.Addresses "Port" $port | first }}
|
{{ $address := where $container.Addresses "Port" $port | first }}
|
||||||
{{ template "upstream" (dict "Container" $container "Address" $address "Network" $containerNetwork) }}
|
{{ template "upstream" (dict "Container" $container "Address" $address "Network" $containerNetwork) }}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
{{ else }}
|
||||||
|
# Cannot connect to network of this container
|
||||||
|
server 127.0.0.1 down;
|
||||||
{{ end }}
|
{{ end }}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
@ -253,14 +256,14 @@ server {
|
||||||
ssl_dhparam {{ printf "/etc/nginx/certs/%s.dhparam.pem" $cert }};
|
ssl_dhparam {{ printf "/etc/nginx/certs/%s.dhparam.pem" $cert }};
|
||||||
{{ end }}
|
{{ 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 on;
|
||||||
ssl_stapling_verify 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 }}
|
{{ end }}
|
||||||
|
|
||||||
{{ if (and (ne $https_method "noredirect") (ne $hsts "off")) }}
|
{{ 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 }}
|
{{ end }}
|
||||||
|
|
||||||
{{ if (exists (printf "/etc/nginx/vhost.d/%s" $host)) }}
|
{{ if (exists (printf "/etc/nginx/vhost.d/%s" $host)) }}
|
||||||
|
|
|
@ -257,7 +257,7 @@ def get_nginx_conf_from_container(container):
|
||||||
strm, stat = container.get_archive('/etc/nginx/conf.d/default.conf')
|
strm, stat = container.get_archive('/etc/nginx/conf.d/default.conf')
|
||||||
with tarfile.open(fileobj=StringIO(strm.read())) as tf:
|
with tarfile.open(fileobj=StringIO(strm.read())) as tf:
|
||||||
conffile = tf.extractfile('default.conf')
|
conffile = tf.extractfile('default.conf')
|
||||||
return conffile.read()
|
return conffile.read()
|
||||||
|
|
||||||
|
|
||||||
def docker_compose_up(compose_file='docker-compose.yml'):
|
def docker_compose_up(compose_file='docker-compose.yml'):
|
||||||
|
@ -445,6 +445,18 @@ def pytest_runtest_logreport(report):
|
||||||
report.longrepr.addsection('nginx-proxy conf', get_nginx_conf_from_container(container))
|
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)
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
#
|
#
|
||||||
|
@ -457,5 +469,5 @@ try:
|
||||||
except docker.errors.ImageNotFound:
|
except docker.errors.ImageNotFound:
|
||||||
pytest.exit("The docker image 'jwilder/nginx-proxy:test' is missing")
|
pytest.exit("The docker image 'jwilder/nginx-proxy:test' is missing")
|
||||||
|
|
||||||
if docker.__version__ != "2.0.2":
|
if docker.__version__ != "2.1.0":
|
||||||
pytest.exit("This test suite is meant to work with the python docker module v2.0.2")
|
pytest.exit("This test suite is meant to work with the python docker module v2.1.0")
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
backoff==1.3.2
|
backoff==1.3.2
|
||||||
docker-compose==1.11.1
|
docker-compose==1.11.2
|
||||||
docker==2.0.2
|
docker==2.1.0
|
||||||
pytest==3.0.5
|
pytest==3.0.5
|
||||||
requests==2.11.1
|
requests==2.11.1
|
|
@ -1,28 +1,35 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import os, sys
|
import os, sys, re
|
||||||
import http.server
|
import http.server
|
||||||
import socketserver
|
import socketserver
|
||||||
|
|
||||||
|
|
||||||
class Handler(http.server.SimpleHTTPRequestHandler):
|
class Handler(http.server.SimpleHTTPRequestHandler):
|
||||||
def do_GET(self):
|
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.send_header("Content-Type", "text/plain")
|
||||||
self.end_headers()
|
self.end_headers()
|
||||||
|
|
||||||
if self.path == "/headers":
|
if (len(response_body)):
|
||||||
self.wfile.write(self.headers.as_string().encode())
|
self.wfile.write(response_body.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 __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
PORT = int(sys.argv[1])
|
PORT = int(sys.argv[1])
|
||||||
|
|
1
test/stress_tests/README.md
Normal file
1
test/stress_tests/README.md
Normal file
|
@ -0,0 +1 @@
|
||||||
|
This directory contains tests that showcase scenarios known to break the expected behavior of nginx-proxy.
|
5
test/stress_tests/test_deleted_cert/README.md
Normal file
5
test/stress_tests/test_deleted_cert/README.md
Normal file
|
@ -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)
|
|
@ -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-----
|
|
@ -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-----
|
17
test/stress_tests/test_deleted_cert/docker-compose.yml
Normal file
17
test/stress_tests/test_deleted_cert/docker-compose.yml
Normal file
|
@ -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
|
|
@ -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")
|
2
test/stress_tests/test_deleted_cert/tmp_certs/.gitignore
vendored
Normal file
2
test/stress_tests/test_deleted_cert/tmp_certs/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
*
|
||||||
|
!.gitignore
|
59
test/stress_tests/test_unreachable_network/README.md
Normal file
59
test/stress_tests/test_unreachable_network/README.md
Normal file
|
@ -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
|
|
@ -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
|
||||||
|
|
|
@ -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 "<title>Welcome to nginx!</title>" 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 "<title>Welcome to nginx!</title>" 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"
|
|
@ -7,6 +7,13 @@ def test_web1_HSTS_default(docker_compose, nginxproxy):
|
||||||
assert "Strict-Transport-Security" in r.headers
|
assert "Strict-Transport-Security" in r.headers
|
||||||
assert "max-age=31536000" == r.headers["Strict-Transport-Security"]
|
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):
|
def test_web2_HSTS_off(docker_compose, nginxproxy):
|
||||||
r = nginxproxy.get("https://web2.nginx-proxy.tld/port", allow_redirects=False)
|
r = nginxproxy.get("https://web2.nginx-proxy.tld/port", allow_redirects=False)
|
||||||
assert "answer from port 81\n" in r.text
|
assert "answer from port 81\n" in r.text
|
||||||
|
|
Loading…
Reference in a new issue