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