1 { config, lib, pkgs, ... }:
7 cfg = config.services.tor;
8 stateDir = "/var/lib/tor";
10 rootDir = "${runDir}/mnt-root";
11 descriptionGeneric = option: ''
12 See <link xlink:href="https://2019.www.torproject.org/docs/tor-manual.html.en#${option}"/>.
16 let p1 = if p0 ? "port" then p0.port else p0; in
18 let p2 = if isInt p1 then p1 else toInt p1;
19 in 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 = 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; });
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 optionBandwith = 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 concatMapStringsSep "\n${k} " (settings: settings.onion + " " settings.auth) v
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: config:
192 "HiddenServiceDir ${config.path}\n" +
193 genTorrc config.settings) cfg.relay.hiddenServices)
198 (mkRenamedOptionModule [ "services" "tor" "relay" "accountingMax" ] [ "services" "tor" "settings" "AccountingMax" ])
199 (mkRenamedOptionModule [ "services" "tor" "relay" "accountingStart" ] [ "services" "tor" "settings" "AccountingStart" ])
200 (mkRenamedOptionModule [ "services" "tor" "relay" "address" ] [ "services" "tor" "settings" "Address" ])
201 (mkRenamedOptionModule [ "services" "tor" "relay" "bandwidthBurst" ] [ "services" "tor" "settings" "BandwidthBurst" ])
202 (mkRenamedOptionModule [ "services" "tor" "relay" "bandwidthRate" ] [ "services" "tor" "settings" "BandwidthRate" ])
203 (mkRenamedOptionModule [ "services" "tor" "relay" "bridgeTransports" ] [ "services" "tor" "settings" "ServerTransportPlugin" "transports" ])
204 (mkRenamedOptionModule [ "services" "tor" "relay" "contactInfo" ] [ "services" "tor" "settings" "ContactInfo" ])
205 (mkRenamedOptionModule [ "services" "tor" "relay" "exitPolicy" ] [ "services" "tor" "settings" "ExitPolicy" ])
206 (mkRemovedOptionModule [ "services" "tor" "relay" "isBridge" ] "Use services.tor.relay.role instead.")
207 (mkRemovedOptionModule [ "services" "tor" "relay" "isExit" ] "Use services.tor.relay.role instead.")
208 (mkRenamedOptionModule [ "services" "tor" "relay" "nickname" ] [ "services" "tor" "settings" "Nickname" ])
209 (mkRenamedOptionModule [ "services" "tor" "relay" "port" ] [ "services" "tor" "settings" "ORPort" ])
210 (mkRenamedOptionModule [ "services" "tor" "relay" "portSpec" ] [ "services" "tor" "settings" "ORPort" ])
211 (mkRemovedOptionModule [ "services" "tor" "extraConfig" ] "Plese use services.tor.settings instead.")
212 (mkRenamedOptionModule [ "services" "tor" "controlPort" ] [ "services" "tor" "settings" "ControlPort" ])
213 (mkRenamedOptionModule [ "services" "tor" "client" "socksPolicy" ] [ "services" "tor" "settings" "SocksPolicy" ])
214 (mkRemovedOptionModule [ "services" "tor" "client" "socksIsolationOptions" ] "Use services.tor.settings.SOCKSPort")
215 (mkRemovedOptionModule [ "services" "tor" "client" "listenAddress" ] "Use services.tor.settings.SOCKSPort instead.")
216 (mkRemovedOptionModule [ "services" "tor" "client" "listenAddressFaster" ] "Use services.tor.settings.SOCKSPort instead.")
217 (mkRemovedOptionModule [ "services" "tor" "client" "transparentProxy" "listenAddress" ] "Use services.tor.settings.TransPort instead.")
218 (mkRemovedOptionModule [ "services" "tor" "client" "transparentProxy" "isolationOptions" ] "Use services.tor.settings.TransPort instead.")
219 (mkRemovedOptionModule [ "services" "tor" "client" "dns" "listenAddress" ] "Use services.tor.settings.DNSPort instead.")
220 (mkRemovedOptionModule [ "services" "tor" "client" "dns" "isolationOptions" ] "Use services.tor.settings.DNSPort instead.")
221 (mkRenamedOptionModule [ "services" "tor" "client" "dns" "automapHostsSuffixes" ] [ "services" "tor" "settings" "AutomapHostsSuffixes" ])
222 (mkRenamedOptionModule [ "services" "tor" "hiddenServices" ] [ "services" "tor" "relay" "hiddenServices" ])
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";
234 type = types.package;
236 defaultText = "pkgs.tor";
237 example = literalExample "pkgs.tor";
238 description = "Tor package to use.";
241 enableGeoIP = mkEnableOption ''use of GeoIP databases.
242 Disabling this will disable by-country statistics for bridges and relays
243 and some client and third-party software functionality'' // { default = true; };
245 controlSocket.enable = mkEnableOption ''control socket,
246 created in <literal>${runDir}/control</literal>'';
249 enable = mkEnableOption ''the routing of application connections.
250 You might want to disable this if you plan running a dedicated Tor relay'';
252 transparentProxy.enable = mkEnableOption "transparent proxy";
253 dns.enable = mkEnableOption "DNS resolver";
255 privoxy.enable = lib.mkEnableOption ''Privoxy system,
256 to use Tor's faster port, suitable for HTTP.
258 To have anonymity, protocols need to be scrubbed of identifying
259 information, and this can be accomplished for HTTP by Privoxy.
261 Privoxy can also be useful for KDE torification. In this setup
262 SOCKS proxy is set to the default Tor port (9050), providing maximum
263 circuit isolation where possible; and HTTP proxy to Privoxy
264 to route HTTP traffic is set over a faster, but less isolated port (9063).
267 hiddenServices = mkOption {
268 description = descriptionGeneric "HiddenServiceDir";
271 "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" = {
272 clientAuthorizations = ["/run/keys/tor/alice.prv.x25519"];
275 type = types.attrsOf (types.submodule ({name, config, ...}: {
276 options.clientAuthorizations = mkOption {
278 Clients' authorizations for a v3 hidden service,
279 as a list of files containing each one private key, in the format:
280 <screen>descriptor:x25519:<base32-private-key></screen>
281 '' + descriptionGeneric "_client_authorization";
282 type = with types; listOf path;
284 example = ["/run/keys/tor/alice.prv.x25519"];
291 enable = mkEnableOption ''relaying of Tor traffic for others.
293 See <link xlink:href="https://www.torproject.org/docs/tor-doc-relay" />
296 Setting this to true requires setting
297 <option>services.tor.relay.role</option>
299 <option>services.tor.settings.ORPort</option>
303 type = types.enum [ "exit" "relay" "bridge" "private-bridge" ];
305 Your role in Tor network. There're several options:
309 <term><literal>exit</literal></term>
312 An exit relay. This allows Tor users to access regular
313 Internet services through your public IP.
317 Running an exit relay may expose you to abuse
319 <link xlink:href="https://www.torproject.org/faq.html.en#ExitPolicies"/>
324 You can specify which services Tor users may access via
325 your exit relay using <option>settings.ExitPolicy</option> option.
331 <term><literal>relay</literal></term>
334 Regular relay. This allows Tor users to relay onion
335 traffic to other Tor nodes, but not to public
340 Note that some misconfigured and/or disrespectful
341 towards privacy sites will block you even if your
342 relay is not an exit relay. That is, just being listed
343 in a public relay directory can have unwanted
346 Which means you might not want to use
347 this role if you browse public Internet from the same
348 network as your relay, unless you want to write
349 e-mails to those sites (you should!).
354 <link xlink:href="https://www.torproject.org/docs/tor-doc-relay.html.en" />
361 <term><literal>bridge</literal></term>
364 Regular bridge. Works like a regular relay, but
365 doesn't list you in the public relay directory and
366 hides your Tor node behind obfs4proxy.
370 Using this option will make Tor advertise your bridge
371 to users through various mechanisms like
372 <link xlink:href="https://bridges.torproject.org/" />, though.
377 WARNING: THE FOLLOWING PARAGRAPH IS NOT LEGAL ADVICE.
378 Consult with your lawyer when in doubt.
382 This role should be safe to use in most situations
383 (unless the act of forwarding traffic for others is
384 a punishable offence under your local laws, which
385 would be pretty insane as it would make ISP illegal).
390 See <link xlink:href="https://www.torproject.org/docs/bridges.html.en" />
397 <term><literal>private-bridge</literal></term>
400 Private bridge. Works like regular bridge, but does
401 not advertise your node in any way.
405 Using this role means that you won't contribute to Tor
406 network in any way unless you advertise your node
407 yourself in some way.
411 Use this if you want to run a private bridge, for
412 example because you'll give out your bridge addr
413 manually to your friends.
417 Switching to this role after measurable time in
418 "bridge" role is pretty useless as some Tor users
419 would have learned about your node already. In the
420 latter case you can still change
421 <option>port</option> option.
425 See <link xlink:href="https://www.torproject.org/docs/bridges.html.en" />
434 hiddenServices = mkOption {
435 description = descriptionGeneric "HiddenServiceDir";
438 "example.org/www" = {
440 authorizedClients = [
441 "descriptor:x25519:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
445 type = types.attrsOf (types.submodule ({name, config, ...}: {
446 options.path = mkOption {
448 default = stateDir + "/onion/${name}";
449 description = "Path where to store the data files of the hidden service.";
452 options.hostname = mkOption {
456 Path where to store the data files of the hidden service.
460 options.authorizeClient = mkOption {
461 description = descriptionGeneric "HiddenServiceAuthorizeClient";
463 type = types.nullOr (types.submodule ({...}: {
465 authType = mkOption {
466 type = types.enum [ "basic" "stealth" ];
468 Either <literal>"basic"</literal> for a general-purpose authorization protocol
469 or <literal>"stealth"</literal> for a less scalable protocol
470 that also hides service activity from unauthorized clients.
473 clientNames = mkOption {
474 type = types.nonEmptyListOf (types.strMatching "[A-Za-z0-9+-_]+");
476 Only clients that are listed here are authorized to access the hidden service.
477 Generated authorization data can be found in <filename>${stateDir}/onion/$name/hostname</filename>.
478 Clients need to put this authorization data in their configuration file using
479 <xref linkend="opt-services.tor.settings.HidServAuth"/>.
485 options.authorizedClients = mkOption {
487 Authorized clients for a v3 hidden service,
488 as a list of public key, in the format:
489 <screen>descriptor:x25519:<base32-public-key></screen>
490 '' + descriptionGeneric "_client_authorization";
491 type = with types; listOf str;
493 example = ["descriptor:x25519:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"];
495 options.map = mkOption {
496 description = descriptionGeneric "HiddenServicePort";
497 type = with types; listOf (oneOf [
498 port (submodule ({...}: {
503 type = nullOr (submodule ({...}: {
506 addr = optionAddress;
514 apply = map (v: if isInt v then {port=v; target=null;} else v);
516 options.version = mkOption {
517 description = descriptionGeneric "HiddenServiceVersion";
518 type = with types; nullOr (enum [2 3]);
521 options.settings = mkOption {
523 type = types.submodule {
524 freeformType = with types;
525 (attrsOf (nullOr (oneOf [str int bool (listOf str)]))) // {
526 description = "settings option";
528 options.HiddenServiceAllowUnknownPorts = optionBool "HiddenServiceAllowUnknownPorts";
529 options.HiddenServiceDirGroupReadable = optionBool "HiddenServiceDirGroupReadable";
530 options.HiddenServiceExportCircuitID = mkOption {
531 description = descriptionGeneric "HiddenServiceExportCircuitID";
532 type = with types; nullOr (enum ["haproxy"]);
535 options.HiddenServiceMaxStreams = mkOption {
536 description = descriptionGeneric "HiddenServiceMaxStreams";
537 type = with types; nullOr (ints.between 0 65535);
540 options.HiddenServiceMaxStreamsCloseCircuit = optionBool "HiddenServiceMaxStreamsCloseCircuit";
541 options.HiddenServiceNumIntroductionPoints = mkOption {
542 description = descriptionGeneric "HiddenServiceNumIntroductionPoints";
543 type = with types; nullOr (ints.between 0 20);
546 options.HiddenServiceSingleHopMode = optionBool "HiddenServiceSingleHopMode";
547 options.RendPostPeriod = optionString "RendPostPeriod";
551 settings.HiddenServiceVersion = config.version;
552 settings.HiddenServiceAuthorizeClient =
553 if config.authorizeClient != null then
554 config.authorizeClient.authType + " " +
555 concatStringsSep "," config.authorizeClient.clientNames
557 settings.HiddenServicePort = map (p: mkValueString "" p.port + " " + mkValueString "" p.target) config.map;
563 settings = mkOption {
565 See <link xlink:href="https://2019.www.torproject.org/docs/tor-manual.html.en">torrc manual</link>
569 type = types.submodule {
570 freeformType = with types;
571 (attrsOf (nullOr (oneOf [str int bool (listOf str)]))) // {
572 description = "settings option";
574 options.Address = optionString "Address";
575 options.AssumeReachable = optionBool "AssumeReachable";
576 options.AccountingMax = optionBandwith "AccountingMax";
577 options.AccountingStart = optionString "AccountingStart";
578 options.AuthDirHasIPv6Connectivity = optionBool "AuthDirHasIPv6Connectivity";
579 options.AuthDirListBadExits = optionBool "AuthDirListBadExits";
580 options.AuthDirPinKeys = optionBool "AuthDirPinKeys";
581 options.AuthDirSharedRandomness = optionBool "AuthDirSharedRandomness";
582 options.AuthDirTestEd25519LinkKeys = optionBool "AuthDirTestEd25519LinkKeys";
583 options.AuthoritativeDirectory = optionBool "AuthoritativeDirectory";
584 options.AutomapHostsOnResolve = optionBool "AutomapHostsOnResolve";
585 options.AutomapHostsSuffixes = optionStrings "AutomapHostsSuffixes" // {
586 default = [".onion" ".exit"];
587 example = [".onion"];
589 options.BandwidthBurst = optionBandwith "BandwidthBurst";
590 options.BandwidthRate = optionBandwith "BandwidthRate";
591 options.BridgeAuthoritativeDir = optionBool "BridgeAuthoritativeDir";
592 options.BridgeRecordUsageByCountry = optionBool "BridgeRecordUsageByCountry";
593 options.BridgeRelay = optionBool "BridgeRelay" // { default = false; };
594 options.CacheDirectory = optionPath "CacheDirectory";
595 options.CacheDirectoryGroupReadable = optionBool "CacheDirectoryGroupReadable"; # default is null and like "auto"
596 options.CellStatistics = optionBool "CellStatistics";
597 options.ClientAutoIPv6ORPort = optionBool "ClientAutoIPv6ORPort";
598 options.ClientDNSRejectInternalAddresses = optionBool "ClientDNSRejectInternalAddresses";
599 options.ClientOnionAuthDir = mkOption {
600 description = descriptionGeneric "ClientOnionAuthDir";
602 type = with types; nullOr path;
604 options.ClientPreferIPv6DirPort = optionBool "ClientPreferIPv6DirPort"; # default is null and like "auto"
605 options.ClientPreferIPv6ORPort = optionBool "ClientPreferIPv6ORPort"; # default is null and like "auto"
606 options.ClientRejectInternalAddresses = optionBool "ClientRejectInternalAddresses";
607 options.ClientUseIPv4 = optionBool "ClientUseIPv4";
608 options.ClientUseIPv6 = optionBool "ClientUseIPv6";
609 options.ConnDirectionStatistics = optionBool "ConnDirectionStatistics";
610 options.ConstrainedSockets = optionBool "ConstrainedSockets";
611 options.ContactInfo = optionString "ContactInfo";
612 options.ControlPort = mkOption rec {
613 description = descriptionGeneric "ControlPort";
615 example = [{port = 9051;}];
616 type = with types; oneOf [port (enum ["auto"]) (listOf (oneOf [
617 port (enum ["auto"]) (submodule ({config, ...}: let
618 flags = ["GroupWritable" "RelaxDirModeCheck" "WorldWritable"];
623 addr = optionAddress;
625 } // genAttrs flags (name: mkOption { type = types.bool; default = false; });
627 flags = filter (name: config.${name} == true) flags;
632 options.ControlPortFileGroupReadable= optionBool "ControlPortFileGroupReadable";
633 options.ControlPortWriteToFile = optionPath "ControlPortWriteToFile";
634 options.ControlSocket = optionPath "ControlSocket";
635 options.ControlSocketsGroupWritable = optionBool "ControlSocketsGroupWritable";
636 options.CookieAuthFile = optionPath "CookieAuthFile";
637 options.CookieAuthFileGroupReadable = optionBool "CookieAuthFileGroupReadable";
638 options.CookieAuthentication = optionBool "CookieAuthentication";
639 options.DataDirectory = optionPath "DataDirectory" // { default = stateDir; };
640 options.DataDirectoryGroupReadable = optionBool "DataDirectoryGroupReadable";
641 options.DirPortFrontPage = optionPath "DirPortFrontPage";
642 options.DirAllowPrivateAddresses = optionBool "DirAllowPrivateAddresses";
643 options.DormantCanceledByStartup = optionBool "DormantCanceledByStartup";
644 options.DormantOnFirstStartup = optionBool "DormantOnFirstStartup";
645 options.DormantTimeoutDisabledByIdleStreams = optionBool "DormantTimeoutDisabledByIdleStreams";
646 options.DirCache = optionBool "DirCache";
647 options.DirPolicy = mkOption {
648 description = descriptionGeneric "DirPolicy";
649 type = with types; listOf str;
651 example = ["accept *:*"];
653 options.DirPort = optionORPort "DirPort";
654 options.DirReqStatistics = optionBool "DirReqStatistics";
655 options.DisableAllSwap = optionBool "DisableAllSwap";
656 options.DisableDebuggerAttachment = optionBool "DisableDebuggerAttachment";
657 options.DisableNetwork = optionBool "DisableNetwork";
658 options.DisableOOSCheck = optionBool "DisableOOSCheck";
659 options.DNSPort = optionIsolablePorts "DNSPort";
660 options.DoSCircuitCreationEnabled = optionBool "DoSCircuitCreationEnabled";
661 options.DoSConnectionEnabled = optionBool "DoSConnectionEnabled"; # default is null and like "auto"
662 options.DoSRefuseSingleHopClientRendezvous = optionBool "DoSRefuseSingleHopClientRendezvous";
663 options.DownloadExtraInfo = optionBool "DownloadExtraInfo";
664 options.EnforceDistinctSubnets = optionBool "EnforceDistinctSubnets";
665 options.EntryStatistics = optionBool "EntryStatistics";
666 options.ExitPolicy = optionStrings "ExitPolicy" // {
667 default = ["reject *:*"];
668 example = ["accept *:*"];
670 options.ExitPolicyRejectLocalInterfaces = optionBool "ExitPolicyRejectLocalInterfaces";
671 options.ExitPolicyRejectPrivate = optionBool "ExitPolicyRejectPrivate";
672 options.ExitPortStatistics = optionBool "ExitPortStatistics";
673 options.ExitRelay = optionBool "ExitRelay"; # default is null and like "auto"
674 options.ExtORPort = mkOption {
675 description = descriptionGeneric "ExtORPort";
677 type = with types; nullOr (oneOf [
678 port (enum ["auto"]) (submodule ({...}: {
680 addr = optionAddress;
686 if isInt p || isString p
690 options.ExtORPortCookieAuthFile = optionPath "ExtORPortCookieAuthFile";
691 options.ExtORPortCookieAuthFileGroupReadable = optionBool "ExtORPortCookieAuthFileGroupReadable";
692 options.ExtendAllowPrivateAddresses = optionBool "ExtendAllowPrivateAddresses";
693 options.ExtraInfoStatistics = optionBool "ExtraInfoStatistics";
694 options.FascistFirewall = optionBool "FascistFirewall";
695 options.FetchDirInfoEarly = optionBool "FetchDirInfoEarly";
696 options.FetchDirInfoExtraEarly = optionBool "FetchDirInfoExtraEarly";
697 options.FetchHidServDescriptors = optionBool "FetchHidServDescriptors";
698 options.FetchServerDescriptors = optionBool "FetchServerDescriptors";
699 options.FetchUselessDescriptors = optionBool "FetchUselessDescriptors";
700 options.ReachableAddresses = optionStrings "ReachableAddresses";
701 options.ReachableDirAddresses = optionStrings "ReachableDirAddresses";
702 options.ReachableORAddresses = optionStrings "ReachableORAddresses";
703 options.GeoIPFile = optionPath "GeoIPFile";
704 options.GeoIPv6File = optionPath "GeoIPv6File";
705 options.GuardfractionFile = optionPath "GuardfractionFile";
706 options.HidServAuth = mkOption {
707 description = descriptionGeneric "HidServAuth";
709 type = with types; listOf (oneOf [
710 (submodule ({config, ...}: {
713 # FIXME: is the regexp correctly written?
714 type = strMatching "[a-z2-7]\\{16\\}\\(\\.onion\\)?";
715 example = "xxxxxxxxxxxxxxxx.onion";
718 type = strMatching "[A-Za-z0-9+/]\\{22\\}";
724 options.HiddenServiceNonAnonymousMode = optionBool "HiddenServiceNonAnonymousMode";
725 options.HiddenServiceStatistics = optionBool "HiddenServiceStatistics";
726 options.HSLayer2Nodes = optionStrings "HSLayer2Nodes";
727 options.HSLayer3Nodes = optionStrings "HSLayer3Nodes";
728 options.HTTPTunnelPort = optionIsolablePorts "HTTPTunnelPort";
729 options.IPv6Exit = optionBool "IPv6Exit";
730 options.KeyDirectory = optionPath "KeyDirectory";
731 options.KeyDirectoryGroupReadable = optionBool "KeyDirectoryGroupReadable";
732 options.LogMessageDomains = optionBool "LogMessageDomains";
733 options.LongLivedPorts = optionPorts "LongLivedPorts";
734 options.MainloopStats = optionBool "MainloopStats";
735 options.MaxAdvertisedBandwidth = optionBandwith "MaxAdvertisedBandwidth";
736 options.MaxCircuitDirtiness = optionInt "MaxCircuitDirtiness";
737 options.MaxClientCircuitsPending = optionInt "MaxClientCircuitsPending";
738 options.NATDPort = optionIsolablePorts "NATDPort";
739 options.NewCircuitPeriod = optionInt "NewCircuitPeriod";
740 options.Nickname = optionString "Nickname";
741 options.ORPort = optionORPort "ORPort";
742 options.OfflineMasterKey = optionBool "OfflineMasterKey";
743 options.OptimisticData = optionBool "OptimisticData"; # default is null and like "auto"
744 options.PaddingStatistics = optionBool "PaddingStatistics";
745 options.PerConnBWBurst = optionBandwith "PerConnBWBurst";
746 options.PerConnBWRate = optionBandwith "PerConnBWRate";
747 options.PidFile = optionPath "PidFile";
748 options.ProtocolWarnings = optionBool "ProtocolWarnings";
749 options.PublishHidServDescriptors = optionBool "PublishHidServDescriptors";
750 options.PublishServerDescriptor = mkOption {
751 description = descriptionGeneric "PublishServerDescriptor";
752 type = with types; nullOr (enum [false true 0 1 "0" "1" "v3" "bridge"]);
755 options.ReducedExitPolicy = optionBool "ReducedExitPolicy";
756 options.RefuseUnknownExits = optionBool "RefuseUnknownExits"; # default is null and like "auto"
757 options.RejectPlaintextPorts = optionPorts "RejectPlaintextPorts";
758 options.RelayBandwidthBurst = optionBandwith "RelayBandwidthBurst";
759 options.RelayBandwidthRate = optionBandwith "RelayBandwidthRate";
761 options.Sandbox = optionBool "Sandbox";
762 options.ServerDNSAllowBrokenConfig = optionBool "ServerDNSAllowBrokenConfig";
763 options.ServerDNSAllowNonRFC953Hostnames = optionBool "ServerDNSAllowNonRFC953Hostnames";
764 options.ServerDNSDetectHijacking = optionBool "ServerDNSDetectHijacking";
765 options.ServerDNSRandomizeCase = optionBool "ServerDNSRandomizeCase";
766 options.ServerDNSResolvConfFile = optionPath "ServerDNSResolvConfFile";
767 options.ServerDNSSearchDomains = optionBool "ServerDNSSearchDomains";
768 options.ServerTransportPlugin = mkOption {
769 description = descriptionGeneric "ServerTransportPlugin";
771 type = with types; nullOr (submodule ({...}: {
773 transports = mkOption {
774 description = "List of pluggable transports";
776 example = ["obfs2" "obfs3" "obfs4" "scramblesuit"];
784 options.SocksPolicy = optionStrings "SocksPolicy" // {
785 example = ["accept *:*"];
787 options.SOCKSPort = mkOption {
788 description = descriptionGeneric "SOCKSPort";
789 default = if cfg.settings.HiddenServiceNonAnonymousMode == true then [{port = 0;}] else [];
790 example = [{port = 9090;}];
791 type = types.listOf optionSOCKSPort;
793 options.TestingTorNetwork = optionBool "TestingTorNetwork";
794 options.TransPort = optionIsolablePorts "TransPort";
795 options.TransProxyType = mkOption {
796 description = descriptionGeneric "TransProxyType";
797 type = with types; nullOr (enum ["default" "TPROXY" "ipfw" "pf-divert"]);
800 #options.TruncateLogFile
801 options.UnixSocksGroupWritable = optionBool "UnixSocksGroupWritable";
802 options.UseDefaultFallbackDirs = optionBool "UseDefaultFallbackDirs";
803 options.UseMicrodescriptors = optionBool "UseMicrodescriptors";
804 options.V3AuthUseLegacyKey = optionBool "V3AuthUseLegacyKey";
805 options.V3AuthoritativeDirectory = optionBool "V3AuthoritativeDirectory";
806 options.VersioningAuthoritativeDirectory = optionBool "VersioningAuthoritativeDirectory";
807 options.VirtualAddrNetworkIPv4 = optionString "VirtualAddrNetworkIPv4";
808 options.VirtualAddrNetworkIPv6 = optionString "VirtualAddrNetworkIPv6";
809 options.WarnPlaintextPorts = optionPorts "WarnPlaintextPorts";
815 config = mkIf cfg.enable {
816 # Not sure if `cfg.relay.role == "private-bridge"` helps as tor
817 # sends a lot of stats
818 warnings = optional (cfg.settings.BridgeRelay &&
819 flatten (mapAttrsToList (n: h: h.map) cfg.relay.hiddenServices) != [])
821 Running Tor hidden services on a public relay makes the
822 presence of hidden services visible through simple statistical
823 analysis of publicly available data.
824 See https://trac.torproject.org/projects/tor/ticket/8742
826 You can safely ignore this warning if you don't intend to
827 actually hide your hidden services. In either case, you can
828 always create a container/VM with a separate Tor daemon instance.
830 flatten (mapAttrsToList (n: h:
831 optional (h.settings.HiddenServiceVersion == 2) [
832 (optional (h.settings.HiddenServiceExportCircuitID != null) ''
833 HiddenServiceExportCircuitID is used in the HiddenService: ${n}
834 but this option is only for v3 hidden services.
837 optional (h.settings.HiddenServiceVersion != 2) [
838 (optional (h.settings.HiddenServiceAuthorizeClient != null) ''
839 HiddenServiceAuthorizeClient is used in the HiddenService: ${n}
840 but this option is only for v2 hidden services.
842 (optional (h.settings.RendPostPeriod != null) ''
843 RendPostPeriod is used in the HiddenService: ${n}
844 but this option is only for v2 hidden services.
847 ) cfg.relay.hiddenServices);
849 users.groups.tor.gid = config.ids.gids.tor;
851 { description = "Tor Daemon User";
855 uid = config.ids.uids.tor;
858 services.tor.settings = mkMerge [
859 (mkIf cfg.enableGeoIP {
860 GeoIPFile = "${cfg.package.geoip}/share/tor/geoip";
861 GeoIPv6File = "${cfg.package.geoip}/share/tor/geoip6";
863 (mkIf cfg.controlSocket.enable {
864 ControlPort = [ { unix = runDir + "/control"; GroupWritable=true; RelaxDirModeCheck=true; } ];
866 (mkIf cfg.relay.enable (
867 optionalAttrs (cfg.relay.role != "exit") {
868 ExitPolicy = mkForce ["reject *:*"];
870 optionalAttrs (elem cfg.relay.role ["bridge" "private-bridge"]) {
872 ExtORPort.port = mkDefault "auto";
873 ServerTransportPlugin.transports = mkDefault ["obfs4"];
874 ServerTransportPlugin.exec = mkDefault "${pkgs.obfs4}/bin/obfs4proxy managed";
875 } // optionalAttrs (cfg.relay.role == "private-bridge") {
876 ExtraInfoStatistics = false;
877 PublishServerDescriptor = false;
880 (mkIf cfg.client.enable (
882 { addr = "127.0.0.1"; port = 9050; IsolateDestAddr = true; }
883 { addr = "127.0.0.1"; port = 9063; }
885 } // optionalAttrs cfg.client.transparentProxy.enable {
886 TransPort = [{ addr = "127.0.0.1"; port = 9040; }];
887 } // optionalAttrs cfg.client.dns.enable {
888 DNSPort = [{ addr = "127.0.0.1"; port = 9053; }];
889 AutomapHostsOnResolve = true;
890 AutomapHostsSuffixes = cfg.client.dns.automapHostsSuffixes;
891 } // optionalAttrs (flatten (mapAttrsToList (n: h: h.clientAuthorizations) cfg.client.hiddenServices) != []) {
892 ClientOnionAuthDir = runDir + "/ClientOnionAuthDir";
897 networking.firewall = mkIf cfg.openFirewall {
899 map (o: optional (isInt o && o > 0 || o ? "port" && isInt o.port && o.port > 0) o.port)
906 systemd.services.tor = {
907 description = "Tor Daemon";
910 wantedBy = [ "multi-user.target" ];
911 after = [ "network.target" ];
912 restartTriggers = [ torrc ];
919 "${cfg.package}/bin/tor -f ${torrc} --verify-config"
921 # DOC: Appendix G of https://spec.torproject.org/rend-spec-v3
922 [("+" + pkgs.writeShellScript "auth" (concatStringsSep "\n" (flatten (
923 mapAttrsToList (name: h:
924 optionals (h.authorizedClients != []) ([''
925 out="${h.path}/authorized_clients"
927 install -d -o tor -g tor -m 0700 "$out"
928 ''] ++ imap0 (i: pubKey: ''
930 install -o tor -g tor -m 0400 /dev/stdin $out/${toString i}.auth
931 '') h.authorizedClients
933 ) cfg.relay.hiddenServices ++
934 mapAttrsToList (name: h: imap0 (i: prvKeyPath:
935 let onion = removeSuffix ".onion" name; in ''
936 printf "${onion}:" | cat - '${prvKeyPath}' |
937 install -o tor -g tor -m 0700 /dev/stdin \
938 ${runDir}/ClientOnionAuthDir/${onion}.${toString i}.auth_private
939 '') h.clientAuthorizations)
940 cfg.client.hiddenServices
942 ExecStart = "${cfg.package}/bin/tor -f ${torrc}";
943 ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
944 KillSignal = "SIGINT";
946 Restart = "on-failure";
948 RuntimeDirectory = [ "tor" "tor/mnt-root" "tor/ClientOnionAuthDir" ];
949 RuntimeDirectoryMode = "0750";
950 StateDirectoryMode = "0700";
952 [ "tor" "tor/onion" ] ++
953 mapAttrsToList (name: v: "tor/onion/${name}") cfg.relay.hiddenServices;
955 # The following are only to optimize:
956 # systemd-analyze security tor
957 RootDirectory = rootDir;
958 RootDirectoryStartOnly = true;
959 #InaccessiblePaths = [ "-+${rootDir}" ];
964 BindReadOnlyPaths = [
968 AmbientCapabilities = [""] ++ lib.optional bindsPrivilegedPort "CAP_NET_BIND_SERVICE";
969 CapabilityBoundingSet = [""] ++ lib.optional bindsPrivilegedPort "CAP_NET_BIND_SERVICE";
970 # ProtectClock= adds DeviceAllow=char-rtc r
972 LockPersonality = true;
973 MemoryDenyWriteExecute = true;
974 NoNewPrivileges = true;
975 PrivateDevices = true;
976 PrivateMounts = true;
977 PrivateNetwork = mkDefault false;
979 # Tor cannot currently bind privileged port when PrivateUsers=true,
980 # see https://gitlab.torproject.org/legacy/trac/-/issues/20930
981 PrivateUsers = !bindsPrivilegedPort;
983 ProtectControlGroups = true;
985 ProtectHostname = true;
986 ProtectKernelLogs = true;
987 ProtectKernelModules = true;
988 ProtectKernelTunables = true;
989 ProtectSystem = "strict";
991 RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
992 RestrictNamespaces = true;
993 RestrictRealtime = true;
994 RestrictSUIDSGID = true;
995 # See also the finer but experimental option settings.Sandbox
998 # Groups in @system-service which do not contain a syscall
999 # listed by perf stat -e 'syscalls:sys_enter_*' tor
1000 # in tests, and seem likely not necessary for tor.
1001 "~@aio" "~@chown" "~@keyring" "~@memlock" "~@resources" "~@setuid" "~@timer"
1003 SystemCallArchitectures = "native";
1004 SystemCallErrorNumber = "EPERM";
1008 environment.systemPackages = [ cfg.package ];
1010 services.privoxy = mkIf (cfg.client.enable && cfg.client.privoxy.enable) {
1013 forward-socks5t / ${mkValueString "" { addr = "127.0.0.1"; port = 9063; }} .
1015 enable-remote-toggle 0
1016 enable-edit-actions 0
1017 enable-remote-http-toggle 0
1022 meta.maintainers = with lib.maintainers; [ julm ];