#!/bin/bash set -e if [[ -z "${AUTH_AUTHORITY}" ]]; then if [[ -z "${AUTH0_DOMAIN}" ]]; then echo "AUTH_AUTHORITY or AUTH0_DOMAIN environment variable must be set" exit 1 fi fi if [[ -z "${AUTH_CLIENT_ID}" ]]; then if [[ -z "${AUTH0_CLIENT_ID}" ]]; then echo "AUTH_CLIENT_ID or AUTH0_CLIENT_ID environment variable must be set" exit 1 fi fi if [[ -z "${AUTH_AUDIENCE}" ]]; then if [[ -z "${AUTH0_AUDIENCE}" ]]; then echo "AUTH_AUDIENCE or AUTH0_AUDIENCE environment variable must be set" exit 1 fi fi if [[ "${AUTH_AUDIENCE}" == "none" ]]; then unset AUTH_AUDIENCE fi if [[ -z "${AUTH_SUPPORTED_SCOPES}" ]]; then if [[ -z "${AUTH0_DOMAIN}" ]]; then echo "AUTH_SUPPORTED_SCOPES environment variable must be set" exit 1 fi fi if [[ -z "${USE_AUTH0}" ]]; then if [[ -z "${AUTH0_DOMAIN}" ]]; then echo "USE_AUTH0 environment variable must be set" exit 1 fi fi if [[ -z "${NETBIRD_MGMT_API_ENDPOINT}" ]]; then echo "NETBIRD_MGMT_API_ENDPOINT environment variable must be set" exit 1 fi export AUTH_AUTHORITY=${AUTH_AUTHORITY:-https://$AUTH0_DOMAIN} export AUTH_CLIENT_ID=${AUTH_CLIENT_ID:-$AUTH0_CLIENT_ID} export AUTH_CLIENT_SECRET=${AUTH_CLIENT_SECRET} export AUTH_AUDIENCE=${AUTH_AUDIENCE:-$AUTH0_AUDIENCE} export AUTH_REDIRECT_URI=${AUTH_REDIRECT_URI} export AUTH_SILENT_REDIRECT_URI=${AUTH_SILENT_REDIRECT_URI} export USE_AUTH0=${USE_AUTH0:-true} export AUTH_SUPPORTED_SCOPES=${AUTH_SUPPORTED_SCOPES:-openid profile email api offline_access email_verified} export NETBIRD_MGMT_API_ENDPOINT=$(echo $NETBIRD_MGMT_API_ENDPOINT | sed -E 's/(:80|:443)$//') export NETBIRD_MGMT_GRPC_API_ENDPOINT=${NETBIRD_MGMT_GRPC_API_ENDPOINT} export NETBIRD_HOTJAR_TRACK_ID=${NETBIRD_HOTJAR_TRACK_ID} export NETBIRD_GOOGLE_ANALYTICS_ID=${NETBIRD_GOOGLE_ANALYTICS_ID} export NETBIRD_GOOGLE_TAG_MANAGER_ID=${NETBIRD_GOOGLE_TAG_MANAGER_ID} export NETBIRD_TOKEN_SOURCE=${NETBIRD_TOKEN_SOURCE:-accessToken} export NETBIRD_DRAG_QUERY_PARAMS=${NETBIRD_DRAG_QUERY_PARAMS:-false} export NETBIRD_AUTH_SERVICE_URL=${NETBIRD_AUTH_SERVICE_URL} export NETBIRD_WASM_PATH=${NETBIRD_WASM_PATH} export NETBIRD_CSP=${NETBIRD_CSP} export NETBIRD_LICENSED=${NETBIRD_LICENSED:-false} export NETBIRD_CLOUD=${NETBIRD_CLOUD:-false} export NETBIRD_AGENT_NETWORK_ONLY=${NETBIRD_AGENT_NETWORK_ONLY:-false} export NETBIRD_AGENT_NETWORK_ENABLED=${NETBIRD_AGENT_NETWORK_ENABLED:-false} export NETBIRD_HUBSPOT_PORTAL_ID=${NETBIRD_HUBSPOT_PORTAL_ID} export NETBIRD_HUBSPOT_SIGNUP_FORM_ID=${NETBIRD_HUBSPOT_SIGNUP_FORM_ID} export NETBIRD_HUBSPOT_ONBOARDING_FORM_ID=${NETBIRD_HUBSPOT_ONBOARDING_FORM_ID} export NETBIRD_HUBSPOT_SURVEY_FORM_ID=${NETBIRD_HUBSPOT_SURVEY_FORM_ID} export NETBIRD_ANALYTICS_EXCLUDED_EMAILS=${NETBIRD_ANALYTICS_EXCLUDED_EMAILS} echo "NetBird latest version: ${NETBIRD_LATEST_VERSION}" # Build CSP FIRST_PARTY_CSP="pkgs.netbird.io" FIRST_PARTY_CSP_CONNECT_SRC="wss://*.netbird.io" THIRD_PARTY_CSP="*.licdn.com *.linkedin.com *.vector.co *.sibforms.com *.hotjar.com *.hotjar.io *.redditstatic.com pixel-config.reddit.com *.clarity.ms c.bing.com *.microsoft.com googleads.g.doubleclick.net pagead2.googlesyndication.com www.google.com www.googleadservices.com *.google-analytics.com *.googletagmanager.com analytics.google.com *.hubapi.com *.hs-banner.com *.hubspot.com *.hubspot.net js.hs-analytics.com *.hsforms.net *.hscollectedforms.net *.hs-analytics.net *.hsforms.com track.hubspot.com *.hsadspixel.net static.hsappstatic.net" THIRD_PARTY_CSP_CONNECT_SRC="https://api.github.com/repos/netbirdio/netbird/releases/latest https://raw.githubusercontent.com/netbirdio/dashboard/ wss://ws.hotjar.com https://api.hetzner.cloud https://api.digitalocean.com" THIRD_PARTY_CSP_SCRIPT_SRC="'sha256-7knV6EIjKUvCpYWE2rCYx8dYV2WCNb2bpTuitFXzBcA=' *.hs-scripts.com" CSP_DOMAINS="" CSP_DOMAINS_CONNECT_SRC="" if [[ -n "${NETBIRD_CSP}" ]]; then CSP_DOMAINS="$CSP_DOMAINS $NETBIRD_CSP" fi # Add AUTH_AUTHORITY to CSP if [[ -n "${AUTH_AUTHORITY}" ]]; then CSP_DOMAINS="$CSP_DOMAINS $AUTH_AUTHORITY" AUTH_AUTHORITY_ORIGIN=$(echo "$AUTH_AUTHORITY" | sed -E 's|^(https?://[^/]+).*|\1|') CSP_DOMAINS="$CSP_DOMAINS $AUTH_AUTHORITY_ORIGIN" fi # Add AUTH_AUDIENCE to CSP if [[ -n "${AUTH_AUDIENCE}" && ("${AUTH_AUDIENCE}" == *"http://"* || "${AUTH_AUDIENCE}" == *"https://"*) ]]; then CSP_DOMAINS="$CSP_DOMAINS $AUTH_AUDIENCE" fi # Add NETBIRD_AUTH_SERVICE_URL to CSP if [[ -n "${NETBIRD_AUTH_SERVICE_URL}" ]]; then CSP_DOMAINS="$CSP_DOMAINS $NETBIRD_AUTH_SERVICE_URL" fi # Add NETBIRD_MGMT_API_ENDPOINT to CSP if [[ -n "${NETBIRD_MGMT_API_ENDPOINT}" ]]; then MGMT_DOMAIN=$(echo "$NETBIRD_MGMT_API_ENDPOINT" | sed -E 's|https?://||' | cut -d'/' -f1 | cut -d':' -f1) if [[ -n "$MGMT_DOMAIN" ]]; then if [[ "$NETBIRD_MGMT_API_ENDPOINT" == https://* ]]; then CSP_DOMAINS="$CSP_DOMAINS $NETBIRD_MGMT_API_ENDPOINT" CSP_DOMAINS_CONNECT_SRC="$CSP_DOMAINS_CONNECT_SRC wss://$MGMT_DOMAIN" elif [[ "$NETBIRD_MGMT_API_ENDPOINT" == http://* ]]; then CSP_DOMAINS="$CSP_DOMAINS $NETBIRD_MGMT_API_ENDPOINT" CSP_DOMAINS_CONNECT_SRC="$CSP_DOMAINS_CONNECT_SRC ws://$MGMT_DOMAIN" fi fi fi # Add LETSENCRYPT_DOMAIN to CSP if [[ -n "${LETSENCRYPT_DOMAIN}" ]]; then if [[ "$LETSENCRYPT_DOMAIN" == *"localhost"* ]]; then CSP_DOMAINS="$CSP_DOMAINS http://$LETSENCRYPT_DOMAIN" CSP_DOMAINS_CONNECT_SRC="$CSP_DOMAINS_CONNECT_SRC ws://$LETSENCRYPT_DOMAIN" else CSP_DOMAINS="$CSP_DOMAINS https://$LETSENCRYPT_DOMAIN" CSP_DOMAINS_CONNECT_SRC="$CSP_DOMAINS_CONNECT_SRC wss://$LETSENCRYPT_DOMAIN" fi fi CSP_CONNECT_SRC="$CSP_DOMAINS $CSP_DOMAINS_CONNECT_SRC $FIRST_PARTY_CSP $FIRST_PARTY_CSP_CONNECT_SRC $THIRD_PARTY_CSP $THIRD_PARTY_CSP_CONNECT_SRC" CSP_FRAME_SRC="$CSP_DOMAINS $FIRST_PARTY_CSP $THIRD_PARTY_CSP" CSP_SCRIPT_SRC="$CSP_DOMAINS $FIRST_PARTY_CSP $THIRD_PARTY_CSP $THIRD_PARTY_CSP_SCRIPT_SRC" # Remove duplicates CSP_CONNECT_SRC=$(echo $CSP_CONNECT_SRC | tr ' ' '\n' | sort -u | tr '\n' ' ' | sed 's/ $//') CSP_FRAME_SRC=$(echo $CSP_FRAME_SRC | tr ' ' '\n' | sort -u | tr '\n' ' ' | sed 's/ $//') CSP_SCRIPT_SRC=$(echo $CSP_SCRIPT_SRC | tr ' ' '\n' | sort -u | tr '\n' ' ' | sed 's/ $//') # upgrade-insecure-requests tells the browser to rewrite every http subresource, # fetch and WebSocket to https/wss. That is correct for https deployments but breaks # plain-http ones (e.g. local or self-hosted over http), where it would rewrite the # working http/ws endpoints to unreachable https/wss. Emit it only when the backend # is served over https, matching the ws:// vs wss:// scheme logic above. CSP_UPGRADE_INSECURE=" upgrade-insecure-requests;" if [[ "$NETBIRD_MGMT_API_ENDPOINT" == http://* || "$AUTH_AUTHORITY" == http://* ]]; then CSP_UPGRADE_INSECURE="" fi # Update CSP in nginx config CSP_POLICY="default-src 'none'; connect-src 'self' $CSP_CONNECT_SRC; frame-src 'self' $CSP_FRAME_SRC; script-src 'self' 'wasm-unsafe-eval' $CSP_SCRIPT_SRC; font-src 'self'; img-src * data:; manifest-src 'self'; style-src 'self' 'unsafe-inline'; frame-ancestors 'self'; base-uri 'self'; form-action 'self';$CSP_UPGRADE_INSECURE" CSP_HEADER="add_header Content-Security-Policy \"$CSP_POLICY\" always;" echo "CSP header: $CSP_HEADER" # Replace CSP header in nginx config sed -i "s|add_header Content-Security-Policy \"[^\"]*\" always;|$CSP_HEADER|g" /etc/nginx/http.d/default.conf || { echo "Failed to replace CSP header" } # replace ENVs in the config ENV_STR="\$\$USE_AUTH0 \$\$AUTH_AUDIENCE \$\$AUTH_AUTHORITY \$\$AUTH_CLIENT_ID \$\$AUTH_CLIENT_SECRET \$\$AUTH_SUPPORTED_SCOPES \$\$NETBIRD_MGMT_API_ENDPOINT \$\$NETBIRD_MGMT_GRPC_API_ENDPOINT \$\$NETBIRD_HOTJAR_TRACK_ID \$\$NETBIRD_GOOGLE_ANALYTICS_ID \$\$NETBIRD_GOOGLE_TAG_MANAGER_ID \$\$AUTH_REDIRECT_URI \$\$AUTH_SILENT_REDIRECT_URI \$\$NETBIRD_TOKEN_SOURCE \$\$NETBIRD_DRAG_QUERY_PARAMS \$\$NETBIRD_AUTH_SERVICE_URL \$\$NETBIRD_WASM_PATH \$\$NETBIRD_LICENSED \$\$NETBIRD_CLOUD \$\$NETBIRD_AGENT_NETWORK_ONLY \$\$NETBIRD_AGENT_NETWORK_ENABLED \$\$NETBIRD_HUBSPOT_PORTAL_ID \$\$NETBIRD_HUBSPOT_SIGNUP_FORM_ID \$\$NETBIRD_HUBSPOT_ONBOARDING_FORM_ID \$\$NETBIRD_HUBSPOT_SURVEY_FORM_ID \$\$NETBIRD_ANALYTICS_EXCLUDED_EMAILS" OIDC_TRUSTED_DOMAINS="/usr/share/nginx/html/OidcTrustedDomains.js" envsubst "$ENV_STR" < "$OIDC_TRUSTED_DOMAINS".tmpl > "$OIDC_TRUSTED_DOMAINS" for f in $(grep -R -l AUTH_SUPPORTED_SCOPES /usr/share/nginx/html); do cp "$f" "$f".copy envsubst "$ENV_STR" < "$f".copy > "$f" rm "$f".copy done # Reload nginx so the patched CSP header takes effect. # supervisord starts nginx (priority 100) before this script (priority 201) and # never reloads it, so without this nginx keeps serving the static default.conf # CSP that has no connect-src, breaking OIDC discovery over plain HTTP. reloaded=false for i in $(seq 1 10); do if nginx -s reload 2>/dev/null; then echo "Reloaded nginx to apply updated CSP configuration" reloaded=true break fi echo "Waiting for nginx to be ready before reload (attempt $i)..." sleep 1 done if [[ "$reloaded" != true ]]; then echo "Failed to reload nginx after patching CSP header" >&2 nginx -t || true exit 1 fi