Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

1010 config flag #326

Merged
merged 2 commits into from Aug 31, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
21 changes: 21 additions & 0 deletions docs/configuration.rst
Expand Up @@ -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
=====================
Expand All @@ -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]``
-----------------

Expand Down
48 changes: 42 additions & 6 deletions src/allmydata/node.py
Expand Up @@ -14,6 +14,21 @@
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():
Expand Down Expand Up @@ -61,6 +76,9 @@ def __str__(self):
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
Expand All @@ -84,6 +102,7 @@ def __init__(self, basedir=u"."):
assert type(self.nickname) is unicode

self.init_tempdir()
self.check_privacy()
self.init_connections()
self.set_tub_options()
self.create_main_tub()
Expand Down Expand Up @@ -166,6 +185,10 @@ def error_about_old_config_files(self):
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
Expand All @@ -175,9 +198,8 @@ def _make_tor_handler(self):
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):
Expand Down Expand Up @@ -215,9 +237,8 @@ def _make_i2p_handler(self):
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)
Expand Down Expand Up @@ -259,8 +280,17 @@ def init_connections(self):
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,
Expand Down Expand Up @@ -321,6 +351,8 @@ def get_tub_location(self, tubport):
# 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])
Expand All @@ -330,6 +362,10 @@ def get_tub_location(self, tubport):
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)

Expand Down
79 changes: 78 additions & 1 deletion src/allmydata/test/test_connections.py
Expand Up @@ -5,12 +5,13 @@
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"
Expand All @@ -29,6 +30,12 @@ def test_disabled(self):
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()
Expand Down Expand Up @@ -109,6 +116,12 @@ def test_disabled(self):
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()
Expand Down Expand Up @@ -204,8 +217,72 @@ def test_tor(self):
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")