Watch
0
0
Fork
You've already forked hyperhive
0

remove the swarm wireguard mesh; tombstone its options

This commit is contained in:
atlas 2026-11-15 02:17:44 +01:00
commit adc61c9043
2 changed files with 330 additions and 1068 deletions

View file

@ -896,16 +896,6 @@ pkgs.testers.runNixOSTest {
"'hive-egress-would-drop IN=eth1 OUT=eth2 .*SRC=fd00:0*98:(0+:)*0*3 DST=fd00:0*99:(0+:)*0*2 .*PROTO=TCP .*DPT=443 '"
)
with checked("a flow to the mesh interface is not logged and one to the uplink is"):
proxy.succeed("ip link add wg-hive type dummy && ip link set wg-hive up")
proxy.succeed("ip route add 198.51.100.0/24 dev wg-hive")
agent.execute("echo x > /dev/udp/198.51.100.7/9; echo x > /dev/udp/203.0.113.77/9")
proxy.wait_until_succeeds(
"journalctl -k -o cat --no-pager | grep -E "
"'hive-egress-would-drop IN=eth1 OUT=eth2 .*DST=203\\.0\\.113\\.77 '"
)
assert not would_drop(r"DST=198\.51\.100\.7 "), "the mesh interface is not egress"
with checked("one busy agent address is cut to a burst plus a trickle, the rest still leave a trace, and every flow is logged once at most"):
agent.execute("for i in $(seq 1 100); do echo x > /dev/udp/203.0.113.78/$((3000 + i)); done")
proxy.wait_until_succeeds(
@ -1176,11 +1166,6 @@ pkgs.testers.runNixOSTest {
# ---- Reject mode ----
# The node switches to its `reject` specialisation, which reloads the
# tables with the refusing forward chain. Nothing is in `direct_mac` yet.
def reject_lines(pattern):
# The reject chain's log lines in the kernel log that match `pattern`.
out = proxy.succeed("journalctl -k -o cat --no-pager | grep hive-egress-reject || true")
return [line for line in out.splitlines() if re.search(pattern, line)]
def wait_reject_line(pattern):
proxy.wait_until_succeeds(
"journalctl -k -o cat --no-pager | grep -E "
@ -1213,20 +1198,12 @@ pkgs.testers.runNixOSTest {
found = re.search(r'oifname != "eth1" counter packets (\d+) bytes \d+$', out, re.M)
assert found and int(found.group(1)) > 0, out
with checked("reject mode: a MAC in direct_mac passes and is refused again once it leaves, and the mesh passes"):
with checked("reject mode: a MAC in direct_mac passes and is refused again once it leaves"):
proxy.succeed(f"nft add element inet hive_egress direct_mac '{{ {AGENT_MAC} }}'")
assert dial_status("203.0.113.2", 8443) == 0, "an exempt MAC (a service container's stand-in) reaches TCP 8443"
agent.succeed("ping -c1 -W3 203.0.113.2")
assert udp_status("203.0.113.77") == 0, "control: an exempt MAC's datagram is not refused"
proxy.succeed(f"nft delete element inet hive_egress direct_mac '{{ {AGENT_MAC} }}'")
assert dial_status("203.0.113.2", 8443) == 1, "refused again"
# The mesh comes first in the chain. A dummy interface answers nothing,
# so the proof is that the chain refuses neither the ping nor the UDP
# datagram, while the same flows to the uplink are refused.
status, out = agent.execute("ping -c1 -W5 198.51.100.7")
assert "filtered" not in out.lower(), f"the mesh interface is not egress: {out}"
agent.execute("echo x > /dev/udp/198.51.100.7/9; echo x > /dev/udp/203.0.113.80/9")
wait_reject_line(r"DST=203\.0\.113\.80 ")
assert not reject_lines(r"DST=198\.51\.100\.7 "), "the mesh interface is not egress"
'';
}

View file

@ -1,136 +0,0 @@
# `vmTests.<system>.wg-hive`: two hives peered over `wg-hive`, with real traffic across the tunnel.
# Unlike ../module-eval, which only reads the rendered `networking.wireguard`
# options, this boots both hosts and sends packets.
#
# Keys are derived from a fixed per-host seed, so every store builds the
# same bytes and nothing is committed. The module takes peer public keys
# as eval-time strings (`swarm.hives.<name>.wireguardPublicKey`), so they
# are read back from the derivation's output.
{
pkgs,
self,
}:
let
mkKey =
name:
pkgs.runCommand "wg-hive-test-key-${name}"
{
nativeBuildInputs = [
pkgs.wireguard-tools
pkgs.openssl
];
}
''
# Test-only key: base64 of sha256(seed); wg clamps the scalar itself.
mkdir $out
printf '%s' 'hyperhive-wg-hive-test-${name}' | openssl dgst -sha256 -binary | base64 > $out/private
wg pubkey < $out/private > $out/public
'';
keys = {
h1 = mkKey "h1";
h2 = mkKey "h2";
# Never installed on any node; stands in for a peer key that does not
# match the real peer's private key.
wrong = mkKey "wrong";
};
pub = name: pkgs.lib.removeSuffix "\n" (builtins.readFile "${keys.${name}}/public");
# The test driver numbers nodes on the test vlan in alphabetical order.
lanAddr = {
h1 = "192.168.1.1";
h2 = "192.168.1.2";
};
meshAddr = {
h1 = "10.100.0.1";
h2 = "10.100.0.2";
};
swarmHives = pkgs.lib.mapAttrs (name: _: {
domain = "${name}.t.local";
wireguardPublicKey = pub name;
wireguardAddress = "${meshAddr.${name}}/32";
wireguardEndpoint = "${lanAddr.${name}}:51820";
}) lanAddr;
node = name: {
imports = [ self.nixosModules.default ];
services.hyperhive = {
swarm.domain = "t.local";
swarm.hives = swarmHives;
deploy.hive-controller.hiveName = name;
deploy.wireguard = {
enable = true;
address = "${meshAddr.${name}}/24";
privateKeyFile = "${keys.${name}}/private";
persistentKeepalive = 1;
};
};
# The listener the TCP check connects to, open on the mesh only.
networking.firewall.interfaces.wg-hive.allowedTCPPorts = [ 9000 ];
environment.systemPackages = [
pkgs.wireguard-tools
pkgs.python3
pkgs.curl
];
};
in
pkgs.testers.runNixOSTest {
name = "wg-hive";
nodes = {
h1 = node "h1";
h2 = node "h2";
};
testScript = ''
def wg_state(m):
return m.execute("wg show wg-hive; ip route; systemctl status 'wireguard-wg-hive*' --no-pager")[1]
start_all()
for m, peer_key in ((h1, "${pub "h2"}"), (h2, "${pub "h1"}")):
m.wait_for_unit("wireguard-wg-hive.service")
m.succeed("ip link show wg-hive")
# The interface unit is active before its peers are installed: each
# peer has its own unit, which runs after network-online.target. Until
# then the interface has no peer and a send fails with ENOKEY.
m.wait_until_succeeds(f"wg show wg-hive allowed-ips | grep -F {peer_key}")
# Traffic makes the tunnel handshake.
for src, addr in ((h1, "${meshAddr.h2}"), (h2, "${meshAddr.h1}")):
status, out = src.execute(f"ping -c 3 -W 5 {addr}")
assert status == 0, f"ping {addr} failed:\n{out}\n{wg_state(src)}"
for m in (h1, h2):
stamps = m.succeed("wg show wg-hive latest-handshakes").split()
assert len(stamps) == 2 and int(stamps[1]) > 0, f"no handshake: {stamps}\n{wg_state(m)}"
# The peer's mesh address is routed through the tunnel, its plain
# address is not.
via_mesh = h1.succeed("ip route get ${meshAddr.h2}")
assert "dev wg-hive" in via_mesh, via_mesh
via_lan = h1.succeed("ip route get ${lanAddr.h2}")
assert "dev wg-hive" not in via_lan, via_lan
# A TCP connect to a listener on the peer's mesh address, and the
# tunnel's own counters moved.
h2.succeed(
"systemd-run --unit=wg-listener ${pkgs.python3}/bin/python3"
" -m http.server 9000 --bind ${meshAddr.h2}"
)
h2.wait_for_open_port(9000, "${meshAddr.h2}")
h1.succeed("${pkgs.curl}/bin/curl -sf --max-time 5 http://${meshAddr.h2}:9000/")
transfer = h1.succeed("wg show wg-hive transfer").split()
assert int(transfer[1]) > 0 and int(transfer[2]) > 0, transfer
# Negative control: h2 swaps h1's public key for one that does not match
# h1's private key. Same endpoint, same address, so the only difference
# from the working peer above is the key, and no handshake may happen.
h2.succeed("wg set wg-hive peer ${pub "h1"} remove")
h2.succeed(
"wg set wg-hive peer ${pub "wrong"} allowed-ips ${meshAddr.h1}/32"
" endpoint ${lanAddr.h1}:51820 persistent-keepalive 1"
)
h2.fail("ping -c 3 -W 2 ${meshAddr.h1}")
stamps = h2.succeed("wg show wg-hive latest-handshakes").split()
assert stamps == ["${pub "wrong"}", "0"], f"handshake with wrong key: {stamps}"
'';
}