From 9777e5f83c3cd976aff948506a8d8e4425c809b0 Mon Sep 17 00:00:00 2001 From: Colin Date: Sun, 2 Jul 2023 08:21:33 +0000 Subject: [PATCH] trust-dns: rework the module to be more suitable for upstreaming still need to do hardening and docs --- hosts/by-name/servo/services/trust-dns.nix | 19 ++- modules/dns.nix | 37 +++--- modules/services/trust-dns.nix | 147 ++++++++++++--------- 3 files changed, 120 insertions(+), 83 deletions(-) diff --git a/hosts/by-name/servo/services/trust-dns.nix b/hosts/by-name/servo/services/trust-dns.nix index ce71087e..f13f174c 100644 --- a/hosts/by-name/servo/services/trust-dns.nix +++ b/hosts/by-name/servo/services/trust-dns.nix @@ -3,13 +3,21 @@ { sane.services.trust-dns.enable = true; - sane.services.trust-dns.listenAddrsIPv4 = [ + sane.services.trust-dns.settings.listen_addrs_ipv4 = [ # specify each address explicitly, instead of using "*". # this ensures responses are sent from the address at which the request was received. config.sane.hosts.by-name."servo".lan-ip "10.0.1.5" ]; sane.services.trust-dns.quiet = true; + # sane.services.trust-dns.debug = true; + + sane.ports.ports."53" = { + protocol = [ "udp" "tcp" ]; + visibleTo.lan = true; + visibleTo.wan = true; + description = "colin-dns-hosting"; + }; sane.dns.zones."uninsane.org".TTL = 900; @@ -53,8 +61,11 @@ # we need trust-dns to load our zone by relative path instead of /nix/store path # because we generate it at runtime. - sane.services.trust-dns.zones."uninsane.org".file = lib.mkForce "uninsane.org.zone"; - sane.services.trust-dns.zonedir = null; + sane.services.trust-dns.settings.zones = [ + { + zone = "uninsane.org"; + } + ]; sane.services.trust-dns.package = let @@ -62,7 +73,7 @@ zone-dir = "/var/lib/trust-dns"; zone-wan = "${zone-dir}/wan/uninsane.org.zone"; zone-lan = "${zone-dir}/lan/uninsane.org.zone"; - zone-template = pkgs.writeText "uninsane.org.zone.in" config.sane.services.trust-dns.zones."uninsane.org".text; + zone-template = pkgs.writeText "uninsane.org.zone.in" config.sane.dns.zones."uninsane.org".rendered; in pkgs.writeShellScriptBin "named" '' # compute wan/lan values mkdir -p ${zone-dir}/{ovpn,wan,lan} diff --git a/modules/dns.nix b/modules/dns.nix index 8adfba01..c2268b56 100644 --- a/modules/dns.nix +++ b/modules/dns.nix @@ -1,3 +1,5 @@ +# TODO: consider using this library for .zone file generation: +# - { config, lib, pkgs, ... }: with builtins; @@ -63,12 +65,22 @@ in options = { sane.dns = with lib; { zones = mkOption { - type = types.attrsOf (types.submodule { + description = '' + declarative zone config. + this doesn't feed into anything, rather, one should read `config.sane.dns.zones."foo".rendered` + and do something with it. + ''; + default = {}; + type = types.attrsOf (types.submodule ({ name, config, ... }: { options = { name = mkOption { type = types.nullOr types.str; description = "zone name. defaults to the attribute name in zones"; - default = null; + default = name; + }; + rendered = mkOption { + type = types.str; + description = "rendered .zone text for this zone (read-only)"; }; TTL = mkOption { type = types.int; @@ -122,25 +134,12 @@ in default = {}; }; }; - - file = mkOption { - type = types.nullOr types.str; - default = null; - description = '' - instead of using the generated zone file, use the specified path (user should populate the file specified here). - ''; - }; }; - }); - default = {}; - description = "Declarative zone config"; + config = { + rendered = genZone config; + }; + })); }; }; }; - - config = { - sane.services.trust-dns.zones = mapAttrs (_name: zcfg: { - text = genZone zcfg; - }) cfg.zones; - }; } diff --git a/modules/services/trust-dns.nix b/modules/services/trust-dns.nix index 6aa3a8e1..82cee2f4 100644 --- a/modules/services/trust-dns.nix +++ b/modules/services/trust-dns.nix @@ -1,23 +1,16 @@ +# WIP: porting to the API described here: +# - +# - TODO: hardening! { config, lib, pkgs, ... }: -# TODO: consider using this library for .zone file generation: -# - - with lib; let cfg = config.sane.services.trust-dns; toml = pkgs.formats.toml { }; - configFile = toml.generate "trust-dns.toml" { - listen_addrs_ipv4 = cfg.listenAddrsIPv4; - zones = attrValues ( - mapAttrs (zname: zcfg: rec { - zone = if zcfg.name == null then zname else zcfg.name; - zone_type = "Primary"; - file = zcfg.file; - }) cfg.zones - ); - }; + configFile = toml.generate "trust-dns.toml" ( + lib.filterAttrsRecursive (_: v: v != null) cfg.settings + ); in { options = { @@ -34,79 +27,113 @@ in should provide bin/named, which will be invoked with --config x and --zonedir d and maybe -q. ''; }; - listenAddrsIPv4 = mkOption { - type = types.listOf types.str; - default = []; - description = "array of ipv4 addresses on which to listen for DNS queries"; - }; quiet = mkOption { type = types.bool; default = false; - }; - zonedir = mkOption { - type = types.nullOr types.str; - default = "/"; description = '' - where the `file` option in zones.* is relative to. + log ERROR level messages only. + if not specified, defaults to INFO level logging. + mutually exclusive with the `debug` option. ''; }; - # reference - zones = mkOption { - type = types.attrsOf (types.submodule ({ config, name, ... }: { + debug = mkOption { + type = types.bool; + default = false; + description = '' + log DEBUG, INFO, WARN and ERROR messages. + if not specified, defaults to INFO level logging. + mutually exclusive with the `quiet` option. + ''; + }; + settings = mkOption { + type = types.submodule { + freeformType = toml.type; options = { - name = mkOption { - type = types.nullOr types.str; - description = "zone name. defaults to the attribute name in zones"; - default = name; + listen_addrs_ipv4 = mkOption { + type = types.listOf types.str; + default = []; + description = "array of ipv4 addresses on which to listen for DNS queries"; }; - text = mkOption { - type = types.nullOr types.lines; - default = null; + listen_addrs_ipv6 = mkOption { + type = types.listOf types.str; + default = []; + description = "array of ipv6 addresses on which to listen for DNS queries"; }; - file = mkOption { - type = types.nullOr (types.either types.path types.str); + listen_port = mkOption { + type = types.port; + default = 53; description = '' - path to a .zone file. - if omitted, will be generated from the `text` option. + port to listen on (applies to all listen addresses). ''; }; + directory = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + directory in which trust-dns will look for .zone files + whenever zones aren't specified by absolute path. + upstream defaults this to "/var/named". + ''; + }; + zones = mkOption { + description = "Declarative zone config"; + default = {}; + type = types.listOf (types.submodule ({ config, name, ... }: { + options = { + zone = mkOption { + type = types.str; + description = '' + zone name, like "example.com", "localhost", or "0.0.127.in-addr.arpa". + ''; + }; + zone_type = mkOption { + type = types.enum [ "Primary" "Secondary" "Hint" "Forward" ]; + default = "Primary"; + description = '' + one of: + - "Primary" (the master, authority for the zone) + - "Secondary" (the slave, replicated from the primary) + - "Hint" (a cached zone with recursive resolver abilities) + - "Forward" (a cached zone where all requests are forwarded to another resolver) + ''; + }; + file = mkOption { + type = types.either types.path types.str; + description = '' + path to a .zone file. + if not a fully-qualified path, it will be interpreted relative to the `directory` option. + defaults to the value of `zone` suffixed with ".zone". + ''; + }; + }; + config = { + file = lib.mkDefault "${config.zone}.zone"; + }; + })); + }; }; - - config = { - file = lib.mkIf (config.text != null) (pkgs.writeText "${config.name}.zone" config.text); - }; - })); - default = {}; - description = "Declarative zone config"; + }; }; }; }; config = mkIf cfg.enable { - sane.ports.ports."53" = { - protocol = [ "udp" "tcp" ]; - visibleTo.lan = true; - visibleTo.wan = true; - description = "colin-dns-hosting"; - }; - systemd.services.trust-dns = { description = "trust-dns DNS server"; serviceConfig = { ExecStart = - let - flags = lib.optional cfg.quiet "-q" ++ - lib.optionals (cfg.zonedir != null) [ "--zonedir" cfg.zonedir ]; - flagsStr = builtins.concatStringsSep " " flags; - in '' - ${cfg.package}/bin/named \ - --config ${configFile} \ - ${flagsStr} - ''; + let + flags = lib.optional cfg.debug "--debug" + ++ lib.optional cfg.quiet "--quiet"; + flagsStr = builtins.concatStringsSep " " flags; + in '' + ${cfg.package}/bin/named --config ${configFile} ${flagsStr} + ''; Type = "simple"; Restart = "on-failure"; RestartSec = "10s"; # TODO: hardening (like, don't run as root!) + # TODO: link to docs }; after = [ "network.target" ]; wantedBy = [ "multi-user.target" ];