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