]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/commitdiff
Make introducer.furl unguessable. Closes #1802.
authorBrian Warner <warner@lothar.com>
Wed, 20 Mar 2013 22:10:47 +0000 (15:10 -0700)
committerDavid-Sarah Hopwood <david-sarah@jacaranda.org>
Wed, 20 Mar 2013 22:40:33 +0000 (22:40 +0000)
Previously, Introducers always used a swissnum of "introducer", so
anyone who could learn the (public) tubid of the introducer would be
able to connect to and use it. This changes new Introducers to use the
same randomly-generated swissnum as clients and storage servers do, so
that you absolutely must learn the introducer.furl from someone who
knows it already before you can connect.

This change also moves the location of the file that stores
introducer.furl from BASEDIR/introducer.furl to
BASEDIR/private/introducer.furl, since that's where we keep the private
things. The first time an introducer is started with the new code, it
will move any existing BASEDIR/introducer.furl into the new place.

Note that this will not change the FURL of existing introducers: it will
only affect newly created ones. When you change an introducer's FURL,
you must also update all of the nodes (clients and storage servers)
which connect to it, so upgrading it to an unguessable one isn't
something we should do automatically.

docs/configuration.rst
docs/frontends/CLI.rst
docs/running.rst
src/allmydata/introducer/server.py
src/allmydata/test/test_introducer.py
src/allmydata/test/test_runner.py

index b737a3d263601192cb807da703aa0a6caa306dfe..f3db298017113d72c162f94c1d7f8caeef22b460 100644 (file)
@@ -294,9 +294,9 @@ Client Configuration
 
     This FURL tells the client how to connect to the introducer. Each
     Tahoe-LAFS grid is defined by an introducer. The introducer's FURL is
-    created by the introducer node and written into its base directory when
-    it starts, whereupon it should be published to everyone who wishes to
-    attach a client to that grid
+    created by the introducer node and written into its private base
+    directory when it starts, whereupon it should be published to everyone
+    who wishes to attach a client to that grid
 
 ``helper.furl = (FURL string, optional)``
 
@@ -507,7 +507,7 @@ the others.
 
 The Introducer node maintains some different state than regular client nodes.
 
-``BASEDIR/introducer.furl``
+``BASEDIR/private/introducer.furl``
 
   This is generated the first time the introducer node is started, and used
   again on subsequent runs, to give the introduction service a persistent
index 34e90c94833f64b0786c2bb6125095c9c0778b1d..b28556715b65053a8c60ffd6ca6e5d9ac8b23d7c 100644 (file)
@@ -111,8 +111,8 @@ That is, it behaves like "``tahoe create-node --no-storage [NODEDIR]``".
 
 "``tahoe create-introducer [NODEDIR]``" is used to create the Introducer node.
 This node provides introduction services and nothing else. When started, this
-node will produce an ``introducer.furl`` file, which should be published to all
-clients.
+node will produce a ``private/introducer.furl`` file, which should be
+published to all clients.
 
 "``tahoe create-key-generator [NODEDIR]``" is used to create a special
 "key-generation" service, which allows a client to offload their RSA key
index de697515c07c7535f6dcd57a6c291512b7f4505c..50ad2cb42856b2868acc3f316739a9ef5ebf4f7d 100644 (file)
@@ -47,10 +47,11 @@ To construct an introducer, create a new base directory for it (the
 name of the directory is up to you), ``cd`` into it, and run
 "``tahoe create-introducer .``". Now run the introducer using
 "``tahoe start .``". After it starts, it will write a file named
-``introducer.furl`` in that base directory. This file contains the URL
-the other nodes must use in order to connect to this introducer. (Note
-that "``tahoe run .``" doesn't work for introducers, this is a known
-issue: `#937 <http://allmydata.org/trac/tahoe-lafs/ticket/937>`_.)
+``introducer.furl`` into the ``private/`` subdirectory of that base
+directory. This file contains the URL the other nodes must use in order
+to connect to this introducer. (Note that "``tahoe run .``" doesn't
+work for introducers, this is a known issue: `#937
+<http://allmydata.org/trac/tahoe-lafs/ticket/937>`_.)
 
 The "``tahoe run``" command above will run the node in the foreground.
 On Unix, you can run it in the background instead by using the
index adc68af3a819c379c3fade1e5b067fcd89db0091..43a02611446245363efba3fc0063fff62de8ea18 100644 (file)
@@ -1,17 +1,21 @@
 
-import time, os.path
+import time, os.path, textwrap
 from zope.interface import implements
 from twisted.application import service
 from foolscap.api import Referenceable
 import allmydata
 from allmydata import node
 from allmydata.util import log, rrefutil
+from allmydata.util.encodingutil import get_filesystem_encoding
 from allmydata.introducer.interfaces import \
      RIIntroducerPublisherAndSubscriberService_v2
 from allmydata.introducer.common import convert_announcement_v1_to_v2, \
      convert_announcement_v2_to_v1, unsign_from_foolscap, make_index, \
      get_tubid_string_from_ann, SubscriberDescriptor, AnnouncementDescriptor
 
+class FurlFileConflictError(Exception):
+    pass
+
 class IntroducerNode(node.Node):
     PORTNUMFILE = "introducer.port"
     NODETYPE = "introducer"
@@ -29,13 +33,27 @@ class IntroducerNode(node.Node):
         introducerservice = IntroducerService(self.basedir)
         self.add_service(introducerservice)
 
+        old_public_fn = os.path.join(self.basedir, "introducer.furl").encode(get_filesystem_encoding())
+        private_fn = os.path.join(self.basedir, "private", "introducer.furl").encode(get_filesystem_encoding())
+
+        if os.path.exists(old_public_fn):
+            if os.path.exists(private_fn):
+                msg = """This directory (%s) contains both an old public
+                'introducer.furl' file, and a new-style
+                'private/introducer.furl', so I cannot safely remove the old
+                one. Please make sure your desired FURL is in
+                private/introducer.furl, and remove the public file. If this
+                causes your Introducer's FURL to change, you need to inform
+                all grid members so they can update their tahoe.cfg.
+                """
+                raise FurlFileConflictError(textwrap.dedent(msg))
+            os.rename(old_public_fn, private_fn)
         d = self.when_tub_ready()
         def _publish(res):
-            self.introducer_url = self.tub.registerReference(introducerservice,
-                                                             "introducer")
-            self.log(" introducer is at %s" % self.introducer_url,
-                     umid="qF2L9A")
-            self.write_config("introducer.furl", self.introducer_url + "\n")
+            furl = self.tub.registerReference(introducerservice,
+                                              furlFile=private_fn)
+            self.log(" introducer is at %s" % furl, umid="qF2L9A")
+            self.introducer_url = furl # for tests
         d.addCallback(_publish)
         d.addErrback(log.err, facility="tahoe.init",
                      level=log.BAD, umid="UaNs9A")
index 0127d282ec81b7c5626d9d5cadc558ccd4bf1edd..15ddfe495990fccc35d28cee1ae3094737c78f3e 100644 (file)
@@ -12,7 +12,7 @@ from twisted.application import service
 from allmydata.interfaces import InsufficientVersionError
 from allmydata.introducer.client import IntroducerClient, \
      WrapV2ClientInV1Interface
-from allmydata.introducer.server import IntroducerService
+from allmydata.introducer.server import IntroducerService, FurlFileConflictError
 from allmydata.introducer.common import get_tubid_string_from_ann, \
      get_tubid_string, sign_to_foolscap, unsign_from_foolscap, \
      UnknownKeyError
@@ -29,15 +29,49 @@ class LoggingMultiService(service.MultiService):
         log.msg(msg, **kw)
 
 class Node(testutil.SignalMixin, unittest.TestCase):
-    def test_loadable(self):
-        basedir = "introducer.IntroducerNode.test_loadable"
+    def test_furl(self):
+        basedir = "introducer.IntroducerNode.test_furl"
         os.mkdir(basedir)
-        q = IntroducerNode(basedir)
+        public_fn = os.path.join(basedir, "introducer.furl")
+        private_fn = os.path.join(basedir, "private", "introducer.furl")
+        q1 = IntroducerNode(basedir)
         d = fireEventually(None)
-        d.addCallback(lambda res: q.startService())
-        d.addCallback(lambda res: q.when_tub_ready())
-        d.addCallback(lambda res: q.stopService())
+        d.addCallback(lambda res: q1.startService())
+        d.addCallback(lambda res: q1.when_tub_ready())
+        d.addCallback(lambda res: q1.stopService())
         d.addCallback(flushEventualQueue)
+        def _check_furl(res):
+            # new nodes create unguessable furls in private/introducer.furl
+            ifurl = fileutil.read(private_fn)
+            self.failUnless(ifurl)
+            ifurl = ifurl.strip()
+            self.failIf(ifurl.endswith("/introducer"), ifurl)
+
+            # old nodes created guessable furls in BASEDIR/introducer.furl
+            guessable = ifurl[:ifurl.rfind("/")] + "/introducer"
+            fileutil.write(public_fn, guessable+"\n", mode="w") # text
+
+            # if we see both files, throw an error
+            self.failUnlessRaises(FurlFileConflictError,
+                                  IntroducerNode, basedir)
+
+            # when we see only the public one, move it to private/ and use
+            # the existing furl instead of creating a new one
+            os.unlink(private_fn)
+            q2 = IntroducerNode(basedir)
+            d2 = fireEventually(None)
+            d2.addCallback(lambda res: q2.startService())
+            d2.addCallback(lambda res: q2.when_tub_ready())
+            d2.addCallback(lambda res: q2.stopService())
+            d2.addCallback(flushEventualQueue)
+            def _check_furl2(res):
+                self.failIf(os.path.exists(public_fn))
+                ifurl2 = fileutil.read(private_fn)
+                self.failUnless(ifurl2)
+                self.failUnlessEqual(ifurl2.strip(), guessable)
+            d2.addCallback(_check_furl2)
+            return d2
+        d.addCallback(_check_furl)
         return d
 
 class ServiceMixin:
index a7be1357a587721d907bfe9b1c9fcc26d00e5949..aed220e4b5fa326bb99a65a3279e9bb5085e99af 100644 (file)
@@ -355,7 +355,7 @@ class RunNode(common_util.SignalMixin, unittest.TestCase, pollmixin.PollMixin,
         c1 = os.path.join(basedir, "c1")
         HOTLINE_FILE = os.path.join(c1, "suicide_prevention_hotline")
         TWISTD_PID_FILE = os.path.join(c1, "twistd.pid")
-        INTRODUCER_FURL_FILE = os.path.join(c1, "introducer.furl")
+        INTRODUCER_FURL_FILE = os.path.join(c1, "private", "introducer.furl")
         PORTNUM_FILE = os.path.join(c1, "introducer.port")
         NODE_URL_FILE = os.path.join(c1, "node.url")
         CONFIG_FILE = os.path.join(c1, "tahoe.cfg")
@@ -406,8 +406,8 @@ class RunNode(common_util.SignalMixin, unittest.TestCase, pollmixin.PollMixin,
         d.addCallback(lambda res: self.poll(_node_has_started))
 
         def _started(res):
-            # read the introducer.furl and introducer.port files so we can check that their
-            # contents don't change on restart
+            # read the introducer.furl and introducer.port files so we can
+            # check that their contents don't change on restart
             self.furl = fileutil.read(INTRODUCER_FURL_FILE)
             self.failUnless(os.path.exists(PORTNUM_FILE))
             self.portnum = fileutil.read(PORTNUM_FILE)