networking/netns: add per-netns nftables

This commit is contained in:
Lu Wang 2024-12-13 17:29:45 +08:00
parent efb448ba9b
commit 3d38c22e93
Signed by: rebmit
SSH key fingerprint: SHA256:3px8QV1zEerIrEWHaqtH5rR9kjetyRST5EipOPrd+bU

View file

@ -0,0 +1,186 @@
{
config,
lib,
pkgs,
...
}:
with lib;
let
allNetns = config.networking.netns;
nftablesEnabledNetns = filterAttrs (name: cfg: name != "default" && cfg.nftables.enable) allNetns;
in
{
options.networking.netns = mkOption {
type = types.attrsOf (
types.submodule (
{ ... }:
{
options.nftables = {
enable = mkEnableOption "per-netns nftables firewall";
checkRuleset = mkOption {
type = types.bool;
default = true;
description = ''
Run `nft check` on the ruleset to spot syntax errors during build.
'';
};
checkRulesetRedirects = mkOption {
type = types.addCheck (types.attrsOf types.path) (attrs: all types.path.check (attrNames attrs));
default = {
"/etc/hosts" = config.environment.etc.hosts.source;
"/etc/protocols" = config.environment.etc.protocols.source;
"/etc/services" = config.environment.etc.services.source;
};
description = ''
Set of paths that should be intercepted and rewritten while checking the ruleset
using `pkgs.buildPackages.libredirect`.
'';
};
tables = mkOption {
type = types.attrsOf (
types.submodule (
{ name, ... }:
{
options = {
enable = mkOption {
type = types.bool;
default = true;
description = ''
Whether to enable this table.
'';
};
name = mkOption {
type = types.str;
default = name;
description = ''
Name of the table.
'';
};
content = mkOption {
type = types.lines;
description = ''
The content of the table.
'';
};
family = mkOption {
type = types.enum [
"ip"
"ip6"
"inet"
"arp"
"bridge"
"netdev"
];
description = ''
Address family of the table.
'';
};
};
}
)
);
default = { };
description = ''
Tables to be added to ruleset.
Tables will be added together with delete statements to clean up the table before every update.
'';
};
};
}
)
);
};
config = mkIf (nftablesEnabledNetns != { }) {
networking.nftables.enable = true;
systemd.services = mapAttrs' (
name: cfg:
nameValuePair "netns-${name}-nftables" {
serviceConfig =
let
enabledTables = filterAttrs (_: table: table.enable) cfg.nftables.tables;
deletionsScript = pkgs.writeScript "nftables-deletions" ''
#! ${pkgs.nftables}/bin/nft -f
${concatStringsSep "\n" (
mapAttrsToList (_: table: ''
table ${table.family} ${table.name}
delete table ${table.family} ${table.name}
'') enabledTables
)}
'';
deletionsScriptVar = "/run/${name}/nftables/deletions.nft";
ensureDeletions = pkgs.writeShellScript "nftables-ensure-deletions" ''
touch ${deletionsScriptVar}
chmod +x ${deletionsScriptVar}
'';
saveDeletionsScript = pkgs.writeShellScript "nftables-save-deletions" ''
cp ${deletionsScript} ${deletionsScriptVar}
'';
cleanupDeletionsScript = pkgs.writeShellScript "nftables-cleanup-deletions" ''
rm ${deletionsScriptVar}
'';
rulesScript = pkgs.writeTextFile {
name = "nftables-rules";
executable = true;
text = ''
#! ${pkgs.nftables}/bin/nft -f
# previous deletions, if any
include "${deletionsScriptVar}"
# current deletions
include "${deletionsScript}"
${concatStringsSep "\n" (
mapAttrsToList (_: table: ''
table ${table.family} ${table.name} {
${table.content}
}
'') enabledTables
)}
'';
checkPhase = optionalString cfg.nftables.checkRuleset ''
cp $out ruleset.conf
sed 's|include "${deletionsScriptVar}"||' -i ruleset.conf
export NIX_REDIRECTS=${
escapeShellArg (
concatStringsSep ":" (mapAttrsToList (n: v: "${n}=${v}") cfg.nftables.checkRulesetRedirects)
)
}
LD_PRELOAD="${pkgs.buildPackages.libredirect}/lib/libredirect.so ${pkgs.buildPackages.lklWithFirewall.lib}/lib/liblkl-hijack.so" \
${pkgs.buildPackages.nftables}/bin/nft --check --file ruleset.conf
'';
};
in
cfg.serviceConfig
// {
Type = "oneshot";
RemainAfterExit = true;
RuntimeDirectory = "${name}/nftables";
ExecStart = [
ensureDeletions
rulesScript
];
ExecStartPost = saveDeletionsScript;
ExecReload = [
ensureDeletions
rulesScript
saveDeletionsScript
];
ExecStop = [
deletionsScriptVar
cleanupDeletionsScript
];
};
reloadIfChanged = true;
after = [
"netns-${name}.service"
"network.target"
];
partOf = [ "netns-${name}.service" ];
wantedBy = [
"multi-user.target"
"netns-${name}.service"
];
}
) nftablesEnabledNetns;
};
}