1{ config, lib, pkgs, ... }: 2with lib; 3 4let 5 apparmorEnabled = config.security.apparmor.enable; 6 dnscrypt-proxy = pkgs.dnscrypt-proxy; 7 cfg = config.services.dnscrypt-proxy; 8 resolverListFile = "${dnscrypt-proxy}/share/dnscrypt-proxy/dnscrypt-resolvers.csv"; 9 localAddress = "${cfg.localAddress}:${toString cfg.localPort}"; 10 daemonArgs = 11 [ "--local-address=${localAddress}" 12 (optionalString cfg.tcpOnly "--tcp-only") 13 ] 14 ++ resolverArgs; 15 resolverArgs = if (cfg.customResolver != null) 16 then 17 [ "--resolver-address=${cfg.customResolver.address}:${toString cfg.customResolver.port}" 18 "--provider-name=${cfg.customResolver.name}" 19 "--provider-key=${cfg.customResolver.key}" 20 ] 21 else 22 [ "--resolvers-list=${resolverListFile}" 23 "--resolver-name=${toString cfg.resolverName}" 24 ]; 25in 26 27{ 28 options = { 29 services.dnscrypt-proxy = { 30 enable = mkEnableOption '' 31 Enable dnscrypt-proxy. The proxy relays regular DNS queries to a 32 DNSCrypt enabled upstream resolver. The traffic between the 33 client and the upstream resolver is encrypted and authenticated, 34 which may mitigate the risk of MITM attacks and third-party 35 snooping (assuming the upstream is trustworthy). 36 ''; 37 localAddress = mkOption { 38 default = "127.0.0.1"; 39 type = types.string; 40 description = '' 41 Listen for DNS queries on this address. 42 ''; 43 }; 44 localPort = mkOption { 45 default = 53; 46 type = types.int; 47 description = '' 48 Listen on this port. 49 ''; 50 }; 51 resolverName = mkOption { 52 default = "opendns"; 53 type = types.nullOr types.string; 54 description = '' 55 The name of the upstream DNSCrypt resolver to use. See 56 <literal>${resolverListFile}</literal> for alternative resolvers 57 (e.g., if you are concerned about logging and/or server 58 location). 59 ''; 60 }; 61 customResolver = mkOption { 62 default = null; 63 description = '' 64 Use a resolver not listed in the upstream list (e.g., 65 a private DNSCrypt provider). For advanced users only. 66 If specified, this option takes precedence. 67 ''; 68 type = types.nullOr (types.submodule ({ ... }: { options = { 69 address = mkOption { 70 type = types.str; 71 description = "Resolver IP address"; 72 example = "208.67.220.220"; 73 }; 74 port = mkOption { 75 type = types.int; 76 description = "Resolver port"; 77 default = 443; 78 }; 79 name = mkOption { 80 type = types.str; 81 description = "Provider fully qualified domain name"; 82 example = "2.dnscrypt-cert.opendns.com"; 83 }; 84 key = mkOption { 85 type = types.str; 86 description = "Provider public key"; 87 example = "B735:1140:206F:225D:3E2B:D822:D7FD:691E:A1C3:3CC8:D666:8D0C:BE04:BFAB:CA43:FB79"; 88 }; }; })); 89 }; 90 tcpOnly = mkOption { 91 default = false; 92 type = types.bool; 93 description = '' 94 Force sending encrypted DNS queries to the upstream resolver 95 over TCP instead of UDP (on port 443). Enabling this option may 96 help circumvent filtering, but should not be used otherwise. 97 ''; 98 }; 99 }; 100 }; 101 102 config = mkIf cfg.enable { 103 104 assertions = [ 105 { assertion = (cfg.customResolver != null) || (cfg.resolverName != null); 106 message = "please configure upstream DNSCrypt resolver"; 107 } 108 ]; 109 110 security.apparmor.profiles = mkIf apparmorEnabled (singleton (pkgs.writeText "apparmor-dnscrypt-proxy" '' 111 ${dnscrypt-proxy}/bin/dnscrypt-proxy { 112 /dev/null rw, 113 /dev/urandom r, 114 115 /etc/passwd r, 116 /etc/group r, 117 ${config.environment.etc."nsswitch.conf".source} r, 118 119 ${pkgs.glibc}/lib/*.so mr, 120 ${pkgs.tzdata}/share/zoneinfo/** r, 121 122 network inet stream, 123 network inet6 stream, 124 network inet dgram, 125 network inet6 dgram, 126 127 ${pkgs.gcc.cc}/lib/libssp.so.* mr, 128 ${pkgs.libsodium}/lib/libsodium.so.* mr, 129 ${pkgs.systemd}/lib/libsystemd.so.* mr, 130 ${pkgs.xz}/lib/liblzma.so.* mr, 131 ${pkgs.libgcrypt}/lib/libgcrypt.so.* mr, 132 ${pkgs.libgpgerror}/lib/libgpg-error.so.* mr, 133 134 ${resolverListFile} r, 135 } 136 '')); 137 138 users.extraUsers.dnscrypt-proxy = { 139 uid = config.ids.uids.dnscrypt-proxy; 140 description = "dnscrypt-proxy daemon user"; 141 }; 142 users.extraGroups.dnscrypt-proxy.gid = config.ids.gids.dnscrypt-proxy; 143 144 systemd.sockets.dnscrypt-proxy = { 145 description = "dnscrypt-proxy listening socket"; 146 socketConfig = { 147 ListenStream = "${localAddress}"; 148 ListenDatagram = "${localAddress}"; 149 }; 150 wantedBy = [ "sockets.target" ]; 151 }; 152 153 systemd.services.dnscrypt-proxy = { 154 description = "dnscrypt-proxy daemon"; 155 after = [ "network.target" ] ++ optional apparmorEnabled "apparmor.service"; 156 requires = [ "dnscrypt-proxy.socket "] ++ optional apparmorEnabled "apparmor.service"; 157 serviceConfig = { 158 Type = "simple"; 159 NonBlocking = "true"; 160 ExecStart = "${dnscrypt-proxy}/bin/dnscrypt-proxy ${toString daemonArgs}"; 161 User = "dnscrypt-proxy"; 162 Group = "dnscrypt-proxy"; 163 PrivateTmp = true; 164 PrivateDevices = true; 165 }; 166 }; 167 }; 168}