at 17.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/jedisct1/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 users.users.dnscrypt-proxy = { 149 description = "dnscrypt-proxy daemon user"; 150 isSystemUser = true; 151 group = "dnscrypt-proxy"; 152 }; 153 users.groups.dnscrypt-proxy = {}; 154 155 systemd.sockets.dnscrypt-proxy = { 156 description = "dnscrypt-proxy listening socket"; 157 documentation = [ "man:dnscrypt-proxy(8)" ]; 158 159 wantedBy = [ "sockets.target" ]; 160 161 socketConfig = { 162 ListenStream = localAddress; 163 ListenDatagram = localAddress; 164 }; 165 }; 166 167 systemd.services.dnscrypt-proxy = { 168 description = "dnscrypt-proxy daemon"; 169 documentation = [ "man:dnscrypt-proxy(8)" ]; 170 171 before = [ "nss-lookup.target" ]; 172 after = [ "network.target" ]; 173 requires = [ "dnscrypt-proxy.socket "]; 174 175 serviceConfig = { 176 NonBlocking = "true"; 177 ExecStart = "${pkgs.dnscrypt-proxy}/bin/dnscrypt-proxy ${toString daemonArgs}"; 178 ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; 179 180 User = "dnscrypt-proxy"; 181 182 PrivateTmp = true; 183 PrivateDevices = true; 184 ProtectHome = true; 185 }; 186 }; 187 } 188 189 (mkIf config.security.apparmor.enable { 190 systemd.services.dnscrypt-proxy.after = [ "apparmor.service" ]; 191 192 security.apparmor.profiles = singleton (pkgs.writeText "apparmor-dnscrypt-proxy" '' 193 ${pkgs.dnscrypt-proxy}/bin/dnscrypt-proxy { 194 /dev/null rw, 195 /dev/urandom r, 196 197 /etc/passwd r, 198 /etc/group r, 199 ${config.environment.etc."nsswitch.conf".source} r, 200 201 ${getLib pkgs.glibc}/lib/*.so mr, 202 ${pkgs.tzdata}/share/zoneinfo/** r, 203 204 network inet stream, 205 network inet6 stream, 206 network inet dgram, 207 network inet6 dgram, 208 209 ${getLib pkgs.dnscrypt-proxy}/lib/dnscrypt-proxy/libdcplugin*.so mr, 210 211 ${getLib pkgs.gcc.cc}/lib/libssp.so.* mr, 212 ${getLib pkgs.libsodium}/lib/libsodium.so.* mr, 213 ${getLib pkgs.systemd}/lib/libsystemd.so.* mr, 214 ${getLib pkgs.xz}/lib/liblzma.so.* mr, 215 ${getLib pkgs.libgcrypt}/lib/libgcrypt.so.* mr, 216 ${getLib pkgs.libgpgerror}/lib/libgpg-error.so.* mr, 217 ${getLib pkgs.libcap}/lib/libcap.so.* mr, 218 ${getLib pkgs.lz4}/lib/liblz4.so.* mr, 219 ${getLib pkgs.attr}/lib/libattr.so.* mr, # */ 220 221 ${resolverList} r, 222 223 /run/systemd/notify rw, 224 } 225 ''); 226 }) 227 228 (mkIf useUpstreamResolverList { 229 systemd.services.init-dnscrypt-proxy-statedir = { 230 description = "Initialize dnscrypt-proxy state directory"; 231 232 wantedBy = [ "dnscrypt-proxy.service" ]; 233 before = [ "dnscrypt-proxy.service" ]; 234 235 script = '' 236 mkdir -pv ${stateDirectory} 237 chown -c dnscrypt-proxy:dnscrypt-proxy ${stateDirectory} 238 cp -uv \ 239 ${pkgs.dnscrypt-proxy}/share/dnscrypt-proxy/dnscrypt-resolvers.csv \ 240 ${stateDirectory} 241 ''; 242 243 serviceConfig = { 244 Type = "oneshot"; 245 RemainAfterExit = true; 246 }; 247 }; 248 249 systemd.services.update-dnscrypt-resolvers = { 250 description = "Update list of DNSCrypt resolvers"; 251 252 requires = [ "init-dnscrypt-proxy-statedir.service" ]; 253 after = [ "init-dnscrypt-proxy-statedir.service" ]; 254 255 path = with pkgs; [ curl diffutils dnscrypt-proxy minisign ]; 256 script = '' 257 cd ${stateDirectory} 258 domain=raw.githubusercontent.com 259 get="curl -fSs --resolve $domain:443:$(hostip -r 8.8.8.8 $domain | head -1)" 260 $get -o dnscrypt-resolvers.csv.tmp \ 261 https://$domain/jedisct1/dnscrypt-proxy/master/dnscrypt-resolvers.csv 262 $get -o dnscrypt-resolvers.csv.minisig.tmp \ 263 https://$domain/jedisct1/dnscrypt-proxy/master/dnscrypt-resolvers.csv.minisig 264 mv dnscrypt-resolvers.csv.minisig{.tmp,} 265 if ! minisign -q -V -p ${upstreamResolverListPubKey} \ 266 -m dnscrypt-resolvers.csv.tmp -x dnscrypt-resolvers.csv.minisig ; then 267 echo "failed to verify resolver list!" >&2 268 exit 1 269 fi 270 [[ -f dnscrypt-resolvers.csv ]] && mv dnscrypt-resolvers.csv{,.old} 271 mv dnscrypt-resolvers.csv{.tmp,} 272 if cmp dnscrypt-resolvers.csv{,.old} ; then 273 echo "no change" 274 else 275 echo "resolver list updated" 276 fi 277 ''; 278 279 serviceConfig = { 280 PrivateTmp = true; 281 PrivateDevices = true; 282 ProtectHome = true; 283 ProtectSystem = "strict"; 284 ReadWritePaths = "${dirOf stateDirectory} ${stateDirectory}"; 285 SystemCallFilter = "~@mount"; 286 }; 287 }; 288 289 systemd.timers.update-dnscrypt-resolvers = { 290 wantedBy = [ "timers.target" ]; 291 timerConfig = { 292 OnBootSec = "5min"; 293 OnUnitActiveSec = "6h"; 294 }; 295 }; 296 }) 297 ]); 298 299 imports = [ 300 (mkRenamedOptionModule [ "services" "dnscrypt-proxy" "port" ] [ "services" "dnscrypt-proxy" "localPort" ]) 301 302 (mkChangedOptionModule 303 [ "services" "dnscrypt-proxy" "tcpOnly" ] 304 [ "services" "dnscrypt-proxy" "extraArgs" ] 305 (config: 306 let val = getAttrFromPath [ "services" "dnscrypt-proxy" "tcpOnly" ] config; in 307 optional val "-T")) 308 309 (mkChangedOptionModule 310 [ "services" "dnscrypt-proxy" "ephemeralKeys" ] 311 [ "services" "dnscrypt-proxy" "extraArgs" ] 312 (config: 313 let val = getAttrFromPath [ "services" "dnscrypt-proxy" "ephemeralKeys" ] config; in 314 optional val "-E")) 315 316 (mkRemovedOptionModule [ "services" "dnscrypt-proxy" "resolverList" ] '' 317 The current resolver listing from upstream is always used 318 unless a custom resolver is specified. 319 '') 320 ]; 321}