Initial Commit
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
# =====================================================================
|
||||
# Second-layer egress filter for the VM sandbox.
|
||||
#
|
||||
# WHY THIS IS THE SECOND LAYER AND NOT THE FIRST
|
||||
#
|
||||
# The primary control is in owuivm.service: systemd's IPAddressDeny=
|
||||
# attaches a BPF filter to the unit's cgroup, which `nft flush ruleset`
|
||||
# cannot reach and which applies to every socket the process opens.
|
||||
# This table says the same thing a second time, independently.
|
||||
#
|
||||
# It is deliberately NOT trusted on its own. nft uid-matching is
|
||||
# fail-open: a `ufw reload`, an `nft -f` carrying `flush ruleset`, a
|
||||
# package upgrade, or this unit simply failing to start, and the rules
|
||||
# are gone with nothing to notice. netguard/firewall.sh exists in this
|
||||
# suite for exactly that class of failure - "a firewall that silently
|
||||
# failed to apply is worse than none, because everything downstream
|
||||
# carries on as though it were enforced".
|
||||
#
|
||||
# So the test that proves the design is not "these rules are loaded".
|
||||
# It is: delete this table, re-run the whole guest negative suite, and
|
||||
# watch it still pass. Then strip IPAddressDeny from a scratch unit and
|
||||
# run it again. Two proofs, neither trusting the other.
|
||||
#
|
||||
# WHAT IT MATCHES
|
||||
#
|
||||
# Every guest packet is re-originated by the qemu process as an ordinary
|
||||
# host socket (see the -netdev user decision in vmd), so the whole guest
|
||||
# is reachable as "traffic owned by uid owuivm". There is no tap, no
|
||||
# bridge and no forwarding path, which is why nothing here hooks forward.
|
||||
#
|
||||
# Note 10.0.0.0/8 does NOT block the guest's own 10.0.2.x addresses.
|
||||
# Those exist only inside libslirp and never become host packets. The
|
||||
# 127.0.0.0/8 line is what closes SLIRP's host aliases - 10.0.2.2 is the
|
||||
# host's loopback and 10.0.2.3 its DNS forwarder - which is why the
|
||||
# guest images carry a static public resolver instead of asking DHCP.
|
||||
# =====================================================================
|
||||
|
||||
table inet owuivm_guard {
|
||||
set rfc1918_and_local {
|
||||
type ipv4_addr
|
||||
flags interval
|
||||
elements = {
|
||||
0.0.0.0/8,
|
||||
10.0.0.0/8,
|
||||
100.64.0.0/10,
|
||||
127.0.0.0/8,
|
||||
169.254.0.0/16,
|
||||
172.16.0.0/12,
|
||||
192.0.0.0/24,
|
||||
192.168.0.0/16,
|
||||
224.0.0.0/4,
|
||||
240.0.0.0/4
|
||||
}
|
||||
}
|
||||
|
||||
chain out {
|
||||
# priority filter - 10, so this runs ahead of ufw's own output
|
||||
# chain. policy accept: this table only ever drops, exactly like
|
||||
# /etc/nftables.d/50-node_exporter_guard.nft does for input.
|
||||
type filter hook output priority filter - 10; policy accept;
|
||||
|
||||
meta skuid "owuivm" ip daddr @rfc1918_and_local \
|
||||
counter log prefix "owuivm-blocked: " level info drop
|
||||
|
||||
# The guest has no IPv6 at all: the netdev is ipv6=off and the
|
||||
# unit omits AF_INET6 from RestrictAddressFamilies. This is the
|
||||
# third statement of the same thing, and it costs nothing.
|
||||
meta skuid "owuivm" meta nfproto ipv6 counter drop
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user