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;
|
|
|
|
|
2023-12-04 17:10:57 -08:00
|
|
|
inherit (pkgs.lib)
|
|
|
|
getHostIps getSiteGatewayV4 getSiteV4PrefixLength getSiteV6PrefixLength;
|
|
|
|
|
2023-10-14 16:15:26 -07:00
|
|
|
hostSecrets = config.fudo.secrets.host-secrets."${hostname}";
|
|
|
|
|
|
|
|
domainName = config.instance.local-domain;
|
|
|
|
|
2023-12-04 17:10:57 -08:00
|
|
|
primaryZone = config.fudo.domains."${domainName}".zone;
|
|
|
|
|
|
|
|
siteName = config.instance.local-site;
|
|
|
|
|
2023-10-14 16:15:26 -07:00
|
|
|
zoneKeySecret = zone: "${zone}-ksk";
|
|
|
|
|
2023-12-04 17:10:57 -08:00
|
|
|
containerModule = { pkgs, config, ... }: {
|
|
|
|
config = mkIf (cfg.enable && !isNull cfg.container) {
|
|
|
|
containers.nameserver = let
|
|
|
|
securedZones =
|
|
|
|
filterAttrs (_: zoneOpts: !isNull zoneOpts.ksk) cfg.zones;
|
|
|
|
in {
|
|
|
|
autoStart = true;
|
|
|
|
additionalCapabilities = [ "CAP_NET_ADMIN" ];
|
|
|
|
macvlans = [ cfg.container.interface ];
|
|
|
|
bindMounts = {
|
|
|
|
"/var/lib/nsd" = {
|
|
|
|
hostPath = cfg.state-directory;
|
|
|
|
isReadOnly = false;
|
|
|
|
};
|
|
|
|
} // (mapAttrs' (zoneName: _:
|
2024-02-10 16:53:55 -08:00
|
|
|
nameValuePair "/run/nsd/keys/${zoneName}" {
|
|
|
|
hostPath =
|
|
|
|
dirOf hostSecrets."${zoneKeySecret zoneName}".target-file;
|
2023-12-04 17:10:57 -08:00
|
|
|
}) securedZones);
|
|
|
|
config = let
|
|
|
|
nameserverHost = cfg.container.hostname;
|
|
|
|
nameserverDeets =
|
|
|
|
config.fudo.zones."${primaryZone}".hosts."${nameserverHost}";
|
|
|
|
in {
|
|
|
|
imports = [ pkgs.moduleRegistry.authoritativeDns ];
|
|
|
|
nixpkgs.pkgs = pkgs;
|
|
|
|
networking = {
|
2024-01-16 19:08:38 -08:00
|
|
|
enableIPv6 = false;
|
2023-12-04 17:10:57 -08:00
|
|
|
defaultGateway = {
|
|
|
|
address = getSiteGatewayV4 siteName;
|
|
|
|
interface = "mv-${cfg.container.interface}";
|
|
|
|
};
|
|
|
|
firewall = {
|
|
|
|
enable = true;
|
|
|
|
allowedTCPPorts = [ 53 ];
|
|
|
|
allowedUDPPorts = [ 53 ];
|
|
|
|
};
|
|
|
|
interfaces."mv-${cfg.container.interface}" = {
|
|
|
|
ipv4.addresses = optional (nameserverDeets.ipv4-address != null) {
|
2024-01-16 19:08:38 -08:00
|
|
|
address = nameserverDeets.ipv4-address;
|
2023-12-04 17:10:57 -08:00
|
|
|
prefixLength = getSiteV4PrefixLength siteName;
|
|
|
|
};
|
|
|
|
ipv6.addresses = optional (nameserverDeets.ipv6-address != null) {
|
|
|
|
address = nameserverDeets.ipv6-address;
|
|
|
|
prefixLength = getSiteV6PrefixLength siteName;
|
|
|
|
};
|
|
|
|
};
|
|
|
|
};
|
|
|
|
services.authoritative-dns = {
|
|
|
|
enable = true;
|
2023-10-14 16:15:26 -07:00
|
|
|
|
2023-12-04 17:10:57 -08:00
|
|
|
identity = "${nameserverHost}.${primaryZone}";
|
2023-10-14 16:15:26 -07:00
|
|
|
|
2023-12-04 17:10:57 -08:00
|
|
|
listen-ips = getHostIps nameserverHost;
|
2023-10-14 16:15:26 -07:00
|
|
|
|
2023-12-04 17:10:57 -08:00
|
|
|
state-directory = "/var/lib/nsd";
|
2023-10-14 16:15:26 -07:00
|
|
|
|
2023-12-04 17:10:57 -08:00
|
|
|
timestamp = toString config.instance.build-timestamp;
|
|
|
|
|
|
|
|
ip-host-map = cfg.ip-host-map;
|
|
|
|
|
|
|
|
domains = mapAttrs' (zoneName: zoneCfg:
|
|
|
|
nameValuePair zoneCfg.domain {
|
2024-02-10 16:53:55 -08:00
|
|
|
ksk.key-file = "/run/nsd/keys/${zoneName}/${
|
|
|
|
baseNameOf
|
|
|
|
hostSecrets."${zoneKeySecret zoneName}".target-file
|
|
|
|
}";
|
2023-12-04 17:10:57 -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;
|
|
|
|
};
|
|
|
|
zone = config.fudo.zones."${zoneName}";
|
|
|
|
}) cfg.zones;
|
|
|
|
};
|
|
|
|
};
|
2023-10-14 16:15:26 -07:00
|
|
|
};
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
2023-12-04 17:10:57 -08:00
|
|
|
hostModule = { config, ... }: {
|
|
|
|
config.services.authoritative-dns =
|
|
|
|
mkIf (cfg.enable && isNull cfg.container) {
|
|
|
|
enable = true;
|
|
|
|
|
|
|
|
identity = "${hostname}.${primaryZone}";
|
|
|
|
|
|
|
|
listen-ips = getHostIps hostname;
|
|
|
|
|
2024-03-23 14:23:43 -07:00
|
|
|
state-directory = cfg.state-directory;
|
2023-12-04 17:10:57 -08:00
|
|
|
|
|
|
|
timestamp = toString config.instance.build-timestamp;
|
|
|
|
|
|
|
|
ip-host-map = cfg.ip-host-map;
|
|
|
|
|
|
|
|
domains = mapAttrs' (zoneName: zoneCfg:
|
|
|
|
nameValuePair zoneCfg.domain {
|
|
|
|
ksk.key-file = mkIf (hasAttr (zoneKeySecret zoneName) hostSecrets)
|
|
|
|
hostSecrets."${zoneKeySecret zoneName}".target-file;
|
|
|
|
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;
|
|
|
|
};
|
|
|
|
zone = config.fudo.zones."${zoneName}";
|
|
|
|
}) cfg.zones;
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
2023-10-14 16:15:26 -07:00
|
|
|
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 {
|
2023-12-04 17:10:57 -08:00
|
|
|
type = nullOr (submodule pkgs.lib.fudo-types.networkHost);
|
2023-10-14 16:15:26 -07:00
|
|
|
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-12-04 17:10:57 -08:00
|
|
|
container = mkOption {
|
|
|
|
type = nullOr (submodule {
|
|
|
|
options = {
|
|
|
|
interface = mkOption {
|
|
|
|
type = str;
|
|
|
|
description = "Interface on which to listen for DNS traffic.";
|
|
|
|
};
|
|
|
|
|
|
|
|
hostname = mkOption {
|
|
|
|
type = str;
|
|
|
|
description = ''
|
|
|
|
Hostname (in the zone) of the container nameserver.
|
|
|
|
|
|
|
|
The associated IP(s) will be assigned to the container,
|
|
|
|
and must be accessible.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
};
|
|
|
|
});
|
2024-01-16 19:08:38 -08:00
|
|
|
default = null;
|
2023-12-04 17:10:57 -08:00
|
|
|
};
|
|
|
|
|
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 {
|
2023-12-04 17:10:57 -08:00
|
|
|
type = listOf (submodule pkgs.lib.fudo-types.networkHost);
|
2023-11-13 10:59:41 -08:00
|
|
|
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.";
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
2023-12-04 17:10:57 -08:00
|
|
|
imports = [ hostModule containerModule ];
|
|
|
|
|
|
|
|
config = mkIf cfg.enable {
|
|
|
|
fileSystems."/var/lib/nsd" = mkIf (isNull cfg.container) {
|
|
|
|
device = cfg.state-directory;
|
|
|
|
options = [ "bind" ];
|
|
|
|
};
|
|
|
|
|
2023-10-14 16:15:26 -07:00
|
|
|
fudo = {
|
2024-02-10 16:53:55 -08:00
|
|
|
secrets.host-secrets."${hostname}" = concatMapAttrs (zone: zoneCfg: {
|
|
|
|
"${zoneKeySecret zone}" = {
|
2023-12-04 17:10:57 -08:00
|
|
|
source-file = zoneCfg.ksk.private-key;
|
2024-02-10 16:53:55 -08:00
|
|
|
target-file =
|
|
|
|
"/run/nsd/${zone}/${baseNameOf zoneCfg.ksk.private-key}";
|
|
|
|
};
|
|
|
|
"${zone}-ds" = {
|
|
|
|
source-file = zoneCfg.ksk.ds;
|
|
|
|
target-file = "/run/nsd/${zone}/${baseNameOf zoneCfg.ksk.ds}";
|
|
|
|
};
|
|
|
|
"${zone}-pubkey" = {
|
|
|
|
source-file = zoneCfg.ksk.public-key;
|
|
|
|
target-file = "/run/nsd/${zone}/${baseNameOf zoneCfg.ksk.public-key}";
|
|
|
|
};
|
|
|
|
}) (filterAttrs (_: zoneCfg: zoneCfg.ksk != null) cfg.zones);
|
2023-10-14 16:15:26 -07:00
|
|
|
|
|
|
|
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;
|
|
|
|
};
|
|
|
|
};
|
|
|
|
}
|