at 18.09-beta 10 kB view raw
1{ config, lib, pkgs, ... }: 2with lib; 3 4let 5 cfg = config.services.dnscrypt-proxy; 6 7 stateDirectory = "/var/lib/dnscrypt-proxy"; 8 9 # The minisign public key used to sign the upstream resolver list. 10 # This is somewhat more flexible than preloading the key as an 11 # embedded string. 12 upstreamResolverListPubKey = pkgs.fetchurl { 13 url = https://raw.githubusercontent.com/dyne/dnscrypt-proxy/master/minisign.pub; 14 sha256 = "18lnp8qr6ghfc2sd46nn1rhcpr324fqlvgsp4zaigw396cd7vnnh"; 15 }; 16 17 # Internal flag indicating whether the upstream resolver list is used. 18 useUpstreamResolverList = cfg.customResolver == null; 19 20 # The final local address. 21 localAddress = "${cfg.localAddress}:${toString cfg.localPort}"; 22 23 # The final resolvers list path. 24 resolverList = "${stateDirectory}/dnscrypt-resolvers.csv"; 25 26 # Build daemon command line 27 28 resolverArgs = 29 if (cfg.customResolver == null) 30 then 31 [ "-L ${resolverList}" 32 "-R ${cfg.resolverName}" 33 ] 34 else with cfg.customResolver; 35 [ "-N ${name}" 36 "-k ${key}" 37 "-r ${address}:${toString port}" 38 ]; 39 40 daemonArgs = 41 [ "-a ${localAddress}" ] 42 ++ resolverArgs 43 ++ cfg.extraArgs; 44in 45 46{ 47 meta = { 48 maintainers = with maintainers; [ joachifm ]; 49 doc = ./dnscrypt-proxy.xml; 50 }; 51 52 options = { 53 # Before adding another option, consider whether it could 54 # equally well be passed via extraArgs. 55 56 services.dnscrypt-proxy = { 57 enable = mkOption { 58 default = false; 59 type = types.bool; 60 description = "Whether to enable the DNSCrypt client proxy"; 61 }; 62 63 localAddress = mkOption { 64 default = "127.0.0.1"; 65 type = types.str; 66 description = '' 67 Listen for DNS queries to relay on this address. The only reason to 68 change this from its default value is to proxy queries on behalf 69 of other machines (typically on the local network). 70 ''; 71 }; 72 73 localPort = mkOption { 74 default = 53; 75 type = types.int; 76 description = '' 77 Listen for DNS queries to relay on this port. The default value 78 assumes that the DNSCrypt proxy should relay DNS queries directly. 79 When running as a forwarder for another DNS client, set this option 80 to a different value; otherwise leave the default. 81 ''; 82 }; 83 84 resolverName = mkOption { 85 default = "random"; 86 example = "dnscrypt.eu-nl"; 87 type = types.nullOr types.str; 88 description = '' 89 The name of the DNSCrypt resolver to use, taken from 90 <filename>${resolverList}</filename>. The default is to 91 pick a random non-logging resolver that supports DNSSEC. 92 ''; 93 }; 94 95 customResolver = mkOption { 96 default = null; 97 description = '' 98 Use an unlisted resolver (e.g., a private DNSCrypt provider). For 99 advanced users only. If specified, this option takes precedence. 100 ''; 101 type = types.nullOr (types.submodule ({ ... }: { options = { 102 address = mkOption { 103 type = types.str; 104 description = "IP address"; 105 example = "208.67.220.220"; 106 }; 107 108 port = mkOption { 109 type = types.int; 110 description = "Port"; 111 default = 443; 112 }; 113 114 name = mkOption { 115 type = types.str; 116 description = "Fully qualified domain name"; 117 example = "2.dnscrypt-cert.example.com"; 118 }; 119 120 key = mkOption { 121 type = types.str; 122 description = "Public key"; 123 example = "B735:1140:206F:225D:3E2B:D822:D7FD:691E:A1C3:3CC8:D666:8D0C:BE04:BFAB:CA43:FB79"; 124 }; 125 }; })); 126 }; 127 128 extraArgs = mkOption { 129 default = []; 130 type = types.listOf types.str; 131 description = '' 132 Additional command-line arguments passed verbatim to the daemon. 133 See <citerefentry><refentrytitle>dnscrypt-proxy</refentrytitle> 134 <manvolnum>8</manvolnum></citerefentry> for details. 135 ''; 136 example = [ "-X libdcplugin_example_cache.so,--min-ttl=60" ]; 137 }; 138 }; 139 }; 140 141 config = mkIf cfg.enable (mkMerge [{ 142 assertions = [ 143 { assertion = (cfg.customResolver != null) || (cfg.resolverName != null); 144 message = "please configure upstream DNSCrypt resolver"; 145 } 146 ]; 147 148 # make man 8 dnscrypt-proxy work 149 environment.systemPackages = [ pkgs.dnscrypt-proxy ]; 150 151 users.users.dnscrypt-proxy = { 152 description = "dnscrypt-proxy daemon user"; 153 isSystemUser = true; 154 group = "dnscrypt-proxy"; 155 }; 156 users.groups.dnscrypt-proxy = {}; 157 158 systemd.sockets.dnscrypt-proxy = { 159 description = "dnscrypt-proxy listening socket"; 160 documentation = [ "man:dnscrypt-proxy(8)" ]; 161 162 wantedBy = [ "sockets.target" ]; 163 164 socketConfig = { 165 ListenStream = localAddress; 166 ListenDatagram = localAddress; 167 }; 168 }; 169 170 systemd.services.dnscrypt-proxy = { 171 description = "dnscrypt-proxy daemon"; 172 documentation = [ "man:dnscrypt-proxy(8)" ]; 173 174 before = [ "nss-lookup.target" ]; 175 after = [ "network.target" ]; 176 requires = [ "dnscrypt-proxy.socket "]; 177 178 serviceConfig = { 179 NonBlocking = "true"; 180 ExecStart = "${pkgs.dnscrypt-proxy}/bin/dnscrypt-proxy ${toString daemonArgs}"; 181 ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; 182 183 User = "dnscrypt-proxy"; 184 185 PrivateTmp = true; 186 PrivateDevices = true; 187 ProtectHome = true; 188 }; 189 }; 190 } 191 192 (mkIf config.security.apparmor.enable { 193 systemd.services.dnscrypt-proxy.after = [ "apparmor.service" ]; 194 195 security.apparmor.profiles = singleton (pkgs.writeText "apparmor-dnscrypt-proxy" '' 196 ${pkgs.dnscrypt-proxy}/bin/dnscrypt-proxy { 197 /dev/null rw, 198 /dev/random r, 199 /dev/urandom r, 200 201 /etc/passwd r, 202 /etc/group r, 203 ${config.environment.etc."nsswitch.conf".source} r, 204 205 ${getLib pkgs.glibc}/lib/*.so mr, 206 ${pkgs.tzdata}/share/zoneinfo/** r, 207 208 network inet stream, 209 network inet6 stream, 210 network inet dgram, 211 network inet6 dgram, 212 213 ${getLib pkgs.dnscrypt-proxy}/lib/dnscrypt-proxy/libdcplugin*.so mr, 214 215 ${getLib pkgs.gcc.cc}/lib/libssp.so.* mr, 216 ${getLib pkgs.libsodium}/lib/libsodium.so.* mr, 217 ${getLib pkgs.systemd}/lib/libsystemd.so.* mr, 218 ${getLib pkgs.utillinuxMinimal.out}/lib/libmount.so.* mr, 219 ${getLib pkgs.utillinuxMinimal.out}/lib/libblkid.so.* mr, 220 ${getLib pkgs.utillinuxMinimal.out}/lib/libuuid.so.* mr, 221 ${getLib pkgs.xz}/lib/liblzma.so.* mr, 222 ${getLib pkgs.libgcrypt}/lib/libgcrypt.so.* mr, 223 ${getLib pkgs.libgpgerror}/lib/libgpg-error.so.* mr, 224 ${getLib pkgs.libcap}/lib/libcap.so.* mr, 225 ${getLib pkgs.lz4}/lib/liblz4.so.* mr, 226 ${getLib pkgs.attr}/lib/libattr.so.* mr, # */ 227 228 ${resolverList} r, 229 230 /run/systemd/notify rw, 231 } 232 ''); 233 }) 234 235 (mkIf useUpstreamResolverList { 236 systemd.services.init-dnscrypt-proxy-statedir = { 237 description = "Initialize dnscrypt-proxy state directory"; 238 239 wantedBy = [ "dnscrypt-proxy.service" ]; 240 before = [ "dnscrypt-proxy.service" ]; 241 242 script = '' 243 mkdir -pv ${stateDirectory} 244 chown -c dnscrypt-proxy:dnscrypt-proxy ${stateDirectory} 245 cp -uv \ 246 ${pkgs.dnscrypt-proxy}/share/dnscrypt-proxy/dnscrypt-resolvers.csv \ 247 ${stateDirectory} 248 ''; 249 250 serviceConfig = { 251 Type = "oneshot"; 252 RemainAfterExit = true; 253 }; 254 }; 255 256 systemd.services.update-dnscrypt-resolvers = { 257 description = "Update list of DNSCrypt resolvers"; 258 259 requires = [ "init-dnscrypt-proxy-statedir.service" ]; 260 after = [ "init-dnscrypt-proxy-statedir.service" ]; 261 262 path = with pkgs; [ curl diffutils dnscrypt-proxy minisign ]; 263 script = '' 264 cd ${stateDirectory} 265 domain=raw.githubusercontent.com 266 get="curl -fSs --resolve $domain:443:$(hostip -r 8.8.8.8 $domain | head -1)" 267 $get -o dnscrypt-resolvers.csv.tmp \ 268 https://$domain/dyne/dnscrypt-proxy/master/dnscrypt-resolvers.csv 269 $get -o dnscrypt-resolvers.csv.minisig.tmp \ 270 https://$domain/dyne/dnscrypt-proxy/master/dnscrypt-resolvers.csv.minisig 271 mv dnscrypt-resolvers.csv.minisig{.tmp,} 272 if ! minisign -q -V -p ${upstreamResolverListPubKey} \ 273 -m dnscrypt-resolvers.csv.tmp -x dnscrypt-resolvers.csv.minisig ; then 274 echo "failed to verify resolver list!" >&2 275 exit 1 276 fi 277 [[ -f dnscrypt-resolvers.csv ]] && mv dnscrypt-resolvers.csv{,.old} 278 mv dnscrypt-resolvers.csv{.tmp,} 279 if cmp dnscrypt-resolvers.csv{,.old} ; then 280 echo "no change" 281 else 282 echo "resolver list updated" 283 fi 284 ''; 285 286 serviceConfig = { 287 PrivateTmp = true; 288 PrivateDevices = true; 289 ProtectHome = true; 290 ProtectSystem = "strict"; 291 ReadWritePaths = "${dirOf stateDirectory} ${stateDirectory}"; 292 SystemCallFilter = "~@mount"; 293 }; 294 }; 295 296 systemd.timers.update-dnscrypt-resolvers = { 297 wantedBy = [ "timers.target" ]; 298 timerConfig = { 299 OnBootSec = "5min"; 300 OnUnitActiveSec = "6h"; 301 }; 302 }; 303 }) 304 ]); 305 306 imports = [ 307 (mkRenamedOptionModule [ "services" "dnscrypt-proxy" "port" ] [ "services" "dnscrypt-proxy" "localPort" ]) 308 309 (mkChangedOptionModule 310 [ "services" "dnscrypt-proxy" "tcpOnly" ] 311 [ "services" "dnscrypt-proxy" "extraArgs" ] 312 (config: 313 let val = getAttrFromPath [ "services" "dnscrypt-proxy" "tcpOnly" ] config; in 314 optional val "-T")) 315 316 (mkChangedOptionModule 317 [ "services" "dnscrypt-proxy" "ephemeralKeys" ] 318 [ "services" "dnscrypt-proxy" "extraArgs" ] 319 (config: 320 let val = getAttrFromPath [ "services" "dnscrypt-proxy" "ephemeralKeys" ] config; in 321 optional val "-E")) 322 323 (mkRemovedOptionModule [ "services" "dnscrypt-proxy" "resolverList" ] '' 324 The current resolver listing from upstream is always used 325 unless a custom resolver is specified. 326 '') 327 ]; 328}