nixos-config/config/service/dns.nix

218 lines
6.6 KiB
Nix

{ config, lib, pkgs, ... } @ toplevel:
with lib;
let
hostname = config.instance.hostname;
cfg = config.fudo.services.dns;
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;
};
};
};
pthru = obj:
builtins.trace "TRACE: ${ obj }" obj;
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 = {
zones = 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;
};
# TODO: move this to a mail service
mail-srv-records = optionalAttrs (domain.primary-mailserver != null) {
tcp = let
mailserver-domain = config.fudo.hosts.${domain.primary-mailserver}.domain;
fqdn = "mail.${mailserver-domain}";
in {
smtp = [(make-srv-record 25 fqdn)];
submission = [(make-srv-record 587 fqdn)];
imap = [(make-srv-record 143 fqdn)];
imaps = [(make-srv-record 993 fqdn)];
pop3 = [(make-srv-record 110 fqdn)];
pop3s = [(make-srv-record 995 fqdn)];
};
};
in {
gssapi-realm = domain.gssapi-realm;
hosts = nameserver-hosts // {
mail = mkIf (domain.primary-nameserver != null) (let
mailserver-deets = host: let
host-domain = config.fudo.hosts.${host}.domain;
in get-host-deets "Primary ${domain-name} mailserver ${host}.${host-domain}." host;
in mailserver-deets domain.primary-nameserver);
};
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;
}) cfg.zones;
dns = let
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;
in {
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 = true;
zone-definition = config.fudo.zones.${zone-name};
}) cfg.zones;
};
};
}