diff --git a/vm/bootstrap.sh b/vm/bootstrap.sh index 4636ba3..6eb49fd 100755 --- a/vm/bootstrap.sh +++ b/vm/bootstrap.sh @@ -198,8 +198,21 @@ setup_nftables() { systemctl disable --now firewalld >/dev/null 2>&1 || true systemctl mask firewalld >/dev/null 2>&1 || true - install -m 0600 "${STATE_DIR}/nftables/gitea.nft" /etc/sysconfig/nftables.conf - nft -c -f /etc/sysconfig/nftables.conf || die "nftables ruleset failed validation" + [[ -n "${PODMAN_SUBNET}" && -n "${PODMAN_GATEWAY}" ]] \ + || die "podman-subnet/podman-gateway metadata missing; cannot render the ruleset" + + # Validate BEFORE installing. A ruleset that fails to parse must never land + # in /etc/sysconfig: nftables.service would fail to load it on the next boot + # and the host would come up with no gitea_filter table at all. + local tmp + tmp=$(mktemp) + envsubst '${PODMAN_SUBNET} ${PODMAN_GATEWAY}' < "${STATE_DIR}/nftables/gitea.nft" > "${tmp}" + if ! nft -c -f "${tmp}"; then + rm -f "${tmp}" + die "nftables ruleset failed validation" + fi + install -m 0600 "${tmp}" /etc/sysconfig/nftables.conf + rm -f "${tmp}" systemctl enable --now nftables systemctl reload nftables diff --git a/vm/nftables/gitea.nft b/vm/nftables/gitea.nft index 2f547fd..0405129 100644 --- a/vm/nftables/gitea.nft +++ b/vm/nftables/gitea.nft @@ -1,6 +1,7 @@ #!/usr/sbin/nft -f # -# Host firewall. Installed by vm/bootstrap.sh as /etc/sysconfig/nftables.conf. +# Host firewall. Rendered by vm/bootstrap.sh into /etc/sysconfig/nftables.conf, +# substituting ${PODMAN_SUBNET} and ${PODMAN_GATEWAY}. # # CRITICAL: this file must NEVER contain `flush ruleset`. The stock nftables # config ships with one, and it would wipe podman/netavark's NAT and forward @@ -37,6 +38,14 @@ table inet gitea_filter { # DHCP renewal from the GCE metadata server. udp sport 67 udp dport 68 accept + # Container DNS. aardvark-dns answers on the bridge gateway, so lookups + # from containers terminate on the host and arrive here, not in forward. + # Netavark accepts them in its own table, but a packet has to survive + # every input-hook chain, and this one's drop policy would discard it + # anyway. Without this rule containers resolve nothing -- Caddy cannot + # reach Let's Encrypt and Gitea cannot reach webhook or mirror hosts. + ip saddr ${PODMAN_SUBNET} ip daddr ${PODMAN_GATEWAY} meta l4proto { tcp, udp } th dport 53 accept comment "container DNS" + # Admin SSH: IAP TCP forwarding range only. There is no other path in -- # the VPC firewall enforces the same restriction as the outer layer. ip saddr 35.235.240.0/20 tcp dport 22 accept comment "IAP SSH"