]> Git — Sourcephile - sourcephile-nix.git/blob - nixos/modules/services/security/tor.nix
tor: improve type-checking and hardening (bis)
[sourcephile-nix.git] / nixos / modules / services / security / tor.nix
1 { config, lib, pkgs, ... }:
2
3 with builtins;
4 with lib;
5
6 let
7 cfg = config.services.tor;
8 stateDir = "/var/lib/tor";
9 runDir = "/run/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}"/>.
13 '';
14 bindsPrivilegedPort =
15 any (p0:
16 let p1 = if p0 ? "port" then p0.port else p0; in
17 p1 == "auto" || (
18 let p2 = if isInt p1 then p1 else toInt p1;
19 in p1 != null && 0 < p2 && p2 < 1024))
20 (flatten [
21 cfg.settings.ORPort
22 cfg.settings.DirPort
23 cfg.settings.DNSPort
24 cfg.settings.ExtORPort
25 cfg.settings.HTTPTunnelPort
26 cfg.settings.NATDPort
27 cfg.settings.SOCKSPort
28 cfg.settings.TransPort
29 ]);
30 optionBool = optionName: mkOption {
31 type = with types; nullOr bool;
32 default = null;
33 description = descriptionGeneric optionName;
34 };
35 optionInt = optionName: mkOption {
36 type = with types; nullOr int;
37 default = null;
38 description = descriptionGeneric optionName;
39 };
40 optionString = optionName: mkOption {
41 type = with types; nullOr str;
42 default = null;
43 description = descriptionGeneric optionName;
44 };
45 optionStrings = optionName: mkOption {
46 type = with types; listOf str;
47 default = [];
48 description = descriptionGeneric optionName;
49 };
50 optionAddress = mkOption {
51 type = with types; nullOr str;
52 default = null;
53 example = "0.0.0.0";
54 description = ''
55 IPv4 or IPv6 (if between brackets) address.
56 '';
57 };
58 optionUnix = mkOption {
59 type = with types; nullOr path;
60 default = null;
61 description = ''
62 Unix domain socket path to use.
63 '';
64 };
65 optionPort = mkOption {
66 type = with types; nullOr (oneOf [port (enum ["auto"])]);
67 default = null;
68 };
69 optionPorts = optionName: mkOption {
70 type = with types; listOf port;
71 default = [];
72 description = descriptionGeneric optionName;
73 };
74 optionIsolablePort = with types; oneOf [
75 port (enum ["auto"])
76 (submodule ({config, ...}: {
77 options = {
78 addr = optionAddress;
79 port = optionPort;
80 flags = optionFlags;
81 SessionGroup = mkOption { type = nullOr int; default = null; };
82 } // genAttrs isolateFlags (name: mkOption { type = types.bool; default = false; });
83 config = {
84 flags = filter (name: config.${name} == true) isolateFlags ++
85 optional (config.SessionGroup != null) "SessionGroup=${toString config.SessionGroup}";
86 };
87 }))
88 ];
89 optionIsolablePorts = optionName: mkOption {
90 default = [];
91 type = with types; either optionIsolablePort (listOf optionIsolablePort);
92 description = descriptionGeneric optionName;
93 };
94 isolateFlags = [
95 "IsolateClientAddr"
96 "IsolateClientProtocol"
97 "IsolateDestAddr"
98 "IsolateDestPort"
99 "IsolateSOCKSAuth"
100 "KeepAliveIsolateSOCKSAuth"
101 ];
102 optionSOCKSPort = let
103 flags = [
104 "CacheDNS" "CacheIPv4DNS" "CacheIPv6DNS" "GroupWritable" "IPv6Traffic"
105 "NoDNSRequest" "NoIPv4Traffic" "NoOnionTraffic" "OnionTrafficOnly"
106 "PreferIPv6" "PreferIPv6Automap" "PreferSOCKSNoAuth" "UseDNSCache"
107 "UseIPv4Cache" "UseIPv6Cache" "WorldWritable"
108 ] ++ isolateFlags;
109 in with types; oneOf [
110 port (submodule ({config, ...}: {
111 options = {
112 unix = optionUnix;
113 addr = optionAddress;
114 port = optionPort;
115 flags = optionFlags;
116 SessionGroup = mkOption { type = nullOr int; default = null; };
117 } // genAttrs flags (name: mkOption { type = types.bool; default = false; });
118 config = {
119 flags = filter (name: config.${name} == true) flags ++
120 optional (config.SessionGroup != null) "SessionGroup=${toString config.SessionGroup}";
121 };
122 }))
123 ];
124 optionFlags = mkOption {
125 type = with types; listOf str;
126 default = [];
127 };
128 optionORPort = optionName: mkOption {
129 default = [];
130 example = 443;
131 type = with types; oneOf [port (enum ["auto"]) (listOf (oneOf [
132 port
133 (enum ["auto"])
134 (submodule ({config, ...}:
135 let flags = [ "IPv4Only" "IPv6Only" "NoAdvertise" "NoListen" ];
136 in {
137 options = {
138 addr = optionAddress;
139 port = optionPort;
140 flags = optionFlags;
141 } // genAttrs flags (name: mkOption { type = types.bool; default = false; });
142 config = {
143 flags = filter (name: config.${name} == true) flags;
144 };
145 }))
146 ]))];
147 description = descriptionGeneric optionName;
148 };
149 optionBandwith = optionName: mkOption {
150 type = with types; nullOr (either int str);
151 default = null;
152 description = descriptionGeneric optionName;
153 };
154 optionPath = optionName: mkOption {
155 type = with types; nullOr path;
156 default = null;
157 description = descriptionGeneric optionName;
158 };
159
160 mkValueString = k: v:
161 if v == null then ""
162 else if isBool v then
163 (if v then "1" else "0")
164 else if v ? "unix" && v.unix != null then
165 "unix:"+v.unix +
166 optionalString (v ? "flags") (" " + concatStringsSep " " v.flags)
167 else if v ? "port" && v.port != null then
168 optionalString (v ? "addr" && v.addr != null) "${v.addr}:" +
169 toString v.port +
170 optionalString (v ? "flags") (" " + concatStringsSep " " v.flags)
171 else if k == "ServerTransportPlugin" then
172 optionalString (v.transports != []) "${concatStringsSep "," v.transports} exec ${v.exec}"
173 else if k == "HidServAuth" then
174 concatMapStringsSep "\n${k} " (settings: settings.onion + " " settings.auth) v
175 else generators.mkValueStringDefault {} v;
176 genTorrc = settings:
177 generators.toKeyValue {
178 listsAsDuplicateKeys = true;
179 mkKeyValue = k: generators.mkKeyValueDefault { mkValueString = mkValueString k; } " " k;
180 }
181 (lib.mapAttrs (k: v:
182 # Not necesssary, but prettier rendering
183 if elem k [ "AutomapHostsSuffixes" "DirPolicy" "ExitPolicy" "SocksPolicy" ]
184 && v != []
185 then concatStringsSep "," v
186 else v)
187 (lib.filterAttrs (k: v: !(v == null || v == ""))
188 settings));
189 torrc = pkgs.writeText "torrc" (
190 genTorrc cfg.settings +
191 concatStrings (mapAttrsToList (name: config:
192 "HiddenServiceDir ${config.path}\n" +
193 genTorrc config.settings) cfg.relay.hiddenServices)
194 );
195 in
196 {
197 imports = [
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" ])
223 ];
224
225 options = {
226 services.tor = {
227 enable = mkEnableOption ''Tor daemon.
228 By default, the daemon is run without
229 relay, exit, bridge or client connectivity'';
230
231 openFirewall = mkEnableOption "opening of the relay port(s) in the firewall";
232
233 package = mkOption {
234 type = types.package;
235 default = pkgs.tor;
236 defaultText = "pkgs.tor";
237 example = literalExample "pkgs.tor";
238 description = "Tor package to use.";
239 };
240
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; };
244
245 controlSocket.enable = mkEnableOption ''control socket,
246 created in <literal>${runDir}/control</literal>'';
247
248 client = {
249 enable = mkEnableOption ''the routing of application connections.
250 You might want to disable this if you plan running a dedicated Tor relay'';
251
252 transparentProxy.enable = mkEnableOption "transparent proxy";
253 dns.enable = mkEnableOption "DNS resolver";
254
255 privoxy.enable = lib.mkEnableOption ''Privoxy system,
256 to use Tor's faster port, suitable for HTTP.
257
258 To have anonymity, protocols need to be scrubbed of identifying
259 information, and this can be accomplished for HTTP by Privoxy.
260
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).
265 '';
266
267 hiddenServices = mkOption {
268 description = descriptionGeneric "HiddenServiceDir";
269 default = {};
270 example = {
271 "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" = {
272 clientAuthorizations = ["/run/keys/tor/alice.prv.x25519"];
273 };
274 };
275 type = types.attrsOf (types.submodule ({name, config, ...}: {
276 options.clientAuthorizations = mkOption {
277 description = ''
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;
283 default = [];
284 example = ["/run/keys/tor/alice.prv.x25519"];
285 };
286 }));
287 };
288 };
289
290 relay = {
291 enable = mkEnableOption ''relaying of Tor traffic for others.
292
293 See <link xlink:href="https://www.torproject.org/docs/tor-doc-relay" />
294 for details.
295
296 Setting this to true requires setting
297 <option>services.tor.relay.role</option>
298 and
299 <option>services.tor.settings.ORPort</option>
300 options'';
301
302 role = mkOption {
303 type = types.enum [ "exit" "relay" "bridge" "private-bridge" ];
304 description = ''
305 Your role in Tor network. There're several options:
306
307 <variablelist>
308 <varlistentry>
309 <term><literal>exit</literal></term>
310 <listitem>
311 <para>
312 An exit relay. This allows Tor users to access regular
313 Internet services through your public IP.
314 </para>
315
316 <important><para>
317 Running an exit relay may expose you to abuse
318 complaints. See
319 <link xlink:href="https://www.torproject.org/faq.html.en#ExitPolicies"/>
320 for more info.
321 </para></important>
322
323 <para>
324 You can specify which services Tor users may access via
325 your exit relay using <option>settings.ExitPolicy</option> option.
326 </para>
327 </listitem>
328 </varlistentry>
329
330 <varlistentry>
331 <term><literal>relay</literal></term>
332 <listitem>
333 <para>
334 Regular relay. This allows Tor users to relay onion
335 traffic to other Tor nodes, but not to public
336 Internet.
337 </para>
338
339 <important><para>
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
344 consequences.
345
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!).
350 </para></important>
351
352 <para>
353 See
354 <link xlink:href="https://www.torproject.org/docs/tor-doc-relay.html.en" />
355 for more info.
356 </para>
357 </listitem>
358 </varlistentry>
359
360 <varlistentry>
361 <term><literal>bridge</literal></term>
362 <listitem>
363 <para>
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.
367 </para>
368
369 <para>
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.
373 </para>
374
375 <important>
376 <para>
377 WARNING: THE FOLLOWING PARAGRAPH IS NOT LEGAL ADVICE.
378 Consult with your lawyer when in doubt.
379 </para>
380
381 <para>
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).
386 </para>
387 </important>
388
389 <para>
390 See <link xlink:href="https://www.torproject.org/docs/bridges.html.en" />
391 for more info.
392 </para>
393 </listitem>
394 </varlistentry>
395
396 <varlistentry>
397 <term><literal>private-bridge</literal></term>
398 <listitem>
399 <para>
400 Private bridge. Works like regular bridge, but does
401 not advertise your node in any way.
402 </para>
403
404 <para>
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.
408 </para>
409
410 <para>
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.
414 </para>
415
416 <para>
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.
422 </para>
423
424 <para>
425 See <link xlink:href="https://www.torproject.org/docs/bridges.html.en" />
426 for more info.
427 </para>
428 </listitem>
429 </varlistentry>
430 </variablelist>
431 '';
432 };
433
434 hiddenServices = mkOption {
435 description = descriptionGeneric "HiddenServiceDir";
436 default = {};
437 example = {
438 "example.org/www" = {
439 map = [ 80 ];
440 authorizedClients = [
441 "descriptor:x25519:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
442 ];
443 };
444 };
445 type = types.attrsOf (types.submodule ({name, config, ...}: {
446 options.path = mkOption {
447 type = types.path;
448 default = stateDir + "/onion/${name}";
449 description = "Path where to store the data files of the hidden service.";
450 };
451 /*
452 options.hostname = mkOption {
453 type = types.str;
454 default = null;
455 description = ''
456 Path where to store the data files of the hidden service.
457 '';
458 };
459 */
460 options.authorizeClient = mkOption {
461 description = descriptionGeneric "HiddenServiceAuthorizeClient";
462 default = null;
463 type = types.nullOr (types.submodule ({...}: {
464 options = {
465 authType = mkOption {
466 type = types.enum [ "basic" "stealth" ];
467 description = ''
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.
471 '';
472 };
473 clientNames = mkOption {
474 type = types.nonEmptyListOf (types.strMatching "[A-Za-z0-9+-_]+");
475 description = ''
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"/>.
480 '';
481 };
482 };
483 }));
484 };
485 options.authorizedClients = mkOption {
486 description = ''
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;
492 default = [];
493 example = ["descriptor:x25519:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"];
494 };
495 options.map = mkOption {
496 description = descriptionGeneric "HiddenServicePort";
497 type = with types; listOf (oneOf [
498 port (submodule ({...}: {
499 options = {
500 port = optionPort;
501 target = mkOption {
502 default = null;
503 type = nullOr (submodule ({...}: {
504 options = {
505 unix = optionUnix;
506 addr = optionAddress;
507 port = optionPort;
508 };
509 }));
510 };
511 };
512 }))
513 ]);
514 apply = map (v: if isInt v then {port=v; target=null;} else v);
515 };
516 options.version = mkOption {
517 description = descriptionGeneric "HiddenServiceVersion";
518 type = with types; nullOr (enum [2 3]);
519 default = null;
520 };
521 options.settings = mkOption {
522 default = {};
523 type = types.submodule {
524 freeformType = with types;
525 (attrsOf (nullOr (oneOf [str int bool (listOf str)]))) // {
526 description = "settings option";
527 };
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"]);
533 default = null;
534 };
535 options.HiddenServiceMaxStreams = mkOption {
536 description = descriptionGeneric "HiddenServiceMaxStreams";
537 type = with types; nullOr (ints.between 0 65535);
538 default = null;
539 };
540 options.HiddenServiceMaxStreamsCloseCircuit = optionBool "HiddenServiceMaxStreamsCloseCircuit";
541 options.HiddenServiceNumIntroductionPoints = mkOption {
542 description = descriptionGeneric "HiddenServiceNumIntroductionPoints";
543 type = with types; nullOr (ints.between 0 20);
544 default = null;
545 };
546 options.HiddenServiceSingleHopMode = optionBool "HiddenServiceSingleHopMode";
547 options.RendPostPeriod = optionString "RendPostPeriod";
548 };
549 };
550 config = {
551 settings.HiddenServiceVersion = config.version;
552 settings.HiddenServiceAuthorizeClient =
553 if config.authorizeClient != null then
554 config.authorizeClient.authType + " " +
555 concatStringsSep "," config.authorizeClient.clientNames
556 else null;
557 settings.HiddenServicePort = map (p: mkValueString "" p.port + " " + mkValueString "" p.target) config.map;
558 };
559 }));
560 };
561 };
562
563 settings = mkOption {
564 description = ''
565 See <link xlink:href="https://2019.www.torproject.org/docs/tor-manual.html.en">torrc manual</link>
566 for documentation.
567 '';
568 default = {};
569 type = types.submodule {
570 freeformType = with types;
571 (attrsOf (nullOr (oneOf [str int bool (listOf str)]))) // {
572 description = "settings option";
573 };
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"];
588 };
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";
601 default = null;
602 type = with types; nullOr path;
603 };
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";
614 default = [];
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"];
619 in {
620 options = {
621 unix = optionUnix;
622 flags = optionFlags;
623 addr = optionAddress;
624 port = optionPort;
625 } // genAttrs flags (name: mkOption { type = types.bool; default = false; });
626 config = {
627 flags = filter (name: config.${name} == true) flags;
628 };
629 }))
630 ]))];
631 };
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;
650 default = [];
651 example = ["accept *:*"];
652 };
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 *:*"];
669 };
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";
676 default = null;
677 type = with types; nullOr (oneOf [
678 port (enum ["auto"]) (submodule ({...}: {
679 options = {
680 addr = optionAddress;
681 port = optionPort;
682 };
683 }))
684 ]);
685 apply = p:
686 if isInt p || isString p
687 then { port = p; }
688 else p;
689 };
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";
708 default = [];
709 type = with types; listOf (oneOf [
710 (submodule ({config, ...}: {
711 options = {
712 onion = mkOption {
713 # FIXME: is the regexp correctly written?
714 type = strMatching "[a-z2-7]\\{16\\}\\(\\.onion\\)?";
715 example = "xxxxxxxxxxxxxxxx.onion";
716 };
717 auth = mkOption {
718 type = strMatching "[A-Za-z0-9+/]\\{22\\}";
719 };
720 };
721 }))
722 ]);
723 };
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"]);
753 default = null;
754 };
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";
760 #options.RunAsDaemon
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";
770 default = null;
771 type = with types; nullOr (submodule ({...}: {
772 options = {
773 transports = mkOption {
774 description = "List of pluggable transports";
775 type = listOf str;
776 example = ["obfs2" "obfs3" "obfs4" "scramblesuit"];
777 };
778 exec = mkOption {
779 type = types.str;
780 };
781 };
782 }));
783 };
784 options.SocksPolicy = optionStrings "SocksPolicy" // {
785 example = ["accept *:*"];
786 };
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;
792 };
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"]);
798 default = null;
799 };
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";
810 };
811 };
812 };
813 };
814
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) != [])
820 ''
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
825
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.
829 '' ++
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.
835 '')
836 ] ++
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.
841 '')
842 (optional (h.settings.RendPostPeriod != null) ''
843 RendPostPeriod is used in the HiddenService: ${n}
844 but this option is only for v2 hidden services.
845 '')
846 ]
847 ) cfg.relay.hiddenServices);
848
849 users.groups.tor.gid = config.ids.gids.tor;
850 users.users.tor =
851 { description = "Tor Daemon User";
852 createHome = true;
853 home = stateDir;
854 group = "tor";
855 uid = config.ids.uids.tor;
856 };
857
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";
862 })
863 (mkIf cfg.controlSocket.enable {
864 ControlPort = [ { unix = runDir + "/control"; GroupWritable=true; RelaxDirModeCheck=true; } ];
865 })
866 (mkIf cfg.relay.enable (
867 optionalAttrs (cfg.relay.role != "exit") {
868 ExitPolicy = mkForce ["reject *:*"];
869 } //
870 optionalAttrs (elem cfg.relay.role ["bridge" "private-bridge"]) {
871 BridgeRelay = true;
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;
878 }
879 ))
880 (mkIf cfg.client.enable (
881 { SOCKSPort = [
882 { addr = "127.0.0.1"; port = 9050; IsolateDestAddr = true; }
883 { addr = "127.0.0.1"; port = 9063; }
884 ];
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";
893 }
894 ))
895 ];
896
897 networking.firewall = mkIf cfg.openFirewall {
898 allowedTCPPorts =
899 map (o: optional (isInt o && o > 0 || o ? "port" && isInt o.port && o.port > 0) o.port)
900 (flatten [
901 cfg.settings.ORPort
902 cfg.settings.DirPort
903 ]);
904 };
905
906 systemd.services.tor = {
907 description = "Tor Daemon";
908 path = [ pkgs.tor ];
909
910 wantedBy = [ "multi-user.target" ];
911 after = [ "network.target" ];
912 restartTriggers = [ torrc ];
913
914 serviceConfig = {
915 Type = "simple";
916 User = "tor";
917 Group = "tor";
918 ExecStartPre = [
919 "${cfg.package}/bin/tor -f ${torrc} --verify-config"
920 ] ++
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"
926 rm -rf "$out"
927 install -d -o tor -g tor -m 0700 "$out"
928 ''] ++ imap0 (i: pubKey: ''
929 echo ${pubKey} |
930 install -o tor -g tor -m 0400 /dev/stdin $out/${toString i}.auth
931 '') h.authorizedClients
932 )
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
941 ))))];
942 ExecStart = "${cfg.package}/bin/tor -f ${torrc}";
943 ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
944 KillSignal = "SIGINT";
945 TimeoutSec = 30;
946 Restart = "on-failure";
947 LimitNOFILE = 32768;
948 RuntimeDirectory = [ "tor" "tor/mnt-root" "tor/ClientOnionAuthDir" ];
949 RuntimeDirectoryMode = "0750";
950 StateDirectoryMode = "0700";
951 StateDirectory =
952 [ "tor" "tor/onion" ] ++
953 mapAttrsToList (name: v: "tor/onion/${name}") cfg.relay.hiddenServices;
954
955 # The following are only to optimize:
956 # systemd-analyze security tor
957 RootDirectory = rootDir;
958 RootDirectoryStartOnly = true;
959 #InaccessiblePaths = [ "-+${rootDir}" ];
960 UMask = "0066";
961 BindPaths = [
962 stateDir
963 ];
964 BindReadOnlyPaths = [
965 storeDir
966 "/etc"
967 ];
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
971 DeviceAllow = "";
972 LockPersonality = true;
973 MemoryDenyWriteExecute = true;
974 NoNewPrivileges = true;
975 PrivateDevices = true;
976 PrivateMounts = true;
977 PrivateNetwork = mkDefault false;
978 PrivateTmp = true;
979 # Tor cannot currently bind privileged port when PrivateUsers=true,
980 # see https://gitlab.torproject.org/legacy/trac/-/issues/20930
981 PrivateUsers = !bindsPrivilegedPort;
982 ProtectClock = true;
983 ProtectControlGroups = true;
984 ProtectHome = true;
985 ProtectHostname = true;
986 ProtectKernelLogs = true;
987 ProtectKernelModules = true;
988 ProtectKernelTunables = true;
989 ProtectSystem = "strict";
990 RemoveIPC = true;
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
996 SystemCallFilter = [
997 "@system-service"
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"
1002 ];
1003 SystemCallArchitectures = "native";
1004 SystemCallErrorNumber = "EPERM";
1005 };
1006 };
1007
1008 environment.systemPackages = [ cfg.package ];
1009
1010 services.privoxy = mkIf (cfg.client.enable && cfg.client.privoxy.enable) {
1011 enable = true;
1012 extraConfig = ''
1013 forward-socks5t / ${mkValueString "" { addr = "127.0.0.1"; port = 9063; }} .
1014 toggle 1
1015 enable-remote-toggle 0
1016 enable-edit-actions 0
1017 enable-remote-http-toggle 0
1018 '';
1019 };
1020 };
1021
1022 meta.maintainers = with lib.maintainers; [ julm ];
1023 }