From c60c8aa759b7b3b396a709effefb879ceed96fa6 Mon Sep 17 00:00:00 2001 From: gnidorah Date: Tue, 29 May 2018 22:10:25 +0300 Subject: [PATCH] nixos/firewall: per-interface port options --- .../modules/services/networking/firewall.nix | 435 +++++++++--------- 1 file changed, 228 insertions(+), 207 deletions(-) diff --git a/nixos/modules/services/networking/firewall.nix b/nixos/modules/services/networking/firewall.nix index c4bd0e7f9eef..36f1dd8d2479 100644 --- a/nixos/modules/services/networking/firewall.nix +++ b/nixos/modules/services/networking/firewall.nix @@ -148,38 +148,42 @@ let ip46tables -A nixos-fw -m conntrack --ctstate ESTABLISHED,RELATED -j nixos-fw-accept # Accept connections to the allowed TCP ports. - ${concatMapStrings (port: + ${concatStrings (mapAttrsToList (iface: cfg: + concatMapStrings (port: '' - ip46tables -A nixos-fw -p tcp --dport ${toString port} -j nixos-fw-accept + ip46tables -A nixos-fw -p tcp --dport ${toString port} -j nixos-fw-accept ${optionalString (iface != "default") "-i ${iface}"} '' ) cfg.allowedTCPPorts - } + ) cfg.interfaces)} # Accept connections to the allowed TCP port ranges. - ${concatMapStrings (rangeAttr: + ${concatStrings (mapAttrsToList (iface: cfg: + concatMapStrings (rangeAttr: let range = toString rangeAttr.from + ":" + toString rangeAttr.to; in '' - ip46tables -A nixos-fw -p tcp --dport ${range} -j nixos-fw-accept + ip46tables -A nixos-fw -p tcp --dport ${range} -j nixos-fw-accept ${optionalString (iface != "default") "-i ${iface}"} '' ) cfg.allowedTCPPortRanges - } + ) cfg.interfaces)} # Accept packets on the allowed UDP ports. - ${concatMapStrings (port: + ${concatStrings (mapAttrsToList (iface: cfg: + concatMapStrings (port: '' - ip46tables -A nixos-fw -p udp --dport ${toString port} -j nixos-fw-accept + ip46tables -A nixos-fw -p udp --dport ${toString port} -j nixos-fw-accept ${optionalString (iface != "default") "-i ${iface}"} '' ) cfg.allowedUDPPorts - } + ) cfg.interfaces)} # Accept packets on the allowed UDP port ranges. - ${concatMapStrings (rangeAttr: + ${concatStrings (mapAttrsToList (iface: cfg: + concatMapStrings (rangeAttr: let range = toString rangeAttr.from + ":" + toString rangeAttr.to; in '' - ip46tables -A nixos-fw -p udp --dport ${range} -j nixos-fw-accept + ip46tables -A nixos-fw -p udp --dport ${range} -j nixos-fw-accept ${optionalString (iface != "default") "-i ${iface}"} '' ) cfg.allowedUDPPortRanges - } + ) cfg.interfaces)} # Accept IPv4 multicast. Not a big security risk since # probably nobody is listening anyway. @@ -254,106 +258,30 @@ let fi ''; -in - -{ - - ###### interface - - options = { - - networking.firewall.enable = mkOption { - type = types.bool; - default = true; - description = - '' - Whether to enable the firewall. This is a simple stateful - firewall that blocks connection attempts to unauthorised TCP - or UDP ports on this machine. It does not affect packet - forwarding. - ''; - }; - - networking.firewall.logRefusedConnections = mkOption { - type = types.bool; - default = true; - description = - '' - Whether to log rejected or dropped incoming connections. - ''; - }; - - networking.firewall.logRefusedPackets = mkOption { - type = types.bool; - default = false; - description = - '' - Whether to log all rejected or dropped incoming packets. - This tends to give a lot of log messages, so it's mostly - useful for debugging. - ''; - }; - - networking.firewall.logRefusedUnicastsOnly = mkOption { - type = types.bool; - default = true; - description = - '' - If - and this option are enabled, then only log packets - specifically directed at this machine, i.e., not broadcasts - or multicasts. - ''; - }; - - networking.firewall.rejectPackets = mkOption { - type = types.bool; - default = false; - description = - '' - If set, refused packets are rejected rather than dropped - (ignored). This means that an ICMP "port unreachable" error - message is sent back to the client (or a TCP RST packet in - case of an existing connection). Rejecting packets makes - port scanning somewhat easier. - ''; - }; - - networking.firewall.trustedInterfaces = mkOption { - type = types.listOf types.str; - default = [ ]; - example = [ "enp0s2" ]; - description = - '' - Traffic coming in from these interfaces will be accepted - unconditionally. Traffic from the loopback (lo) interface - will always be accepted. - ''; - }; - - networking.firewall.allowedTCPPorts = mkOption { + commonOptions = { + allowedTCPPorts = mkOption { type = types.listOf types.int; default = [ ]; example = [ 22 80 ]; description = - '' + '' List of TCP ports on which incoming connections are accepted. ''; }; - networking.firewall.allowedTCPPortRanges = mkOption { + allowedTCPPortRanges = mkOption { type = types.listOf (types.attrsOf types.int); default = [ ]; example = [ { from = 8999; to = 9003; } ]; description = - '' + '' A range of TCP ports on which incoming connections are accepted. ''; }; - networking.firewall.allowedUDPPorts = mkOption { + allowedUDPPorts = mkOption { type = types.listOf types.int; default = [ ]; example = [ 53 ]; @@ -363,7 +291,7 @@ in ''; }; - networking.firewall.allowedUDPPortRanges = mkOption { + allowedUDPPortRanges = mkOption { type = types.listOf (types.attrsOf types.int); default = [ ]; example = [ { from = 60000; to = 61000; } ]; @@ -372,133 +300,226 @@ in Range of open UDP ports. ''; }; + }; - networking.firewall.allowPing = mkOption { - type = types.bool; - default = true; - description = - '' - Whether to respond to incoming ICMPv4 echo requests - ("pings"). ICMPv6 pings are always allowed because the - larger address space of IPv6 makes network scanning much - less effective. - ''; - }; +in - networking.firewall.pingLimit = mkOption { - type = types.nullOr (types.separatedString " "); - default = null; - example = "--limit 1/minute --limit-burst 5"; - description = - '' - If pings are allowed, this allows setting rate limits - on them. If non-null, this option should be in the form of - flags like "--limit 1/minute --limit-burst 5" - ''; - }; +{ - networking.firewall.checkReversePath = mkOption { - type = types.either types.bool (types.enum ["strict" "loose"]); - default = kernelHasRPFilter; - example = "loose"; - description = - '' - Performs a reverse path filter test on a packet. If a reply - to the packet would not be sent via the same interface that - the packet arrived on, it is refused. + ###### interface - If using asymmetric routing or other complicated routing, set - this option to loose mode or disable it and setup your own - counter-measures. + options = { - This option can be either true (or "strict"), "loose" (only - drop the packet if the source address is not reachable via any - interface) or false. Defaults to the value of - kernelHasRPFilter. + networking.firewall = { + enable = mkOption { + type = types.bool; + default = true; + description = + '' + Whether to enable the firewall. This is a simple stateful + firewall that blocks connection attempts to unauthorised TCP + or UDP ports on this machine. It does not affect packet + forwarding. + ''; + }; - (needs kernel 3.3+) - ''; - }; + logRefusedConnections = mkOption { + type = types.bool; + default = true; + description = + '' + Whether to log rejected or dropped incoming connections. + ''; + }; - networking.firewall.logReversePathDrops = mkOption { - type = types.bool; - default = false; - description = - '' - Logs dropped packets failing the reverse path filter test if - the option networking.firewall.checkReversePath is enabled. - ''; - }; + logRefusedPackets = mkOption { + type = types.bool; + default = false; + description = + '' + Whether to log all rejected or dropped incoming packets. + This tends to give a lot of log messages, so it's mostly + useful for debugging. + ''; + }; - networking.firewall.connectionTrackingModules = mkOption { - type = types.listOf types.str; - default = [ ]; - example = [ "ftp" "irc" "sane" "sip" "tftp" "amanda" "h323" "netbios_sn" "pptp" "snmp" ]; - description = - '' - List of connection-tracking helpers that are auto-loaded. - The complete list of possible values is given in the example. + logRefusedUnicastsOnly = mkOption { + type = types.bool; + default = true; + description = + '' + If + and this option are enabled, then only log packets + specifically directed at this machine, i.e., not broadcasts + or multicasts. + ''; + }; - As helpers can pose as a security risk, it is advised to - set this to an empty list and disable the setting - networking.firewall.autoLoadConntrackHelpers unless you - know what you are doing. Connection tracking is disabled - by default. + rejectPackets = mkOption { + type = types.bool; + default = false; + description = + '' + If set, refused packets are rejected rather than dropped + (ignored). This means that an ICMP "port unreachable" error + message is sent back to the client (or a TCP RST packet in + case of an existing connection). Rejecting packets makes + port scanning somewhat easier. + ''; + }; - Loading of helpers is recommended to be done through the - CT target. More info: - https://home.regit.org/netfilter-en/secure-use-of-helpers/ - ''; - }; + trustedInterfaces = mkOption { + type = types.listOf types.str; + default = [ ]; + example = [ "enp0s2" ]; + description = + '' + Traffic coming in from these interfaces will be accepted + unconditionally. Traffic from the loopback (lo) interface + will always be accepted. + ''; + }; - networking.firewall.autoLoadConntrackHelpers = mkOption { - type = types.bool; - default = false; - description = - '' - Whether to auto-load connection-tracking helpers. - See the description at networking.firewall.connectionTrackingModules + allowPing = mkOption { + type = types.bool; + default = true; + description = + '' + Whether to respond to incoming ICMPv4 echo requests + ("pings"). ICMPv6 pings are always allowed because the + larger address space of IPv6 makes network scanning much + less effective. + ''; + }; - (needs kernel 3.5+) - ''; - }; + pingLimit = mkOption { + type = types.nullOr (types.separatedString " "); + default = null; + example = "--limit 1/minute --limit-burst 5"; + description = + '' + If pings are allowed, this allows setting rate limits + on them. If non-null, this option should be in the form of + flags like "--limit 1/minute --limit-burst 5" + ''; + }; - networking.firewall.extraCommands = mkOption { - type = types.lines; - default = ""; - example = "iptables -A INPUT -p icmp -j ACCEPT"; - description = - '' - Additional shell commands executed as part of the firewall - initialisation script. These are executed just before the - final "reject" firewall rule is added, so they can be used - to allow packets that would otherwise be refused. - ''; - }; + checkReversePath = mkOption { + type = types.either types.bool (types.enum ["strict" "loose"]); + default = kernelHasRPFilter; + example = "loose"; + description = + '' + Performs a reverse path filter test on a packet. If a reply + to the packet would not be sent via the same interface that + the packet arrived on, it is refused. - networking.firewall.extraPackages = mkOption { - type = types.listOf types.package; - default = [ ]; - example = literalExample "[ pkgs.ipset ]"; - description = - '' - Additional packages to be included in the environment of the system - as well as the path of networking.firewall.extraCommands. - ''; - }; + If using asymmetric routing or other complicated routing, set + this option to loose mode or disable it and setup your own + counter-measures. - networking.firewall.extraStopCommands = mkOption { - type = types.lines; - default = ""; - example = "iptables -P INPUT ACCEPT"; - description = - '' - Additional shell commands executed as part of the firewall - shutdown script. These are executed just after the removal - of the NixOS input rule, or if the service enters a failed - state. - ''; - }; + This option can be either true (or "strict"), "loose" (only + drop the packet if the source address is not reachable via any + interface) or false. Defaults to the value of + kernelHasRPFilter. + + (needs kernel 3.3+) + ''; + }; + + logReversePathDrops = mkOption { + type = types.bool; + default = false; + description = + '' + Logs dropped packets failing the reverse path filter test if + the option networking.firewall.checkReversePath is enabled. + ''; + }; + + connectionTrackingModules = mkOption { + type = types.listOf types.str; + default = [ ]; + example = [ "ftp" "irc" "sane" "sip" "tftp" "amanda" "h323" "netbios_sn" "pptp" "snmp" ]; + description = + '' + List of connection-tracking helpers that are auto-loaded. + The complete list of possible values is given in the example. + + As helpers can pose as a security risk, it is advised to + set this to an empty list and disable the setting + networking.firewall.autoLoadConntrackHelpers unless you + know what you are doing. Connection tracking is disabled + by default. + + Loading of helpers is recommended to be done through the + CT target. More info: + https://home.regit.org/netfilter-en/secure-use-of-helpers/ + ''; + }; + + autoLoadConntrackHelpers = mkOption { + type = types.bool; + default = false; + description = + '' + Whether to auto-load connection-tracking helpers. + See the description at networking.firewall.connectionTrackingModules + + (needs kernel 3.5+) + ''; + }; + + extraCommands = mkOption { + type = types.lines; + default = ""; + example = "iptables -A INPUT -p icmp -j ACCEPT"; + description = + '' + Additional shell commands executed as part of the firewall + initialisation script. These are executed just before the + final "reject" firewall rule is added, so they can be used + to allow packets that would otherwise be refused. + ''; + }; + + extraPackages = mkOption { + type = types.listOf types.package; + default = [ ]; + example = literalExample "[ pkgs.ipset ]"; + description = + '' + Additional packages to be included in the environment of the system + as well as the path of networking.firewall.extraCommands. + ''; + }; + + extraStopCommands = mkOption { + type = types.lines; + default = ""; + example = "iptables -P INPUT ACCEPT"; + description = + '' + Additional shell commands executed as part of the firewall + shutdown script. These are executed just after the removal + of the NixOS input rule, or if the service enters a failed + state. + ''; + }; + + interfaces = mkOption { + default = { + default = mapAttrs (name: value: cfg."${name}") commonOptions; + }; + type = with types; attrsOf (submodule [ { options = commonOptions; } ]); + description = + '' + Interface-specific open ports. Setting this value will override + all values of the networking.firewall.allowed* + options. + ''; + }; + } // commonOptions; };