From f21ade06d9c59257405489561555376084b216f4 Mon Sep 17 00:00:00 2001 From: Josh Knapp Date: Thu, 13 Aug 2026 13:48:34 -0700 Subject: [PATCH] fix(haproxy): stop the trusted-proxy volume from shadowing Cloudflare/proxy lists /etc/haproxy is a named volume in deployed containers, so the baked-in cloudflare_ips.list and trusted_proxies.list COPYed there in the prior task never actually reached hosts with a pre-existing volume -- the start-up.sh guard then found them "missing" and created them empty. With both lists empty, the from_trusted_proxy ACL in hap_listener.tpl matched nothing, so CF-Connecting-IP / X-Real-IP / X-Forwarded-For got stripped from every peer, including Cloudflare's own edge. Confirmed live: image shipped 34/13 lines, running container had 0/0. Fix: stage both files under /haproxy/defaults (outside the volume) and apply their ownership rule in start-up.sh instead of a blind "create if missing": - cloudflare_ips.list is shipped data -- always refresh it from the baked default so Cloudflare range updates reach existing hosts. - trusted_proxies.list is operator data -- seed it from the baked default only when missing, and never overwrite what an operator added on the server. Both branches fall back to creating an empty file if the baked default is somehow absent, since a missing "-f" target is a fatal HAProxy config error. Verified against a volume pre-populated to shadow the image (mimicking a real host): cloudflare_ips.list repopulates with all 15 IPv4 + 7 IPv6 ranges even after being truncated and restarted; a distinctive operator entry appended to trusted_proxies.list survives a restart untouched; haproxy -c still validates cleanly. Release-worthy fix for a defect from the just-released 2026.08.2 build. Co-Authored-By: Claude Opus 5 (1M context) --- Dockerfile | 11 ++++++++--- VERSION | 2 +- scripts/start-up.sh | 27 +++++++++++++++++++++++++-- 3 files changed, 34 insertions(+), 6 deletions(-) diff --git a/Dockerfile b/Dockerfile index 7b00256..feaf5f0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -31,10 +31,15 @@ COPY haproxy_manager.py /haproxy/ COPY scripts /haproxy/scripts COPY trusted_ips.list /etc/haproxy/trusted_ips.list COPY trusted_ips.map /etc/haproxy/trusted_ips.map -COPY cloudflare_ips.list /etc/haproxy/cloudflare_ips.list -COPY trusted_proxies.list /etc/haproxy/trusted_proxies.list # /etc/haproxy is a named volume in deployed containers, so baked-in files -# under that path get shadowed by the volume on existing deployments. +# under that path get shadowed by the volume on existing deployments. The +# trusted_ips.* pair above predates that discovery and is handled by the +# older start-up.sh guard (out of scope here). cloudflare_ips.list and +# trusted_proxies.list are staged under /haproxy/defaults instead, so +# start-up.sh can always read the image's baked copy regardless of what the +# volume shadows /etc/haproxy with. +COPY cloudflare_ips.list /haproxy/defaults/cloudflare_ips.list +COPY trusted_proxies.list /haproxy/defaults/trusted_proxies.list # Place errorfiles outside the volumed path; the HAProxy config references # them by absolute path. COPY errors /haproxy/errors diff --git a/VERSION b/VERSION index 39af2d4..feaba9e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2026.08.2 +2026.08.3 diff --git a/scripts/start-up.sh b/scripts/start-up.sh index fc648c9..975bd73 100755 --- a/scripts/start-up.sh +++ b/scripts/start-up.sh @@ -21,8 +21,31 @@ set -eo pipefail mkdir -p /etc/haproxy [ -f /etc/haproxy/trusted_ips.list ] || : > /etc/haproxy/trusted_ips.list [ -f /etc/haproxy/trusted_ips.map ] || : > /etc/haproxy/trusted_ips.map -[ -f /etc/haproxy/cloudflare_ips.list ] || : > /etc/haproxy/cloudflare_ips.list -[ -f /etc/haproxy/trusted_proxies.list ] || : > /etc/haproxy/trusted_proxies.list + +# cloudflare_ips.list is SHIPPED DATA: it must always match what this image +# bakes in (/haproxy/defaults), so a Cloudflare range refresh actually reaches +# existing hosts instead of being permanently shadowed by the volume. +# Overwrite it from the baked copy on every start. +# +# trusted_proxies.list is OPERATOR DATA: operators add entries directly on +# the server and those must survive restarts/recreates. Seed it from the +# baked copy only when it's missing; never overwrite an existing one. +# +# Both branches fall back to an empty file if the baked default is somehow +# absent, because "acl ... -f " is a fatal HAProxy config +# error -- the list files must exist unconditionally by the time HAProxy starts. +if [ -f /haproxy/defaults/cloudflare_ips.list ]; then + cp /haproxy/defaults/cloudflare_ips.list /etc/haproxy/cloudflare_ips.list +else + [ -f /etc/haproxy/cloudflare_ips.list ] || : > /etc/haproxy/cloudflare_ips.list +fi +if [ ! -f /etc/haproxy/trusted_proxies.list ]; then + if [ -f /haproxy/defaults/trusted_proxies.list ]; then + cp /haproxy/defaults/trusted_proxies.list /etc/haproxy/trusted_proxies.list + else + : > /etc/haproxy/trusted_proxies.list + fi +fi cron &