Merge branch '1010-config-flag'

This commit is contained in:
Brian Warner 2016-08-31 12:23:52 -07:00
commit 4b21f605ad
3 changed files with 141 additions and 7 deletions

View File

@ -324,6 +324,23 @@ set the ``tub.location`` option described below.
used for files that usually (on a Unix system) go into ``/tmp``. The
string will be interpreted relative to the node's base directory.
``reveal-IP-address = (boolean, optional, defaults to True)``
This is a safety flag. If False, any of the following configuration
problems will cause ``tahoe start`` to throw a PrivacyError instead of
starting the node:
* ``[node] tub.location`` contains any ``tcp:`` hints
* ``[node] tub.location`` uses ``AUTO``, or is missing/empty (because
that defaults to AUTO)
* ``[connections] tcp =`` is set to ``tcp`` (or left as the default),
rather than being set to ``tor``
These configuration problems would reveal the node's IP address to
servers and external networks.
Connection Management
=====================
@ -334,6 +351,10 @@ also controls when Tor and I2P are used: for all TCP connections (to hide
your IP address), or only when necessary (just for servers which declare that
they need Tor, because they use ``.onion`` addresses).
Note that if you want to protect your node's IP address, you should set
``[node] reveal-IP-address = False``, which will refuse to launch the node if
any of the other configuration settings might violate this privacy property.
``[connections]``
-----------------

View File

@ -14,6 +14,21 @@ from allmydata.util.fileutil import abspath_expanduser_unicode
from allmydata.util.encodingutil import get_filesystem_encoding, quote_output
from allmydata.util import configutil
def _import_tor():
# this exists to be overridden by unit tests
try:
from foolscap.connections import tor
return tor
except ImportError: # pragma: no cover
return None
def _import_i2p():
try:
from foolscap.connections import i2p
return i2p
except ImportError: # pragma: no cover
return None
# Add our application versions to the data that Foolscap's LogPublisher
# reports.
for thing, things_version in get_package_versions().iteritems():
@ -61,6 +76,9 @@ class UnescapedHashError(Exception):
return ("The configuration entry %s contained an unescaped '#' character."
% quote_output("[%s]%s = %s" % self.args))
class PrivacyError(Exception):
"""reveal-IP-address = false, but the node is configured in such a way
that the IP address could be revealed"""
class Node(service.MultiService):
# this implements common functionality of both Client nodes and Introducer
@ -84,6 +102,7 @@ class Node(service.MultiService):
assert type(self.nickname) is unicode
self.init_tempdir()
self.check_privacy()
self.init_connections()
self.set_tub_options()
self.create_main_tub()
@ -166,6 +185,10 @@ class Node(service.MultiService):
twlog.msg(e)
raise e
def check_privacy(self):
self._reveal_ip = self.get_config("node", "reveal-IP-address", True,
boolean=True)
def _make_tcp_handler(self):
# this is always available
from foolscap.connections.tcp import default
@ -175,9 +198,8 @@ class Node(service.MultiService):
enabled = self.get_config("tor", "enable", True, boolean=True)
if not enabled:
return None
try:
from foolscap.connections import tor
except ImportError:
tor = _import_tor()
if not tor:
return None
if self.get_config("tor", "launch", False, boolean=True):
@ -215,9 +237,8 @@ class Node(service.MultiService):
enabled = self.get_config("i2p", "enable", True, boolean=True)
if not enabled:
return None
try:
from foolscap.connections import i2p
except ImportError:
i2p = _import_i2p()
if not i2p:
return None
samport = self.get_config("i2p", "sam.port", None)
@ -259,8 +280,17 @@ class Node(service.MultiService):
raise ValueError("'tahoe.cfg [connections] tcp='"
" uses unknown handler type '%s'"
% tcp_handler_name)
if not handlers[tcp_handler_name]:
raise ValueError("'tahoe.cfg [connections] tcp=' uses "
"unavailable/unimportable handler type '%s'. "
"Please pip install tahoe-lafs[%s] to fix."
% (tcp_handler_name, tcp_handler_name))
self._default_connection_handlers["tcp"] = tcp_handler_name
if not self._reveal_ip:
if self._default_connection_handlers["tcp"] == "tcp":
raise PrivacyError("tcp = tcp, must be set to 'tor'")
def set_tub_options(self):
self.tub_options = {
"logLocalFailures": True,
@ -321,6 +351,8 @@ class Node(service.MultiService):
# addresses. Don't probe for local addresses unless necessary.
split_location = location.split(",")
if "AUTO" in split_location:
if not self._reveal_ip:
raise PrivacyError("tub.location uses AUTO")
local_addresses = iputil.get_local_addresses_sync()
# tubport must be like "tcp:12345" or "tcp:12345:morestuff"
local_portnum = int(tubport.split(":")[1])
@ -330,6 +362,10 @@ class Node(service.MultiService):
new_locations.extend(["tcp:%s:%d" % (ip, local_portnum)
for ip in local_addresses])
else:
if not self._reveal_ip:
hint_type = loc.split(":")[0]
if hint_type == "tcp":
raise PrivacyError("tub.location includes tcp: hint")
new_locations.append(loc)
return ",".join(new_locations)

View File

@ -5,12 +5,13 @@ from twisted.trial import unittest
from twisted.internet import reactor, endpoints
from ConfigParser import SafeConfigParser
from foolscap.connections import tcp
from ..node import Node
from ..node import Node, PrivacyError
class FakeNode(Node):
def __init__(self, config_str):
self.config = SafeConfigParser()
self.config.readfp(BytesIO(config_str))
self._reveal_ip = True
BASECONFIG = ("[client]\n"
"introducer.furl = \n"
@ -29,6 +30,12 @@ class Tor(unittest.TestCase):
h = n._make_tor_handler()
self.assertEqual(h, None)
def test_unimportable(self):
n = FakeNode(BASECONFIG)
with mock.patch("allmydata.node._import_tor", return_value=None):
h = n._make_tor_handler()
self.assertEqual(h, None)
def test_default(self):
n = FakeNode(BASECONFIG)
h1 = mock.Mock()
@ -109,6 +116,12 @@ class I2P(unittest.TestCase):
h = n._make_i2p_handler()
self.assertEqual(h, None)
def test_unimportable(self):
n = FakeNode(BASECONFIG)
with mock.patch("allmydata.node._import_i2p", return_value=None):
h = n._make_i2p_handler()
self.assertEqual(h, None)
def test_default(self):
n = FakeNode(BASECONFIG)
h1 = mock.Mock()
@ -204,8 +217,72 @@ class Connections(unittest.TestCase):
self.assertEqual(n._default_connection_handlers["tor"], "tor")
self.assertEqual(n._default_connection_handlers["i2p"], "i2p")
def test_tor_unimportable(self):
n = FakeNode(BASECONFIG+"[connections]\ntcp = tor\n")
with mock.patch("allmydata.node._import_tor", return_value=None):
e = self.assertRaises(ValueError, n.init_connections)
self.assertEqual(str(e),
"'tahoe.cfg [connections] tcp='"
" uses unavailable/unimportable handler type 'tor'."
" Please pip install tahoe-lafs[tor] to fix.")
def test_unknown(self):
n = FakeNode(BASECONFIG+"[connections]\ntcp = unknown\n")
e = self.assertRaises(ValueError, n.init_connections)
self.assertIn("'tahoe.cfg [connections] tcp='", str(e))
self.assertIn("uses unknown handler type 'unknown'", str(e))
class Privacy(unittest.TestCase):
def test_flag(self):
n = FakeNode(BASECONFIG)
n.check_privacy()
self.assertTrue(n._reveal_ip)
n = FakeNode(BASECONFIG+"[node]\nreveal-IP-address = true\n")
n.check_privacy()
self.assertTrue(n._reveal_ip)
n = FakeNode(BASECONFIG+"[node]\nreveal-IP-address = false\n")
n.check_privacy()
self.assertFalse(n._reveal_ip)
n = FakeNode(BASECONFIG+"[node]\nreveal-ip-address = false\n")
n.check_privacy()
self.assertFalse(n._reveal_ip)
def test_connections(self):
n = FakeNode(BASECONFIG+"[node]\nreveal-IP-address = false\n")
n.check_privacy()
e = self.assertRaises(PrivacyError, n.init_connections)
self.assertEqual(str(e), "tcp = tcp, must be set to 'tor'")
def test_tub_location_auto(self):
n = FakeNode(BASECONFIG+"[node]\nreveal-IP-address = false\n")
n._portnumfile = "missing"
n.check_privacy()
e = self.assertRaises(PrivacyError, n.get_tub_location, None)
self.assertEqual(str(e), "tub.location uses AUTO")
n = FakeNode(BASECONFIG+"[node]\nreveal-IP-address = false\n" +
"tub.location = AUTO\n")
n._portnumfile = "missing"
n.check_privacy()
e = self.assertRaises(PrivacyError, n.get_tub_location, None)
self.assertEqual(str(e), "tub.location uses AUTO")
n = FakeNode(BASECONFIG+"[node]\nreveal-IP-address = false\n" +
"tub.location = AUTO,tcp:hostname:1234\n")
n._portnumfile = "missing"
n.check_privacy()
e = self.assertRaises(PrivacyError, n.get_tub_location, None)
self.assertEqual(str(e), "tub.location uses AUTO")
def test_tub_location_tcp(self):
n = FakeNode(BASECONFIG+"[node]\nreveal-IP-address = false\n" +
"tub.location = tcp:hostname:1234\n")
n._portnumfile = "missing"
n.check_privacy()
e = self.assertRaises(PrivacyError, n.get_tub_location, None)
self.assertEqual(str(e), "tub.location includes tcp: hint")