nixos/firewall: Cleanup in case reload fails
This commit is contained in:
parent
e0b6da1708
commit
ec9c4143a7
@ -37,6 +37,180 @@ let
|
|||||||
}
|
}
|
||||||
'';
|
'';
|
||||||
|
|
||||||
|
writeShScript = name: text: let dir = pkgs.writeScriptBin name ''
|
||||||
|
#! ${pkgs.stdenv.shell} -e
|
||||||
|
${text}
|
||||||
|
''; in "${dir}/bin/${name}";
|
||||||
|
|
||||||
|
startScript = writeShScript "firewall-start" ''
|
||||||
|
${helpers}
|
||||||
|
|
||||||
|
# Flush the old firewall rules. !!! Ideally, updating the
|
||||||
|
# firewall would be atomic. Apparently that's possible
|
||||||
|
# with iptables-restore.
|
||||||
|
ip46tables -D INPUT -j nixos-fw 2> /dev/null || true
|
||||||
|
for chain in nixos-fw nixos-fw-accept nixos-fw-log-refuse nixos-fw-refuse FW_REFUSE; do
|
||||||
|
ip46tables -F "$chain" 2> /dev/null || true
|
||||||
|
ip46tables -X "$chain" 2> /dev/null || true
|
||||||
|
done
|
||||||
|
|
||||||
|
|
||||||
|
# The "nixos-fw-accept" chain just accepts packets.
|
||||||
|
ip46tables -N nixos-fw-accept
|
||||||
|
ip46tables -A nixos-fw-accept -j ACCEPT
|
||||||
|
|
||||||
|
|
||||||
|
# The "nixos-fw-refuse" chain rejects or drops packets.
|
||||||
|
ip46tables -N nixos-fw-refuse
|
||||||
|
|
||||||
|
${if cfg.rejectPackets then ''
|
||||||
|
# Send a reset for existing TCP connections that we've
|
||||||
|
# somehow forgotten about. Send ICMP "port unreachable"
|
||||||
|
# for everything else.
|
||||||
|
ip46tables -A nixos-fw-refuse -p tcp ! --syn -j REJECT --reject-with tcp-reset
|
||||||
|
ip46tables -A nixos-fw-refuse -j REJECT
|
||||||
|
'' else ''
|
||||||
|
ip46tables -A nixos-fw-refuse -j DROP
|
||||||
|
''}
|
||||||
|
|
||||||
|
|
||||||
|
# The "nixos-fw-log-refuse" chain performs logging, then
|
||||||
|
# jumps to the "nixos-fw-refuse" chain.
|
||||||
|
ip46tables -N nixos-fw-log-refuse
|
||||||
|
|
||||||
|
${optionalString cfg.logRefusedConnections ''
|
||||||
|
ip46tables -A nixos-fw-log-refuse -p tcp --syn -j LOG --log-level info --log-prefix "rejected connection: "
|
||||||
|
''}
|
||||||
|
${optionalString (cfg.logRefusedPackets && !cfg.logRefusedUnicastsOnly) ''
|
||||||
|
ip46tables -A nixos-fw-log-refuse -m pkttype --pkt-type broadcast \
|
||||||
|
-j LOG --log-level info --log-prefix "rejected broadcast: "
|
||||||
|
ip46tables -A nixos-fw-log-refuse -m pkttype --pkt-type multicast \
|
||||||
|
-j LOG --log-level info --log-prefix "rejected multicast: "
|
||||||
|
''}
|
||||||
|
ip46tables -A nixos-fw-log-refuse -m pkttype ! --pkt-type unicast -j nixos-fw-refuse
|
||||||
|
${optionalString cfg.logRefusedPackets ''
|
||||||
|
ip46tables -A nixos-fw-log-refuse \
|
||||||
|
-j LOG --log-level info --log-prefix "rejected packet: "
|
||||||
|
''}
|
||||||
|
ip46tables -A nixos-fw-log-refuse -j nixos-fw-refuse
|
||||||
|
|
||||||
|
|
||||||
|
# The "nixos-fw" chain does the actual work.
|
||||||
|
ip46tables -N nixos-fw
|
||||||
|
|
||||||
|
# Perform a reverse-path test to refuse spoofers
|
||||||
|
# For now, we just drop, as the raw table doesn't have a log-refuse yet
|
||||||
|
${optionalString (kernelHasRPFilter && cfg.checkReversePath) ''
|
||||||
|
if ! ip46tables -A PREROUTING -t raw -m rpfilter --invert -j DROP; then
|
||||||
|
echo "<2>failed to initialise rpfilter support" >&2
|
||||||
|
fi
|
||||||
|
''}
|
||||||
|
|
||||||
|
# Accept all traffic on the trusted interfaces.
|
||||||
|
${flip concatMapStrings cfg.trustedInterfaces (iface: ''
|
||||||
|
ip46tables -A nixos-fw -i ${iface} -j nixos-fw-accept
|
||||||
|
'')}
|
||||||
|
|
||||||
|
# Accept packets from established or related connections.
|
||||||
|
ip46tables -A nixos-fw -m conntrack --ctstate ESTABLISHED,RELATED -j nixos-fw-accept
|
||||||
|
|
||||||
|
# Accept connections to the allowed TCP ports.
|
||||||
|
${concatMapStrings (port:
|
||||||
|
''
|
||||||
|
ip46tables -A nixos-fw -p tcp --dport ${toString port} -j nixos-fw-accept
|
||||||
|
''
|
||||||
|
) cfg.allowedTCPPorts
|
||||||
|
}
|
||||||
|
|
||||||
|
# Accept connections to the allowed TCP port ranges.
|
||||||
|
${concatMapStrings (rangeAttr:
|
||||||
|
let range = toString rangeAttr.from + ":" + toString rangeAttr.to; in
|
||||||
|
''
|
||||||
|
ip46tables -A nixos-fw -p tcp --dport ${range} -j nixos-fw-accept
|
||||||
|
''
|
||||||
|
) cfg.allowedTCPPortRanges
|
||||||
|
}
|
||||||
|
|
||||||
|
# Accept packets on the allowed UDP ports.
|
||||||
|
${concatMapStrings (port:
|
||||||
|
''
|
||||||
|
ip46tables -A nixos-fw -p udp --dport ${toString port} -j nixos-fw-accept
|
||||||
|
''
|
||||||
|
) cfg.allowedUDPPorts
|
||||||
|
}
|
||||||
|
|
||||||
|
# Accept packets on the allowed UDP port ranges.
|
||||||
|
${concatMapStrings (rangeAttr:
|
||||||
|
let range = toString rangeAttr.from + ":" + toString rangeAttr.to; in
|
||||||
|
''
|
||||||
|
ip46tables -A nixos-fw -p udp --dport ${range} -j nixos-fw-accept
|
||||||
|
''
|
||||||
|
) cfg.allowedUDPPortRanges
|
||||||
|
}
|
||||||
|
|
||||||
|
# Accept IPv4 multicast. Not a big security risk since
|
||||||
|
# probably nobody is listening anyway.
|
||||||
|
#iptables -A nixos-fw -d 224.0.0.0/4 -j nixos-fw-accept
|
||||||
|
|
||||||
|
# Optionally respond to ICMPv4 pings.
|
||||||
|
${optionalString cfg.allowPing ''
|
||||||
|
iptables -w -A nixos-fw -p icmp --icmp-type echo-request ${optionalString (cfg.pingLimit != null)
|
||||||
|
"-m limit ${cfg.pingLimit} "
|
||||||
|
}-j nixos-fw-accept
|
||||||
|
''}
|
||||||
|
|
||||||
|
# Accept all ICMPv6 messages except redirects and node
|
||||||
|
# information queries (type 139). See RFC 4890, section
|
||||||
|
# 4.4.
|
||||||
|
${optionalString config.networking.enableIPv6 ''
|
||||||
|
ip6tables -A nixos-fw -p icmpv6 --icmpv6-type redirect -j DROP
|
||||||
|
ip6tables -A nixos-fw -p icmpv6 --icmpv6-type 139 -j DROP
|
||||||
|
ip6tables -A nixos-fw -p icmpv6 -j nixos-fw-accept
|
||||||
|
''}
|
||||||
|
|
||||||
|
${cfg.extraCommands}
|
||||||
|
|
||||||
|
# Reject/drop everything else.
|
||||||
|
ip46tables -A nixos-fw -j nixos-fw-log-refuse
|
||||||
|
|
||||||
|
|
||||||
|
# Enable the firewall.
|
||||||
|
ip46tables -A INPUT -j nixos-fw
|
||||||
|
'';
|
||||||
|
|
||||||
|
stopScript = writeShScript "firewall-stop" ''
|
||||||
|
${helpers}
|
||||||
|
|
||||||
|
# Clean up in case reload fails
|
||||||
|
ip46tables -D INPUT -j nixos-drop 2>/dev/null || true
|
||||||
|
|
||||||
|
# Clean up after added ruleset
|
||||||
|
ip46tables -D INPUT -j nixos-fw 2>/dev/null || true
|
||||||
|
|
||||||
|
${cfg.extraStopCommands}
|
||||||
|
'';
|
||||||
|
|
||||||
|
reloadScript = writeShScript "firewall-reload" ''
|
||||||
|
${helpers}
|
||||||
|
|
||||||
|
# Create a unique drop rule
|
||||||
|
ip46tables -D INPUT -j nixos-drop 2>/dev/null || true
|
||||||
|
ip46tables -F nixos-drop 2>/dev/null || true
|
||||||
|
ip46tables -X nixos-drop 2>/dev/null || true
|
||||||
|
ip46tables -N nixos-drop
|
||||||
|
ip46tables -A nixos-drop -j DROP
|
||||||
|
|
||||||
|
# Don't allow traffic to leak out until the script has completed
|
||||||
|
ip46tables -A INPUT -j nixos-drop
|
||||||
|
if ${startScript}; then
|
||||||
|
ip46tables -D INPUT -j nixos-drop 2>/dev/null || true
|
||||||
|
else
|
||||||
|
echo "Failed to reload firewall... Stopping"
|
||||||
|
${stopScript}
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
'';
|
||||||
|
|
||||||
kernelPackages = config.boot.kernelPackages;
|
kernelPackages = config.boot.kernelPackages;
|
||||||
|
|
||||||
kernelHasRPFilter = kernelPackages.kernel.features.netfilterRPFilter or false;
|
kernelHasRPFilter = kernelPackages.kernel.features.netfilterRPFilter or false;
|
||||||
@ -248,7 +422,7 @@ in
|
|||||||
''
|
''
|
||||||
Additional shell commands executed as part of the firewall
|
Additional shell commands executed as part of the firewall
|
||||||
shutdown script. These are executed just after the removal
|
shutdown script. These are executed just after the removal
|
||||||
of the nixos input rule.
|
of the nixos input rule, or if the service enters a failed state.
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -276,177 +450,28 @@ in
|
|||||||
message = "This kernel does not support disabling conntrack helpers"; }
|
message = "This kernel does not support disabling conntrack helpers"; }
|
||||||
];
|
];
|
||||||
|
|
||||||
systemd.services.firewall = rec
|
systemd.services.firewall = {
|
||||||
{ description = "Firewall";
|
description = "Firewall";
|
||||||
|
wantedBy = [ "network.target" ];
|
||||||
|
after = [ "network-interfaces.target" "systemd-modules-load.service" ];
|
||||||
|
|
||||||
wantedBy = [ "network.target" ];
|
path = [ pkgs.iptables ];
|
||||||
after = [ "network-interfaces.target" "systemd-modules-load.service" ];
|
|
||||||
|
|
||||||
path = [ pkgs.iptables ];
|
# FIXME: this module may also try to load kernel modules, but
|
||||||
|
# containers don't have CAP_SYS_MODULE. So the host system had
|
||||||
|
# better have all necessary modules already loaded.
|
||||||
|
unitConfig.ConditionCapability = "CAP_NET_ADMIN";
|
||||||
|
|
||||||
# FIXME: this module may also try to load kernel modules, but
|
reloadIfChanged = true;
|
||||||
# containers don't have CAP_SYS_MODULE. So the host system had
|
|
||||||
# better have all necessary modules already loaded.
|
|
||||||
unitConfig.ConditionCapability = "CAP_NET_ADMIN";
|
|
||||||
|
|
||||||
reloadIfChanged = true;
|
serviceConfig = {
|
||||||
|
Type = "oneshot";
|
||||||
serviceConfig = {
|
RemainAfterExit = true;
|
||||||
Type = "oneshot";
|
ExecStart = "@${startScript} firewall-start";
|
||||||
RemainAfterExit = true;
|
ExecReload = "@${reloadScript} firewall-reload";
|
||||||
};
|
ExecStop = "@${stopScript} firewall-stop";
|
||||||
|
|
||||||
script =
|
|
||||||
''
|
|
||||||
${helpers}
|
|
||||||
|
|
||||||
# Flush the old firewall rules. !!! Ideally, updating the
|
|
||||||
# firewall would be atomic. Apparently that's possible
|
|
||||||
# with iptables-restore.
|
|
||||||
ip46tables -D INPUT -j nixos-fw 2> /dev/null || true
|
|
||||||
for chain in nixos-fw nixos-fw-accept nixos-fw-log-refuse nixos-fw-refuse FW_REFUSE; do
|
|
||||||
ip46tables -F "$chain" 2> /dev/null || true
|
|
||||||
ip46tables -X "$chain" 2> /dev/null || true
|
|
||||||
done
|
|
||||||
|
|
||||||
|
|
||||||
# The "nixos-fw-accept" chain just accepts packets.
|
|
||||||
ip46tables -N nixos-fw-accept
|
|
||||||
ip46tables -A nixos-fw-accept -j ACCEPT
|
|
||||||
|
|
||||||
|
|
||||||
# The "nixos-fw-refuse" chain rejects or drops packets.
|
|
||||||
ip46tables -N nixos-fw-refuse
|
|
||||||
|
|
||||||
${if cfg.rejectPackets then ''
|
|
||||||
# Send a reset for existing TCP connections that we've
|
|
||||||
# somehow forgotten about. Send ICMP "port unreachable"
|
|
||||||
# for everything else.
|
|
||||||
ip46tables -A nixos-fw-refuse -p tcp ! --syn -j REJECT --reject-with tcp-reset
|
|
||||||
ip46tables -A nixos-fw-refuse -j REJECT
|
|
||||||
'' else ''
|
|
||||||
ip46tables -A nixos-fw-refuse -j DROP
|
|
||||||
''}
|
|
||||||
|
|
||||||
|
|
||||||
# The "nixos-fw-log-refuse" chain performs logging, then
|
|
||||||
# jumps to the "nixos-fw-refuse" chain.
|
|
||||||
ip46tables -N nixos-fw-log-refuse
|
|
||||||
|
|
||||||
${optionalString cfg.logRefusedConnections ''
|
|
||||||
ip46tables -A nixos-fw-log-refuse -p tcp --syn -j LOG --log-level info --log-prefix "rejected connection: "
|
|
||||||
''}
|
|
||||||
${optionalString (cfg.logRefusedPackets && !cfg.logRefusedUnicastsOnly) ''
|
|
||||||
ip46tables -A nixos-fw-log-refuse -m pkttype --pkt-type broadcast \
|
|
||||||
-j LOG --log-level info --log-prefix "rejected broadcast: "
|
|
||||||
ip46tables -A nixos-fw-log-refuse -m pkttype --pkt-type multicast \
|
|
||||||
-j LOG --log-level info --log-prefix "rejected multicast: "
|
|
||||||
''}
|
|
||||||
ip46tables -A nixos-fw-log-refuse -m pkttype ! --pkt-type unicast -j nixos-fw-refuse
|
|
||||||
${optionalString cfg.logRefusedPackets ''
|
|
||||||
ip46tables -A nixos-fw-log-refuse \
|
|
||||||
-j LOG --log-level info --log-prefix "rejected packet: "
|
|
||||||
''}
|
|
||||||
ip46tables -A nixos-fw-log-refuse -j nixos-fw-refuse
|
|
||||||
|
|
||||||
|
|
||||||
# The "nixos-fw" chain does the actual work.
|
|
||||||
ip46tables -N nixos-fw
|
|
||||||
|
|
||||||
# Perform a reverse-path test to refuse spoofers
|
|
||||||
# For now, we just drop, as the raw table doesn't have a log-refuse yet
|
|
||||||
${optionalString (kernelHasRPFilter && cfg.checkReversePath) ''
|
|
||||||
if ! ip46tables -A PREROUTING -t raw -m rpfilter --invert -j DROP; then
|
|
||||||
echo "<2>failed to initialise rpfilter support" >&2
|
|
||||||
fi
|
|
||||||
''}
|
|
||||||
|
|
||||||
# Accept all traffic on the trusted interfaces.
|
|
||||||
${flip concatMapStrings cfg.trustedInterfaces (iface: ''
|
|
||||||
ip46tables -A nixos-fw -i ${iface} -j nixos-fw-accept
|
|
||||||
'')}
|
|
||||||
|
|
||||||
# Accept packets from established or related connections.
|
|
||||||
ip46tables -A nixos-fw -m conntrack --ctstate ESTABLISHED,RELATED -j nixos-fw-accept
|
|
||||||
|
|
||||||
# Accept connections to the allowed TCP ports.
|
|
||||||
${concatMapStrings (port:
|
|
||||||
''
|
|
||||||
ip46tables -A nixos-fw -p tcp --dport ${toString port} -j nixos-fw-accept
|
|
||||||
''
|
|
||||||
) cfg.allowedTCPPorts
|
|
||||||
}
|
|
||||||
|
|
||||||
# Accept connections to the allowed TCP port ranges.
|
|
||||||
${concatMapStrings (rangeAttr:
|
|
||||||
let range = toString rangeAttr.from + ":" + toString rangeAttr.to; in
|
|
||||||
''
|
|
||||||
ip46tables -A nixos-fw -p tcp --dport ${range} -j nixos-fw-accept
|
|
||||||
''
|
|
||||||
) cfg.allowedTCPPortRanges
|
|
||||||
}
|
|
||||||
|
|
||||||
# Accept packets on the allowed UDP ports.
|
|
||||||
${concatMapStrings (port:
|
|
||||||
''
|
|
||||||
ip46tables -A nixos-fw -p udp --dport ${toString port} -j nixos-fw-accept
|
|
||||||
''
|
|
||||||
) cfg.allowedUDPPorts
|
|
||||||
}
|
|
||||||
|
|
||||||
# Accept packets on the allowed UDP port ranges.
|
|
||||||
${concatMapStrings (rangeAttr:
|
|
||||||
let range = toString rangeAttr.from + ":" + toString rangeAttr.to; in
|
|
||||||
''
|
|
||||||
ip46tables -A nixos-fw -p udp --dport ${range} -j nixos-fw-accept
|
|
||||||
''
|
|
||||||
) cfg.allowedUDPPortRanges
|
|
||||||
}
|
|
||||||
|
|
||||||
# Accept IPv4 multicast. Not a big security risk since
|
|
||||||
# probably nobody is listening anyway.
|
|
||||||
#iptables -A nixos-fw -d 224.0.0.0/4 -j nixos-fw-accept
|
|
||||||
|
|
||||||
# Optionally respond to ICMPv4 pings.
|
|
||||||
${optionalString cfg.allowPing ''
|
|
||||||
iptables -w -A nixos-fw -p icmp --icmp-type echo-request ${optionalString (cfg.pingLimit != null)
|
|
||||||
"-m limit ${cfg.pingLimit} "
|
|
||||||
}-j nixos-fw-accept
|
|
||||||
''}
|
|
||||||
|
|
||||||
# Accept all ICMPv6 messages except redirects and node
|
|
||||||
# information queries (type 139). See RFC 4890, section
|
|
||||||
# 4.4.
|
|
||||||
${optionalString config.networking.enableIPv6 ''
|
|
||||||
ip6tables -A nixos-fw -p icmpv6 --icmpv6-type redirect -j DROP
|
|
||||||
ip6tables -A nixos-fw -p icmpv6 --icmpv6-type 139 -j DROP
|
|
||||||
ip6tables -A nixos-fw -p icmpv6 -j nixos-fw-accept
|
|
||||||
''}
|
|
||||||
|
|
||||||
${cfg.extraCommands}
|
|
||||||
|
|
||||||
# Reject/drop everything else.
|
|
||||||
ip46tables -A nixos-fw -j nixos-fw-log-refuse
|
|
||||||
|
|
||||||
|
|
||||||
# Enable the firewall.
|
|
||||||
ip46tables -A INPUT -j nixos-fw
|
|
||||||
'';
|
|
||||||
|
|
||||||
reload = ''
|
|
||||||
${helpers}
|
|
||||||
ip46tables -A INPUT -j DROP
|
|
||||||
${script}
|
|
||||||
ip46tables -D INPUT -j DROP || true # extraCommands might delete the above rule and cause this to fail
|
|
||||||
'';
|
|
||||||
|
|
||||||
postStop =
|
|
||||||
''
|
|
||||||
${helpers}
|
|
||||||
ip46tables -D INPUT -j nixos-fw || true
|
|
||||||
${cfg.extraStopCommands}
|
|
||||||
'';
|
|
||||||
};
|
};
|
||||||
|
};
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user