diff --git a/docs/configuration/ssl.md b/docs/configuration/ssl.md index 5455ce56a..a03dad677 100644 --- a/docs/configuration/ssl.md +++ b/docs/configuration/ssl.md @@ -136,35 +136,12 @@ Certain versions of nginx have bugs that prevent [HTTP/2](https://nginx.org/en/d Your application has access to the HTTP headers `X-Forwarded-Proto`, `X-Forwarded-Port` and `X-Forwarded-For`. These headers indicate the protocol of the original request (HTTP or HTTPS), the port number, and the IP address of the client making the request, respectively. The default configuration is for Nginx to set these headers. -If your server runs behind an HTTP(S) load balancer, then Nginx will see all requests as coming from the load balancer. If your load balancer sets the `X-Forwarded-` headers, you can tell Nginx to pass these headers from load balancer to your application by using a [custom nginx template](/docs/configuration/nginx.md#customizing-the-nginx-configuration). The following is a simple example of how to do so. +If your server runs behind an HTTP(S) load balancer, then Nginx will see all requests as coming from the load balancer. If your load balancer sets the `X-Forwarded-` headers, you can tell Nginx to pass these headers from load balancer to your application via `nginx:set`: -```go -server { - listen [::]:{{ .PROXY_PORT }}; - listen {{ .PROXY_PORT }}; - server_name {{ .NOSSL_SERVER_NAME }}; - access_log /var/log/nginx/{{ .APP }}-access.log; - error_log /var/log/nginx/{{ .APP }}-error.log; - - location / { - proxy_pass http://{{ .APP }}; - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection $http_connection; - proxy_set_header Host $http_host; - proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto; - proxy_set_header X-Forwarded-For $http_x_forwarded_for; - proxy_set_header X-Forwarded-Port $http_x_forwarded_port; - proxy_set_header X-Request-Start $msec; - } - include {{ .DOKKU_ROOT }}/{{ .APP }}/nginx.conf.d/*.conf; -} - -upstream {{ .APP }} { -{{ range .DOKKU_APP_WEB_LISTENERS | split " " }} - server {{ . }}; -{{ end }} -} +```shell +dokku nginx:set node-js-app x-forwarded-for-value "\$http_x_forwarded_for" +dokku nginx:set node-js-app x-forwarded-port-value "\$http_x_forwarded_port" +dokku nginx:set node-js-app x-forwarded-proto-value "\$http_x_forwarded_proto" ``` Only use this option if: @@ -173,8 +150,6 @@ Only use this option if: If it's possible to make HTTP(S) requests directly to Nginx, bypassing the load balancer, or if the load balancer is not configured to set these headers, then it becomes possible for a client to set these headers to arbitrary values. -This could result in security issue, for example, if your application looks at the value of the `X-Forwarded-Proto` to determine if the request was made over HTTPS. - ### SSL Port Exposure When your app is served from port `80` then the `/home/dokku/APP/nginx.conf` file will automatically be updated to instruct nginx to respond to ssl on port 443 as a new cert is added. If your app uses a non-standard port (perhaps you have a dockerfile deploy exposing port `99999`) you may need to manually expose an ssl port via `dokku proxy:ports-add https:443:99999`. diff --git a/plugins/nginx-vhosts/command-functions b/plugins/nginx-vhosts/command-functions index 1fda16313..99e954391 100755 --- a/plugins/nginx-vhosts/command-functions +++ b/plugins/nginx-vhosts/command-functions @@ -54,6 +54,9 @@ cmd-nginx-report-single() { "--nginx-proxy-busy-buffers-size: $(fn-nginx-proxy-busy-buffers-size "$APP")" "--nginx-proxy-read-timeout: $(fn-nginx-proxy-read-timeout "$APP")" "--nginx-last-visited-at: $(fn-nginx-vhosts-last-visited-at "$APP")" + "--nginx-x-forwarded-for-value: $(fn-plugin-property-get-default "nginx" "$APP" "x-forwarded-for-value" "\$remote_addr")" + "--nginx-x-forwarded-port-value: $(fn-plugin-property-get-default "nginx" "$APP" "x-forwarded-port-value" "\$server_port")" + "--nginx-x-forwarded-proto-value: $(fn-plugin-property-get-default "nginx" "$APP" "x-forwarded-proto-value" "\$scheme")" ) if [[ -z "$INFO_FLAG" ]]; then diff --git a/plugins/nginx-vhosts/functions b/plugins/nginx-vhosts/functions index 23c60b27a..a2f04e9c8 100755 --- a/plugins/nginx-vhosts/functions +++ b/plugins/nginx-vhosts/functions @@ -462,6 +462,10 @@ nginx_build_config() { local NGINX_BIND_ADDRESS_IP6="$(fn-plugin-property-get-default "nginx" "$APP" "bind-address-ipv6" "::")" [[ -z "$NGINX_BIND_ADDRESS_IP6" ]] && NGINX_BIND_ADDRESS_IP6="::" + local PROXY_X_FORWARDED_FOR="$(fn-plugin-property-get-default "nginx" "$APP" "x-forwarded-for-value" "\$remote_addr")" + local PROXY_X_FORWARDED_PORT="$(fn-plugin-property-get-default "nginx" "$APP" "x-forwarded-port-value" "\$server_port")" + local PROXY_X_FORWARDED_PROTO="$(fn-plugin-property-get-default "nginx" "$APP" "x-forwarded-proto-value" "\$scheme")" + eval "$(config_export app "$APP")" local SIGIL_PARAMS=(-f "$NGINX_TEMPLATE" APP="$APP" DOKKU_ROOT="$DOKKU_ROOT" NOSSL_SERVER_NAME="$NOSSL_SERVER_NAME" @@ -490,7 +494,10 @@ nginx_build_config() { # Deprecated: Remove this after a few versions NGINX_PORT="$PROXY_PORT" NGINX_SSL_PORT="$PROXY_SSL_PORT" PROXY_PORT="$PROXY_PORT" PROXY_SSL_PORT="$PROXY_SSL_PORT" RAW_TCP_PORTS="$RAW_TCP_PORTS" - PROXY_PORT_MAP="$PROXY_PORT_MAP" PROXY_UPSTREAM_PORTS="$PROXY_UPSTREAM_PORTS") + PROXY_PORT_MAP="$PROXY_PORT_MAP" PROXY_UPSTREAM_PORTS="$PROXY_UPSTREAM_PORTS" + PROXY_X_FORWARDED_FOR="$PROXY_X_FORWARDED_FOR" + PROXY_X_FORWARDED_PORT="$PROXY_X_FORWARDED_PORT" + PROXY_X_FORWARDED_PROTO="$PROXY_X_FORWARDED_PROTO") local DOKKU_SCALE_FILE="$DOKKU_ROOT/$APP/DOKKU_SCALE" while read -r line || [[ -n "$line" ]]; do diff --git a/plugins/nginx-vhosts/subcommands/set b/plugins/nginx-vhosts/subcommands/set index 5066e860e..681041933 100755 --- a/plugins/nginx-vhosts/subcommands/set +++ b/plugins/nginx-vhosts/subcommands/set @@ -9,13 +9,13 @@ cmd-nginx-set() { declare cmd="nginx:set" [[ "$1" == "$cmd" ]] && shift 1 declare APP="$1" KEY="$2" VALUE="$3" - local VALID_KEYS=("access-log-format" "access-log-path" "bind-address-ipv4" "bind-address-ipv6" "disable-custom-config" "error-log-path" "hsts" "hsts-include-subdomains" "hsts-preload" "hsts-max-age" "proxy-read-timeout" "proxy-buffer-size" "proxy-buffering" "proxy-buffers" "proxy-busy-buffers-size") + local VALID_KEYS=("access-log-format" "access-log-path" "bind-address-ipv4" "bind-address-ipv6" "disable-custom-config" "error-log-path" "hsts" "hsts-include-subdomains" "hsts-preload" "hsts-max-age" "proxy-read-timeout" "proxy-buffer-size" "proxy-buffering" "proxy-buffers" "proxy-busy-buffers-size" "x-forwarded-for-value" "x-forwarded-port-value" "x-forwarded-proto-value") verify_app_name "$APP" [[ -z "$KEY" ]] && dokku_log_fail "No key specified" if ! fn-in-array "$KEY" "${VALID_KEYS[@]}"; then - dokku_log_fail "Invalid key specified, valid keys include: access-log-format, access-log-path, bind-address-ipv4, bind-address-ipv6, disable-custom-config, error-log-path, hsts, hsts-include-subdomains, hsts-preload, hsts-max-age, proxy-read-timeout, proxy-buffer-size, proxy-buffering, proxy-buffers, proxy-busy-buffers-size" + dokku_log_fail "Invalid key specified, valid keys include: access-log-format, access-log-path, bind-address-ipv4, bind-address-ipv6, disable-custom-config, error-log-path, hsts, hsts-include-subdomains, hsts-preload, hsts-max-age, proxy-read-timeout, proxy-buffer-size, proxy-buffering, proxy-buffers, proxy-busy-buffers-size, x-forwarded-for-value, x-forwarded-port-value, x-forwarded-proto-value" fi if [[ -n "$VALUE" ]]; then diff --git a/plugins/nginx-vhosts/templates/nginx.conf.sigil b/plugins/nginx-vhosts/templates/nginx.conf.sigil index a195a0a17..dd3fae51f 100644 --- a/plugins/nginx-vhosts/templates/nginx.conf.sigil +++ b/plugins/nginx-vhosts/templates/nginx.conf.sigil @@ -33,9 +33,9 @@ server { proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $http_connection; proxy_set_header Host $http_host; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header X-Forwarded-For $remote_addr; - proxy_set_header X-Forwarded-Port $server_port; + proxy_set_header X-Forwarded-For {{ $.PROXY_X_FORWARDED_FOR }}; + proxy_set_header X-Forwarded-Port {{ $.PROXY_X_FORWARDED_PORT }}; + proxy_set_header X-Forwarded-Proto {{ $.PROXY_X_FORWARDED_PROTO }}; proxy_set_header X-Request-Start $msec; } include {{ $.DOKKU_ROOT }}/{{ $.APP }}/nginx.conf.d/*.conf; @@ -96,9 +96,9 @@ server { proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $http_connection; proxy_set_header Host $http_host; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header X-Forwarded-For $remote_addr; - proxy_set_header X-Forwarded-Port $server_port; + proxy_set_header X-Forwarded-For {{ $.PROXY_X_FORWARDED_FOR }}; + proxy_set_header X-Forwarded-Port {{ $.PROXY_X_FORWARDED_PORT }}; + proxy_set_header X-Forwarded-Proto {{ $.PROXY_X_FORWARDED_PROTO }}; proxy_set_header X-Request-Start $msec; } include {{ $.DOKKU_ROOT }}/{{ $.APP }}/nginx.conf.d/*.conf; diff --git a/tests/unit/nginx-vhosts_6.bats b/tests/unit/nginx-vhosts_6.bats index 0ff29f4e5..1e49fa352 100644 --- a/tests/unit/nginx-vhosts_6.bats +++ b/tests/unit/nginx-vhosts_6.bats @@ -156,6 +156,102 @@ teardown() { assert_output_contains "127.0.0.1:80;" 0 } +@test "(nginx-vhosts) nginx:set x-forwarded-for-value" { + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku nginx:build-config $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku nginx:show-config $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_output_contains "X-Forwarded-For \$remote_addr;" + + run /bin/bash -c "dokku nginx:set $TEST_APP x-forwarded-for-value '\$http_x_forwarded_for'" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku nginx:build-config $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku nginx:show-config $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_output_contains "X-Forwarded-For \$http_x_forwarded_for;" +} + +@test "(nginx-vhosts) nginx:set x-forwarded-port-value" { + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku nginx:build-config $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku nginx:show-config $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_output_contains "X-Forwarded-Port \$server_port;" + + run /bin/bash -c "dokku nginx:set $TEST_APP x-forwarded-port-value '\$http_x_forwarded_port'" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku nginx:build-config $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku nginx:show-config $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_output_contains "X-Forwarded-Port \$http_x_forwarded_port;" +} + +@test "(nginx-vhosts) nginx:set x-forwarded-proto-value" { + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku nginx:build-config $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku nginx:show-config $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_output_contains "X-Forwarded-Proto \$scheme;" + + run /bin/bash -c "dokku nginx:set $TEST_APP x-forwarded-proto-value '\$http_x_forwarded_proto'" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku nginx:build-config $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku nginx:show-config $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_output_contains "X-Forwarded-Proto \$http_x_forwarded_proto;" +} + @test "(nginx-vhosts) nginx:validate-config" { deploy_app run /bin/bash -c "dokku nginx:validate-config"