Add firewall module

Also: tmproot tweaks
This commit is contained in:
Jack O'Sullivan 2022-02-12 01:53:57 +00:00
parent bb9a5b2536
commit 33c643f6d8
7 changed files with 233 additions and 24 deletions

View File

@ -8,7 +8,22 @@
}; };
}; };
networking = {};
my = { my = {
firewall = {
trustedInterfaces = [ "blah" ];
nat = {
externalInterface = "eth0";
forwardPorts = [
{
proto = "tcp";
sourcePort = 2222;
destination = "127.0.0.1:22";
}
];
};
};
server.enable = true; server.enable = true;
}; };
} }

6
flake.lock generated
View File

@ -95,11 +95,11 @@
}, },
"impermanence": { "impermanence": {
"locked": { "locked": {
"lastModified": 1644585928, "lastModified": 1644623728,
"narHash": "sha256-jOnLRLnzFI/YHE53bHgz/9QjR4Qt6dgIXLnTZOf5oLc=", "narHash": "sha256-aG+JnIaFXTM9YqcE5uyBgPlPrkmX4bs+yY5YCfA/vBQ=",
"owner": "devplayer0", "owner": "devplayer0",
"repo": "impermanence", "repo": "impermanence",
"rev": "47809005570ee4d5b504e382309f5b6dcc5999e5", "rev": "74be13a87a3bbcbbaf94aea66f9576a1163db4f0",
"type": "github" "type": "github"
}, },
"original": { "original": {

View File

@ -65,6 +65,7 @@
common = "common.nix"; common = "common.nix";
build = "build.nix"; build = "build.nix";
tmproot = "tmproot.nix"; tmproot = "tmproot.nix";
firewall = "firewall.nix";
server = "server.nix"; server = "server.nix";
}; };

View File

@ -68,13 +68,23 @@
}; };
}; };
networking = {
useDHCP = mkDefault false;
enableIPv6 = mkDefault true;
};
environment.systemPackages = with pkgs; [ environment.systemPackages = with pkgs; [
bash-completion bash-completion
tree
vim vim
htop htop
iperf3 iperf3
]; ];
services.openssh = {
enable = true;
};
system = { system = {
stateVersion = "21.11"; stateVersion = "21.11";
configurationRevision = with inputs; mkIf (self ? rev) self.rev; configurationRevision = with inputs; mkIf (self ? rev) self.rev;

166
modules/firewall.nix Normal file
View File

@ -0,0 +1,166 @@
{ lib, options, config, ... }:
let
inherit (lib) optionalString concatStringsSep concatMapStringsSep optionalAttrs mkIf mkDefault mkMerge mkOverride;
inherit (lib.my) parseIPPort mkOpt mkBoolOpt dummyOption;
cfg = config.my.firewall;
in {
options.my.firewall = with lib.types; {
enable = mkBoolOpt true;
trustedInterfaces = options.networking.firewall.trustedInterfaces;
tcp = {
allowed = mkOpt (listOf (either port str)) [ "ssh" ];
};
udp = {
allowed = mkOpt (listOf (either port str)) [];
};
extraRules = mkOpt lines "";
nat = with options.networking.nat; {
enable = mkBoolOpt true;
inherit externalInterface forwardPorts;
};
};
config = mkIf cfg.enable (mkMerge [
{
networking = {
firewall.enable = false;
nftables = {
enable = true;
ruleset =
let
trusted' = "{ ${concatStringsSep ", " cfg.trustedInterfaces} }";
in
''
table inet filter {
chain wan-tcp {
${concatMapStringsSep "\n " (p: "tcp dport ${toString p} accept") cfg.tcp.allowed}
}
chain wan-udp {
${concatMapStringsSep "\n " (p: "udp dport ${toString p} accept") cfg.udp.allowed}
}
chain wan {
ip6 nexthdr icmpv6 icmpv6 type {
destination-unreachable,
packet-too-big,
time-exceeded,
parameter-problem,
mld-listener-query,
mld-listener-report,
mld-listener-reduction,
nd-router-solicit,
nd-router-advert,
nd-neighbor-solicit,
nd-neighbor-advert,
ind-neighbor-solicit,
ind-neighbor-advert,
mld2-listener-report,
echo-request
} accept
ip protocol icmp icmp type {
destination-unreachable,
router-solicitation,
router-advertisement,
time-exceeded,
parameter-problem,
echo-request
} accept
ip protocol igmp accept
ip protocol tcp tcp flags & (fin|syn|rst|ack) == syn ct state new jump wan-tcp
ip protocol udp ct state new jump wan-udp
}
chain input {
type filter hook input priority 0; policy drop;
ct state established,related accept
ct state invalid drop
iif lo accept
${optionalString (cfg.trustedInterfaces != []) "iifname ${trusted'} accept\n"}
jump wan
}
chain forward {
type filter hook forward priority 0; policy drop;
${optionalString (cfg.trustedInterfaces != []) "\n iifname ${trusted'} accept\n"}
ct state related,established accept
}
chain output {
type filter hook output priority 0; policy accept;
}
}
table nat {
chain prerouting {
type nat hook prerouting priority 0;
}
chain postrouting {
type nat hook postrouting priority 100;
}
}
${cfg.extraRules}
'';
};
};
}
(mkIf cfg.nat.enable {
assertions = [
{
assertion = (cfg.nat.forwardPorts != []) -> (cfg.nat.externalInterface != null);
message = "my.firewall.nat.forwardPorts requires my.firewall.nat.externalInterface";
}
];
# Yoinked from nixpkgs/nixos/modules/services/networking/nat.nix
boot = {
kernel.sysctl = {
"net.ipv4.conf.all.forwarding" = mkOverride 99 true;
"net.ipv4.conf.default.forwarding" = mkOverride 99 true;
} // optionalAttrs config.networking.enableIPv6 {
# Do not prevent IPv6 autoconfiguration.
# See <http://strugglers.net/~andy/blog/2011/09/04/linux-ipv6-router-advertisements-and-forwarding/>.
"net.ipv6.conf.all.accept_ra" = mkOverride 99 2;
"net.ipv6.conf.default.accept_ra" = mkOverride 99 2;
# Forward IPv6 packets.
"net.ipv6.conf.all.forwarding" = mkOverride 99 true;
"net.ipv6.conf.default.forwarding" = mkOverride 99 true;
};
};
my.firewall.extraRules =
let
makeFilter = f:
let
ipp = parseIPPort f.destination;
in
"ip${optionalString ipp.v6 "6"} daddr ${ipp.ip} ${f.proto} dport ${toString f.sourcePort} accept";
makeForward = f: "${f.proto} dport ${toString f.sourcePort} dnat to ${f.destination}";
in
''
table inet filter {
chain filter-port-forwards {
${concatMapStringsSep "\n " makeFilter cfg.nat.forwardPorts}
}
chain forward {
iifname ${cfg.nat.externalInterface} jump filter-port-forwards
}
}
table nat {
chain port-forward {
${concatMapStringsSep "\n " makeForward cfg.nat.forwardPorts}
}
chain prerouting {
iifname ${cfg.nat.externalInterface} jump port-forward
}
}
'';
})
]);
}

View File

@ -1,6 +1,6 @@
{ lib, pkgs, inputs, config, ... }@args: { lib, pkgs, inputs, config, ... }:
let let
inherit (lib) any concatStringsSep mkIf mkDefault mkMerge mkVMOverride; inherit (lib) concatStringsSep mkIf mkDefault mkMerge mkVMOverride;
inherit (lib.my) mkOpt mkBoolOpt mkVMOverride' dummyOption; inherit (lib.my) mkOpt mkBoolOpt mkVMOverride' dummyOption;
cfg = config.my.tmproot; cfg = config.my.tmproot;
@ -52,24 +52,26 @@
options = [ "size=${cfg.size}" ]; options = [ "size=${cfg.size}" ];
}; };
in { in {
imports = [ inputs.impermanence.nixosModules.impermanence ]; imports = [ inputs.impermanence.nixosModule ];
options.my.tmproot = with lib.types; { options = {
enable = mkBoolOpt true; my.tmproot = with lib.types; {
persistDir = mkOpt str "/persist"; enable = mkBoolOpt true;
size = mkOpt str "2G"; persistDir = mkOpt str "/persist";
ignoreUnsaved = mkOpt (listOf str) [ size = mkOpt str "2G";
"/tmp" ignoreUnsaved = mkOpt (listOf str) [
]; "/tmp"
];
};
# Forward declare options that won't exist until the VM module is actually imported
virtualisation = {
diskImage = dummyOption;
};
}; };
# Forward declare options that won't exist until the VM module is actually imported config = mkIf cfg.enable (mkMerge [
options.virtualisation = { {
diskImage = dummyOption;
};
config = mkMerge [
(mkIf cfg.enable {
assertions = [ assertions = [
{ {
assertion = config.fileSystems ? "${cfg.persistDir}"; assertion = config.fileSystems ? "${cfg.persistDir}";
@ -96,8 +98,8 @@
virtualisation = { virtualisation = {
diskImage = "./.vms/${config.system.name}-persist.qcow2"; diskImage = "./.vms/${config.system.name}-persist.qcow2";
}; };
}) }
(mkIf (cfg.enable && config.my.boot.isDevVM) { (mkIf config.my.boot.isDevVM {
fileSystems = mkVMOverride { fileSystems = mkVMOverride {
"/" = mkVMOverride' rootDef; "/" = mkVMOverride' rootDef;
# Hijack the "root" device for persistence in the VM # Hijack the "root" device for persistence in the VM
@ -107,5 +109,5 @@
}; };
}; };
}) })
]; ]);
} }

View File

@ -1,9 +1,24 @@
{ lib }: { lib }:
let let
inherit (builtins) replaceStrings elemAt;
inherit (lib) genAttrs mapAttrs' types mkOption mkOverride; inherit (lib) genAttrs mapAttrs' types mkOption mkOverride;
inherit (lib.flake) defaultSystems; inherit (lib.flake) defaultSystems;
in { in rec {
addPrefix = prefix: mapAttrs' (n: v: { name = "${prefix}${n}"; value = v; }); addPrefix = prefix: mapAttrs' (n: v: { name = "${prefix}${n}"; value = v; });
# Yoinked from nixpkgs/nixos/modules/services/networking/nat.nix
isIPv6 = ip: builtins.length (lib.splitString ":" ip) > 2;
parseIPPort = ipp:
let
v6 = isIPv6 ipp;
matchIP = if v6 then "[[]([0-9a-fA-F:]+)[]]" else "([0-9.]+)";
m = builtins.match "${matchIP}:([0-9-]+)" ipp;
checked = v: if m == null then throw "bad ip:ports `${ipp}'" else v;
in {
inherit v6;
ip = checked (elemAt m 0);
ports = checked (replaceStrings ["-"] [":"] (elemAt m 1));
};
mkPkgs = path: args: genAttrs defaultSystems (system: import path (args // { inherit system; })); mkPkgs = path: args: genAttrs defaultSystems (system: import path (args // { inherit system; }));
mkOpt = type: default: mkOption { inherit type default; }; mkOpt = type: default: mkOption { inherit type default; };