1{ config, lib, options, pkgs, ... }:
2
3with builtins;
4with lib;
5
6let
7 cfg = config.services.tor;
8 opt = options.services.tor;
9 stateDir = "/var/lib/tor";
10 runDir = "/run/tor";
11 descriptionGeneric = option: ''
12 See [torrc manual](https://2019.www.torproject.org/docs/tor-manual.html.en#${option}).
13 '';
14 bindsPrivilegedPort =
15 any (p0:
16 let p1 = if p0 ? "port" then p0.port else p0; in
17 if p1 == "auto" then false
18 else let p2 = if isInt p1 then p1 else toInt p1; in
19 p1 != null && 0 < p2 && p2 < 1024)
20 (flatten [
21 cfg.settings.ORPort
22 cfg.settings.DirPort
23 cfg.settings.DNSPort
24 cfg.settings.ExtORPort
25 cfg.settings.HTTPTunnelPort
26 cfg.settings.NATDPort
27 cfg.settings.SOCKSPort
28 cfg.settings.TransPort
29 ]);
30 optionBool = optionName: mkOption {
31 type = with types; nullOr bool;
32 default = null;
33 description = lib.mdDoc (descriptionGeneric optionName);
34 };
35 optionInt = optionName: mkOption {
36 type = with types; nullOr int;
37 default = null;
38 description = lib.mdDoc (descriptionGeneric optionName);
39 };
40 optionString = optionName: mkOption {
41 type = with types; nullOr str;
42 default = null;
43 description = lib.mdDoc (descriptionGeneric optionName);
44 };
45 optionStrings = optionName: mkOption {
46 type = with types; listOf str;
47 default = [];
48 description = lib.mdDoc (descriptionGeneric optionName);
49 };
50 optionAddress = mkOption {
51 type = with types; nullOr str;
52 default = null;
53 example = "0.0.0.0";
54 description = lib.mdDoc ''
55 IPv4 or IPv6 (if between brackets) address.
56 '';
57 };
58 optionUnix = mkOption {
59 type = with types; nullOr path;
60 default = null;
61 description = lib.mdDoc ''
62 Unix domain socket path to use.
63 '';
64 };
65 optionPort = mkOption {
66 type = with types; nullOr (oneOf [port (enum ["auto"])]);
67 default = null;
68 };
69 optionPorts = optionName: mkOption {
70 type = with types; listOf port;
71 default = [];
72 description = lib.mdDoc (descriptionGeneric optionName);
73 };
74 optionIsolablePort = with types; oneOf [
75 port (enum ["auto"])
76 (submodule ({config, ...}: {
77 options = {
78 addr = optionAddress;
79 port = optionPort;
80 flags = optionFlags;
81 SessionGroup = mkOption { type = nullOr int; default = null; };
82 } // genAttrs isolateFlags (name: mkOption { type = types.bool; default = false; });
83 config = {
84 flags = filter (name: config.${name} == true) isolateFlags ++
85 optional (config.SessionGroup != null) "SessionGroup=${toString config.SessionGroup}";
86 };
87 }))
88 ];
89 optionIsolablePorts = optionName: mkOption {
90 default = [];
91 type = with types; either optionIsolablePort (listOf optionIsolablePort);
92 description = lib.mdDoc (descriptionGeneric optionName);
93 };
94 isolateFlags = [
95 "IsolateClientAddr"
96 "IsolateClientProtocol"
97 "IsolateDestAddr"
98 "IsolateDestPort"
99 "IsolateSOCKSAuth"
100 "KeepAliveIsolateSOCKSAuth"
101 ];
102 optionSOCKSPort = doConfig: let
103 flags = [
104 "CacheDNS" "CacheIPv4DNS" "CacheIPv6DNS" "GroupWritable" "IPv6Traffic"
105 "NoDNSRequest" "NoIPv4Traffic" "NoOnionTraffic" "OnionTrafficOnly"
106 "PreferIPv6" "PreferIPv6Automap" "PreferSOCKSNoAuth" "UseDNSCache"
107 "UseIPv4Cache" "UseIPv6Cache" "WorldWritable"
108 ] ++ isolateFlags;
109 in with types; oneOf [
110 port (submodule ({config, ...}: {
111 options = {
112 unix = optionUnix;
113 addr = optionAddress;
114 port = optionPort;
115 flags = optionFlags;
116 SessionGroup = mkOption { type = nullOr int; default = null; };
117 } // genAttrs flags (name: mkOption { type = types.bool; default = false; });
118 config = mkIf doConfig { # Only add flags in SOCKSPort to avoid duplicates
119 flags = filter (name: config.${name} == true) flags ++
120 optional (config.SessionGroup != null) "SessionGroup=${toString config.SessionGroup}";
121 };
122 }))
123 ];
124 optionFlags = mkOption {
125 type = with types; listOf str;
126 default = [];
127 };
128 optionORPort = optionName: mkOption {
129 default = [];
130 example = 443;
131 type = with types; oneOf [port (enum ["auto"]) (listOf (oneOf [
132 port
133 (enum ["auto"])
134 (submodule ({config, ...}:
135 let flags = [ "IPv4Only" "IPv6Only" "NoAdvertise" "NoListen" ];
136 in {
137 options = {
138 addr = optionAddress;
139 port = optionPort;
140 flags = optionFlags;
141 } // genAttrs flags (name: mkOption { type = types.bool; default = false; });
142 config = {
143 flags = filter (name: config.${name} == true) flags;
144 };
145 }))
146 ]))];
147 description = lib.mdDoc (descriptionGeneric optionName);
148 };
149 optionBandwidth = optionName: mkOption {
150 type = with types; nullOr (either int str);
151 default = null;
152 description = lib.mdDoc (descriptionGeneric optionName);
153 };
154 optionPath = optionName: mkOption {
155 type = with types; nullOr path;
156 default = null;
157 description = lib.mdDoc (descriptionGeneric optionName);
158 };
159
160 mkValueString = k: v:
161 if v == null then ""
162 else if isBool v then
163 (if v then "1" else "0")
164 else if v ? "unix" && v.unix != null then
165 "unix:"+v.unix +
166 optionalString (v ? "flags") (" " + concatStringsSep " " v.flags)
167 else if v ? "port" && v.port != null then
168 optionalString (v ? "addr" && v.addr != null) "${v.addr}:" +
169 toString v.port +
170 optionalString (v ? "flags") (" " + concatStringsSep " " v.flags)
171 else if k == "ServerTransportPlugin" then
172 optionalString (v.transports != []) "${concatStringsSep "," v.transports} exec ${v.exec}"
173 else if k == "HidServAuth" then
174 v.onion + " " + v.auth
175 else generators.mkValueStringDefault {} v;
176 genTorrc = settings:
177 generators.toKeyValue {
178 listsAsDuplicateKeys = true;
179 mkKeyValue = k: generators.mkKeyValueDefault { mkValueString = mkValueString k; } " " k;
180 }
181 (lib.mapAttrs (k: v:
182 # Not necesssary, but prettier rendering
183 if elem k [ "AutomapHostsSuffixes" "DirPolicy" "ExitPolicy" "SocksPolicy" ]
184 && v != []
185 then concatStringsSep "," v
186 else v)
187 (lib.filterAttrs (k: v: !(v == null || v == ""))
188 settings));
189 torrc = pkgs.writeText "torrc" (
190 genTorrc cfg.settings +
191 concatStrings (mapAttrsToList (name: onion:
192 "HiddenServiceDir ${onion.path}\n" +
193 genTorrc onion.settings) cfg.relay.onionServices)
194 );
195in
196{
197 imports = [
198 (mkRenamedOptionModule [ "services" "tor" "client" "dns" "automapHostsSuffixes" ] [ "services" "tor" "settings" "AutomapHostsSuffixes" ])
199 (mkRemovedOptionModule [ "services" "tor" "client" "dns" "isolationOptions" ] "Use services.tor.settings.DNSPort instead.")
200 (mkRemovedOptionModule [ "services" "tor" "client" "dns" "listenAddress" ] "Use services.tor.settings.DNSPort instead.")
201 (mkRemovedOptionModule [ "services" "tor" "client" "privoxy" "enable" ] "Use services.privoxy.enable and services.privoxy.enableTor instead.")
202 (mkRemovedOptionModule [ "services" "tor" "client" "socksIsolationOptions" ] "Use services.tor.settings.SOCKSPort instead.")
203 (mkRemovedOptionModule [ "services" "tor" "client" "socksListenAddressFaster" ] "Use services.tor.settings.SOCKSPort instead.")
204 (mkRenamedOptionModule [ "services" "tor" "client" "socksPolicy" ] [ "services" "tor" "settings" "SocksPolicy" ])
205 (mkRemovedOptionModule [ "services" "tor" "client" "transparentProxy" "isolationOptions" ] "Use services.tor.settings.TransPort instead.")
206 (mkRemovedOptionModule [ "services" "tor" "client" "transparentProxy" "listenAddress" ] "Use services.tor.settings.TransPort instead.")
207 (mkRenamedOptionModule [ "services" "tor" "controlPort" ] [ "services" "tor" "settings" "ControlPort" ])
208 (mkRemovedOptionModule [ "services" "tor" "extraConfig" ] "Please use services.tor.settings instead.")
209 (mkRenamedOptionModule [ "services" "tor" "hiddenServices" ] [ "services" "tor" "relay" "onionServices" ])
210 (mkRenamedOptionModule [ "services" "tor" "relay" "accountingMax" ] [ "services" "tor" "settings" "AccountingMax" ])
211 (mkRenamedOptionModule [ "services" "tor" "relay" "accountingStart" ] [ "services" "tor" "settings" "AccountingStart" ])
212 (mkRenamedOptionModule [ "services" "tor" "relay" "address" ] [ "services" "tor" "settings" "Address" ])
213 (mkRenamedOptionModule [ "services" "tor" "relay" "bandwidthBurst" ] [ "services" "tor" "settings" "BandwidthBurst" ])
214 (mkRenamedOptionModule [ "services" "tor" "relay" "bandwidthRate" ] [ "services" "tor" "settings" "BandwidthRate" ])
215 (mkRenamedOptionModule [ "services" "tor" "relay" "bridgeTransports" ] [ "services" "tor" "settings" "ServerTransportPlugin" "transports" ])
216 (mkRenamedOptionModule [ "services" "tor" "relay" "contactInfo" ] [ "services" "tor" "settings" "ContactInfo" ])
217 (mkRenamedOptionModule [ "services" "tor" "relay" "exitPolicy" ] [ "services" "tor" "settings" "ExitPolicy" ])
218 (mkRemovedOptionModule [ "services" "tor" "relay" "isBridge" ] "Use services.tor.relay.role instead.")
219 (mkRemovedOptionModule [ "services" "tor" "relay" "isExit" ] "Use services.tor.relay.role instead.")
220 (mkRenamedOptionModule [ "services" "tor" "relay" "nickname" ] [ "services" "tor" "settings" "Nickname" ])
221 (mkRenamedOptionModule [ "services" "tor" "relay" "port" ] [ "services" "tor" "settings" "ORPort" ])
222 (mkRenamedOptionModule [ "services" "tor" "relay" "portSpec" ] [ "services" "tor" "settings" "ORPort" ])
223 ];
224
225 options = {
226 services.tor = {
227 enable = mkEnableOption (lib.mdDoc ''Tor daemon.
228 By default, the daemon is run without
229 relay, exit, bridge or client connectivity'');
230
231 openFirewall = mkEnableOption (lib.mdDoc "opening of the relay port(s) in the firewall");
232
233 package = mkOption {
234 type = types.package;
235 default = pkgs.tor;
236 defaultText = literalExpression "pkgs.tor";
237 description = lib.mdDoc "Tor package to use.";
238 };
239
240 enableGeoIP = mkEnableOption (lib.mdDoc ''use of GeoIP databases.
241 Disabling this will disable by-country statistics for bridges and relays
242 and some client and third-party software functionality'') // { default = true; };
243
244 controlSocket.enable = mkEnableOption (lib.mdDoc ''control socket,
245 created in `${runDir}/control`'');
246
247 client = {
248 enable = mkEnableOption (lib.mdDoc ''the routing of application connections.
249 You might want to disable this if you plan running a dedicated Tor relay'');
250
251 transparentProxy.enable = mkEnableOption (lib.mdDoc "transparent proxy");
252 dns.enable = mkEnableOption (lib.mdDoc "DNS resolver");
253
254 socksListenAddress = mkOption {
255 type = optionSOCKSPort false;
256 default = {addr = "127.0.0.1"; port = 9050; IsolateDestAddr = true;};
257 example = {addr = "192.168.0.1"; port = 9090; IsolateDestAddr = true;};
258 description = lib.mdDoc ''
259 Bind to this address to listen for connections from
260 Socks-speaking applications.
261 '';
262 };
263
264 onionServices = mkOption {
265 description = lib.mdDoc (descriptionGeneric "HiddenServiceDir");
266 default = {};
267 example = {
268 "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" = {
269 clientAuthorizations = ["/run/keys/tor/alice.prv.x25519"];
270 };
271 };
272 type = types.attrsOf (types.submodule ({name, config, ...}: {
273 options.clientAuthorizations = mkOption {
274 description = lib.mdDoc ''
275 Clients' authorizations for a v3 onion service,
276 as a list of files containing each one private key, in the format:
277 ```
278 descriptor:x25519:<base32-private-key>
279 ```
280 ${descriptionGeneric "_client_authorization"}
281 '';
282 type = with types; listOf path;
283 default = [];
284 example = ["/run/keys/tor/alice.prv.x25519"];
285 };
286 }));
287 };
288 };
289
290 relay = {
291 enable = mkEnableOption (lib.mdDoc "tor relaying") // {
292 description = lib.mdDoc ''
293 Whether to enable relaying of Tor traffic for others.
294
295 See <https://www.torproject.org/docs/tor-doc-relay>
296 for details.
297
298 Setting this to true requires setting
299 {option}`services.tor.relay.role`
300 and
301 {option}`services.tor.settings.ORPort`
302 options.
303 '';
304 };
305
306 role = mkOption {
307 type = types.enum [ "exit" "relay" "bridge" "private-bridge" ];
308 description = lib.mdDoc ''
309 Your role in Tor network. There're several options:
310
311 - `exit`:
312 An exit relay. This allows Tor users to access regular
313 Internet services through your public IP.
314
315 You can specify which services Tor users may access via
316 your exit relay using {option}`settings.ExitPolicy` option.
317
318 - `relay`:
319 Regular relay. This allows Tor users to relay onion
320 traffic to other Tor nodes, but not to public
321 Internet.
322
323 See
324 <https://www.torproject.org/docs/tor-doc-relay.html.en>
325 for more info.
326
327 - `bridge`:
328 Regular bridge. Works like a regular relay, but
329 doesn't list you in the public relay directory and
330 hides your Tor node behind obfs4proxy.
331
332 Using this option will make Tor advertise your bridge
333 to users through various mechanisms like
334 <https://bridges.torproject.org/>, though.
335
336 See <https://www.torproject.org/docs/bridges.html.en>
337 for more info.
338
339 - `private-bridge`:
340 Private bridge. Works like regular bridge, but does
341 not advertise your node in any way.
342
343 Using this role means that you won't contribute to Tor
344 network in any way unless you advertise your node
345 yourself in some way.
346
347 Use this if you want to run a private bridge, for
348 example because you'll give out your bridge addr
349 manually to your friends.
350
351 Switching to this role after measurable time in
352 "bridge" role is pretty useless as some Tor users
353 would have learned about your node already. In the
354 latter case you can still change
355 {option}`port` option.
356
357 See <https://www.torproject.org/docs/bridges.html.en>
358 for more info.
359
360 ::: {.important}
361 Running an exit relay may expose you to abuse
362 complaints. See
363 <https://www.torproject.org/faq.html.en#ExitPolicies>
364 for more info.
365 :::
366
367 ::: {.important}
368 Note that some misconfigured and/or disrespectful
369 towards privacy sites will block you even if your
370 relay is not an exit relay. That is, just being listed
371 in a public relay directory can have unwanted
372 consequences.
373
374 Which means you might not want to use
375 this role if you browse public Internet from the same
376 network as your relay, unless you want to write
377 e-mails to those sites (you should!).
378 :::
379
380 ::: {.important}
381 WARNING: THE FOLLOWING PARAGRAPH IS NOT LEGAL ADVICE.
382 Consult with your lawyer when in doubt.
383
384 The `bridge` role should be safe to use in most situations
385 (unless the act of forwarding traffic for others is
386 a punishable offence under your local laws, which
387 would be pretty insane as it would make ISP illegal).
388 :::
389 '';
390 };
391
392 onionServices = mkOption {
393 description = lib.mdDoc (descriptionGeneric "HiddenServiceDir");
394 default = {};
395 example = {
396 "example.org/www" = {
397 map = [ 80 ];
398 authorizedClients = [
399 "descriptor:x25519:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
400 ];
401 };
402 };
403 type = types.attrsOf (types.submodule ({name, config, ...}: {
404 options.path = mkOption {
405 type = types.path;
406 description = lib.mdDoc ''
407 Path where to store the data files of the hidden service.
408 If the {option}`secretKey` is null
409 this defaults to `${stateDir}/onion/$onion`,
410 otherwise to `${runDir}/onion/$onion`.
411 '';
412 };
413 options.secretKey = mkOption {
414 type = with types; nullOr path;
415 default = null;
416 example = "/run/keys/tor/onion/expyuzz4wqqyqhjn/hs_ed25519_secret_key";
417 description = lib.mdDoc ''
418 Secret key of the onion service.
419 If null, Tor reuses any preexisting secret key (in {option}`path`)
420 or generates a new one.
421 The associated public key and hostname are deterministically regenerated
422 from this file if they do not exist.
423 '';
424 };
425 options.authorizeClient = mkOption {
426 description = lib.mdDoc (descriptionGeneric "HiddenServiceAuthorizeClient");
427 default = null;
428 type = types.nullOr (types.submodule ({...}: {
429 options = {
430 authType = mkOption {
431 type = types.enum [ "basic" "stealth" ];
432 description = lib.mdDoc ''
433 Either `"basic"` for a general-purpose authorization protocol
434 or `"stealth"` for a less scalable protocol
435 that also hides service activity from unauthorized clients.
436 '';
437 };
438 clientNames = mkOption {
439 type = with types; nonEmptyListOf (strMatching "[A-Za-z0-9+-_]+");
440 description = lib.mdDoc ''
441 Only clients that are listed here are authorized to access the hidden service.
442 Generated authorization data can be found in {file}`${stateDir}/onion/$name/hostname`.
443 Clients need to put this authorization data in their configuration file using
444 [](#opt-services.tor.settings.HidServAuth).
445 '';
446 };
447 };
448 }));
449 };
450 options.authorizedClients = mkOption {
451 description = lib.mdDoc ''
452 Authorized clients for a v3 onion service,
453 as a list of public key, in the format:
454 ```
455 descriptor:x25519:<base32-public-key>
456 ```
457 ${descriptionGeneric "_client_authorization"}
458 '';
459 type = with types; listOf str;
460 default = [];
461 example = ["descriptor:x25519:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"];
462 };
463 options.map = mkOption {
464 description = lib.mdDoc (descriptionGeneric "HiddenServicePort");
465 type = with types; listOf (oneOf [
466 port (submodule ({...}: {
467 options = {
468 port = optionPort;
469 target = mkOption {
470 default = null;
471 type = nullOr (submodule ({...}: {
472 options = {
473 unix = optionUnix;
474 addr = optionAddress;
475 port = optionPort;
476 };
477 }));
478 };
479 };
480 }))
481 ]);
482 apply = map (v: if isInt v then {port=v; target=null;} else v);
483 };
484 options.version = mkOption {
485 description = lib.mdDoc (descriptionGeneric "HiddenServiceVersion");
486 type = with types; nullOr (enum [2 3]);
487 default = null;
488 };
489 options.settings = mkOption {
490 description = lib.mdDoc ''
491 Settings of the onion service.
492 ${descriptionGeneric "_hidden_service_options"}
493 '';
494 default = {};
495 type = types.submodule {
496 freeformType = with types;
497 (attrsOf (nullOr (oneOf [str int bool (listOf str)]))) // {
498 description = "settings option";
499 };
500 options.HiddenServiceAllowUnknownPorts = optionBool "HiddenServiceAllowUnknownPorts";
501 options.HiddenServiceDirGroupReadable = optionBool "HiddenServiceDirGroupReadable";
502 options.HiddenServiceExportCircuitID = mkOption {
503 description = lib.mdDoc (descriptionGeneric "HiddenServiceExportCircuitID");
504 type = with types; nullOr (enum ["haproxy"]);
505 default = null;
506 };
507 options.HiddenServiceMaxStreams = mkOption {
508 description = lib.mdDoc (descriptionGeneric "HiddenServiceMaxStreams");
509 type = with types; nullOr (ints.between 0 65535);
510 default = null;
511 };
512 options.HiddenServiceMaxStreamsCloseCircuit = optionBool "HiddenServiceMaxStreamsCloseCircuit";
513 options.HiddenServiceNumIntroductionPoints = mkOption {
514 description = lib.mdDoc (descriptionGeneric "HiddenServiceNumIntroductionPoints");
515 type = with types; nullOr (ints.between 0 20);
516 default = null;
517 };
518 options.HiddenServiceSingleHopMode = optionBool "HiddenServiceSingleHopMode";
519 options.RendPostPeriod = optionString "RendPostPeriod";
520 };
521 };
522 config = {
523 path = mkDefault ((if config.secretKey == null then stateDir else runDir) + "/onion/${name}");
524 settings.HiddenServiceVersion = config.version;
525 settings.HiddenServiceAuthorizeClient =
526 if config.authorizeClient != null then
527 config.authorizeClient.authType + " " +
528 concatStringsSep "," config.authorizeClient.clientNames
529 else null;
530 settings.HiddenServicePort = map (p: mkValueString "" p.port + " " + mkValueString "" p.target) config.map;
531 };
532 }));
533 };
534 };
535
536 settings = mkOption {
537 description = lib.mdDoc ''
538 See [torrc manual](https://2019.www.torproject.org/docs/tor-manual.html.en)
539 for documentation.
540 '';
541 default = {};
542 type = types.submodule {
543 freeformType = with types;
544 (attrsOf (nullOr (oneOf [str int bool (listOf str)]))) // {
545 description = "settings option";
546 };
547 options.Address = optionString "Address";
548 options.AssumeReachable = optionBool "AssumeReachable";
549 options.AccountingMax = optionBandwidth "AccountingMax";
550 options.AccountingStart = optionString "AccountingStart";
551 options.AuthDirHasIPv6Connectivity = optionBool "AuthDirHasIPv6Connectivity";
552 options.AuthDirListBadExits = optionBool "AuthDirListBadExits";
553 options.AuthDirPinKeys = optionBool "AuthDirPinKeys";
554 options.AuthDirSharedRandomness = optionBool "AuthDirSharedRandomness";
555 options.AuthDirTestEd25519LinkKeys = optionBool "AuthDirTestEd25519LinkKeys";
556 options.AuthoritativeDirectory = optionBool "AuthoritativeDirectory";
557 options.AutomapHostsOnResolve = optionBool "AutomapHostsOnResolve";
558 options.AutomapHostsSuffixes = optionStrings "AutomapHostsSuffixes" // {
559 default = [".onion" ".exit"];
560 example = [".onion"];
561 };
562 options.BandwidthBurst = optionBandwidth "BandwidthBurst";
563 options.BandwidthRate = optionBandwidth "BandwidthRate";
564 options.BridgeAuthoritativeDir = optionBool "BridgeAuthoritativeDir";
565 options.BridgeRecordUsageByCountry = optionBool "BridgeRecordUsageByCountry";
566 options.BridgeRelay = optionBool "BridgeRelay" // { default = false; };
567 options.CacheDirectory = optionPath "CacheDirectory";
568 options.CacheDirectoryGroupReadable = optionBool "CacheDirectoryGroupReadable"; # default is null and like "auto"
569 options.CellStatistics = optionBool "CellStatistics";
570 options.ClientAutoIPv6ORPort = optionBool "ClientAutoIPv6ORPort";
571 options.ClientDNSRejectInternalAddresses = optionBool "ClientDNSRejectInternalAddresses";
572 options.ClientOnionAuthDir = mkOption {
573 description = lib.mdDoc (descriptionGeneric "ClientOnionAuthDir");
574 default = null;
575 type = with types; nullOr path;
576 };
577 options.ClientPreferIPv6DirPort = optionBool "ClientPreferIPv6DirPort"; # default is null and like "auto"
578 options.ClientPreferIPv6ORPort = optionBool "ClientPreferIPv6ORPort"; # default is null and like "auto"
579 options.ClientRejectInternalAddresses = optionBool "ClientRejectInternalAddresses";
580 options.ClientUseIPv4 = optionBool "ClientUseIPv4";
581 options.ClientUseIPv6 = optionBool "ClientUseIPv6";
582 options.ConnDirectionStatistics = optionBool "ConnDirectionStatistics";
583 options.ConstrainedSockets = optionBool "ConstrainedSockets";
584 options.ContactInfo = optionString "ContactInfo";
585 options.ControlPort = mkOption rec {
586 description = lib.mdDoc (descriptionGeneric "ControlPort");
587 default = [];
588 example = [{port = 9051;}];
589 type = with types; oneOf [port (enum ["auto"]) (listOf (oneOf [
590 port (enum ["auto"]) (submodule ({config, ...}: let
591 flags = ["GroupWritable" "RelaxDirModeCheck" "WorldWritable"];
592 in {
593 options = {
594 unix = optionUnix;
595 flags = optionFlags;
596 addr = optionAddress;
597 port = optionPort;
598 } // genAttrs flags (name: mkOption { type = types.bool; default = false; });
599 config = {
600 flags = filter (name: config.${name} == true) flags;
601 };
602 }))
603 ]))];
604 };
605 options.ControlPortFileGroupReadable= optionBool "ControlPortFileGroupReadable";
606 options.ControlPortWriteToFile = optionPath "ControlPortWriteToFile";
607 options.ControlSocket = optionPath "ControlSocket";
608 options.ControlSocketsGroupWritable = optionBool "ControlSocketsGroupWritable";
609 options.CookieAuthFile = optionPath "CookieAuthFile";
610 options.CookieAuthFileGroupReadable = optionBool "CookieAuthFileGroupReadable";
611 options.CookieAuthentication = optionBool "CookieAuthentication";
612 options.DataDirectory = optionPath "DataDirectory" // { default = stateDir; };
613 options.DataDirectoryGroupReadable = optionBool "DataDirectoryGroupReadable";
614 options.DirPortFrontPage = optionPath "DirPortFrontPage";
615 options.DirAllowPrivateAddresses = optionBool "DirAllowPrivateAddresses";
616 options.DormantCanceledByStartup = optionBool "DormantCanceledByStartup";
617 options.DormantOnFirstStartup = optionBool "DormantOnFirstStartup";
618 options.DormantTimeoutDisabledByIdleStreams = optionBool "DormantTimeoutDisabledByIdleStreams";
619 options.DirCache = optionBool "DirCache";
620 options.DirPolicy = mkOption {
621 description = lib.mdDoc (descriptionGeneric "DirPolicy");
622 type = with types; listOf str;
623 default = [];
624 example = ["accept *:*"];
625 };
626 options.DirPort = optionORPort "DirPort";
627 options.DirReqStatistics = optionBool "DirReqStatistics";
628 options.DisableAllSwap = optionBool "DisableAllSwap";
629 options.DisableDebuggerAttachment = optionBool "DisableDebuggerAttachment";
630 options.DisableNetwork = optionBool "DisableNetwork";
631 options.DisableOOSCheck = optionBool "DisableOOSCheck";
632 options.DNSPort = optionIsolablePorts "DNSPort";
633 options.DoSCircuitCreationEnabled = optionBool "DoSCircuitCreationEnabled";
634 options.DoSConnectionEnabled = optionBool "DoSConnectionEnabled"; # default is null and like "auto"
635 options.DoSRefuseSingleHopClientRendezvous = optionBool "DoSRefuseSingleHopClientRendezvous";
636 options.DownloadExtraInfo = optionBool "DownloadExtraInfo";
637 options.EnforceDistinctSubnets = optionBool "EnforceDistinctSubnets";
638 options.EntryStatistics = optionBool "EntryStatistics";
639 options.ExitPolicy = optionStrings "ExitPolicy" // {
640 default = ["reject *:*"];
641 example = ["accept *:*"];
642 };
643 options.ExitPolicyRejectLocalInterfaces = optionBool "ExitPolicyRejectLocalInterfaces";
644 options.ExitPolicyRejectPrivate = optionBool "ExitPolicyRejectPrivate";
645 options.ExitPortStatistics = optionBool "ExitPortStatistics";
646 options.ExitRelay = optionBool "ExitRelay"; # default is null and like "auto"
647 options.ExtORPort = mkOption {
648 description = lib.mdDoc (descriptionGeneric "ExtORPort");
649 default = null;
650 type = with types; nullOr (oneOf [
651 port (enum ["auto"]) (submodule ({...}: {
652 options = {
653 addr = optionAddress;
654 port = optionPort;
655 };
656 }))
657 ]);
658 apply = p: if isInt p || isString p then { port = p; } else p;
659 };
660 options.ExtORPortCookieAuthFile = optionPath "ExtORPortCookieAuthFile";
661 options.ExtORPortCookieAuthFileGroupReadable = optionBool "ExtORPortCookieAuthFileGroupReadable";
662 options.ExtendAllowPrivateAddresses = optionBool "ExtendAllowPrivateAddresses";
663 options.ExtraInfoStatistics = optionBool "ExtraInfoStatistics";
664 options.FascistFirewall = optionBool "FascistFirewall";
665 options.FetchDirInfoEarly = optionBool "FetchDirInfoEarly";
666 options.FetchDirInfoExtraEarly = optionBool "FetchDirInfoExtraEarly";
667 options.FetchHidServDescriptors = optionBool "FetchHidServDescriptors";
668 options.FetchServerDescriptors = optionBool "FetchServerDescriptors";
669 options.FetchUselessDescriptors = optionBool "FetchUselessDescriptors";
670 options.ReachableAddresses = optionStrings "ReachableAddresses";
671 options.ReachableDirAddresses = optionStrings "ReachableDirAddresses";
672 options.ReachableORAddresses = optionStrings "ReachableORAddresses";
673 options.GeoIPFile = optionPath "GeoIPFile";
674 options.GeoIPv6File = optionPath "GeoIPv6File";
675 options.GuardfractionFile = optionPath "GuardfractionFile";
676 options.HidServAuth = mkOption {
677 description = lib.mdDoc (descriptionGeneric "HidServAuth");
678 default = [];
679 type = with types; listOf (oneOf [
680 (submodule {
681 options = {
682 onion = mkOption {
683 type = strMatching "[a-z2-7]{16}\\.onion";
684 description = lib.mdDoc "Onion address.";
685 example = "xxxxxxxxxxxxxxxx.onion";
686 };
687 auth = mkOption {
688 type = strMatching "[A-Za-z0-9+/]{22}";
689 description = lib.mdDoc "Authentication cookie.";
690 };
691 };
692 })
693 ]);
694 example = [
695 {
696 onion = "xxxxxxxxxxxxxxxx.onion";
697 auth = "xxxxxxxxxxxxxxxxxxxxxx";
698 }
699 ];
700 };
701 options.HiddenServiceNonAnonymousMode = optionBool "HiddenServiceNonAnonymousMode";
702 options.HiddenServiceStatistics = optionBool "HiddenServiceStatistics";
703 options.HSLayer2Nodes = optionStrings "HSLayer2Nodes";
704 options.HSLayer3Nodes = optionStrings "HSLayer3Nodes";
705 options.HTTPTunnelPort = optionIsolablePorts "HTTPTunnelPort";
706 options.IPv6Exit = optionBool "IPv6Exit";
707 options.KeyDirectory = optionPath "KeyDirectory";
708 options.KeyDirectoryGroupReadable = optionBool "KeyDirectoryGroupReadable";
709 options.LogMessageDomains = optionBool "LogMessageDomains";
710 options.LongLivedPorts = optionPorts "LongLivedPorts";
711 options.MainloopStats = optionBool "MainloopStats";
712 options.MaxAdvertisedBandwidth = optionBandwidth "MaxAdvertisedBandwidth";
713 options.MaxCircuitDirtiness = optionInt "MaxCircuitDirtiness";
714 options.MaxClientCircuitsPending = optionInt "MaxClientCircuitsPending";
715 options.NATDPort = optionIsolablePorts "NATDPort";
716 options.NewCircuitPeriod = optionInt "NewCircuitPeriod";
717 options.Nickname = optionString "Nickname";
718 options.ORPort = optionORPort "ORPort";
719 options.OfflineMasterKey = optionBool "OfflineMasterKey";
720 options.OptimisticData = optionBool "OptimisticData"; # default is null and like "auto"
721 options.PaddingStatistics = optionBool "PaddingStatistics";
722 options.PerConnBWBurst = optionBandwidth "PerConnBWBurst";
723 options.PerConnBWRate = optionBandwidth "PerConnBWRate";
724 options.PidFile = optionPath "PidFile";
725 options.ProtocolWarnings = optionBool "ProtocolWarnings";
726 options.PublishHidServDescriptors = optionBool "PublishHidServDescriptors";
727 options.PublishServerDescriptor = mkOption {
728 description = lib.mdDoc (descriptionGeneric "PublishServerDescriptor");
729 type = with types; nullOr (enum [false true 0 1 "0" "1" "v3" "bridge"]);
730 default = null;
731 };
732 options.ReducedExitPolicy = optionBool "ReducedExitPolicy";
733 options.RefuseUnknownExits = optionBool "RefuseUnknownExits"; # default is null and like "auto"
734 options.RejectPlaintextPorts = optionPorts "RejectPlaintextPorts";
735 options.RelayBandwidthBurst = optionBandwidth "RelayBandwidthBurst";
736 options.RelayBandwidthRate = optionBandwidth "RelayBandwidthRate";
737 #options.RunAsDaemon
738 options.Sandbox = optionBool "Sandbox";
739 options.ServerDNSAllowBrokenConfig = optionBool "ServerDNSAllowBrokenConfig";
740 options.ServerDNSAllowNonRFC953Hostnames = optionBool "ServerDNSAllowNonRFC953Hostnames";
741 options.ServerDNSDetectHijacking = optionBool "ServerDNSDetectHijacking";
742 options.ServerDNSRandomizeCase = optionBool "ServerDNSRandomizeCase";
743 options.ServerDNSResolvConfFile = optionPath "ServerDNSResolvConfFile";
744 options.ServerDNSSearchDomains = optionBool "ServerDNSSearchDomains";
745 options.ServerTransportPlugin = mkOption {
746 description = lib.mdDoc (descriptionGeneric "ServerTransportPlugin");
747 default = null;
748 type = with types; nullOr (submodule ({...}: {
749 options = {
750 transports = mkOption {
751 description = lib.mdDoc "List of pluggable transports.";
752 type = listOf str;
753 example = ["obfs2" "obfs3" "obfs4" "scramblesuit"];
754 };
755 exec = mkOption {
756 type = types.str;
757 description = lib.mdDoc "Command of pluggable transport.";
758 };
759 };
760 }));
761 };
762 options.ShutdownWaitLength = mkOption {
763 type = types.int;
764 default = 30;
765 description = lib.mdDoc (descriptionGeneric "ShutdownWaitLength");
766 };
767 options.SocksPolicy = optionStrings "SocksPolicy" // {
768 example = ["accept *:*"];
769 };
770 options.SOCKSPort = mkOption {
771 description = lib.mdDoc (descriptionGeneric "SOCKSPort");
772 default = if cfg.settings.HiddenServiceNonAnonymousMode == true then [{port = 0;}] else [];
773 defaultText = literalExpression ''
774 if config.${opt.settings}.HiddenServiceNonAnonymousMode == true
775 then [ { port = 0; } ]
776 else [ ]
777 '';
778 example = [{port = 9090;}];
779 type = types.listOf (optionSOCKSPort true);
780 };
781 options.TestingTorNetwork = optionBool "TestingTorNetwork";
782 options.TransPort = optionIsolablePorts "TransPort";
783 options.TransProxyType = mkOption {
784 description = lib.mdDoc (descriptionGeneric "TransProxyType");
785 type = with types; nullOr (enum ["default" "TPROXY" "ipfw" "pf-divert"]);
786 default = null;
787 };
788 #options.TruncateLogFile
789 options.UnixSocksGroupWritable = optionBool "UnixSocksGroupWritable";
790 options.UseDefaultFallbackDirs = optionBool "UseDefaultFallbackDirs";
791 options.UseMicrodescriptors = optionBool "UseMicrodescriptors";
792 options.V3AuthUseLegacyKey = optionBool "V3AuthUseLegacyKey";
793 options.V3AuthoritativeDirectory = optionBool "V3AuthoritativeDirectory";
794 options.VersioningAuthoritativeDirectory = optionBool "VersioningAuthoritativeDirectory";
795 options.VirtualAddrNetworkIPv4 = optionString "VirtualAddrNetworkIPv4";
796 options.VirtualAddrNetworkIPv6 = optionString "VirtualAddrNetworkIPv6";
797 options.WarnPlaintextPorts = optionPorts "WarnPlaintextPorts";
798 };
799 };
800 };
801 };
802
803 config = mkIf cfg.enable {
804 # Not sure if `cfg.relay.role == "private-bridge"` helps as tor
805 # sends a lot of stats
806 warnings = optional (cfg.settings.BridgeRelay &&
807 flatten (mapAttrsToList (n: o: o.map) cfg.relay.onionServices) != [])
808 ''
809 Running Tor hidden services on a public relay makes the
810 presence of hidden services visible through simple statistical
811 analysis of publicly available data.
812 See https://trac.torproject.org/projects/tor/ticket/8742
813
814 You can safely ignore this warning if you don't intend to
815 actually hide your hidden services. In either case, you can
816 always create a container/VM with a separate Tor daemon instance.
817 '' ++
818 flatten (mapAttrsToList (n: o:
819 optionals (o.settings.HiddenServiceVersion == 2) [
820 (optional (o.settings.HiddenServiceExportCircuitID != null) ''
821 HiddenServiceExportCircuitID is used in the HiddenService: ${n}
822 but this option is only for v3 hidden services.
823 '')
824 ] ++
825 optionals (o.settings.HiddenServiceVersion != 2) [
826 (optional (o.settings.HiddenServiceAuthorizeClient != null) ''
827 HiddenServiceAuthorizeClient is used in the HiddenService: ${n}
828 but this option is only for v2 hidden services.
829 '')
830 (optional (o.settings.RendPostPeriod != null) ''
831 RendPostPeriod is used in the HiddenService: ${n}
832 but this option is only for v2 hidden services.
833 '')
834 ]
835 ) cfg.relay.onionServices);
836
837 users.groups.tor.gid = config.ids.gids.tor;
838 users.users.tor =
839 { description = "Tor Daemon User";
840 createHome = true;
841 home = stateDir;
842 group = "tor";
843 uid = config.ids.uids.tor;
844 };
845
846 services.tor.settings = mkMerge [
847 (mkIf cfg.enableGeoIP {
848 GeoIPFile = "${cfg.package.geoip}/share/tor/geoip";
849 GeoIPv6File = "${cfg.package.geoip}/share/tor/geoip6";
850 })
851 (mkIf cfg.controlSocket.enable {
852 ControlPort = [ { unix = runDir + "/control"; GroupWritable=true; RelaxDirModeCheck=true; } ];
853 })
854 (mkIf cfg.relay.enable (
855 optionalAttrs (cfg.relay.role != "exit") {
856 ExitPolicy = mkForce ["reject *:*"];
857 } //
858 optionalAttrs (elem cfg.relay.role ["bridge" "private-bridge"]) {
859 BridgeRelay = true;
860 ExtORPort.port = mkDefault "auto";
861 ServerTransportPlugin.transports = mkDefault ["obfs4"];
862 ServerTransportPlugin.exec = mkDefault "${pkgs.obfs4}/bin/obfs4proxy managed";
863 } // optionalAttrs (cfg.relay.role == "private-bridge") {
864 ExtraInfoStatistics = false;
865 PublishServerDescriptor = false;
866 }
867 ))
868 (mkIf (!cfg.relay.enable) {
869 # Avoid surprises when leaving ORPort/DirPort configurations in cfg.settings,
870 # because it would still enable Tor as a relay,
871 # which can trigger all sort of problems when not carefully done,
872 # like the blocklisting of the machine's IP addresses
873 # by some hosting providers...
874 DirPort = mkForce [];
875 ORPort = mkForce [];
876 PublishServerDescriptor = mkForce false;
877 })
878 (mkIf (!cfg.client.enable) {
879 # Make sure application connections via SOCKS are disabled
880 # when services.tor.client.enable is false
881 SOCKSPort = mkForce [ 0 ];
882 })
883 (mkIf cfg.client.enable (
884 { SOCKSPort = [ cfg.client.socksListenAddress ];
885 } // optionalAttrs cfg.client.transparentProxy.enable {
886 TransPort = [{ addr = "127.0.0.1"; port = 9040; }];
887 } // optionalAttrs cfg.client.dns.enable {
888 DNSPort = [{ addr = "127.0.0.1"; port = 9053; }];
889 AutomapHostsOnResolve = true;
890 } // optionalAttrs (flatten (mapAttrsToList (n: o: o.clientAuthorizations) cfg.client.onionServices) != []) {
891 ClientOnionAuthDir = runDir + "/ClientOnionAuthDir";
892 }
893 ))
894 ];
895
896 networking.firewall = mkIf cfg.openFirewall {
897 allowedTCPPorts =
898 concatMap (o:
899 if isInt o && o > 0 then [o]
900 else if o ? "port" && isInt o.port && o.port > 0 then [o.port]
901 else []
902 ) (flatten [
903 cfg.settings.ORPort
904 cfg.settings.DirPort
905 ]);
906 };
907
908 systemd.services.tor = {
909 description = "Tor Daemon";
910 path = [ pkgs.tor ];
911
912 wantedBy = [ "multi-user.target" ];
913 after = [ "network.target" ];
914 restartTriggers = [ torrc ];
915
916 serviceConfig = {
917 Type = "simple";
918 User = "tor";
919 Group = "tor";
920 ExecStartPre = [
921 "${cfg.package}/bin/tor -f ${torrc} --verify-config"
922 # DOC: Appendix G of https://spec.torproject.org/rend-spec-v3
923 ("+" + pkgs.writeShellScript "ExecStartPre" (concatStringsSep "\n" (flatten (["set -eu"] ++
924 mapAttrsToList (name: onion:
925 optional (onion.authorizedClients != []) ''
926 rm -rf ${escapeShellArg onion.path}/authorized_clients
927 install -d -o tor -g tor -m 0700 ${escapeShellArg onion.path} ${escapeShellArg onion.path}/authorized_clients
928 '' ++
929 imap0 (i: pubKey: ''
930 echo ${pubKey} |
931 install -o tor -g tor -m 0400 /dev/stdin ${escapeShellArg onion.path}/authorized_clients/${toString i}.auth
932 '') onion.authorizedClients ++
933 optional (onion.secretKey != null) ''
934 install -d -o tor -g tor -m 0700 ${escapeShellArg onion.path}
935 key="$(cut -f1 -d: ${escapeShellArg onion.secretKey} | head -1)"
936 case "$key" in
937 ("== ed25519v"*"-secret")
938 install -o tor -g tor -m 0400 ${escapeShellArg onion.secretKey} ${escapeShellArg onion.path}/hs_ed25519_secret_key;;
939 (*) echo >&2 "NixOS does not (yet) support secret key type for onion: ${name}"; exit 1;;
940 esac
941 ''
942 ) cfg.relay.onionServices ++
943 mapAttrsToList (name: onion: imap0 (i: prvKeyPath:
944 let hostname = removeSuffix ".onion" name; in ''
945 printf "%s:" ${escapeShellArg hostname} | cat - ${escapeShellArg prvKeyPath} |
946 install -o tor -g tor -m 0700 /dev/stdin \
947 ${runDir}/ClientOnionAuthDir/${escapeShellArg hostname}.${toString i}.auth_private
948 '') onion.clientAuthorizations)
949 cfg.client.onionServices
950 ))))
951 ];
952 ExecStart = "${cfg.package}/bin/tor -f ${torrc}";
953 ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
954 KillSignal = "SIGINT";
955 TimeoutSec = cfg.settings.ShutdownWaitLength + 30; # Wait a bit longer than ShutdownWaitLength before actually timing out
956 Restart = "on-failure";
957 LimitNOFILE = 32768;
958 RuntimeDirectory = [
959 # g+x allows access to the control socket
960 "tor"
961 "tor/root"
962 # g+x can't be removed in ExecStart=, but will be removed by Tor
963 "tor/ClientOnionAuthDir"
964 ];
965 RuntimeDirectoryMode = "0710";
966 StateDirectoryMode = "0700";
967 StateDirectory = [
968 "tor"
969 "tor/onion"
970 ] ++
971 flatten (mapAttrsToList (name: onion:
972 optional (onion.secretKey == null) "tor/onion/${name}"
973 ) cfg.relay.onionServices);
974 # The following options are only to optimize:
975 # systemd-analyze security tor
976 RootDirectory = runDir + "/root";
977 RootDirectoryStartOnly = true;
978 #InaccessiblePaths = [ "-+${runDir}/root" ];
979 UMask = "0066";
980 BindPaths = [ stateDir ];
981 BindReadOnlyPaths = [ storeDir "/etc" ] ++
982 optionals config.services.resolved.enable [
983 "/run/systemd/resolve/stub-resolv.conf"
984 "/run/systemd/resolve/resolv.conf"
985 ];
986 AmbientCapabilities = [""] ++ lib.optional bindsPrivilegedPort "CAP_NET_BIND_SERVICE";
987 CapabilityBoundingSet = [""] ++ lib.optional bindsPrivilegedPort "CAP_NET_BIND_SERVICE";
988 # ProtectClock= adds DeviceAllow=char-rtc r
989 DeviceAllow = "";
990 LockPersonality = true;
991 MemoryDenyWriteExecute = true;
992 NoNewPrivileges = true;
993 PrivateDevices = true;
994 PrivateMounts = true;
995 PrivateNetwork = mkDefault false;
996 PrivateTmp = true;
997 # Tor cannot currently bind privileged port when PrivateUsers=true,
998 # see https://gitlab.torproject.org/legacy/trac/-/issues/20930
999 PrivateUsers = !bindsPrivilegedPort;
1000 ProcSubset = "pid";
1001 ProtectClock = true;
1002 ProtectControlGroups = true;
1003 ProtectHome = true;
1004 ProtectHostname = true;
1005 ProtectKernelLogs = true;
1006 ProtectKernelModules = true;
1007 ProtectKernelTunables = true;
1008 ProtectProc = "invisible";
1009 ProtectSystem = "strict";
1010 RemoveIPC = true;
1011 RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" "AF_NETLINK" ];
1012 RestrictNamespaces = true;
1013 RestrictRealtime = true;
1014 RestrictSUIDSGID = true;
1015 # See also the finer but experimental option settings.Sandbox
1016 SystemCallFilter = [
1017 "@system-service"
1018 # Groups in @system-service which do not contain a syscall listed by:
1019 # perf stat -x, 2>perf.log -e 'syscalls:sys_enter_*' tor
1020 # in tests, and seem likely not necessary for tor.
1021 "~@aio" "~@chown" "~@keyring" "~@memlock" "~@resources" "~@setuid" "~@timer"
1022 ];
1023 SystemCallArchitectures = "native";
1024 SystemCallErrorNumber = "EPERM";
1025 };
1026 };
1027
1028 environment.systemPackages = [ cfg.package ];
1029 };
1030
1031 meta.maintainers = with lib.maintainers; [ julm ];
1032}