Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 1 addition & 7 deletions .github/workflows/build-and-publish-nuxt-base.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,8 @@ jobs:
version:
- alpine: '3.22'
name: lts
target:
- name: secure
suffix:
- name: unsecured
suffix: -unsecured
with:
image: nuxt-base
version: ${{ matrix.version.name }}${{ matrix.target.suffix }}
target: ${{ matrix.target.name }}
version: ${{ matrix.version.name }}
build-args: |
ALPINE_VERSION=${{ matrix.version.alpine }}
8 changes: 1 addition & 7 deletions .github/workflows/build-and-publish-web-base.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,8 @@ jobs:
version:
- alpine: '3.22'
name: latest
target:
- name: secure
suffix:
- name: unsecured
suffix: -unsecured
with:
image: web-base
version: ${{ matrix.version.name }}${{ matrix.target.suffix }}
target: ${{ matrix.target.name }}
version: ${{ matrix.version.name }}
build-args: |
ALPINE_VERSION=${{ matrix.version.alpine }}
40 changes: 40 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,46 @@ Images with `nginx` can be configured using the following environment variables.
| NGINX_CORS_ORIGINS | Every run | Comma separated list of hostnames (without `https://`) | `*` |
| NGINX_CORS_RESOURCE_POLICY | Every run | `Cross-Origin-Resource-Policy` value | `same-origin` |

### Content Security Policy

You can control the CSP behaviour with the `NGINX_CSP_MODE` key:
- `enforce`: Configure the `Content-Security-Policy` header.
- `report-only` (default): Instead configure the `Content-Security-Policy-Report-Only` header.

Note: the following fetch & navigation CSP keys can also be set via an embedded file located at `/etc/csp-generator/default`.

Fetch:

| Environment Key | Applied | Description | Default |
|-----------------|---------|-------------|---------|
| NGINX_CSP_CHILD_SRC | Every run | Allowed children (workers, frames) | |
| NGINX_CSP_CONNECT_SRC | Every run | Allowed connections (socket, xhr, …) | |
| NGINX_CSP_FONT_SRC | Every run | Allowed fonts | |
| NGINX_CSP_FRAME_SRC | Every run | Allowed iframes | |
| NGINX_CSP_IMG_SRC | Every run | Allowed images | |
| NGINX_CSP_MANIFEST_SRC | Every run | Allowed manifests | |
| NGINX_CSP_MEDIA_SRC | Every run | Allowed media | |
| NGINX_CSP_OBJECT_SRC | Every run | Allowed embeds | |
| NGINX_CSP_REQUIRE_TRUSTED_TYPES_FOR| Every run | Enable type checking for … | |
| NGINX_CSP_SCRIPT_SRC | Every run | Allowed scripts | |
| NGINX_CSP_STYLE_SRC | Every run | Allowed styles | |
| NGINX_CSP_TRUSTED_TYPES | Every run | List of type policies | |
| NGINX_CSP_WORKER_SRC | Every run | Allowed workers | |

Navigation:

| Environment Key | Applied | Description | Default |
|-----------------|---------|-------------|---------|
| NGINX_FRAME_OPTIONS | Every run | Possible embedders, deprecated. Note that setting to `disable` removes the header completely. | `deny` |
| NGINX_CSP_FRAME_ANCESTORS | Every run | Possible embedders | |
| NGINX_CSP_FORM_ACTION | Every run | Form submit action | |

Reporting:

| Environment Key | Applied | Description | Default |
|-----------------|---------|-------------|---------|
| NGINX_CSP_REPORT_URI | Every run | Set to Sentry CSP reporting URI | |

### Paths

| Environment Key | Applied | Description | Default |
Expand Down
1 change: 1 addition & 0 deletions common/config/nginx/nginx.conf
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ http {

# Variables
include /etc/nginx/snippets/vars/cors-origin.conf;
include /etc/nginx/snippets/vars/csp-and-robots.conf;
include /etc/nginx/snippets/vars/robots-tag.conf;

# Includes virtual hosts configs.
Expand Down

This file was deleted.

This file was deleted.

10 changes: 5 additions & 5 deletions common/config/nginx/snippets/headers/security-web-content.conf
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
include /etc/nginx/snippets/headers/security-base.conf;

# $x_robots_tag is set in the `http` block, generated via a script.
# $content_security_policy (and …_report_only), $x_frame_options and
# $x_robots_tag are set in the `http` block, generated via a script.

# Which content is allowed to load (CSP)
# TODO: should be dynamic / toggleable
# add_header 'Content-Security-Policy' "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://*.googleapis.com https://*.gstatic.com *.google.com https://*.ggpht.com *.googleusercontent.com blob:; img-src * data: blob:; media-src *; frame-src 'self' www.youtube.com youtube.com *.google.com; connect-src 'self' https://*.appwi.se https://*.google-analytics.com https://*.googleapis.com https://*.google.com https://*.gstatic.com https://*.scw.cloud data: blob:; font-src 'self' https://fonts.gstatic.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; worker-src 'self' blob:;" always;
add_header 'Content-Security-Policy' $content_security_policy always;
add_header 'Content-Security-Policy-Report-Only' $content_security_policy_report_only always;

# Disable embedding
# TODO: only works if resource has CORP header
Expand All @@ -23,8 +24,7 @@ add_header 'Permissions-Policy' 'interest-cohort=()';
add_header 'Referrer-Policy' 'same-origin' always;

# Prevent clickjacking
# TODO: should be dynamic / toggleable
# add_header 'X-Frame-Options' 'deny' always;
add_header 'X-Frame-Options' $x_frame_options always;

# Legacy, security for adobe clients
add_header 'X-Permitted-Cross-Domain-Policies' 'none' always;
Expand Down
1 change: 1 addition & 0 deletions common/config/nginx/snippets/vars/csp-and-robots.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# WARNING: lines below generated by bootstrap script
112 changes: 112 additions & 0 deletions common/scripts/startup/50-env-configure-nginx-csp.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
#!/usr/bin/env sh

set -euo pipefail

# Configure nginx security based on ENV vars, and if available the defaults
# located at `/etc/csp-generator/default`.
#
# The defaults file should be a list of variable declarations, such as
# `CHILD_SRC="…"`. Essentially 1 variable for each option that exists. Be
# careful about using quotes though! Keywords such as `none` need to be
# surrounded by single `'` quotes, so the value would be `"'none'"`.
#
# Equivalent settings can be set via ENV, just prefix the variables with
# `NGINX_CSP_…`, like `NGINX_CSP_CHILD_SRC`.
#
# Inputs (aside from all the individual CSP settings):
# - NGINX_CSP_MODE: defaults to 'report-only'
# - NGINX_CSP_REPORT_URI: defaults to ''
# - NGINX_FRAME_OPTIONS: defaults to 'deny', note that setting to `disable` removes the header completely.

# Set defaults
NGINX_CONFIG_FILE='/etc/nginx/snippets/vars/csp-and-robots.conf'
NGINX_CSP_ITEMS='child-src connect-src font-src form-action frame-ancestors frame-src img-src manifest-src media-src object-src require-trusted-types-for script-src style-src trusted-types worker-src'
NGINX_CSP_MODE="${NGINX_CSP_MODE:-report-only}"
NGINX_CSP_REPORT_URI="${NGINX_CSP_REPORT_URI:-}"
NGINX_FRAME_OPTIONS="${NGINX_FRAME_OPTIONS:-deny}"

# Validate input
if [ "${NGINX_CSP_MODE}" = 'enforce' ]; then
NGINX_CSP_VAR_NAME='content_security_policy'
elif [ "${NGINX_CSP_MODE}" = 'report-only' ]; then
NGINX_CSP_VAR_NAME='content_security_policy_report_only'
else
echo "Nginx: invalid CSP mode ${NGINX_CSP_MODE}"
exit 1
fi

# Check nginx structure
if [ ! -f "${NGINX_CONFIG_FILE}" ]; then
echo "Nginx: var-csp-and-robots.conf file is missing, skipping configuring it…"
exit 0
fi

# Helper to print nginx constant
nginx_var_definition() {
cat <<EOF
map "" \$$1 {
default "$2";
}
EOF
}

# Load embedded CSP values from file (if it exists)
EMBEDDED_CSP_PATH=/etc/csp-generator/default
if [ -f "${EMBEDDED_CSP_PATH}" ]; then
echo "Nginx: found CSP defaults at '$EMBEDDED_CSP_PATH', processing…"
PROCESSED_CSP_PATH=$(mktemp)
sed 's/[^=]\+=/export EMBEDDED_CSP_&/' "${EMBEDDED_CSP_PATH}" > "${PROCESSED_CSP_PATH}"
. "${PROCESSED_CSP_PATH}"
rm "${PROCESSED_CSP_PATH}"
fi

# nginx frame options header
if [ "${NGINX_FRAME_OPTIONS}" = 'disable' ]; then
echo "Nginx: configuring frame options as disabled…"
nginx_var_definition 'x_frame_options' '' > "${NGINX_CONFIG_FILE}"
else
echo "Nginx: configuring frame options with '${NGINX_FRAME_OPTIONS}'…"
nginx_var_definition 'x_frame_options' "${NGINX_FRAME_OPTIONS}" > "${NGINX_CONFIG_FILE}"
fi

# Helper to lookup & output CSP items
csp_item() {
item="$1"

# Lookup values if needed, checking `EMBEDDED_CSP_…` and `NGINX_CSP_…`
if [ -n "${2:-}" ]; then
value="$2"
else
uc_item=$(echo "$item" | tr '[:lower:]-' '[:upper:]_')
embedded_val=$( (printenv "EMBEDDED_CSP_${uc_item}" || true) | sed 's/^"//; s/"$//')
nginx_val=$( (printenv "NGINX_CSP_${uc_item}" || true) | sed 's/^"//; s/"$//')
value="${embedded_val}${embedded_val:+${nginx_val:+ }}${nginx_val}"
fi

# Only output if we have a value
if [ -n "$value" ]; then
printf " %s %s;" "$item" "$value"
fi
}

# Helper to print a full CSP definition
nginx_csp_definition() {
name="$1"

if [ "$name" = "$NGINX_CSP_VAR_NAME" ]; then
nginx_var_definition "$name" "$(
printf "default-src 'self';"
for item in $NGINX_CSP_ITEMS; do
csp_item "$item"
done
csp_item 'report-uri' "$NGINX_CSP_REPORT_URI"
)"
else
nginx_var_definition "$name" ''
fi
}

# nginx content policy
echo "Nginx: configuring content security policy…"
nginx_csp_definition content_security_policy >> "${NGINX_CONFIG_FILE}"
nginx_csp_definition content_security_policy_report_only >> "${NGINX_CONFIG_FILE}"
12 changes: 2 additions & 10 deletions nuxt-base/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,7 @@ COPY scripts/ /scripts/
ENV NGINX_API_PATHS=api

#
# --- Variant: Unsecured ---
# --- Variant: Default ---
#

FROM base AS unsecured

RUN rm /etc/nginx/site-mods-enabled.d/headers-extra-security.conf

#
# --- Variant: Secure (default) ---
#

FROM base AS secure
FROM base AS final
1 change: 0 additions & 1 deletion nuxt-base/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

Available images:
- `lts`: normal version.
- `lts-unsecured`: **without** extra security nginx options

## Commands

Expand Down
19 changes: 19 additions & 0 deletions tests/nuxt-base/final/csp/env.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
NGINX_CSP_CHILD_SRC="https://children.environment.com data:"
NGINX_CSP_CONNECT_SRC="https://connection.environment.com data:"
NGINX_CSP_FONT_SRC="https://fonts.environment.com data:"
NGINX_CSP_FORM_ACTION="https://forms.environment.com data:"
NGINX_CSP_FRAME_ANCESTORS="https://frames-anc.environment.com"
NGINX_CSP_FRAME_SRC="https://frames.environment.com data:"
NGINX_CSP_IMG_SRC="https://imgs.environment.com data:"
NGINX_CSP_MANIFEST_SRC="https://manifests.environment.com data:"
NGINX_CSP_MEDIA_SRC="https://media.environment.com data:"
NGINX_CSP_OBJECT_SRC="https://objects.environment.com data:"
NGINX_CSP_REQUIRE_TRUSTED_TYPES_FOR=""
NGINX_CSP_SCRIPT_SRC="https://scripts.environment.com data: 'unsafe-inline'"
NGINX_CSP_STYLE_SRC="https://styles.environment.com data: 'unsafe-inline'"
NGINX_CSP_TRUSTED_TYPES="environment-foo env-bar"
NGINX_CSP_WORKER_SRC="https://workers.environment.com data:"

NGINX_CSP_MODE=enforce
NGINX_CSP_REPORT_URI=https://sentry.appwi.se/api/347/security/?sentry_key=foo123
NGINX_FRAME_OPTIONS=sameorigin
15 changes: 15 additions & 0 deletions tests/nuxt-base/final/csp/extra-files/csp.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
CHILD_SRC="https://children.embedded.com 'self'"
CONNECT_SRC="https://connection.embedded.com 'self'"
FONT_SRC="https://fonts.embedded.com 'self'"
FORM_ACTION="https://forms.embedded.com 'self'"
FRAME_ANCESTORS="https://frames-anc.embedded.com"
FRAME_SRC="https://frames.embedded.com 'self'"
IMG_SRC="https://imgs.embedded.com 'self'"
MANIFEST_SRC="https://manifests.embedded.com 'self'"
MEDIA_SRC="https://media.embedded.com 'self'"
OBJECT_SRC="https://objects.embedded.com 'self'"
REQUIRE_TRUSTED_TYPES_FOR="'script'"
SCRIPT_SRC="https://scripts.embedded.com 'self' 'unsafe-inline'"
STYLE_SRC="https://styles.embedded.com 'self' 'unsafe-inline'"
TRUSTED_TYPES="embedded-foo 'allow-duplicates'"
WORKER_SRC="https://workers.embedded.com 'self'"
26 changes: 26 additions & 0 deletions tests/nuxt-base/final/csp/goss.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
http:
check envs and secrets:
headers:
- >-
Content-Security-Policy:
default-src 'self';
child-src https://children.embedded.com 'self' https://children.environment.com data:;
connect-src https://connection.embedded.com 'self' https://connection.environment.com data:;
font-src https://fonts.embedded.com 'self' https://fonts.environment.com data:;
form-action https://forms.embedded.com 'self' https://forms.environment.com data:;
frame-ancestors https://frames-anc.embedded.com https://frames-anc.environment.com;
frame-src https://frames.embedded.com 'self' https://frames.environment.com data:;
img-src https://imgs.embedded.com 'self' https://imgs.environment.com data:;
manifest-src https://manifests.embedded.com 'self' https://manifests.environment.com data:;
media-src https://media.embedded.com 'self' https://media.environment.com data:;
object-src https://objects.embedded.com 'self' https://objects.environment.com data:;
require-trusted-types-for 'script';
script-src https://scripts.embedded.com 'self' 'unsafe-inline' https://scripts.environment.com data: 'unsafe-inline';
style-src https://styles.embedded.com 'self' 'unsafe-inline' https://styles.environment.com data: 'unsafe-inline';
trusted-types embedded-foo 'allow-duplicates' environment-foo env-bar;
worker-src https://workers.embedded.com 'self' https://workers.environment.com data:;
report-uri https://sentry.appwi.se/api/347/security/?sentry_key=foo123;
- 'X-Frame-Options: sameorigin'
status: 200
url: http://localhost:8080/
3 changes: 3 additions & 0 deletions tests/nuxt-base/final/csp/run_overrides
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
CUSTOM_ARGUMENTS=(
--mount "type=bind,ro,source=$PWD/extra-files/csp.properties,target=/etc/csp-generator/default"
)
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ http:
body:
- 'Hey from index.mjs'
headers:
- 'Content-Security-Policy-Report-Only: default-src ''self'';'
- 'Cross-Origin-Opener-Policy: same-origin'
- 'Cross-Origin-Resource-Policy: same-origin'
- 'Permissions-Policy: interest-cohort=()'
- 'Referrer-Policy: same-origin'
- 'Strict-Transport-Security'
- 'X-Content-Type-Options: nosniff'
- 'X-Frame-Options: deny'
- 'X-Permitted-Cross-Domain-Policies: none'
- 'X-Robots-Tag: none'
status: 200
Expand Down
1 change: 0 additions & 1 deletion tests/nuxt-base/unsecured/api-paths/env.properties

This file was deleted.

16 changes: 0 additions & 16 deletions tests/nuxt-base/unsecured/api-paths/goss.yaml

This file was deleted.

Loading