From 869cecc8a0dd9f5af2e36c89ddabe54f25bc2ebc Mon Sep 17 00:00:00 2001 From: Jakub Stachurski Date: Thu, 30 Jul 2026 21:58:26 +0200 Subject: Get inventory to manage monitoring connections This makes it so you can define the monitoring in the inventory and tacitus will automatically, pick it up. --- .gitignore | 3 +- hosts/apocalypse/default.nix | 2 + hosts/apocalypse/firewall.nix | 29 ++++--- hosts/omega-relay/firewall.nix | 47 ++++++----- hosts/tacitus/network.nix | 26 +++--- inventory.nix | 60 ++++++++++++- modules/default.nix | 1 + modules/firewall.nix | 132 +++++++++++++++++++++++++++++ modules/prometheus.nix | 185 ++++++++++++++++++++++++++--------------- 9 files changed, 361 insertions(+), 124 deletions(-) create mode 100644 modules/firewall.nix diff --git a/.gitignore b/.gitignore index 9e91979..150aa83 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ *.pass *_ed25519.pub *_ed25519 -secrets/*.pem +secrets/*.pem +.hidden diff --git a/hosts/apocalypse/default.nix b/hosts/apocalypse/default.nix index cb81aa7..238ab09 100644 --- a/hosts/apocalypse/default.nix +++ b/hosts/apocalypse/default.nix @@ -13,6 +13,8 @@ "127.0.0.1" = [ "apocalypse.local" ]; }; + wilkuu.services.prometheus.enableExporters = true; + services.logind.settings.Login = { HandleLidSwitch = "suspend"; HandleLidSwitchExternalPower = "lock"; diff --git a/hosts/apocalypse/firewall.nix b/hosts/apocalypse/firewall.nix index f45d4f7..4bc4b32 100644 --- a/hosts/apocalypse/firewall.nix +++ b/hosts/apocalypse/firewall.nix @@ -3,6 +3,7 @@ let baseTCP = [ 22000 # Syncthng 5352 # Zeroconf for spotifyd + 22 # ssh ]; baseUDP = [ 22000 # Syncthing @@ -24,7 +25,6 @@ let ]; secureTCP = [ - 22 80 433 5900 # SSH HTTP VNC @@ -42,23 +42,28 @@ let ]; in { - environment.etc.hosts.mode = "0644"; - networking.nftables.enable = true; - networking.firewall = { - enable = false; - checkReversePath = false; - allowedTCPPorts = baseTCP; - allowedUDPPorts = baseUDP; - allowedUDPPortRanges = baseUDPRanges; - allowedTCPPortRanges = baseTCPRanges; - interfaces = { - "nix-laptop" = { + wilkuu.firewall = { + enable = true; + defaultLayer = "external"; + layers = { + internal = { allowedTCPPorts = secureTCP; allowedUDPPorts = secureUDP; allowedUDPPortRanges = secureUDPRanges; allowedTCPPortRanges = secureTCPRanges; }; + external = { + allowedTCPPorts = baseTCP; + allowedUDPPorts = baseUDP; + allowedUDPPortRanges = baseUDPRanges; + allowedTCPPortRanges = baseTCPRanges; + }; }; + }; + environment.etc.hosts.mode = "0644"; + networking.nftables.enable = true; + networking.firewall = { + enable = true; trustedInterfaces = [ "docker0" "br-*" diff --git a/hosts/omega-relay/firewall.nix b/hosts/omega-relay/firewall.nix index 0e82fea..8e35fa3 100644 --- a/hosts/omega-relay/firewall.nix +++ b/hosts/omega-relay/firewall.nix @@ -1,5 +1,12 @@ -{ config, lib, ... }: +{ + config, + lib, + inventory, + self_name, + ... +}: let + net = inventory.${self_name}.interfaces; wgHomePort = 16888; baseTCP = [ 20 @@ -23,16 +30,7 @@ let secureTCP = [ # config.wilkuu.services.mysql.port - ] - ++ lib.mapAttrsToList (_: opt: opt.port) ( - lib.filterAttrs ( - _: e: - let - evaluated = builtins.tryEval e; - in - evaluated.success && e ? enable && e.enable - ) config.services.prometheus.exporters - ); + ]; secureUDP = [ ]; @@ -63,7 +61,7 @@ in useNetworkd = true; interfaces = { wg-home = { - ips = [ "192.168.80.100/24" ]; + ips = [ "${net.wg-home.ip}/24" ]; extraOptions = { DNS = "192.168.88.1"; }; @@ -113,24 +111,27 @@ in linkConfig.RequiredForOnline = "routable"; }; - networking.useDHCP = false; - networking.useNetworkd = true; - networking.nftables.enable = true; - networking.firewall = { + wilkuu.firewall = { enable = true; - checkReversePath = false; - allowedTCPPorts = baseTCP; - allowedUDPPorts = baseUDP; - allowedUDPPortRanges = baseUDPRanges; - allowedTCPPortRanges = baseTCPRanges; - interfaces = { - "wg-home" = { + defaultLayer = "external"; + layers = { + external = { + allowedTCPPorts = baseTCP; + allowedUDPPorts = baseUDP; + allowedUDPPortRanges = baseUDPRanges; + allowedTCPPortRanges = baseTCPRanges; + }; + internal = { allowedTCPPorts = secureTCP; allowedUDPPorts = secureUDP; allowedUDPPortRanges = secureUDPRanges; allowedTCPPortRanges = secureTCPRanges; }; }; + }; + networking.useDHCP = false; + networking.useNetworkd = true; + networking.firewall = { trustedInterfaces = [ "docker0" "br-*" diff --git a/hosts/tacitus/network.nix b/hosts/tacitus/network.nix index 513d89c..9b3beef 100644 --- a/hosts/tacitus/network.nix +++ b/hosts/tacitus/network.nix @@ -48,27 +48,21 @@ in ]; }; }; - networking = { - useNetworkd = true; - nftables.enable = true; - useDHCP = true; - firewall = { - # check enable = true; - checkReversePath = false; + + wilkuu.firewall = { + enable = true; + defaultLayer = "internal"; + layers.internal = { allowedTCPPorts = baseTCP; allowedUDPPorts = baseUDP; allowedUDPPortRanges = baseUDPRanges; allowedTCPPortRanges = baseTCPRanges; - # TODO: Figure out how to do FW that allows only on the internal ip range - #interfaces = { - # "wg-home" = { - # allowedTCPPorts = secureTCP; - # allowedUDPPorts = secureUDP; - # allowedUDPPortRanges = secureUDPRanges; - # allowedTCPPortRanges = secureTCPRanges; - # }; - #}; }; }; + networking = { + useNetworkd = true; + nftables.enable = true; + useDHCP = true; + }; } diff --git a/inventory.nix b/inventory.nix index db3d3f9..a476191 100644 --- a/inventory.nix +++ b/inventory.nix @@ -7,7 +7,27 @@ ./users/wilkuu-server.nix inputs.stalwart-nix.nixosModules.default ]; - interfaces = { }; + monitoring = { + internal = [ + "wireguard" + "fail2ban" + "node" + ]; + }; + interfaces = { + wg-home = { + type = "wireguard"; + layer = "internal"; + ip = "192.168.80.100"; + }; + enp6s18 = { + type = "eth-networkd"; + layer = "external"; + ip = "45.136.141.133"; + ip6 = "2a12:bec0:650:128::133/64"; + }; + }; + }; apocalypse = { type = "desktop"; @@ -16,8 +36,24 @@ nix-modules = [ ./users/wilkuu.nix ]; - interfaces = { }; + monitoring = { + internal = [ "node" ]; + }; + interfaces = { + nix-laptop = { + type = "wireguard"; + layer = "internal"; + ip = "192.168.80.99"; + }; + wifi = { + type = "roaming"; + }; + eth = { + type = "roaming"; + }; + }; }; + tacitus = { type = "desktop"; system = "x86_64-linux"; @@ -25,7 +61,25 @@ nix-modules = [ ./users/wilkuu-server.nix ]; - interfaces = { }; + monitoring = { + local = [ + "node" + "mikrotik" + ]; + }; + interfaces = { + enp7s0 = { + type = "eth-networkd"; + layer = "internal"; + ip = "192.168.88.5"; + }; + lo = { + # A little workaround for not needing to go over the net to get own stats + type = "roaming"; # TODO Set to something more sensible + layer = "local"; + ip = "localhost"; + }; + }; }; # TODO: Support for live images as packages diff --git a/modules/default.nix b/modules/default.nix index 6cc52d8..39915dd 100644 --- a/modules/default.nix +++ b/modules/default.nix @@ -17,6 +17,7 @@ ./test_endpoint.nix ./prometheus.nix ./mjmap.nix + ./firewall.nix ]; nixpkgs.overlays = [ diff --git a/modules/firewall.nix b/modules/firewall.nix new file mode 100644 index 0000000..575de7d --- /dev/null +++ b/modules/firewall.nix @@ -0,0 +1,132 @@ +{ + config, + inventory, + self_name, + lib, + ... +}: +let + inherit (lib) + mkOption + types + mkIf + mkEnableOption + mkDefault + ; + inv = inventory.${self_name}; + net = inv.interfaces; + cfg = config.wilkuu.firewall; + + portRangeType = types.addCheck (types.submodule { + options = { + from = mkOption { + type = types.port; + example = 100; + description = "lowest part of the range (inclusive)"; + }; + to = mkOption { + type = types.port; + example = 200; + description = "highest part of the range (inclusive)"; + }; + }; + }) (range: range.from < range.to); + + mkPortsOption = + protocol: + mkOption { + type = types.listOf types.port; + default = [ ]; + example = [ 1234 ]; + description = "List of ${protocol} ports to accept"; + }; + + mkPortRangesOption = + protocol: + mkOption { + type = types.listOf portRangeType; + default = [ ]; + example = [ 1234 ]; + description = "List of ${protocol} port ranges to accept"; + }; + + layerAssertions = lib.mapAttrsToList ( + layerName: layerConfig: + let + missing = builtins.filter (importLayer: !(cfg.layers ? ${importLayer})) layerConfig.import-layer; + in + { + assertion = missing == [ ]; + message = "Layer ${layerName} imports undefined layer(s): ${toString missing}"; + } + ) cfg.layers; +in +{ + options.wilkuu.firewall = { + enable = mkEnableOption "firewall module"; + defaultLayer = lib.mkOption { + type = types.str; + default = "external"; + example = "eth"; + }; + layers = mkOption { + default = { }; + description = "Layers and which ports should be open"; + type = types.attrsOf ( + types.submodule { + options = { + import-layer = mkOption { + type = types.listOf types.str; + default = [ ]; + example = [ "external" ]; + description = "Layer names which ports will also be opened on this layer"; + }; + allowedTCPPorts = mkPortsOption "TCP"; + allowedUDPPorts = mkPortsOption "UDP"; + allowedTCPPortRanges = mkPortRangesOption "TCP"; + allowedUDPPortRanges = mkPortRangesOption "UDP"; + }; + } + ); + }; + }; + + config = mkIf cfg.enable { + assertions = layerAssertions; + networking.nftables.enable = true; + networking.firewall = + let + # TODO: reconsider doing this as this will only do a flat-import anything more complex would require building an import tree. + resolvedLayers = lib.mapAttrs ( + _: l1: + lib.foldl' (acc: set2: { + allowedTCPPorts = acc.allowedTCPPorts ++ set2.allowedTCPPorts; + allowedUDPPorts = acc.allowedUDPPorts ++ set2.allowedUDPPorts; + allowedTCPPortRanges = acc.allowedTCPPortRanges ++ set2.allowedTCPPortRanges; + allowedUDPPortRanges = acc.allowedUDPPortRanges ++ set2.allowedUDPPortRanges; + }) l1 (map (lk: cfg.layers.${lk}) l1.import-layer) + ) cfg.layers; + + interfaces = lib.mapAttrs (_n: interface: { + inherit (resolvedLayers.${interface.layer}) + allowedTCPPorts + allowedUDPPorts + allowedTCPPortRanges + allowedUDPPortRanges + ; + }) (lib.filterAttrs (_n: interface: !(builtins.elem interface.type [ "roaming" ])) net); + + in + { + enable = true; + checkReversePath = mkDefault false; + inherit interfaces; + inherit (resolvedLayers.${cfg.defaultLayer}) + allowedTCPPorts + allowedUDPPorts + allowedTCPPortRanges + allowedUDPPortRanges + ; + }; + }; +} diff --git a/modules/prometheus.nix b/modules/prometheus.nix index 63b761f..83eda86 100644 --- a/modules/prometheus.nix +++ b/modules/prometheus.nix @@ -1,11 +1,18 @@ -{ lib, config, ... }: +{ + lib, + config, + inventory, + self_name, + options, + ... +}: let cfg = config.wilkuu.services.prometheus; + mon = inventory.${self_name}.monitoring; inherit (lib) mkEnableOption mkIf mkMerge - mkDefault ; in { @@ -15,16 +22,45 @@ in }; config = mkMerge [ - (mkIf cfg.enableExporters { - services.prometheus.exporters = { - wireguard.enable = mkDefault config.networking.wireguard.enable; - fail2ban.enable = mkDefault config.services.fail2ban.enable; - node = { + (mkIf cfg.enableExporters ( + let + data = lib.mapAttrs ( + _name: mn: + let + default = (builtins.filter (a: builtins.isString a) mn); + custom = (builtins.filter (a: builtins.isAttrs a) mn); + in + { + ports = + builtins.map (n: options.services.prometheus.exporters.value.${n}.port) default + ++ (builtins.map (a: a.port) custom); + custom_port_mappings = builtins.listToAttrs ( + map ({ name, port, ... }: { + inherit name; + value = port; + }) custom + ); + exporter_services = + default + ++ (builtins.map (a: a.name) (builtins.filter ((a: !(a.skipExporterConfig or false))) custom)); + } + ) mon; + data_all = (lib.foldl lib.recursiveUpdate { } (lib.attrValues data)); + in + { + # Enable all the exporters outlined in the inventory + services.prometheus.exporters = lib.genAttrs data_all.exporter_services (name: { enable = true; - }; - }; + port = lib.mkIf (data_all.custom_port_mappings ? name) data_all.custom_port_mappings.${name}; + + }); + # Let all the exporters export on all layers + wilkuu.firewall.layers = lib.mapAttrs (_n: a: { + allowedTCPPorts = a.ports; + }) data; + } + )) - }) (mkIf cfg.enableScraper { sops.secrets = { "prometheus/stalwart-pass" = { @@ -52,76 +88,87 @@ in services.prometheus = let - omega-relay-ip = "192.168.80.100"; + nets_in_layer = inv: layer: lib.filterAttrs (_n: ifi: (ifi.layer or "") == layer) inv.interfaces; + dests_in_layer = + inv: layer: port: + (map (net: "${net.ip}:${toString port}") (lib.attrValues (nets_in_layer inv layer))); + + job_destinations = ( + builtins.foldl' + ( + acc: perHost: + (builtins.foldl' ( + acc2: hostMonitorName: + let + current = acc2.${hostMonitorName} or [ ]; + new = perHost.${hostMonitorName}; + in + acc2 // { ${hostMonitorName} = current ++ new; } + ) acc (lib.attrNames perHost)) + ) + { } + ( + lib.mapAttrsToList ( + _host: inv: + (lib.foldl' (a: b: a // b) { } ( + lib.mapAttrsToList ( + + layer: lst: + (lib.listToAttrs ( + builtins.map ( + a: + let + ifSimple = + a: t: f: + if builtins.isString a then t else f; + name = ifSimple a a a.name; + port = ifSimple a options.services.prometheus.exporters.value.${a}.port ( + a.port or options.services.prometheus.exporters.${a.name}.port.default + ); + in + { + inherit name; + value = (dests_in_layer inv layer port); + } + ) lst + )) + ) inv.monitoring + )) + ) inventory + ) + ); + in { enable = true; globalConfig = { scrape_interval = "10s"; }; - scrapeConfigs = [ - { - job_name = "node"; - static_configs = - let - port = toString config.services.prometheus.exporters.node.port; - in - [ - { - targets = [ - "localhost:${port}" - "${omega-relay-ip}:${port}" - ]; - } - ]; - } - { - job_name = "wireguard"; - static_configs = - let - port = toString config.services.prometheus.exporters.wireguard.port; - in - [ - { targets = [ "${omega-relay-ip}:${port}" ]; } - ]; - } - { - job_name = "fail2ban"; - static_configs = - let - port = toString config.services.prometheus.exporters.fail2ban.port; - in - [ - { targets = [ "${omega-relay-ip}:${port}" ]; } - ]; - - } - { - job_name = "stalwart"; - metrics_path = "/metrics/prometheus"; - scheme = "https"; - basic_auth = { - username = "prometheus_wilkuu"; - password_file = config.sops.secrets."prometheus/stalwart-pass".path; - }; + scrapeConfigs = + (lib.mapAttrsToList (name: dests: { + job_name = name; static_configs = [ { - targets = [ "mail.wilkuu.xyz:443" ]; + targets = dests; } ]; - } - { - job_name = "mikrotik"; - static_configs = - let - port = toString config.services.prometheus.exporters.mikrotik.port; - in - [ - { targets = [ "localhost:${port}" ]; } + }) job_destinations) + ++ [ + { + job_name = "stalwart"; + metrics_path = "/metrics/prometheus"; + scheme = "https"; + basic_auth = { + username = "prometheus_wilkuu"; + password_file = config.sops.secrets."prometheus/stalwart-pass".path; + }; + static_configs = [ + { + targets = [ "mail.wilkuu.xyz:443" ]; + } ]; - - } - ]; + } + ]; }; }) ]; -- cgit v1.3.1