53 lines
2.5 KiB
Bash
53 lines
2.5 KiB
Bash
|
|
#!/usr/bin/env bash
|
||
|
|
# Restrict wg-easy's admin API (tcp/51821) to loopback and the gateway's own
|
||
|
|
# docker bridge network. Run this once on the VPS after `docker compose up
|
||
|
|
# -d`, and again if the "internal" network's subnet ever changes.
|
||
|
|
#
|
||
|
|
# Why this exists: wg-easy runs with network_mode: host (see
|
||
|
|
# docker-compose.yaml's comment on why — Traefik needs a route to
|
||
|
|
# WireGuard peer addresses that only exists in whatever network namespace
|
||
|
|
# wg-easy's wg0 interface lives in). Host networking means Docker itself
|
||
|
|
# can no longer keep wg-easy's admin port off the VPS's public interface
|
||
|
|
# the way it does for every other container here — this script is that
|
||
|
|
# missing piece, done with iptables instead.
|
||
|
|
#
|
||
|
|
# NOT yet validated against a real multi-interface VPS. Review
|
||
|
|
# DOCKER_BRIDGE_SUBNET against your actual `docker network inspect
|
||
|
|
# furtka-gateway_internal` output before relying on this as your only line
|
||
|
|
# of defense. A cloud provider security group that blocks 51821/tcp
|
||
|
|
# entirely is a good belt-and-suspenders addition on top of this — wg-easy's
|
||
|
|
# admin API has no legitimate reason to ever be reached from outside this
|
||
|
|
# host.
|
||
|
|
#
|
||
|
|
# IPv4 only. If this VPS also has a public IPv6 address, ip6tables needs
|
||
|
|
# the equivalent rules added by hand — not yet handled here.
|
||
|
|
|
||
|
|
set -euo pipefail
|
||
|
|
|
||
|
|
ADMIN_PORT=51821
|
||
|
|
# Must match docker-compose.yaml's networks.internal.ipam.config subnet.
|
||
|
|
DOCKER_BRIDGE_SUBNET="172.28.0.0/24"
|
||
|
|
|
||
|
|
if [[ $EUID -ne 0 ]]; then
|
||
|
|
echo "must be run as root" >&2
|
||
|
|
exit 1
|
||
|
|
fi
|
||
|
|
|
||
|
|
# Insert ACCEPT rules at the head of the chain, then append DROP at the
|
||
|
|
# tail — this ordering holds regardless of how many times the script runs,
|
||
|
|
# since -I always lands above whatever's already there (including a
|
||
|
|
# previous run's DROP) and -A always lands below.
|
||
|
|
iptables -C INPUT -p tcp --dport "$ADMIN_PORT" -s 127.0.0.1 -j ACCEPT 2>/dev/null \
|
||
|
|
|| iptables -I INPUT -p tcp --dport "$ADMIN_PORT" -s 127.0.0.1 -j ACCEPT
|
||
|
|
|
||
|
|
iptables -C INPUT -p tcp --dport "$ADMIN_PORT" -s "$DOCKER_BRIDGE_SUBNET" -j ACCEPT 2>/dev/null \
|
||
|
|
|| iptables -I INPUT -p tcp --dport "$ADMIN_PORT" -s "$DOCKER_BRIDGE_SUBNET" -j ACCEPT
|
||
|
|
|
||
|
|
iptables -C INPUT -p tcp --dport "$ADMIN_PORT" -j DROP 2>/dev/null \
|
||
|
|
|| iptables -A INPUT -p tcp --dport "$ADMIN_PORT" -j DROP
|
||
|
|
|
||
|
|
echo "wg-easy admin API (tcp/$ADMIN_PORT) now restricted to loopback + $DOCKER_BRIDGE_SUBNET"
|
||
|
|
echo "NOTE: these rules do not persist across reboot on most distros —"
|
||
|
|
echo "install iptables-persistent (Debian/Ubuntu) or an equivalent, or add"
|
||
|
|
echo "this script to a boot-time hook."
|