stockholm/krebs/3modules/iptables.nix
2016-06-28 17:41:15 +02:00

187 lines
4.6 KiB
Nix

{ config, lib, pkgs, ... }:
let
inherit (pkgs) writeText;
inherit (builtins)
elem
;
inherit (lib)
concatMapStringsSep
concatStringsSep
attrNames
unique
fold
any
attrValues
catAttrs
filter
flatten
length
hasAttr
hasPrefix
mkEnableOption
mkOption
mkIf
types
sort
;
cfg = config.krebs.iptables;
out = {
options.krebs.iptables = api;
config = mkIf cfg.enable imp;
};
api = {
enable = mkEnableOption "iptables";
#tables.filter.INPUT = {
# policy = "DROP";
# rules = [
# { predicate = "-i retiolum"; target = "ACCEPT"; priority = -10; }
# ];
#};
#new api
tables = mkOption {
type = with types; attrsOf (attrsOf (submodule ({
options = {
policy = mkOption {
type = str;
default = "-";
};
rules = mkOption {
type = nullOr (listOf (submodule ({
options = {
predicate = mkOption {
type = str;
};
target = mkOption {
type = str;
};
precedence = mkOption {
type = int;
default = 0;
};
};
})));
default = null;
};
};
})));
};
};
imp = {
networking.firewall.enable = false;
systemd.services.krebs-iptables = {
description = "krebs-iptables";
wantedBy = [ "network-pre.target" ];
before = [ "network-pre.target" ];
after = [ "systemd-modules-load.service" ];
path = with pkgs; [
iptables
];
restartIfChanged = true;
serviceConfig = {
Type = "simple";
RemainAfterExit = true;
Restart = "always";
ExecStart = "@${startScript} krebs-iptables_start";
};
};
};
#buildTable :: iptablesVersion -> iptablesAttrSet` -> str
#todo: differentiate by iptables-version
buildTables = v: ts:
let
declareChain = t: cn:
#TODO: find out what to do whit these count numbers
":${cn} ${t."${cn}".policy} [0:0]";
buildChain = tn: cn:
let
sortedRules = sort (a: b: a.precedence > b.precedence) ts."${tn}"."${cn}".rules;
in
#TODO: double check should be unneccessary, refactor!
if ts.${tn}.${cn}.rules or null != null then
concatMapStringsSep "\n" (rule: "\n-A ${cn} ${rule}") ([]
++ map (buildRule tn cn) sortedRules
)
else
""
;
buildRule = tn: cn: rule:
#target validation test:
assert (elem rule.target ([ "ACCEPT" "REJECT" "DROP" "QUEUE" "LOG" "RETURN" ] ++ (attrNames ts."${tn}"))) || hasPrefix "REDIRECT" rule.target || hasPrefix "DNAT" rule.target;
#predicate validation test:
#maybe use iptables-test
#TODO: howto exit with evaluation error by shellscript?
#apperantly not possible from nix because evalatution wouldn't be deterministic.
"${rule.predicate} -j ${rule.target}";
buildTable = tn:
"*${tn}\n" +
concatStringsSep "\n" ([]
++ map (declareChain ts."${tn}") (attrNames ts."${tn}")
) +
#this looks dirty, find a better way to do this (maybe optionalString)
concatStringsSep "" ([]
++ map (buildChain tn) (attrNames ts."${tn}")
) +
"\nCOMMIT";
in
concatStringsSep "\n" ([]
++ map buildTable (attrNames ts)
);
#=====
rules4 = iptables-version:
let
#TODO: find out good defaults.
tables-defaults = {
nat.PREROUTING.policy = "ACCEPT";
nat.INPUT.policy = "ACCEPT";
nat.OUTPUT.policy = "ACCEPT";
nat.POSTROUTING.policy = "ACCEPT";
filter.INPUT.policy = "ACCEPT";
filter.FORWARD.policy = "ACCEPT";
filter.OUTPUT.policy = "ACCEPT";
#if someone specifies any other rules on this chain, the default rules get lost.
#is this wanted beahiviour or a bug?
#TODO: implement abstraction of rules
filter.INPUT.rules = [
{ predicate = "-m conntrack --ctstate RELATED,ESTABLISHED"; target = "ACCEPT"; }
];
};
tables = tables-defaults // cfg.tables;
in
writeText "krebs-iptables-rules${toString iptables-version}" ''
${buildTables iptables-version tables}
'';
startScript = pkgs.writeDash "krebs-iptables_start" ''
set -euf
iptables-restore < ${rules4 4}
ip6tables-restore < ${rules4 6}
'';
in
out