1 import ./make-test-python.nix (
5 networking.firewall.enable = false;
6 networking.useDHCP = false;
8 exampleZone = pkgs.writeTextDir "example.com.zone" ''
9 @ SOA ns.example.com. noc.example.com. 2019031301 86400 7200 3600000 172800
18 sub NS ns.example.com.
20 delegatedZone = pkgs.writeTextDir "sub.example.com.zone" ''
21 @ SOA ns.example.com. noc.example.com. 2019031301 86400 7200 3600000 172800
28 knotZonesEnv = pkgs.buildEnv {
35 # DO NOT USE pkgs.writeText IN PRODUCTION. This put secrets in the nix store!
36 tsigFile = pkgs.writeText "tsig.conf" ''
39 algorithm: hmac-sha256
40 secret: zOYgOgnzx3TGe5J5I/0kxd7gTcxXhLYMEq3Ek3fY37s=
45 meta = with pkgs.lib.maintainers; {
46 maintainers = [ hexa ];
55 # trigger sched_setaffinity syscall
56 virtualisation.cores = 2;
58 networking.interfaces.eth1 = {
59 ipv4.addresses = lib.mkForce [
61 address = "192.168.0.1";
65 ipv6.addresses = lib.mkForce [
72 services.knot.enable = true;
73 services.knot.extraArgs = [ "-v" ];
74 services.knot.keyFiles = [ tsigFile ];
75 services.knot.settings = {
89 address = "192.168.0.2";
94 remote.secondary.address = "192.168.0.2@53";
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
105 zonefile-load = "difference";
106 journal-content = "changes";
110 "example.com".file = "example.com.zone";
111 "sub.example.com".file = "sub.example.com.zone";
114 log.syslog.any = "info";
121 imports = [ common ];
122 networking.interfaces.eth1 = {
123 ipv4.addresses = lib.mkForce [
125 address = "192.168.0.2";
129 ipv6.addresses = lib.mkForce [
136 services.knot.enable = true;
137 services.knot.keyFiles = [ tsigFile ];
138 services.knot.extraArgs = [ "-v" ];
139 services.knot.settings = {
141 automatic-acl = true;
152 address = "192.168.0.1@53";
156 remote.primary-quic = {
157 address = "192.168.0.1@853";
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";
173 file = "example.com.zone";
175 "sub.example.com" = {
176 master = "primary-quic";
177 file = "sub.example.com.zone";
181 log.syslog.any = "debug";
187 imports = [ common ];
188 networking.interfaces.eth1 = {
191 address = "192.168.0.3";
202 environment.systemPackages = [ pkgs.knot-dns ];
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;
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'"
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 '✓'"))