diff --git a/nixos/tests/dnscrypt-wrapper/default.nix b/nixos/tests/dnscrypt-wrapper/default.nix index 1c05376e097b..1a794931dc50 100644 --- a/nixos/tests/dnscrypt-wrapper/default.nix +++ b/nixos/tests/dnscrypt-wrapper/default.nix @@ -1,5 +1,15 @@ + { lib, pkgs, ... }: +let + snakeoil = import ../common/acme/server/snakeoil-certs.nix; + + hosts = lib.mkForce + { "fd::a" = [ "server" snakeoil.domain ]; + "fd::b" = [ "client" ]; + }; +in + { name = "dnscrypt-wrapper"; meta = with pkgs.lib.maintainers; { @@ -7,59 +17,122 @@ }; nodes = { - server = { lib, ... }: - { services.dnscrypt-wrapper = with builtins; + server = { + networking.hosts = hosts; + networking.interfaces.eth1.ipv6.addresses = lib.singleton + { address = "fd::a"; prefixLength = 64; }; + + services.dnscrypt-wrapper = { enable = true; - address = "192.168.1.1"; + address = "[::]"; + port = 5353; keys.expiration = 5; # days keys.checkInterval = 2; # min # The keypair was generated by the command: # dnscrypt-wrapper --gen-provider-keypair \ # --provider-name=2.dnscrypt-cert.server \ - # --ext-address=192.168.1.1:5353 - providerKey.public = toFile "public.key" (readFile ./public.key); - providerKey.secret = toFile "secret.key" (readFile ./secret.key); + providerKey.public = "${./public.key}"; + providerKey.secret = "${./secret.key}"; }; - services.tinydns.enable = true; - services.tinydns.data = '' - ..:192.168.1.1:a - +it.works:1.2.3.4 - ''; - networking.firewall.allowedUDPPorts = [ 5353 ]; - networking.firewall.allowedTCPPorts = [ 5353 ]; - networking.interfaces.eth1.ipv4.addresses = lib.mkForce - [ { address = "192.168.1.1"; prefixLength = 24; } ]; + + # nameserver + services.bind.enable = true; + services.bind.zones = lib.singleton + { name = "."; + master = true; + file = pkgs.writeText "root.zone" '' + $TTL 3600 + . IN SOA example.org. admin.example.org. ( 1 3h 1h 1w 1d ) + . IN NS example.org. + example.org. IN AAAA 2001:db8::1 + ''; + }; + + # webserver + services.nginx.enable = true; + services.nginx.virtualHosts.${snakeoil.domain} = + { onlySSL = true; + listenAddresses = [ "localhost" ]; + sslCertificate = snakeoil.${snakeoil.domain}.cert; + sslCertificateKey = snakeoil.${snakeoil.domain}.key; + locations."/ip".extraConfig = '' + default_type text/plain; + return 200 "Ciao $remote_addr!\n"; + ''; + }; + + # demultiplex HTTP and DNS from port 443 + services.sslh = + { enable = true; + method = "ev"; + settings.transparent = true; + settings.listen = lib.mkForce + [ { host = "server"; port = "443"; is_udp = false; } + { host = "server"; port = "443"; is_udp = true; } + ]; + settings.protocols = + [ # Send TLS to webserver (TCP) + { name = "tls"; host= "localhost"; port= "443"; } + # Send DNSCrypt to dnscrypt-wrapper (TCP or UDP) + { name = "anyprot"; host = "localhost"; port = "5353"; } + { name = "anyprot"; host = "localhost"; port = "5353"; is_udp = true;} + ]; + }; + + networking.firewall.allowedTCPPorts = [ 443 ]; + networking.firewall.allowedUDPPorts = [ 443 ]; }; - client = { lib, ... }: - { services.dnscrypt-proxy2.enable = true; - services.dnscrypt-proxy2.upstreamDefaults = false; - services.dnscrypt-proxy2.settings = { - server_names = [ "server" ]; - static.server.stamp = "sdns://AQAAAAAAAAAAEDE5Mi4xNjguMS4xOjUzNTMgFEHYOv0SCKSuqR5CDYa7-58cCBuXO2_5uTSVU9wNQF0WMi5kbnNjcnlwdC1jZXJ0LnNlcnZlcg"; + client = { + networking.hosts = hosts; + networking.interfaces.eth1.ipv6.addresses = lib.singleton + { address = "fd::b"; prefixLength = 64; }; + + services.dnscrypt-proxy2.enable = true; + services.dnscrypt-proxy2.upstreamDefaults = false; + services.dnscrypt-proxy2.settings = + { server_names = [ "server" ]; + listen_addresses = [ "[::1]:53" ]; + cache = false; + # Computed using https://dnscrypt.info/stamps/ + static.server.stamp = + "sdns://AQAAAAAAAAAADzE5Mi4xNjguMS4yOjQ0MyAUQdg6" + +"_RIIpK6pHkINhrv7nxwIG5c7b_m5NJVT3A1AXRYyLmRuc2NyeXB0LWNlcnQuc2VydmVy"; }; - networking.nameservers = [ "127.0.0.1" ]; - networking.interfaces.eth1.ipv4.addresses = lib.mkForce - [ { address = "192.168.1.2"; prefixLength = 24; } ]; - }; + networking.nameservers = [ "::1" ]; + security.pki.certificateFiles = [ snakeoil.ca.cert ]; + }; }; testScript = '' - start_all() - with subtest("The server can generate the ephemeral keypair"): server.wait_for_unit("dnscrypt-wrapper") server.wait_for_file("/var/lib/dnscrypt-wrapper/2.dnscrypt-cert.server.key") server.wait_for_file("/var/lib/dnscrypt-wrapper/2.dnscrypt-cert.server.crt") almost_expiration = server.succeed("date --date '4days 23 hours 56min'").strip() - with subtest("The client can connect to the server"): - server.wait_for_unit("tinydns") - client.wait_for_unit("dnscrypt-proxy2") - assert "1.2.3.4" in client.wait_until_succeeds( - "host it.works" - ), "The IP address of 'it.works' does not match 1.2.3.4" + with subtest("The DNSCrypt client can connect to the server"): + server.wait_for_unit("sslh") + client.wait_until_succeeds("journalctl -u dnscrypt-proxy2 --grep '\[server\] OK'") + + with subtest("HTTP client can connect to the server"): + server.wait_for_unit("nginx") + client.succeed("curl -s --fail https://${snakeoil.domain}/ip | grep -q fd::b") + + with subtest("DNS queries over UDP are working"): + server.wait_for_unit("bind") + client.wait_for_open_port(53) + assert "2001:db8::1" in client.wait_until_succeeds( + "host -U example.org" + ), "The IP address of 'example.org' does not match 2001:db8::1" + + with subtest("DNS queries over TCP are working"): + server.wait_for_unit("bind") + client.wait_for_open_port(53) + assert "2001:db8::1" in client.wait_until_succeeds( + "host -T example.org" + ), "The IP address of 'example.org' does not match 2001:db8::1" with subtest("The server rotates the ephemeral keys"): # advance time by a little less than 5 days @@ -68,7 +141,8 @@ server.wait_for_file("/var/lib/dnscrypt-wrapper/oldkeys") with subtest("The client can still connect to the server"): - server.wait_for_unit("dnscrypt-wrapper") - client.succeed("host it.works") + client.systemctl("restart dnscrypt-proxy2") + client.wait_until_succeeds("host -T example.org") + client.wait_until_succeeds("host -U example.org") ''; }