diff --git a/flake.nix b/flake.nix index 6424333..4068c36 100644 --- a/flake.nix +++ b/flake.nix @@ -70,7 +70,7 @@ user = "root"; autoRollback = true; magicRollback = true; - remoteBuild = true; + remoteBuild = false; nodes = lib.mapAttrs (name: config: { hostname = lib.lists.head (lib.strings.splitString "/" config.ip4); profilesOrder = [ "system" ]; diff --git a/shared/bastion/wireguard.nix b/shared/bastion/wireguard.nix index 184b2aa..cc3cd23 100644 --- a/shared/bastion/wireguard.nix +++ b/shared/bastion/wireguard.nix @@ -52,4 +52,7 @@ in ]; }; }; + + fwtables.allowedMgmtFwdToMesh = true; + fwtables.allowedUDPPorts = [{ port = 51920; public = true; }]; } \ No newline at end of file diff --git a/shared/commons/mesh.nix b/shared/commons/mesh.nix index f530baf..d4f3c40 100644 --- a/shared/commons/mesh.nix +++ b/shared/commons/mesh.nix @@ -11,6 +11,8 @@ let # And mappings mapping = import ./../../mapping.nix; + meshPort = 51820; + buildSecret = zone: id: { "wg-private-zone-${toString zone}-id-${toString id}" = { file = ./../../secrets/wireguard + ( "/wg-private-zone-" + toString zone + "-id-" + toString id + ".age" ); @@ -32,13 +34,13 @@ let "172.19.${toString (peerConfig.zone + 127)}.0/24" "fc00:f::${toString (peerConfig.zone + 127)}:0/112" ]; - Endpoint = "${lib.lists.head (lib.strings.splitString "/" peerConfig.ip4)}:51820"; + Endpoint = "${lib.lists.head (lib.strings.splitString "/" peerConfig.ip4)}:${toString meshPort}"; PersistentKeepalive = 25; }) peerConfigs; interfaceConfig = { PrivateKeyFile = config.age.secrets."wg-private-zone-${toString myZone}-id-${toString myId}".path; - ListenPort = 51820; + ListenPort = meshPort; }; # Return route for mgmt traffic @@ -76,4 +78,6 @@ in routes = rtwg4 ++ rtwg6; }; }; + + fwtables.allowedUDPPorts = [{ port = meshPort; public = true; }]; } \ No newline at end of file diff --git a/shared/commons/nftables.nix b/shared/commons/nftables.nix index 003a017..88aa422 100644 --- a/shared/commons/nftables.nix +++ b/shared/commons/nftables.nix @@ -7,115 +7,150 @@ let # Import mapping mapping = import ./../../mapping.nix; + + generatePortRules = protocol: ports: let + publicPorts = lib.filter (p: p.public) ports; + privatePorts = lib.filter (p: !p.public) ports; + + publicRules = map (p: "${protocol} dport ${toString p.port} accept") publicPorts; + privateRules = map (p: "iifname mesh ${protocol} dport ${toString p.port} accept") privatePorts; + in + publicRules ++ privateRules; + + cfg = config.fwtables; in { - networking = { - nat.enable = false; - firewall.enable = false; - nftables = { - enable = true; - checkRuleset = true; - flushRuleset = true; - tables = { - filter = { - family = "inet"; - content = '' - chain preroute { - type filter hook prerouting priority -150; policy accept; - } - chain input { - type filter hook input priority 0; policy drop; - - # Authorized already setup connection - ct state related,established accept - - # Reject sus stuff - ct state invalid counter drop - tcp flags & (fin|syn|rst|ack) != syn ct state new counter drop - - # Loopback - iif lo accept - - # ICMP - icmp type { echo-request } limit rate 4/second accept - icmpv6 type { echo-request } limit rate 4/second accept - ip protocol icmp accept - icmpv6 type { nd-neighbor-solicit, nd-router-advert, nd-neighbor-advert } accept - - # SSH - tcp dport 22 accept - - # Mesh - udp dport 51820 accept - - ${if lib.elem myName mapping.bastion.hosts then '' - # Mgmt - udp dport 51920 accept - '' else ""} - - ${if myName == mapping.dns.master then '' - # DNS Master - iifname mesh tcp dport 53 accept - iifname mesh udp dport 53 accept - '' else ""} - - ${if lib.elem myName mapping.dns.secondary then '' - # DNS Secondary - tcp dport 53 accept - udp dport 53 accept - '' else ""} - - ${if lib.elem myName mapping.mail.hosts then '' - # Mail server (without IMAP) - # With support for both SSL & STARTTLS - tcp dport 25 accept - tcp dport 465 accept - tcp dport 587 accept - '' else ""} - - ${if lib.elem myName mapping.db.hosts then '' - # DNS Secondary - iifname mesh tcp dport 5432 accept - '' else ""} - - # Log anything else - ip protocol tcp counter log prefix "tcp.in.dropped: " - ip protocol udp counter log prefix "udp.in.dropped: " - - } - chain forward { - type filter hook forward priority 0; policy drop; - - ct state related,established accept - ct state invalid counter drop - - ${if lib.elem myName mapping.bastion.hosts then '' - iifname mgmt oifname mesh accept - '' else ""} - } - chain output { - type filter hook output priority 0; policy accept; - } - chain postroute { - type filter hook postrouting priority 50; policy accept; - } - ''; + options.fwtables = with lib; { + allowedTCPPorts = mkOption { + type = types.listOf (types.submodule { + options = { + port = mkOption { + type = types.port; + description = "The TCP port number"; + }; + public = mkOption { + type = types.bool; + default = false; + description = "Whether the port should be accessible from public internet (true) or only from mesh network (false)"; + }; }; - # We'll not nat the outgoing wg packet - # Instead custom routing is done on all - # other node to sent it back correclty - # That allow for custom ACL for the mgmt - # ip addresses - nat = { - family = "inet"; - content = '' - chain prerouting { - type nat hook prerouting priority -100; policy accept; - } - chain postrouting { - type nat hook postrouting priority 100; policy accept; - } - ''; + }); + default = []; + description = "List of allowed TCP ports with their accessibility settings"; + example = [ + { port = 80; public = true; } + { port = 8080; public = false; } + ]; + }; + + allowedUDPPorts = mkOption { + type = types.listOf (types.submodule { + options = { + port = mkOption { + type = types.port; + description = "The UDP port number"; + }; + public = mkOption { + type = types.bool; + default = false; + description = "Whether the port should be accessible from public internet (true) or only from mesh network (false)"; + }; + }; + }); + default = []; + description = "List of allowed UDP ports with their accessibility settings"; + example = [ + { port = 53; public = true; } + { port = 1234; public = false; } + ]; + }; + + allowedMgmtFwdToMesh = mkOption { + type = types.bool; + default = false; + description = "Allow traffic to jump from mgmt if to mesh if"; + }; + }; + + config = { + networking = { + nat.enable = false; + firewall.enable = false; + nftables = { + enable = true; + checkRuleset = true; + flushRuleset = true; + tables = { + filter = { + family = "inet"; + content = '' + chain preroute { + type filter hook prerouting priority -150; policy accept; + } + chain input { + type filter hook input priority 0; policy drop; + + # Authorized already setup connection + ct state related,established accept + + # Reject sus stuff + ct state invalid counter drop + tcp flags & (fin|syn|rst|ack) != syn ct state new counter drop + + # Loopback + iif lo accept + + # ICMP + icmp type { echo-request } limit rate 4/second accept + icmpv6 type { echo-request } limit rate 4/second accept + ip protocol icmp accept + icmpv6 type { nd-neighbor-solicit, nd-router-advert, nd-neighbor-advert } accept + + # Temporary SSH access for convenience during early stages + tcp dport 22 accept + + ${lib.concatStringsSep "\n" (generatePortRules "tcp" cfg.allowedTCPPorts)} + ${lib.concatStringsSep "\n" (generatePortRules "udp" cfg.allowedUDPPorts)} + + # Log anything else + ip protocol tcp counter log prefix "tcp.in.dropped: " + ip protocol udp counter log prefix "udp.in.dropped: " + + } + chain forward { + type filter hook forward priority 0; policy drop; + + ${lib.optionalString cfg.allowedMgmtFwdToMesh '' + ct state related,established accept + ct state invalid counter drop + + iifname mgmt oifname mesh accept + ''} + } + chain output { + type filter hook output priority 0; policy accept; + } + chain postroute { + type filter hook postrouting priority 50; policy accept; + } + ''; + }; + # We'll not nat the outgoing wg packet + # Instead custom routing is done on all + # other node to sent it back correclty + # That allow for custom ACL for the mgmt + # ip addresses + nat = { + family = "inet"; + content = '' + chain prerouting { + type nat hook prerouting priority -100; policy accept; + } + chain postrouting { + type nat hook postrouting priority 100; policy accept; + } + ''; + }; }; }; }; diff --git a/shared/db/postgres.nix b/shared/db/postgres.nix index da7ad96..90b4b25 100644 --- a/shared/db/postgres.nix +++ b/shared/db/postgres.nix @@ -84,4 +84,8 @@ in echo "PostgreSQL dataDir not empty, skipping initial master to slave replication" fi ''); + + fwtables.allowedTCPPorts = [ + { port = cfg.settings.port; public = false; } + ]; } \ No newline at end of file diff --git a/shared/dns/knot.nix b/shared/dns/knot.nix index 70a5228..4ef96cb 100644 --- a/shared/dns/knot.nix +++ b/shared/dns/knot.nix @@ -285,4 +285,7 @@ in ]; }; }; + + fwtables.allowedTCPPorts = [{ port = 53; public = true; }]; + fwtables.allowedUDPPorts = [{ port = 53; public = true; }]; } \ No newline at end of file diff --git a/shared/mail/maddy.nix b/shared/mail/maddy.nix index f2cd1b0..9a9c0b3 100644 --- a/shared/mail/maddy.nix +++ b/shared/mail/maddy.nix @@ -223,4 +223,10 @@ in }; }; }; + + fwtables.allowedTCPPorts = [ + { port = 25; public = true; } + { port = 465; public = true; } + { port = 587; public = true; } + ]; } \ No newline at end of file diff --git a/shared/users.nix b/shared/users.nix index 7567850..4fe0a6c 100644 --- a/shared/users.nix +++ b/shared/users.nix @@ -3,6 +3,13 @@ { # Users # Uid 1000 - 1999 are reserved for specific system users that need uid > 999 + nix.settings.trusted-users = [ + "asyncnomi" + "gamma" + "jeltz" + "soyouzpanda" + "raito" + ]; # Wheeler users.users.asyncnomi = {