{ lib, config, pkgs, ... }: with lib; let cfg = config.fudo.dns; join-lines = concatStringsSep "\n"; domainOpts = { domain, ... }: { options = with types; { dnssec = mkOption { type = bool; description = "Enable DNSSEC security for this zone."; default = true; }; dmarc-report-address = mkOption { type = nullOr str; description = "The email to use to recieve DMARC reports, if any."; example = "admin-user@domain.com"; default = null; }; network-definition = mkOption { type = submodule (import ../types/network-definition.nix); description = "Definition of network to be served by local server."; }; }; }; hostRecords = host: data: join-lines ((optional (data.ipv4-address != null) "${host} IN A ${data.ipv4-address}") ++ (optional (data.ipv6-address != null) "${host} IN AAAA ${data.ipv6-address}") ++ (map (sshfp: "${host} IN SSHFP ${sshfp}") data.ssh-fingerprints) ++ (optional (data.rp != null) "${host} IN RP ${data.rp}") ++ (optional (data.description != null) "${host} IN TXT ${data.description}")); makeSrvRecords = protocol: type: records: join-lines (map (record: "_${type}._${protocol} IN SRV ${toString record.priority} ${ toString record.weight } ${toString record.port} ${toString record.host}.") records); makeSrvProtocolRecords = protocol: types: join-lines (mapAttrsToList (makeSrvRecords protocol) types); cnameRecord = alias: host: "${alias} IN CNAME ${host}"; mxRecords = mxs: concatStringsSep "\n" (map (mx: "@ IN MX 10 ${mx}.") mxs); dmarcRecord = dmarc-email: optionalString (dmarc-email != null) '' _dmarc IN TXT "v=DMARC1;p=quarantine;sp=quarantine;rua=mailto:${dmarc-email};"''; nsRecords = domain: ns-hosts: join-lines (mapAttrsToList (host: _: "@ IN NS ${host}.${domain}.") ns-hosts); in { options.fudo.dns = with types; { enable = mkEnableOption "Enable master DNS services."; # FIXME: This should allow for AAAA addresses too... nameservers = mkOption { type = attrsOf (submodule hostOpts); description = "Map of domain nameserver FQDNs to IP."; example = { "ns1.domain.com" = { ip-addresses = [ "1.1.1.1" ]; ipv6-addresses = [ ]; description = "my fancy dns server"; }; }; }; identity = mkOption { type = str; description = "The identity (CH TXT ID.SERVER) of this host."; }; domains = mkOption { type = attrsOf (submodule domainOpts); default = { }; description = "A map of domain to domain options."; }; listen-ips = mkOption { type = listOf str; description = "A list of IPs on which to listen for DNS queries."; example = [ "1.2.3.4" ]; }; }; config = mkIf cfg.enable { services.nsd = { enable = true; identity = cfg.identity; interfaces = cfg.listen-ips; zones = mapAttrs' (dom: dom-cfg: nameValuePair "${dom}." { dnssec = dom-cfg.dnssec; data = '' $ORIGIN ${dom}. $TTL 12h @ IN SOA ns1.${dom}. hostmaster.${dom}. ( ${toString builtins.currentTime} 5m 2m 6w 5m) ${optionalString (dom-cfg.default-host != null) "@ IN A ${dom-cfg.default-host}"} ${mxRecords dom-cfg.mx} $TTL 6h ${optionalString (dom-cfg.gssapi-realm != null) ''_kerberos IN TXT "${dom-cfg.gssapi-realm}"''} ${nsRecords dom cfg.nameservers} ${join-lines (mapAttrsToList hostRecords cfg.nameservers)} ${dmarcRecord dom-cfg.dmarc-report-address} ${join-lines (mapAttrsToList makeSrvProtocolRecords dom-cfg.srv-records)} ${join-lines (mapAttrsToList hostRecords dom-cfg.hosts)} ${join-lines (mapAttrsToList cnameRecord dom-cfg.aliases)} ${join-lines dom-cfg.extra-dns-records} ''; }) cfg.domains; }; }; }