Steve Kamerman 8 anni fa
parent
commit
f186815c2d

+ 9 - 3
.travis.yml

@@ -1,16 +1,17 @@
+dist: trusty
 sudo: required
 services:
   - docker
 
 env:
     global:
-        - DOCKER_VERSION=1.12.1-0~trusty
+        - DOCKER_VERSION=1.12.3-0~trusty
 
 before_install:
   # list docker-engine versions
   - apt-cache madison docker-engine
   # upgrade docker-engine to specific version
-  - sudo apt-get -o Dpkg::Options::="--force-confnew" install -y docker-engine=${DOCKER_VERSION}
+  - sudo apt-get -o Dpkg::Options::="--force-confnew" install -y --force-yes docker-engine=${DOCKER_VERSION}
   - docker version
   - docker info
   - sudo add-apt-repository ppa:duggan/bats --yes
@@ -18,5 +19,10 @@ before_install:
   - sudo apt-get install -qq bats
   - make update-dependencies
 
+matrix:
+  include:
+    - env: TEST_ID=test-debian
+    - env: TEST_ID=test-alpine
+
 script:
-  - make test
+  - make $TEST_ID

+ 1 - 1
Dockerfile

@@ -1,4 +1,4 @@
-FROM nginx:1.11.3
+FROM nginx:1.11.8
 MAINTAINER Jason Wilder mail@jasonwilder.com
 
 # Install wget and install/updates certificates

+ 31 - 0
Dockerfile.alpine

@@ -0,0 +1,31 @@
+FROM nginx:1.11.8-alpine
+MAINTAINER Jason Wilder mail@jasonwilder.com
+
+# Install wget and install/updates certificates
+RUN apk add --no-cache --virtual .run-deps \
+    ca-certificates bash wget \
+    && update-ca-certificates
+
+# Configure Nginx and apply fix for very long server names
+RUN echo "daemon off;" >> /etc/nginx/nginx.conf \
+ && sed -i 's/^http {/&\n    server_names_hash_bucket_size 128;/g' /etc/nginx/nginx.conf
+
+# Install 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
+
+ENV DOCKER_GEN_VERSION 0.7.3
+
+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 \
+ && rm /docker-gen-alpine-linux-amd64-$DOCKER_GEN_VERSION.tar.gz
+
+COPY . /app/
+WORKDIR /app/
+
+ENV DOCKER_HOST unix:///tmp/docker.sock
+
+VOLUME ["/etc/nginx/certs"]
+
+ENTRYPOINT ["/app/docker-entrypoint.sh"]
+CMD ["forego", "start", "-r"]

+ 9 - 2
Makefile

@@ -3,12 +3,19 @@
 
 update-dependencies:
 	docker pull jwilder/docker-gen:0.7.3
-	docker pull nginx:1.11.3
+	docker pull nginx:1.11.6
+	docker pull nginx:1.11.8-alpine
 	docker pull python:3
 	docker pull rancher/socat-docker:latest
 	docker pull appropriate/curl:latest
 	docker pull docker:1.10
 
-test:
+test-debian:
 	docker build -t jwilder/nginx-proxy:bats .
 	bats test
+
+test-alpine:
+	docker build -f Dockerfile.alpine -t jwilder/nginx-proxy:bats .
+	bats test
+
+test: test-debian test-alpine

+ 11 - 2
README.md

@@ -1,4 +1,4 @@
-![nginx 1.11.3](https://img.shields.io/badge/nginx-1.11.3-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')
+![nginx 1.11.8](https://img.shields.io/badge/nginx-1.11.8-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')
 
 
 nginx-proxy sets up a container running nginx and [docker-gen][1].  docker-gen generates reverse proxy configs for nginx and reloads nginx when containers are started and stopped.
@@ -76,7 +76,7 @@ In this example, the `my-nginx-proxy` container will be connected to `my-network
 
 ### SSL Backends
 
-If you would like to connect to your backend using HTTPS instead of HTTP, set `VIRTUAL_PROTO=https` on the backend container.
+If you would like the reverse proxy to connect to your backend using HTTPS instead of HTTP, set `VIRTUAL_PROTO=https` on the backend container.
 
 > Note: If you use `VIRTUAL_PROTO=https` and your backend container exposes port 80 and 443, `nginx-proxy` will use HTTPS on port 80.  This is almost certainly not what you want, so you should also include `VIRTUAL_PORT=443`.
 
@@ -127,6 +127,9 @@ $ docker run --volumes-from nginx \
 Finally, start your containers with `VIRTUAL_HOST` environment variables.
 
     $ docker run -e VIRTUAL_HOST=foo.bar.com  ...
+### SSL Support using letsencrypt
+
+[letsencrypt-nginx-proxy-companion](https://github.com/JrCs/docker-letsencrypt-nginx-proxy-companion)is a lightweight companion container for the nginx-proxy. It allow the creation/renewal of Let's Encrypt certificates automatically. 
 
 ### SSL Support
 
@@ -142,6 +145,10 @@ hosts in use.  The certificate and keys should be named after the virtual host w
 `.key` extension.  For example, a container with `VIRTUAL_HOST=foo.bar.com` should have a
 `foo.bar.com.crt` and `foo.bar.com.key` file in the certs directory.
 
+If you are running the container in a virtualized environment (Hyper-V, VirtualBox, etc...),
+/path/to/certs must exist in that environment or be made accessible to that environment.
+By default, Docker is not able to mount directories on the host machine to containers running in a virtual machine.
+
 #### Diffie-Hellman Groups
 
 Diffie-Hellman groups are enabled by default, with a pregenerated key in `/etc/nginx/dhparam.pem`.
@@ -226,6 +233,8 @@ proxy_set_header Connection $proxy_connection;
 proxy_set_header X-Real-IP $remote_addr;
 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
 proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
+proxy_set_header X-Forwarded-Ssl $proxy_x_forwarded_ssl;
+proxy_set_header X-Forwarded-Port $proxy_x_forwarded_port;
 
 # Mitigate httpoxy attack (see README for details)
 proxy_set_header Proxy "";

+ 22 - 6
nginx.tmpl

@@ -24,6 +24,13 @@ map $http_x_forwarded_proto $proxy_x_forwarded_proto {
   ''      $scheme;
 }
 
+# If we receive X-Forwarded-Port, pass it through; otherwise, pass along the
+# server port the client connected to
+map $http_x_forwarded_port $proxy_x_forwarded_port {
+  default $http_x_forwarded_port;
+  ''      $server_port;
+}
+
 # If we receive Upgrade, set Connection to "upgrade"; otherwise, delete any
 # Connection header that may have been passed to this server
 map $http_upgrade $proxy_connection {
@@ -37,6 +44,12 @@ server_names_hash_bucket_size 128;
 # Default dhparam
 ssl_dhparam /etc/nginx/dhparam.pem;
 
+# Set appropriate X-Forwarded-Ssl header
+map $scheme $proxy_x_forwarded_ssl {
+  default off;
+  https on;
+}
+
 gzip_types text/plain text/css application/javascript application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;
 
 log_format vhost '$host $remote_addr - $remote_user [$time_local] '
@@ -57,6 +70,8 @@ proxy_set_header Connection $proxy_connection;
 proxy_set_header X-Real-IP $remote_addr;
 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
 proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
+proxy_set_header X-Forwarded-Ssl $proxy_x_forwarded_ssl;
+proxy_set_header X-Forwarded-Port $proxy_x_forwarded_port;
 
 # Mitigate httpoxy attack (see README for details)
 proxy_set_header Proxy "";
@@ -83,8 +98,9 @@ server {
 {{ end }}
 
 {{ range $host, $containers := groupByMulti $ "Env.VIRTUAL_HOST" "," }}
-
-upstream {{ $host }} {
+{{ $upstream_name := sha1 $host }}
+# {{ $host }}
+upstream {{ $upstream_name }} {
 {{ range $container := $containers }}
 	{{ $addrLen := len $container.Addresses }}
 
@@ -177,9 +193,9 @@ server {
 	location / {
 		{{ if eq $proto "uwsgi" }}
 		include uwsgi_params;
-		uwsgi_pass {{ trim $proto }}://{{ trim $host }};
+		uwsgi_pass {{ trim $proto }}://{{ trim $upstream_name }};
 		{{ else }}
-		proxy_pass {{ trim $proto }}://{{ trim $host }};
+		proxy_pass {{ trim $proto }}://{{ trim $upstream_name }};
 		{{ end }}
 		{{ if (exists (printf "/etc/nginx/htpasswd/%s" $host)) }}
 		auth_basic	"Restricted {{ $host }}";
@@ -211,9 +227,9 @@ server {
 	location / {
 		{{ if eq $proto "uwsgi" }}
 		include uwsgi_params;
-		uwsgi_pass {{ trim $proto }}://{{ trim $host }};
+		uwsgi_pass {{ trim $proto }}://{{ trim $upstream_name }};
 		{{ else }}
-		proxy_pass {{ trim $proto }}://{{ trim $host }};
+		proxy_pass {{ trim $proto }}://{{ trim $upstream_name }};
 		{{ end }}
 		{{ if (exists (printf "/etc/nginx/htpasswd/%s" $host)) }}
 		auth_basic	"Restricted {{ $host }}";

+ 3 - 3
test/docker.bats

@@ -111,13 +111,13 @@ function assert_nginxproxy_behaves {
 	assert_output -l 0 $'HTTP/1.1 503 Service Temporarily Unavailable\r'
 
 	# Querying the proxy with Host header → 200
-	run curl_container $container /data --header "Host: web1.bats"
+	run curl_container $container /port --header "Host: web1.bats"
 	assert_output "answer from port 81"
 
-	run curl_container $container /data --header "Host: web2.bats"
+	run curl_container $container /port --header "Host: web2.bats"
 	assert_output "answer from port 82"
 
 	# Querying the proxy with unknown Host header → 503
-	run curl_container $container /data --header "Host: webFOO.bats" --head
+	run curl_container $container /port --header "Host: webFOO.bats" --head
 	assert_output -l 0 $'HTTP/1.1 503 Service Temporarily Unavailable\r'
 }

+ 139 - 0
test/headers.bats

@@ -0,0 +1,139 @@
+#!/usr/bin/env bats
+load test_helpers
+SUT_CONTAINER=bats-nginx-proxy-${TEST_FILE}
+
+function setup {
+	# make sure to stop any web container before each test so we don't
+	# have any unexpected container running with VIRTUAL_HOST or VIRUTAL_PORT set
+	stop_bats_containers web
+}
+
+
+@test "[$TEST_FILE] start a nginx-proxy container" {
+	# GIVEN
+	run nginxproxy $SUT_CONTAINER -v /var/run/docker.sock:/tmp/docker.sock:ro
+	assert_success
+	docker_wait_for_log $SUT_CONTAINER 9 "Watching docker events"
+}
+
+@test "[$TEST_FILE] nginx-proxy passes arbitrary header" {
+	# WHEN
+	prepare_web_container bats-host-1 80 -e VIRTUAL_HOST=web.bats
+	dockergen_wait_for_event $SUT_CONTAINER start bats-host-1
+	sleep 1
+
+	# THEN
+	run curl_container $SUT_CONTAINER /headers -H "Foo: Bar" -H "Host: web.bats"
+	assert_output -l 'Foo: Bar'
+}
+
+##### Testing the handling of X-Forwarded-For #####
+
+@test "[$TEST_FILE] nginx-proxy generates X-Forwarded-For" {
+	# WHEN
+	prepare_web_container bats-host-2 80 -e VIRTUAL_HOST=web.bats
+	dockergen_wait_for_event $SUT_CONTAINER start bats-host-2
+	sleep 1
+
+	# THEN
+	run curl_container $SUT_CONTAINER /headers -H "Host: web.bats"
+	assert_output -p 'X-Forwarded-For:'
+}
+
+@test "[$TEST_FILE] nginx-proxy passes X-Forwarded-For" {
+	# WHEN
+	prepare_web_container bats-host-3 80 -e VIRTUAL_HOST=web.bats
+	dockergen_wait_for_event $SUT_CONTAINER start bats-host-3
+	sleep 1
+
+	# THEN
+	run curl_container $SUT_CONTAINER /headers -H "X-Forwarded-For: 1.2.3.4" -H "Host: web.bats"
+	assert_output -p 'X-Forwarded-For: 1.2.3.4, '
+}
+
+##### Testing the handling of X-Forwarded-Proto #####
+
+@test "[$TEST_FILE] nginx-proxy generates X-Forwarded-Proto" {
+	# WHEN
+	prepare_web_container bats-host-4 80 -e VIRTUAL_HOST=web.bats
+	dockergen_wait_for_event $SUT_CONTAINER start bats-host-4
+	sleep 1
+
+	# THEN
+	run curl_container $SUT_CONTAINER /headers -H "Host: web.bats"
+	assert_output -l 'X-Forwarded-Proto: http'
+}
+
+@test "[$TEST_FILE] nginx-proxy passes X-Forwarded-Proto" {
+	# WHEN
+	prepare_web_container bats-host-5 80 -e VIRTUAL_HOST=web.bats
+	dockergen_wait_for_event $SUT_CONTAINER start bats-host-5
+	sleep 1
+
+	# THEN
+	run curl_container $SUT_CONTAINER /headers -H "X-Forwarded-Proto: https" -H "Host: web.bats"
+	assert_output -l 'X-Forwarded-Proto: https'
+}
+
+##### Testing the handling of X-Forwarded-Port #####
+
+@test "[$TEST_FILE] nginx-proxy generates X-Forwarded-Port" {
+	# WHEN
+	prepare_web_container bats-host-6 80 -e VIRTUAL_HOST=web.bats
+	dockergen_wait_for_event $SUT_CONTAINER start bats-host-6
+	sleep 1
+
+	# THEN
+	run curl_container $SUT_CONTAINER /headers -H "Host: web.bats"
+	assert_output -l 'X-Forwarded-Port: 80'
+}
+
+@test "[$TEST_FILE] nginx-proxy passes X-Forwarded-Port" {
+	# WHEN
+	prepare_web_container bats-host-7 80 -e VIRTUAL_HOST=web.bats
+	dockergen_wait_for_event $SUT_CONTAINER start bats-host-7
+	sleep 1
+
+	# THEN
+	run curl_container $SUT_CONTAINER /headers -H "X-Forwarded-Port: 1234" -H "Host: web.bats"
+	assert_output -l 'X-Forwarded-Port: 1234'
+}
+
+##### Other headers
+
+@test "[$TEST_FILE] nginx-proxy generates X-Real-IP" {
+	# WHEN
+	prepare_web_container bats-host-8 80 -e VIRTUAL_HOST=web.bats
+	dockergen_wait_for_event $SUT_CONTAINER start bats-host-8
+	sleep 1
+
+	# THEN
+	run curl_container $SUT_CONTAINER /headers -H "Host: web.bats"
+	assert_output -p 'X-Real-IP: '
+}
+
+@test "[$TEST_FILE] nginx-proxy passes Host" {
+	# WHEN
+	prepare_web_container bats-host-9 80 -e VIRTUAL_HOST=web.bats
+	dockergen_wait_for_event $SUT_CONTAINER start bats-host-9
+	sleep 1
+
+	# THEN
+	run curl_container $SUT_CONTAINER /headers -H "Host: web.bats"
+	assert_output -l 'Host: web.bats'
+}
+
+@test "[$TEST_FILE] nginx-proxy supresses Proxy for httpoxy protection" {
+	# WHEN
+	prepare_web_container bats-host-10 80 -e VIRTUAL_HOST=web.bats
+	dockergen_wait_for_event $SUT_CONTAINER start bats-host-10
+	sleep 1
+
+	# THEN
+	run curl_container $SUT_CONTAINER /headers -H "Proxy: tcp://foo.com" -H "Host: web.bats"
+	refute_output -l 'Proxy: tcp://foo.com'
+}
+
+@test "[$TEST_FILE] stop all bats containers" {
+	stop_bats_containers
+}

+ 3 - 3
test/multiple-hosts.bats

@@ -26,15 +26,15 @@ function setup {
 	assert_output -l 0 $'HTTP/1.1 503 Service Temporarily Unavailable\r'
 
 	# THEN querying the proxy with unknown Host header → 503
-	run curl_container $SUT_CONTAINER /data --header "Host: webFOO.bats" --head
+	run curl_container $SUT_CONTAINER /port --header "Host: webFOO.bats" --head
 	assert_output -l 0 $'HTTP/1.1 503 Service Temporarily Unavailable\r'
 
 	# THEN
-	run curl_container $SUT_CONTAINER /data --header 'Host: multiple-hosts-1-A.bats'
+	run curl_container $SUT_CONTAINER /port --header 'Host: multiple-hosts-1-A.bats'
 	assert_output "answer from port 80"
 
 	# THEN
-	run curl_container $SUT_CONTAINER /data --header 'Host: multiple-hosts-1-B.bats'
+	run curl_container $SUT_CONTAINER /port --header 'Host: multiple-hosts-1-B.bats'
 	assert_output "answer from port 80"
 }
 

+ 1 - 1
test/multiple-ports.bats

@@ -58,7 +58,7 @@ function setup {
 # $1 port we are expecting an response from
 function assert_response_is_from_port {
 	local -r port=$1
-	run curl_container $SUT_CONTAINER /data --header "Host: web.bats"
+	run curl_container $SUT_CONTAINER /port --header "Host: web.bats"
 	assert_output "answer from port $port"
 }
 

+ 3 - 5
test/test_helpers.bash

@@ -124,6 +124,7 @@ function prepare_web_container {
 		--name $container_name \
 		$expose_option \
 		-w /var/www/ \
+		-v $DIR/web_helpers:/var/www:ro \
 		$options \
 		-e PYTHON_PORTS="$ports" \
 		python:3 bash -c "
@@ -131,10 +132,7 @@ function prepare_web_container {
 			declare -a PIDS
 			for port in \$PYTHON_PORTS; do
 				echo starting a web server listening on port \$port;
-				mkdir /var/www/\$port
-				cd /var/www/\$port
-				echo \"answer from port \$port\" > data
-				python -m http.server \$port &
+				./webserver.py \$port &
 				PIDS+=(\$!)
 			done
 			wait \${PIDS[@]}
@@ -146,7 +144,7 @@ function prepare_web_container {
 	# THEN querying directly port works
 	IFS=$' \t\n' # See https://github.com/sstephenson/bats/issues/89
 	for port in $ports; do
-		run retry 5 1s docker run --label bats-type="curl" appropriate/curl --silent --fail http://$(docker_ip $container_name):$port/data
+		run retry 5 1s docker run --label bats-type="curl" appropriate/curl --silent --fail http://$(docker_ip $container_name):$port/port
 		assert_output "answer from port $port"
 	done
 }

+ 27 - 0
test/web_helpers/webserver.py

@@ -0,0 +1,27 @@
+#!/usr/bin/env python3
+
+import os, sys
+import http.server
+import socketserver
+
+class BatsHandler(http.server.SimpleHTTPRequestHandler):
+    def do_GET(self):
+        root = os.getcwd()
+        
+        self.send_response(200)
+        self.send_header("Content-Type", "text/plain")
+        self.end_headers()
+
+        if self.path == "/headers":
+            self.wfile.write(self.headers.as_string().encode())
+        elif self.path == "/port":
+            response = "answer from port %s\n" % PORT
+            self.wfile.write(response.encode())
+        else:
+            self.wfile.write("No route for this path!\n".encode())
+
+if __name__ == '__main__':
+    PORT = int(sys.argv[1])
+    socketserver.TCPServer.allow_reuse_address = True
+    httpd = socketserver.TCPServer(('0.0.0.0', PORT), BatsHandler)
+    httpd.serve_forever()

+ 17 - 2
test/wildcard-hosts.bats

@@ -43,13 +43,28 @@ function setup {
 
 @test "[$TEST_FILE] VIRTUAL_HOST=~^foo\.bar\..*\.bats" {
 	# WHEN
-	prepare_web_container bats-wildcard-hosts-2 80 -e VIRTUAL_HOST=~^foo\.bar\..*\.bats
-	dockergen_wait_for_event $SUT_CONTAINER start bats-wildcard-hosts-2
+	prepare_web_container bats-wildcard-hosts-3 80 -e VIRTUAL_HOST=~^foo\.bar\..*\.bats
+	dockergen_wait_for_event $SUT_CONTAINER start bats-wildcard-hosts-3
+	sleep 1
+
+	# THEN
+	assert_200 foo.bar.whatever.bats
+	assert_200 foo.bar.why.not.bats
+	assert_200 foo.bar.why.not.bats-to-infinity-and-beyond
+	assert_503 unexpected.host.bats
+
+}
+
+@test "[$TEST_FILE] VIRTUAL_HOST=~^foo\.bar\..*\.bats$" {
+	# WHEN
+	prepare_web_container bats-wildcard-hosts-4 80 -e VIRTUAL_HOST=~^foo\.bar\..*\.bats$
+	dockergen_wait_for_event $SUT_CONTAINER start bats-wildcard-hosts-4
 	sleep 1
 
 	# THEN
 	assert_200 foo.bar.whatever.bats
 	assert_200 foo.bar.why.not.bats
+	assert_503 foo.bar.why.not.bats-to-infinity-and-beyond
 	assert_503 unexpected.host.bats
 
 }