at 18.09-beta 5.7 kB view raw
1# This module automatically discovers zones in BIND and NSD NixOS 2# configurations and creates zones for all definitions of networking.extraHosts 3# (except those that point to 127.0.0.1 or ::1) within the current test network 4# and delegates these zones using a fake root zone served by a BIND recursive 5# name server. 6{ config, nodes, pkgs, lib, ... }: 7 8{ 9 options.test-support.resolver.enable = lib.mkOption { 10 type = lib.types.bool; 11 default = true; 12 internal = true; 13 description = '' 14 Whether to enable the resolver that automatically discovers zone in the 15 test network. 16 17 This option is <literal>true</literal> by default, because the module 18 defining this option needs to be explicitly imported. 19 20 The reason this option exists is for the 21 <filename>nixos/tests/common/letsencrypt</filename> module, which 22 needs that option to disable the resolver once the user has set its own 23 resolver. 24 ''; 25 }; 26 27 config = lib.mkIf config.test-support.resolver.enable { 28 networking.firewall.enable = false; 29 services.bind.enable = true; 30 services.bind.cacheNetworks = lib.mkForce [ "any" ]; 31 services.bind.forwarders = lib.mkForce []; 32 services.bind.zones = lib.singleton { 33 name = "."; 34 file = let 35 addDot = zone: zone + lib.optionalString (!lib.hasSuffix "." zone) "."; 36 mkNsdZoneNames = zones: map addDot (lib.attrNames zones); 37 mkBindZoneNames = zones: map (zone: addDot zone.name) zones; 38 getZones = cfg: mkNsdZoneNames cfg.services.nsd.zones 39 ++ mkBindZoneNames cfg.services.bind.zones; 40 41 getZonesForNode = attrs: { 42 ip = attrs.config.networking.primaryIPAddress; 43 zones = lib.filter (zone: zone != ".") (getZones attrs.config); 44 }; 45 46 zoneInfo = lib.mapAttrsToList (lib.const getZonesForNode) nodes; 47 48 # A and AAAA resource records for all the definitions of 49 # networking.extraHosts except those for 127.0.0.1 or ::1. 50 # 51 # The result is an attribute set with keys being the host name and the 52 # values are either { ipv4 = ADDR; } or { ipv6 = ADDR; } where ADDR is 53 # the IP address for the corresponding key. 54 recordsFromExtraHosts = let 55 getHostsForNode = lib.const (n: n.config.networking.extraHosts); 56 allHostsList = lib.mapAttrsToList getHostsForNode nodes; 57 allHosts = lib.concatStringsSep "\n" allHostsList; 58 59 reIp = "[a-fA-F0-9.:]+"; 60 reHost = "[a-zA-Z0-9.-]+"; 61 62 matchAliases = str: let 63 matched = builtins.match "[ \t]+(${reHost})(.*)" str; 64 continue = lib.singleton (lib.head matched) 65 ++ matchAliases (lib.last matched); 66 in if matched == null then [] else continue; 67 68 matchLine = str: let 69 result = builtins.match "[ \t]*(${reIp})[ \t]+(${reHost})(.*)" str; 70 in if result == null then null else { 71 ipAddr = lib.head result; 72 hosts = lib.singleton (lib.elemAt result 1) 73 ++ matchAliases (lib.last result); 74 }; 75 76 skipLine = str: let 77 rest = builtins.match "[^\n]*\n(.*)" str; 78 in if rest == null then "" else lib.head rest; 79 80 getEntries = str: acc: let 81 result = matchLine str; 82 next = getEntries (skipLine str); 83 newEntry = acc ++ lib.singleton result; 84 continue = if result == null then next acc else next newEntry; 85 in if str == "" then acc else continue; 86 87 isIPv6 = str: builtins.match ".*:.*" str != null; 88 loopbackIps = [ "127.0.0.1" "::1" ]; 89 filterLoopback = lib.filter (e: !lib.elem e.ipAddr loopbackIps); 90 91 allEntries = lib.concatMap (entry: map (host: { 92 inherit host; 93 ${if isIPv6 entry.ipAddr then "ipv6" else "ipv4"} = entry.ipAddr; 94 }) entry.hosts) (filterLoopback (getEntries (allHosts + "\n") [])); 95 96 mkRecords = entry: let 97 records = lib.optional (entry ? ipv6) "AAAA ${entry.ipv6}" 98 ++ lib.optional (entry ? ipv4) "A ${entry.ipv4}"; 99 mkRecord = typeAndData: "${entry.host}. IN ${typeAndData}"; 100 in lib.concatMapStringsSep "\n" mkRecord records; 101 102 in lib.concatMapStringsSep "\n" mkRecords allEntries; 103 104 # All of the zones that are subdomains of existing zones. 105 # For example if there is only "example.com" the following zones would 106 # be 'subZones': 107 # 108 # * foo.example.com. 109 # * bar.example.com. 110 # 111 # While the following would *not* be 'subZones': 112 # 113 # * example.com. 114 # * com. 115 # 116 subZones = let 117 allZones = lib.concatMap (zi: zi.zones) zoneInfo; 118 isSubZoneOf = z1: z2: lib.hasSuffix z2 z1 && z1 != z2; 119 in lib.filter (z: lib.any (isSubZoneOf z) allZones) allZones; 120 121 # All the zones without 'subZones'. 122 filteredZoneInfo = map (zi: zi // { 123 zones = lib.filter (x: !lib.elem x subZones) zi.zones; 124 }) zoneInfo; 125 126 in pkgs.writeText "fake-root.zone" '' 127 $TTL 3600 128 . IN SOA ns.fakedns. admin.fakedns. ( 1 3h 1h 1w 1d ) 129 ns.fakedns. IN A ${config.networking.primaryIPAddress} 130 . IN NS ns.fakedns. 131 ${lib.concatImapStrings (num: { ip, zones }: '' 132 ns${toString num}.fakedns. IN A ${ip} 133 ${lib.concatMapStrings (zone: '' 134 ${zone} IN NS ns${toString num}.fakedns. 135 '') zones} 136 '') (lib.filter (zi: zi.zones != []) filteredZoneInfo)} 137 ${recordsFromExtraHosts} 138 ''; 139 }; 140 }; 141}