From 5078f6f3b17118d272fbf650de546ab6e2fce76c Mon Sep 17 00:00:00 2001 From: hided62 Date: Thu, 6 Aug 2026 11:32:40 +0000 Subject: [PATCH] Generalize reverse proxy source allowlist --- .env.example | 3 ++- README.md | 17 +++++++------ compose.yaml | 2 +- deploy/nginx/entrypoint.sh | 26 ++++++++++++++++---- deploy/nginx/templates/default.conf.template | 2 +- deploy/scripts/firewall-8191.sh | 24 +++++++++++++++--- 6 files changed, 55 insertions(+), 19 deletions(-) diff --git a/.env.example b/.env.example index 426ace6..20c3a53 100644 --- a/.env.example +++ b/.env.example @@ -3,7 +3,8 @@ IMAGE_PORT=8191 IMAGE_REPOSITORY_PATH=/home/letrhee/sam_rebuild/image IMAGE_UID=1000 IMAGE_GID=1000 -CADDY_SOURCE_CIDR=172.30.1.75/32 +# Comma- or space-separated source CIDRs of hosts that reverse-proxy to port 8191. +TRUSTED_PROXY_CIDRS=172.30.1.75/32 IMAGE_REMOTE_URL=https://gitea.hided.net/devsam/image.git IMAGE_REPOSITORY_FULL_NAME=devsam/image diff --git a/README.md b/README.md index a3ff063..048aaac 100644 --- a/README.md +++ b/README.md @@ -39,11 +39,14 @@ remote branch tip, rejects dirty or non-fast-forward updates, and serializes all Git changes. The initial and only allowed branch is `master` unless `IMAGE_ALLOWED_BRANCHES` is explicitly expanded. -The current Caddy host reaches this server from `172.30.1.75`. Keep the observed -source in the untracked `.env`; re-check it whenever Caddy networking changes. -The published port binds all server interfaces, so production also needs a -host `DOCKER-USER` (or equivalent) firewall rule allowing that source CIDR to -TCP 8191 and rejecting other sources. Nginx applies the same source allowlist. +The service is reverse-proxy agnostic. Its current reverse proxy happens to +reach this server from `172.30.1.75`, but Caddy, Nginx, HAProxy, or another +proxy can be used. Keep every observed proxy source CIDR in the untracked +`TRUSTED_PROXY_CIDRS` value, separated by commas or spaces, and re-check the +value whenever proxy networking changes. The published port binds all server +interfaces, so production also needs a host `DOCKER-USER` (or equivalent) +firewall rule allowing those source CIDRs to TCP 8191 and rejecting other +sources. The static Nginx edge applies the same source allowlist. ### Prepare @@ -57,7 +60,7 @@ docker compose build Apply and inspect the dedicated Docker ingress chain with root privileges: ```sh -sudo env CADDY_SOURCE_CIDR=172.30.1.75/32 IMAGE_PORT=8191 \ +sudo env TRUSTED_PROXY_CIDRS=172.30.1.75/32 IMAGE_PORT=8191 \ ./deploy/scripts/firewall-8191.sh apply sudo ./deploy/scripts/firewall-8191.sh check ``` @@ -109,7 +112,7 @@ signed administration command: ./deploy/scripts/admin-deploy.sh [expected-commit] ``` -The administration route is not proxied through Nginx or Caddy. +The administration route is not exposed through the public reverse proxy. ### Tests and rollback diff --git a/compose.yaml b/compose.yaml index b701f43..85629e0 100644 --- a/compose.yaml +++ b/compose.yaml @@ -54,7 +54,7 @@ services: condition: service_healthy read_only: true environment: - CADDY_SOURCE_CIDR: ${CADDY_SOURCE_CIDR:?Set CADDY_SOURCE_CIDR to the direct Caddy source CIDR} + TRUSTED_PROXY_CIDRS: ${TRUSTED_PROXY_CIDRS:?Set TRUSTED_PROXY_CIDRS to the direct reverse-proxy source CIDR list} ports: - "${IMAGE_BIND_ADDRESS:-0.0.0.0}:${IMAGE_PORT:-8191}:8080" volumes: diff --git a/deploy/nginx/entrypoint.sh b/deploy/nginx/entrypoint.sh index ed53798..7cd18e2 100755 --- a/deploy/nginx/entrypoint.sh +++ b/deploy/nginx/entrypoint.sh @@ -1,13 +1,29 @@ #!/bin/sh set -eu -if [ -z "${CADDY_SOURCE_CIDR:-}" ]; then - echo "CADDY_SOURCE_CIDR is required" >&2 +trusted_proxy_cidrs=${TRUSTED_PROXY_CIDRS:-${CADDY_SOURCE_CIDR:-}} + +if [ -z "$trusted_proxy_cidrs" ]; then + echo "TRUSTED_PROXY_CIDRS is required" >&2 exit 1 fi -envsubst '${CADDY_SOURCE_CIDR}' \ - < /etc/image/default.conf.template \ - > /tmp/nginx.conf +: > /tmp/trusted-proxy-allow.conf +for trusted_proxy_cidr in $(printf '%s' "$trusted_proxy_cidrs" | tr ',' ' '); do + case "$trusted_proxy_cidr" in + *[!0-9A-Fa-f:./]*) + echo "Invalid trusted proxy CIDR: $trusted_proxy_cidr" >&2 + exit 2 + ;; + esac + printf 'allow %s;\n' "$trusted_proxy_cidr" >> /tmp/trusted-proxy-allow.conf +done + +if [ ! -s /tmp/trusted-proxy-allow.conf ]; then + echo "TRUSTED_PROXY_CIDRS must contain at least one CIDR" >&2 + exit 2 +fi + +cp /etc/image/default.conf.template /tmp/nginx.conf exec nginx -c /tmp/nginx.conf -g 'daemon off;' diff --git a/deploy/nginx/templates/default.conf.template b/deploy/nginx/templates/default.conf.template index babbedd..b2526c1 100644 --- a/deploy/nginx/templates/default.conf.template +++ b/deploy/nginx/templates/default.conf.template @@ -24,7 +24,7 @@ http { allow 127.0.0.1; allow ::1; - allow ${CADDY_SOURCE_CIDR}; + include /tmp/trusted-proxy-allow.conf; deny all; location = /healthz { diff --git a/deploy/scripts/firewall-8191.sh b/deploy/scripts/firewall-8191.sh index cbec51c..0f1e84b 100755 --- a/deploy/scripts/firewall-8191.sh +++ b/deploy/scripts/firewall-8191.sh @@ -2,7 +2,7 @@ set -eu action=${1:-check} -source_cidr=${CADDY_SOURCE_CIDR:-} +trusted_proxy_cidrs=${TRUSTED_PROXY_CIDRS:-${CADDY_SOURCE_CIDR:-}} image_port=${IMAGE_PORT:-8191} chain=SAM_IMAGE_INGRESS @@ -24,15 +24,31 @@ case "$action" in ;; apply) require_root - if [ -z "$source_cidr" ]; then - echo "CADDY_SOURCE_CIDR is required" >&2 + if [ -z "$trusted_proxy_cidrs" ]; then + echo "TRUSTED_PROXY_CIDRS is required" >&2 + exit 2 + fi + rule_count=0 + for trusted_proxy_cidr in $(printf '%s' "$trusted_proxy_cidrs" | tr ',' ' '); do + case "$trusted_proxy_cidr" in + *[!0-9A-Fa-f:./]*) + echo "Invalid trusted proxy CIDR: $trusted_proxy_cidr" >&2 + exit 2 + ;; + esac + rule_count=$((rule_count + 1)) + done + if [ "$rule_count" -eq 0 ]; then + echo "TRUSTED_PROXY_CIDRS must contain at least one CIDR" >&2 exit 2 fi iptables -n -L DOCKER-USER >/dev/null iptables -n -L "$chain" >/dev/null 2>&1 || iptables -N "$chain" iptables -F "$chain" - iptables -A "$chain" -s "$source_cidr" -j ACCEPT iptables -A "$chain" -j DROP + for trusted_proxy_cidr in $(printf '%s' "$trusted_proxy_cidrs" | tr ',' ' '); do + iptables -I "$chain" 1 -s "$trusted_proxy_cidr" -j ACCEPT + done iptables -C DOCKER-USER -p tcp -m conntrack --ctorigdstport "$image_port" -j "$chain" 2>/dev/null \ || iptables -I DOCKER-USER 1 -p tcp -m conntrack --ctorigdstport "$image_port" -j "$chain" ;;