Files
Gitea/vm/nftables/gitea.nft
T
JMR-devandClaude Opus 5.5 b4fad783e4 nftables: let containers reach aardvark-dns
Caddy could not obtain a certificate on first boot: every request to
acme-v02.api.letsencrypt.org timed out. Containers could reach
1.1.1.1:443 by address but resolved no names at all.

Container DNS goes to aardvark-dns on the bridge gateway (10.89.10.1:53).
That traffic terminates on the host, so it takes the input hook, not
forward. Netavark accepts it in its own table, but gitea_filter's input
chain has policy drop, and a packet must be accepted by every base chain
on the hook. Our drop won.

gitea_filter now accepts tcp/udp 53 from the podman subnet to its
gateway. Both come from instance metadata, so the ruleset is rendered
with envsubst, as the fail2ban jail already is. It is not interface-based
because netavark's bridge name (podman1) is not pinned.

setup_nftables also validates the rendered file before installing it.
Previously it installed first and validated second, so a ruleset that
failed to parse stayed in /etc/sysconfig and would fail nftables.service
on the next boot, leaving the host with no gitea_filter table.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 15:09:49 +07:00

64 lines
2.8 KiB
Plaintext

#!/usr/sbin/nft -f
#
# 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
# rules -- silently breaking all container networking on every reload. We touch
# exactly one table and nothing else.
#
# Equally deliberate: there is NO forward chain here. Netavark manages forward
# and nat rules in its own `netavark` table. A drop-policy forward chain in this
# table would drop container traffic regardless of what netavark allows, because
# a packet is dropped if any chain drops it.
#
# Traffic to published container ports (443, 2222) is DNAT'd in prerouting and
# traverses forward, never input -- so the input rules below only actually
# govern host-terminated traffic. Blocking abusive clients from container ports
# is fail2ban's job, via the prerouting-hook action in
# vm/fail2ban/action.d/nft-prerouting.conf.
# Create-then-delete makes this reload idempotent: the bare `table` line is a
# no-op if it already exists, so the delete can never fail on a fresh boot.
table inet gitea_filter
delete table inet gitea_filter
table inet gitea_filter {
chain input {
type filter hook input priority filter; policy drop;
iif "lo" accept comment "loopback"
ct state established,related accept
ct state invalid drop
meta l4proto icmp accept comment "IPv4 ICMP"
meta l4proto ipv6-icmp accept comment "IPv6 ICMP / NDP"
# 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"
# Only reached when Caddy runs with Network=host (the metadata-server
# fallback path). Harmless otherwise: on the bridge these are DNAT'd
# before input and never match here.
tcp dport { 80, 443 } accept comment "HTTP/HTTPS"
udp dport 443 accept comment "HTTP/3"
tcp dport 2222 accept comment "git over SSH"
# Rate-limited logging so a scan cannot fill the disk via journald.
limit rate 5/minute burst 10 packets log prefix "nft-drop-in: " level info
}
}