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