Generalize reverse proxy source allowlist

This commit is contained in:
2026-08-06 11:32:40 +00:00
parent 87cc2977d5
commit 5078f6f3b1
6 changed files with 55 additions and 19 deletions
+2 -1
View File
@@ -3,7 +3,8 @@ IMAGE_PORT=8191
IMAGE_REPOSITORY_PATH=/home/letrhee/sam_rebuild/image IMAGE_REPOSITORY_PATH=/home/letrhee/sam_rebuild/image
IMAGE_UID=1000 IMAGE_UID=1000
IMAGE_GID=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_REMOTE_URL=https://gitea.hided.net/devsam/image.git
IMAGE_REPOSITORY_FULL_NAME=devsam/image IMAGE_REPOSITORY_FULL_NAME=devsam/image
+10 -7
View File
@@ -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 Git changes. The initial and only allowed branch is `master` unless
`IMAGE_ALLOWED_BRANCHES` is explicitly expanded. `IMAGE_ALLOWED_BRANCHES` is explicitly expanded.
The current Caddy host reaches this server from `172.30.1.75`. Keep the observed The service is reverse-proxy agnostic. Its current reverse proxy happens to
source in the untracked `.env`; re-check it whenever Caddy networking changes. reach this server from `172.30.1.75`, but Caddy, Nginx, HAProxy, or another
The published port binds all server interfaces, so production also needs a proxy can be used. Keep every observed proxy source CIDR in the untracked
host `DOCKER-USER` (or equivalent) firewall rule allowing that source CIDR to `TRUSTED_PROXY_CIDRS` value, separated by commas or spaces, and re-check the
TCP 8191 and rejecting other sources. Nginx applies the same source allowlist. 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 ### Prepare
@@ -57,7 +60,7 @@ docker compose build
Apply and inspect the dedicated Docker ingress chain with root privileges: Apply and inspect the dedicated Docker ingress chain with root privileges:
```sh ```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 ./deploy/scripts/firewall-8191.sh apply
sudo ./deploy/scripts/firewall-8191.sh check sudo ./deploy/scripts/firewall-8191.sh check
``` ```
@@ -109,7 +112,7 @@ signed administration command:
./deploy/scripts/admin-deploy.sh <branch> [expected-commit] ./deploy/scripts/admin-deploy.sh <branch> [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 ### Tests and rollback
+1 -1
View File
@@ -54,7 +54,7 @@ services:
condition: service_healthy condition: service_healthy
read_only: true read_only: true
environment: 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: ports:
- "${IMAGE_BIND_ADDRESS:-0.0.0.0}:${IMAGE_PORT:-8191}:8080" - "${IMAGE_BIND_ADDRESS:-0.0.0.0}:${IMAGE_PORT:-8191}:8080"
volumes: volumes:
+21 -5
View File
@@ -1,13 +1,29 @@
#!/bin/sh #!/bin/sh
set -eu set -eu
if [ -z "${CADDY_SOURCE_CIDR:-}" ]; then trusted_proxy_cidrs=${TRUSTED_PROXY_CIDRS:-${CADDY_SOURCE_CIDR:-}}
echo "CADDY_SOURCE_CIDR is required" >&2
if [ -z "$trusted_proxy_cidrs" ]; then
echo "TRUSTED_PROXY_CIDRS is required" >&2
exit 1 exit 1
fi fi
envsubst '${CADDY_SOURCE_CIDR}' \ : > /tmp/trusted-proxy-allow.conf
< /etc/image/default.conf.template \ for trusted_proxy_cidr in $(printf '%s' "$trusted_proxy_cidrs" | tr ',' ' '); do
> /tmp/nginx.conf 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;' exec nginx -c /tmp/nginx.conf -g 'daemon off;'
+1 -1
View File
@@ -24,7 +24,7 @@ http {
allow 127.0.0.1; allow 127.0.0.1;
allow ::1; allow ::1;
allow ${CADDY_SOURCE_CIDR}; include /tmp/trusted-proxy-allow.conf;
deny all; deny all;
location = /healthz { location = /healthz {
+20 -4
View File
@@ -2,7 +2,7 @@
set -eu set -eu
action=${1:-check} action=${1:-check}
source_cidr=${CADDY_SOURCE_CIDR:-} trusted_proxy_cidrs=${TRUSTED_PROXY_CIDRS:-${CADDY_SOURCE_CIDR:-}}
image_port=${IMAGE_PORT:-8191} image_port=${IMAGE_PORT:-8191}
chain=SAM_IMAGE_INGRESS chain=SAM_IMAGE_INGRESS
@@ -24,15 +24,31 @@ case "$action" in
;; ;;
apply) apply)
require_root require_root
if [ -z "$source_cidr" ]; then if [ -z "$trusted_proxy_cidrs" ]; then
echo "CADDY_SOURCE_CIDR is required" >&2 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 exit 2
fi fi
iptables -n -L DOCKER-USER >/dev/null iptables -n -L DOCKER-USER >/dev/null
iptables -n -L "$chain" >/dev/null 2>&1 || iptables -N "$chain" iptables -n -L "$chain" >/dev/null 2>&1 || iptables -N "$chain"
iptables -F "$chain" iptables -F "$chain"
iptables -A "$chain" -s "$source_cidr" -j ACCEPT
iptables -A "$chain" -j DROP 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 -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" || iptables -I DOCKER-USER 1 -p tcp -m conntrack --ctorigdstport "$image_port" -j "$chain"
;; ;;