nixos: overhaul Tor module

This overhauls the Tor module in a few ways:

  - Uses systemd service files, including hardening/config checks
  - Removed old privoxy support; users should use the Tor Browser
    instead.
  - Remove 'fast' circuit/SOCKS port; most users don't care (and it adds
    added complexity and confusion)
  - Added support for bandwidth accounting
  - Removed old relay listenAddress option; taken over by portSpec
  - Formatting, description, code cleanups.

Signed-off-by: Austin Seipp <aseipp@pobox.com>
This commit is contained in:
Austin Seipp 2014-12-06 03:37:31 -06:00
parent e5e8efc1f4
commit bc10c92377
2 changed files with 199 additions and 195 deletions

View File

@ -212,6 +212,7 @@
privoxy = 32; privoxy = 32;
disnix = 33; disnix = 33;
osgi = 34; osgi = 34;
tor = 35;
ghostOne = 40; ghostOne = 40;
git = 41; git = 41;
fourstore = 42; fourstore = 42;

View File

@ -3,120 +3,116 @@
with lib; with lib;
let let
inherit (pkgs) tor privoxy;
stateDir = "/var/lib/tor";
privoxyDir = stateDir+"/privoxy";
cfg = config.services.tor; cfg = config.services.tor;
torDirectory = "/var/lib/tor";
torUser = "tor"; opt = name: value: optionalString (value != null) "${name} ${value}";
optint = name: value: optionalString (value != 0) "${name} ${toString value}";
opt = name: value: if value != "" then "${name} ${value}" else ""; torRc = ''
optint = name: value: if value != 0 then "${name} ${toString value}" else ""; User tor
DataDirectory ${torDirectory}
${optint "ControlPort" cfg.controlPort}
''
# Client connection config
+ optionalString cfg.client.enable ''
SOCKSPort ${cfg.client.socksListenAddress}
${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 in
{ {
###### interface
options = { options = {
services.tor = { 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.
'';
};
config = mkOption { extraConfig = mkOption {
type = types.str;
default = ""; default = "";
description = '' description = ''
Extra configuration. Contents will be added verbatim to the Extra configuration. Contents will be added verbatim to the
configuration file. 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 = { client = {
enable = mkOption { enable = mkOption {
type = types.bool;
default = false; default = false;
description = '' description = ''
Whether to enable Tor daemon to route application connections. Whether to enable Tor daemon to route application
You might want to disable this if you plan running a dedicated Tor relay. connections. You might want to disable this if you plan
running a dedicated Tor relay.
''; '';
}; };
socksListenAddress = mkOption { socksListenAddress = mkOption {
type = types.str;
default = "127.0.0.1:9050"; default = "127.0.0.1:9050";
example = "192.168.0.1:9100"; example = "192.168.0.1:9100";
description = '' description = ''
Bind to this address to listen for connections from Socks-speaking Bind to this address to listen for connections from
applications. Socks-speaking applications.
'';
};
socksListenAddressFaster = mkOption {
default = "127.0.0.1:9063";
description = ''
Same as socksListenAddress but uses weaker circuit isolation to provide
performance suitable for a web browser.
''; '';
}; };
socksPolicy = mkOption { socksPolicy = mkOption {
default = ""; type = types.nullOr types.str;
default = null;
example = "accept 192.168.0.0/16, reject *"; example = "accept 192.168.0.0/16, reject *";
description = '' description = ''
Entry policies to allow/deny SOCKS requests based on IP address. Entry policies to allow/deny SOCKS requests based on IP
First entry that matches wins. If no SocksPolicy is set, we accept address. First entry that matches wins. If no SocksPolicy
all (and only) requests from SocksListenAddress. is set, we accept all (and only) requests from
SocksListenAddress.
''; '';
}; };
privoxy = {
enable = mkOption {
default = true;
description = ''
Whether to enable a special instance of privoxy dedicated to Tor.
To have anonymity, protocols need to be scrubbed of identifying
information.
Most people using Tor want to anonymize their web traffic, so by
default we enable an special instance of privoxy specifically for
Tor.
However, if you are only going to use Tor only for other kinds of
traffic then you can disable this option.
'';
};
listenAddress = mkOption {
default = "127.0.0.1:8118";
description = ''
Address that Tor's instance of privoxy is listening to.
*This does not configure the standard NixOS instance of privoxy.*
This is for Tor connections only!
See services.privoxy.listenAddress to configure the standard NixOS
instace of privoxy.
'';
};
config = mkOption {
default = "";
description = ''
Extra configuration for Tor's instance of privoxy. Contents will be
added verbatim to the configuration file.
*This does not configure the standard NixOS instance of privoxy.*
This is for Tor connections only!
See services.privoxy.extraConfig to configure the standard NixOS
instace of privoxy.
'';
};
};
}; };
relay = { relay = {
enable = mkOption { enable = mkOption {
type = types.bool;
default = false; default = false;
description = '' description = ''
Whether to enable relaying TOR traffic for others. Whether to enable relaying TOR traffic for others.
@ -126,16 +122,19 @@ in
}; };
isBridge = mkOption { isBridge = mkOption {
type = types.bool;
default = false; default = false;
description = '' description = ''
Bridge relays (or "bridges" ) are Tor relays that aren't listed in the Bridge relays (or "bridges") are Tor relays that aren't
main directory. Since there is no complete public list of them, even if an listed in the main directory. Since there is no complete
ISP is filtering connections to all the known Tor relays, they probably 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. won't be able to block all the bridges.
A bridge relay can't be an exit relay. A bridge relay can't be an exit relay.
You need to set relay.enable to true for this option to take effect. You need to set relay.enable to true for this option to
take effect.
The bridge is set up with an obfuscated transport proxy. The bridge is set up with an obfuscated transport proxy.
@ -144,25 +143,72 @@ in
}; };
isExit = mkOption { isExit = mkOption {
type = types.bool;
default = false; default = false;
description = '' description = ''
An exit relay allows Tor users to access regular Internet services. An exit relay allows Tor users to access regular Internet
services.
Unlike running a non-exit relay, running an exit relay may expose Unlike running a non-exit relay, running an exit relay may
you to abuse complaints. See https://www.torproject.org/faq.html.en#ExitPolicies for more info. 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. You can specify which services Tor users may access via
your exit relay using exitPolicy option.
''; '';
}; };
nickname = mkOption { nickname = mkOption {
type = types.str;
default = "anonymous"; default = "anonymous";
description = '' description = ''
A unique handle for your TOR relay. 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 { bandwidthRate = mkOption {
type = types.int;
default = 0; default = 0;
example = 100; example = 100;
description = '' description = ''
@ -172,6 +218,7 @@ in
}; };
bandwidthBurst = mkOption { bandwidthBurst = mkOption {
type = types.int;
default = cfg.relay.bandwidthRate; default = cfg.relay.bandwidthRate;
example = 200; example = 200;
description = '' description = ''
@ -181,143 +228,99 @@ in
''; '';
}; };
port = mkOption { portSpec = mkOption {
default = 9001; type = types.str;
example = "143";
description = '' description = ''
What port to advertise for Tor connections. 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.
listenAddress = mkOption { At a minimum, you should just specify the port for the
default = ""; relay to listen on; a common one like 143, 22, 80, or 443
example = "0.0.0.0:9090"; to help Tor users who may have very restrictive port-based
description = '' firewalls.
Set this if you need to listen on a port other than the one advertised
in relayPort (e.g. to advertise 443 but bind to 9090). You'll need to do
ipchains or other port forwsarding yourself to make this work.
''; '';
}; };
exitPolicy = mkOption { exitPolicy = mkOption {
default = ""; type = types.nullOr types.str;
default = null;
example = "accept *:6660-6667,reject *:*"; example = "accept *:6660-6667,reject *:*";
description = '' description = ''
A comma-separated list of exit policies. They're considered first A comma-separated list of exit policies. They're
to last, and the first match wins. If you want to _replace_ considered first to last, and the first match wins. If you
the default exit policy, end this with either a reject *:* or an want to _replace_ the default exit policy, end this with
accept *:*. Otherwise, you're _augmenting_ (prepending to) the either a reject *:* or an accept *:*. Otherwise, you're
default exit policy. Leave commented to just use the default, which is _augmenting_ (prepending to) the default exit
available in the man page or at https://www.torproject.org/documentation.html 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 Look at https://www.torproject.org/faq-abuse.html#TypicalAbuses
for issues you might encounter if you use the default exit policy. 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, If certain IPs and ports are blocked externally, e.g. by
you should update your exit policy to reflect this -- otherwise Tor your firewall, you should update your exit policy to
users will be told that those destinations are down. reflect this -- otherwise Tor users will be told that
those destinations are down.
''; '';
}; };
}; };
}; };
}; };
config = mkIf cfg.enable {
###### implementation
config = mkIf (cfg.client.enable || cfg.relay.enable) {
assertions = singleton assertions = singleton
{ assertion = cfg.relay.enable -> !(cfg.relay.isBridge && cfg.relay.isExit); { message = "Can't be both an exit and a bridge relay at the same time";
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.extraUsers = singleton users.extraGroups.tor.gid = config.ids.gids.tor;
{ name = torUser; users.extraUsers.tor =
uid = config.ids.uids.tor; { description = "Tor Daemon User";
description = "Tor daemon user"; createHome = true;
home = stateDir; home = torDirectory;
group = "tor";
uid = config.ids.uids.tor;
}; };
jobs = { systemd.services.tor =
tor = { name = "tor"; { description = "Tor Daemon";
path = [ pkgs.tor ];
startOn = "started network-interfaces"; wantedBy = [ "multi-user.target" ];
stopOn = "stopping network-interfaces"; after = [ "network.target" ];
restartTriggers = [ torRcFile ];
preStart = '' # Translated from the upstream contrib/dist/tor.service.in
mkdir -m 0755 -p ${stateDir} serviceConfig =
chown ${torUser} ${stateDir} { Type = "simple";
''; ExecStartPre = "${pkgs.tor}/bin/tor -f ${torRcFile} --verify-config";
exec = "${tor}/bin/tor -f ${pkgs.writeText "torrc" cfg.config}"; ExecStart = "${pkgs.tor}/bin/tor -f ${torRcFile} --RunAsDaemon 0";
}; } ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
// optionalAttrs (cfg.client.privoxy.enable && cfg.client.enable) { KillSignal = "SIGINT";
torPrivoxy = { name = "tor-privoxy"; TimeoutSec = 30;
Restart = "on-failure";
LimitNOFILE = 32768;
startOn = "started network-interfaces"; # Hardening
stopOn = "stopping network-interfaces"; # Note: DevicePolicy is set to 'closed', although the
# minimal permissions are really:
preStart = '' # DeviceAllow /dev/null rw
mkdir -m 0755 -p ${privoxyDir} # DeviceAllow /dev/urandom r
chown ${torUser} ${privoxyDir} # .. but we can't specify DeviceAllow multiple times. 'closed'
''; # is close enough.
exec = "${privoxy}/sbin/privoxy --no-daemon --user ${torUser} ${pkgs.writeText "torPrivoxy.conf" cfg.client.privoxy.config}"; PrivateTmp = "yes";
}; }; DevicePolicy = "closed";
InaccessibleDirectories = "/home";
services.tor.config = '' ReadOnlyDirectories = "/";
DataDirectory ${stateDir} ReadWriteDirectories = torDirectory;
User ${torUser} NoNewPrivileges = "yes";
'' };
+ optionalString cfg.client.enable '' };
SOCKSPort ${cfg.client.socksListenAddress} IsolateDestAddr
SOCKSPort ${cfg.client.socksListenAddressFaster}
${opt "SocksPolicy" cfg.client.socksPolicy}
''
+ optionalString cfg.relay.enable ''
ORPort ${toString cfg.relay.port}
${opt "ORListenAddress" cfg.relay.listenAddress }
${opt "Nickname" cfg.relay.nickname}
${optint "RelayBandwidthRate" cfg.relay.bandwidthRate}
${optint "RelayBandwidthBurst" cfg.relay.bandwidthBurst}
${if cfg.relay.isExit then opt "ExitPolicy" cfg.relay.exitPolicy else "ExitPolicy reject *:*"}
${if cfg.relay.isBridge then ''
BridgeRelay 1
ServerTransportPlugin obfs2,obfs3 exec ${pkgs.pythonPackages.obfsproxy}/bin/obfsproxy managed
'' else ""}
'';
services.tor.client.privoxy.config = ''
# Generally, this file goes in /etc/privoxy/config
#
# Tor listens as a SOCKS4a proxy here:
forward-socks4a / ${cfg.client.socksListenAddressFaster} .
confdir ${privoxy}/etc
logdir ${privoxyDir}
# actionsfile standard # Internal purpose, recommended
actionsfile default.action # Main actions file
actionsfile user.action # User customizations
filterfile default.filter
# Don't log interesting things, only startup messages, warnings and errors
logfile logfile
#jarfile jarfile
#debug 0 # show each GET/POST/CONNECT request
debug 4096 # Startup banner and warnings
debug 8192 # Errors - *we highly recommended enabling this*
user-manual ${privoxy}/doc/privoxy/user-manual
listen-address ${cfg.client.privoxy.listenAddress}
toggle 1
enable-remote-toggle 0
enable-edit-actions 0
enable-remote-http-toggle 0
buffer-limit 4096
# Extra config goes here
'';
environment.systemPackages = [ pkgs.tor ];
}; };
} }