diff --git a/docs/README.md b/docs/README.md index 5d8da45..30e571c 100644 --- a/docs/README.md +++ b/docs/README.md @@ -56,7 +56,7 @@ For each host defined into `VIRTUAL_HOST`, the associated virtual port is retrie ### Multiple ports -If your container expose more than one service on different ports and those services need to be proxied, you'll need to use the `VIRTUAL_HOST_MULTIPORTS` environment variable. This variable takes virtual host, path, port and dest definition in YAML (or JSON) form, and completely override the `VIRTUAL_HOST`, `VIRTUAL_PORT`, `VIRTUAL_PATH` and `VIRTUAL_DEST` environment variables on this container. +If your container expose more than one service on different ports and those services need to be proxied, you'll need to use the `VIRTUAL_HOST_MULTIPORTS` environment variable. This variable takes virtual host, path, port and dest definition in YAML (or JSON) form, and completely override the `VIRTUAL_HOST`, `VIRTUAL_PORT`, `VIRTUAL_PROTO`, `VIRTUAL_PATH` and `VIRTUAL_DEST` environment variables on this container. The YAML syntax should be easier to write on Docker compose files, while the JSON syntax can be used for CLI invocation. @@ -66,19 +66,21 @@ The expected format is the following: hostname: path: port: int + proto: string dest: string ``` -For each hostname entry, `path`, `port` and `dest` are optional and are assigned default values when missing: +For each hostname entry, `path`, `port`, `proto` and `dest` are optional and are assigned default values when missing: - `path` = "/" - `port` = default port +- `proto` = "http" - `dest` = "" -The following examples use an hypothetical container running services on port 80, 8000 and 9000: - #### Multiple ports routed to different hostnames +The following example use an hypothetical container running services over HTTP on port 80, 8000 and 9000: + ```yaml services: multiport-container: @@ -111,12 +113,14 @@ services: This would result in the following proxy config: -- `www.example.org` -> `multiport-container:80` -- `service1.example.org` -> `multiport-container:8000` -- `service2.example.org` -> `multiport-container:9000` +- `www.example.org` -> `multiport-container:80` over `HTTP` +- `service1.example.org` -> `multiport-container:8000` over `HTTP` +- `service2.example.org` -> `multiport-container:9000` over `HTTP` #### Multiple ports routed to same hostname and different paths +The following example use an hypothetical container running services over HTTP on port 80 and 8000 and over HTTPS on port 9443: + ```yaml services: multiport-container: @@ -130,11 +134,12 @@ services: port: 8000 dest: "/" "/service2": - port: 9000 + port: 9443 + proto: "https" dest: "/" -# port and dest are not specified on the / path, so this path is routed -# to the default port with the default dest value (empty string) +# port and dest are not specified on the / path, so this path is routed to the +# default port with the default dest value (empty string) and default proto (http) # JSON equivalent: # VIRTUAL_HOST_MULTIPORTS: |- @@ -142,16 +147,16 @@ services: # "www.example.org": { # "/": {}, # "/service1": { "port": 8000, "dest": "/" }, -# "/service2": { "port": 9000, "dest": "/" } +# "/service2": { "port": 9443, "proto": "https", "dest": "/" } # } # } ``` This would result in the following proxy config: -- `www.example.org` -> `multiport-container:80` -- `www.example.org/service1` -> `multiport-container:8000` -- `www.example.org/service2` -> `multiport-container:9000` +- `www.example.org` -> `multiport-container:80` over `HTTP` +- `www.example.org/service1` -> `multiport-container:8000` over `HTTP` +- `www.example.org/service2` -> `multiport-container:9443` over `HTTPS` ⬆️ [back to table of contents](#table-of-contents) diff --git a/nginx.tmpl b/nginx.tmpl index d18b2ab..a78e545 100644 --- a/nginx.tmpl +++ b/nginx.tmpl @@ -589,18 +589,31 @@ proxy_set_header Proxy ""; {{- range $path, $vpath := $vhost }} {{- if (empty $vpath) }} - {{- $vpath = dict "dest" "" "port" "default" }} + {{- $vpath = dict + "dest" "" + "port" "default" + "proto" "http" + }} {{- end }} + {{- $dest := $vpath.dest | default "" }} {{- $port := $vpath.port | default "default" | toString }} + {{- $proto := $vpath.proto | default "http" }} + {{- $path_data := get $paths $path | default (dict) }} {{- $path_ports := $path_data.ports | default (dict) }} {{- $path_port_containers := get $path_ports $port | default (list) | concat $containers }} {{- $_ := set $path_ports $port $path_port_containers }} {{- $_ := set $path_data "ports" $path_ports }} + {{- if (not (hasKey $path_data "dest")) }} {{- $_ := set $path_data "dest" $dest }} {{- end }} + + {{- if (not (hasKey $path_data "proto")) }} + {{- $_ := set $path_data "proto" $proto }} + {{- end }} + {{- $_ := set $paths $path $path_data }} {{- end }} {{- $_ := set $vhost_data "paths" $paths }} @@ -635,6 +648,8 @@ proxy_set_header Proxy ""; {{- range $path, $containers := $tmp_paths }} {{- $dest := groupByKeys $containers "Env.VIRTUAL_DEST" | first | default "" }} + {{- $proto := groupByKeys $containers "Env.VIRTUAL_PROTO" | first | default "http" | trim }} + {{- $path_data := get $paths $path | default (dict) }} {{- $path_ports := $path_data.ports | default (dict) }} {{- range $port, $containers := groupByWithDefault $containers "Env.VIRTUAL_PORT" "default" }} @@ -642,9 +657,15 @@ proxy_set_header Proxy ""; {{- $_ := set $path_ports $port $path_port_containers }} {{- end }} {{- $_ := set $path_data "ports" $path_ports }} + {{- if (not (hasKey $path_data "dest")) }} {{- $_ := set $path_data "dest" $dest }} {{- end }} + + {{- if (not (hasKey $path_data "proto")) }} + {{- $_ := set $path_data "proto" $proto }} + {{- end }} + {{- $_ := set $paths $path $path_data }} {{- end }} {{- $_ := set $vhost_data "paths" $paths }} @@ -664,8 +685,6 @@ proxy_set_header Proxy ""; {{ $vpath_containers = concat $vpath_containers $vport_containers }} {{- end }} - {{- /* Get the VIRTUAL_PROTO defined by containers w/ the same vhost-vpath, falling back to "http". */}} - {{- $proto := groupByKeys $vpath_containers "Env.VIRTUAL_PROTO" | first | default "http" | trim }} {{- /* Get the NETWORK_ACCESS defined by containers w/ the same vhost, falling back to "external". */}} {{- $network_tag := groupByKeys $vpath_containers "Env.NETWORK_ACCESS" | first | default "external" }} @@ -678,7 +697,6 @@ proxy_set_header Proxy ""; {{- $upstream = printf "%s-%s" $upstream $sum }} {{- end }} - {{- $_ := set $vpath_data "proto" $proto }} {{- $_ := set $vpath_data "network_tag" $network_tag }} {{- $_ := set $vpath_data "upstream" $upstream }} {{- $_ := set $vpath_data "loadbalance" $loadbalance }} diff --git a/test/test_virtual-proto/nginx-proxy.tld.crt b/test/test_virtual-proto/nginx-proxy.tld.crt new file mode 100644 index 0000000..cd7284b --- /dev/null +++ b/test/test_virtual-proto/nginx-proxy.tld.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: Jan 10 00:08:52 2017 GMT + Not After : May 28 00:08:52 2044 GMT + Subject: CN=*.nginx-proxy.tld + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (2048 bit) + Modulus: + 00:cb:45:f4:14:9b:fe:64:85:79:4a:36:8d:3d:d1: + 27:d0:7c:36:28:30:e6:73:80:6f:7c:49:23:d0:6c: + 17:e4:44:c0:77:4d:9a:c2:bc:24:84:e3:a5:4d:ba: + d2:da:51:7b:a1:2a:12:d4:c0:19:55:69:2c:22:27: + 2d:1a:f6:fc:4b:7f:e9:cb:a8:3c:e8:69:b8:d2:4f: + de:4e:50:e2:d0:74:30:7c:42:5a:ae:aa:85:a5:b1: + 71:4d:c9:7e:86:8b:62:8c:3e:0d:e3:3b:c3:f5:81: + 0b:8c:68:79:fe:bf:10:fb:ae:ec:11:49:6d:64:5e: + 1a:7d:b3:92:93:4e:96:19:3a:98:04:a7:66:b2:74: + 61:2d:41:13:0c:a4:54:0d:2c:78:fd:b4:a3:e8:37: + 78:9a:de:fa:bc:2e:a8:0f:67:14:58:ce:c3:87:d5: + 14:0e:8b:29:7d:48:19:b2:a9:f5:b4:e8:af:32:21: + 67:15:7e:43:52:8b:20:cf:9f:38:43:bf:fd:c8:24: + 7f:52:a3:88:f2:f1:4a:14:91:2a:6e:91:6f:fb:7d: + 6a:78:c6:6d:2e:dd:1e:4c:2b:63:bb:3a:43:9c:91: + f9:df:d3:08:13:63:86:7d:ce:e8:46:cf:f1:6c:1f: + ca:f7:4c:de:d8:4b:e0:da:bc:06:d9:87:0f:ff:96: + 45:85 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Alternative Name: + DNS:*.nginx-proxy.tld + Signature Algorithm: sha256WithRSAEncryption + 6e:a5:0e:e4:d3:cc:d5:b7:fc:34:75:89:4e:98:8c:e7:08:06: + a8:5b:ec:13:7d:83:99:a2:61:b8:d5:12:6e:c5:b4:53:4e:9a: + 22:cd:ad:14:30:6a:7d:58:d7:23:d9:a4:2a:96:a0:40:9e:50: + 9f:ce:f2:fe:8c:dd:9a:ac:99:39:5b:89:2d:ca:e5:3e:c3:bc: + 03:04:1c:12:d9:6e:b8:9f:f0:3a:be:12:44:7e:a4:21:86:73: + af:d5:00:51:3f:2c:56:70:34:8f:26:b0:7f:b0:cf:cf:7f:f9: + 40:6f:00:29:c4:cf:c3:b7:c2:49:3d:3f:b0:26:78:87:b9:c7: + 6c:1b:aa:6a:1a:dd:c5:eb:f2:69:ba:6d:46:0b:92:49:b5:11: + 3c:eb:48:c7:2f:fb:33:a6:6a:82:a2:ab:f8:1e:5f:7d:e3:b7: + f2:fd:f5:88:a5:09:4d:a0:bc:f4:3b:cd:d2:8b:d7:57:1f:86: + 3b:d2:3e:a4:92:21:b0:02:0b:e9:e0:c4:1c:f1:78:e2:58:a7: + 26:5f:4c:29:c8:23:f0:6e:12:3f:bd:ad:44:7b:0b:bd:db:ba: + 63:8d:07:c6:9d:dc:46:cc:63:40:ba:5e:45:82:dd:9a:e5:50: + e8:e7:d7:27:88:fc:6f:1d:8a:e7:5c:49:28:aa:10:29:75:28: + c7:52:de:f9 +-----BEGIN CERTIFICATE----- +MIIC9zCCAd+gAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwPzEfMB0GA1UECgwWbmdp +bngtcHJveHkgdGVzdCBzdWl0ZTEcMBoGA1UEAwwTd3d3Lm5naW54LXByb3h5LnRs +ZDAeFw0xNzAxMTAwMDA4NTJaFw00NDA1MjgwMDA4NTJaMBwxGjAYBgNVBAMMESou +bmdpbngtcHJveHkudGxkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA +y0X0FJv+ZIV5SjaNPdEn0Hw2KDDmc4BvfEkj0GwX5ETAd02awrwkhOOlTbrS2lF7 +oSoS1MAZVWksIictGvb8S3/py6g86Gm40k/eTlDi0HQwfEJarqqFpbFxTcl+hoti +jD4N4zvD9YELjGh5/r8Q+67sEUltZF4afbOSk06WGTqYBKdmsnRhLUETDKRUDSx4 +/bSj6Dd4mt76vC6oD2cUWM7Dh9UUDospfUgZsqn1tOivMiFnFX5DUosgz584Q7/9 +yCR/UqOI8vFKFJEqbpFv+31qeMZtLt0eTCtjuzpDnJH539MIE2OGfc7oRs/xbB/K +90ze2Evg2rwG2YcP/5ZFhQIDAQABoyAwHjAcBgNVHREEFTATghEqLm5naW54LXBy +b3h5LnRsZDANBgkqhkiG9w0BAQsFAAOCAQEAbqUO5NPM1bf8NHWJTpiM5wgGqFvs +E32DmaJhuNUSbsW0U06aIs2tFDBqfVjXI9mkKpagQJ5Qn87y/ozdmqyZOVuJLcrl +PsO8AwQcEtluuJ/wOr4SRH6kIYZzr9UAUT8sVnA0jyawf7DPz3/5QG8AKcTPw7fC +ST0/sCZ4h7nHbBuqahrdxevyabptRguSSbURPOtIxy/7M6ZqgqKr+B5ffeO38v31 +iKUJTaC89DvN0ovXVx+GO9I+pJIhsAIL6eDEHPF44linJl9MKcgj8G4SP72tRHsL +vdu6Y40Hxp3cRsxjQLpeRYLdmuVQ6OfXJ4j8bx2K51xJKKoQKXUox1Le+Q== +-----END CERTIFICATE----- diff --git a/test/test_virtual-proto/nginx-proxy.tld.key b/test/test_virtual-proto/nginx-proxy.tld.key new file mode 100644 index 0000000..91adb14 --- /dev/null +++ b/test/test_virtual-proto/nginx-proxy.tld.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAy0X0FJv+ZIV5SjaNPdEn0Hw2KDDmc4BvfEkj0GwX5ETAd02a +wrwkhOOlTbrS2lF7oSoS1MAZVWksIictGvb8S3/py6g86Gm40k/eTlDi0HQwfEJa +rqqFpbFxTcl+hotijD4N4zvD9YELjGh5/r8Q+67sEUltZF4afbOSk06WGTqYBKdm +snRhLUETDKRUDSx4/bSj6Dd4mt76vC6oD2cUWM7Dh9UUDospfUgZsqn1tOivMiFn +FX5DUosgz584Q7/9yCR/UqOI8vFKFJEqbpFv+31qeMZtLt0eTCtjuzpDnJH539MI +E2OGfc7oRs/xbB/K90ze2Evg2rwG2YcP/5ZFhQIDAQABAoIBAQCjAro2PNLJMfCO +fyjNRgmzu6iCmpR0U68T8GN0JPsT576g7e8J828l0pkhuIyW33lRSThIvLSUNf9a +dChL032H3lBTLduKVh4NKleQXnVFzaeEPoISSFVdButiAhAhPW4OIUVp0OfY3V+x +fac3j2nDLAfL5SKAtqZv363Py9m66EBYm5BmGTQqT/frQWeCEBvlErQef5RIaU8p +e2zMWgSNNojVai8U3nKNRvYHWeWXM6Ck7lCvkHhMF+RpbmCZuqhbEARVnehU/Jdn +QHJ3nxeA2OWpoWKXvAHtSnno49yxq1UIstiQvY+ng5C5i56UlB60UiU2NJ6doZkB +uQ7/1MaBAoGBAORdcFtgdgRALjXngFWhpCp0CseyUehn1KhxDCG+D1pJ142/ymcf +oJOzKJPMRNDdDUBMnR1GBfy7rmwvYevI/SMNy2Qs7ofcXPbdtwwvTCToZ1V9/54k +VfuPBFT+3QzWRvG1tjTV3E4L2VV3nrl2qNPhE5DlfIaU3nQq5Fl0HprJAoGBAOPf +MWOTGev61CdODO5KN3pLAoamiPs5lEUlz3kM3L1Q52YLITxNDjRj9hWBUATJZOS2 +pLOoYRwmhD7vrnimMc41+NuuFX+4T7hWPc8uSuOxX0VijYtULyNRK57mncG1Fq9M +RMLbOJ7FD+8jdXNsSMqpQ+pxLJRX/A10O2fOQnbdAoGAL5hV4YWSM0KZHvz332EI +ER0MXiCJN7HkPZMKH0I4eu3m8hEmAyYxVndBnsQ1F37q0xrkqAQ/HTSUntGlS/og +4Bxw5pkCwegoq/77tpto+ExDtSrEitYx4XMmSPyxX4qNULU5m3tzJgUML+b1etwD +Rd2kMU/TC02dq4KBAy/TbRkCgYAl1xN5iJz+XenLGR/2liZ+TWR+/bqzlU006mF4 +pZUmbv/uJxz+yYD5XDwqOA4UrWjuvhG9r9FoflDprp2XdWnB556KxG7XhcDfSJr9 +A5/2DadXe1Ur9O/a+oi2228JEsxQkea9QPA3FVxfBtFjOHEiDlez39VaUP4PMeUH +iO3qlQKBgFQhdTb7HeYnApYIDHLmd1PvjRvp8XKR1CpEN0nkw8HpHcT1q1MUjQCr +iT6FQupULEvGmO3frQsgVeRIQDbEdZK3C5xCtn6qOw70sYATVf361BbTtidmU9yV +THFxwDSVLiVZgFryoY/NtAc27sVdJnGsPRjjaeVgALAsLbmZ1K/H +-----END RSA PRIVATE KEY----- diff --git a/test/test_virtual-proto/ssl-app-multi-ports.conf b/test/test_virtual-proto/ssl-app-multi-ports.conf new file mode 100644 index 0000000..03956b2 --- /dev/null +++ b/test/test_virtual-proto/ssl-app-multi-ports.conf @@ -0,0 +1,26 @@ +server { + listen 80; + server_name web2.nginx-proxy.tld; + + location / { + default_type text/plain; + return 200 'This is web2.nginx-proxy.tld'; + } +} + +server { + listen 443 ssl; + server_name web3.nginx-proxy.tld; + + ssl_certificate /etc/nginx/certs/server.crt; + ssl_certificate_key /etc/nginx/certs/server.key; + + ssl_protocols TLSv1.2 TLSv1.3; + ssl_prefer_server_ciphers off; + ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305'; + + location / { + default_type text/plain; + return 200 'This is web3.nginx-proxy.tld'; + } +} diff --git a/test/test_virtual-proto/ssl-app-single-port.conf b/test/test_virtual-proto/ssl-app-single-port.conf new file mode 100644 index 0000000..26c7b66 --- /dev/null +++ b/test/test_virtual-proto/ssl-app-single-port.conf @@ -0,0 +1,16 @@ +server { + listen 443 ssl; + server_name web1.nginx-proxy.tld; + + ssl_certificate /etc/nginx/certs/server.crt; + ssl_certificate_key /etc/nginx/certs/server.key; + + ssl_protocols TLSv1.2 TLSv1.3; + ssl_prefer_server_ciphers off; + ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305'; + + location / { + default_type text/plain; + return 200 'This is web1.nginx-proxy.tld'; + } +} diff --git a/test/test_virtual-proto/test_virtual-proto.py b/test/test_virtual-proto/test_virtual-proto.py new file mode 100644 index 0000000..b605b13 --- /dev/null +++ b/test/test_virtual-proto/test_virtual-proto.py @@ -0,0 +1,8 @@ +import pytest + + +@pytest.mark.parametrize('subdomain', ['web1', 'web2', 'web3']) +def test_upstream_with_https_virtual_proto(docker_compose, nginxproxy, subdomain): + r = nginxproxy.get(f"http://{subdomain}.nginx-proxy.tld") + assert r.status_code == 200 + assert r.text == f"This is {subdomain}.nginx-proxy.tld" diff --git a/test/test_virtual-proto/test_virtual-proto.yml b/test/test_virtual-proto/test_virtual-proto.yml new file mode 100644 index 0000000..60d072f --- /dev/null +++ b/test/test_virtual-proto/test_virtual-proto.yml @@ -0,0 +1,25 @@ +services: + ssl-app-single-port: + image: nginx:alpine + volumes: + - ${PYTEST_MODULE_PATH}/ssl-app-single-port.conf:/etc/nginx/conf.d/default.conf:ro + - ${PYTEST_MODULE_PATH}/nginx-proxy.tld.crt:/etc/nginx/certs/server.crt:ro + - ${PYTEST_MODULE_PATH}/nginx-proxy.tld.key:/etc/nginx/certs/server.key:ro + environment: + VIRTUAL_HOST: "web1.nginx-proxy.tld" + VIRTUAL_PROTO: "https" + VIRTUAL_PORT: "443" + + ssl-app-multi-ports: + image: nginx:alpine + volumes: + - ${PYTEST_MODULE_PATH}/ssl-app-multi-ports.conf:/etc/nginx/conf.d/default.conf:ro + - ${PYTEST_MODULE_PATH}/nginx-proxy.tld.crt:/etc/nginx/certs/server.crt:ro + - ${PYTEST_MODULE_PATH}/nginx-proxy.tld.key:/etc/nginx/certs/server.key:ro + environment: + VIRTUAL_HOST_MULTIPORTS: |- + web2.nginx-proxy.tld: + web3.nginx-proxy.tld: + "/": + port: 443 + proto: "https"