浏览代码

Merge branch 'master' into alpine

* master:
  Updated nginx to 1.11.6
  Travis-CI's apt-get doesn't have --allow-downgrades yet, which is annoying because --force-yes is deprecated
  Put --allow-downgrades in the right place
  Upgrade docker-engine and allow downgrades
  Clarified a couple parts in the README
  Comment typo
  Added httpoxy test
  Updated README to reflect X-Forwarded-Port
  Implemented more advanced webserver with routing and request header echoing, added header tests
  Honor upstream forwarded port if available
  add ssl_session_tickets to default site
  Replace "replace" to "trimSuffix"
  do not enable HSTS for subdomains
  Remove proxy-tier network in favor of the default.
  Added X-Forwarded-Port
  Add docker-compose file for separate containers.
  connect to uWSGI backends
Matthias Döring 8 年之前
父节点
当前提交
2a8f4554a7
共有 11 个文件被更改,包括 246 次插入21 次删除
  1. 2 2
      .travis.yml
  2. 1 1
      Dockerfile
  3. 22 3
      README.md
  4. 23 0
      docker-compose-separate-containers.yml
  5. 22 3
      nginx.tmpl
  6. 3 3
      test/docker.bats
  7. 139 0
      test/headers.bats
  8. 3 3
      test/multiple-hosts.bats
  9. 1 1
      test/multiple-ports.bats
  10. 3 5
      test/test_helpers.bash
  11. 27 0
      test/web_helpers/webserver.py

+ 2 - 2
.travis.yml

@@ -4,13 +4,13 @@ services:
 
 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

+ 1 - 1
Dockerfile

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

+ 22 - 3
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.6](https://img.shields.io/badge/nginx-1.11.6-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.
@@ -42,7 +42,7 @@ services:
 ```shell
 $ docker-compose up
 $ curl -H "Host: whoami.local" localhost
-I''m 5b129ab83266
+I'm 5b129ab83266
 ```
 
 ### Multiple Ports
@@ -76,7 +76,13 @@ 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.
+
+### uWSGI Backends
+
+If you would like to connect to uWSGI backend, set `VIRTUAL_PROTO=uwsgi` on the
+backend container. Your backend container should than listen on a port rather
+than a socket and expose that port.
 
 ### Default Host
 
@@ -92,6 +98,14 @@ image and the official [nginx](https://registry.hub.docker.com/_/nginx/) image.
 
 You may want to do this to prevent having the docker socket bound to a publicly exposed container service.
 
+You can demo this pattern with docker-compose:
+
+```console
+$ docker-compose --file docker-compose-separate-containers.yml up
+$ curl -H "Host: whoami.local" localhost
+I'm 5b129ab83266
+```
+
 To run nginx proxy as a separate container you'll need to have [nginx.tmpl](https://github.com/jwilder/nginx-proxy/blob/master/nginx.tmpl) on your host system.
 
 First start nginx with a volume:
@@ -126,6 +140,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
 
 If you have Diffie-Hellman groups enabled, the files should be named after the virtual host with a
@@ -205,6 +223,7 @@ 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-Port $proxy_x_forwarded_port;
 
 # Mitigate httpoxy attack (see README for details)
 proxy_set_header Proxy "";

+ 23 - 0
docker-compose-separate-containers.yml

@@ -0,0 +1,23 @@
+version: '2'
+services:
+  nginx:
+    image: nginx
+    container_name: nginx
+    ports:
+      - "80:80"
+    volumes:
+      - /etc/nginx/conf.d
+
+  dockergen:
+    image: jwilder/docker-gen
+    command: -notify-sighup nginx -watch /etc/docker-gen/templates/nginx.tmpl /etc/nginx/conf.d/default.conf
+    volumes_from:
+      - nginx
+    volumes:
+      - /var/run/docker.sock:/tmp/docker.sock:ro
+      - ./nginx.tmpl:/etc/docker-gen/templates/nginx.tmpl
+
+  whoami:
+    image: jwilder/whoami
+    environment:
+      - VIRTUAL_HOST=whoami.local

+ 22 - 3
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 {
@@ -51,6 +58,7 @@ 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-Port $proxy_x_forwarded_port;
 
 # Mitigate httpoxy attack (see README for details)
 proxy_set_header Proxy "";
@@ -70,6 +78,7 @@ server {
 	access_log /var/log/nginx/access.log vhost;
 	return 503;
 
+	ssl_session_tickets off;
 	ssl_certificate /etc/nginx/certs/default.crt;
 	ssl_certificate_key /etc/nginx/certs/default.key;
 }
@@ -118,8 +127,8 @@ upstream {{ $host }} {
 {{ $vhostCert := (closest (dir "/etc/nginx/certs") (printf "%s.crt" $host))}}
 
 {{/* vhostCert is actually a filename so remove any suffixes since they are added later */}}
-{{ $vhostCert := replace $vhostCert ".crt" "" -1 }}
-{{ $vhostCert := replace $vhostCert ".key" "" -1 }}
+{{ $vhostCert := trimSuffix ".crt" $vhostCert }}
+{{ $vhostCert := trimSuffix ".key" $vhostCert }}
 
 {{/* Use the cert specified on the container or fallback to the best vhost match */}}
 {{ $cert := (coalesce $certName $vhostCert) }}
@@ -158,7 +167,7 @@ server {
 	{{ end }}
 
 	{{ if (ne $https_method "noredirect") }}
-	add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
+	add_header Strict-Transport-Security "max-age=31536000";
 	{{ end }}
 
 	{{ if (exists (printf "/etc/nginx/vhost.d/%s" $host)) }}
@@ -168,7 +177,12 @@ server {
 	{{ end }}
 
 	location / {
+		{{ if eq $proto "uwsgi" }}
+		include uwsgi_params;
+		uwsgi_pass {{ trim $proto }}://{{ trim $host }};
+		{{ else }}
 		proxy_pass {{ trim $proto }}://{{ trim $host }};
+		{{ end }}
 		{{ if (exists (printf "/etc/nginx/htpasswd/%s" $host)) }}
 		auth_basic	"Restricted {{ $host }}";
 		auth_basic_user_file	{{ (printf "/etc/nginx/htpasswd/%s" $host) }};
@@ -197,7 +211,12 @@ server {
 	{{ end }}
 
 	location / {
+		{{ if eq $proto "uwsgi" }}
+		include uwsgi_params;
+		uwsgi_pass {{ trim $proto }}://{{ trim $host }};
+		{{ else }}
 		proxy_pass {{ trim $proto }}://{{ trim $host }};
+		{{ end }}
 		{{ if (exists (printf "/etc/nginx/htpasswd/%s" $host)) }}
 		auth_basic	"Restricted {{ $host }}";
 		auth_basic_user_file	{{ (printf "/etc/nginx/htpasswd/%s" $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()