Files
watchtower-observability-stack/ansible/roles/firewall/templates/watchtower.nft.j2
T

105 lines
3.5 KiB
Django/Jinja

#!/usr/sbin/nft -f
# Watchtower host firewall — explicit nftables ruleset.
#
# Layered defense:
# 1. Hetzner Cloud Firewall (edge) — allows ssh + wg from admin CIDRs
# 2. This nftables ruleset (host) — same policy + state tracking + rate limits
# 3. fail2ban — adds dynamic bans into table inet f2b-table
# 4. WireGuard — only authenticated peers reach services
# 5. Podman PublishPort=10.8.0.1:* — services bound to wg0 only
#
# Reload with: systemctl reload nftables
# Test parse with: nft --check --file /etc/nftables/watchtower.nft
# Flush only our own table so we don't disturb fail2ban or podman tables.
table inet watchtower
delete table inet watchtower
table inet watchtower {
set admin_v4 {
type ipv4_addr
flags interval
elements = {
{% for cidr in admin_allow_ipv4 %}
{{ cidr }}{% if not loop.last %},{% endif %}
{% endfor %}
}
}
set admin_v6 {
type ipv6_addr
flags interval
elements = {
{% for cidr in admin_allow_ipv6 %}
{{ cidr }}{% if not loop.last %},{% endif %}
{% endfor %}
}
}
chain input {
type filter hook input priority filter; policy drop;
# Stateful base
ct state established,related accept
ct state invalid drop
# Loopback
iif "lo" accept
# ICMP / ICMPv6 (rate-limited)
ip protocol icmp limit rate 20/second accept
ip6 nexthdr icmpv6 limit rate 20/second accept
# Anything arriving on WireGuard is treated as trusted (only authenticated
# peers can reach this interface). Service ports are bound to 10.8.0.1.
# This also implicitly allows the wg-easy web UI on TCP 51821, which
# is bound to 10.8.0.1 and therefore only reachable from VPN peers.
iifname "{{ wireguard_interface }}" accept
# Podman bridge networks: allow the kernel to deliver packets the
# netavark/aardvark stack creates for inter-container DNS, etc.
# Podman manages its own filter rules in separate tables.
iifname "podman*" accept
iifname "cni-*" accept
# SSH from admin CIDRs only — short-burst rate limit deters scanners.
# fail2ban will additionally ban repeat offenders.
ip saddr @admin_v4 tcp dport 22 ct state new limit rate 6/minute accept
ip6 saddr @admin_v6 tcp dport 22 ct state new limit rate 6/minute accept
# WireGuard listen port from admin CIDRs.
ip saddr @admin_v4 udp dport {{ wireguard_listen_port }} accept
ip6 saddr @admin_v6 udp dport {{ wireguard_listen_port }} accept
# Log a sample of dropped packets so we know if something is mis-routed.
limit rate 5/minute log prefix "nft-drop-input: " level info
counter drop
}
chain forward {
type filter hook forward priority filter; policy drop;
ct state established,related accept
ct state invalid drop
# Allow forwarding for Podman-managed bridges. Podman/netavark also
# installs its own rules; this is a safety allow-list.
iifname "podman*" accept
oifname "podman*" accept
iifname "cni-*" accept
oifname "cni-*" accept
# WireGuard egress to services on the host loopback / podman:
iifname "{{ wireguard_interface }}" accept
oifname "{{ wireguard_interface }}" accept
counter drop
}
chain output {
type filter hook output priority filter; policy accept;
}
}