diff --git a/nixos/modules/rename.nix b/nixos/modules/rename.nix
index 08146d1f568..2079ed544ae 100644
--- a/nixos/modules/rename.nix
+++ b/nixos/modules/rename.nix
@@ -25,6 +25,7 @@ with lib;
(mkRenamedOptionModule [ "services" "sslh" "host" ] [ "services" "sslh" "listenAddress" ])
(mkRenamedOptionModule [ "services" "statsd" "host" ] [ "services" "statsd" "listenAddress" ])
(mkRenamedOptionModule [ "services" "subsonic" "host" ] [ "services" "subsonic" "listenAddress" ])
+ (mkRenamedOptionModule [ "services" "tor" "relay" "portSpec" ] [ "services" "tor" "relay" "port" ])
(mkRenamedOptionModule [ "jobs" ] [ "systemd" "services" ])
(mkRenamedOptionModule [ "services" "gitlab" "stateDir" ] [ "services" "gitlab" "statePath" ])
@@ -195,6 +196,8 @@ with lib;
(mkRemovedOptionModule [ "services" "openvpn" "enable" ] "")
(mkRemovedOptionModule [ "services" "printing" "cupsFilesConf" ] "")
(mkRemovedOptionModule [ "services" "printing" "cupsdConf" ] "")
+ (mkRemovedOptionModule [ "services" "tor" "relay" "isBridge" ] "Use services.tor.relay.role instead.")
+ (mkRemovedOptionModule [ "services" "tor" "relay" "isExit" ] "Use services.tor.relay.role instead.")
(mkRemovedOptionModule [ "services" "xserver" "startGnuPGAgent" ]
"See the 16.09 release notes for more information.")
(mkRemovedOptionModule [ "services" "phpfpm" "phpIni" ] "")
diff --git a/nixos/modules/services/security/tor.nix b/nixos/modules/services/security/tor.nix
index 3f1450ebfbd..04b065f6ae4 100644
--- a/nixos/modules/services/security/tor.nix
+++ b/nixos/modules/services/security/tor.nix
@@ -7,7 +7,7 @@ let
torDirectory = "/var/lib/tor";
opt = name: value: optionalString (value != null) "${name} ${value}";
- optint = name: value: optionalString (value != 0) "${name} ${toString value}";
+ optint = name: value: optionalString (value != null && value != 0) "${name} ${toString value}";
torRc = ''
User tor
@@ -17,7 +17,7 @@ let
GeoIPv6File ${pkgs.tor.geoip}/share/tor/geoip6
''}
- ${optint "ControlPort" cfg.controlPort}
+ ${optint "ControlPort" (toString cfg.controlPort)}
''
# Client connection config
+ optionalString cfg.client.enable ''
@@ -27,7 +27,8 @@ let
''
# Relay config
+ optionalString cfg.relay.enable ''
- ORPort ${cfg.relay.portSpec}
+ ORPort ${toString cfg.relay.port}
+ ${opt "Address" cfg.relay.address}
${opt "Nickname" cfg.relay.nickname}
${opt "ContactInfo" cfg.relay.contactInfo}
@@ -36,31 +37,32 @@ let
${opt "AccountingMax" cfg.relay.accountingMax}
${opt "AccountingStart" cfg.relay.accountingStart}
- ${if cfg.relay.isExit then
+ ${if (cfg.relay.role == "exit") then
opt "ExitPolicy" cfg.relay.exitPolicy
else
"ExitPolicy reject *:*"}
- ${optionalString cfg.relay.isBridge ''
+ ${optionalString (elem cfg.relay.role ["bridge" "private-bridge"]) ''
BridgeRelay 1
ServerTransportPlugin obfs2,obfs3 exec ${pkgs.pythonPackages.obfsproxy}/bin/obfsproxy managed
+ ExtORPort auto
+ ${optionalString (cfg.relay.role == "private-bridge") ''
+ ExtraInfoStatistics 0
+ PublishServerDescriptor 0
+ ''}
''}
''
- + hiddenServices
+ # Hidden services
+ + concatStrings (flip mapAttrsToList cfg.hiddenServices (n: v: ''
+ HiddenServiceDir ${torDirectory}/onion/${v.name}
+ ${flip concatMapStrings v.map (p: ''
+ HiddenServicePort ${toString p.port} ${p.destination}
+ '')}
+ ''))
+ cfg.extraConfig;
- hiddenServices = concatStrings (mapAttrsToList (hiddenServiceDir: hs:
- let
- hsports = concatStringsSep "\n" (map mkHiddenServicePort hs.hiddenServicePorts);
- in
- "HiddenServiceDir ${hiddenServiceDir}\n${hsports}\n${hs.extraConfig}\n"
- ) cfg.hiddenServices);
-
- mkHiddenServicePort = hsport: let
- trgt = optionalString (hsport.target != null) (" " + hsport.target);
- in "HiddenServicePort ${toString hsport.virtualPort}${trgt}";
-
torRcFile = pkgs.writeText "torrc" torRc;
+
in
{
options = {
@@ -96,8 +98,8 @@ in
};
controlPort = mkOption {
- type = types.int;
- default = 0;
+ type = types.nullOr (types.either types.int types.str);
+ default = null;
example = 9051;
description = ''
If set, Tor will accept connections on the specified port
@@ -133,9 +135,10 @@ in
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.
+ Socks-speaking applications. Same as
+ but uses weaker
+ circuit isolation to provide performance suitable for a
+ web browser.
'';
};
@@ -145,9 +148,9 @@ in
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
+ address. First entry that matches wins. If no SocksPolicy
is set, we accept all (and only) requests from
- SocksListenAddress.
+ .
'';
};
@@ -176,45 +179,147 @@ in
description = ''
Whether to enable relaying TOR traffic for others.
- See https://www.torproject.org/docs/tor-doc-relay for details.
+ See
+ for details.
+
+ Setting this to true requires setting
+
+ and
+
+ options.
'';
};
- isBridge = mkOption {
- type = types.bool;
- default = false;
+ role = mkOption {
+ type = types.enum [ "exit" "relay" "bridge" "private-bridge" ];
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.
+ Your role in Tor network. There're several options:
- A bridge relay can't be an exit relay.
+
+
+ exit
+
+
+ An exit relay. This allows Tor users to access regular
+ Internet services through your public IP.
+
- You need to set relay.enable to true for this option to
- take effect.
+
+ Running an exit relay may expose you to abuse
+ complaints. See
+
+ for more info.
+
- The bridge is set up with an obfuscated transport proxy.
+
+ You can specify which services Tor users may access via
+ your exit relay using option.
+
+
+
- See https://www.torproject.org/bridges.html.en for more info.
- '';
- };
+
+ relay
+
+
+ Regular relay. This allows Tor users to relay onion
+ traffic to other Tor nodes, but not to public
+ Internet.
+
- isExit = mkOption {
- type = types.bool;
- default = false;
- description = ''
- An exit relay allows Tor users to access regular Internet
- services.
+
+ Note that some misconfigured and/or disrespectful
+ towards privacy sites will block you even if your
+ relay is not an exit relay. That is, just being listed
+ in a public relay directory can have unwanted
+ consequences.
- 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.
+ Which means you might not want to use
+ this role if you browse public Internet from the same
+ network as your relay, unless you want to write
+ e-mails to those sites (you should!).
+
- You can specify which services Tor users may access via
- your exit relay using exitPolicy option.
+
+ See
+
+ for more info.
+
+
+
+
+
+ bridge
+
+
+ Regular bridge. Works like a regular relay, but
+ doesn't list you in the public relay directory and
+ hides your Tor node behind obfsproxy.
+
+
+
+ Using this option will make Tor advertise your bridge
+ to users through various mechanisms like
+ , though.
+
+
+
+
+ WARNING: THE FOLLOWING PARAGRAPH IS NOT LEGAL ADVISE.
+ Consult with your lawer when in doubt.
+
+
+
+ This role should be safe to use in most situations
+ (unless the act of forwarding traffic for others is
+ a punishable offence under your local laws, which
+ would be pretty insane as it would make ISP
+ illegal).
+
+
+
+
+ See
+ for more info.
+
+
+
+
+
+ private-bridge
+
+
+ Private bridge. Works like regular bridge, but does
+ not advertise your node in any way.
+
+
+
+ Using this role means that you won't contribute to Tor
+ network in any way unless you advertise your node
+ yourself in some way.
+
+
+
+ Use this if you want to run a private bridge, for
+ example because you'll give out your bridge address
+ manually to your friends.
+
+
+
+ Switching to this role after measurable time in
+ "bridge" role is pretty useless as some Tor users
+ would have learned about your node already. In the
+ latter case you can still change
+ option.
+
+
+
+ See
+ for more info.
+
+
+
+
'';
};
@@ -268,8 +373,8 @@ in
};
bandwidthRate = mkOption {
- type = types.int;
- default = 0;
+ type = types.nullOr types.int;
+ default = null;
example = 100;
description = ''
Specify this to limit the bandwidth usage of relayed (server)
@@ -278,7 +383,7 @@ in
};
bandwidthBurst = mkOption {
- type = types.int;
+ type = types.nullOr types.int;
default = cfg.relay.bandwidthRate;
example = 200;
description = ''
@@ -288,9 +393,19 @@ in
'';
};
- portSpec = mkOption {
- type = types.str;
- example = "143";
+ address = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "noname.example.com";
+ description = ''
+ The IP address or full DNS name for advertised address of your relay.
+ Leave unset and Tor will guess.
+ '';
+ };
+
+ port = mkOption {
+ type = types.either types.int types.str;
+ example = 143;
description = ''
What port to advertise for Tor connections. This corresponds to the
ORPort section in the Tor manual; see
@@ -313,13 +428,15 @@ in
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
+ _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.
+ Look at
+
+ 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
@@ -330,79 +447,122 @@ in
};
hiddenServices = mkOption {
- type = types.attrsOf (types.submodule ({
+ description = ''
+ A set of static hidden services that terminate their Tor
+ circuits at this node.
+
+ Every element in this set declares a virtual onion host.
+
+ You can specify your onion address by putting corresponding
+ private key to an appropriate place in ${torDirectory}.
+
+ For services without private keys in ${torDirectory} Tor
+ daemon will generate random key pairs (which implies random
+ onion addresses) on restart. The latter could take a while,
+ please be patient.
+
+
+ Hidden services can be useful even if you don't intend to
+ actually hide them, since they can
+ also be seen as a kind of NAT traversal mechanism.
+
+ E.g. the example will make your sshd, whatever runs on
+ "8080" and your mail server available from anywhere where
+ the Tor network is available (which, with the help from
+ bridges, is pretty much everywhere), even if both client
+ and server machines are behind NAT you have no control
+ over.
+
+ '';
+ default = {};
+ example = literalExample ''
+ { "my-hidden-service-example".map = [
+ { port = 22; } # map ssh port to this machine's ssh
+ { port = 80; toPort = 8080; } # map http port to whatever runs on 8080
+ { port = "sip"; toHost = "mail.example.com"; toPort = "imap"; } # because we can
+ ];
+ }
+ '';
+ type = types.loaOf (types.submodule ({name, config, ...}: {
options = {
- hiddenServicePorts = mkOption {
- type = types.listOf (types.submodule {
- options = {
- virtualPort = mkOption {
- type = types.int;
- example = 80;
- description = "Virtual port.";
- };
- target = mkOption {
- type = types.nullOr types.str;
- default = null;
- example = "127.0.0.1:8080";
- description = ''
- Target virtual Port shall be mapped to.
- You may override the target port, address, or both by
- specifying a target of addr, port, addr:port, or
- unix:path. (You can specify an IPv6 target as
- [addr]:port. Unix paths may be quoted, and may use
- standard C escapes.)
- '';
- };
- };
- });
- example = [ { virtualPort = 80; target = "127.0.0.1:8080"; } { virtualPort = 6667; } ];
- description = ''
- If target is null the virtual port is mapped
- to the same port on 127.0.0.1 over TCP. You may use
- target to overwrite this behaviour (see
- description of target).
+ name = mkOption {
+ type = types.str;
+ description = ''
+ Name of this tor hidden service.
- This corresponds to the HiddenServicePort VIRTPORT
- [TARGET] option by looking at the tor manual
- tor
- 1 for more information.
- '';
- };
- extraConfig = mkOption {
- type = types.str;
- default = "";
- description = ''
- Extra configuration. Contents will be added in the current
- hidden service context.
- '';
- };
+ This is purely descriptive.
+
+ After restarting Tor daemon you should be able to
+ find your .onion address in
+ ${torDirectory}/onion/$name/hostname.
+ '';
+ };
+
+ map = mkOption {
+ default = [];
+ description = "Port mapping for this hidden service.";
+ type = types.listOf (types.submodule ({config, ...}: {
+ options = {
+
+ port = mkOption {
+ type = types.either types.int types.str;
+ example = 80;
+ description = ''
+ Hidden service port to "bind to".
+ '';
+ };
+
+ destination = mkOption {
+ internal = true;
+ type = types.str;
+ description = "Forward these connections where?";
+ };
+
+ toHost = mkOption {
+ type = types.str;
+ default = "127.0.0.1";
+ description = "Mapping destination host.";
+ };
+
+ toPort = mkOption {
+ type = types.either types.int types.str;
+ example = 8080;
+ description = "Mapping destination port.";
+ };
+
+ };
+
+ config = {
+ toPort = mkDefault config.port;
+ destination = mkDefault "${config.toHost}:${toString config.toPort}";
+ };
+ }));
+ };
+
+ };
+
+ config = {
+ name = mkDefault name;
};
}));
- default = {};
- example = {
- "/var/lib/tor/webserver" = {
- hiddenServicePorts = [ { virtualPort = 80; } ];
- };
- };
- description = ''
- Configure hidden services.
-
- Please consult the tor manual
- tor
- 1 for a more detailed
- explanation. (search for 'HIDDEN').
- '';
};
};
};
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);
- };
+ # Not sure if `cfg.relay.role == "private-bridge"` helps as tor
+ # sends a lot of stats
+ warnings = optional (cfg.relay.enable && cfg.hiddenServices != {})
+ ''
+ Running Tor hidden services on a public relay makes the
+ presence of hidden services visible through simple statistical
+ analysis of publicly available data.
+
+ You can safely ignore this warning if you don't intend to
+ actually hide your hidden services. In either case, you can
+ always create a container/VM with a separate Tor daemon instance.
+ '';
users.extraGroups.tor.gid = config.ids.gids.tor;
users.extraUsers.tor =
@@ -422,9 +582,13 @@ in
restartTriggers = [ torRcFile ];
# Translated from the upstream contrib/dist/tor.service.in
+ preStart = ''
+ install -o tor -g tor -d ${torDirectory}/onion
+ ${pkgs.tor}/bin/tor -f ${torRcFile} --verify-config
+ '';
+
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";