1 { config, lib, pkgs, ... }:
7 cfg = config.services.tor;
8 stateDir = "/var/lib/tor";
10 descriptionGeneric = option: ''
11 See <link xlink:href="https://2019.www.torproject.org/docs/tor-manual.html.en#${option}"/>.
15 let p1 = if p0 ? "port" then p0.port else p0; in
16 if p1 == "auto" then false
17 else let p2 = if isInt p1 then p1 else toInt p1; in
18 p1 != null && 0 < p2 && p2 < 1024)
23 cfg.settings.ExtORPort
24 cfg.settings.HTTPTunnelPort
26 cfg.settings.SOCKSPort
27 cfg.settings.TransPort
29 optionBool = optionName: mkOption {
30 type = with types; nullOr bool;
32 description = descriptionGeneric optionName;
34 optionInt = optionName: mkOption {
35 type = with types; nullOr int;
37 description = descriptionGeneric optionName;
39 optionString = optionName: mkOption {
40 type = with types; nullOr str;
42 description = descriptionGeneric optionName;
44 optionStrings = optionName: mkOption {
45 type = with types; listOf str;
47 description = descriptionGeneric optionName;
49 optionAddress = mkOption {
50 type = with types; nullOr str;
54 IPv4 or IPv6 (if between brackets) address.
57 optionUnix = mkOption {
58 type = with types; nullOr path;
61 Unix domain socket path to use.
64 optionPort = mkOption {
65 type = with types; nullOr (oneOf [port (enum ["auto"])]);
68 optionPorts = optionName: mkOption {
69 type = with types; listOf port;
71 description = descriptionGeneric optionName;
73 optionIsolablePort = with types; oneOf [
75 (submodule ({config, ...}: {
80 SessionGroup = mkOption { type = nullOr int; default = null; };
81 } // genAttrs isolateFlags (name: mkOption { type = types.bool; default = false; });
83 flags = filter (name: config.${name} == true) isolateFlags ++
84 optional (config.SessionGroup != null) "SessionGroup=${toString config.SessionGroup}";
88 optionIsolablePorts = optionName: mkOption {
90 type = with types; either optionIsolablePort (listOf optionIsolablePort);
91 description = descriptionGeneric optionName;
95 "IsolateClientProtocol"
99 "KeepAliveIsolateSOCKSAuth"
101 optionSOCKSPort = let
103 "CacheDNS" "CacheIPv4DNS" "CacheIPv6DNS" "GroupWritable" "IPv6Traffic"
104 "NoDNSRequest" "NoIPv4Traffic" "NoOnionTraffic" "OnionTrafficOnly"
105 "PreferIPv6" "PreferIPv6Automap" "PreferSOCKSNoAuth" "UseDNSCache"
106 "UseIPv4Cache" "UseIPv6Cache" "WorldWritable"
108 in with types; oneOf [
109 port (submodule ({config, ...}: {
112 addr = optionAddress;
115 SessionGroup = mkOption { type = nullOr int; default = null; };
116 } // genAttrs flags (name: mkOption { type = types.bool; default = false; });
118 flags = filter (name: config.${name} == true) flags ++
119 optional (config.SessionGroup != null) "SessionGroup=${toString config.SessionGroup}";
123 optionFlags = mkOption {
124 type = with types; listOf str;
127 optionORPort = optionName: mkOption {
130 type = with types; oneOf [port (enum ["auto"]) (listOf (oneOf [
133 (submodule ({config, ...}:
134 let flags = [ "IPv4Only" "IPv6Only" "NoAdvertise" "NoListen" ];
137 addr = optionAddress;
140 } // genAttrs flags (name: mkOption { type = types.bool; default = false; });
142 flags = filter (name: config.${name} == true) flags;
146 description = descriptionGeneric optionName;
148 optionBandwith = optionName: mkOption {
149 type = with types; nullOr (either int str);
151 description = descriptionGeneric optionName;
153 optionPath = optionName: mkOption {
154 type = with types; nullOr path;
156 description = descriptionGeneric optionName;
159 mkValueString = k: v:
161 else if isBool v then
162 (if v then "1" else "0")
163 else if v ? "unix" && v.unix != null then
165 optionalString (v ? "flags") (" " + concatStringsSep " " v.flags)
166 else if v ? "port" && v.port != null then
167 optionalString (v ? "addr" && v.addr != null) "${v.addr}:" +
169 optionalString (v ? "flags") (" " + concatStringsSep " " v.flags)
170 else if k == "ServerTransportPlugin" then
171 optionalString (v.transports != []) "${concatStringsSep "," v.transports} exec ${v.exec}"
172 else if k == "HidServAuth" then
173 concatMapStringsSep "\n${k} " (settings: settings.onion + " " settings.auth) v
174 else generators.mkValueStringDefault {} v;
176 generators.toKeyValue {
177 listsAsDuplicateKeys = true;
178 mkKeyValue = k: generators.mkKeyValueDefault { mkValueString = mkValueString k; } " " k;
181 # Not necesssary, but prettier rendering
182 if elem k [ "AutomapHostsSuffixes" "DirPolicy" "ExitPolicy" "SocksPolicy" ]
184 then concatStringsSep "," v
186 (lib.filterAttrs (k: v: !(v == null || v == ""))
188 torrc = pkgs.writeText "torrc" (
189 genTorrc cfg.settings +
190 concatStrings (mapAttrsToList (name: onion:
191 "HiddenServiceDir ${onion.path}\n" +
192 genTorrc onion.settings) cfg.relay.onionServices)
197 (mkRenamedOptionModule [ "services" "tor" "relay" "accountingMax" ] [ "services" "tor" "settings" "AccountingMax" ])
198 (mkRenamedOptionModule [ "services" "tor" "relay" "accountingStart" ] [ "services" "tor" "settings" "AccountingStart" ])
199 (mkRenamedOptionModule [ "services" "tor" "relay" "address" ] [ "services" "tor" "settings" "Address" ])
200 (mkRenamedOptionModule [ "services" "tor" "relay" "bandwidthBurst" ] [ "services" "tor" "settings" "BandwidthBurst" ])
201 (mkRenamedOptionModule [ "services" "tor" "relay" "bandwidthRate" ] [ "services" "tor" "settings" "BandwidthRate" ])
202 (mkRenamedOptionModule [ "services" "tor" "relay" "bridgeTransports" ] [ "services" "tor" "settings" "ServerTransportPlugin" "transports" ])
203 (mkRenamedOptionModule [ "services" "tor" "relay" "contactInfo" ] [ "services" "tor" "settings" "ContactInfo" ])
204 (mkRenamedOptionModule [ "services" "tor" "relay" "exitPolicy" ] [ "services" "tor" "settings" "ExitPolicy" ])
205 (mkRemovedOptionModule [ "services" "tor" "relay" "isBridge" ] "Use services.tor.relay.role instead.")
206 (mkRemovedOptionModule [ "services" "tor" "relay" "isExit" ] "Use services.tor.relay.role instead.")
207 (mkRenamedOptionModule [ "services" "tor" "relay" "nickname" ] [ "services" "tor" "settings" "Nickname" ])
208 (mkRenamedOptionModule [ "services" "tor" "relay" "port" ] [ "services" "tor" "settings" "ORPort" ])
209 (mkRenamedOptionModule [ "services" "tor" "relay" "portSpec" ] [ "services" "tor" "settings" "ORPort" ])
210 (mkRemovedOptionModule [ "services" "tor" "extraConfig" ] "Plese use services.tor.settings instead.")
211 (mkRenamedOptionModule [ "services" "tor" "controlPort" ] [ "services" "tor" "settings" "ControlPort" ])
212 (mkRenamedOptionModule [ "services" "tor" "client" "socksPolicy" ] [ "services" "tor" "settings" "SocksPolicy" ])
213 (mkRemovedOptionModule [ "services" "tor" "client" "socksIsolationOptions" ] "Use services.tor.settings.SOCKSPort")
214 (mkRemovedOptionModule [ "services" "tor" "client" "listenAddress" ] "Use services.tor.settings.SOCKSPort instead.")
215 (mkRemovedOptionModule [ "services" "tor" "client" "listenAddressFaster" ] "Use services.tor.settings.SOCKSPort instead.")
216 (mkRemovedOptionModule [ "services" "tor" "client" "transparentProxy" "listenAddress" ] "Use services.tor.settings.TransPort instead.")
217 (mkRemovedOptionModule [ "services" "tor" "client" "transparentProxy" "isolationOptions" ] "Use services.tor.settings.TransPort instead.")
218 (mkRemovedOptionModule [ "services" "tor" "client" "dns" "listenAddress" ] "Use services.tor.settings.DNSPort instead.")
219 (mkRemovedOptionModule [ "services" "tor" "client" "dns" "isolationOptions" ] "Use services.tor.settings.DNSPort instead.")
220 (mkRenamedOptionModule [ "services" "tor" "client" "dns" "automapHostsSuffixes" ] [ "services" "tor" "settings" "AutomapHostsSuffixes" ])
221 (mkRenamedOptionModule [ "services" "tor" "hiddenServices" ] [ "services" "tor" "relay" "onionServices" ])
226 enable = mkEnableOption ''Tor daemon.
227 By default, the daemon is run without
228 relay, exit, bridge or client connectivity'';
230 openFirewall = mkEnableOption "opening of the relay port(s) in the firewall";
233 type = types.package;
235 defaultText = "pkgs.tor";
236 example = literalExample "pkgs.tor";
237 description = "Tor package to use.";
240 enableGeoIP = mkEnableOption ''use of GeoIP databases.
241 Disabling this will disable by-country statistics for bridges and relays
242 and some client and third-party software functionality'' // { default = true; };
244 controlSocket.enable = mkEnableOption ''control socket,
245 created in <literal>${runDir}/control</literal>'';
248 enable = mkEnableOption ''the routing of application connections.
249 You might want to disable this if you plan running a dedicated Tor relay'';
251 transparentProxy.enable = mkEnableOption "transparent proxy";
252 dns.enable = mkEnableOption "DNS resolver";
254 privoxy.enable = lib.mkEnableOption ''Privoxy system,
255 to use Tor's faster port, suitable for HTTP.
257 To have anonymity, protocols need to be scrubbed of identifying
258 information, and this can be accomplished for HTTP by Privoxy.
260 Privoxy can also be useful for KDE torification. In this setup
261 SOCKS proxy is set to the default Tor port (9050), providing maximum
262 circuit isolation where possible; and HTTP proxy to Privoxy
263 to route HTTP traffic is set over a faster, but less isolated port (9063).
266 onionServices = mkOption {
267 description = descriptionGeneric "HiddenServiceDir";
270 "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" = {
271 clientAuthorizations = ["/run/keys/tor/alice.prv.x25519"];
274 type = types.attrsOf (types.submodule ({name, config, ...}: {
275 options.clientAuthorizations = mkOption {
277 Clients' authorizations for a v3 hidden service,
278 as a list of files containing each one private key, in the format:
279 <screen>descriptor:x25519:<base32-private-key></screen>
280 '' + descriptionGeneric "_client_authorization";
281 type = with types; listOf path;
283 example = ["/run/keys/tor/alice.prv.x25519"];
290 enable = mkEnableOption ''relaying of Tor traffic for others.
292 See <link xlink:href="https://www.torproject.org/docs/tor-doc-relay" />
295 Setting this to true requires setting
296 <option>services.tor.relay.role</option>
298 <option>services.tor.settings.ORPort</option>
302 type = types.enum [ "exit" "relay" "bridge" "private-bridge" ];
304 Your role in Tor network. There're several options:
308 <term><literal>exit</literal></term>
311 An exit relay. This allows Tor users to access regular
312 Internet services through your public IP.
316 Running an exit relay may expose you to abuse
318 <link xlink:href="https://www.torproject.org/faq.html.en#ExitPolicies"/>
323 You can specify which services Tor users may access via
324 your exit relay using <option>settings.ExitPolicy</option> option.
330 <term><literal>relay</literal></term>
333 Regular relay. This allows Tor users to relay onion
334 traffic to other Tor nodes, but not to public
339 Note that some misconfigured and/or disrespectful
340 towards privacy sites will block you even if your
341 relay is not an exit relay. That is, just being listed
342 in a public relay directory can have unwanted
345 Which means you might not want to use
346 this role if you browse public Internet from the same
347 network as your relay, unless you want to write
348 e-mails to those sites (you should!).
353 <link xlink:href="https://www.torproject.org/docs/tor-doc-relay.html.en" />
360 <term><literal>bridge</literal></term>
363 Regular bridge. Works like a regular relay, but
364 doesn't list you in the public relay directory and
365 hides your Tor node behind obfs4proxy.
369 Using this option will make Tor advertise your bridge
370 to users through various mechanisms like
371 <link xlink:href="https://bridges.torproject.org/" />, though.
376 WARNING: THE FOLLOWING PARAGRAPH IS NOT LEGAL ADVICE.
377 Consult with your lawyer when in doubt.
381 This role should be safe to use in most situations
382 (unless the act of forwarding traffic for others is
383 a punishable offence under your local laws, which
384 would be pretty insane as it would make ISP illegal).
389 See <link xlink:href="https://www.torproject.org/docs/bridges.html.en" />
396 <term><literal>private-bridge</literal></term>
399 Private bridge. Works like regular bridge, but does
400 not advertise your node in any way.
404 Using this role means that you won't contribute to Tor
405 network in any way unless you advertise your node
406 yourself in some way.
410 Use this if you want to run a private bridge, for
411 example because you'll give out your bridge addr
412 manually to your friends.
416 Switching to this role after measurable time in
417 "bridge" role is pretty useless as some Tor users
418 would have learned about your node already. In the
419 latter case you can still change
420 <option>port</option> option.
424 See <link xlink:href="https://www.torproject.org/docs/bridges.html.en" />
433 onionServices = mkOption {
434 description = descriptionGeneric "HiddenServiceDir";
437 "example.org/www" = {
439 authorizedClients = [
440 "descriptor:x25519:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
444 type = types.attrsOf (types.submodule ({name, config, ...}: {
445 options.path = mkOption {
448 Path where to store the data files of the hidden service.
449 If the <option>secretKey</option> is null
450 this defaults to <literal>${stateDir}/onion/$onion</literal>,
451 otherwise to <literal>${runDir}/onion/$onion</literal>.
454 options.secretKey = mkOption {
455 type = with types; nullOr path;
457 example = "/run/keys/tor/onion/expyuzz4wqqyqhjn/hs_ed25519_secret_key";
459 Secret key of the onion service.
460 If null, Tor reuses any preexisting secret key (in <option>path</option>)
461 or generates a new one.
462 The associated public key and hostname are deterministically regenerated
463 from this file if they do not exist.
466 options.authorizeClient = mkOption {
467 description = descriptionGeneric "HiddenServiceAuthorizeClient";
469 type = types.nullOr (types.submodule ({...}: {
471 authType = mkOption {
472 type = types.enum [ "basic" "stealth" ];
474 Either <literal>"basic"</literal> for a general-purpose authorization protocol
475 or <literal>"stealth"</literal> for a less scalable protocol
476 that also hides service activity from unauthorized clients.
479 clientNames = mkOption {
480 type = with types; nonEmptyListOf (strMatching "[A-Za-z0-9+-_]+");
482 Only clients that are listed here are authorized to access the hidden service.
483 Generated authorization data can be found in <filename>${stateDir}/onion/$name/hostname</filename>.
484 Clients need to put this authorization data in their configuration file using
485 <xref linkend="opt-services.tor.settings.HidServAuth"/>.
491 options.authorizedClients = mkOption {
493 Authorized clients for a v3 hidden service,
494 as a list of public key, in the format:
495 <screen>descriptor:x25519:<base32-public-key></screen>
496 '' + descriptionGeneric "_client_authorization";
497 type = with types; listOf str;
499 example = ["descriptor:x25519:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"];
501 options.map = mkOption {
502 description = descriptionGeneric "HiddenServicePort";
503 type = with types; listOf (oneOf [
504 port (submodule ({...}: {
509 type = nullOr (submodule ({...}: {
512 addr = optionAddress;
520 apply = map (v: if isInt v then {port=v; target=null;} else v);
522 options.version = mkOption {
523 description = descriptionGeneric "HiddenServiceVersion";
524 type = with types; nullOr (enum [2 3]);
527 options.settings = mkOption {
529 type = types.submodule {
530 freeformType = with types;
531 (attrsOf (nullOr (oneOf [str int bool (listOf str)]))) // {
532 description = "settings option";
534 options.HiddenServiceAllowUnknownPorts = optionBool "HiddenServiceAllowUnknownPorts";
535 options.HiddenServiceDirGroupReadable = optionBool "HiddenServiceDirGroupReadable";
536 options.HiddenServiceExportCircuitID = mkOption {
537 description = descriptionGeneric "HiddenServiceExportCircuitID";
538 type = with types; nullOr (enum ["haproxy"]);
541 options.HiddenServiceMaxStreams = mkOption {
542 description = descriptionGeneric "HiddenServiceMaxStreams";
543 type = with types; nullOr (ints.between 0 65535);
546 options.HiddenServiceMaxStreamsCloseCircuit = optionBool "HiddenServiceMaxStreamsCloseCircuit";
547 options.HiddenServiceNumIntroductionPoints = mkOption {
548 description = descriptionGeneric "HiddenServiceNumIntroductionPoints";
549 type = with types; nullOr (ints.between 0 20);
552 options.HiddenServiceSingleHopMode = optionBool "HiddenServiceSingleHopMode";
553 options.RendPostPeriod = optionString "RendPostPeriod";
557 path = mkDefault ((if config.secretKey == null then stateDir else runDir) + "/onion/${name}");
558 settings.HiddenServiceVersion = config.version;
559 settings.HiddenServiceAuthorizeClient =
560 if config.authorizeClient != null then
561 config.authorizeClient.authType + " " +
562 concatStringsSep "," config.authorizeClient.clientNames
564 settings.HiddenServicePort = map (p: mkValueString "" p.port + " " + mkValueString "" p.target) config.map;
570 settings = mkOption {
572 See <link xlink:href="https://2019.www.torproject.org/docs/tor-manual.html.en">torrc manual</link>
576 type = types.submodule {
577 freeformType = with types;
578 (attrsOf (nullOr (oneOf [str int bool (listOf str)]))) // {
579 description = "settings option";
581 options.Address = optionString "Address";
582 options.AssumeReachable = optionBool "AssumeReachable";
583 options.AccountingMax = optionBandwith "AccountingMax";
584 options.AccountingStart = optionString "AccountingStart";
585 options.AuthDirHasIPv6Connectivity = optionBool "AuthDirHasIPv6Connectivity";
586 options.AuthDirListBadExits = optionBool "AuthDirListBadExits";
587 options.AuthDirPinKeys = optionBool "AuthDirPinKeys";
588 options.AuthDirSharedRandomness = optionBool "AuthDirSharedRandomness";
589 options.AuthDirTestEd25519LinkKeys = optionBool "AuthDirTestEd25519LinkKeys";
590 options.AuthoritativeDirectory = optionBool "AuthoritativeDirectory";
591 options.AutomapHostsOnResolve = optionBool "AutomapHostsOnResolve";
592 options.AutomapHostsSuffixes = optionStrings "AutomapHostsSuffixes" // {
593 default = [".onion" ".exit"];
594 example = [".onion"];
596 options.BandwidthBurst = optionBandwith "BandwidthBurst";
597 options.BandwidthRate = optionBandwith "BandwidthRate";
598 options.BridgeAuthoritativeDir = optionBool "BridgeAuthoritativeDir";
599 options.BridgeRecordUsageByCountry = optionBool "BridgeRecordUsageByCountry";
600 options.BridgeRelay = optionBool "BridgeRelay" // { default = false; };
601 options.CacheDirectory = optionPath "CacheDirectory";
602 options.CacheDirectoryGroupReadable = optionBool "CacheDirectoryGroupReadable"; # default is null and like "auto"
603 options.CellStatistics = optionBool "CellStatistics";
604 options.ClientAutoIPv6ORPort = optionBool "ClientAutoIPv6ORPort";
605 options.ClientDNSRejectInternalAddresses = optionBool "ClientDNSRejectInternalAddresses";
606 options.ClientOnionAuthDir = mkOption {
607 description = descriptionGeneric "ClientOnionAuthDir";
609 type = with types; nullOr path;
611 options.ClientPreferIPv6DirPort = optionBool "ClientPreferIPv6DirPort"; # default is null and like "auto"
612 options.ClientPreferIPv6ORPort = optionBool "ClientPreferIPv6ORPort"; # default is null and like "auto"
613 options.ClientRejectInternalAddresses = optionBool "ClientRejectInternalAddresses";
614 options.ClientUseIPv4 = optionBool "ClientUseIPv4";
615 options.ClientUseIPv6 = optionBool "ClientUseIPv6";
616 options.ConnDirectionStatistics = optionBool "ConnDirectionStatistics";
617 options.ConstrainedSockets = optionBool "ConstrainedSockets";
618 options.ContactInfo = optionString "ContactInfo";
619 options.ControlPort = mkOption rec {
620 description = descriptionGeneric "ControlPort";
622 example = [{port = 9051;}];
623 type = with types; oneOf [port (enum ["auto"]) (listOf (oneOf [
624 port (enum ["auto"]) (submodule ({config, ...}: let
625 flags = ["GroupWritable" "RelaxDirModeCheck" "WorldWritable"];
630 addr = optionAddress;
632 } // genAttrs flags (name: mkOption { type = types.bool; default = false; });
634 flags = filter (name: config.${name} == true) flags;
639 options.ControlPortFileGroupReadable= optionBool "ControlPortFileGroupReadable";
640 options.ControlPortWriteToFile = optionPath "ControlPortWriteToFile";
641 options.ControlSocket = optionPath "ControlSocket";
642 options.ControlSocketsGroupWritable = optionBool "ControlSocketsGroupWritable";
643 options.CookieAuthFile = optionPath "CookieAuthFile";
644 options.CookieAuthFileGroupReadable = optionBool "CookieAuthFileGroupReadable";
645 options.CookieAuthentication = optionBool "CookieAuthentication";
646 options.DataDirectory = optionPath "DataDirectory" // { default = stateDir; };
647 options.DataDirectoryGroupReadable = optionBool "DataDirectoryGroupReadable";
648 options.DirPortFrontPage = optionPath "DirPortFrontPage";
649 options.DirAllowPrivateAddresses = optionBool "DirAllowPrivateAddresses";
650 options.DormantCanceledByStartup = optionBool "DormantCanceledByStartup";
651 options.DormantOnFirstStartup = optionBool "DormantOnFirstStartup";
652 options.DormantTimeoutDisabledByIdleStreams = optionBool "DormantTimeoutDisabledByIdleStreams";
653 options.DirCache = optionBool "DirCache";
654 options.DirPolicy = mkOption {
655 description = descriptionGeneric "DirPolicy";
656 type = with types; listOf str;
658 example = ["accept *:*"];
660 options.DirPort = optionORPort "DirPort";
661 options.DirReqStatistics = optionBool "DirReqStatistics";
662 options.DisableAllSwap = optionBool "DisableAllSwap";
663 options.DisableDebuggerAttachment = optionBool "DisableDebuggerAttachment";
664 options.DisableNetwork = optionBool "DisableNetwork";
665 options.DisableOOSCheck = optionBool "DisableOOSCheck";
666 options.DNSPort = optionIsolablePorts "DNSPort";
667 options.DoSCircuitCreationEnabled = optionBool "DoSCircuitCreationEnabled";
668 options.DoSConnectionEnabled = optionBool "DoSConnectionEnabled"; # default is null and like "auto"
669 options.DoSRefuseSingleHopClientRendezvous = optionBool "DoSRefuseSingleHopClientRendezvous";
670 options.DownloadExtraInfo = optionBool "DownloadExtraInfo";
671 options.EnforceDistinctSubnets = optionBool "EnforceDistinctSubnets";
672 options.EntryStatistics = optionBool "EntryStatistics";
673 options.ExitPolicy = optionStrings "ExitPolicy" // {
674 default = ["reject *:*"];
675 example = ["accept *:*"];
677 options.ExitPolicyRejectLocalInterfaces = optionBool "ExitPolicyRejectLocalInterfaces";
678 options.ExitPolicyRejectPrivate = optionBool "ExitPolicyRejectPrivate";
679 options.ExitPortStatistics = optionBool "ExitPortStatistics";
680 options.ExitRelay = optionBool "ExitRelay"; # default is null and like "auto"
681 options.ExtORPort = mkOption {
682 description = descriptionGeneric "ExtORPort";
684 type = with types; nullOr (oneOf [
685 port (enum ["auto"]) (submodule ({...}: {
687 addr = optionAddress;
692 apply = p: if isInt p || isString p then { port = p; } else p;
694 options.ExtORPortCookieAuthFile = optionPath "ExtORPortCookieAuthFile";
695 options.ExtORPortCookieAuthFileGroupReadable = optionBool "ExtORPortCookieAuthFileGroupReadable";
696 options.ExtendAllowPrivateAddresses = optionBool "ExtendAllowPrivateAddresses";
697 options.ExtraInfoStatistics = optionBool "ExtraInfoStatistics";
698 options.FascistFirewall = optionBool "FascistFirewall";
699 options.FetchDirInfoEarly = optionBool "FetchDirInfoEarly";
700 options.FetchDirInfoExtraEarly = optionBool "FetchDirInfoExtraEarly";
701 options.FetchHidServDescriptors = optionBool "FetchHidServDescriptors";
702 options.FetchServerDescriptors = optionBool "FetchServerDescriptors";
703 options.FetchUselessDescriptors = optionBool "FetchUselessDescriptors";
704 options.ReachableAddresses = optionStrings "ReachableAddresses";
705 options.ReachableDirAddresses = optionStrings "ReachableDirAddresses";
706 options.ReachableORAddresses = optionStrings "ReachableORAddresses";
707 options.GeoIPFile = optionPath "GeoIPFile";
708 options.GeoIPv6File = optionPath "GeoIPv6File";
709 options.GuardfractionFile = optionPath "GuardfractionFile";
710 options.HidServAuth = mkOption {
711 description = descriptionGeneric "HidServAuth";
713 type = with types; listOf (oneOf [
714 (submodule ({config, ...}: {
717 type = strMatching "[a-z2-7]{16}(\\.onion)?";
718 example = "xxxxxxxxxxxxxxxx.onion";
721 type = strMatching "[A-Za-z0-9+/]{22}";
727 options.HiddenServiceNonAnonymousMode = optionBool "HiddenServiceNonAnonymousMode";
728 options.HiddenServiceStatistics = optionBool "HiddenServiceStatistics";
729 options.HSLayer2Nodes = optionStrings "HSLayer2Nodes";
730 options.HSLayer3Nodes = optionStrings "HSLayer3Nodes";
731 options.HTTPTunnelPort = optionIsolablePorts "HTTPTunnelPort";
732 options.IPv6Exit = optionBool "IPv6Exit";
733 options.KeyDirectory = optionPath "KeyDirectory";
734 options.KeyDirectoryGroupReadable = optionBool "KeyDirectoryGroupReadable";
735 options.LogMessageDomains = optionBool "LogMessageDomains";
736 options.LongLivedPorts = optionPorts "LongLivedPorts";
737 options.MainloopStats = optionBool "MainloopStats";
738 options.MaxAdvertisedBandwidth = optionBandwith "MaxAdvertisedBandwidth";
739 options.MaxCircuitDirtiness = optionInt "MaxCircuitDirtiness";
740 options.MaxClientCircuitsPending = optionInt "MaxClientCircuitsPending";
741 options.NATDPort = optionIsolablePorts "NATDPort";
742 options.NewCircuitPeriod = optionInt "NewCircuitPeriod";
743 options.Nickname = optionString "Nickname";
744 options.ORPort = optionORPort "ORPort";
745 options.OfflineMasterKey = optionBool "OfflineMasterKey";
746 options.OptimisticData = optionBool "OptimisticData"; # default is null and like "auto"
747 options.PaddingStatistics = optionBool "PaddingStatistics";
748 options.PerConnBWBurst = optionBandwith "PerConnBWBurst";
749 options.PerConnBWRate = optionBandwith "PerConnBWRate";
750 options.PidFile = optionPath "PidFile";
751 options.ProtocolWarnings = optionBool "ProtocolWarnings";
752 options.PublishHidServDescriptors = optionBool "PublishHidServDescriptors";
753 options.PublishServerDescriptor = mkOption {
754 description = descriptionGeneric "PublishServerDescriptor";
755 type = with types; nullOr (enum [false true 0 1 "0" "1" "v3" "bridge"]);
758 options.ReducedExitPolicy = optionBool "ReducedExitPolicy";
759 options.RefuseUnknownExits = optionBool "RefuseUnknownExits"; # default is null and like "auto"
760 options.RejectPlaintextPorts = optionPorts "RejectPlaintextPorts";
761 options.RelayBandwidthBurst = optionBandwith "RelayBandwidthBurst";
762 options.RelayBandwidthRate = optionBandwith "RelayBandwidthRate";
764 options.Sandbox = optionBool "Sandbox";
765 options.ServerDNSAllowBrokenConfig = optionBool "ServerDNSAllowBrokenConfig";
766 options.ServerDNSAllowNonRFC953Hostnames = optionBool "ServerDNSAllowNonRFC953Hostnames";
767 options.ServerDNSDetectHijacking = optionBool "ServerDNSDetectHijacking";
768 options.ServerDNSRandomizeCase = optionBool "ServerDNSRandomizeCase";
769 options.ServerDNSResolvConfFile = optionPath "ServerDNSResolvConfFile";
770 options.ServerDNSSearchDomains = optionBool "ServerDNSSearchDomains";
771 options.ServerTransportPlugin = mkOption {
772 description = descriptionGeneric "ServerTransportPlugin";
774 type = with types; nullOr (submodule ({...}: {
776 transports = mkOption {
777 description = "List of pluggable transports";
779 example = ["obfs2" "obfs3" "obfs4" "scramblesuit"];
787 options.SocksPolicy = optionStrings "SocksPolicy" // {
788 example = ["accept *:*"];
790 options.SOCKSPort = mkOption {
791 description = descriptionGeneric "SOCKSPort";
792 default = if cfg.settings.HiddenServiceNonAnonymousMode == true then [{port = 0;}] else [];
793 example = [{port = 9090;}];
794 type = types.listOf optionSOCKSPort;
796 options.TestingTorNetwork = optionBool "TestingTorNetwork";
797 options.TransPort = optionIsolablePorts "TransPort";
798 options.TransProxyType = mkOption {
799 description = descriptionGeneric "TransProxyType";
800 type = with types; nullOr (enum ["default" "TPROXY" "ipfw" "pf-divert"]);
803 #options.TruncateLogFile
804 options.UnixSocksGroupWritable = optionBool "UnixSocksGroupWritable";
805 options.UseDefaultFallbackDirs = optionBool "UseDefaultFallbackDirs";
806 options.UseMicrodescriptors = optionBool "UseMicrodescriptors";
807 options.V3AuthUseLegacyKey = optionBool "V3AuthUseLegacyKey";
808 options.V3AuthoritativeDirectory = optionBool "V3AuthoritativeDirectory";
809 options.VersioningAuthoritativeDirectory = optionBool "VersioningAuthoritativeDirectory";
810 options.VirtualAddrNetworkIPv4 = optionString "VirtualAddrNetworkIPv4";
811 options.VirtualAddrNetworkIPv6 = optionString "VirtualAddrNetworkIPv6";
812 options.WarnPlaintextPorts = optionPorts "WarnPlaintextPorts";
818 config = mkIf cfg.enable {
819 # Not sure if `cfg.relay.role == "private-bridge"` helps as tor
820 # sends a lot of stats
821 warnings = optional (cfg.settings.BridgeRelay &&
822 flatten (mapAttrsToList (n: o: o.map) cfg.relay.onionServices) != [])
824 Running Tor hidden services on a public relay makes the
825 presence of hidden services visible through simple statistical
826 analysis of publicly available data.
827 See https://trac.torproject.org/projects/tor/ticket/8742
829 You can safely ignore this warning if you don't intend to
830 actually hide your hidden services. In either case, you can
831 always create a container/VM with a separate Tor daemon instance.
833 flatten (mapAttrsToList (n: o:
834 optional (o.settings.HiddenServiceVersion == 2) [
835 (optional (o.settings.HiddenServiceExportCircuitID != null) ''
836 HiddenServiceExportCircuitID is used in the HiddenService: ${n}
837 but this option is only for v3 hidden services.
840 optional (o.settings.HiddenServiceVersion != 2) [
841 (optional (o.settings.HiddenServiceAuthorizeClient != null) ''
842 HiddenServiceAuthorizeClient is used in the HiddenService: ${n}
843 but this option is only for v2 hidden services.
845 (optional (o.settings.RendPostPeriod != null) ''
846 RendPostPeriod is used in the HiddenService: ${n}
847 but this option is only for v2 hidden services.
850 ) cfg.relay.onionServices);
852 users.groups.tor.gid = config.ids.gids.tor;
854 { description = "Tor Daemon User";
858 uid = config.ids.uids.tor;
861 services.tor.settings = mkMerge [
862 (mkIf cfg.enableGeoIP {
863 GeoIPFile = "${cfg.package.geoip}/share/tor/geoip";
864 GeoIPv6File = "${cfg.package.geoip}/share/tor/geoip6";
866 (mkIf cfg.controlSocket.enable {
867 ControlPort = [ { unix = runDir + "/control"; GroupWritable=true; RelaxDirModeCheck=true; } ];
869 (mkIf cfg.relay.enable (
870 optionalAttrs (cfg.relay.role != "exit") {
871 ExitPolicy = mkForce ["reject *:*"];
873 optionalAttrs (elem cfg.relay.role ["bridge" "private-bridge"]) {
875 ExtORPort.port = mkDefault "auto";
876 ServerTransportPlugin.transports = mkDefault ["obfs4"];
877 ServerTransportPlugin.exec = mkDefault "${pkgs.obfs4}/bin/obfs4proxy managed";
878 } // optionalAttrs (cfg.relay.role == "private-bridge") {
879 ExtraInfoStatistics = false;
880 PublishServerDescriptor = false;
883 (mkIf (!cfg.relay.enable) {
884 # Avoid surprises when leaving ORPort/DirPort configurations in cfg.settings,
885 # because it would still enable Tor as a relay,
886 # which can trigger all sort of problems when not carefully done,
887 # like the blocklisting of the machine's IP addresses
888 # by some hosting providers...
889 DirPort = mkForce [];
891 PublishServerDescriptor = mkForce false;
893 (mkIf cfg.client.enable (
895 { addr = "127.0.0.1"; port = 9050; IsolateDestAddr = true; }
896 { addr = "127.0.0.1"; port = 9063; }
898 } // optionalAttrs cfg.client.transparentProxy.enable {
899 TransPort = [{ addr = "127.0.0.1"; port = 9040; }];
900 } // optionalAttrs cfg.client.dns.enable {
901 DNSPort = [{ addr = "127.0.0.1"; port = 9053; }];
902 AutomapHostsOnResolve = true;
903 AutomapHostsSuffixes = cfg.client.dns.automapHostsSuffixes;
904 } // optionalAttrs (flatten (mapAttrsToList (n: o: o.clientAuthorizations) cfg.client.onionServices) != []) {
905 ClientOnionAuthDir = runDir + "/ClientOnionAuthDir";
910 networking.firewall = mkIf cfg.openFirewall {
912 concatMap (o: optional (isInt o && o > 0 || o ? "port" && isInt o.port && o.port > 0) o.port)
919 systemd.services.tor = {
920 description = "Tor Daemon";
923 wantedBy = [ "multi-user.target" ];
924 after = [ "network.target" ];
925 restartTriggers = [ torrc ];
932 "${cfg.package}/bin/tor -f ${torrc} --verify-config"
933 # DOC: Appendix G of https://spec.torproject.org/rend-spec-v3
934 ("+" + pkgs.writeShellScript "ExecStartPre" (concatStringsSep "\n" (flatten (["set -eu"] ++
935 mapAttrsToList (name: onion:
936 optional (onion.authorizedClients != []) ''
937 rm -rf '${onion.path}/authorized_clients'
938 install -d -o tor -g tor -m 0700 '${onion.path}' '${onion.path}/authorized_clients'
942 install -o tor -g tor -m 0400 /dev/stdin '${onion.path}/authorized_clients/${toString i}.auth'
943 '') onion.authorizedClients ++
944 optional (onion.secretKey != null) ''
945 install -d -o tor -g tor -m 0700 '${onion.path}'
946 key="$(cut -f1 -d: '${onion.secretKey}')"
948 ("== ed25519v"*"-secret")
949 install -o tor -g tor -m 0400 '${onion.secretKey}' '${onion.path}/hs_ed25519_secret_key';;
950 (*) echo >&2 "NixOS does not (yet) support secret key type for onion: ${name}"; exit 1;;
953 ) cfg.relay.onionServices ++
954 mapAttrsToList (name: onion: imap0 (i: prvKeyPath:
955 let hostname = removeSuffix ".onion" name; in ''
956 printf "%s:" "${hostname}" | cat - '${prvKeyPath}' |
957 install -o tor -g tor -m 0700 /dev/stdin \
958 ${runDir}/ClientOnionAuthDir/${hostname}.${toString i}.auth_private
959 '') onion.clientAuthorizations)
960 cfg.client.onionServices
963 ExecStart = "${cfg.package}/bin/tor -f ${torrc}";
964 ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
965 KillSignal = "SIGINT";
967 Restart = "on-failure";
970 # g+x allows access to the control socket
973 # g+x can't be removed in ExecStart=, but will be removed by Tor
974 "tor/ClientOnionAuthDir"
976 RuntimeDirectoryMode = "0710";
977 StateDirectoryMode = "0700";
982 flatten (mapAttrsToList (name: onion:
983 optional (onion.secretKey == null) "tor/onion/${name}"
984 ) cfg.relay.onionServices);
985 # The following options are only to optimize:
986 # systemd-analyze security tor
987 RootDirectory = runDir + "/mnt-root";
988 RootDirectoryStartOnly = true;
989 #InaccessiblePaths = [ "-+${runDir}/mnt-root" ];
991 BindPaths = [ stateDir ];
992 BindReadOnlyPaths = [ storeDir "/etc" ];
993 AmbientCapabilities = [""] ++ lib.optional bindsPrivilegedPort "CAP_NET_BIND_SERVICE";
994 CapabilityBoundingSet = [""] ++ lib.optional bindsPrivilegedPort "CAP_NET_BIND_SERVICE";
995 # ProtectClock= adds DeviceAllow=char-rtc r
997 LockPersonality = true;
998 MemoryDenyWriteExecute = true;
999 NoNewPrivileges = true;
1000 PrivateDevices = true;
1001 PrivateMounts = true;
1002 PrivateNetwork = mkDefault false;
1004 # Tor cannot currently bind privileged port when PrivateUsers=true,
1005 # see https://gitlab.torproject.org/legacy/trac/-/issues/20930
1006 PrivateUsers = !bindsPrivilegedPort;
1007 ProtectClock = true;
1008 ProtectControlGroups = true;
1010 ProtectHostname = true;
1011 ProtectKernelLogs = true;
1012 ProtectKernelModules = true;
1013 ProtectKernelTunables = true;
1014 ProtectSystem = "strict";
1016 RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
1017 RestrictNamespaces = true;
1018 RestrictRealtime = true;
1019 RestrictSUIDSGID = true;
1020 # See also the finer but experimental option settings.Sandbox
1021 SystemCallFilter = [
1023 # Groups in @system-service which do not contain a syscall
1024 # listed by perf stat -e 'syscalls:sys_enter_*' tor
1025 # in tests, and seem likely not necessary for tor.
1026 "~@aio" "~@chown" "~@keyring" "~@memlock" "~@resources" "~@setuid" "~@timer"
1028 SystemCallArchitectures = "native";
1029 SystemCallErrorNumber = "EPERM";
1033 environment.systemPackages = [ cfg.package ];
1035 services.privoxy = mkIf (cfg.client.enable && cfg.client.privoxy.enable) {
1038 forward-socks5t / ${mkValueString "" { addr = "127.0.0.1"; port = 9063; }} .
1040 enable-remote-toggle 0
1041 enable-edit-actions 0
1042 enable-remote-http-toggle 0
1047 meta.maintainers = with lib.maintainers; [ julm ];