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.
56 '';
57 };
58 customResolver = mkOption {
59 default = null;
60 description = ''
61 Use a resolver not listed in the upstream list (e.g.,
62 a private DNSCrypt provider). For advanced users only.
63 If specified, this option takes precedence.
64 '';
65 type = types.nullOr (types.submodule ({ ... }: { options = {
66 address = mkOption {
67 type = types.str;
68 description = "Resolver IP address";
69 example = "208.67.220.220";
70 };
71 port = mkOption {
72 type = types.int;
73 description = "Resolver port";
74 default = 443;
75 };
76 name = mkOption {
77 type = types.str;
78 description = "Provider fully qualified domain name";
79 example = "2.dnscrypt-cert.opendns.com";
80 };
81 key = mkOption {
82 type = types.str;
83 description = "Provider public key";
84 example = "B735:1140:206F:225D:3E2B:D822:D7FD:691E:A1C3:3CC8:D666:8D0C:BE04:BFAB:CA43:FB79";
85 }; }; }));
86 };
87 tcpOnly = mkOption {
88 default = false;
89 type = types.bool;
90 description = ''
91 Force sending encrypted DNS queries to the upstream resolver
92 over TCP instead of UDP (on port 443). Enabling this option may
93 help circumvent filtering, but should not be used otherwise.
94 '';
95 };
96 };
97 };
98
99 config = mkIf cfg.enable {
100
101 assertions = [
102 { assertion = (cfg.customResolver != null) || (cfg.resolverName != null);
103 message = "please configure upstream DNSCrypt resolver";
104 }
105 ];
106
107 security.apparmor.profiles = mkIf apparmorEnabled (singleton (pkgs.writeText "apparmor-dnscrypt-proxy" ''
108 ${dnscrypt-proxy}/bin/dnscrypt-proxy {
109 /dev/null rw,
110 /dev/urandom r,
111
112 /etc/passwd r,
113 /etc/group r,
114 ${config.environment.etc."nsswitch.conf".source} r,
115
116 ${pkgs.glibc}/lib/*.so mr,
117 ${pkgs.tzdata}/share/zoneinfo/** r,
118
119 network inet stream,
120 network inet6 stream,
121 network inet dgram,
122 network inet6 dgram,
123
124 ${pkgs.gcc.cc}/lib/libssp.so.* mr,
125 ${pkgs.libsodium}/lib/libsodium.so.* mr,
126 ${pkgs.systemd}/lib/libsystemd.so.* mr,
127 ${pkgs.xz}/lib/liblzma.so.* mr,
128 ${pkgs.libgcrypt}/lib/libgcrypt.so.* mr,
129 ${pkgs.libgpgerror}/lib/libgpg-error.so.* mr,
130
131 ${resolverListFile} r,
132 }
133 ''));
134
135 users.extraUsers.dnscrypt-proxy = {
136 uid = config.ids.uids.dnscrypt-proxy;
137 description = "dnscrypt-proxy daemon user";
138 };
139 users.extraGroups.dnscrypt-proxy.gid = config.ids.gids.dnscrypt-proxy;
140
141 systemd.sockets.dnscrypt-proxy = {
142 description = "dnscrypt-proxy listening socket";
143 socketConfig = {
144 ListenStream = "${localAddress}";
145 ListenDatagram = "${localAddress}";
146 };
147 wantedBy = [ "sockets.target" ];
148 };
149
150 systemd.services.dnscrypt-proxy = {
151 description = "dnscrypt-proxy daemon";
152 after = [ "network.target" ] ++ optional apparmorEnabled "apparmor.service";
153 requires = [ "dnscrypt-proxy.socket "] ++ optional apparmorEnabled "apparmor.service";
154 serviceConfig = {
155 Type = "simple";
156 NonBlocking = "true";
157 ExecStart = "${dnscrypt-proxy}/bin/dnscrypt-proxy ${toString daemonArgs}";
158 User = "dnscrypt-proxy";
159 Group = "dnscrypt-proxy";
160 PrivateTmp = true;
161 PrivateDevices = true;
162 };
163 };
164 };
165}