Merge pull request #3819 from dokku/3805-bind-address
Allow binding nginx to specific IPv4/IPv6 interfaces
This commit is contained in:
@@ -6,10 +6,32 @@ Dokku uses nginx as its server for routing requests to specific applications. By
|
||||
nginx:access-logs <app> [-t] # Show the nginx access logs for an application (-t follows)
|
||||
nginx:build-config <app> # (Re)builds nginx config for given app
|
||||
nginx:error-logs <app> [-t] # Show the nginx error logs for an application (-t follows)
|
||||
nginx:report [<app>] [<flag>] # Displays a nginx report for one or more apps
|
||||
nginx:set <app> <property> (<value>) # Set or clear an nginx property for an app
|
||||
nginx:show-conf <app> # Display app nginx config
|
||||
nginx:validate [<app>] [--clean] # Validates and optionally cleans up invalid nginx configurations
|
||||
```
|
||||
|
||||
## Binding to specific addresses
|
||||
|
||||
By default, nginx will listen to all interfaces (`[::]` for IPv6, `0.0.0.0` for IPv4) when proxying requests to applications. This may be changed using the `bind-address-ipv4` and `bind-address-ipv6` properties. This is useful in cases where the proxying should be internal to a network or if there are multiple network interfaces that should respond with different content.
|
||||
|
||||
```shell
|
||||
dokku nginx:set node-js-app bind-address-ipv4 127.0.0.1
|
||||
dokku nginx:set node-js-app bind-address-ipv6 ::1
|
||||
```
|
||||
|
||||
This may be reverted by setting an empty bind address.
|
||||
|
||||
```shell
|
||||
dokku nginx:set node-js-app bind-address-ipv4
|
||||
dokku nginx:set node-js-app bind-address-ipv6
|
||||
```
|
||||
|
||||
> Warning: Validation is not performed on either value.
|
||||
|
||||
Users with apps that contain a custom `nginx.conf.sigil` file will need to modify the files to respect the new `NGINX_BIND_ADDRESS_IPV4` and `NGINX_BIND_ADDRESS_IPV6` variables.
|
||||
|
||||
## Checking access logs
|
||||
|
||||
You may check nginx access logs via the `nginx:access-logs` command. This assumes that app access logs are being stored in `/var/log/nginx/$APP-access.log`, as is the default in the generated `nginx.conf`.
|
||||
|
||||
@@ -125,7 +125,7 @@ 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 the following [nginx custom template](/docs/configuration/nginx.md#customizing-the-nginx-configuration)
|
||||
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.
|
||||
|
||||
```go
|
||||
server {
|
||||
@@ -160,7 +160,7 @@ Only use this option if:
|
||||
1. All requests are terminated at the load balancer, and forwarded to Nginx
|
||||
2. The load balancer is configured to send the `X-Forwarded-` headers (this may be off by default)
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
|
||||
@@ -957,6 +957,16 @@ is_val_in_list() {
|
||||
echo "$val_in_list"
|
||||
}
|
||||
|
||||
fn-in-array() {
|
||||
declare desc="return true if value ($1) is in list (all other arguments)"
|
||||
|
||||
local e
|
||||
for e in "${@:2}"; do
|
||||
[[ "$e" == "$1" ]] && return 0
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
remove_val_from_list() {
|
||||
declare desc="remove value ($1) from list ($2) separated by delimiter ($3) (delimiter defaults to comma) and return list"
|
||||
local value="$1" list="$2" delimiter="${3:-,}"
|
||||
|
||||
@@ -100,6 +100,7 @@ func PropertyGetAll(pluginName string, appName string) (map[string]string, error
|
||||
// PropertyGetDefault returns the value for a given property with a specified default value
|
||||
func PropertyGetDefault(pluginName, appName, property, defaultValue string) (val string) {
|
||||
if !PropertyExists(pluginName, appName, property) {
|
||||
val = defaultValue
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -4,16 +4,6 @@ source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions"
|
||||
set -eo pipefail
|
||||
[[ $DOKKU_TRACE ]] && set -x
|
||||
|
||||
fn-in-array() {
|
||||
declare desc="return true if value ($1) is in list (all other arguments)"
|
||||
|
||||
local e
|
||||
for e in "${@:2}"; do
|
||||
[[ "$e" == "$1" ]] && return 0
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
git-set-cmd() {
|
||||
declare desc="set or clear a git property for an app"
|
||||
local cmd="git:set" argv=("$@")
|
||||
|
||||
@@ -378,6 +378,10 @@ nginx_build_config() {
|
||||
local NGINX_TEMPLATE_SOURCE="app-supplied"
|
||||
fi
|
||||
|
||||
local NGINX_BIND_ADDRESS_IP4="$(fn-plugin-property-get-default "nginx" "$APP" "bind-address-ipv4" "")"
|
||||
local NGINX_BIND_ADDRESS_IP6="$(fn-plugin-property-get-default "nginx" "$APP" "bind-address-ipv6" "::")"
|
||||
[[ -z "$NGINX_BIND_ADDRESS_IP6" ]] && NGINX_BIND_ADDRESS_IP6="::"
|
||||
|
||||
eval "$(config_export app "$APP")"
|
||||
local SIGIL_PARAMS=(-f "$NGINX_TEMPLATE" APP="$APP" DOKKU_ROOT="$DOKKU_ROOT"
|
||||
NOSSL_SERVER_NAME="$NOSSL_SERVER_NAME"
|
||||
@@ -388,6 +392,8 @@ nginx_build_config() {
|
||||
TLS13_SUPPORTED="$TLS13_SUPPORTED"
|
||||
HTTP2_SUPPORTED="$HTTP2_SUPPORTED"
|
||||
NGINX_LOG_ROOT="$NGINX_LOG_ROOT"
|
||||
NGINX_BIND_ADDRESS_IP4="$NGINX_BIND_ADDRESS_IP4"
|
||||
NGINX_BIND_ADDRESS_IP6="$NGINX_BIND_ADDRESS_IP6"
|
||||
HTTP2_PUSH_SUPPORTED="$HTTP2_PUSH_SUPPORTED"
|
||||
GRPC_SUPPORTED="$GRPC_SUPPORTED"
|
||||
DOKKU_APP_LISTEN_PORT="$DOKKU_APP_LISTEN_PORT" DOKKU_APP_LISTEN_IP="$DOKKU_APP_LISTEN_IP"
|
||||
|
||||
@@ -2,6 +2,68 @@
|
||||
set -eo pipefail
|
||||
[[ $DOKKU_TRACE ]] && set -x
|
||||
source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions"
|
||||
source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions"
|
||||
|
||||
cmd-nginx-report() {
|
||||
declare desc="displays a nginx report for one or more apps"
|
||||
local cmd="nginx:report"
|
||||
local INSTALLED_APPS=$(dokku_apps)
|
||||
local APP="$2" INFO_FLAG="$3"
|
||||
|
||||
if [[ -n "$APP" ]] && [[ "$APP" == --* ]]; then
|
||||
INFO_FLAG="$APP"
|
||||
APP=""
|
||||
fi
|
||||
|
||||
if [[ -z "$APP" ]] && [[ -z "$INFO_FLAG" ]]; then
|
||||
INFO_FLAG="true"
|
||||
fi
|
||||
|
||||
if [[ -z "$APP" ]]; then
|
||||
for app in $INSTALLED_APPS; do
|
||||
cmd-nginx-report-single "$app" "$INFO_FLAG" | tee || true
|
||||
done
|
||||
else
|
||||
cmd-nginx-report-single "$APP" "$INFO_FLAG"
|
||||
fi
|
||||
}
|
||||
|
||||
cmd-nginx-report-single() {
|
||||
declare APP="$1" INFO_FLAG="$2"
|
||||
if [[ "$INFO_FLAG" == "true" ]]; then
|
||||
INFO_FLAG=""
|
||||
fi
|
||||
verify_app_name "$APP"
|
||||
local flag_map=(
|
||||
"--nginx-bind-address-ipv4: $(fn-plugin-property-get-default "nginx" "$APP" "bind-address-ipv4" "")"
|
||||
"--nginx-bind-address-ipv6: $(fn-plugin-property-get-default "nginx" "$APP" "bind-address-ipv6" "::")"
|
||||
)
|
||||
|
||||
if [[ -z "$INFO_FLAG" ]]; then
|
||||
dokku_log_info2_quiet "${APP} nginx information"
|
||||
for flag in "${flag_map[@]}"; do
|
||||
key="$(echo "${flag#--}" | cut -f1 -d' ' | tr - ' ')"
|
||||
dokku_log_verbose "$(printf "%-30s %-25s" "${key^}" "${flag#*: }")"
|
||||
done
|
||||
else
|
||||
local match=false
|
||||
local value_exists=false
|
||||
for flag in "${flag_map[@]}"; do
|
||||
valid_flags="${valid_flags} $(echo "$flag" | cut -d':' -f1)"
|
||||
if [[ "$flag" == "${INFO_FLAG}:"* ]]; then
|
||||
value=${flag#*: }
|
||||
size="${#value}"
|
||||
if [[ "$size" -ne 0 ]]; then
|
||||
echo "$value" && match=true && value_exists=true
|
||||
else
|
||||
match=true
|
||||
fi
|
||||
fi
|
||||
done
|
||||
[[ "$match" == "true" ]] || dokku_log_fail "Invalid flag passed, valid flags:${valid_flags}"
|
||||
[[ "$value_exists" == "true" ]] || dokku_log_fail "not deployed"
|
||||
fi
|
||||
}
|
||||
|
||||
fn-nginx-vhosts-uses-openresty() {
|
||||
declare desc="returns whether openresty is in use or not"
|
||||
@@ -112,6 +174,8 @@ nginx_vhosts_help_content_func() {
|
||||
nginx:access-logs <app> [-t], Show the nginx access logs for an application (-t follows)
|
||||
nginx:build-config <app>, (Re)builds nginx config for given app
|
||||
nginx:error-logs <app> [-t], Show the nginx error logs for an application (-t follows)
|
||||
nginx:report [<app>] [<flag>], Displays an nginx report for one or more apps
|
||||
nginx:set <app> <property> (<value>), Set or clear an nginx property for an app
|
||||
nginx:show-conf <app>, Display app nginx config
|
||||
nginx:validate [<app>] [--clean], Validates and optionally cleans up invalid nginx configurations
|
||||
help_content
|
||||
|
||||
6
plugins/nginx-vhosts/report
Executable file
6
plugins/nginx-vhosts/report
Executable file
@@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
source "$PLUGIN_AVAILABLE_PATH/nginx-vhosts/internal-functions"
|
||||
set -eo pipefail
|
||||
[[ $DOKKU_TRACE ]] && set -x
|
||||
|
||||
cmd-nginx-report-single "$@"
|
||||
@@ -2,7 +2,6 @@
|
||||
set -eo pipefail
|
||||
[[ $DOKKU_TRACE ]] && set -x
|
||||
source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions"
|
||||
source "$PLUGIN_AVAILABLE_PATH/config/functions"
|
||||
source "$PLUGIN_AVAILABLE_PATH/nginx-vhosts/functions"
|
||||
|
||||
nginx_logs_cmd() {
|
||||
|
||||
6
plugins/nginx-vhosts/subcommands/report
Executable file
6
plugins/nginx-vhosts/subcommands/report
Executable file
@@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
source "$PLUGIN_AVAILABLE_PATH/nginx/internal-functions"
|
||||
set -eo pipefail
|
||||
[[ $DOKKU_TRACE ]] && set -x
|
||||
|
||||
cmd-nginx-report "$@"
|
||||
29
plugins/nginx-vhosts/subcommands/set
Executable file
29
plugins/nginx-vhosts/subcommands/set
Executable file
@@ -0,0 +1,29 @@
|
||||
#!/usr/bin/env bash
|
||||
source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions"
|
||||
source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions"
|
||||
set -eo pipefail
|
||||
[[ $DOKKU_TRACE ]] && set -x
|
||||
|
||||
nginx-set-cmd() {
|
||||
declare desc="set or clear an nginx property for an app"
|
||||
local cmd="nginx:set" argv=("$@")
|
||||
[[ ${argv[0]} == "$cmd" ]] && shift 1
|
||||
declare APP="$1" KEY="$2" VALUE="$3"
|
||||
local VALID_KEYS=("bind-address-ipv4" "bind-address-ipv6")
|
||||
[[ -z "$APP" ]] && dokku_log_fail "Please specify an app to run the command on"
|
||||
[[ -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: bind-address-ipv4, bind-address-ipv6"
|
||||
fi
|
||||
|
||||
if [[ -n "$VALUE" ]]; then
|
||||
dokku_log_info2_quiet "Setting ${KEY} to ${VALUE}"
|
||||
fn-plugin-property-write "nginx" "$APP" "$KEY" "$VALUE"
|
||||
else
|
||||
dokku_log_info2_quiet "Unsetting ${KEY}"
|
||||
fn-plugin-property-delete "nginx" "$APP" "$KEY"
|
||||
fi
|
||||
}
|
||||
|
||||
nginx-set-cmd "$@"
|
||||
@@ -2,7 +2,6 @@
|
||||
set -eo pipefail
|
||||
[[ $DOKKU_TRACE ]] && set -x
|
||||
source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions"
|
||||
source "$PLUGIN_AVAILABLE_PATH/config/functions"
|
||||
source "$PLUGIN_AVAILABLE_PATH/nginx-vhosts/functions"
|
||||
|
||||
nginx_validate_cmd() {
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
|
||||
{{ if eq $scheme "http" }}
|
||||
server {
|
||||
listen [::]:{{ $listen_port }};
|
||||
listen {{ $listen_port }};
|
||||
listen [{{ $.NGINX_BIND_ADDRESS_IP6 }}]:{{ $listen_port }};
|
||||
listen {{ if $.NGINX_BIND_ADDRESS_IP4 }}{{ $.NGINX_BIND_ADDRESS_IP4 }}:{{end}}{{ $listen_port }};
|
||||
{{ if $.NOSSL_SERVER_NAME }}server_name {{ $.NOSSL_SERVER_NAME }}; {{ end }}
|
||||
access_log {{ $.NGINX_LOG_ROOT }}/{{ $.APP }}-access.log;
|
||||
error_log {{ $.NGINX_LOG_ROOT }}/{{ $.APP }}-error.log;
|
||||
@@ -56,8 +56,8 @@ server {
|
||||
}
|
||||
{{ else if eq $scheme "https"}}
|
||||
server {
|
||||
listen [::]:{{ $listen_port }} ssl {{ if eq $.HTTP2_SUPPORTED "true" }}http2{{ else if eq $.SPDY_SUPPORTED "true" }}spdy{{ end }};
|
||||
listen {{ $listen_port }} ssl {{ if eq $.HTTP2_SUPPORTED "true" }}http2{{ else if eq $.SPDY_SUPPORTED "true" }}spdy{{ end }};
|
||||
listen [{{ $.NGINX_BIND_ADDRESS_IP6 }}]:{{ $listen_port }} ssl {{ if eq $.HTTP2_SUPPORTED "true" }}http2{{ else if eq $.SPDY_SUPPORTED "true" }}spdy{{ end }};
|
||||
listen {{ if $.NGINX_BIND_ADDRESS_IP4 }}{{ $.NGINX_BIND_ADDRESS_IP4 }}:{{end}}{{ $listen_port }} ssl {{ if eq $.HTTP2_SUPPORTED "true" }}http2{{ else if eq $.SPDY_SUPPORTED "true" }}spdy{{ end }};
|
||||
{{ if $.SSL_SERVER_NAME }}server_name {{ $.SSL_SERVER_NAME }}; {{ end }}
|
||||
{{ if $.NOSSL_SERVER_NAME }}server_name {{ $.NOSSL_SERVER_NAME }}; {{ end }}
|
||||
access_log {{ $.NGINX_LOG_ROOT }}/{{ $.APP }}-access.log;
|
||||
@@ -120,8 +120,8 @@ server {
|
||||
{{ else if eq $scheme "grpc"}}
|
||||
{{ if eq $.GRPC_SUPPORTED "true"}}{{ if eq $.HTTP2_SUPPORTED "true"}}
|
||||
server {
|
||||
listen [::]:{{ $listen_port }} http2;
|
||||
listen {{ $listen_port }} http2;
|
||||
listen [{{ $.NGINX_BIND_ADDRESS_IP6 }}]:{{ $listen_port }} http2;
|
||||
listen {{ if $.NGINX_BIND_ADDRESS_IP4 }}{{ $.NGINX_BIND_ADDRESS_IP4 }}:{{end}}{{ $listen_port }} http2;
|
||||
{{ if $.NOSSL_SERVER_NAME }}server_name {{ $.NOSSL_SERVER_NAME }}; {{ end }}
|
||||
access_log {{ $.NGINX_LOG_ROOT }}/{{ $.APP }}-access.log;
|
||||
error_log {{ $.NGINX_LOG_ROOT }}/{{ $.APP }}-error.log;
|
||||
@@ -134,8 +134,8 @@ server {
|
||||
{{ else if eq $scheme "grpcs"}}
|
||||
{{ if eq $.GRPC_SUPPORTED "true"}}{{ if eq $.HTTP2_SUPPORTED "true"}}
|
||||
server {
|
||||
listen [::]:{{ $listen_port }} ssl http2;
|
||||
listen {{ $listen_port }} ssl http2;
|
||||
listen [{{ $.NGINX_BIND_ADDRESS_IP6 }}]:{{ $listen_port }} ssl http2;
|
||||
listen {{ if $.NGINX_BIND_ADDRESS_IP4 }}{{ $.NGINX_BIND_ADDRESS_IP4 }}:{{end}}{{ $listen_port }} ssl http2;
|
||||
{{ if $.NOSSL_SERVER_NAME }}server_name {{ $.NOSSL_SERVER_NAME }}; {{ end }}
|
||||
access_log {{ $.NGINX_LOG_ROOT }}/{{ $.APP }}-access.log;
|
||||
error_log {{ $.NGINX_LOG_ROOT }}/{{ $.APP }}-error.log;
|
||||
|
||||
@@ -4,16 +4,6 @@ source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions"
|
||||
set -eo pipefail
|
||||
[[ $DOKKU_TRACE ]] && set -x
|
||||
|
||||
fn-in-array() {
|
||||
declare desc="return true if value ($1) is in list (all other arguments)"
|
||||
|
||||
local e
|
||||
for e in "${@:2}"; do
|
||||
[[ "$e" == "$1" ]] && return 0
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
scheduler-docker-local-set-cmd() {
|
||||
declare desc="set or clear a scheduler-docker-local property for an app"
|
||||
local cmd="scheduler-docker-local:set" argv=("$@")
|
||||
|
||||
@@ -107,6 +107,55 @@ assert_error_log() {
|
||||
assert_failure
|
||||
}
|
||||
|
||||
@test "(nginx-vhosts) nginx:set bind-address" {
|
||||
run deploy_app
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku nginx:set $TEST_APP bind-address-ipv4 127.0.0.1"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku nginx:set $TEST_APP bind-address-ipv6 ::1"
|
||||
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-conf $TEST_APP"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_output_contains "[::1]:80;"
|
||||
assert_output_contains "127.0.0.1:80;"
|
||||
|
||||
run /bin/bash -c "dokku nginx:set $TEST_APP bind-address-ipv4"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku nginx:set $TEST_APP bind-address-ipv6"
|
||||
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-conf $TEST_APP"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_output_contains "[::1]:80;" 0
|
||||
assert_output_contains "127.0.0.1:80;" 0
|
||||
}
|
||||
|
||||
@test "(nginx-vhosts) nginx:validate" {
|
||||
deploy_app
|
||||
run /bin/bash -c "dokku nginx:validate"
|
||||
|
||||
Reference in New Issue
Block a user