86 lines
3 KiB
Nix
86 lines
3 KiB
Nix
# The hive's own nftables table, `inet hive_egress`, loaded by a systemd
|
|
# oneshot. It is independent of the NixOS firewall: `networking.firewall`
|
|
# renders only the rules of the backend in use (`iptables` unless
|
|
# `networking.nftables.enable` is set), so a rule that has to hold on every
|
|
# host lives here, in a table no firewall backend writes. Only
|
|
# `networking.nftables.flushRuleset` removes it; the unit re-applies it then.
|
|
#
|
|
# The table holds one chain: traffic from the agent bridge to host
|
|
# loopback is dropped. The kernel does the same by default (a packet for
|
|
# 127.0.0.0/8 arriving on a non-loopback interface is a martian while
|
|
# `route_localnet` is 0); this rule is the second layer, and the assertion
|
|
# below keeps the first one on.
|
|
{
|
|
lib,
|
|
config,
|
|
pkgs,
|
|
...
|
|
}:
|
|
let
|
|
cfg = config.services.hyperhive.network;
|
|
nft = lib.getExe pkgs.nftables;
|
|
|
|
# One transaction: `add` + `delete` drop an old copy of the table and the
|
|
# definition recreates it, so the file loads the same on first start, on a
|
|
# reload and after something else flushed the ruleset.
|
|
ruleset = pkgs.writeText "hive-egress.nft" ''
|
|
add table inet hive_egress
|
|
delete table inet hive_egress
|
|
|
|
table inet hive_egress {
|
|
chain input {
|
|
type filter hook input priority filter - 10; policy accept;
|
|
iifname "${cfg.bridgeName}" ip daddr 127.0.0.0/8 counter drop
|
|
iifname "${cfg.bridgeName}" ip6 daddr ::1 counter drop
|
|
}
|
|
}
|
|
'';
|
|
|
|
routeLocalnetKeys = [
|
|
"net.ipv4.conf.all.route_localnet"
|
|
"net.ipv4.conf.default.route_localnet"
|
|
"net.ipv4.conf.${cfg.bridgeName}.route_localnet"
|
|
];
|
|
isOn =
|
|
v:
|
|
builtins.elem v [
|
|
1
|
|
"1"
|
|
true
|
|
];
|
|
in
|
|
{
|
|
config = lib.mkIf cfg.enable {
|
|
assertions = [
|
|
{
|
|
assertion = !(lib.any (k: isOn (config.boot.kernel.sysctl.${k} or 0)) routeLocalnetKeys);
|
|
message = ''
|
|
boot.kernel.sysctl must not turn on route_localnet (${lib.concatStringsSep ", " routeLocalnetKeys}).
|
|
With it on, the kernel routes a packet from the agent bridge to 127.0.0.0/8
|
|
instead of dropping it as a martian, which leaves the hive_egress
|
|
table as the only barrier between an agent and the host's loopback services.
|
|
'';
|
|
}
|
|
];
|
|
|
|
systemd.services.hive-egress = {
|
|
description = "hive nftables table (agent bridge to host loopback drop)";
|
|
wantedBy = [ "multi-user.target" ];
|
|
wants = [ "network-pre.target" ];
|
|
before = [ "network-pre.target" ];
|
|
# `nftables.service` runs `flush ruleset` where `networking.nftables.flushRuleset`
|
|
# is on: start after it, follow its restarts and re-apply on its reload.
|
|
after = [ "nftables.service" ];
|
|
partOf = [ "nftables.service" ];
|
|
unitConfig.ReloadPropagatedFrom = [ "nftables.service" ];
|
|
reloadIfChanged = true;
|
|
serviceConfig = {
|
|
Type = "oneshot";
|
|
RemainAfterExit = true;
|
|
ExecStart = "${nft} -f ${ruleset}";
|
|
ExecReload = "${nft} -f ${ruleset}";
|
|
ExecStop = "-${nft} delete table inet hive_egress";
|
|
};
|
|
};
|
|
};
|
|
}
|