python312Packages.dissect-extfs: 3.11 -> 3.12
[NixPkgs.git] / nixos / modules / services / security / tor.nix
blob6cceb1bad82d579a1666441c9dd1bbb941fb035e
1 { config, lib, options, pkgs, ... }:
3 with builtins;
4 with lib;
6 let
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   };
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   );
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   ];
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'';
231       openFirewall = mkEnableOption "opening of the relay port(s) in the firewall";
233       package = mkPackageOption pkgs "tor" { };
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; };
239       controlSocket.enable = mkEnableOption ''control socket,
240         created in `${runDir}/control`'';
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'';
246         transparentProxy.enable = mkEnableOption "transparent proxy";
247         dns.enable = mkEnableOption "DNS resolver";
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         };
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       };
285       relay = {
286         enable = mkEnableOption "tor relaying" // {
287           description = ''
288             Whether to enable relaying of Tor traffic for others.
290             See <https://www.torproject.org/docs/tor-doc-relay>
291             for details.
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         };
301         role = mkOption {
302           type = types.enum [ "exit" "relay" "bridge" "private-bridge" ];
303           description = ''
304             Your role in Tor network. There're several options:
306             - `exit`:
307               An exit relay. This allows Tor users to access regular
308               Internet services through your public IP.
310               You can specify which services Tor users may access via
311               your exit relay using {option}`settings.ExitPolicy` option.
313             - `relay`:
314               Regular relay. This allows Tor users to relay onion
315               traffic to other Tor nodes, but not to public
316               Internet.
318               See
319               <https://www.torproject.org/docs/tor-doc-relay.html.en>
320               for more info.
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.
327               Using this option will make Tor advertise your bridge
328               to users through various mechanisms like
329               <https://bridges.torproject.org/>, though.
331               See <https://www.torproject.org/docs/bridges.html.en>
332               for more info.
334             - `private-bridge`:
335               Private bridge. Works like regular bridge, but does
336               not advertise your node in any way.
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.
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.
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.
352               See <https://www.torproject.org/docs/bridges.html.en>
353               for more info.
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             :::
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.
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             :::
375             ::: {.important}
376             WARNING: THE FOLLOWING PARAGRAPH IS NOT LEGAL ADVICE.
377             Consult with your lawyer when in doubt.
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         };
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       };
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   };
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
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);
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       };
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     ];
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     };
902     systemd.services.tor = {
903       description = "Tor Daemon";
904       path = [ pkgs.tor ];
906       wantedBy = [ "multi-user.target" ];
907       after    = [ "network.target" ];
908       restartTriggers = [ torrc ];
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     };
1022     environment.systemPackages = [ cfg.package ];
1023   };
1025   meta.maintainers = with lib.maintainers; [ julm ];