at 24.11-pre 5.4 kB view raw
1 2{ lib, pkgs, ... }: 3 4let 5 snakeoil = import ../common/acme/server/snakeoil-certs.nix; 6 7 hosts = lib.mkForce 8 { "fd::a" = [ "server" snakeoil.domain ]; 9 "fd::b" = [ "client" ]; 10 }; 11in 12 13{ 14 name = "dnscrypt-wrapper"; 15 meta = with pkgs.lib.maintainers; { 16 maintainers = [ rnhmjoj ]; 17 }; 18 19 nodes = { 20 server = { 21 networking.hosts = hosts; 22 networking.interfaces.eth1.ipv6.addresses = lib.singleton 23 { address = "fd::a"; prefixLength = 64; }; 24 25 services.dnscrypt-wrapper = 26 { enable = true; 27 address = "[::]"; 28 port = 5353; 29 keys.expiration = 5; # days 30 keys.checkInterval = 2; # min 31 # The keypair was generated by the command: 32 # dnscrypt-wrapper --gen-provider-keypair \ 33 # --provider-name=2.dnscrypt-cert.server \ 34 providerKey.public = "${./public.key}"; 35 providerKey.secret = "${./secret.key}"; 36 }; 37 38 # nameserver 39 services.bind.enable = true; 40 services.bind.zones = lib.singleton 41 { name = "."; 42 master = true; 43 file = pkgs.writeText "root.zone" '' 44 $TTL 3600 45 . IN SOA example.org. admin.example.org. ( 1 3h 1h 1w 1d ) 46 . IN NS example.org. 47 example.org. IN AAAA 2001:db8::1 48 ''; 49 }; 50 51 # webserver 52 services.nginx.enable = true; 53 services.nginx.virtualHosts.${snakeoil.domain} = 54 { onlySSL = true; 55 listenAddresses = [ "localhost" ]; 56 sslCertificate = snakeoil.${snakeoil.domain}.cert; 57 sslCertificateKey = snakeoil.${snakeoil.domain}.key; 58 locations."/ip".extraConfig = '' 59 default_type text/plain; 60 return 200 "Ciao $remote_addr!\n"; 61 ''; 62 }; 63 64 # demultiplex HTTP and DNS from port 443 65 services.sslh = 66 { enable = true; 67 method = "ev"; 68 settings.transparent = true; 69 settings.listen = lib.mkForce 70 [ { host = "server"; port = "443"; is_udp = false; } 71 { host = "server"; port = "443"; is_udp = true; } 72 ]; 73 settings.protocols = 74 [ # Send TLS to webserver (TCP) 75 { name = "tls"; host= "localhost"; port= "443"; } 76 # Send DNSCrypt to dnscrypt-wrapper (TCP or UDP) 77 { name = "anyprot"; host = "localhost"; port = "5353"; } 78 { name = "anyprot"; host = "localhost"; port = "5353"; is_udp = true;} 79 ]; 80 }; 81 82 networking.firewall.allowedTCPPorts = [ 443 ]; 83 networking.firewall.allowedUDPPorts = [ 443 ]; 84 }; 85 86 client = { 87 networking.hosts = hosts; 88 networking.interfaces.eth1.ipv6.addresses = lib.singleton 89 { address = "fd::b"; prefixLength = 64; }; 90 91 services.dnscrypt-proxy2.enable = true; 92 services.dnscrypt-proxy2.upstreamDefaults = false; 93 services.dnscrypt-proxy2.settings = 94 { server_names = [ "server" ]; 95 listen_addresses = [ "[::1]:53" ]; 96 cache = false; 97 # Computed using https://dnscrypt.info/stamps/ 98 static.server.stamp = 99 "sdns://AQAAAAAAAAAADzE5Mi4xNjguMS4yOjQ0MyAUQdg6" 100 +"_RIIpK6pHkINhrv7nxwIG5c7b_m5NJVT3A1AXRYyLmRuc2NyeXB0LWNlcnQuc2VydmVy"; 101 }; 102 networking.nameservers = [ "::1" ]; 103 security.pki.certificateFiles = [ snakeoil.ca.cert ]; 104 }; 105 106 }; 107 108 testScript = '' 109 with subtest("The server can generate the ephemeral keypair"): 110 server.wait_for_unit("dnscrypt-wrapper") 111 server.wait_for_file("/var/lib/dnscrypt-wrapper/2.dnscrypt-cert.server.key") 112 server.wait_for_file("/var/lib/dnscrypt-wrapper/2.dnscrypt-cert.server.crt") 113 almost_expiration = server.succeed("date --date '4days 23 hours 56min'").strip() 114 115 with subtest("The DNSCrypt client can connect to the server"): 116 server.wait_for_unit("sslh") 117 client.wait_until_succeeds("journalctl -u dnscrypt-proxy2 --grep '\[server\] OK'") 118 119 with subtest("HTTP client can connect to the server"): 120 server.wait_for_unit("nginx") 121 client.succeed("curl -s --fail https://${snakeoil.domain}/ip | grep -q fd::b") 122 123 with subtest("DNS queries over UDP are working"): 124 server.wait_for_unit("bind") 125 client.wait_for_open_port(53) 126 assert "2001:db8::1" in client.wait_until_succeeds( 127 "host -U example.org" 128 ), "The IP address of 'example.org' does not match 2001:db8::1" 129 130 with subtest("DNS queries over TCP are working"): 131 server.wait_for_unit("bind") 132 client.wait_for_open_port(53) 133 assert "2001:db8::1" in client.wait_until_succeeds( 134 "host -T example.org" 135 ), "The IP address of 'example.org' does not match 2001:db8::1" 136 137 with subtest("The server rotates the ephemeral keys"): 138 # advance time by a little less than 5 days 139 server.succeed(f"date -s '{almost_expiration}'") 140 client.succeed(f"date -s '{almost_expiration}'") 141 server.wait_for_file("/var/lib/dnscrypt-wrapper/oldkeys") 142 143 with subtest("The client can still connect to the server"): 144 client.systemctl("restart dnscrypt-proxy2") 145 client.wait_until_succeeds("host -T example.org") 146 client.wait_until_succeeds("host -U example.org") 147 ''; 148}