diff --git a/nixos/modules/system/boot/resolved.nix b/nixos/modules/system/boot/resolved.nix index 9bcb9bd0cb01..25569afdf78b 100644 --- a/nixos/modules/system/boot/resolved.nix +++ b/nixos/modules/system/boot/resolved.nix @@ -1,7 +1,6 @@ { config, lib, - pkgs, utils, ... }: @@ -14,12 +13,15 @@ let elem isList literalExpression + mapAttrs' + mapAttrsToList mkIf mkMerge mkOption mkOrder mkRenamedOptionModule mkRemovedOptionModule + nameValuePair optionalAttrs types ; @@ -132,6 +134,27 @@ in }; }; + dnsDelegates = mkOption { + description = '' + dns-delegate files to be created. + See {manpage}`systemd.dns-delegate(5)` for more info. + ''; + default = { }; + type = types.attrsOf ( + types.submodule { + options.Delegate = mkOption { + description = '' + Settings option for systemd dns-delegate files. + See {manpage}`systemd.dns-delegate(5)` for all available options. + ''; + type = types.submodule { + freeformType = types.attrsOf unitOption; + }; + }; + } + ); + }; + }; boot.initrd.services.resolved.enable = mkOption { @@ -167,7 +190,12 @@ in systemd.services.systemd-resolved = { wantedBy = [ "sysinit.target" ]; aliases = [ "dbus-org.freedesktop.resolve1.service" ]; - reloadTriggers = [ config.environment.etc."systemd/resolved.conf".source ]; + reloadTriggers = [ + config.environment.etc."systemd/resolved.conf".source + ] + ++ mapAttrsToList ( + name: _: config.environment.etc."systemd/dns-delegate.d/${name}.dns-delegate".source + ) cfg.dnsDelegates; stopIfChanged = false; }; @@ -180,7 +208,13 @@ in } // optionalAttrs dnsmasqResolve { "dnsmasq-resolv.conf".source = "/run/systemd/resolve/resolv.conf"; - }; + } + // mapAttrs' ( + name: value: + nameValuePair "systemd/dns-delegate.d/${name}.dns-delegate" { + text = settingsToSections (transformSettings value); + } + ) cfg.dnsDelegates; # If networkmanager is enabled, ask it to interface with resolved. networking.networkmanager.dns = "systemd-resolved"; diff --git a/nixos/tests/systemd-resolved.nix b/nixos/tests/systemd-resolved.nix index 5d526d46a4db..15243097c07f 100644 --- a/nixos/tests/systemd-resolved.nix +++ b/nixos/tests/systemd-resolved.nix @@ -36,13 +36,48 @@ }; }; + nodes.delegate_server = + { lib, config, ... }: + let + delegateZone = pkgs.writeTextDir "delegated.example.org.zone" '' + @ SOA ns.delegated.example.org. noc.delegated.example.org. 2019031301 86400 7200 3600000 172800 + test A ${(lib.head config.networking.interfaces.eth1.ipv4.addresses).address} + test AAAA ${(lib.head config.networking.interfaces.eth1.ipv6.addresses).address} + ''; + in + { + networking.firewall.enable = false; + networking.useDHCP = false; + + networking.interfaces.eth1.ipv6.addresses = lib.mkForce [ + { + address = "fd00::3"; + prefixLength = 64; + } + ]; + + services.knot = { + enable = true; + settings = { + server.listen = [ + "0.0.0.0@53" + "::@53" + ]; + template.default.storage = delegateZone; + zone."delegated.example.org".file = "delegated.example.org.zone"; + }; + }; + }; + nodes.client = { nodes, ... }: let - inherit (lib.head nodes.server.networking.interfaces.eth1.ipv4.addresses) address; + serverAddress = (lib.head nodes.server.networking.interfaces.eth1.ipv4.addresses).address; + delegateAddress = + (lib.head nodes.delegate_server.networking.interfaces.eth1.ipv4.addresses).address; in { - networking.nameservers = [ address ]; + networking.nameservers = [ serverAddress ]; networking.interfaces.eth1.ipv6.addresses = lib.mkForce [ { address = "fd00::2"; @@ -51,6 +86,12 @@ ]; services.resolved.enable = true; services.resolved.settings.Resolve.FallbackDNS = [ ]; + services.resolved.dnsDelegates.example-org = { + Delegate = { + DNS = delegateAddress; + Domains = [ "delegated.example.org" ]; + }; + }; networking.useNetworkd = true; networking.useDHCP = false; systemd.network.networks."40-eth0".enable = false; @@ -69,23 +110,35 @@ let address4 = (lib.head nodes.server.networking.interfaces.eth1.ipv4.addresses).address; address6 = (lib.head nodes.server.networking.interfaces.eth1.ipv6.addresses).address; + delegateAddress4 = + (lib.head nodes.delegate_server.networking.interfaces.eth1.ipv4.addresses).address; + delegateAddress6 = + (lib.head nodes.delegate_server.networking.interfaces.eth1.ipv6.addresses).address; in + #python '' start_all() server.wait_for_unit("multi-user.target") + delegate_server.wait_for_unit("multi-user.target") - def test_client(): - query = client.succeed("resolvectl query example.com") - assert "${address4}" in query - assert "${address6}" in query - client.succeed("ping -4 -c 1 example.com") - client.succeed("ping -6 -c 1 example.com") + def test_resolve(domain, expected_addrs): + query = client.succeed(f"resolvectl query {domain}") + for addr in expected_addrs: + assert addr in query, f"Expected {addr} in: {query}" + client.succeed(f"ping -4 -c 1 {domain}") + client.succeed(f"ping -6 -c 1 {domain}") + + with subtest("resolve in initrd"): + client.wait_for_unit("initrd.target") + test_resolve("example.com", ["${address4}", "${address6}"]) - client.wait_for_unit("initrd.target") - test_client() client.switch_root() - client.wait_for_unit("multi-user.target") - test_client() + with subtest("resolve after switch-root"): + client.wait_for_unit("multi-user.target") + test_resolve("example.com", ["${address4}", "${address6}"]) + + with subtest("dns-delegate resolves delegated subdomain"): + test_resolve("test.delegated.example.org", ["${delegateAddress4}", "${delegateAddress6}"]) ''; }