summaryrefslogtreecommitdiff
path: root/sys/net
diff options
context:
space:
mode:
Diffstat (limited to 'sys/net')
-rw-r--r--sys/net/default.nix9
-rw-r--r--sys/net/fail2ban.nix37
-rw-r--r--sys/net/interfaces.nix120
-rw-r--r--sys/net/nets.nix1
-rw-r--r--sys/net/options.nix278
-rw-r--r--sys/net/vsock.nix63
6 files changed, 508 insertions, 0 deletions
diff --git a/sys/net/default.nix b/sys/net/default.nix
new file mode 100644
index 0000000..c3c5740
--- /dev/null
+++ b/sys/net/default.nix
@@ -0,0 +1,9 @@
+{
+ imports = [
+ ./fail2ban.nix
+ ./interfaces.nix
+ ./nets.nix
+ ./options.nix
+ ./vsock.nix
+ ];
+}
diff --git a/sys/net/fail2ban.nix b/sys/net/fail2ban.nix
new file mode 100644
index 0000000..32197b6
--- /dev/null
+++ b/sys/net/fail2ban.nix
@@ -0,0 +1,37 @@
+{
+ lib,
+ config,
+ pkgs,
+ ...
+}:
+with lib; let
+ cfg = config.local.net.fail2ban;
+ inherit (config.local) nets;
+in {
+ options.local.net.fail2ban = {
+ enable = mkEnableOption "fail2ban";
+ };
+
+ config = mkIf cfg.enable {
+ services.fail2ban = {
+ enable = true;
+
+ bantime = "10m";
+
+ bantime-increment = {
+ enable = true;
+
+ maxtime = "48h";
+ rndtime = "10m";
+ overalljails = true;
+ };
+
+ ignoreIP = [
+ nets.static-vpn.v6.cidr
+ nets.gate-srv.v6.cidr
+ nets.gate-public.hosts.gate.v4.address
+ nets.gate-public.hosts.gate.v6.address
+ ];
+ };
+ };
+}
diff --git a/sys/net/interfaces.nix b/sys/net/interfaces.nix
new file mode 100644
index 0000000..3150b02
--- /dev/null
+++ b/sys/net/interfaces.nix
@@ -0,0 +1,120 @@
+{
+ lib,
+ config,
+ pkgs,
+ ...
+}:
+with lib; let
+ cfg = config.local.net;
+in {
+ options.local.net = with lib.types; {
+ enable = mkEnableOption "networking stack";
+
+ hostname = mkOption {
+ type = str;
+ };
+
+ dhcpInterface = mkOption {
+ type = nullOr str;
+ default = null;
+ };
+ };
+
+ config = mkIf cfg.enable {
+ boot.kernel.sysctl = {
+ # rp_filter=1 reemplazado por nixos-fw-rpfilter
+ "net.ipv4.conf.all.rp_filter" = mkForce 2;
+ "net.ipv4.conf.default.rp_filter" = mkForce 2;
+
+ "net.ipv4.conf.all.forwarding" = mkForce true;
+ "net.ipv6.conf.all.forwarding" = mkForce true;
+ "net.ipv4.conf.default.forwarding" = mkForce true;
+ "net.ipv6.conf.default.forwarding" = mkForce true;
+
+ "net.ipv4.conf.all.accept_redirects" = mkForce false;
+ "net.ipv6.conf.all.accept_redirects" = mkForce false;
+ "net.ipv4.conf.default.accept_redirects" = mkForce false;
+ "net.ipv6.conf.default.accept_redirects" = mkForce false;
+ };
+
+ environment.systemPackages = with pkgs; [
+ conntrack-tools
+ dhcpcd
+ dnsutils
+ nmap
+ socat
+ tcpdump
+ wireguard-tools
+ ];
+
+ networking = {
+ domain = mkDefault config.local.domains.host.main;
+ hostName = cfg.hostname;
+
+ firewall = {
+ logReversePathDrops = true;
+ checkReversePath = "loose";
+
+ extraCommands = mkBefore ''
+ ip46tables -t filter -P INPUT DROP
+ ip46tables -t filter -P FORWARD ACCEPT #TODO: DROP
+
+ ip46tables -t filter -N local-input
+ ip46tables -t filter -N local-forward
+ ip46tables -t nat -N local-prerouting
+ ip46tables -t nat -N local-postrouting
+
+ ip46tables -t filter -I INPUT -j local-input
+ ip46tables -t filter -I FORWARD -j local-forward
+ ip46tables -t nat -I PREROUTING -j local-prerouting
+ ip46tables -t nat -I POSTROUTING -j local-postrouting
+
+ ip46tables -t filter -A local-forward -m conntrack --ctstate RELATED,ESTABLISHED,SNAT,DNAT -j ACCEPT
+ '';
+
+ extraStopCommands = mkAfter ''
+ ip46tables -t filter -D INPUT -j local-input || true
+ ip46tables -t filter -D FORWARD -j local-forward || true
+ ip46tables -t nat -D PREROUTING -j local-prerouting || true
+ ip46tables -t nat -D POSTROUTING -j local-postrouting || true
+
+ ip46tables -t filter -F local-input || true
+ ip46tables -t filter -X local-input || true
+ ip46tables -t filter -F local-forward || true
+ ip46tables -t filter -X local-forward || true
+ ip46tables -t nat -F local-prerouting || true
+ ip46tables -t nat -X local-prerouting || true
+ ip46tables -t nat -F local-postrouting || true
+ ip46tables -t nat -X local-postrouting || true
+
+ ip46tables -t filter -P INPUT ACCEPT
+ ip46tables -t filter -P FORWARD ACCEPT
+ '';
+
+ logRefusedConnections = false;
+ };
+
+ useDHCP = false;
+ enableIPv6 = mkDefault true;
+ useNetworkd = mkDefault true;
+ useHostResolvConf = false;
+
+ wireguard.enable = true;
+ };
+
+ systemd.network.networks = mkIf (cfg.dhcpInterface != null) {
+ "40-${cfg.dhcpInterface}" = {
+ matchConfig.Name = cfg.dhcpInterface;
+
+ networkConfig = {
+ DHCP = "ipv4";
+ IPv6AcceptRA = true;
+ IPv6PrivacyExtensions = "kernel";
+ };
+
+ # make routing on this interface a dependency for network-online.target
+ linkConfig.RequiredForOnline = "routable";
+ };
+ };
+ };
+}
diff --git a/sys/net/nets.nix b/sys/net/nets.nix
new file mode 100644
index 0000000..1bb3788
--- /dev/null
+++ b/sys/net/nets.nix
@@ -0,0 +1 @@
+# This file has been lustrated.
diff --git a/sys/net/options.nix b/sys/net/options.nix
new file mode 100644
index 0000000..0608fb9
--- /dev/null
+++ b/sys/net/options.nix
@@ -0,0 +1,278 @@
+{
+ config,
+ lib,
+ ...
+}:
+with lib; let
+ v4PtrHierarchy = address: bits: reverseList (sublist 0 (bits / 8) (splitString "." address));
+
+ v6PtrHierarchy = address: bits: let
+ separator = lists.findFirstIndex (hextet: hextet == "") null colonSplit;
+ colonSplit = splitString ":" address;
+
+ zeroFill = replicate (8 - length colonSplit + 1) "0000";
+ leftSplit = sublist 0 separator colonSplit;
+ rightSplit = sublist (separator + 1) (length colonSplit - separator - 1) colonSplit;
+
+ fullSplit =
+ if separator != null
+ then leftSplit ++ zeroFill ++ rightSplit
+ else colonSplit;
+
+ padded = map (hextet: strings.replicate (4 - stringLength hextet) "0" + hextet) fullSplit;
+ in
+ reverseList (sublist 0 (bits / 4) (flatten (map stringToCharacters padded)));
+
+ matchPtrRecordName = {
+ splitter,
+ netAddress,
+ netBits,
+ targetAddress,
+ targetBits,
+ }: let
+ netSplit = splitter netAddress netBits;
+ targetSplit = splitter targetAddress targetBits;
+
+ netLength = length netSplit;
+ lengthDelta = length targetSplit - netLength;
+
+ withinNet = lengthDelta >= 0 && sublist lengthDelta netLength targetSplit == netSplit;
+ throwMessage = "${targetAddress}/${toString targetBits} is not a subset of ${netAddress}/${toString netBits}";
+
+ recordHierarchy = sublist 0 lengthDelta targetSplit;
+
+ recordName =
+ if recordHierarchy != []
+ then concatStringsSep "." recordHierarchy
+ else "@";
+ in
+ throwIfNot withinNet throwMessage recordName;
+in {
+ options.local.nets = with lib.types;
+ mkOption {
+ readOnly = true;
+
+ type = attrsOf (submodule ({config, ...}: {
+ options = let
+ v4config = config.v4;
+ v6config = config.v6;
+ in {
+ hosts = mkOption {
+ default = {};
+
+ type = attrsOf (submodule {
+ options = {
+ v4 = mkOption {
+ default = null;
+
+ type = nullOr (submodule ({config, ...}: {
+ options = {
+ suffix = mkOption {
+ type = str;
+ };
+
+ address = mkOption {
+ type = str;
+ readOnly = true;
+ };
+
+ cidr = mkOption {
+ type = str;
+ readOnly = true;
+ };
+
+ single = mkOption {
+ type = str;
+ readOnly = true;
+ };
+ };
+
+ config = {
+ address =
+ if v4config.bits == 0
+ then config.suffix
+ else if v4config.bits == 32
+ then v4config.subnet
+ else "${v4config.prefix}.${config.suffix}";
+
+ cidr = "${config.address}/${toString v4config.bits}";
+ single = "${config.address}/32";
+ };
+ }));
+ };
+
+ v6 = mkOption {
+ default = null;
+
+ type = nullOr (submodule ({config, ...}: {
+ options = {
+ suffix = mkOption {
+ type = str;
+ };
+
+ address = mkOption {
+ type = str;
+ readOnly = true;
+ };
+
+ cidr = mkOption {
+ type = str;
+ readOnly = true;
+ };
+
+ single = mkOption {
+ type = str;
+ readOnly = true;
+ };
+ };
+
+ config = {
+ address = let
+ hextets = fragment: length (splitString ":" fragment);
+ separator =
+ if doubleColon
+ then "::"
+ else ":";
+ doubleColon = hextets v6config.prefix + hextets config.suffix < 8;
+
+ joined =
+ if v6config.bits == 128
+ then v6config.prefix
+ else if v6config.bits == 0
+ then config.suffix
+ else "${v6config.prefix}${separator}${config.suffix}";
+ in
+ joined;
+
+ cidr = "${config.address}/${toString v6config.bits}";
+ single = "${config.address}/128";
+ };
+ }));
+ };
+ };
+ });
+ };
+
+ v4 = mkOption {
+ default = null;
+
+ type = nullOr (submodule ({config, ...}: {
+ options = {
+ bits = mkOption {
+ type = enum [0 8 16 24 32];
+ };
+
+ prefix = mkOption {
+ type = str;
+ };
+
+ subnet = mkOption {
+ type = str;
+ readOnly = true;
+ };
+
+ cidr = mkOption {
+ type = str;
+ readOnly = true;
+ };
+
+ ptrDomain = mkOption {
+ type = str;
+ readOnly = true;
+ };
+
+ ptrRecordName = mkOption {
+ type = functionTo (functionTo str);
+ readOnly = true;
+ };
+ };
+
+ config = {
+ cidr = "${config.subnet}/${toString config.bits}";
+
+ subnet =
+ if config.bits != 0
+ then config.prefix + strings.replicate (4 - config.bits / 8) ".0"
+ else "0.0.0.0";
+
+ ptrDomain = concatStrings (map (x: x + ".") (v4PtrHierarchy config.subnet config.bits)) + "in-addr.arpa";
+
+ ptrRecordName = address: bits:
+ matchPtrRecordName {
+ splitter = v4PtrHierarchy;
+
+ netBits = config.bits;
+ netAddress = config.subnet;
+
+ targetBits = bits;
+ targetAddress = address;
+ };
+ };
+ }));
+ };
+
+ v6 = mkOption {
+ default = null;
+
+ type = nullOr (submodule ({config, ...}: {
+ options = {
+ bits = mkOption {
+ type =
+ addCheck (ints.between 0 128) (b: mod b 4 == 0)
+ // {
+ description = "IPv6 subnet bits at nibble boundary";
+ };
+ };
+
+ prefix = mkOption {
+ type = str;
+ };
+
+ subnet = mkOption {
+ type = str;
+ readOnly = true;
+ };
+
+ cidr = mkOption {
+ type = str;
+ readOnly = true;
+ };
+
+ ptrDomain = mkOption {
+ type = str;
+ readOnly = true;
+ };
+
+ ptrRecordName = mkOption {
+ type = functionTo (functionTo str);
+ readOnly = true;
+ };
+ };
+
+ config = {
+ cidr = "${config.subnet}/${toString config.bits}";
+
+ subnet =
+ if config.bits == 128 || length (splitString "::" config.prefix) > 1
+ then config.prefix
+ else "${config.prefix}::";
+
+ ptrDomain = concatStrings (map (x: x + ".") (v6PtrHierarchy config.subnet config.bits)) + "ip6.arpa";
+
+ ptrRecordName = address: bits:
+ matchPtrRecordName {
+ splitter = v6PtrHierarchy;
+
+ netBits = config.bits;
+ netAddress = config.subnet;
+
+ targetBits = bits;
+ targetAddress = address;
+ };
+ };
+ }));
+ };
+ };
+ }));
+ };
+}
diff --git a/sys/net/vsock.nix b/sys/net/vsock.nix
new file mode 100644
index 0000000..c6b0ad6
--- /dev/null
+++ b/sys/net/vsock.nix
@@ -0,0 +1,63 @@
+{
+ lib,
+ config,
+ pkgs,
+ ...
+}:
+with lib; let
+ cfg = config.local.net.vsock;
+in {
+ options.local.net.vsock = {
+ connect = mkOption {
+ default = {};
+ type = with lib.types;
+ attrsOf (submodule ({name, ...}: {
+ options = {
+ enable = mkEnableOption "vsock connect '${name}'";
+
+ cid = mkOption {
+ type = ints.u32;
+ default = 2;
+ };
+
+ localPort = mkOption {
+ type = port;
+ };
+
+ vsockPort = mkOption {
+ type = port;
+ };
+ };
+ }));
+ };
+ };
+
+ config = {
+ systemd = let
+ connects =
+ mapAttrs
+ (_: connect: {
+ service.serviceConfig = {
+ Type = "simple";
+ ExecStart = "${getExe pkgs.socat} - VSOCK:${toString connect.cid}:${toString connect.vsockPort}";
+ StandardInput = "socket";
+ };
+
+ socket = {
+ wantedBy = ["sockets.target"];
+
+ socketConfig = {
+ Accept = true;
+ ListenStream = "[::1]:${toString connect.localPort}";
+ };
+
+ unitConfig.ConditionVirtualization = "kvm";
+ };
+ })
+ cfg.connect;
+ in {
+ sockets = mapAttrs' (name: connect: nameValuePair "vsock-${name}" connect.socket) connects;
+ services = mapAttrs' (name: connect: nameValuePair "vsock-${name}@" connect.service) connects;
+ };
+ };
+}