authoritative-dns/zone-to-zonefile.nix

167 lines
4.8 KiB
Nix
Raw Permalink Normal View History

{ lib, ... }:
2023-10-04 09:31:17 -07:00
{ timestamp, domain, zone, ... }:
with lib;
2023-10-04 09:31:17 -07:00
let
removeBlankLines = str:
2023-10-07 13:29:06 -07:00
concatStringsSep "\n\n" (filter isString (builtins.split ''
+'' str));
2023-10-04 09:31:17 -07:00
joinLines = concatStringsSep "\n";
nSpaces = n: concatStrings (genList (_: " ") n);
padToLength = strlen: str:
let spaces = nSpaces (strlen - (stringLength str));
in str + spaces;
maxInt = foldr (a: b: if (a < b) then b else a) 0;
2023-10-06 10:37:44 -07:00
recordMatcher = builtins.match "^([^;].*) IN ([A-Z][A-Z0-9]*) (.+)$";
2023-10-04 09:31:17 -07:00
isRecord = str: (recordMatcher str) != null;
makeZoneFormatter = zonedata:
let
lines = splitString "\n" zonedata;
records = filter isRecord lines;
2023-10-06 09:41:44 -07:00
splitRecords = map recordMatcher records;
2023-10-04 09:31:17 -07:00
indexStrlen = i: record: stringLength (elemAt record i);
recordIndexMaxlen = i: maxInt (map (indexStrlen i) splitRecords);
in recordFormatter (recordIndexMaxlen 0) (recordIndexMaxlen 1);
recordFormatter = nameMax: typeMax:
let
namePadder = padToLength nameMax;
typePadder = padToLength typeMax;
in recordLine:
let recordParts = recordMatcher recordLine;
in if (recordParts == null) then
recordLine
else
(let
name = elemAt recordParts 0;
type = elemAt recordParts 1;
data = elemAt recordParts 2;
in "${namePadder name} IN ${typePadder type} ${data}");
formatZone = zonedata:
let
formatter = makeZoneFormatter zonedata;
lines = splitString "\n" zonedata;
in concatStringsSep "\n" (map formatter lines);
2023-10-06 10:35:42 -07:00
isNotNull = o: !isNull o;
2023-10-04 09:31:17 -07:00
hostToFqdn = host:
2023-10-07 10:00:18 -07:00
let hostChars = "[a-zA-Z0-9_-]";
2023-10-07 22:31:37 -07:00
in if isNotNull (builtins.match "^${hostChars}+$" host) then
2023-10-07 10:00:18 -07:00
"${host}.${domain}."
2023-11-18 11:49:29 -08:00
else if isNotNull
(builtins.match "(${hostChars}+\\.)+${hostChars}+\\.$" host) then
2023-10-04 09:31:17 -07:00
host
2023-10-07 10:00:18 -07:00
else if isNotNull
(builtins.match "(${hostChars}+\\.)+${hostChars}+$" host) then
2023-10-04 09:31:17 -07:00
"${host}."
else
abort "unrecognized hostname: ${host}";
2023-10-07 21:43:21 -07:00
makeSrvRecords = protocol: service: records:
joinLines (map (record:
2023-10-07 20:34:26 -07:00
let fqdn = hostToFqdn record.host;
in "_${service}._${protocol} IN SRV ${toString record.priority} ${
2023-10-04 09:31:17 -07:00
toString record.weight
2023-10-15 20:49:20 -07:00
} ${toString record.port} ${fqdn}") records);
2023-10-04 09:31:17 -07:00
makeSrvProtocolRecords = protocol: serviceRecords:
joinLines (mapAttrsToList (makeSrvRecords protocol) serviceRecords);
2023-11-09 13:51:11 -08:00
makeMetricRecords = metricType: records:
joinLines (map (record:
"${metricType}._metrics._tcp IN SRV ${toString record.priority} ${
toString record.weight
} ${toString record.port} ${hostToFqdn record.host}") records);
2023-10-04 09:31:17 -07:00
2023-11-02 10:07:51 -07:00
makeHostRecords = hostname:
{ ipv4-address, ipv6-address, sshfp-records, description, ... }:
2023-10-04 09:31:17 -07:00
let
2023-11-02 10:07:51 -07:00
sshfpRecords = map (sshfp: "${hostname} IN SSHFP ${sshfp}") sshfp-records;
2023-11-02 10:09:57 -07:00
aRecord =
optional (ipv4-address != null) "${hostname} IN A ${ipv4-address}";
aaaaRecord =
optional (ipv6-address != null) "${hostname} IN AAAA ${ipv6-address}";
descriptionRecord =
optional (description != null) ''${hostname} IN TXT "${description}"'';
in joinLines (aRecord ++ aaaaRecord ++ sshfpRecords ++ descriptionRecord);
2023-10-04 09:31:17 -07:00
cnameRecord = alias: host: "${alias} IN CNAME ${host}";
2023-10-06 10:30:22 -07:00
dmarcRecord = dmarcEmail:
2023-10-04 09:31:17 -07:00
optionalString (dmarcEmail != null) ''
2023-10-06 10:31:59 -07:00
_dmarc IN TXT "v=DMARC1;p=quarantine;sp=quarantine;rua=mailto:${dmarcEmail};"'';
2023-10-04 09:31:17 -07:00
2023-11-18 12:27:40 -08:00
mxRecords = map (mx: "@ IN MX 10 ${hostToFqdn mx}");
2023-10-04 09:31:17 -07:00
2023-11-02 09:15:38 -07:00
nsRecords = map (ns-host: "@ IN NS ${ns-host}");
2023-10-04 09:31:17 -07:00
flatmapAttrsToList = f: attrs:
foldr (a: b: a ++ b) [ ] (mapAttrsToList f attrs);
domainRecords = domain: zone:
let
defaultHostRecords = optionalString (zone.default-host != null)
2023-11-02 09:15:38 -07:00
(makeHostRecords "@" zone.default-host);
2023-10-04 09:31:17 -07:00
kerberosRecord = optionalString (zone.gssapi-realm != null)
''_kerberos IN TXT "${zone.gssapi-realm}"'';
subdomainRecords = joinLines (mapAttrsToList
2023-10-06 12:44:55 -07:00
(subdom: subdomCfg: domainRecords "${subdom}.${domain}" subdomCfg)
zone.subdomains);
2023-10-04 09:31:17 -07:00
in ''
2023-10-06 13:19:23 -07:00
$ORIGIN ${domain}.
2023-10-04 09:31:17 -07:00
$TTL ${zone.default-ttl}
${defaultHostRecords}
2023-10-04 09:31:17 -07:00
${joinLines (mxRecords zone.mx)}
${dmarcRecord zone.dmarc-report-address}
${kerberosRecord}
${joinLines (nsRecords zone.nameservers)}
${joinLines (mapAttrsToList makeSrvProtocolRecords zone.srv-records)}
${joinLines (mapAttrsToList makeMetricRecords zone.metric-records)}
$TTL ${zone.host-record-ttl}
2023-10-06 10:39:28 -07:00
${joinLines (mapAttrsToList makeHostRecords zone.hosts)}
2023-10-04 09:31:17 -07:00
${joinLines (mapAttrsToList cnameRecord zone.aliases)}
${joinLines zone.verbatim-dns-records}
${subdomainRecords}
'';
2023-10-06 09:39:43 -07:00
in removeBlankLines (formatZone ''
2023-10-04 09:31:17 -07:00
$ORIGIN ${domain}.
$TTL ${zone.default-ttl}
@ IN SOA ns1.${domain}. hostmaster.${domain}. (
${toString timestamp}
30m
2m
3w
5m)
${domainRecords domain zone}
'')