1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6 cfg = config.services.tor;
7 torDirectory = "/var/lib/tor";
8
9 opt = name: value: optionalString (value != null) "${name} ${value}";
10 optint = name: value: optionalString (value != 0) "${name} ${toString value}";
11
12 torRc = ''
13 User tor
14 DataDirectory ${torDirectory}
15
16 ${optint "ControlPort" cfg.controlPort}
17 ''
18 # Client connection config
19 + optionalString cfg.client.enable ''
20 SOCKSPort ${cfg.client.socksListenAddress} IsolateDestAddr
21 SOCKSPort ${cfg.client.socksListenAddressFaster}
22 ${opt "SocksPolicy" cfg.client.socksPolicy}
23 ''
24 # Relay config
25 + optionalString cfg.relay.enable ''
26 ORPort ${cfg.relay.portSpec}
27 ${opt "Nickname" cfg.relay.nickname}
28 ${opt "ContactInfo" cfg.relay.contactInfo}
29
30 ${optint "RelayBandwidthRate" cfg.relay.bandwidthRate}
31 ${optint "RelayBandwidthBurst" cfg.relay.bandwidthBurst}
32 ${opt "AccountingMax" cfg.relay.accountingMax}
33 ${opt "AccountingStart" cfg.relay.accountingStart}
34
35 ${if cfg.relay.isExit then
36 opt "ExitPolicy" cfg.relay.exitPolicy
37 else
38 "ExitPolicy reject *:*"}
39
40 ${optionalString cfg.relay.isBridge ''
41 BridgeRelay 1
42 ServerTransportPlugin obfs2,obfs3 exec ${pkgs.pythonPackages.obfsproxy}/bin/obfsproxy managed
43 ''}
44 ''
45 + cfg.extraConfig;
46
47 torRcFile = pkgs.writeText "torrc" torRc;
48in
49{
50 options = {
51 services.tor = {
52 enable = mkOption {
53 type = types.bool;
54 default = false;
55 description = ''
56 Enable the Tor daemon. By default, the daemon is run without
57 relay, exit, bridge or client connectivity.
58 '';
59 };
60
61 extraConfig = mkOption {
62 type = types.lines;
63 default = "";
64 description = ''
65 Extra configuration. Contents will be added verbatim to the
66 configuration file at the end.
67 '';
68 };
69
70 controlPort = mkOption {
71 type = types.int;
72 default = 0;
73 example = 9051;
74 description = ''
75 If set, Tor will accept connections on the specified port
76 and allow them to control the tor process.
77 '';
78 };
79
80 client = {
81 enable = mkOption {
82 type = types.bool;
83 default = false;
84 description = ''
85 Whether to enable Tor daemon to route application
86 connections. You might want to disable this if you plan
87 running a dedicated Tor relay.
88 '';
89 };
90
91 socksListenAddress = mkOption {
92 type = types.str;
93 default = "127.0.0.1:9050";
94 example = "192.168.0.1:9100";
95 description = ''
96 Bind to this address to listen for connections from
97 Socks-speaking applications. Provides strong circuit
98 isolation, separate circuit per IP address.
99 '';
100 };
101
102 socksListenAddressFaster = mkOption {
103 type = types.str;
104 default = "127.0.0.1:9063";
105 example = "192.168.0.1:9101";
106 description = ''
107 Bind to this address to listen for connections from
108 Socks-speaking applications. Same as socksListenAddress
109 but uses weaker circuit isolation to provide performance
110 suitable for a web browser.
111 '';
112 };
113
114 socksPolicy = mkOption {
115 type = types.nullOr types.str;
116 default = null;
117 example = "accept 192.168.0.0/16, reject *";
118 description = ''
119 Entry policies to allow/deny SOCKS requests based on IP
120 address. First entry that matches wins. If no SocksPolicy
121 is set, we accept all (and only) requests from
122 SocksListenAddress.
123 '';
124 };
125
126 privoxy.enable = mkOption {
127 default = true;
128 description = ''
129 Whether to enable and configure the system Privoxy to use Tor's
130 faster port, suitable for HTTP.
131
132 To have anonymity, protocols need to be scrubbed of identifying
133 information, and this can be accomplished for HTTP by Privoxy.
134
135 Privoxy can also be useful for KDE torification. A good setup would be:
136 setting SOCKS proxy to the default Tor port, providing maximum
137 circuit isolation where possible; and setting HTTP proxy to Privoxy
138 to route HTTP traffic over faster, but less isolated port.
139 '';
140 };
141 };
142
143 relay = {
144 enable = mkOption {
145 type = types.bool;
146 default = false;
147 description = ''
148 Whether to enable relaying TOR traffic for others.
149
150 See https://www.torproject.org/docs/tor-doc-relay for details.
151 '';
152 };
153
154 isBridge = mkOption {
155 type = types.bool;
156 default = false;
157 description = ''
158 Bridge relays (or "bridges") are Tor relays that aren't
159 listed in the main directory. Since there is no complete
160 public list of them, even if an ISP is filtering
161 connections to all the known Tor relays, they probably
162 won't be able to block all the bridges.
163
164 A bridge relay can't be an exit relay.
165
166 You need to set relay.enable to true for this option to
167 take effect.
168
169 The bridge is set up with an obfuscated transport proxy.
170
171 See https://www.torproject.org/bridges.html.en for more info.
172 '';
173 };
174
175 isExit = mkOption {
176 type = types.bool;
177 default = false;
178 description = ''
179 An exit relay allows Tor users to access regular Internet
180 services.
181
182 Unlike running a non-exit relay, running an exit relay may
183 expose you to abuse complaints. See
184 https://www.torproject.org/faq.html.en#ExitPolicies for
185 more info.
186
187 You can specify which services Tor users may access via
188 your exit relay using exitPolicy option.
189 '';
190 };
191
192 nickname = mkOption {
193 type = types.str;
194 default = "anonymous";
195 description = ''
196 A unique handle for your TOR relay.
197 '';
198 };
199
200 contactInfo = mkOption {
201 type = types.nullOr types.str;
202 default = null;
203 example = "admin@relay.com";
204 description = ''
205 Contact information for the relay owner (e.g. a mail
206 address and GPG key ID).
207 '';
208 };
209
210 accountingMax = mkOption {
211 type = types.nullOr types.str;
212 default = null;
213 example = "450 GBytes";
214 description = ''
215 Specify maximum bandwidth allowed during an accounting
216 period. This allows you to limit overall tor bandwidth
217 over some time period. See the
218 <literal>AccountingMax</literal> option by looking at the
219 tor manual (<literal>man tor</literal>) for more.
220
221 Note this limit applies individually to upload and
222 download; if you specify <literal>"500 GBytes"</literal>
223 here, then you may transfer up to 1 TBytes of overall
224 bandwidth (500 GB upload, 500 GB download).
225 '';
226 };
227
228 accountingStart = mkOption {
229 type = types.nullOr types.str;
230 default = null;
231 example = "month 1 1:00";
232 description = ''
233 Specify length of an accounting period. This allows you to
234 limit overall tor bandwidth over some time period. See the
235 <literal>AccountingStart</literal> option by looking at
236 the tor manual (<literal>man tor</literal>) for more.
237 '';
238 };
239
240 bandwidthRate = mkOption {
241 type = types.int;
242 default = 0;
243 example = 100;
244 description = ''
245 Specify this to limit the bandwidth usage of relayed (server)
246 traffic. Your own traffic is still unthrottled. Units: bytes/second.
247 '';
248 };
249
250 bandwidthBurst = mkOption {
251 type = types.int;
252 default = cfg.relay.bandwidthRate;
253 example = 200;
254 description = ''
255 Specify this to allow bursts of the bandwidth usage of relayed (server)
256 traffic. The average usage will still be as specified in relayBandwidthRate.
257 Your own traffic is still unthrottled. Units: bytes/second.
258 '';
259 };
260
261 portSpec = mkOption {
262 type = types.str;
263 example = "143";
264 description = ''
265 What port to advertise for Tor connections. This corresponds
266 to the <literal>ORPort</literal> section in the Tor manual; see
267 <literal>man tor</literal> for more details.
268
269 At a minimum, you should just specify the port for the
270 relay to listen on; a common one like 143, 22, 80, or 443
271 to help Tor users who may have very restrictive port-based
272 firewalls.
273 '';
274 };
275
276 exitPolicy = mkOption {
277 type = types.nullOr types.str;
278 default = null;
279 example = "accept *:6660-6667,reject *:*";
280 description = ''
281 A comma-separated list of exit policies. They're
282 considered first to last, and the first match wins. If you
283 want to _replace_ the default exit policy, end this with
284 either a reject *:* or an accept *:*. Otherwise, you're
285 _augmenting_ (prepending to) the default exit
286 policy. Leave commented to just use the default, which is
287 available in the man page or at
288 https://www.torproject.org/documentation.html
289
290 Look at https://www.torproject.org/faq-abuse.html#TypicalAbuses
291 for issues you might encounter if you use the default exit policy.
292
293 If certain IPs and ports are blocked externally, e.g. by
294 your firewall, you should update your exit policy to
295 reflect this -- otherwise Tor users will be told that
296 those destinations are down.
297 '';
298 };
299 };
300 };
301 };
302
303 config = mkIf cfg.enable {
304 assertions = singleton
305 { message = "Can't be both an exit and a bridge relay at the same time";
306 assertion =
307 cfg.relay.enable -> !(cfg.relay.isBridge && cfg.relay.isExit);
308 };
309
310 users.extraGroups.tor.gid = config.ids.gids.tor;
311 users.extraUsers.tor =
312 { description = "Tor Daemon User";
313 createHome = true;
314 home = torDirectory;
315 group = "tor";
316 uid = config.ids.uids.tor;
317 };
318
319 systemd.services.tor =
320 { description = "Tor Daemon";
321 path = [ pkgs.tor ];
322
323 wantedBy = [ "multi-user.target" ];
324 after = [ "network.target" ];
325 restartTriggers = [ torRcFile ];
326
327 # Translated from the upstream contrib/dist/tor.service.in
328 serviceConfig =
329 { Type = "simple";
330 ExecStartPre = "${pkgs.tor}/bin/tor -f ${torRcFile} --verify-config";
331 ExecStart = "${pkgs.tor}/bin/tor -f ${torRcFile} --RunAsDaemon 0";
332 ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
333 KillSignal = "SIGINT";
334 TimeoutSec = 30;
335 Restart = "on-failure";
336 LimitNOFILE = 32768;
337
338 # Hardening
339 # Note: DevicePolicy is set to 'closed', although the
340 # minimal permissions are really:
341 # DeviceAllow /dev/null rw
342 # DeviceAllow /dev/urandom r
343 # .. but we can't specify DeviceAllow multiple times. 'closed'
344 # is close enough.
345 PrivateTmp = "yes";
346 DevicePolicy = "closed";
347 InaccessibleDirectories = "/home";
348 ReadOnlyDirectories = "/";
349 ReadWriteDirectories = torDirectory;
350 NoNewPrivileges = "yes";
351 };
352 };
353
354 environment.systemPackages = [ pkgs.tor ];
355
356 services.privoxy = mkIf (cfg.client.enable && cfg.client.privoxy.enable) {
357 enable = true;
358 extraConfig = ''
359 forward-socks4a / ${cfg.client.socksListenAddressFaster} .
360 toggle 1
361 enable-remote-toggle 0
362 enable-edit-actions 0
363 enable-remote-http-toggle 0
364 '';
365 };
366 };
367}