diff --git a/nix/nixos-module/container/dns.nix b/nix/nixos-module/container/dns.nix index 776d991..515627c 100644 --- a/nix/nixos-module/container/dns.nix +++ b/nix/nixos-module/container/dns.nix @@ -1,180 +1,182 @@ { hostName, config, lib, pkgs, self, ... }: -lib.mkIf config.site.hosts.${hostName}.services.dns.enable { - services.bind = +let + fqdn = "${hostName}.serv.zentralwerk.org"; + # public servers (slaves) + publicNS = [ "ns.c3d2.de" "ns.spaceboyz.net" ]; +in +{ + options = + with lib; let - fqdn = "${hostName}.serv.zentralwerk.org"; - # public servers (slaves) - publicNS = [ "ns.c3d2.de" "ns.spaceboyz.net" ]; - # allowed for zone-transfer - slaves = [ - # ns.c3d2.de - "217.197.84.53" "2001:67c:1400:2240::a" - # ns.spaceboyz.net - "172.22.24.4" "2a01:4f9:4b:39ec::4" - ]; - # ip6.arpa aggregation size in CIDR bits - reverseZone6Size = 60; - - serial = - let - timestamp = toString self.lastModified; - datePkg = pkgs.runCommandLocal "date-${timestamp}" {} '' - date -d @${timestamp} +%Y%m%d%H > $out - ''; - in - toString (import datePkg); - - staticZone = { name, ns, records }: { - inherit name; - master = true; - file = builtins.toFile "${name}.zone" '' - $ORIGIN ${name}. - $TTL 1h - - @ IN SOA ${fqdn}. astro.spaceboyz.net. ( - ${serial} ; serial - 1h ; refresh - 1m ; retry - 2h ; expire - 1m ; minimum - ) - ${lib.concatMapStrings (ns: " IN NS ${ns}.\n") ns} - - ${lib.concatMapStrings ({ name, type, data }: - "${name} IN ${type} ${data}\n" - ) records} - ''; + recordOpts = { + name = mkOption { + description = "DNS label"; + type = types.str; + }; + type = mkOption { + type = types.enum [ "A" "AAAA" "PTR" ]; + }; + data = mkOption { + type = types.str; + }; }; - hosts4Records = hosts4: - builtins.attrValues ( - builtins.mapAttrs (name: addr: { - inherit name; - type = "A"; - data = addr; - }) hosts4 - ); - hosts6Records = hosts6: - builtins.attrValues ( - builtins.mapAttrs (name: addr: { - inherit name; - type = "AAAA"; - data = addr; - }) hosts6 - ); - - # generate zones only for nets with hosts - namedNets = lib.filterAttrs (_: { hosts4, hosts6, dynamicDomain, ... }: - (hosts4 != [] && hosts6 != []) || - dynamicDomain - ) config.site.net; - - # converts an IPv4 address to its reverse DNS form - ipv4ToReverse = ipv4: - builtins.concatStringsSep "." ( - lib.reverseList ( - builtins.filter builtins.isString ( - builtins.split "\\." ipv4 - ) - ) - ) + ".in-addr.arpa"; - - # `{ "1,0.0.127.in-addr.arpa" = "lo.core.zentralwerk.dn42"; }` - reverseHosts4 = builtins.foldl' (result: { hosts4, domainName, ... }: - builtins.foldl' (result: host: result // { - "${ipv4ToReverse hosts4.${host}}" = "${host}.${domainName}"; - }) result (builtins.attrNames hosts4) - ) {} (builtins.attrValues namedNets); - - # `[ "0.0.127.in-addr.arpa" ]` - reverseZones4 = builtins.attrNames ( - builtins.foldl' (result: rname: - let - zone = builtins.head ( - builtins.match "[[:digit:]]+\\.(.+)" rname - ); - in result // { - "${zone}" = true; - } - ) {} (builtins.attrNames reverseHosts4) - ); - - # turns `::` into `0000:0000:0000:0000:0000:0000:0000:0000` - expandIpv6 = ipv6: - if lib.hasPrefix "::" ipv6 - then expandIpv6 "0${ipv6}" - - else if lib.hasSuffix "::" ipv6 - then expandIpv6 "${ipv6}0" - - else let - words = builtins.filter builtins.isString ( - builtins.split ":" ipv6 - ); - fillWordCount = 8 - builtins.length words; - fillWords = n: - if n >= 0 - then [ "0000" ] ++ fillWords (n - 1) - else []; - words' = builtins.concatMap (word: - if word == "" - then fillWords fillWordCount - else [ word ] - ) words; - leftPad = padding: target: s: - if builtins.stringLength s < target - then leftPad padding target "${padding}${s}" - else s; - words'' = map (leftPad "0" 4) words'; - in - builtins.concatStringsSep ":" words''; - - # turns `::1` into `1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa` - ipv6ToReverse = ipv6: - builtins.concatStringsSep "." ( - lib.reverseList ( - lib.stringToCharacters ( - builtins.replaceStrings [":"] [""] (expandIpv6 ipv6) - ) - ) - ) + ".ip6.arpa"; - - # `{ dn42 = { "...ip6.arpa" = "lo.core.zentralwerk.dn42"; }; }` - reverseHosts6 = builtins.foldl' (result: net: lib.recursiveUpdate result ( - builtins.mapAttrs (ctx: hosts: - builtins.foldl' (result: host: - let - domain = - if ctx == "dn42" - then namedNets.${net}.domainName - else if builtins.match "up.*" ctx != null - then "${net}.zentralwerk.org" - else throw "Invalid IPv6 context: ${ctx}"; - in - lib.recursiveUpdate result { - "${ipv6ToReverse hosts.${host}}" = "${host}.${domain}"; - } - ) {} (builtins.attrNames hosts) - ) namedNets.${net}.hosts6 - )) {} (builtins.attrNames namedNets); - - # `{ dn42 = [ "....ip6.arpa" ]; }` - reverseZones6 = builtins.mapAttrs (ctx: reverseHosts6ctx: - builtins.attrNames ( - builtins.foldl' (result: rname: result // { - "${builtins.substring ((128 - reverseZone6Size) / 2) (72 - ((128 - reverseZone6Size) / 2)) rname}" = true; - }) {} (builtins.attrNames reverseHosts6ctx) - ) - ) reverseHosts6; + zoneOpts = { + name = mkOption { + description = "DNS FQDN w/o trailing dot"; + type = types.str; + }; + ns = mkOption { + type = with types; listOf str; + }; + records = mkOption { + type = with types; listOf (submodule { + options = recordOpts; + }); + }; + }; in { - enable = true; - zones = [ (staticZone { + site.dns.localZones = mkOption { + type = with types; listOf (submodule { + options = zoneOpts; + }); + }; + }; + + config = { + site.dns.localZones = + let + # ip6.arpa aggregation size in CIDR bits + reverseZone6Size = 60; + + hosts4Records = hosts4: + builtins.attrValues ( + builtins.mapAttrs (name: addr: { + inherit name; + type = "A"; + data = addr; + }) hosts4 + ); + hosts6Records = hosts6: + builtins.attrValues ( + builtins.mapAttrs (name: addr: { + inherit name; + type = "AAAA"; + data = addr; + }) hosts6 + ); + + # generate zones only for nets with hosts + namedNets = lib.filterAttrs (_: { hosts4, hosts6, dynamicDomain, ... }: + (hosts4 != [] && hosts6 != []) || + dynamicDomain + ) config.site.net; + + # converts an IPv4 address to its reverse DNS form + ipv4ToReverse = ipv4: + builtins.concatStringsSep "." ( + lib.reverseList ( + builtins.filter builtins.isString ( + builtins.split "\\." ipv4 + ) + ) + ) + ".in-addr.arpa"; + + # `{ "1,0.0.127.in-addr.arpa" = "lo.core.zentralwerk.dn42"; }` + reverseHosts4 = builtins.foldl' (result: { hosts4, domainName, ... }: + builtins.foldl' (result: host: result // { + "${ipv4ToReverse hosts4.${host}}" = "${host}.${domainName}"; + }) result (builtins.attrNames hosts4) + ) {} (builtins.attrValues namedNets); + + # `[ "0.0.127.in-addr.arpa" ]` + reverseZones4 = builtins.attrNames ( + builtins.foldl' (result: rname: + let + zone = builtins.head ( + builtins.match "[[:digit:]]+\\.(.+)" rname + ); + in result // { + "${zone}" = true; + } + ) {} (builtins.attrNames reverseHosts4) + ); + + # turns `::` into `0000:0000:0000:0000:0000:0000:0000:0000` + expandIpv6 = ipv6: + if lib.hasPrefix "::" ipv6 + then expandIpv6 "0${ipv6}" + + else if lib.hasSuffix "::" ipv6 + then expandIpv6 "${ipv6}0" + + else let + words = builtins.filter builtins.isString ( + builtins.split ":" ipv6 + ); + fillWordCount = 8 - builtins.length words; + fillWords = n: + if n >= 0 + then [ "0000" ] ++ fillWords (n - 1) + else []; + words' = builtins.concatMap (word: + if word == "" + then fillWords fillWordCount + else [ word ] + ) words; + leftPad = padding: target: s: + if builtins.stringLength s < target + then leftPad padding target "${padding}${s}" + else s; + words'' = map (leftPad "0" 4) words'; + in + builtins.concatStringsSep ":" words''; + + # turns `::1` into `1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa` + ipv6ToReverse = ipv6: + builtins.concatStringsSep "." ( + lib.reverseList ( + lib.stringToCharacters ( + builtins.replaceStrings [":"] [""] (expandIpv6 ipv6) + ) + ) + ) + ".ip6.arpa"; + + # `{ dn42 = { "...ip6.arpa" = "lo.core.zentralwerk.dn42"; }; }` + reverseHosts6 = builtins.foldl' (result: net: lib.recursiveUpdate result ( + builtins.mapAttrs (ctx: hosts: + builtins.foldl' (result: host: + let + domain = + if ctx == "dn42" + then namedNets.${net}.domainName + else if builtins.match "up.*" ctx != null + then "${net}.zentralwerk.org" + else throw "Invalid IPv6 context: ${ctx}"; + in + lib.recursiveUpdate result { + "${ipv6ToReverse hosts.${host}}" = "${host}.${domain}"; + } + ) {} (builtins.attrNames hosts) + ) namedNets.${net}.hosts6 + )) {} (builtins.attrNames namedNets); + + # `{ dn42 = [ "....ip6.arpa" ]; }` + reverseZones6 = builtins.mapAttrs (ctx: reverseHosts6ctx: + builtins.attrNames ( + builtins.foldl' (result: rname: result // { + "${builtins.substring ((128 - reverseZone6Size) / 2) (72 - ((128 - reverseZone6Size) / 2)) rname}" = true; + }) {} (builtins.attrNames reverseHosts6ctx) + ) + ) reverseHosts6; + + in [ { name = "zentralwerk.org"; ns = publicNS; records = []; - }) (staticZone { + } { name = "zentralwerk.dn42"; ns = [ fqdn ]; records = [ { @@ -182,7 +184,7 @@ lib.mkIf config.site.hosts.${hostName}.services.dns.enable { type = "A"; data = config.site.net.serv.hosts4.ipa; } ]; - }) (staticZone { + } { name = "dyn.zentralwerk.org"; ns = publicNS; # TODO: implement dyndns @@ -195,28 +197,28 @@ lib.mkIf config.site.hosts.${hostName}.services.dns.enable { type = "A"; data = "24.134.252.105"; } ]; - }) ] ++ builtins.concatLists ( + } ] ++ builtins.concatLists ( builtins.attrValues ( builtins.mapAttrs (net: { dynamicDomain, hosts4, hosts6, ... }: [ (if dynamicDomain then throw "TODO" - else staticZone { + else { name = "${net}.zentralwerk.dn42"; ns = [ fqdn ]; records = hosts4Records hosts4 ++ lib.optionals (hosts6 ? dn42) (hosts6Records hosts6.dn42); }) - (staticZone { + { name = "${net}.zentralwerk.org"; ns = publicNS; records = lib.optionals (hosts6 ? up1) (hosts6Records hosts6.up1) ++ lib.optionals (hosts6 ? up2) (hosts6Records hosts6.up2); - }) + } ]) namedNets ) - ) ++ map (zone: staticZone { + ) ++ map (zone: { name = zone; ns = [ fqdn ]; records = @@ -232,7 +234,7 @@ lib.mkIf config.site.hosts.${hostName}.services.dns.enable { ); }) reverseZones4 ++ builtins.concatMap (ctx: - map (zone: staticZone { + map (zone: { name = zone; ns = if ctx == "dn42" @@ -251,7 +253,51 @@ lib.mkIf config.site.hosts.${hostName}.services.dns.enable { ); }) reverseZones6.${ctx} ) (builtins.attrNames reverseZones6); - }; - # TODO: dyn + services.bind = lib.mkIf config.site.hosts.${hostName}.services.dns.enable ( + let + serial = + let + timestamp = toString self.lastModified; + datePkg = pkgs.runCommandLocal "date-${timestamp}" {} '' + date -d @${timestamp} +%Y%m%d%H > $out + ''; + in + toString (import datePkg); + + generateZone = { name, ns, records }: { + inherit name; + master = true; + # allowed for zone-transfer + slaves = [ + # ns.c3d2.de + "217.197.84.53" "2001:67c:1400:2240::a" + # ns.spaceboyz.net + "172.22.24.4" "2a01:4f9:4b:39ec::4" + ]; + file = builtins.toFile "${name}.zone" '' + $ORIGIN ${name}. + $TTL 1h + + @ IN SOA ${fqdn}. astro.spaceboyz.net. ( + ${serial} ; serial + 1h ; refresh + 1m ; retry + 2h ; expire + 1m ; minimum + ) + ${lib.concatMapStrings (ns: " IN NS ${ns}.\n") ns} + + ${lib.concatMapStrings ({ name, type, data }: + "${name} IN ${type} ${data}\n" + ) records} + ''; + }; + in { + enable = true; + zones = map generateZone config.site.dns.localZones; + }); + + # TODO: dyn + }; }