diff --git a/deploy/systemd/docker.service.d/mealquest-firewall.conf b/deploy/systemd/docker.service.d/mealquest-firewall.conf deleted file mode 100644 index afe3355..0000000 --- a/deploy/systemd/docker.service.d/mealquest-firewall.conf +++ /dev/null @@ -1,3 +0,0 @@ -# Re-apply the MealQuest DOCKER-USER rules after every docker (re)start. -[Service] -ExecStartPost=/usr/local/sbin/mealquest-firewall diff --git a/deploy/systemd/mealquest-firewall.service b/deploy/systemd/mealquest-firewall.service index fd98254..c7c5f8e 100644 --- a/deploy/systemd/mealquest-firewall.service +++ b/deploy/systemd/mealquest-firewall.service @@ -1,7 +1,17 @@ +# Applies the MealQuest DOCKER-USER rules (/usr/local/sbin/mealquest-firewall). +# +# PartOf= re-runs this unit whenever docker.service is stopped or restarted, +# and WantedBy=docker.service starts it whenever docker.service starts. Both +# are one-way: a failure here fails this unit visibly and never fails +# docker.service. +# +# Start MealQuest only after this unit has succeeded: +# systemctl start mealquest-firewall && docker compose up -d [Unit] Description=Restrict MealQuest port 8090 to Traefik (DOCKER-USER) After=docker.service Requires=docker.service +PartOf=docker.service [Service] Type=oneshot @@ -9,4 +19,4 @@ RemainAfterExit=yes ExecStart=/usr/local/sbin/mealquest-firewall [Install] -WantedBy=multi-user.target +WantedBy=multi-user.target docker.service diff --git a/deploy/systemd/mealquest-firewall.sh b/deploy/systemd/mealquest-firewall.sh index ca0ff66..361668c 100755 --- a/deploy/systemd/mealquest-firewall.sh +++ b/deploy/systemd/mealquest-firewall.sh @@ -1,21 +1,29 @@ #!/bin/sh -# Restrict the MealQuest published host port to Traefik (10.60.1.33) and the -# Docker host itself (10.60.1.14); drop every other source. +# Restrict the MealQuest published host port (10.60.1.14:8090) to Traefik +# (10.60.1.33) and the Docker host itself (10.60.1.14); drop every other +# source. # -# DOCKER-USER runs after Docker's DNAT, so the destination port there is the -# container port (8080), not the published one. Match the original -# destination port with conntrack instead of --dport. +# DOCKER-USER runs after Docker's DNAT, so the destination there is the +# container address and port, not the published ones. Match the original +# destination address and port with conntrack instead of -d/--dport. # -# Idempotent: each rule is checked (-C) before it is added, so re-runs add -# nothing. Installed as /usr/local/sbin/mealquest-firewall. +# The rules must sit at the top of DOCKER-USER, ahead of any other project's +# rules (an earlier RETURN or DROP would decide MealQuest traffic first). +# Every run deletes all rules tagged with the mealquest-firewall comment and +# re-inserts them at positions 1-3, so re-runs leave the same ordered rules +# and no duplicates. Installed as /usr/local/sbin/mealquest-firewall. set -eu -port=8090 -match="-p tcp -m conntrack --ctorigdstport $port --ctdir ORIGINAL" +tag="-m comment --comment mealquest-firewall" +match="-m conntrack --ctorigdst 10.60.1.14 --ctorigdstport 8090 --ctdir ORIGINAL" -iptables -C DOCKER-USER -s 10.60.1.33/32 $match -j RETURN 2>/dev/null || - iptables -I DOCKER-USER 1 -s 10.60.1.33/32 $match -j RETURN -iptables -C DOCKER-USER -s 10.60.1.14/32 $match -j RETURN 2>/dev/null || - iptables -I DOCKER-USER 2 -s 10.60.1.14/32 $match -j RETURN -iptables -C DOCKER-USER $match -j DROP 2>/dev/null || - iptables -A DOCKER-USER $match -j DROP +rules=$(iptables -w -S DOCKER-USER) +printf '%s\n' "$rules" | while read -r _ chain spec; do + case " $spec " in + *" --comment mealquest-firewall "*) iptables -w -D "$chain" $spec ;; + esac +done + +iptables -w -I DOCKER-USER 1 -s 10.60.1.33/32 $match $tag -j RETURN +iptables -w -I DOCKER-USER 2 -s 10.60.1.14/32 $match $tag -j RETURN +iptables -w -I DOCKER-USER 3 $match $tag -j DROP