{ config, lib, pkgs, ... }@toplevel: with lib; let hostname = config.instance.hostname; cfg = config.fudo.services.dns; host-secrets = config.fudo.secrets.host-secrets."${hostname}"; domain-name = config.instance.local-domain; domain = config.fudo.domains.${domain-name}; primary-nameserver = domain.primary-nameserver; primary-nameserver-ip = pkgs.lib.network.host-ipv4 config primary-nameserver; primary-nameserver-fqdn = "${primary-nameserver}.${domain-name}"; is-primary-nameserver = primary-nameserver == hostname; zoneKeySecret = zone: "${zone}-ksk"; nameserverOpts = { name, ... }: { options = with types; { hostname = mkOption { type = str; description = "Hostname of the external nameserver."; default = name; }; ipv4-address = mkOption { type = nullOr str; description = "Host ipv4 address of the external nameserver."; default = null; }; ipv6-address = mkOption { type = nullOr str; description = "Host ipv6 address of the external nameserver."; default = null; }; authoritative-hostname = mkOption { type = nullOr str; description = "Authoritative hostname of this nameserver."; default = null; }; description = mkOption { type = str; description = "Description of the external nameserver."; }; }; }; zoneOpts = { name, ... }: let zone-name = name; in { options = with types; { enable = mkOption { type = bool; description = "Enable ${zone-name} zone on the local nameserver."; default = zone-name == toplevel.config.instance.local-zone; }; default-host = mkOption { type = nullOr str; description = "IP which will respond to requests for the base domain."; default = null; }; external-nameservers = mkOption { type = listOf (submodule nameserverOpts); description = "Off-network secondary nameservers."; default = [ ]; }; domain = mkOption { type = str; description = "Domain which this zone serves."; default = zone-name; }; ksk = mkOption { type = nullOr (submodule { options = { private-key = mkOption { type = path; description = "KSK private key."; }; public-key = mkOption { type = path; description = "KSK public key."; }; ds = mkOption { type = path; description = "KSK ds record."; }; }; }); description = "Location of the zone-signing private & public keys and DS record."; default = toplevel.config.fudo.secrets.files.dns.key-signing-keys."${zone-name}"; }; }; }; in { options.fudo.services.dns = with types; { zones = mkOption { type = attrsOf (submodule zoneOpts); description = "Map of served zone to extra zone details."; default = { }; }; }; config.fudo = mkIf (!isNull domain.primary-nameserver) { secrets.host-secrets."${hostname}" = mkIf is-primary-nameserver (mapAttrs' (zone: zone-cfg: nameValuePair (zoneKeySecret zone) { source-file = zone-cfg.ksk.private-key; target-file = "/run/nsd/${baseNameOf zone-cfg.ksk.private-key}"; user = config.fudo.nsd.user; }) cfg.zones); zones = mkIf (hostname == primary-nameserver) (mapAttrs (zone-name: zone-cfg: let domain-name = zone-cfg.domain; domain = config.fudo.domains.${domain-name}; make-srv-record = port: host: { inherit port host; }; served-domain = domain.primary-nameserver != null; primary-nameserver = domain.primary-nameserver; is-primary-nameserver = hostname == primary-nameserver; internal-nameserver-hostnames = [ domain.primary-nameserver ] ++ domain.secondary-nameservers; get-host-deets = description: hostname: { ipv4-address = pkgs.lib.network.host-ipv4 config hostname; ipv6-address = pkgs.lib.network.host-ipv6 config hostname; description = description; }; get-ns-deets = hostname: let host-domain = config.fudo.hosts.${hostname}.domain; desc = "${domain-name} nameserver ${hostname}.${host-domain}."; in get-host-deets desc hostname; nameserver-deets = let internal-nameservers = map get-ns-deets internal-nameserver-hostnames; in internal-nameservers ++ zone-cfg.external-nameservers; has-auth-hostname = ns-host: ns-opts: (hasAttr "authoritative-hostname" ns-opts) && (ns-opts.authoritative-hostname != null); all-nameservers = listToAttrs (imap1 (i: nsOpts: nameValuePair "ns${toString i}" nsOpts) nameserver-deets); nameserver-aliases = mapAttrs (hostname: opts: "${opts.authoritative-hostname}.") (filterAttrs has-auth-hostname all-nameservers); nameserver-hosts = mapAttrs (hostname: opts: { inherit (opts) ipv4-address ipv6-address description; }) (filterAttrs (hostname: opts: !has-auth-hostname hostname opts) all-nameservers); dns-srv-records = let nameserver-srv-records = mapAttrsToList (hostname: hostOpts: let target-host = if (has-auth-hostname hostname hostOpts) then "${hostOpts.authoritative-hostname}" else "${hostname}.${domain-name}"; in make-srv-record 53 target-host) all-nameservers; in { tcp.domain = nameserver-srv-records; udp.domain = nameserver-srv-records; }; in { gssapi-realm = domain.gssapi-realm; hosts = nameserver-hosts; aliases = nameserver-aliases; mx = optional (domain.primary-mailserver != null) (let mail-domain-name = config.fudo.hosts."${domain.primary-mailserver}".domain; in "mail.${mail-domain-name}"); dmarc-report-address = "dmarc-report@${domain-name}"; nameservers = let direct-external = attrValues nameserver-aliases; internal = map (hostname: "${hostname}.${domain-name}.") (attrNames nameserver-hosts); in internal ++ direct-external; srv-records = dns-srv-records; # // mail-srv-records; verbatim-dns-records = let pthru = o: trace o o; in mkIf (zone-cfg.ksk != null) [ (pthru (readFile zone-cfg.ksk.public-key)) (pthru (readFile zone-cfg.ksk.ds)) ]; }) cfg.zones); dns = { enable = is-primary-nameserver; identity = "${hostname}.${domain-name}"; listen-ips = optionals is-primary-nameserver (pkgs.lib.network.host-ips config hostname); domains = mapAttrs' (zone-name: zone-cfg: nameValuePair zone-cfg.domain { dnssec = zone-cfg.ksk != null; ksk.key-file = host-secrets."${zoneKeySecret zone-name}".target-file; zone-definition = config.fudo.zones."${zone-name}" // { inherit (zone-cfg) default-host; }; }) cfg.zones; }; }; }