summary refs log tree commit diff
path: root/nixos/modules
diff options
context:
space:
mode:
authorJoachim F <joachifm@users.noreply.github.com>2017-08-24 08:12:59 +0000
committerGitHub <noreply@github.com>2017-08-24 08:12:59 +0000
commit9447b8b9cd9ac1ff8e5d051de752e7f78c8bbca5 (patch)
tree3b92d337794dcac8a20f8a74da259757a4e04997 /nixos/modules
parentab3de8ed6c976caecf4fda637aa25d5c64ffa5b2 (diff)
parent2c4a925ab07e4ebfd932730b358771e3547be605 (diff)
downloadnixlib-9447b8b9cd9ac1ff8e5d051de752e7f78c8bbca5.tar
nixlib-9447b8b9cd9ac1ff8e5d051de752e7f78c8bbca5.tar.gz
nixlib-9447b8b9cd9ac1ff8e5d051de752e7f78c8bbca5.tar.bz2
nixlib-9447b8b9cd9ac1ff8e5d051de752e7f78c8bbca5.tar.lz
nixlib-9447b8b9cd9ac1ff8e5d051de752e7f78c8bbca5.tar.xz
nixlib-9447b8b9cd9ac1ff8e5d051de752e7f78c8bbca5.tar.zst
nixlib-9447b8b9cd9ac1ff8e5d051de752e7f78c8bbca5.zip
Merge pull request #28338 from oxij/nixos/better-tor
nixos: better tor config
Diffstat (limited to 'nixos/modules')
-rw-r--r--nixos/modules/rename.nix3
-rw-r--r--nixos/modules/services/security/tor.nix434
2 files changed, 302 insertions, 135 deletions
diff --git a/nixos/modules/rename.nix b/nixos/modules/rename.nix
index 08146d1f5687..2079ed544aee 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 3f1450ebfbd7..04b065f6ae4b 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
+            <option>socksListenAddress</option> 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.
+            <option>socksListenAddress</option>.
           '';
         };
 
@@ -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.
-          '';
-        };
-
-        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.
+            See <link xlink:href="https://www.torproject.org/docs/tor-doc-relay" />
+            for details.
 
-            The bridge is set up with an obfuscated transport proxy.
-
-            See https://www.torproject.org/bridges.html.en for more info.
+            Setting this to true requires setting
+            <option>services.tor.relay.role</option>
+            and
+            <option>services.tor.relay.port</option>
+            options.
           '';
         };
 
-        isExit = mkOption {
-          type = types.bool;
-          default = false;
+        role = mkOption {
+          type = types.enum [ "exit" "relay" "bridge" "private-bridge" ];
           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.
+            Your role in Tor network. There're several options:
+
+            <variablelist>
+            <varlistentry>
+              <term><literal>exit</literal></term>
+              <listitem>
+                <para>
+                  An exit relay. This allows Tor users to access regular
+                  Internet services through your public IP.
+                </para>
+
+                <important><para>
+                  Running an exit relay may expose you to abuse
+                  complaints. See
+                  <link xlink:href="https://www.torproject.org/faq.html.en#ExitPolicies" />
+                  for more info.
+                </para></important>
+
+                <para>
+                  You can specify which services Tor users may access via
+                  your exit relay using <option>exitPolicy</option> option.
+                </para>
+              </listitem>
+            </varlistentry>
+
+            <varlistentry>
+              <term><literal>relay</literal></term>
+              <listitem>
+                <para>
+                  Regular relay. This allows Tor users to relay onion
+                  traffic to other Tor nodes, but not to public
+                  Internet.
+                </para>
+
+                <important><para>
+                  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.
+
+                  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!).
+                </para></important>
+
+                <para>
+                  See
+                  <link xlink:href="https://www.torproject.org/docs/tor-doc-relay.html.en" />
+                  for more info.
+                </para>
+              </listitem>
+            </varlistentry>
+
+            <varlistentry>
+              <term><literal>bridge</literal></term>
+              <listitem>
+                <para>
+                  Regular bridge. Works like a regular relay, but
+                  doesn't list you in the public relay directory and
+                  hides your Tor node behind obfsproxy.
+                </para>
+
+                <para>
+                  Using this option will make Tor advertise your bridge
+                  to users through various mechanisms like
+                  <link xlink:href="https://bridges.torproject.org/" />, though.
+                </para>
+
+                <important>
+                  <para>
+                    WARNING: THE FOLLOWING PARAGRAPH IS NOT LEGAL ADVISE.
+                    Consult with your lawer when in doubt.
+                  </para>
+
+                  <para>
+                    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).
+                  </para>
+                </important>
+
+                <para>
+                  See <link xlink:href="https://www.torproject.org/docs/bridges.html.en" />
+                  for more info.
+                </para>
+              </listitem>
+            </varlistentry>
+
+            <varlistentry>
+              <term><literal>private-bridge</literal></term>
+              <listitem>
+                <para>
+                  Private bridge. Works like regular bridge, but does
+                  not advertise your node in any way.
+                </para>
+
+                <para>
+                  Using this role means that you won't contribute to Tor
+                  network in any way unless you advertise your node
+                  yourself in some way.
+                </para>
+
+                <para>
+                  Use this if you want to run a private bridge, for
+                  example because you'll give out your bridge address
+                  manually to your friends.
+                </para>
+
+                <para>
+                  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>port</option> option.
+                </para>
+
+                <para>
+                  See <link xlink:href="https://www.torproject.org/docs/bridges.html.en" />
+                  for more info.
+                </para>
+              </listitem>
+            </varlistentry>
+            </variablelist>
           '';
         };
 
@@ -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
             <literal>ORPort</literal> 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
+            <link xlink:href="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
+            <link xlink:href="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
@@ -330,79 +447,122 @@ in
       };
 
       hiddenServices = mkOption {
-        type = types.attrsOf (types.submodule ({
-          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 <literal>null</literal> the virtual port is mapped
-                to the same port on 127.0.0.1 over TCP. You may use
-                <literal>target</literal> to overwrite this behaviour (see
-                description of target).
-
-                This corresponds to the <literal>HiddenServicePort VIRTPORT
-                [TARGET]</literal> option by looking at the tor manual
-                <citerefentry><refentrytitle>tor</refentrytitle>
-                <manvolnum>1</manvolnum></citerefentry> for more information.
-              '';
-            };
-            extraConfig = mkOption {
-              type = types.str;
-              default = "";
-              description = ''
-                Extra configuration. Contents will be added in the current
-                hidden service context.
-              '';
-            };
-          };
-        }));
+        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.
+
+          <note><para>
+            Hidden services can be useful even if you don't intend to
+            actually <emphasis>hide</emphasis> 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.
+          </para></note>
+        '';
         default = {};
-        example = {
-          "/var/lib/tor/webserver" = {
-            hiddenServicePorts = [ { virtualPort = 80; } ];
+        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 = {
+
+             name = mkOption {
+               type = types.str;
+               description = ''
+                 Name of this tor hidden service.
+
+                 This is purely descriptive.
+
+                 After restarting Tor daemon you should be able to
+                 find your .onion address in
+                 <literal>${torDirectory}/onion/$name/hostname</literal>.
+               '';
+             };
+
+             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}";
+                 };
+               }));
+             };
+
           };
-        };
-        description = ''
-          Configure hidden services.
 
-          Please consult the tor manual
-          <citerefentry><refentrytitle>tor</refentrytitle>
-          <manvolnum>1</manvolnum></citerefentry> for a more detailed
-          explanation. (search for 'HIDDEN').
-        '';
+          config = {
+            name = mkDefault name;
+          };
+        }));
       };
     };
   };
 
   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";