Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

nixos-firewall-tool: add nftables support #275126

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions maintainers/maintainer-list.nix
Original file line number Diff line number Diff line change
Expand Up @@ -15962,6 +15962,12 @@
githubId = 141248;
name = "Ramses";
};
rvfg = {
email = "[email protected]";
github = "duament";
githubId = 30264485;
name = "Rvfg";
};
rvl = {
email = "[email protected]";
github = "rvl";
Expand Down
2 changes: 2 additions & 0 deletions nixos/doc/manual/release-notes/rl-2405.section.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ The pre-existing [services.ankisyncd](#opt-services.ankisyncd.enable) has been m

- The `hardware.pulseaudio` module now sets permission of pulse user home directory to 755 when running in "systemWide" mode. It fixes [issue 114399](https://github.com/NixOS/nixpkgs/issues/114399).

- `nixos-firewall-tool` now supports nftables and is installed by default when NixOS firewall is enabled.

- The `btrbk` module now automatically selects and provides required compression
program depending on the configured `stream_compress` option. Since this
replaces the need for the `extraPackages` option, this option will be
Expand Down
1 change: 0 additions & 1 deletion nixos/modules/services/networking/firewall-iptables.nix
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,6 @@ in
}
];

environment.systemPackages = [ pkgs.nixos-firewall-tool ];
networking.firewall.checkReversePath = mkIf (!kernelHasRPFilter) (mkDefault false);

systemd.services.firewall = {
Expand Down
58 changes: 42 additions & 16 deletions nixos/modules/services/networking/firewall-nftables.nix
Original file line number Diff line number Diff line change
@@ -1,20 +1,30 @@
{ config, lib, pkgs, ... }:

with lib;
{ config, lib, ... }:

let

inherit (lib) mkIf mkOption types mdDoc optionalString concatStringsSep hasPrefix mapAttrsToList;

cfg = config.networking.firewall;

ifaceSet = concatStringsSep ", " (
map (x: ''"${x}"'') cfg.trustedInterfaces
);

writeElements = elements: optionalString (elements != "") "elements = { ${elements} }";

portsToNftSet = ports: portRanges: concatStringsSep ", " (
map (x: toString x) ports
++ map (x: "${toString x.from}-${toString x.to}") portRanges
);

interfacePorts = builtins.concatStringsSep ", " (builtins.concatLists (mapAttrsToList
(interface: value:
map (port: "${interface} . tcp . ${toString port}") value.allowedTCPPorts
++ map (range: "${interface} . tcp . ${toString range.from}-${toString range.to}") value.allowedTCPPortRanges
++ map (port: "${interface} . udp . ${toString port}") value.allowedUDPPorts
++ map (range: "${interface} . udp . ${toString range.from}-${toString range.to}") value.allowedUDPPortRanges
) cfg.interfaces));

in

{
Expand All @@ -26,7 +36,7 @@ in
type = types.lines;
default = "";
example = "ip6 saddr { fc00::/7, fe80::/10 } tcp dport 24800 accept";
description = lib.mdDoc ''
description = mdDoc ''
Additional nftables rules to be appended to the input-allow
chain.

Expand All @@ -38,7 +48,7 @@ in
type = types.lines;
default = "";
example = "iifname wg0 accept";
description = lib.mdDoc ''
description = mdDoc ''
Additional nftables rules to be appended to the forward-allow
chain.

Expand Down Expand Up @@ -72,6 +82,30 @@ in

networking.nftables.tables."nixos-fw".family = "inet";
networking.nftables.tables."nixos-fw".content = ''
set tcp-ports {
comment "Open TCP ports"
type inet_service
flags interval
auto-merge
${writeElements (portsToNftSet cfg.allowedTCPPorts cfg.allowedTCPPortRanges)}
}

set udp-ports {
comment "Open UDP ports"
type inet_service
flags interval
auto-merge
${writeElements (portsToNftSet cfg.allowedUDPPorts cfg.allowedUDPPortRanges)}
}

set interface-ports {
comment "Interface-specific open ports"
type ifname . inet_proto . inet_service
flags interval
auto-merge
${writeElements interfacePorts}
}

${optionalString (cfg.checkReversePath != false) ''
chain rpfilter {
type filter hook prerouting priority mangle + 10; policy drop;
Expand Down Expand Up @@ -120,17 +154,9 @@ in

chain input-allow {

${concatStrings (mapAttrsToList (iface: cfg:
let
ifaceExpr = optionalString (iface != "default") "iifname ${iface}";
tcpSet = portsToNftSet cfg.allowedTCPPorts cfg.allowedTCPPortRanges;
udpSet = portsToNftSet cfg.allowedUDPPorts cfg.allowedUDPPortRanges;
in
''
${optionalString (tcpSet != "") "${ifaceExpr} tcp dport { ${tcpSet} } accept"}
${optionalString (udpSet != "") "${ifaceExpr} udp dport { ${udpSet} } accept"}
''
) cfg.allInterfaces)}
tcp dport @tcp-ports accept
udp dport @udp-ports accept
iifname . meta l4proto . th dport @interface-ports accept

${optionalString cfg.allowPing ''
icmp type echo-request ${optionalString (cfg.pingLimit != null) "limit rate ${cfg.pingLimit}"} accept comment "allow ping"
Expand Down
7 changes: 6 additions & 1 deletion nixos/modules/services/networking/firewall.nix
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ let

cfg = config.networking.firewall;

backend = if config.networking.nftables.enable then "nftables" else "iptables";

Comment on lines +9 to +10
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you make the iptables backend selection also explicit? There might be more than two firewall implementations in the future and I'd rather throw here than install the iptables firewall tool in that case.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, we only have a boolean option networking.nftables.enable for firewall backends. Do you mean we add a new option to set the backend?

canonicalizePortList =
ports: lib.unique (builtins.sort builtins.lessThan ports);

Expand Down Expand Up @@ -277,7 +279,10 @@ in

networking.firewall.trustedInterfaces = [ "lo" ];

environment.systemPackages = [ cfg.package ] ++ cfg.extraPackages;
environment.systemPackages = [
cfg.package
(pkgs.nixos-firewall-tool.override { inherit backend; })
] ++ cfg.extraPackages;

boot.kernelModules = (optional cfg.autoLoadConntrackHelpers "nf_conntrack")
++ map (x: "nf_conntrack_${x}") cfg.connectionTrackingModules;
Expand Down
33 changes: 29 additions & 4 deletions nixos/tests/firewall.nix
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,31 @@
import ./make-test-python.nix ( { pkgs, nftables, ... } : {
name = "firewall" + pkgs.lib.optionalString nftables "-nftables";
meta = with pkgs.lib.maintainers; {
maintainers = [ eelco ];
maintainers = [ eelco rvfg ];
};

nodes =
{ walled =
{ ... }:
{ networking.firewall.enable = true;
networking.firewall.logRefusedPackets = true;
{ networking.firewall = {
enable = true;
logRefusedPackets = true;
# Syntax smoke test, not actually verified otherwise
allowedTCPPorts = [ 25 993 8005 ];
allowedTCPPortRanges = [
{ from = 980; to = 1000; }
{ from = 990; to = 1010; }
{ from = 8000; to = 8010; }
];
interfaces.eth0 = {
allowedTCPPorts = [ 10003 ];
allowedTCPPortRanges = [ { from = 10000; to = 10005; } ];
};
interfaces.eth3 = {
allowedUDPPorts = [ 10003 ];
allowedUDPPortRanges = [ { from = 10000; to = 10005; } ];
};
};
duament marked this conversation as resolved.
Show resolved Hide resolved
networking.nftables.enable = nftables;
services.httpd.enable = true;
services.httpd.adminAddr = "[email protected]";
Expand All @@ -36,7 +53,7 @@ import ./make-test-python.nix ( { pkgs, nftables, ... } : {
};

testScript = { nodes, ... }: let
newSystem = nodes.walled2.config.system.build.toplevel;
newSystem = nodes.walled2.system.build.toplevel;
unit = if nftables then "nftables" else "firewall";
in ''
start_all()
Expand All @@ -56,6 +73,14 @@ import ./make-test-python.nix ( { pkgs, nftables, ... } : {
walled.succeed("curl -v http://attacker/ >&2")
walled.succeed("ping -c 1 attacker >&2")

# Open tcp port 80 at runtime
walled.succeed("nixos-firewall-tool open tcp 80")
attacker.succeed("curl -v http://walled/ >&2")

# Reset the firewall
walled.succeed("nixos-firewall-tool reset")
attacker.fail("curl --fail --connect-timeout 2 http://walled/ >&2")

# If we stop the firewall, then connections should succeed.
walled.stop_job("${unit}")
attacker.succeed("curl -v http://walled/ >&2")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#!/usr/bin/env bash

set -euo pipefail

show_help() {
echo "nixos-firewall-tool"
echo ""
echo "Can temporarily manipulate the NixOS firewall"
echo ""
echo "Open TCP port:"
echo " nixos-firewall-tool open tcp 8888"
echo ""
echo "Show all firewall rules:"
echo " nixos-firewall-tool show"
echo ""
echo "Open UDP port:"
echo " nixos-firewall-tool open udp 51820"
echo ""
echo "Reset firewall configuration to system settings:"
echo " nixos-firewall-tool reset"
}

if [[ -z ${1+x} ]]; then
show_help
exit 1
fi

case $1 in
"open")
protocol="$2"
port="$3"

nft add element inet nixos-fw "$protocol-ports" "{ $port }"
;;
"show")
nft list table inet nixos-fw
;;
"reset")
systemctl reload nftables.service
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reloading nftables for undoing port changes is a bit excessive, see: #286584.

Instead of the current approach, I would suggest add a set of type inet_proto . inet_service, call it something like tool-ports, and let this command do a single flush of this set.

;;
-h|--help|help)
show_help
exit 0
;;
*)
show_help
exit 1
;;
esac
25 changes: 17 additions & 8 deletions pkgs/by-name/ni/nixos-firewall-tool/package.nix
Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
{ writeShellApplication, iptables, lib }:
{ writeShellApplication, iptables, lib
, nftables, backend ? "iptables"
}:

writeShellApplication {
writeShellApplication ({
name = "nixos-firewall-tool";
text = builtins.readFile ./nixos-firewall-tool.sh;
runtimeInputs = [
iptables
];

meta = with lib; {
description = "Temporarily manipulate the NixOS firewall";
license = licenses.mit;
maintainers = with maintainers; [ clerie ];
maintainers = with maintainers; [ clerie rvfg ];
};
}
} // (
if backend == "iptables" then {
text = builtins.readFile ./nixos-firewall-tool.sh;
runtimeInputs = [ iptables ];

} else if backend == "nftables" then {
text = builtins.readFile ./nixos-firewall-tool-nftables.sh;
runtimeInputs = [ nftables ];

} else
throw "Unknown firewall backend."
))