at 21.11-pre 6.6 kB view raw
1import ./make-test-python.nix ({ pkgs, lib, ...} : 2let 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 ''; 26 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: slave_key 35 algorithm: hmac-sha256 36 secret: zOYgOgnzx3TGe5J5I/0kxd7gTcxXhLYMEq3Ek3fY37s= 37 ''; 38in { 39 name = "knot"; 40 meta = with pkgs.lib.maintainers; { 41 maintainers = [ hexa ]; 42 }; 43 44 45 nodes = { 46 master = { lib, ... }: { 47 imports = [ common ]; 48 networking.interfaces.eth1 = { 49 ipv4.addresses = lib.mkForce [ 50 { address = "192.168.0.1"; prefixLength = 24; } 51 ]; 52 ipv6.addresses = lib.mkForce [ 53 { address = "fd00::1"; prefixLength = 64; } 54 ]; 55 }; 56 services.knot.enable = true; 57 services.knot.extraArgs = [ "-v" ]; 58 services.knot.keyFiles = [ tsigFile ]; 59 services.knot.extraConfig = '' 60 server: 61 listen: 0.0.0.0@53 62 listen: ::@53 63 64 acl: 65 - id: slave_acl 66 address: 192.168.0.2 67 key: slave_key 68 action: transfer 69 70 remote: 71 - id: slave 72 address: 192.168.0.2@53 73 74 template: 75 - id: default 76 storage: ${knotZonesEnv} 77 notify: [slave] 78 acl: [slave_acl] 79 dnssec-signing: on 80 # Input-only zone files 81 # https://www.knot-dns.cz/docs/2.8/html/operation.html#example-3 82 # prevents modification of the zonefiles, since the zonefiles are immutable 83 zonefile-sync: -1 84 zonefile-load: difference 85 journal-content: changes 86 # move databases below the state directory, because they need to be writable 87 journal-db: /var/lib/knot/journal 88 kasp-db: /var/lib/knot/kasp 89 timer-db: /var/lib/knot/timer 90 91 zone: 92 - domain: example.com 93 file: example.com.zone 94 95 - domain: sub.example.com 96 file: sub.example.com.zone 97 98 log: 99 - target: syslog 100 any: info 101 ''; 102 }; 103 104 slave = { lib, ... }: { 105 imports = [ common ]; 106 networking.interfaces.eth1 = { 107 ipv4.addresses = lib.mkForce [ 108 { address = "192.168.0.2"; prefixLength = 24; } 109 ]; 110 ipv6.addresses = lib.mkForce [ 111 { address = "fd00::2"; prefixLength = 64; } 112 ]; 113 }; 114 services.knot.enable = true; 115 services.knot.keyFiles = [ tsigFile ]; 116 services.knot.extraArgs = [ "-v" ]; 117 services.knot.extraConfig = '' 118 server: 119 listen: 0.0.0.0@53 120 listen: ::@53 121 122 acl: 123 - id: notify_from_master 124 address: 192.168.0.1 125 action: notify 126 127 remote: 128 - id: master 129 address: 192.168.0.1@53 130 key: slave_key 131 132 template: 133 - id: default 134 master: master 135 acl: [notify_from_master] 136 # zonefileless setup 137 # https://www.knot-dns.cz/docs/2.8/html/operation.html#example-2 138 zonefile-sync: -1 139 zonefile-load: none 140 journal-content: all 141 # move databases below the state directory, because they need to be writable 142 journal-db: /var/lib/knot/journal 143 kasp-db: /var/lib/knot/kasp 144 timer-db: /var/lib/knot/timer 145 146 zone: 147 - domain: example.com 148 file: example.com.zone 149 150 - domain: sub.example.com 151 file: sub.example.com.zone 152 153 log: 154 - target: syslog 155 any: info 156 ''; 157 }; 158 client = { lib, nodes, ... }: { 159 imports = [ common ]; 160 networking.interfaces.eth1 = { 161 ipv4.addresses = [ 162 { address = "192.168.0.3"; prefixLength = 24; } 163 ]; 164 ipv6.addresses = [ 165 { address = "fd00::3"; prefixLength = 64; } 166 ]; 167 }; 168 environment.systemPackages = [ pkgs.knot-dns ]; 169 }; 170 }; 171 172 testScript = { nodes, ... }: let 173 master4 = (lib.head nodes.master.config.networking.interfaces.eth1.ipv4.addresses).address; 174 master6 = (lib.head nodes.master.config.networking.interfaces.eth1.ipv6.addresses).address; 175 176 slave4 = (lib.head nodes.slave.config.networking.interfaces.eth1.ipv4.addresses).address; 177 slave6 = (lib.head nodes.slave.config.networking.interfaces.eth1.ipv6.addresses).address; 178 in '' 179 import re 180 181 start_all() 182 183 client.wait_for_unit("network.target") 184 master.wait_for_unit("knot.service") 185 slave.wait_for_unit("knot.service") 186 187 188 def test(host, query_type, query, pattern): 189 out = client.succeed(f"khost -t {query_type} {query} {host}").strip() 190 client.log(f"{host} replied with: {out}") 191 assert re.search(pattern, out), f'Did not match "{pattern}"' 192 193 194 for host in ("${master4}", "${master6}", "${slave4}", "${slave6}"): 195 with subtest(f"Interrogate {host}"): 196 test(host, "SOA", "example.com", r"start of authority.*noc\.example\.com\.") 197 test(host, "A", "example.com", r"has no [^ ]+ record") 198 test(host, "AAAA", "example.com", r"has no [^ ]+ record") 199 200 test(host, "A", "www.example.com", r"address 192.0.2.1$") 201 test(host, "AAAA", "www.example.com", r"address 2001:db8::1$") 202 203 test(host, "NS", "sub.example.com", r"nameserver is ns\d\.example\.com.$") 204 test(host, "A", "sub.example.com", r"address 192.0.2.2$") 205 test(host, "AAAA", "sub.example.com", r"address 2001:db8::2$") 206 207 test(host, "RRSIG", "www.example.com", r"RR set signature is") 208 test(host, "DNSKEY", "example.com", r"DNSSEC key is") 209 ''; 210})