From 2c3456e66b4137d686d4475969830dbb2bdc7776 Mon Sep 17 00:00:00 2001 From: Abel Luck Date: Tue, 5 May 2026 14:07:27 +0200 Subject: [PATCH] Add nixos-module --- flake.nix | 27 ++++--- nixos-module.nix | 190 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 203 insertions(+), 14 deletions(-) create mode 100644 nixos-module.nix diff --git a/flake.nix b/flake.nix index 6344226..af586d9 100644 --- a/flake.nix +++ b/flake.nix @@ -50,7 +50,6 @@ # } # // pkgs.lib.optionalAttrs pkgs.stdenv.isLinux { # nixos-module = pkgs.testers.runNixOSTest (import ./nixos-test.nix self); - # nixos-module-server = pkgs.testers.runNixOSTest (import ./nixos-test-server.nix self); # } #); @@ -62,19 +61,19 @@ }; }); - #nixosModules.default = - # { - # config, - # lib, - # pkgs, - # ... - # }: - # { - # imports = [ ./nixos-module.nix ]; - # services.prometheus.exporters.dnstt.package = - # lib.mkDefault - # self.packages.${pkgs.stdenv.hostPlatform.system}.default; + nixosModules.default = + { + lib, + pkgs, + ... + }: + { + imports = [ ./nixos-module.nix ]; + services.prometheus.exporters.dnstt.package = + lib.mkDefault + self.packages.${pkgs.stdenv.hostPlatform.system}.default; + }; - # }; + nixosModules.dnstt-exporter = self.nixosModules.default; }; } diff --git a/nixos-module.nix b/nixos-module.nix new file mode 100644 index 0000000..d01ff26 --- /dev/null +++ b/nixos-module.nix @@ -0,0 +1,190 @@ +{ + config, + lib, + pkgs, + ... +}: + +let + cfg = config.services.prometheus.exporters.dnstt; + inherit (lib) + escapeShellArgs + mkDefault + mkEnableOption + mkIf + mkOption + optionals + types + ; + + listenAddress = "${cfg.listenAddress}:${toString cfg.port}"; + args = [ + "-web.listen-address" + listenAddress + "-web.telemetry-path" + cfg.telemetryPath + "-dnstt.port" + (toString cfg.dnsttPort) + ] + ++ lib.concatMap (domain: [ + "-dnstt.domain" + domain + ]) cfg.domains + ++ optionals (cfg.geoip.countryDatabase != null) [ + "-geoip.country-database" + cfg.geoip.countryDatabase + ] + ++ optionals (cfg.geoip.asnDatabase != null) [ + "-geoip.asn-database" + cfg.geoip.asnDatabase + ] + ++ cfg.extraFlags; +in +{ + options.services.prometheus.exporters.dnstt = { + enable = mkEnableOption "the Prometheus DNSTT exporter"; + + package = mkOption { + type = types.package; + description = '' + Package providing the `dnstt_exporter` binary. + ''; + }; + + domains = mkOption { + type = types.nonEmptyListOf types.str; + example = [ "tunnel.example.com" ]; + description = '' + DNSTT tunnel domains to observe. The exporter matches DNS query names + below these domains and decodes DNSTT session IDs from the query prefix. + ''; + }; + + dnsttPort = mkOption { + type = types.port; + default = 53; + description = '' + UDP port where DNSTT DNS traffic is observed. + ''; + }; + + port = mkOption { + type = types.port; + default = 9713; + description = '' + Port to listen on for Prometheus metrics. + ''; + }; + + listenAddress = mkOption { + type = types.str; + default = "0.0.0.0"; + description = '' + Address to listen on for Prometheus metrics. + ''; + }; + + telemetryPath = mkOption { + type = types.str; + default = "/metrics"; + description = '' + HTTP path under which to expose Prometheus metrics. + ''; + }; + + geoip = { + countryDatabase = mkOption { + type = types.nullOr types.str; + default = null; + example = "/var/lib/GeoIP/GeoLite2-Country.mmdb"; + description = '' + Optional MaxMind GeoIP2 or GeoLite2 Country database path. When set, + exported DNSTT metrics include a `country` label for the resolver + address seen by the server. + ''; + }; + + asnDatabase = mkOption { + type = types.nullOr types.str; + default = null; + example = "/var/lib/GeoIP/GeoLite2-ASN.mmdb"; + description = '' + Optional MaxMind GeoIP2 or GeoLite2 ASN database path. When set, + exported DNSTT metrics include an `asn` label for the resolver address + seen by the server. + ''; + }; + }; + + extraFlags = mkOption { + type = types.listOf types.str; + default = [ ]; + description = '' + Extra command line flags to pass to `dnstt_exporter`. + ''; + }; + + openFirewall = mkOption { + type = types.bool; + default = false; + description = '' + Open the exporter metrics port in the firewall. + ''; + }; + }; + + config = mkIf cfg.enable { + assertions = [ + { + assertion = cfg.domains != [ ]; + message = "services.prometheus.exporters.dnstt.domains must contain at least one DNSTT domain."; + } + ]; + + networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [ cfg.port ]; + + systemd.services.prometheus-dnstt-exporter = { + description = "Prometheus DNSTT exporter"; + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + + serviceConfig = { + ExecStart = "${cfg.package}/bin/dnstt_exporter ${escapeShellArgs args}"; + Restart = "always"; + DynamicUser = true; + + AmbientCapabilities = [ "CAP_NET_RAW" ]; + CapabilityBoundingSet = [ "CAP_NET_RAW" ]; + NoNewPrivileges = true; + + LockPersonality = true; + MemoryDenyWriteExecute = true; + PrivateDevices = mkDefault false; + PrivateTmp = true; + ProtectClock = true; + ProtectControlGroups = true; + ProtectHome = true; + ProtectHostname = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectSystem = "strict"; + RemoveIPC = true; + RestrictAddressFamilies = [ + "AF_INET" + "AF_INET6" + "AF_PACKET" + ]; + RestrictNamespaces = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + SystemCallArchitectures = "native"; + UMask = "0077"; + }; + }; + }; + + meta.maintainers = [ ]; + meta.doc = false; + meta.buildDocsInSandbox = false; +}