#!/usr/bin/env bash
# The belt-and-braces rule for the browser sidecar. Running list `do-sidecar-firewall-rule`.
# OWNER-APPROVED ONLY: a firewall change. Nothing runs this automatically.
#
# Why: the sidecar is a headless Chromium that loads retailer pages. Its code refuses raw
# IP addresses and private hostnames, but a hostname resolves to whatever its DNS owner
# says (DNS rebinding): a compromised or hostile retailer name could point at a private
# address and the browser would fetch it. On a cloud host the prize is the metadata
# service at 169.254.169.254, then the Docker bridge and the tailnet. This rule drops any
# NEW connection from the render network to those ranges at the kernel, regardless of
# what the code decided. Traffic within the render network itself (the app talking to the
# browser, and the replies) passes; the first two rules return it before the drops.
#
# Install as a oneshot after Docker so the chain survives reboots and Docker restarts:
#   sudo install -m 755 deploy/sidecar-firewall.sh /usr/local/sbin/sidecar-firewall
#   sudo tee /etc/systemd/system/sidecar-firewall.service > /dev/null << 'UNIT'
#   [Unit]
#   Description=Fence the browser sidecar network off private ranges
#   After=docker.service
#   Requires=docker.service
#   [Service]
#   Type=oneshot
#   ExecStart=/usr/local/sbin/sidecar-firewall
#   RemainAfterExit=yes
#   [Install]
#   WantedBy=multi-user.target
#   UNIT
#   sudo systemctl enable --now sidecar-firewall
# Verify: sudo iptables -L DOCKER-USER -n --line-numbers
set -euo pipefail
RENDER_NET="${RENDER_NET:-172.30.0.0/24}"    # pinned in docker-compose.production.yml
COMMENT="dfp-sidecar-fence"

# Idempotent: flush our own rules (by comment) before inserting.
while n=$(iptables -L DOCKER-USER -n --line-numbers 2>/dev/null | grep "$COMMENT" | head -1 | awk '{print $1}'); [ -n "$n" ]; do
  iptables -D DOCKER-USER "$n"
done
iptables -N DOCKER-USER 2>/dev/null || true

add() { iptables -I DOCKER-USER "$1" "${@:2}" -m comment --comment "$COMMENT"; }
# Rules are inserted at position 1 in reverse order, so the last add() is evaluated first.
add 1 -s "$RENDER_NET" -d 192.168.0.0/16   -m conntrack --ctstate NEW -j DROP
add 1 -s "$RENDER_NET" -d 172.16.0.0/12    -m conntrack --ctstate NEW -j DROP   # docker bridges incl. the app network
add 1 -s "$RENDER_NET" -d 10.0.0.0/8       -m conntrack --ctstate NEW -j DROP
add 1 -s "$RENDER_NET" -d 100.64.0.0/10    -m conntrack --ctstate NEW -j DROP   # tailnet
add 1 -s "$RENDER_NET" -d 169.254.0.0/16   -m conntrack --ctstate NEW -j DROP   # cloud metadata
add 1 -s "$RENDER_NET" -d 127.0.0.0/8      -m conntrack --ctstate NEW -j DROP
for ip in $(hostname -I | tr " " "\n" | grep -E "^[0-9.]+$"); do   # IPv4 only: this is iptables, not ip6tables
  add 1 -s "$RENDER_NET" -d "$ip" -m conntrack --ctstate NEW -j DROP            # the host's own addresses
done
# The app is on the render network too (that is how it reaches the browser), so traffic
# inside that one subnet passes; everything else private is dropped.
add 1 -s "$RENDER_NET" -d "$RENDER_NET" -j RETURN
add 1 -m conntrack --ctstate ESTABLISHED,RELATED -j RETURN
iptables -L DOCKER-USER -n --line-numbers | grep -c "$COMMENT" | xargs -I{} echo "{} sidecar fence rules in DOCKER-USER"
