nixos-config/config/service/authoritative-dns.nix

294 lines
8.5 KiB
Nix
Raw Normal View History

2023-10-14 16:15:26 -07:00
{ config, lib, pkgs, ... }@toplevel:
with lib;
let
hostname = config.instance.hostname;
cfg = config.fudo.services.authoritative-dns;
hostSecrets = config.fudo.secrets.host-secrets."${hostname}";
domainName = config.instance.local-domain;
zoneKeySecret = zone: "${zone}-ksk";
networkHostOpts = {
options = with types; {
hostname = mkOption {
type = str;
description = "Hostname.";
};
ipv4-address = mkOption {
type = nullOr str;
description = "The V4 IP of a given host, if any.";
default = null;
};
ipv6-address = mkOption {
type = nullOr str;
description = "The V6 IP of a given host, if any.";
default = null;
};
mac-address = mkOption {
type = nullOr str;
description =
"The MAC address of a given host, if desired for IP reservation.";
default = null;
};
description = mkOption {
type = nullOr str;
description = "Description of the host.";
default = null;
};
sshfp-records = mkOption {
type = listOf str;
description = "List of SSHFP records for this host.";
default = [ ];
};
};
};
zoneOpts = { name, ... }:
let zoneName = name;
in {
options = with types; {
enable = mkOption {
type = bool;
description = "Enable ${zone-name} zone on the local nameserver.";
default = true;
};
default-host = mkOption {
type = nullOr (submodule networkHostOpts);
description =
"Host which will respond to requests for the base domain.";
default = null;
};
domain = mkOption {
type = str;
description = "Domain which this zone serves.";
default = zoneName;
};
2023-11-13 10:59:41 -08:00
reverse-zones = mkOption {
type = listOf str;
description = "List of networks for which to generate reverse zones.";
default = [ ];
};
mail = {
smtp-servers = mkOption {
type = listOf str;
description = "List of SMTP server for this zone.";
default = [ ];
};
imap-servers = mkOption {
type = listOf str;
description = "List of IMAP server for this zone.";
default = [ ];
};
};
2023-10-14 16:15:26 -07:00
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."${zoneName}";
};
};
};
in {
options.fudo.services.authoritative-dns = with types; {
enable = mkEnableOption "Enable Authoritative DNS server.";
2023-11-13 10:59:41 -08:00
enable-notifications =
mkEnableOption "Enable notifications to secondary servers.";
2023-10-14 16:15:26 -07:00
zones = mkOption {
type = attrsOf (submodule zoneOpts);
description = "Map of served zone to extra zone details.";
default = { };
};
2023-11-13 10:59:41 -08:00
ip-host-map = mkOption {
type = attrsOf str;
description =
"Map of ip address to authoritative hostname, for reverse zones.";
default = { };
};
2023-10-14 16:15:26 -07:00
nameservers = {
primary = mkOption {
type = str;
description = "Hostname of the primary nameserver.";
};
secondary = mkOption {
type = listOf str;
description = "List of internal secondary nameservers.";
default = [ ];
};
2023-11-13 10:59:41 -08:00
external = mkOption {
type = listOf (submodule networkHostOpts);
description = "List of external secondary nameserver attributes.";
default = [ ];
};
2023-10-14 16:15:26 -07:00
};
state-directory = mkOption {
type = str;
description =
"Directory at which to store DNS state data, including keys.";
};
};
config = {
fudo = {
secrets.host-secrets."${hostname}" = mkIf cfg.enable (mapAttrs'
(zone: zoneCfg:
nameValuePair (zoneKeySecret zone) {
source-file = zoneCfg.ksk.private-key;
target-file = "/run/nsd/${baseNameOf zoneCfg.ksk.private-key}";
user = config.fudo.nsd.user;
}) (filterAttrs (_: zoneCfg: zoneCfg.ksk != null) cfg.zones));
zones = mapAttrs (zone-name: zoneCfg:
let
domainName = zoneCfg.domain;
domain = config.fudo.domains."${domainName}";
makeSrvRecord = port: host: { inherit port host; };
isPrimaryNameserver = hostname == cfg.nameservers.primary;
internalNameserverHostnames = [ cfg.nameservers.primary ]
++ cfg.nameservers.secondary;
getNsDeets = hostname:
let hostDomain = config.fudo.hosts."${hostname}".domain;
in {
ipv4-address = pkgs.lib.network.host-ipv4 config hostname;
ipv6-address = pkgs.lib.network.host-ipv6 config hostname;
description =
"${domainName} nameserver ${hostname}.${hostDomain}.";
};
2023-11-13 10:59:41 -08:00
nameserverDeets = (map getNsDeets internalNameserverHostnames)
++ cfg.nameservers.external;
2023-10-14 16:15:26 -07:00
allNameservers = listToAttrs
(imap1 (i: nsOpts: nameValuePair "ns${toString i}" nsOpts)
nameserverDeets);
dnsSrvRecords = let
nameserverSrvRecords = mapAttrsToList (hostname: hostOpts:
let
targetHost = if (hasAuthHostname hostname hostOpts) then
"${hostOpts.authoritative-hostname}"
else
"${hostname}.${domainName}";
in makeSrvRecord 53 targetHost) allNameservers;
in {
tcp.domain = nameserverSrvRecords;
udp.domain = nameserverSrvRecords;
};
2023-11-13 10:59:41 -08:00
mailSrvRecords = let
mkRecords = port: servers:
map (host: { inherit host port; }) servers;
2023-10-14 16:15:26 -07:00
in {
tcp = {
2023-11-13 10:59:41 -08:00
smtp = mkRecords 25 zoneCfg.mail.smtp-servers;
submission = mkRecords 587 zoneCfg.mail.smtp-servers;
submissions = mkRecords 465 zoneCfg.mail.smtp-servers;
imap = mkRecords 143 zoneCfg.mail.imap-servers;
imaps = mkRecords 993 zoneCfg.mail.imap-servers;
2023-10-14 16:15:26 -07:00
};
udp = {
2023-11-13 10:59:41 -08:00
smtp = mkRecords 25 zoneCfg.mail.smtp-servers;
submission = mkRecords 587 zoneCfg.mail.smtp-servers;
2023-10-14 16:15:26 -07:00
};
2023-11-13 10:59:41 -08:00
};
2023-10-14 16:15:26 -07:00
in {
gssapi-realm = mkIf (!isNull domain.gssapi-realm) domain.gssapi-realm;
2023-11-13 10:59:41 -08:00
hosts = allNameservers;
2023-10-14 16:15:26 -07:00
2023-11-13 10:59:41 -08:00
mx = zoneCfg.mail.smtp-servers;
2023-10-14 16:15:26 -07:00
2023-11-13 10:59:41 -08:00
dmarc-report-address = "dmarc-report@${domainName}";
2023-10-14 16:15:26 -07:00
2023-11-13 10:59:41 -08:00
default-host = zoneCfg.default-host;
2023-10-14 16:15:26 -07:00
2023-11-13 10:59:41 -08:00
nameservers = map (hostname: "${hostname}.${domainName}.")
(attrNames allNameservers);
2023-10-14 16:15:26 -07:00
srv-records = dnsSrvRecords // mailSrvRecords;
verbatim-dns-records = mkIf (zoneCfg.ksk != null) [
(readFile zoneCfg.ksk.public-key)
(readFile zoneCfg.ksk.ds)
];
}) cfg.zones;
};
services = {
authoritative-dns = {
enable = cfg.enable;
identity = "${hostname}.${domainName}";
listen-ips =
optionals cfg.enable (pkgs.lib.network.host-ips config hostname);
state-directory = cfg.state-directory;
timestamp = toString config.instance.build-timestamp;
2023-11-13 10:59:41 -08:00
ip-host-map = cfg.ip-host-map;
2023-10-14 16:15:26 -07:00
domains = mapAttrs' (zoneName: zoneCfg:
nameValuePair zoneCfg.domain {
ksk.key-file = mkIf (hasAttr (zoneKeySecret zoneName) hostSecrets)
hostSecrets."${zoneKeySecret zoneName}".target-file;
2023-11-13 10:59:41 -08:00
reverse-zones = zoneCfg.reverse-zones;
notify = mkIf cfg.enable-notifications {
ipv4 = concatMap
(ns: optional (ns.ipv4-address != null) ns.ipv4-address)
cfg.nameservers.external;
ipv6 = concatMap
(ns: optional (ns.ipv6-address != null) ns.ipv6-address)
cfg.nameservers.external;
2023-10-14 16:15:26 -07:00
};
2023-11-13 10:59:41 -08:00
zone = config.fudo.zones."${zoneName}";
2023-10-14 16:15:26 -07:00
}) cfg.zones;
};
};
};
}