|
82 | 82 | nodes = {
|
83 | 83 | resolver = {
|
84 | 84 | networking.domain = vpnDomain;
|
| 85 | + |
| 86 | + networking.firewall = { |
| 87 | + #allowedTCPPorts = [resolverPort 5353]; |
| 88 | + #allowedUDPPorts = [resolverPort 5353]; |
| 89 | + allowedTCPPorts = [5353]; |
| 90 | + allowedUDPPorts = [5353]; |
| 91 | + }; |
| 92 | + |
85 | 93 | services.dnsmasq = {
|
86 | 94 | enable = true;
|
87 | 95 | resolveLocalQueries = false;
|
|
118 | 126 | "server-cname,server-cname.${vpnDomain},server"
|
119 | 127 | "client-cname,client-cname.${vpnDomain},client"
|
120 | 128 | ];
|
| 129 | + |
| 130 | + dnssec = true; |
| 131 | + trust-anchor = lib.importJSON ./trust-anchor.json; |
121 | 132 | };
|
122 | 133 | };
|
123 | 134 |
|
124 |
| - networking.firewall = { |
125 |
| - allowedTCPPorts = [resolverPort]; |
126 |
| - allowedUDPPorts = [resolverPort]; |
| 135 | + services.routedns = { |
| 136 | + enable = true; |
| 137 | + |
| 138 | + settings = { |
| 139 | + resolvers = { |
| 140 | + local-tcp = { |
| 141 | + address = "127.0.0.1:53"; |
| 142 | + protocol = "tcp"; |
| 143 | + }; |
| 144 | + |
| 145 | + local-udp = { |
| 146 | + address = "127.0.0.1:53"; |
| 147 | + protocol = "udp"; |
| 148 | + }; |
| 149 | + }; |
| 150 | + |
| 151 | + listeners = let |
| 152 | + commonConfig = { |
| 153 | + address = ":5353"; |
| 154 | + |
| 155 | + # Generated with `mkcert`. |
| 156 | + server-crt = ./resolver.crt; |
| 157 | + server-key = ./resolver.key; |
| 158 | + }; |
| 159 | + in { |
| 160 | + local-dot = commonConfig // { |
| 161 | + protocol = "dot"; |
| 162 | + resolver = "local-tcp"; |
| 163 | + }; |
| 164 | + |
| 165 | + local-dtls = commonConfig // { |
| 166 | + protocol = "dtls"; |
| 167 | + resolver = "local-udp"; |
| 168 | + }; |
| 169 | + }; |
| 170 | + }; |
127 | 171 | };
|
128 | 172 | };
|
129 | 173 |
|
|
176 | 220 | polkitRules = mkPolkitRulesForService config.systemd.services.${serviceAttrName};
|
177 | 221 | in {
|
178 | 222 | networking.useNetworkd = true;
|
179 |
| - services.resolved.enable = true; |
180 |
| - services.resolved.dnssec = "false"; |
| 223 | + |
| 224 | + services.resolved = { |
| 225 | + enable = true; |
| 226 | + dnssec = "false"; # overridden for VPN interface |
| 227 | + extraConfig = '' |
| 228 | + MulticastDNS=no |
| 229 | + ''; |
| 230 | + }; |
181 | 231 |
|
182 | 232 | users.users.openvpn = {
|
183 | 233 | description = "openvpn client user";
|
|
218 | 268 |
|
219 | 269 | config ${perSystem.config.packages.update-systemd-resolved}/libexec/openvpn/update-systemd-resolved.conf
|
220 | 270 |
|
221 |
| - dhcp-option DNS ${resolverIP} |
| 271 | + dhcp-option DNS ${resolverIP}:5353#resolver |
222 | 272 | dhcp-option DOMAIN ${vpnDomain}
|
| 273 | +
|
| 274 | + dhcp-option FLUSH-CACHES yes |
| 275 | + dhcp-option RESET-SERVER-FEATURES true |
| 276 | + dhcp-option RESET-STATISTICS yes |
| 277 | +
|
| 278 | + dhcp-option DEFAULT-ROUTE yes |
| 279 | + dhcp-option DNS-OVER-TLS yes |
| 280 | + dhcp-option LLMNR resolve |
| 281 | + dhcp-option MULTICAST-DNS default |
| 282 | +
|
| 283 | + dhcp-option DNSSEC true |
| 284 | + dhcp-option DNSSEC-NEGATIVE-TRUST-ANCHORS ${vpnDomain} |
223 | 285 | '';
|
224 | 286 | };
|
225 | 287 |
|
226 | 288 | # Add our generated ruleset to the system's polkit rules
|
227 | 289 | environment.etc."polkit-1/rules.d/10-update-systemd-resolved.rules".source = polkitRules;
|
228 | 290 |
|
| 291 | + # `mkcert` CA |
| 292 | + security.pki.certificateFiles = [./rootCA.pem]; |
| 293 | + |
229 | 294 | security.polkit = {
|
230 | 295 | enable = true;
|
231 | 296 | debug = true;
|
|
292 | 357 | machine.execute('systemctl status -l {0} 1>&2'.format(unit))
|
293 | 358 | raise(e)
|
294 | 359 |
|
| 360 | + def dump_resolved_info(machine): |
| 361 | + with machine.nested('printing resolved status and statistics'): |
| 362 | + machine.succeed('resolvectl status 1>&2') |
| 363 | + machine.succeed('resolvectl statistics 1>&2') |
| 364 | +
|
295 | 365 | def assert_hostname_match(machine, expected, *args):
|
296 | 366 | cmd = shlex.join(['dig', '+short', *args])
|
297 | 367 |
|
|
313 | 383 | with machine.nested('checking that hostname resolves to expected address "{0}" from {1}'.format(expected, machine.name)):
|
314 | 384 | retry(hostname_matches)
|
315 | 385 |
|
| 386 | + def extract_interface_property(machine, interface, property, *args): |
| 387 | + with machine.nested('extracting property "{0}" of interface "{1}"'.format(property, interface)): |
| 388 | + cmd = shlex.join(['resolvectl', *args, property, interface]) |
| 389 | + return machine.succeed("{0} | grep -m1 -Po '(?<=:\s).*'".format(cmd)).rstrip() |
| 390 | +
|
| 391 | + def assert_interface_property(machine, interface, property, expected, *args): |
| 392 | + with machine.nested('checking that property "{0}" of interface "{1}" is "{2}"'.format(property, interface, expected)): |
| 393 | + actual = extract_interface_property(machine, interface, property, *args) |
| 394 | + machine.log('property "{0}" of interface "{1}" is "{2}"'.format(property, interface, actual)) |
| 395 | + if actual != expected: |
| 396 | + raise Exception('expected property "{0}" of interface "{1}" to be "{2}", but got "{3}"'.format(property, interface, expected, actual)) |
| 397 | +
|
316 | 398 | # Machine.wait_for_open_port only checks ports on localhost
|
317 | 399 | def wait_for_open_host_port(machine, host, port, extra=[]):
|
318 | 400 | cmd = shlex.join(['nc'] + extra + ['-z', host, str(port)])
|
|
330 | 412 |
|
331 | 413 | resolver.start()
|
332 | 414 | wait_for_unit_with_output(resolver, 'dnsmasq')
|
| 415 | + wait_for_unit_with_output(resolver, 'routedns') |
333 | 416 |
|
334 | 417 | server.start()
|
335 | 418 | wait_for_unit_with_output(server, '${serviceName}')
|
336 | 419 |
|
337 | 420 | client.start()
|
| 421 | +
|
338 | 422 | wait_for_unit_with_output(client, '${serviceName}')
|
339 | 423 |
|
340 | 424 | # Block until we can reach the resolver (or until we hit the retry
|
341 | 425 | # timeout). Pass `-u` flag to check UDP port; also check TCP port.
|
342 |
| - wait_for_open_host_port(client, '${resolverIP}', 53, extra=['-u']) |
343 |
| - wait_for_open_host_port(client, '${resolverIP}', 53) |
| 426 | + #wait_for_open_host_port(client, '${resolverIP}', 53, extra=['-u']) |
| 427 | + #wait_for_open_host_port(client, '${resolverIP}', 53) |
| 428 | + wait_for_open_host_port(client, '${resolverIP}', 5353, extra=['-u']) |
| 429 | + wait_for_open_host_port(client, '${resolverIP}', 5353) |
344 | 430 |
|
345 | 431 | assert_hostname_match(client, '${resolverIP}', 'resolver-cname.${vpnDomain}')
|
346 | 432 | assert_hostname_match(client, '${serverIP}', 'server-cname.${vpnDomain}')
|
| 433 | +
|
| 434 | + dump_resolved_info(client) |
| 435 | +
|
| 436 | + assert_interface_property(client, '${interface}', 'default-route', 'yes') |
| 437 | + assert_interface_property(client, '${interface}', 'llmnr', 'resolve') |
| 438 | + assert_interface_property(client, '${interface}', 'mdns', 'no') |
| 439 | + assert_interface_property(client, '${interface}', 'dnsovertls', 'yes') |
| 440 | + assert_interface_property(client, '${interface}', 'dnssec', 'yes') |
| 441 | +
|
| 442 | + client.succeed('systemctl restart ${serviceName}') |
| 443 | + wait_for_unit_with_output(client, '${serviceName}') |
| 444 | +
|
| 445 | + dump_resolved_info(client) |
347 | 446 | '';
|
348 | 447 | };
|
349 | 448 | };
|
|
0 commit comments