0954876295
Implement B01-B06 with regression coverage and retained validation evidence. Preserve the production ingress preflight; F04 runtime verification remains open.
80 lines
3.3 KiB
Bash
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
|