feat: Enable HSTS by default

This enables the HSTS header by default when an SSL certificate is in use. HSTS options can also be managed via the nginx:set command, which also exposes the ability to disable HSTS for your application.
This commit is contained in:
Jose Diaz-Gonzalez
2020-02-01 21:16:39 -05:00
parent d42fb02136
commit 73e7ff7b18
8 changed files with 151 additions and 8 deletions

View File

@@ -14,6 +14,8 @@ nginx:validate [<app>] [--clean] # Validates and optionally cleans up in
## Binding to specific addresses ## Binding to specific addresses
> New as of 0.19.2
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. 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 ```shell
@@ -32,6 +34,26 @@ dokku nginx:set node-js-app bind-address-ipv6
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. 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.
## HSTS Header
> New as of 0.20.0
If SSL certificates are present, HSTS will be automatically enabled. It can be toggled via `nginx:set`:
```shell
dokku nginx:set node-js-app hsts true
dokku nginx:set node-js-app hsts false
```
The following options are also available via the `nginx:set` command:
- `hsts` (type: boolean, default: `true`): Enables or disables HSTS for your application.
- `hsts-include-subdomains` (type: boolean, default: `true`): Tells the browser that the HSTS policy also applies to all subdomains of the current domain.
- `hsts-max-age` (type: integer, default: `15724800`): Time in seconds to cache HSTS configuration.
- `hsts-preload` (type: boolean, default: `false`): Tells most major web browsers to include the domain in their HSTS preload lists.
Beware that if you enable the header and a subsequent deploy of your application results in an HTTP deploy (for whatever reason), the way the header works means that a browser will not attempt to request the HTTP version of your site if the HTTPS version fails until the max-age is reached.
## Checking access logs ## 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`. 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`.
@@ -197,10 +219,6 @@ See the [default site documentation](/docs/configuration/domains.md#default-site
See the [load balancer documentation](/docs/configuration/ssl.md#running-behind-a-load-balancer). See the [load balancer documentation](/docs/configuration/ssl.md#running-behind-a-load-balancer).
## HSTS Header
See the [HSTS documentation](/docs/configuration/ssl.md#hsts-header).
## SSL Configuration ## SSL Configuration
See the [ssl documentation](/docs/configuration/ssl.md). See the [ssl documentation](/docs/configuration/ssl.md).

View File

@@ -113,9 +113,9 @@ dokku certs:report node-js-app --ssl-enabled
## HSTS Header ## HSTS Header
The [HSTS header](https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security) is an HTTP header that can inform browsers that all requests to a given site should be made via HTTPS. Dokku does not, by default, enable this header. It is thus left up to you, the user, to enable it for your site. The [HSTS header](https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security) is an HTTP header that can inform browsers that all requests to a given site should be made via HTTPS. Dokku does not enables this header by default
Beware that if you enable the header and a subsequent deploy of your application results in an HTTP deploy (for whatever reason), the way the header works means that a browser will not attempt to request the HTTP version of your site if the HTTPS version fails. See the [NGINX HSTS documentation](/docs/configuration/nginx.md#hsts-header) for more information.
## HTTP/2 support ## HTTP/2 support

View File

@@ -317,6 +317,7 @@ nginx_build_config() {
done done
local PROXY_UPSTREAM_PORTS="$(echo "$PROXY_UPSTREAM_PORTS" | xargs)" local PROXY_UPSTREAM_PORTS="$(echo "$PROXY_UPSTREAM_PORTS" | xargs)"
local SSL_INUSE=
local NONSSL_VHOSTS=$(get_app_domains "$APP") local NONSSL_VHOSTS=$(get_app_domains "$APP")
local NOSSL_SERVER_NAME=$(echo "$NONSSL_VHOSTS" | xargs) local NOSSL_SERVER_NAME=$(echo "$NONSSL_VHOSTS" | xargs)
if is_ssl_enabled "$APP"; then if is_ssl_enabled "$APP"; then
@@ -412,6 +413,8 @@ nginx_build_config() {
dokku_log_info1 "Creating $SCHEME nginx.conf" dokku_log_info1 "Creating $SCHEME nginx.conf"
mv "$NGINX_CONF" "$DOKKU_ROOT/$APP/nginx.conf" mv "$NGINX_CONF" "$DOKKU_ROOT/$APP/nginx.conf"
fn-nginx-vhosts-manage-hsts "$APP" "$SSL_INUSE"
plugn trigger nginx-pre-reload "$APP" "$DOKKU_APP_LISTEN_PORT" "$DOKKU_APP_LISTEN_IP" plugn trigger nginx-pre-reload "$APP" "$DOKKU_APP_LISTEN_PORT" "$DOKKU_APP_LISTEN_IP"
dokku_log_verbose "Reloading nginx" dokku_log_verbose "Reloading nginx"

View File

@@ -2,9 +2,12 @@
set -eo pipefail set -eo pipefail
[[ $DOKKU_TRACE ]] && set -x [[ $DOKKU_TRACE ]] && set -x
source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions"
source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions"
source "$PLUGIN_AVAILABLE_PATH/config/functions" source "$PLUGIN_AVAILABLE_PATH/config/functions"
source "$PLUGIN_AVAILABLE_PATH/nginx-vhosts/internal-functions" source "$PLUGIN_AVAILABLE_PATH/nginx-vhosts/internal-functions"
fn-plugin-property-setup "nginx"
NGINX_BIN="$(fn-nginx-vhosts-nginx-location)" NGINX_BIN="$(fn-nginx-vhosts-nginx-location)"
NGINX_ROOT="/etc/nginx" NGINX_ROOT="/etc/nginx"
NGINX_LOG_ROOT="/var/log/nginx" NGINX_LOG_ROOT="/var/log/nginx"

View File

@@ -37,6 +37,10 @@ cmd-nginx-report-single() {
local flag_map=( local flag_map=(
"--nginx-bind-address-ipv4: $(fn-plugin-property-get-default "nginx" "$APP" "bind-address-ipv4" "")" "--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" "::")" "--nginx-bind-address-ipv6: $(fn-plugin-property-get-default "nginx" "$APP" "bind-address-ipv6" "::")"
"--nginx-hsts: $(fn-plugin-property-get-default "nginx" "$APP" "hsts" "true")"
"--nginx-hsts-include-subdomains: $(fn-plugin-property-get-default "nginx" "$APP" "hsts-include-subdomains" "true")"
"--nginx-hsts-max-age: $(fn-plugin-property-get-default "nginx" "$APP" "hsts-max-age" "15724800")"
"--nginx-hsts-preload: $(fn-plugin-property-get-default "nginx" "$APP" "hsts-preload" "false")"
) )
if [[ -z "$INFO_FLAG" ]]; then if [[ -z "$INFO_FLAG" ]]; then
@@ -65,6 +69,38 @@ cmd-nginx-report-single() {
fi fi
} }
fn-nginx-vhosts-manage-hsts() {
declare APP="$1" SSL_ENABLED="$2"
local HSTS="$(fn-plugin-property-get-default "nginx" "$APP" "nginx-hsts" "true")"
local HSTS_INCLUDE_SUBDOMAINS="$(fn-plugin-property-get-default "nginx" "$APP" "nginx-hsts-include-subdomains" "true")"
local HSTS_MAX_AGE="$(fn-plugin-property-get-default "nginx" "$APP" "nginx-hsts-max-age" "15724800")"
local HSTS_PRELOAD="$(fn-plugin-property-get-default "nginx" "$APP" "nginx-hsts-preload" "false")"
local NGINX_HSTS_CONF="$DOKKU_ROOT/$APP/nginx.conf.d/hsts.conf"
local HSTS_TEMPLATE="$PLUGIN_AVAILABLE_PATH/nginx-vhosts/templates/hsts.conf.sigil"
if [[ "$HSTS" == "false" ]] || [[ "$SSL_ENABLED" != "true" ]]; then
rm -rf "NGINX_HSTS_CONF"
return
fi
dokku_log_verbose_quiet "Enabling HSTS"
local HSTS_HEADERS=""
if [[ -n "$HSTS_MAX_AGE" ]]; then
HSTS_HEADERS="max-age=$HSTS_MAX_AGE"
fi
if [[ "$HSTS_INCLUDE_SUBDOMAINS" == "true" ]]; then
HSTS_HEADERS+="; includeSubdomains"
fi
if [[ "$HSTS_PRELOAD" == "true" ]]; then
HSTS_HEADERS+="; preload"
fi
mkdir -p "$DOKKU_ROOT/$APP/nginx.conf.d"
sigil -f "$HSTS_TEMPLATE" HSTS_HEADERS="$HSTS_HEADERS" | cat -s >"$NGINX_HSTS_CONF"
}
fn-nginx-vhosts-uses-openresty() { fn-nginx-vhosts-uses-openresty() {
declare desc="returns whether openresty is in use or not" declare desc="returns whether openresty is in use or not"

View File

@@ -9,12 +9,12 @@ nginx-set-cmd() {
local cmd="nginx:set" argv=("$@") local cmd="nginx:set" argv=("$@")
[[ ${argv[0]} == "$cmd" ]] && shift 1 [[ ${argv[0]} == "$cmd" ]] && shift 1
declare APP="$1" KEY="$2" VALUE="$3" declare APP="$1" KEY="$2" VALUE="$3"
local VALID_KEYS=("bind-address-ipv4" "bind-address-ipv6") local VALID_KEYS=("bind-address-ipv4" "bind-address-ipv6" "hsts" "hsts-include-subdomains" "hsts-preload" "hsts-max-age")
[[ -z "$APP" ]] && dokku_log_fail "Please specify an app to run the command on" [[ -z "$APP" ]] && dokku_log_fail "Please specify an app to run the command on"
[[ -z "$KEY" ]] && dokku_log_fail "No key specified" [[ -z "$KEY" ]] && dokku_log_fail "No key specified"
if ! fn-in-array "$KEY" "${VALID_KEYS[@]}"; then if ! fn-in-array "$KEY" "${VALID_KEYS[@]}"; then
dokku_log_fail "Invalid key specified, valid keys include: bind-address-ipv4, bind-address-ipv6" dokku_log_fail "Invalid key specified, valid keys include: bind-address-ipv4, bind-address-ipv6, hsts, hsts-include-subdomains, hsts-preload, hsts-max-age"
fi fi
if [[ -n "$VALUE" ]]; then if [[ -n "$VALUE" ]]; then

View File

@@ -0,0 +1 @@
add_header Strict-Transport-Security "{{ $.HSTS_HEADERS }}" always;

View File

@@ -107,6 +107,88 @@ assert_error_log() {
assert_failure assert_failure
} }
@test "(nginx-vhosts) nginx:set hsts" {
local HSTS_CONF="/home/dokku/${TEST_APP}/nginx.conf.d/hsts.conf"
run deploy_app
echo "output: $output"
echo "status: $status"
assert_success
assert_output_contains "Enabling HSTS"
run /bin/bash -c "test -f $HSTS_CONF"
echo "output: $output"
echo "status: $status"
assert_success
run /bin/bash -c "cat $HSTS_CONF | grep includeSubdomains"
echo "output: $output"
echo "status: $status"
assert_success
run /bin/bash -c "cat $HSTS_CONF | grep 'max-age=15724800'"
echo "output: $output"
echo "status: $status"
assert_success
run /bin/bash -c "cat $HSTS_CONF | grep preload"
echo "output: $output"
echo "status: $status"
assert_failure
run /bin/bash -c "dokku nginx:set $TEST_APP hsts-include-subdomains false"
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 "cat $HSTS_CONF | grep includeSubdomains"
echo "output: $output"
echo "status: $status"
assert_failure
run /bin/bash -c "dokku nginx:set $TEST_APP hsts-max-age 120"
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 "cat $HSTS_CONF | grep 'max-age=120'"
echo "output: $output"
echo "status: $status"
assert_success
run /bin/bash -c "dokku nginx:set $TEST_APP hsts-preload true"
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 "cat $HSTS_CONF | grep preload"
echo "output: $output"
echo "status: $status"
assert_success
run /bin/bash -c "dokku nginx:set $TEST_APP hsts false"
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
assert_output_contains "Enabling HSTS" 0
run /bin/bash -c "test -f $DOKKU_ROOT/$TEST_APP/nginx.conf.d/hsts.conf"
echo "output: $output"
echo "status: $status"
assert_failure
}
@test "(nginx-vhosts) nginx:set bind-address" { @test "(nginx-vhosts) nginx:set bind-address" {
run deploy_app run deploy_app
echo "output: $output" echo "output: $output"