Watch
0
0
Fork
You've already forked hyperhive
0

egress anti-spoofing: keep established bindings, load tables with bindings and leases in one transaction

This commit is contained in:
atlas 2026-11-13 02:53:37 +01:00 • committed by mara
commit 70179b6c1f
2 changed files with 400 additions and 134 deletions

View file

@ -2,7 +2,7 @@
# ../host-modules/hive-egress.nix on a real agent bridge, with two nspawn
# containers on it that take their addresses from the hive's dnsmasq
# (../host-modules/hive-gateway/dnsmasq.nix' lease script, the real
# `hive-priv egress-pin` and `egress-lease`). `hive-squid` cannot host this: its
# `hive-priv egress-load`, `egress-pin` and `egress-lease`). `hive-squid` cannot host this: its
# stand-in bridge is one NIC, so every agent shares one port.
#
# What it pins:
@ -14,9 +14,10 @@
# that is not link-local and a DHCP message with another's `chaddr` are
# dropped, and each leaves a `hive-egress-spoof ` line that names the port;
# - a DHCP renewal, a link-local IPv6 packet and a restored MAC go through;
# - a port with no bound MAC sends nothing, and the pin unit binds it again;
# - `antiSpoof = "log"`, reached by a reload of the unit, keeps the bindings
# and the leases, lets the same spoofs through and logs them.
# - a port with no bound MAC sends nothing, and the timer binds it again;
# - `antiSpoof = "log"`, reached by a reload of the unit, brings the bindings
# and the leases with the new table (nothing is dropped in between),
# lets the same spoofs through and logs them.
{ pkgs, hivePriv }:
let
bridge = "hive0";
@ -265,20 +266,21 @@ pkgs.testers.runNixOSTest {
host.wait_until_succeeds(f"nft list set {TABLE} lease_v4 | grep -q '{MAC_A} . {IP_A}'")
assert ct_ok("a", "ping -c1 -W2 ${gateway}")
with subtest("a port with no bound MAC sends nothing, and the pin unit binds it again"):
with subtest("a port with no bound MAC sends nothing, and the timer binds it again"):
host.succeed(f"nft flush set {TABLE} port_mac")
assert not ct_ok("a", "ping -c1 -W2 ${gateway}")
assert not ct_ok("b", "ping -c1 -W2 ${gateway}")
host.succeed("systemctl start hive-egress-pin@vb-a.service hive-egress-pin@vb-b.service")
# Nothing starts the pin unit by hand: the timer finds the unbound ports.
host.wait_until_succeeds("nixos-container run a -- ping -c1 -W2 ${gateway}")
host.wait_until_succeeds("nixos-container run b -- ping -c1 -W2 ${gateway}")
with subtest("antiSpoof = log keeps the bindings and the leases, lets a spoofed datagram through and logs it"):
host.succeed("/run/current-system/specialisation/logmode/bin/switch-to-configuration test")
assert "counter accept" in host.succeed(f"nft list chain {TABLE} spoof"), "the table was reloaded in log mode"
host.wait_until_succeeds(f"nft list set {TABLE} port_mac | grep -q '\"vb-a\" . {MAC_A}'")
host.wait_until_succeeds(f"nft list set {TABLE} lease_v4 | grep -q '{MAC_B} . {IP_B}'")
assert ct_ok("a", "ping -c1 -W2 ${gateway}"), "control: own address, after the reload"
# No waiting: the reload put the sets in with the table.
host.succeed(f"nft list set {TABLE} port_mac | grep -q '\"vb-a\" . {MAC_A}'")
host.succeed(f"nft list set {TABLE} lease_v4 | grep -q '{MAC_B} . {IP_B}'")
assert ct_ok("a", "ping -c1 -W2 ${gateway}"), "control: own address, right after the reload"
seen = len(spoof_lines())
ct("a", f"ip addr add {IP_B}/24 dev eth0")
assert udp_reaches_host("a", IP_B), "log mode lets the spoofed datagram through"

View file

@ -197,10 +197,14 @@ pkgs.testers.runNixOSTest {
);
# The gateway: nginx answers on the bridge, which agents reach directly.
networking.firewall.interfaces.eth1.allowedTCPPorts = [ 443 ];
# hive-priv.nix links the binary the hive-egress unit runs to bind bridge
# ports; `eth1` is no bridge here, so a stub that binds nothing stands in.
# hive-priv.nix links the binary the hive-egress unit loads its tables with;
# `eth1` is no bridge here, so a stub that only loads the ruleset (no
# bindings, no leases) stands in.
systemd.tmpfiles.rules = [
"L+ /run/hive-rootfs-check - - - - ${pkgs.writeShellScript "hive-priv-stub" "exit 0"}"
"L+ /run/hive-rootfs-check - - - - ${pkgs.writeShellScript "hive-priv-stub" ''
[ "$1" = egress-load ] && exec ${pkgs.nftables}/bin/nft -f "$2"
exit 0
''}"
];
# Renders the users file the way hive-c0re does.
environment.systemPackages = [