mirror of
https://github.com/jmcorgan/fips.git
synced 2026-10-30 07:37:36 +00:00
The gateway how-to said the mesh firewall baseline gates port forwards and that a fips.d drop-in restricts them. It does neither: the gateway rewrites a forwarded connection's destination in prerouting, so the packet takes the forward path, while fips.nft and its drop-ins hook input only. With the baseline loaded, every mesh node that reaches the gateway reaches every port forward. The how-to now says so and gives a restriction that works: a separate table with a forward-hook chain that matches the post-DNAT target address and port, scoped to the mesh interface through its own fips_if define, ending in a drop of other translated connections, loaded alongside the baseline through a fips-firewall.service drop-in. On OpenWrt it points at the packaged zone's src fips rule instead. The mesh firewall how-to and the fips.nft header say port forwards are outside the baseline, and the how-to's TUN rename section says to change the restriction's fips_if too. The gateway troubleshooting step and the host-a-service tutorial no longer describe the input baseline as gating port forwards. Also corrected: the gateway design document attributed the virtual-IP source on replies to the per-mapping SNAT, which only sees the first packet of a new flow sourced from a mapped mesh address; conntrack reversing the DNAT is what does it. It also said port forwards are validated on every config reload, but the gateway has no reload. The configuration reference said the gateway terminates forwarded connections; the kernel translates and forwards them.
146 lines
6.5 KiB
Plaintext
146 lines
6.5 KiB
Plaintext
#!/usr/sbin/nft -f
|
|
#
|
|
# ╔═══════════════════════════════════════════════════════════════════╗
|
|
# ║ fips0 nftables policy — security baseline ║
|
|
# ╚═══════════════════════════════════════════════════════════════════╝
|
|
#
|
|
# Polices ONLY the FIPS mesh interface named by `fips_if` below —
|
|
# every packet not arriving on it returns immediately, leaving Docker,
|
|
# Tor, OPNsense, the host's main /etc/nftables.conf, and any other
|
|
# firewall undisturbed.
|
|
#
|
|
# ─── THE MESH INTERFACE NAME ────────────────────────────────────────
|
|
#
|
|
# `fips_if` must equal the daemon's `tun.name` in fips.yaml (default
|
|
# "fips0"). If you rename the TUN, change the define to match and
|
|
# reload. Until you do, this table polices an interface that does not
|
|
# carry mesh traffic, and every listener on the renamed TUN is
|
|
# reachable from the mesh. The daemon logs a warning at startup when
|
|
# the loaded table is scoped to another interface, and fipstop's
|
|
# listening panel reports every listener as exposed.
|
|
#
|
|
# ─── DEFAULT POLICY ─────────────────────────────────────────────────
|
|
#
|
|
# INBOUND on mesh: DROP everything except —
|
|
# • replies to outbound flows we initiated
|
|
# (conntrack established/related)
|
|
# • ICMPv6 echo-request (ping6 reachability)
|
|
# • rules in /etc/fips/fips.d/*.nft drop-ins
|
|
#
|
|
# OUTBOUND from mesh: unrestricted
|
|
#
|
|
# Why default-deny: services binding to 0.0.0.0 or [::] accept
|
|
# connections on the mesh interface by default. This baseline ensures that
|
|
# accidental exposure of HTTP, SSH, databases, etc. through the
|
|
# mesh is impossible without explicit operator action.
|
|
#
|
|
# ─── LOADING / RELOADING ────────────────────────────────────────────
|
|
#
|
|
# Apply this file:
|
|
#
|
|
# sudo nft -f /etc/fips/fips.nft
|
|
#
|
|
# To make it persist across reboots, load it from your preferred
|
|
# mechanism: a systemd unit that runs `nft -f` on start, an include
|
|
# from /etc/nftables.conf, etc.
|
|
#
|
|
# Verify the loaded ruleset:
|
|
#
|
|
# sudo nft list table inet fips
|
|
#
|
|
# ─── ADDING INBOUND ALLOWANCES ──────────────────────────────────────
|
|
#
|
|
# Drop a file into /etc/fips/fips.d/ ending in .nft. Each file is
|
|
# included inline into the `inbound` chain at the marked point below.
|
|
# Format: chain-context rule lines, one per line. For example:
|
|
#
|
|
# /etc/fips/fips.d/services.nft
|
|
# ────────────────────────────
|
|
# tcp dport 22 accept
|
|
# tcp dport 80 accept
|
|
# udp dport 53 accept
|
|
# ip6 saddr fd97:467a::/64 tcp dport 22 accept # SSH from one /64
|
|
# ip6 saddr fd97:.../128 tcp dport 8443 accept # one specific peer
|
|
#
|
|
# After editing, reload:
|
|
#
|
|
# sudo nft -f /etc/fips/fips.nft
|
|
#
|
|
# (or trigger your loading mechanism's reload).
|
|
#
|
|
# Source-based filtering uses the peer's mesh address (npub-derived
|
|
# fd97:... format). To find a peer's mesh address, look in their
|
|
# fips.pub or query the daemon.
|
|
#
|
|
# Note: an empty /etc/fips/fips.d/ is fine — the include glob simply
|
|
# matches nothing and the baseline policy applies as-is.
|
|
#
|
|
# ─── DEBUGGING / DROP VISIBILITY ────────────────────────────────────
|
|
#
|
|
# To see drops in the systemd journal:
|
|
# 1. Uncomment the `log` rule near the bottom of the inbound chain.
|
|
# 2. Reload: sudo nft -f /etc/fips/fips.nft
|
|
# 3. Tail: sudo journalctl -k -f -g "fips drop:"
|
|
#
|
|
# Drop counter (always on, no log):
|
|
# sudo nft list table inet fips
|
|
#
|
|
# ─── COEXISTENCE WITH OTHER NFTABLES TABLES ─────────────────────────
|
|
#
|
|
# This table lives in the `inet` family. Other tables you may see:
|
|
#
|
|
# inet fips_gateway — created by the fips-gateway binary when
|
|
# running (separate concern from this file).
|
|
# See the fips-gateway design doc for details.
|
|
# Its port forwards are forwarded traffic, which
|
|
# this input chain never sees; to restrict them,
|
|
# see docs/how-to/deploy-gateway.md.
|
|
#
|
|
# ip / ip6 family — Docker, /etc/nftables.conf, etc.
|
|
# Independent of this table.
|
|
#
|
|
# All hook at standard filter priority (0). Order between same-family
|
|
# same-priority tables is determined by load order, but the early-
|
|
# return for non-mesh traffic makes this chain a no-op for anything
|
|
# not on the mesh interface, so coexistence is safe.
|
|
|
|
# The mesh interface this table polices. Must equal `tun.name`.
|
|
define fips_if = "fips0"
|
|
|
|
# ━━━ Idempotent table replacement ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
add table inet fips
|
|
flush table inet fips
|
|
|
|
table inet fips {
|
|
|
|
chain inbound {
|
|
type filter hook input priority 0; policy accept;
|
|
|
|
# Non-mesh traffic returns immediately — chain is a no-op
|
|
# for anything not arriving on the mesh interface.
|
|
iifname != $fips_if return
|
|
|
|
# Replies to outbound flows + related ICMPv6 errors
|
|
# (Packet Too Big for PMTU, Destination Unreachable, etc.)
|
|
ct state established,related accept
|
|
|
|
# ICMPv6 echo-request — ping6 reachability tests.
|
|
meta nfproto ipv6 icmpv6 type echo-request accept
|
|
|
|
# Operator-supplied allow rules from drop-in files.
|
|
# See "ADDING INBOUND ALLOWANCES" in the header.
|
|
include "/etc/fips/fips.d/*.nft"
|
|
|
|
# Optional: log drops to journal (rate-limited).
|
|
# Uncomment to enable, then reload via `nft -f`.
|
|
#
|
|
#log prefix "fips drop: " level info limit rate 10/minute
|
|
|
|
# Default deny on fips0 inbound. Counter visible via
|
|
# `nft list table inet fips`.
|
|
counter drop
|
|
}
|
|
}
|
|
|
|
# ━━━ END OF FILE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|