nixos/firewall: per-interface port options

This commit is contained in:
gnidorah 2018-05-29 22:10:25 +03:00
parent 0450c7f5f3
commit c60c8aa759

View File

@ -148,38 +148,42 @@ let
ip46tables -A nixos-fw -m conntrack --ctstate ESTABLISHED,RELATED -j nixos-fw-accept ip46tables -A nixos-fw -m conntrack --ctstate ESTABLISHED,RELATED -j nixos-fw-accept
# Accept connections to the allowed TCP ports. # 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.allowedTCPPorts
} ) cfg.interfaces)}
# Accept connections to the allowed TCP port ranges. # 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 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.allowedTCPPortRanges
} ) cfg.interfaces)}
# Accept packets on the allowed UDP ports. # 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.allowedUDPPorts
} ) cfg.interfaces)}
# Accept packets on the allowed UDP port ranges. # 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 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.allowedUDPPortRanges
} ) cfg.interfaces)}
# Accept IPv4 multicast. Not a big security risk since # Accept IPv4 multicast. Not a big security risk since
# probably nobody is listening anyway. # probably nobody is listening anyway.
@ -254,106 +258,30 @@ let
fi fi
''; '';
in commonOptions = {
allowedTCPPorts = mkOption {
{
###### 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 <option>networking.firewall.logRefusedPackets</option>
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 {
type = types.listOf types.int; type = types.listOf types.int;
default = [ ]; default = [ ];
example = [ 22 80 ]; example = [ 22 80 ];
description = description =
'' ''
List of TCP ports on which incoming connections are List of TCP ports on which incoming connections are
accepted. accepted.
''; '';
}; };
networking.firewall.allowedTCPPortRanges = mkOption { allowedTCPPortRanges = mkOption {
type = types.listOf (types.attrsOf types.int); type = types.listOf (types.attrsOf types.int);
default = [ ]; default = [ ];
example = [ { from = 8999; to = 9003; } ]; example = [ { from = 8999; to = 9003; } ];
description = description =
'' ''
A range of TCP ports on which incoming connections are A range of TCP ports on which incoming connections are
accepted. accepted.
''; '';
}; };
networking.firewall.allowedUDPPorts = mkOption { allowedUDPPorts = mkOption {
type = types.listOf types.int; type = types.listOf types.int;
default = [ ]; default = [ ];
example = [ 53 ]; example = [ 53 ];
@ -363,7 +291,7 @@ in
''; '';
}; };
networking.firewall.allowedUDPPortRanges = mkOption { allowedUDPPortRanges = mkOption {
type = types.listOf (types.attrsOf types.int); type = types.listOf (types.attrsOf types.int);
default = [ ]; default = [ ];
example = [ { from = 60000; to = 61000; } ]; example = [ { from = 60000; to = 61000; } ];
@ -372,133 +300,226 @@ in
Range of open UDP ports. Range of open UDP ports.
''; '';
}; };
};
networking.firewall.allowPing = mkOption { in
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.
'';
};
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 { ###### interface
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.
If using asymmetric routing or other complicated routing, set options = {
this option to loose mode or disable it and setup your own
counter-measures.
This option can be either true (or "strict"), "loose" (only networking.firewall = {
drop the packet if the source address is not reachable via any enable = mkOption {
interface) or false. Defaults to the value of type = types.bool;
kernelHasRPFilter. 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 { logRefusedPackets = mkOption {
type = types.bool; type = types.bool;
default = false; default = false;
description = description =
'' ''
Logs dropped packets failing the reverse path filter test if Whether to log all rejected or dropped incoming packets.
the option networking.firewall.checkReversePath is enabled. This tends to give a lot of log messages, so it's mostly
''; useful for debugging.
}; '';
};
networking.firewall.connectionTrackingModules = mkOption { logRefusedUnicastsOnly = mkOption {
type = types.listOf types.str; type = types.bool;
default = [ ]; default = true;
example = [ "ftp" "irc" "sane" "sip" "tftp" "amanda" "h323" "netbios_sn" "pptp" "snmp" ]; description =
description = ''
'' If <option>networking.firewall.logRefusedPackets</option>
List of connection-tracking helpers that are auto-loaded. and this option are enabled, then only log packets
The complete list of possible values is given in the example. specifically directed at this machine, i.e., not broadcasts
or multicasts.
'';
};
As helpers can pose as a security risk, it is advised to rejectPackets = mkOption {
set this to an empty list and disable the setting type = types.bool;
networking.firewall.autoLoadConntrackHelpers unless you default = false;
know what you are doing. Connection tracking is disabled description =
by default. ''
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 trustedInterfaces = mkOption {
CT target. More info: type = types.listOf types.str;
https://home.regit.org/netfilter-en/secure-use-of-helpers/ 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 { allowPing = mkOption {
type = types.bool; type = types.bool;
default = false; default = true;
description = description =
'' ''
Whether to auto-load connection-tracking helpers. Whether to respond to incoming ICMPv4 echo requests
See the description at networking.firewall.connectionTrackingModules ("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 { checkReversePath = mkOption {
type = types.lines; type = types.either types.bool (types.enum ["strict" "loose"]);
default = ""; default = kernelHasRPFilter;
example = "iptables -A INPUT -p icmp -j ACCEPT"; example = "loose";
description = description =
'' ''
Additional shell commands executed as part of the firewall Performs a reverse path filter test on a packet. If a reply
initialisation script. These are executed just before the to the packet would not be sent via the same interface that
final "reject" firewall rule is added, so they can be used the packet arrived on, it is refused.
to allow packets that would otherwise be refused.
'';
};
networking.firewall.extraPackages = mkOption { If using asymmetric routing or other complicated routing, set
type = types.listOf types.package; this option to loose mode or disable it and setup your own
default = [ ]; counter-measures.
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.
'';
};
networking.firewall.extraStopCommands = mkOption { This option can be either true (or "strict"), "loose" (only
type = types.lines; drop the packet if the source address is not reachable via any
default = ""; interface) or false. Defaults to the value of
example = "iptables -P INPUT ACCEPT"; kernelHasRPFilter.
description =
'' (needs kernel 3.3+)
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. 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 <literal>networking.firewall.allowed*</literal>
options.
'';
};
} // commonOptions;
}; };