feat: publish the Hub through a reverse proxy on another host
GTI terminates TLS in its Nginx Proxy Manager (10.0.10.100), not on the Hub host, so the Hub must be reachable on the LAN — by the proxy only. - docker-compose.prod.yml passes FORWARDED_ALLOW_IPS (uvicorn then trusts X-Forwarded-For/-Proto from the proxy only: real client IPs in audit and session records, https scheme) and WEB_COOKIE_SECURE. Defaults keep the previous behaviour. - restrict_hub_port.sh + gti-fiscal-hub-port.service: Docker-published ports bypass UFW, so the source restriction lives in DOCKER-USER, matched on the conntrack original destination with --ctdir ORIGINAL, and is re-applied whenever docker.service starts (the chain is not persisted across reboots). - *.service forced to LF like *.sh; tests cover both. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -8,9 +8,15 @@ POSTGRES_PASSWORD=
|
||||
BASE_URL=https://fiscal.example.gti
|
||||
|
||||
# Interface the Hub listens on. Keep 127.0.0.1 when a reverse proxy runs on the same host; use the LAN IP only behind a firewall.
|
||||
# Docker-published ports bypass UFW: restrict the source in the DOCKER-USER chain (see docs/PILOT_DEPLOYMENT.md).
|
||||
HUB_BIND=127.0.0.1
|
||||
HUB_PORT=8000
|
||||
|
||||
# IP(s) of the reverse proxy allowed to set X-Forwarded-For/-Proto (comma-separated). 127.0.0.1 = same host.
|
||||
FORWARDED_ALLOW_IPS=127.0.0.1
|
||||
# 1 once HTTPS terminates in front of the Hub (session/CSRF cookies become Secure).
|
||||
WEB_COOKIE_SECURE=0
|
||||
|
||||
LOG_LEVEL=INFO
|
||||
AGENT_ONLINE_THRESHOLD_SECONDS=120
|
||||
|
||||
|
||||
@@ -47,6 +47,11 @@ services:
|
||||
LOG_LEVEL: ${LOG_LEVEL:-INFO}
|
||||
BASE_URL: ${BASE_URL:?BASE_URL is required (public HTTPS URL of the Hub)}
|
||||
AGENT_ONLINE_THRESHOLD_SECONDS: ${AGENT_ONLINE_THRESHOLD_SECONDS:-120}
|
||||
# Reverse proxy on another host: uvicorn only honours X-Forwarded-For/-Proto
|
||||
# from these addresses (real client IP in audit/session records, scheme=https).
|
||||
FORWARDED_ALLOW_IPS: ${FORWARDED_ALLOW_IPS:-127.0.0.1}
|
||||
# 1 = session/CSRF cookies are sent only over HTTPS. Set once TLS is in front.
|
||||
WEB_COOKIE_SECURE: ${WEB_COOKIE_SECURE:-0}
|
||||
# AGENT_DEV_TOKEN is intentionally NOT set: real Agents are registered with central/scripts/register_agent.py.
|
||||
ports:
|
||||
- "${HUB_BIND:-127.0.0.1}:${HUB_PORT:-8000}:8000"
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
# Re-applies the Hub port restriction (restrict_hub_port.sh) whenever Docker starts.
|
||||
# Install:
|
||||
# sudo install -m 0755 restrict_hub_port.sh /usr/local/sbin/gti-fiscal-restrict-hub-port
|
||||
# printf 'HUB_BIND=10.0.10.117\nHUB_PORT=8000\nPROXY_IP=10.0.10.100\nIFACE=ens18\n' | sudo tee /etc/default/gti-fiscal-hub-port
|
||||
# sudo install -m 0644 gti-fiscal-hub-port.service /etc/systemd/system/
|
||||
# sudo systemctl daemon-reload && sudo systemctl enable --now gti-fiscal-hub-port.service
|
||||
[Unit]
|
||||
Description=GTI Fiscal: allow the Hub port only from the reverse proxy
|
||||
After=docker.service
|
||||
Requires=docker.service
|
||||
PartOf=docker.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
RemainAfterExit=yes
|
||||
EnvironmentFile=/etc/default/gti-fiscal-hub-port
|
||||
ExecStart=/usr/local/sbin/gti-fiscal-restrict-hub-port
|
||||
|
||||
[Install]
|
||||
WantedBy=docker.service
|
||||
@@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env sh
|
||||
# Restrict the published Hub port so that only the reverse proxy host reaches it.
|
||||
#
|
||||
# HUB_BIND=10.0.10.117 HUB_PORT=8000 PROXY_IP=10.0.10.100 IFACE=ens18 sh restrict_hub_port.sh
|
||||
#
|
||||
# Docker-published ports never pass through UFW/INPUT: the packets are DNATed
|
||||
# and forwarded, so the only supported place for this rule is DOCKER-USER.
|
||||
# Matching on the conntrack *original* destination selects this published port
|
||||
# (after DNAT the packet already points at the container); --ctdir ORIGINAL keeps
|
||||
# the container's replies out of the DROP rule. Idempotent: safe to re-run.
|
||||
# DOCKER-USER is not persisted across reboots — gti-fiscal-hub-port.service
|
||||
# re-applies it every time docker.service starts.
|
||||
set -eu
|
||||
: "${HUB_BIND:?HUB_BIND is required}" "${PROXY_IP:?PROXY_IP is required}" "${IFACE:?IFACE is required}"
|
||||
HUB_PORT="${HUB_PORT:-8000}"
|
||||
MATCH="-i $IFACE -p tcp -m conntrack --ctdir ORIGINAL --ctorigdst $HUB_BIND --ctorigdstport $HUB_PORT"
|
||||
|
||||
iptables -N DOCKER-USER 2>/dev/null || true
|
||||
while iptables -D DOCKER-USER $MATCH -s "$PROXY_IP" -j RETURN 2>/dev/null; do :; done
|
||||
while iptables -D DOCKER-USER $MATCH -j DROP 2>/dev/null; do :; done
|
||||
iptables -I DOCKER-USER 1 $MATCH -j DROP
|
||||
iptables -I DOCKER-USER 1 $MATCH -s "$PROXY_IP" -j RETURN
|
||||
iptables -S DOCKER-USER
|
||||
Reference in New Issue
Block a user