1 { config, lib, options, pkgs, ... }:
7 cfg = config.services.tor;
8 opt = options.services.tor;
9 stateDir = "/var/lib/tor";
11 descriptionGeneric = option: ''
12 See [torrc manual](https://2019.www.torproject.org/docs/tor-manual.html.en#${option}).
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)
24 cfg.settings.ExtORPort
25 cfg.settings.HTTPTunnelPort
27 cfg.settings.SOCKSPort
28 cfg.settings.TransPort
30 optionBool = optionName: mkOption {
31 type = with types; nullOr bool;
33 description = (descriptionGeneric optionName);
35 optionInt = optionName: mkOption {
36 type = with types; nullOr int;
38 description = (descriptionGeneric optionName);
40 optionString = optionName: mkOption {
41 type = with types; nullOr str;
43 description = (descriptionGeneric optionName);
45 optionStrings = optionName: mkOption {
46 type = with types; listOf str;
48 description = (descriptionGeneric optionName);
50 optionAddress = mkOption {
51 type = with types; nullOr str;
55 IPv4 or IPv6 (if between brackets) address.
58 optionUnix = mkOption {
59 type = with types; nullOr path;
62 Unix domain socket path to use.
65 optionPort = mkOption {
66 type = with types; nullOr (oneOf [port (enum ["auto"])]);
69 optionPorts = optionName: mkOption {
70 type = with types; listOf port;
72 description = (descriptionGeneric optionName);
74 optionIsolablePort = with types; oneOf [
76 (submodule ({config, ...}: {
81 SessionGroup = mkOption { type = nullOr int; default = null; };
82 } // genAttrs isolateFlags (name: mkOption { type = types.bool; default = false; });
84 flags = filter (name: config.${name} == true) isolateFlags ++
85 optional (config.SessionGroup != null) "SessionGroup=${toString config.SessionGroup}";
89 optionIsolablePorts = optionName: mkOption {
91 type = with types; either optionIsolablePort (listOf optionIsolablePort);
92 description = (descriptionGeneric optionName);
96 "IsolateClientProtocol"
100 "KeepAliveIsolateSOCKSAuth"
102 optionSOCKSPort = doConfig: let
104 "CacheDNS" "CacheIPv4DNS" "CacheIPv6DNS" "GroupWritable" "IPv6Traffic"
105 "NoDNSRequest" "NoIPv4Traffic" "NoOnionTraffic" "OnionTrafficOnly"
106 "PreferIPv6" "PreferIPv6Automap" "PreferSOCKSNoAuth" "UseDNSCache"
107 "UseIPv4Cache" "UseIPv6Cache" "WorldWritable"
109 in with types; oneOf [
110 port (submodule ({config, ...}: {
113 addr = optionAddress;
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}";
124 optionFlags = mkOption {
125 type = with types; listOf str;
128 optionORPort = optionName: mkOption {
131 type = with types; oneOf [port (enum ["auto"]) (listOf (oneOf [
134 (submodule ({config, ...}:
135 let flags = [ "IPv4Only" "IPv6Only" "NoAdvertise" "NoListen" ];
138 addr = optionAddress;
141 } // genAttrs flags (name: mkOption { type = types.bool; default = false; });
143 flags = filter (name: config.${name} == true) flags;
147 description = (descriptionGeneric optionName);
149 optionBandwidth = optionName: mkOption {
150 type = with types; nullOr (either int str);
152 description = (descriptionGeneric optionName);
154 optionPath = optionName: mkOption {
155 type = with types; nullOr path;
157 description = (descriptionGeneric optionName);
160 mkValueString = k: v:
162 else if isBool v then
163 (if v then "1" else "0")
164 else if v ? "unix" && v.unix != null then
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}:" +
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;
177 generators.toKeyValue {
178 listsAsDuplicateKeys = true;
179 mkKeyValue = k: generators.mkKeyValueDefault { mkValueString = mkValueString k; } " " k;
182 # Not necesssary, but prettier rendering
183 if elem k [ "AutomapHostsSuffixes" "DirPolicy" "ExitPolicy" "SocksPolicy" ]
185 then concatStringsSep "," v
187 (lib.filterAttrs (k: v: !(v == null || v == ""))
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)
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" ])
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`'';
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;};
254 Bind to this address to listen for connections from
255 Socks-speaking applications.
259 onionServices = mkOption {
260 description = (descriptionGeneric "HiddenServiceDir");
263 "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" = {
264 clientAuthorizations = ["/run/keys/tor/alice.prv.x25519"];
267 type = types.attrsOf (types.submodule ({name, config, ...}: {
268 options.clientAuthorizations = mkOption {
270 Clients' authorizations for a v3 onion service,
271 as a list of files containing each one private key, in the format:
273 descriptor:x25519:<base32-private-key>
275 ${descriptionGeneric "_client_authorization"}
277 type = with types; listOf path;
279 example = ["/run/keys/tor/alice.prv.x25519"];
286 enable = mkEnableOption "tor relaying" // {
288 Whether to enable relaying of Tor traffic for others.
290 See <https://www.torproject.org/docs/tor-doc-relay>
293 Setting this to true requires setting
294 {option}`services.tor.relay.role`
296 {option}`services.tor.settings.ORPort`
302 type = types.enum [ "exit" "relay" "bridge" "private-bridge" ];
304 Your role in Tor network. There're several options:
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.
314 Regular relay. This allows Tor users to relay onion
315 traffic to other Tor nodes, but not to public
319 <https://www.torproject.org/docs/tor-doc-relay.html.en>
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>
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>
356 Running an exit relay may expose you to abuse
358 <https://www.torproject.org/faq.html.en#ExitPolicies>
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
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!).
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).
387 onionServices = mkOption {
388 description = (descriptionGeneric "HiddenServiceDir");
391 "example.org/www" = {
393 authorizedClients = [
394 "descriptor:x25519:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
398 type = types.attrsOf (types.submodule ({name, config, ...}: {
399 options.path = mkOption {
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`.
408 options.secretKey = mkOption {
409 type = with types; nullOr path;
411 example = "/run/keys/tor/onion/expyuzz4wqqyqhjn/hs_ed25519_secret_key";
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.
420 options.authorizeClient = mkOption {
421 description = (descriptionGeneric "HiddenServiceAuthorizeClient");
423 type = types.nullOr (types.submodule ({...}: {
425 authType = mkOption {
426 type = types.enum [ "basic" "stealth" ];
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.
433 clientNames = mkOption {
434 type = with types; nonEmptyListOf (strMatching "[A-Za-z0-9+-_]+");
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).
445 options.authorizedClients = mkOption {
447 Authorized clients for a v3 onion service,
448 as a list of public key, in the format:
450 descriptor:x25519:<base32-public-key>
452 ${descriptionGeneric "_client_authorization"}
454 type = with types; listOf str;
456 example = ["descriptor:x25519:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"];
458 options.map = mkOption {
459 description = (descriptionGeneric "HiddenServicePort");
460 type = with types; listOf (oneOf [
461 port (submodule ({...}: {
466 type = nullOr (submodule ({...}: {
469 addr = optionAddress;
477 apply = map (v: if isInt v then {port=v; target=null;} else v);
479 options.version = mkOption {
480 description = (descriptionGeneric "HiddenServiceVersion");
481 type = with types; nullOr (enum [2 3]);
484 options.settings = mkOption {
486 Settings of the onion service.
487 ${descriptionGeneric "_hidden_service_options"}
490 type = types.submodule {
491 freeformType = with types;
492 (attrsOf (nullOr (oneOf [str int bool (listOf str)]))) // {
493 description = "settings option";
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"]);
502 options.HiddenServiceMaxStreams = mkOption {
503 description = (descriptionGeneric "HiddenServiceMaxStreams");
504 type = with types; nullOr (ints.between 0 65535);
507 options.HiddenServiceMaxStreamsCloseCircuit = optionBool "HiddenServiceMaxStreamsCloseCircuit";
508 options.HiddenServiceNumIntroductionPoints = mkOption {
509 description = (descriptionGeneric "HiddenServiceNumIntroductionPoints");
510 type = with types; nullOr (ints.between 0 20);
513 options.HiddenServiceSingleHopMode = optionBool "HiddenServiceSingleHopMode";
514 options.RendPostPeriod = optionString "RendPostPeriod";
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
525 settings.HiddenServicePort = map (p: mkValueString "" p.port + " " + mkValueString "" p.target) config.map;
531 settings = mkOption {
533 See [torrc manual](https://2019.www.torproject.org/docs/tor-manual.html.en)
537 type = types.submodule {
538 freeformType = with types;
539 (attrsOf (nullOr (oneOf [str int bool (listOf str)]))) // {
540 description = "settings option";
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"];
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");
570 type = with types; nullOr path;
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");
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"];
591 addr = optionAddress;
593 } // genAttrs flags (name: mkOption { type = types.bool; default = false; });
595 flags = filter (name: config.${name} == true) flags;
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;
619 example = ["accept *:*"];
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 *:*"];
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");
645 type = with types; nullOr (oneOf [
646 port (enum ["auto"]) (submodule ({...}: {
648 addr = optionAddress;
653 apply = p: if isInt p || isString p then { port = p; } else p;
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");
674 type = with types; listOf (oneOf [
678 type = strMatching "[a-z2-7]{16}\\.onion";
679 description = "Onion address.";
680 example = "xxxxxxxxxxxxxxxx.onion";
683 type = strMatching "[A-Za-z0-9+/]{22}";
684 description = "Authentication cookie.";
691 onion = "xxxxxxxxxxxxxxxx.onion";
692 auth = "xxxxxxxxxxxxxxxxxxxxxx";
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"]);
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";
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");
743 type = with types; nullOr (submodule ({...}: {
745 transports = mkOption {
746 description = "List of pluggable transports.";
748 example = ["obfs2" "obfs3" "obfs4" "scramblesuit"];
752 description = "Command of pluggable transport.";
757 options.ShutdownWaitLength = mkOption {
760 description = (descriptionGeneric "ShutdownWaitLength");
762 options.SocksPolicy = optionStrings "SocksPolicy" // {
763 example = ["accept *:*"];
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; } ]
773 example = [{port = 9090;}];
774 type = types.listOf (optionSOCKSPort true);
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"]);
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";
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) != [])
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.
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.
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.
825 (optional (o.settings.RendPostPeriod != null) ''
826 RendPostPeriod is used in the HiddenService: ${n}
827 but this option is only for v2 hidden services.
830 ) cfg.relay.onionServices);
832 users.groups.tor.gid = config.ids.gids.tor;
834 { description = "Tor Daemon User";
838 uid = config.ids.uids.tor;
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";
846 (mkIf cfg.controlSocket.enable {
847 ControlPort = [ { unix = runDir + "/control"; GroupWritable=true; RelaxDirModeCheck=true; } ];
849 (mkIf cfg.relay.enable (
850 optionalAttrs (cfg.relay.role != "exit") {
851 ExitPolicy = mkForce ["reject *:*"];
853 optionalAttrs (elem cfg.relay.role ["bridge" "private-bridge"]) {
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;
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 [];
871 PublishServerDescriptor = mkForce false;
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 ];
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";
891 networking.firewall = mkIf cfg.openFirewall {
894 if isInt o && o > 0 then [o]
895 else optionals (o ? "port" && isInt o.port && o.port > 0) [o.port]
902 systemd.services.tor = {
903 description = "Tor Daemon";
906 wantedBy = [ "multi-user.target" ];
907 after = [ "network.target" ];
908 restartTriggers = [ torrc ];
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
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)"
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;;
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
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";
953 # g+x allows access to the control socket
956 # g+x can't be removed in ExecStart=, but will be removed by Tor
957 "tor/ClientOnionAuthDir"
959 RuntimeDirectoryMode = "0710";
960 StateDirectoryMode = "0700";
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" ];
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"
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
984 LockPersonality = true;
985 MemoryDenyWriteExecute = true;
986 NoNewPrivileges = true;
987 PrivateDevices = true;
988 PrivateMounts = true;
989 PrivateNetwork = mkDefault false;
991 # Tor cannot currently bind privileged port when PrivateUsers=true,
992 # see https://gitlab.torproject.org/legacy/trac/-/issues/20930
993 PrivateUsers = !bindsPrivilegedPort;
996 ProtectControlGroups = true;
998 ProtectHostname = true;
999 ProtectKernelLogs = true;
1000 ProtectKernelModules = true;
1001 ProtectKernelTunables = true;
1002 ProtectProc = "invisible";
1003 ProtectSystem = "strict";
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 = [
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"
1017 SystemCallArchitectures = "native";
1018 SystemCallErrorNumber = "EPERM";
1022 environment.systemPackages = [ cfg.package ];
1025 meta.maintainers = with lib.maintainers; [ julm ];