about summary refs log tree commit diff
path: root/nixos/modules/services/security/tor.nix
blob: 10596d6431d006250c11e46dc6de3f2023dcea08 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
{ config, lib, pkgs, ... }:

with lib;

let
  cfg = config.services.tor;
  torDirectory = "/var/lib/tor";

  opt    = name: value: optionalString (value != null) "${name} ${value}";
  optint = name: value: optionalString (value != 0)    "${name} ${toString value}";

  torRc = ''
    User tor
    DataDirectory ${torDirectory}
    ${optionalString cfg.enableGeoIP ''
      GeoIPFile ${pkgs.tor.geoip}/share/tor/geoip
      GeoIPv6File ${pkgs.tor.geoip}/share/tor/geoip6
    ''}

    ${optint "ControlPort" cfg.controlPort}
  ''
  # Client connection config
  + optionalString cfg.client.enable  ''
    SOCKSPort ${cfg.client.socksListenAddress} IsolateDestAddr
    SOCKSPort ${cfg.client.socksListenAddressFaster}
    ${opt "SocksPolicy" cfg.client.socksPolicy}
  ''
  # Relay config
  + optionalString cfg.relay.enable ''
    ORPort ${cfg.relay.portSpec}
    ${opt "Nickname" cfg.relay.nickname}
    ${opt "ContactInfo" cfg.relay.contactInfo}

    ${optint "RelayBandwidthRate" cfg.relay.bandwidthRate}
    ${optint "RelayBandwidthBurst" cfg.relay.bandwidthBurst}
    ${opt "AccountingMax" cfg.relay.accountingMax}
    ${opt "AccountingStart" cfg.relay.accountingStart}

    ${if cfg.relay.isExit then
        opt "ExitPolicy" cfg.relay.exitPolicy
      else
        "ExitPolicy reject *:*"}

    ${optionalString cfg.relay.isBridge ''
      BridgeRelay 1
      ServerTransportPlugin obfs2,obfs3 exec ${pkgs.pythonPackages.obfsproxy}/bin/obfsproxy managed
    ''}
  ''
  + cfg.extraConfig;

  torRcFile = pkgs.writeText "torrc" torRc;
in
{
  options = {
    services.tor = {
      enable = mkOption {
        type = types.bool;
        default = false;
        description = ''
          Enable the Tor daemon. By default, the daemon is run without
          relay, exit, bridge or client connectivity.
        '';
      };

      enableGeoIP = mkOption {
        type = types.bool;
        default = true;
        description = ''
          Whenever to configure Tor daemon to use GeoIP databases.

          Disabling this will disable by-country statistics for
          bridges and relays and some client and third-party software
          functionality.
        '';
      };

      extraConfig = mkOption {
        type = types.lines;
        default = "";
        description = ''
          Extra configuration. Contents will be added verbatim to the
          configuration file at the end.
        '';
      };

      controlPort = mkOption {
        type = types.int;
        default = 0;
        example = 9051;
        description = ''
          If set, Tor will accept connections on the specified port
          and allow them to control the tor process.
        '';
      };

      client = {
        enable = mkOption {
          type = types.bool;
          default = false;
          description = ''
            Whether to enable Tor daemon to route application
            connections.  You might want to disable this if you plan
            running a dedicated Tor relay.
          '';
        };

        socksListenAddress = mkOption {
          type = types.str;
          default = "127.0.0.1:9050";
          example = "192.168.0.1:9100";
          description = ''
            Bind to this address to listen for connections from
            Socks-speaking applications. Provides strong circuit
            isolation, separate circuit per IP address.
          '';
        };

        socksListenAddressFaster = mkOption {
          type = types.str;
          default = "127.0.0.1:9063";
          example = "192.168.0.1:9101";
          description = ''
            Bind to this address to listen for connections from
            Socks-speaking applications. Same as socksListenAddress
            but uses weaker circuit isolation to provide performance
            suitable for a web browser.
           '';
         };

        socksPolicy = mkOption {
          type = types.nullOr types.str;
          default = null;
          example = "accept 192.168.0.0/16, reject *";
          description = ''
            Entry policies to allow/deny SOCKS requests based on IP
            address.  First entry that matches wins. If no SocksPolicy
            is set, we accept all (and only) requests from
            SocksListenAddress.
          '';
        };

        privoxy.enable = mkOption {
          type = types.bool;
          default = true;
          description = ''
            Whether to enable and configure the system Privoxy to use Tor's
            faster port, suitable for HTTP.

            To have anonymity, protocols need to be scrubbed of identifying
            information, and this can be accomplished for HTTP by Privoxy.

            Privoxy can also be useful for KDE torification. A good setup would be:
            setting SOCKS proxy to the default Tor port, providing maximum
            circuit isolation where possible; and setting HTTP proxy to Privoxy
            to route HTTP traffic over faster, but less isolated port.
          '';
        };
      };

      relay = {
        enable = mkOption {
          type = types.bool;
          default = false;
          description = ''
            Whether to enable relaying TOR traffic for others.

            See https://www.torproject.org/docs/tor-doc-relay for details.
          '';
        };

        isBridge = mkOption {
          type = types.bool;
          default = false;
          description = ''
            Bridge relays (or "bridges") are Tor relays that aren't
            listed in the main directory. Since there is no complete
            public list of them, even if an ISP is filtering
            connections to all the known Tor relays, they probably
            won't be able to block all the bridges.

            A bridge relay can't be an exit relay.

            You need to set relay.enable to true for this option to
            take effect.

            The bridge is set up with an obfuscated transport proxy.

            See https://www.torproject.org/bridges.html.en for more info.
          '';
        };

        isExit = mkOption {
          type = types.bool;
          default = false;
          description = ''
            An exit relay allows Tor users to access regular Internet
            services.

            Unlike running a non-exit relay, running an exit relay may
            expose you to abuse complaints. See
            https://www.torproject.org/faq.html.en#ExitPolicies for
            more info.

            You can specify which services Tor users may access via
            your exit relay using exitPolicy option.
          '';
        };

        nickname = mkOption {
          type = types.str;
          default = "anonymous";
          description = ''
            A unique handle for your TOR relay.
          '';
        };

        contactInfo = mkOption {
          type = types.nullOr types.str;
          default = null;
          example = "admin@relay.com";
          description = ''
            Contact information for the relay owner (e.g. a mail
            address and GPG key ID).
          '';
        };

        accountingMax = mkOption {
          type = types.nullOr types.str;
          default = null;
          example = "450 GBytes";
          description = ''
            Specify maximum bandwidth allowed during an accounting
            period. This allows you to limit overall tor bandwidth
            over some time period. See the
            <literal>AccountingMax</literal> option by looking at the
            tor manual (<literal>man tor</literal>) for more.

            Note this limit applies individually to upload and
            download; if you specify <literal>"500 GBytes"</literal>
            here, then you may transfer up to 1 TBytes of overall
            bandwidth (500 GB upload, 500 GB download).
          '';
        };

        accountingStart = mkOption {
          type = types.nullOr types.str;
          default = null;
          example = "month 1 1:00";
          description = ''
            Specify length of an accounting period. This allows you to
            limit overall tor bandwidth over some time period. See the
            <literal>AccountingStart</literal> option by looking at
            the tor manual (<literal>man tor</literal>) for more.
          '';
        };

        bandwidthRate = mkOption {
          type = types.int;
          default = 0;
          example = 100;
          description = ''
            Specify this to limit the bandwidth usage of relayed (server)
            traffic. Your own traffic is still unthrottled. Units: bytes/second.
          '';
        };

        bandwidthBurst = mkOption {
          type = types.int;
          default = cfg.relay.bandwidthRate;
          example = 200;
          description = ''
            Specify this to allow bursts of the bandwidth usage of relayed (server)
            traffic. The average usage will still be as specified in relayBandwidthRate.
            Your own traffic is still unthrottled. Units: bytes/second.
          '';
        };

        portSpec = mkOption {
          type    = types.str;
          example = "143";
          description = ''
            What port to advertise for Tor connections. This corresponds
            to the <literal>ORPort</literal> section in the Tor manual; see
            <literal>man tor</literal> for more details.

            At a minimum, you should just specify the port for the
            relay to listen on; a common one like 143, 22, 80, or 443
            to help Tor users who may have very restrictive port-based
            firewalls.
          '';
        };

        exitPolicy = mkOption {
          type    = types.nullOr types.str;
          default = null;
          example = "accept *:6660-6667,reject *:*";
          description = ''
            A comma-separated list of exit policies. They're
            considered first to last, and the first match wins. If you
            want to _replace_ the default exit policy, end this with
            either a reject *:* or an accept *:*. Otherwise, you're
            _augmenting_ (prepending to) the default exit
            policy. Leave commented to just use the default, which is
            available in the man page or at
            https://www.torproject.org/documentation.html

            Look at https://www.torproject.org/faq-abuse.html#TypicalAbuses
            for issues you might encounter if you use the default exit policy.

            If certain IPs and ports are blocked externally, e.g. by
            your firewall, you should update your exit policy to
            reflect this -- otherwise Tor users will be told that
            those destinations are down.
          '';
        };
      };
    };
  };

  config = mkIf cfg.enable {
    assertions = singleton
      { message = "Can't be both an exit and a bridge relay at the same time";
        assertion =
          cfg.relay.enable -> !(cfg.relay.isBridge && cfg.relay.isExit);
      };

    users.extraGroups.tor.gid = config.ids.gids.tor;
    users.extraUsers.tor =
      { description = "Tor Daemon User";
        createHome  = true;
        home        = torDirectory;
        group       = "tor";
        uid         = config.ids.uids.tor;
      };

    systemd.services.tor =
      { description = "Tor Daemon";
        path = [ pkgs.tor ];

        wantedBy = [ "multi-user.target" ];
        after    = [ "network.target" ];
        restartTriggers = [ torRcFile ];

        # Translated from the upstream contrib/dist/tor.service.in
        serviceConfig =
          { Type         = "simple";
            ExecStartPre = "${pkgs.tor}/bin/tor -f ${torRcFile} --verify-config";
            ExecStart    = "${pkgs.tor}/bin/tor -f ${torRcFile} --RunAsDaemon 0";
            ExecReload   = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
            KillSignal   = "SIGINT";
            TimeoutSec   = 30;
            Restart      = "on-failure";
            LimitNOFILE  = 32768;

            # Hardening
            # Note: DevicePolicy is set to 'closed', although the
            # minimal permissions are really:
            #   DeviceAllow /dev/null rw
            #   DeviceAllow /dev/urandom r
            # .. but we can't specify DeviceAllow multiple times. 'closed'
            # is close enough.
            PrivateTmp              = "yes";
            DevicePolicy            = "closed";
            InaccessibleDirectories = "/home";
            ReadOnlyDirectories     = "/";
            ReadWriteDirectories    = torDirectory;
            NoNewPrivileges         = "yes";
          };
      };

    environment.systemPackages = [ pkgs.tor ];

    services.privoxy = mkIf (cfg.client.enable && cfg.client.privoxy.enable) {
      enable = true;
      extraConfig = ''
        forward-socks4a / ${cfg.client.socksListenAddressFaster} .
        toggle  1
        enable-remote-toggle 0
        enable-edit-actions 0
        enable-remote-http-toggle 0
      '';
    };
  };
}