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}