238 lines
7.5 KiB
Nix
238 lines
7.5 KiB
Nix
{ 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;
|
|
};
|
|
};
|
|
}
|