load the agent-bridge to loopback drop in its own nft table
This commit is contained in:
parent
748ef2da08
commit
eb39a90057
1 changed files with 186 additions and 19 deletions
|
|
@ -8,6 +8,9 @@
|
|||
# `proxy` imports the squid module against stub options for the few hive
|
||||
# options it reads, so no hive-c0re build is pulled in; the module-eval suite
|
||||
# covers the real option wiring.
|
||||
#
|
||||
# It also covers ../host-modules/hive-egress.nix: `proxy` loads the real module, so the
|
||||
# agent's dials to the host's loopback run against the table the module installs.
|
||||
{ pkgs }:
|
||||
let
|
||||
bridgeIp = "192.168.1.1";
|
||||
|
|
@ -37,6 +40,10 @@ let
|
|||
type = lib.types.bool;
|
||||
default = true;
|
||||
};
|
||||
bridgeName = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "eth1";
|
||||
};
|
||||
};
|
||||
otel = {
|
||||
scrapeTargets = lib.mkOption {
|
||||
|
|
@ -114,9 +121,12 @@ pkgs.testers.runNixOSTest {
|
|||
imports = [
|
||||
hiveStubs
|
||||
../host-modules/hive-squid.nix
|
||||
../host-modules/hive-egress.nix
|
||||
# Listeners a leaked destination would reach: loopback (`::` lands on `::1`) and the bridge.
|
||||
(listen443 [
|
||||
"127.0.0.1"
|
||||
# An address the agent can route to the proxy: the agent's own lo owns 127.0.0.1 itself.
|
||||
"127.0.0.5"
|
||||
"[::1]"
|
||||
bridgeIp
|
||||
])
|
||||
|
|
@ -174,7 +184,10 @@ pkgs.testers.runNixOSTest {
|
|||
# The gateway: nginx answers on the bridge, which agents reach directly.
|
||||
networking.firewall.interfaces.eth1.allowedTCPPorts = [ 443 ];
|
||||
# Renders the users file the way hive-c0re does.
|
||||
environment.systemPackages = [ pkgs.mkpasswd ];
|
||||
environment.systemPackages = [
|
||||
pkgs.mkpasswd
|
||||
pkgs.nftables
|
||||
];
|
||||
services.hyperhive.deploy.hive-controller.squid.selfcheck.unrestrictedCanary = "other.t.local";
|
||||
services.hyperhive.deploy.hive-controller.squid.selfcheck.canary = "cache.t.local";
|
||||
services.hyperhive.deploy.hive-controller.squid.baseAllowedDomains = [
|
||||
|
|
@ -684,5 +697,43 @@ pkgs.testers.runNixOSTest {
|
|||
write_users(ATLAS, UMA, SELFCHECK, SELFCHECK_U)
|
||||
assert connect("forge.t.local", ATLAS) == "200", "the file appeared"
|
||||
assert connect("other.t.local", UMA) == "200", "listed, and now logged in"
|
||||
|
||||
def dial(addr):
|
||||
return agent.execute(f"timeout 3 bash -c 'echo > /dev/tcp/{addr}/443'")[0] == 0
|
||||
|
||||
def dropped():
|
||||
# Packets the table's v4 rule has dropped so far.
|
||||
out = proxy.succeed("nft list chain inet hive_egress input")
|
||||
found = re.search(r"ip daddr 127\.0\.0\.0/8 counter packets (\d+)", out)
|
||||
assert found, out
|
||||
return int(found.group(1))
|
||||
|
||||
with checked("the agent bridge cannot reach the host's loopback, and the table is what stops it"):
|
||||
assert dial("${bridgeIp}"), "control: a listener on the bridge IP answers"
|
||||
# The agent has a route for an address the proxy owns on lo, and both
|
||||
# ends accept loopback addresses on the link, so only the table is left
|
||||
# between the dial and the listener.
|
||||
for vm, dev in ((agent, "eth1"), (proxy, "eth1")):
|
||||
vm.succeed(
|
||||
"sysctl -w net.ipv4.conf.all.route_localnet=1 net.ipv4.conf.all.rp_filter=0 "
|
||||
f"net.ipv4.conf.{dev}.route_localnet=1 net.ipv4.conf.{dev}.rp_filter=0"
|
||||
)
|
||||
agent.succeed("ip route add 127.0.0.5/32 via ${bridgeIp} table local")
|
||||
proxy.succeed("nft delete table inet hive_egress")
|
||||
assert dial("127.0.0.5"), "control: with the table gone the loopback listener answers"
|
||||
proxy.succeed("systemctl reload hive-egress.service")
|
||||
before = dropped()
|
||||
assert not dial("127.0.0.5"), "the table drops the dial"
|
||||
assert dropped() > before, "the drop rule counted it"
|
||||
assert dial("${bridgeIp}"), "the bridge IP still answers"
|
||||
# A firewall reload rewrites the NixOS chains and leaves this table alone.
|
||||
proxy.succeed("systemctl reload firewall.service")
|
||||
assert not dial("127.0.0.5"), "still dropped after a firewall reload"
|
||||
assert dial("${bridgeIp}"), "the bridge IP still answers after the reload"
|
||||
# With the kernel default back, the dial is refused with no table either.
|
||||
proxy.succeed("sysctl -w net.ipv4.conf.all.route_localnet=0 net.ipv4.conf.eth1.route_localnet=0")
|
||||
proxy.succeed("nft delete table inet hive_egress")
|
||||
assert not dial("127.0.0.5"), "route_localnet=0 refuses it alone"
|
||||
proxy.succeed("systemctl reload hive-egress.service")
|
||||
'';
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue