Files
cesnimda 0954876295 fix(security): enforce audited account, network and resource boundaries
Implement B01-B06 with regression coverage and retained validation evidence. Preserve the production ingress preflight; F04 runtime verification remains open.
2026-09-09 18:21:47 +02:00

80 lines
3.3 KiB
Bash

#!/bin/sh
set -eu
origin="${APP_PUBLIC_BASE_URL:-}"
case "$origin" in
http://*|https://*) ;;
*) echo "APP_PUBLIC_BASE_URL must be an HTTP(S) origin." >&2; exit 1 ;;
esac
authority="${origin#*://}"
authority="${authority%/}"
case "$authority" in
""|*/*|*\?*|*\#*|*@*) echo "APP_PUBLIC_BASE_URL must not contain credentials, a path, query, or fragment." >&2; exit 1 ;;
esac
external_host="${authority%%:*}"
port="${authority#"$external_host"}"
case "$external_host" in
""|*[!A-Za-z0-9.-]*) echo "APP_PUBLIC_BASE_URL contains an unsupported host." >&2; exit 1 ;;
esac
if [ -n "$port" ]; then
port="${port#:}"
case "$port" in ""|*[!0-9]*) echo "APP_PUBLIC_BASE_URL contains an invalid port." >&2; exit 1 ;; esac
fi
scheme="${origin%%:*}"
http_config="$(mktemp)"
checks="$(mktemp)"
trap 'rm -f "$http_config" "$checks"' EXIT
case "${INGRESS_MODE:-proxy}" in
proxy)
[ "$scheme" = https ] || { echo "Proxy ingress requires the canonical HTTPS origin." >&2; exit 1; }
[ -n "${INGRESS_PROXY_CIDRS:-}" ] || { echo "INGRESS_PROXY_CIDRS must identify the observed Traefik socket peers." >&2; exit 1; }
printf 'geo $realip_remote_addr $ingress_peer_trusted {\n default 0;\n' > "$http_config"
for cidr in $INGRESS_PROXY_CIDRS; do
case "$cidr" in
*[!0-9A-Fa-f:./]*|*/0|*/*/*) echo "Invalid ingress proxy CIDR." >&2; exit 1 ;;
*/*) ;;
*) echo "Ingress proxy peers must use explicit CIDR notation." >&2; exit 1 ;;
esac
prefix="${cidr##*/}"
case "$prefix" in ''|*[!0-9]*) echo "Invalid ingress prefix." >&2; exit 1 ;; esac
# Only RFC1918/ULA ranges may group peers; all other addresses must be individual.
# This also rejects IPv6 prefixes covering every IPv4-mapped address.
case "$cidr" in
[fF][cCdD]*:*) minimum=64; maximum=128 ;;
*:*) minimum=128; maximum=128 ;;
10.*|192.168.*|172.1[6-9].*|172.2[0-9].*|172.3[01].*) minimum=24; maximum=32 ;;
*) minimum=32; maximum=32 ;;
esac
[ "$prefix" -ge "$minimum" ] && [ "$prefix" -le "$maximum" ] || { echo "Ingress proxy CIDR is too broad or invalid." >&2; exit 1; }
printf ' %s 1;\n' "$cidr" >> "$http_config"
done
printf '}\nreal_ip_header X-Forwarded-For;\nreal_ip_recursive off;\n' >> "$http_config"
for cidr in $INGRESS_PROXY_CIDRS; do printf 'set_real_ip_from %s;\n' "$cidr" >> "$http_config"; done
cat > "$checks" <<'CHECKS'
if ($ingress_peer_trusted = 0) { return 403; }
if ($http_x_forwarded_proto != "https") { return 400; }
if ($http_x_forwarded_for = "") { return 400; }
if ($http_x_forwarded_for ~ ",") { return 400; }
# A malformed address is not forwarded verbatim: realip leaves the socket IP intact.
CHECKS
;;
direct)
[ "$scheme" = http ] || { echo "Direct ingress is only supported for HTTP development." >&2; exit 1; }
;;
*) echo "INGRESS_MODE must be proxy or direct." >&2; exit 1 ;;
esac
sed -e "s/__APP_EXTERNAL_HOST__/$external_host/g" \
-e "s/__APP_EXTERNAL_AUTHORITY__/$authority/g" \
-e "s/__APP_EXTERNAL_SCHEME__/$scheme/g" \
-e "/__APP_INGRESS_HTTP__/r $http_config" -e '/__APP_INGRESS_HTTP__/d' \
-e "/__APP_INGRESS_CHECK__/r $checks" -e '/__APP_INGRESS_CHECK__/d' \
/etc/nginx/jobtracker.conf.template > /etc/nginx/conf.d/default.conf
# Validate addresses and the generated configuration before the entrypoint starts serving.
nginx -t