142 lines
		
	
	
		
			5.5 KiB
		
	
	
	
		
			Nix
		
	
	
	
	
	
		
		
			
		
	
	
			142 lines
		
	
	
		
			5.5 KiB
		
	
	
	
		
			Nix
		
	
	
	
	
	
| 
								 | 
							
								# This module automatically discovers zones in BIND and NSD NixOS
							 | 
						||
| 
								 | 
							
								# configurations and creates zones for all definitions of networking.extraHosts
							 | 
						||
| 
								 | 
							
								# (except those that point to 127.0.0.1 or ::1) within the current test network
							 | 
						||
| 
								 | 
							
								# and delegates these zones using a fake root zone served by a BIND recursive
							 | 
						||
| 
								 | 
							
								# name server.
							 | 
						||
| 
								 | 
							
								{ config, nodes, pkgs, lib, ... }:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								{
							 | 
						||
| 
								 | 
							
								  options.test-support.resolver.enable = lib.mkOption {
							 | 
						||
| 
								 | 
							
								    type = lib.types.bool;
							 | 
						||
| 
								 | 
							
								    default = true;
							 | 
						||
| 
								 | 
							
								    internal = true;
							 | 
						||
| 
								 | 
							
								    description = ''
							 | 
						||
| 
								 | 
							
								      Whether to enable the resolver that automatically discovers zone in the
							 | 
						||
| 
								 | 
							
								      test network.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      This option is <literal>true</literal> by default, because the module
							 | 
						||
| 
								 | 
							
								      defining this option needs to be explicitly imported.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      The reason this option exists is for the
							 | 
						||
| 
								 | 
							
								      <filename>nixos/tests/common/letsencrypt.nix</filename> module, which
							 | 
						||
| 
								 | 
							
								      needs that option to disable the resolver once the user has set its own
							 | 
						||
| 
								 | 
							
								      resolver.
							 | 
						||
| 
								 | 
							
								    '';
							 | 
						||
| 
								 | 
							
								  };
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  config = lib.mkIf config.test-support.resolver.enable {
							 | 
						||
| 
								 | 
							
								    networking.firewall.enable = false;
							 | 
						||
| 
								 | 
							
								    services.bind.enable = true;
							 | 
						||
| 
								 | 
							
								    services.bind.cacheNetworks = lib.mkForce [ "any" ];
							 | 
						||
| 
								 | 
							
								    services.bind.forwarders = lib.mkForce [];
							 | 
						||
| 
								 | 
							
								    services.bind.zones = lib.singleton {
							 | 
						||
| 
								 | 
							
								      name = ".";
							 | 
						||
| 
								 | 
							
								      file = let
							 | 
						||
| 
								 | 
							
								        addDot = zone: zone + lib.optionalString (!lib.hasSuffix "." zone) ".";
							 | 
						||
| 
								 | 
							
								        mkNsdZoneNames = zones: map addDot (lib.attrNames zones);
							 | 
						||
| 
								 | 
							
								        mkBindZoneNames = zones: map (zone: addDot zone.name) zones;
							 | 
						||
| 
								 | 
							
								        getZones = cfg: mkNsdZoneNames cfg.services.nsd.zones
							 | 
						||
| 
								 | 
							
								                     ++ mkBindZoneNames cfg.services.bind.zones;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        getZonesForNode = attrs: {
							 | 
						||
| 
								 | 
							
								          ip = attrs.config.networking.primaryIPAddress;
							 | 
						||
| 
								 | 
							
								          zones = lib.filter (zone: zone != ".") (getZones attrs.config);
							 | 
						||
| 
								 | 
							
								        };
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        zoneInfo = lib.mapAttrsToList (lib.const getZonesForNode) nodes;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # A and AAAA resource records for all the definitions of
							 | 
						||
| 
								 | 
							
								        # networking.extraHosts except those for 127.0.0.1 or ::1.
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        # The result is an attribute set with keys being the host name and the
							 | 
						||
| 
								 | 
							
								        # values are either { ipv4 = ADDR; } or { ipv6 = ADDR; } where ADDR is
							 | 
						||
| 
								 | 
							
								        # the IP address for the corresponding key.
							 | 
						||
| 
								 | 
							
								        recordsFromExtraHosts = let
							 | 
						||
| 
								 | 
							
								          getHostsForNode = lib.const (n: n.config.networking.extraHosts);
							 | 
						||
| 
								 | 
							
								          allHostsList = lib.mapAttrsToList getHostsForNode nodes;
							 | 
						||
| 
								 | 
							
								          allHosts = lib.concatStringsSep "\n" allHostsList;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								          reIp = "[a-fA-F0-9.:]+";
							 | 
						||
| 
								 | 
							
								          reHost = "[a-zA-Z0-9.-]+";
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								          matchAliases = str: let
							 | 
						||
| 
								 | 
							
								            matched = builtins.match "[ \t]+(${reHost})(.*)" str;
							 | 
						||
| 
								 | 
							
								            continue = lib.singleton (lib.head matched)
							 | 
						||
| 
								 | 
							
								                    ++ matchAliases (lib.last matched);
							 | 
						||
| 
								 | 
							
								          in if matched == null then [] else continue;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								          matchLine = str: let
							 | 
						||
| 
								 | 
							
								            result = builtins.match "[ \t]*(${reIp})[ \t]+(${reHost})(.*)" str;
							 | 
						||
| 
								 | 
							
								          in if result == null then null else {
							 | 
						||
| 
								 | 
							
								            ipAddr = lib.head result;
							 | 
						||
| 
								 | 
							
								            hosts = lib.singleton (lib.elemAt result 1)
							 | 
						||
| 
								 | 
							
								                 ++ matchAliases (lib.last result);
							 | 
						||
| 
								 | 
							
								          };
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								          skipLine = str: let
							 | 
						||
| 
								 | 
							
								            rest = builtins.match "[^\n]*\n(.*)" str;
							 | 
						||
| 
								 | 
							
								          in if rest == null then "" else lib.head rest;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								          getEntries = str: acc: let
							 | 
						||
| 
								 | 
							
								            result = matchLine str;
							 | 
						||
| 
								 | 
							
								            next = getEntries (skipLine str);
							 | 
						||
| 
								 | 
							
								            newEntry = acc ++ lib.singleton result;
							 | 
						||
| 
								 | 
							
								            continue = if result == null then next acc else next newEntry;
							 | 
						||
| 
								 | 
							
								          in if str == "" then acc else continue;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								          isIPv6 = str: builtins.match ".*:.*" str != null;
							 | 
						||
| 
								 | 
							
								          loopbackIps = [ "127.0.0.1" "::1" ];
							 | 
						||
| 
								 | 
							
								          filterLoopback = lib.filter (e: !lib.elem e.ipAddr loopbackIps);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								          allEntries = lib.concatMap (entry: map (host: {
							 | 
						||
| 
								 | 
							
								            inherit host;
							 | 
						||
| 
								 | 
							
								            ${if isIPv6 entry.ipAddr then "ipv6" else "ipv4"} = entry.ipAddr;
							 | 
						||
| 
								 | 
							
								          }) entry.hosts) (filterLoopback (getEntries (allHosts + "\n") []));
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								          mkRecords = entry: let
							 | 
						||
| 
								 | 
							
								            records = lib.optional (entry ? ipv6) "AAAA ${entry.ipv6}"
							 | 
						||
| 
								 | 
							
								                   ++ lib.optional (entry ? ipv4) "A ${entry.ipv4}";
							 | 
						||
| 
								 | 
							
								            mkRecord = typeAndData: "${entry.host}. IN ${typeAndData}";
							 | 
						||
| 
								 | 
							
								          in lib.concatMapStringsSep "\n" mkRecord records;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        in lib.concatMapStringsSep "\n" mkRecords allEntries;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # All of the zones that are subdomains of existing zones.
							 | 
						||
| 
								 | 
							
								        # For example if there is only "example.com" the following zones would
							 | 
						||
| 
								 | 
							
								        # be 'subZones':
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        #  * foo.example.com.
							 | 
						||
| 
								 | 
							
								        #  * bar.example.com.
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        # While the following would *not* be 'subZones':
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        #  * example.com.
							 | 
						||
| 
								 | 
							
								        #  * com.
							 | 
						||
| 
								 | 
							
								        #
							 | 
						||
| 
								 | 
							
								        subZones = let
							 | 
						||
| 
								 | 
							
								          allZones = lib.concatMap (zi: zi.zones) zoneInfo;
							 | 
						||
| 
								 | 
							
								          isSubZoneOf = z1: z2: lib.hasSuffix z2 z1 && z1 != z2;
							 | 
						||
| 
								 | 
							
								        in lib.filter (z: lib.any (isSubZoneOf z) allZones) allZones;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # All the zones without 'subZones'.
							 | 
						||
| 
								 | 
							
								        filteredZoneInfo = map (zi: zi // {
							 | 
						||
| 
								 | 
							
								          zones = lib.filter (x: !lib.elem x subZones) zi.zones;
							 | 
						||
| 
								 | 
							
								        }) zoneInfo;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      in pkgs.writeText "fake-root.zone" ''
							 | 
						||
| 
								 | 
							
								        $TTL 3600
							 | 
						||
| 
								 | 
							
								        . IN SOA ns.fakedns. admin.fakedns. ( 1 3h 1h 1w 1d )
							 | 
						||
| 
								 | 
							
								        ns.fakedns. IN A ${config.networking.primaryIPAddress}
							 | 
						||
| 
								 | 
							
								        . IN NS ns.fakedns.
							 | 
						||
| 
								 | 
							
								        ${lib.concatImapStrings (num: { ip, zones }: ''
							 | 
						||
| 
								 | 
							
								          ns${toString num}.fakedns. IN A ${ip}
							 | 
						||
| 
								 | 
							
								          ${lib.concatMapStrings (zone: ''
							 | 
						||
| 
								 | 
							
								          ${zone} IN NS ns${toString num}.fakedns.
							 | 
						||
| 
								 | 
							
								          '') zones}
							 | 
						||
| 
								 | 
							
								        '') (lib.filter (zi: zi.zones != []) filteredZoneInfo)}
							 | 
						||
| 
								 | 
							
								        ${recordsFromExtraHosts}
							 | 
						||
| 
								 | 
							
								      '';
							 | 
						||
| 
								 | 
							
								    };
							 | 
						||
| 
								 | 
							
								  };
							 | 
						||
| 
								 | 
							
								}
							 |