hydra: 0-unstable-2024-11-25 -> 0-unstable-2024-12-05 (#363987)
[NixPkgs.git] / nixos / tests / knot.nix
blob10ab1390696f70b10b5269cd99e6479b18927533
1 import ./make-test-python.nix (
2   { pkgs, lib, ... }:
3   let
4     common = {
5       networking.firewall.enable = false;
6       networking.useDHCP = false;
7     };
8     exampleZone = pkgs.writeTextDir "example.com.zone" ''
9       @ SOA ns.example.com. noc.example.com. 2019031301 86400 7200 3600000 172800
10       @       NS      ns1
11       @       NS      ns2
12       ns1     A       192.168.0.1
13       ns1     AAAA    fd00::1
14       ns2     A       192.168.0.2
15       ns2     AAAA    fd00::2
16       www     A       192.0.2.1
17       www     AAAA    2001:DB8::1
18       sub     NS      ns.example.com.
19     '';
20     delegatedZone = pkgs.writeTextDir "sub.example.com.zone" ''
21       @ SOA ns.example.com. noc.example.com. 2019031301 86400 7200 3600000 172800
22       @       NS      ns1.example.com.
23       @       NS      ns2.example.com.
24       @       A       192.0.2.2
25       @       AAAA    2001:DB8::2
26     '';
28     knotZonesEnv = pkgs.buildEnv {
29       name = "knot-zones";
30       paths = [
31         exampleZone
32         delegatedZone
33       ];
34     };
35     # DO NOT USE pkgs.writeText IN PRODUCTION. This put secrets in the nix store!
36     tsigFile = pkgs.writeText "tsig.conf" ''
37       key:
38         - id: xfr_key
39           algorithm: hmac-sha256
40           secret: zOYgOgnzx3TGe5J5I/0kxd7gTcxXhLYMEq3Ek3fY37s=
41     '';
42   in
43   {
44     name = "knot";
45     meta = with pkgs.lib.maintainers; {
46       maintainers = [ hexa ];
47     };
49     nodes = {
50       primary =
51         { lib, ... }:
52         {
53           imports = [ common ];
55           # trigger sched_setaffinity syscall
56           virtualisation.cores = 2;
58           networking.interfaces.eth1 = {
59             ipv4.addresses = lib.mkForce [
60               {
61                 address = "192.168.0.1";
62                 prefixLength = 24;
63               }
64             ];
65             ipv6.addresses = lib.mkForce [
66               {
67                 address = "fd00::1";
68                 prefixLength = 64;
69               }
70             ];
71           };
72           services.knot.enable = true;
73           services.knot.extraArgs = [ "-v" ];
74           services.knot.keyFiles = [ tsigFile ];
75           services.knot.settings = {
76             server = {
77               listen = [
78                 "0.0.0.0@53"
79                 "::@53"
80               ];
81               listen-quic = [
82                 "0.0.0.0@853"
83                 "::@853"
84               ];
85               automatic-acl = true;
86             };
88             acl.secondary_acl = {
89               address = "192.168.0.2";
90               key = "xfr_key";
91               action = "transfer";
92             };
94             remote.secondary.address = "192.168.0.2@53";
96             template.default = {
97               storage = knotZonesEnv;
98               notify = [ "secondary" ];
99               acl = [ "secondary_acl" ];
100               dnssec-signing = true;
101               # Input-only zone files
102               # https://www.knot-dns.cz/docs/2.8/html/operation.html#example-3
103               # prevents modification of the zonefiles, since the zonefiles are immutable
104               zonefile-sync = -1;
105               zonefile-load = "difference";
106               journal-content = "changes";
107             };
109             zone = {
110               "example.com".file = "example.com.zone";
111               "sub.example.com".file = "sub.example.com.zone";
112             };
114             log.syslog.any = "info";
115           };
116         };
118       secondary =
119         { lib, ... }:
120         {
121           imports = [ common ];
122           networking.interfaces.eth1 = {
123             ipv4.addresses = lib.mkForce [
124               {
125                 address = "192.168.0.2";
126                 prefixLength = 24;
127               }
128             ];
129             ipv6.addresses = lib.mkForce [
130               {
131                 address = "fd00::2";
132                 prefixLength = 64;
133               }
134             ];
135           };
136           services.knot.enable = true;
137           services.knot.keyFiles = [ tsigFile ];
138           services.knot.extraArgs = [ "-v" ];
139           services.knot.settings = {
140             server = {
141               automatic-acl = true;
142             };
144             xdp = {
145               listen = [
146                 "eth1"
147               ];
148               tcp = true;
149             };
151             remote.primary = {
152               address = "192.168.0.1@53";
153               key = "xfr_key";
154             };
156             remote.primary-quic = {
157               address = "192.168.0.1@853";
158               key = "xfr_key";
159               quic = true;
160             };
162             template.default = {
163               # zonefileless setup
164               # https://www.knot-dns.cz/docs/2.8/html/operation.html#example-2
165               zonefile-sync = "-1";
166               zonefile-load = "none";
167               journal-content = "all";
168             };
170             zone = {
171               "example.com" = {
172                 master = "primary";
173                 file = "example.com.zone";
174               };
175               "sub.example.com" = {
176                 master = "primary-quic";
177                 file = "sub.example.com.zone";
178               };
179             };
181             log.syslog.any = "debug";
182           };
183         };
184       client =
185         { lib, nodes, ... }:
186         {
187           imports = [ common ];
188           networking.interfaces.eth1 = {
189             ipv4.addresses = [
190               {
191                 address = "192.168.0.3";
192                 prefixLength = 24;
193               }
194             ];
195             ipv6.addresses = [
196               {
197                 address = "fd00::3";
198                 prefixLength = 64;
199               }
200             ];
201           };
202           environment.systemPackages = [ pkgs.knot-dns ];
203         };
204     };
206     testScript =
207       { nodes, ... }:
208       let
209         primary4 = (lib.head nodes.primary.config.networking.interfaces.eth1.ipv4.addresses).address;
210         primary6 = (lib.head nodes.primary.config.networking.interfaces.eth1.ipv6.addresses).address;
212         secondary4 = (lib.head nodes.secondary.config.networking.interfaces.eth1.ipv4.addresses).address;
213         secondary6 = (lib.head nodes.secondary.config.networking.interfaces.eth1.ipv6.addresses).address;
214       in
215       ''
216         import re
218         start_all()
220         client.wait_for_unit("network.target")
221         primary.wait_for_unit("knot.service")
222         secondary.wait_for_unit("knot.service")
224         for zone in ("example.com.", "sub.example.com."):
225             secondary.wait_until_succeeds(
226               f"knotc zone-status {zone} | grep -q 'serial: 2019031302'"
227             )
229         def test(host, query_type, query, pattern):
230             out = client.succeed(f"khost -t {query_type} {query} {host}").strip()
231             client.log(f"{host} replied with: {out}")
232             assert re.search(pattern, out), f'Did not match "{pattern}"'
235         for host in ("${primary4}", "${primary6}", "${secondary4}", "${secondary6}"):
236             with subtest(f"Interrogate {host}"):
237                 test(host, "SOA", "example.com", r"start of authority.*noc\.example\.com\.")
238                 test(host, "A", "example.com", r"has no [^ ]+ record")
239                 test(host, "AAAA", "example.com", r"has no [^ ]+ record")
241                 test(host, "A", "www.example.com", r"address 192.0.2.1$")
242                 test(host, "AAAA", "www.example.com", r"address 2001:db8::1$")
244                 test(host, "NS", "sub.example.com", r"nameserver is ns\d\.example\.com.$")
245                 test(host, "A", "sub.example.com", r"address 192.0.2.2$")
246                 test(host, "AAAA", "sub.example.com", r"address 2001:db8::2$")
248                 test(host, "RRSIG", "www.example.com", r"RR set signature is")
249                 test(host, "DNSKEY", "example.com", r"DNSSEC key is")
251         primary.log(primary.succeed("systemd-analyze security knot.service | grep -v '✓'"))
252       '';
253   }