Ticket #1802: 1802-port-to-ticket999.darcs.patch

File 1802-port-to-ticket999.darcs.patch, 20.9 KB (added by daira, at 2013-07-10T01:00:31Z)
Line 
12 patches for repository https://tahoe-lafs.org/source/tahoe/ticket999-S3-backend:
2
3Wed Jul 10 00:19:16 BST 2013  daira@jacaranda.org
4  * Make introducer.furl unguessable [backport of git changeset 0a89b738bc05f17597555786b8f59dc05c46be0f/trunk to ticket999-S3-backend branch]. Closes #1802 for this branch.
5 
6  Previously, Introducers always used a swissnum of "introducer", so
7  anyone who could learn the (public) tubid of the introducer would be
8  able to connect to and use it. This changes new Introducers to use the
9  same randomly-generated swissnum as clients and storage servers do, so
10  that you absolutely must learn the introducer.furl from someone who
11  knows it already before you can connect.
12 
13  This change also moves the location of the file that stores
14  introducer.furl from BASEDIR/introducer.furl to
15  BASEDIR/private/introducer.furl, since that's where we keep the private
16  things. The first time an introducer is started with the new code, it
17  will move any existing BASEDIR/introducer.furl into the new place.
18 
19  Note that this will not change the FURL of existing introducers: it will
20  only affect newly created ones. When you change an introducer's FURL,
21  you must also update all of the nodes (clients and storage servers)
22  which connect to it, so upgrading it to an unguessable one isn't
23  something we should do automatically.
24
25Wed Jul 10 01:55:33 BST 2013  daira@jacaranda.org
26  * Censor the introducer and helper furls' swissnums from the web welcome page [backport of 9be1a94043ce1518d70ebe8b1d0c44b54ec6652f/trunk to ticket999-S3-backend branch]. refs #1802
27
28
29New patches:
30
31[Make introducer.furl unguessable [backport of git changeset 0a89b738bc05f17597555786b8f59dc05c46be0f/trunk to ticket999-S3-backend branch]. Closes #1802 for this branch.
32daira@jacaranda.org**20130709231916
33 Ignore-this: 5ba82c60951c3a0c9cb51151c3758ddb
34 
35 Previously, Introducers always used a swissnum of "introducer", so
36 anyone who could learn the (public) tubid of the introducer would be
37 able to connect to and use it. This changes new Introducers to use the
38 same randomly-generated swissnum as clients and storage servers do, so
39 that you absolutely must learn the introducer.furl from someone who
40 knows it already before you can connect.
41 
42 This change also moves the location of the file that stores
43 introducer.furl from BASEDIR/introducer.furl to
44 BASEDIR/private/introducer.furl, since that's where we keep the private
45 things. The first time an introducer is started with the new code, it
46 will move any existing BASEDIR/introducer.furl into the new place.
47 
48 Note that this will not change the FURL of existing introducers: it will
49 only affect newly created ones. When you change an introducer's FURL,
50 you must also update all of the nodes (clients and storage servers)
51 which connect to it, so upgrading it to an unguessable one isn't
52 something we should do automatically.
53] {
54hunk ./docs/configuration.rst 297
55 
56     This FURL tells the client how to connect to the introducer. Each
57     Tahoe-LAFS grid is defined by an introducer. The introducer's FURL is
58-    created by the introducer node and written into its base directory when
59-    it starts, whereupon it should be published to everyone who wishes to
60-    attach a client to that grid
61+    created by the introducer node and written into its private base
62+    directory when it starts, whereupon it should be published to everyone
63+    who wishes to attach a client to that grid
64 
65 ``helper.furl = (FURL string, optional)``
66 
67hunk ./docs/configuration.rst 478
68 
69 The Introducer node maintains some different state than regular client nodes.
70 
71-``BASEDIR/introducer.furl``
72+``BASEDIR/private/introducer.furl``
73 
74   This is generated the first time the introducer node is started, and used
75   again on subsequent runs, to give the introduction service a persistent
76hunk ./docs/frontends/CLI.rst 107
77 
78 "``tahoe create-introducer [NODEDIR]``" is used to create the Introducer node.
79 This node provides introduction services and nothing else. When started, this
80-node will produce an ``introducer.furl`` file, which should be published to all
81-clients.
82+node will produce a ``private/introducer.furl`` file, which should be
83+published to all clients.
84 
85 "``tahoe create-key-generator [NODEDIR]``" is used to create a special
86 "key-generation" service, which allows a client to offload their RSA key
87hunk ./docs/running.rst 50
88 name of the directory is up to you), ``cd`` into it, and run
89 "``tahoe create-introducer .``". Now run the introducer using
90 "``tahoe start .``". After it starts, it will write a file named
91-``introducer.furl`` in that base directory. This file contains the URL
92-the other nodes must use in order to connect to this introducer. (Note
93-that "``tahoe run .``" doesn't work for introducers, this is a known
94-issue: `#937 <http://allmydata.org/trac/tahoe-lafs/ticket/937>`_.)
95+``introducer.furl`` into the ``private/`` subdirectory of that base
96+directory. This file contains the URL the other nodes must use in order
97+to connect to this introducer. (Note that "``tahoe run .``" doesn't
98+work for introducers, this is a known issue: `#937
99+<http://allmydata.org/trac/tahoe-lafs/ticket/937>`_.)
100 
101 The "``tahoe run``" command above will run the node in the foreground.
102 On Unix, you can run it in the background instead by using the
103hunk ./src/allmydata/introducer/server.py 2
104 
105-import time, os.path
106+import time, os.path, textwrap
107 from base64 import b32decode
108 from zope.interface import implements
109 from twisted.application import service
110hunk ./src/allmydata/introducer/server.py 10
111 import allmydata
112 from allmydata import node
113 from allmydata.util import log, rrefutil
114+from allmydata.util.encodingutil import get_filesystem_encoding
115 from allmydata.introducer.interfaces import \
116      RIIntroducerPublisherAndSubscriberService
117 
118hunk ./src/allmydata/introducer/server.py 14
119+class FurlFileConflictError(Exception):
120+    pass
121+
122 class IntroducerNode(node.Node):
123     PORTNUMFILE = "introducer.port"
124     NODETYPE = "introducer"
125hunk ./src/allmydata/introducer/server.py 34
126         introducerservice = IntroducerService(self.basedir)
127         self.add_service(introducerservice)
128 
129+        old_public_fn = os.path.join(self.basedir, "introducer.furl").encode(get_filesystem_encoding())
130+        private_fn = os.path.join(self.basedir, "private", "introducer.furl").encode(get_filesystem_encoding())
131+
132+        if os.path.exists(old_public_fn):
133+            if os.path.exists(private_fn):
134+                msg = """This directory (%s) contains both an old public
135+                'introducer.furl' file, and a new-style
136+                'private/introducer.furl', so I cannot safely remove the old
137+                one. Please make sure your desired FURL is in
138+                private/introducer.furl, and remove the public file. If this
139+                causes your Introducer's FURL to change, you need to inform
140+                all grid members so they can update their tahoe.cfg.
141+                """
142+                raise FurlFileConflictError(textwrap.dedent(msg))
143+            os.rename(old_public_fn, private_fn)
144+
145         d = self.when_tub_ready()
146         def _publish(res):
147hunk ./src/allmydata/introducer/server.py 52
148-            self.introducer_url = self.tub.registerReference(introducerservice,
149-                                                             "introducer")
150-            self.log(" introducer is at %s" % self.introducer_url)
151-            self.write_config("introducer.furl", self.introducer_url + "\n")
152+            furl = self.tub.registerReference(introducerservice,
153+                                              furlFile=private_fn)
154+            self.log(" introducer is at %s" % furl, umid="qF2L9A")
155+            self.introducer_url = furl # for tests
156         d.addCallback(_publish)
157         d.addErrback(log.err, facility="tahoe.init",
158                      level=log.BAD, umid="UaNs9A")
159hunk ./src/allmydata/test/test_introducer.py 13
160 from twisted.application import service
161 from allmydata.interfaces import InsufficientVersionError
162 from allmydata.introducer.client import IntroducerClient
163-from allmydata.introducer.server import IntroducerService
164+from allmydata.introducer.server import IntroducerService, FurlFileConflictError
165 # test compatibility with old introducer .tac files
166 from allmydata.introducer import IntroducerNode
167 from allmydata.util import pollmixin
168hunk ./src/allmydata/test/test_introducer.py 24
169         log.msg(msg, **kw)
170 
171 class Node(testutil.SignalMixin, unittest.TestCase):
172-    def test_loadable(self):
173-        basedir = "introducer.IntroducerNode.test_loadable"
174+    def test_furl(self):
175+        basedir = "introducer.IntroducerNode.test_furl"
176         os.mkdir(basedir)
177hunk ./src/allmydata/test/test_introducer.py 27
178-        q = IntroducerNode(basedir)
179+        public_fn = os.path.join(basedir, "introducer.furl")
180+        private_fn = os.path.join(basedir, "private", "introducer.furl")
181+        q1 = IntroducerNode(basedir)
182         d = fireEventually(None)
183hunk ./src/allmydata/test/test_introducer.py 31
184-        d.addCallback(lambda res: q.startService())
185-        d.addCallback(lambda res: q.when_tub_ready())
186-        d.addCallback(lambda res: q.stopService())
187+        d.addCallback(lambda res: q1.startService())
188+        d.addCallback(lambda res: q1.when_tub_ready())
189+        d.addCallback(lambda res: q1.stopService())
190         d.addCallback(flushEventualQueue)
191hunk ./src/allmydata/test/test_introducer.py 35
192+        def _check_furl(res):
193+            # new nodes create unguessable furls in private/introducer.furl
194+            ifurl = fileutil.read(private_fn)
195+            self.failUnless(ifurl)
196+            ifurl = ifurl.strip()
197+            self.failIf(ifurl.endswith("/introducer"), ifurl)
198+
199+            # old nodes created guessable furls in BASEDIR/introducer.furl
200+            guessable = ifurl[:ifurl.rfind("/")] + "/introducer"
201+            fileutil.write(public_fn, guessable+"\n", mode="w") # text
202+
203+            # if we see both files, throw an error
204+            self.failUnlessRaises(FurlFileConflictError,
205+                                  IntroducerNode, basedir)
206+
207+            # when we see only the public one, move it to private/ and use
208+            # the existing furl instead of creating a new one
209+            os.unlink(private_fn)
210+            q2 = IntroducerNode(basedir)
211+            d2 = fireEventually(None)
212+            d2.addCallback(lambda res: q2.startService())
213+            d2.addCallback(lambda res: q2.when_tub_ready())
214+            d2.addCallback(lambda res: q2.stopService())
215+            d2.addCallback(flushEventualQueue)
216+            def _check_furl2(res):
217+                self.failIf(os.path.exists(public_fn))
218+                ifurl2 = fileutil.read(private_fn)
219+                self.failUnless(ifurl2)
220+                self.failUnlessEqual(ifurl2.strip(), guessable)
221+            d2.addCallback(_check_furl2)
222+            return d2
223+        d.addCallback(_check_furl)
224         return d
225 
226 class ServiceMixin:
227hunk ./src/allmydata/test/test_runner.py 357
228         c1 = os.path.join(basedir, "c1")
229         HOTLINE_FILE = os.path.join(c1, "suicide_prevention_hotline")
230         TWISTD_PID_FILE = os.path.join(c1, "twistd.pid")
231-        INTRODUCER_FURL_FILE = os.path.join(c1, "introducer.furl")
232+        INTRODUCER_FURL_FILE = os.path.join(c1, "private", "introducer.furl")
233         PORTNUM_FILE = os.path.join(c1, "introducer.port")
234         NODE_URL_FILE = os.path.join(c1, "node.url")
235         CONFIG_FILE = os.path.join(c1, "tahoe.cfg")
236hunk ./src/allmydata/test/test_runner.py 408
237         d.addCallback(lambda res: self.poll(_node_has_started))
238 
239         def _started(res):
240-            # read the introducer.furl and introducer.port files so we can check that their
241-            # contents don't change on restart
242+            # read the introducer.furl and introducer.port files so we can
243+            # check that their contents don't change on restart
244             self.furl = fileutil.read(INTRODUCER_FURL_FILE)
245             self.failUnless(os.path.exists(PORTNUM_FILE))
246             self.portnum = fileutil.read(PORTNUM_FILE)
247}
248[Censor the introducer and helper furls' swissnums from the web welcome page [backport of 9be1a94043ce1518d70ebe8b1d0c44b54ec6652f/trunk to ticket999-S3-backend branch]. refs #1802
249daira@jacaranda.org**20130710005533
250 Ignore-this: 7a1ca42a71c9ea63b1500439905a1d77
251] {
252hunk ./src/allmydata/test/test_introducer.py 16
253 from allmydata.introducer.server import IntroducerService, FurlFileConflictError
254 # test compatibility with old introducer .tac files
255 from allmydata.introducer import IntroducerNode
256-from allmydata.util import pollmixin
257+from allmydata.util import pollmixin, fileutil
258 import allmydata.test.common_util as testutil
259 
260 class LoggingMultiService(service.MultiService):
261hunk ./src/allmydata/test/test_introducer.py 44
262 
263             # old nodes created guessable furls in BASEDIR/introducer.furl
264             guessable = ifurl[:ifurl.rfind("/")] + "/introducer"
265-            fileutil.write(public_fn, guessable+"\n", mode="w") # text
266+            fileutil.write(public_fn, guessable+"\n") # text
267 
268             # if we see both files, throw an error
269             self.failUnlessRaises(FurlFileConflictError,
270hunk ./src/allmydata/test/test_web.py 74
271 
272 class FakeUploader(service.Service):
273     name = "uploader"
274+    def __init__(self):
275+        self.helper_furl = None
276+        self.helper_connected = False
277     def upload(self, uploadable):
278         d = uploadable.get_size()
279         d.addCallback(lambda size: uploadable.read(size))
280hunk ./src/allmydata/test/test_web.py 89
281         d.addCallback(_got_data)
282         return d
283     def get_helper_info(self):
284-        return (None, False)
285+        return (self.helper_furl, self.helper_connected)
286 
287 class FakeIServer:
288     def __init__(self, binaryserverid):
289hunk ./src/allmydata/test/test_web.py 517
290         d.addCallback(_check)
291         return d
292 
293+    def test_introducer_status(self):
294+        class MockIntroducerClient(object):
295+            def __init__(self, connected):
296+                self.connected = connected
297+            def connected_to_introducer(self):
298+                return self.connected
299+
300+        d = defer.succeed(None)
301+
302+        # introducer not connected, unguessable furl
303+        def _set_introducer_not_connected_unguessable(ign):
304+            self.s.introducer_furl = "pb://someIntroducer/secret"
305+            self.s.introducer_client = MockIntroducerClient(False)
306+            return self.GET("/")
307+        d.addCallback(_set_introducer_not_connected_unguessable)
308+        def _check_introducer_not_connected_unguessable(res):
309+            html = res.replace('\n', ' ')
310+            self.failUnlessIn('<div>Introducer: <span class="data-chars">pb://someIntroducer/[censored]</span></div>', html)
311+            self.failIfIn('pb://someIntroducer/secret', html)
312+            self.failUnlessIn('<div>Connected to introducer?: <span>no</span></div>', html)
313+        d.addCallback(_check_introducer_not_connected_unguessable)
314+
315+        # introducer connected, unguessable furl
316+        def _set_introducer_connected_unguessable(ign):
317+            self.s.introducer_furl = "pb://someIntroducer/secret"
318+            self.s.introducer_client = MockIntroducerClient(True)
319+            return self.GET("/")
320+        d.addCallback(_set_introducer_connected_unguessable)
321+        def _check_introducer_connected_unguessable(res):
322+            html = res.replace('\n', ' ')
323+            self.failUnlessIn('<div>Introducer: <span class="data-chars">pb://someIntroducer/[censored]</span></div>', html)
324+            self.failIfIn('pb://someIntroducer/secret', html)
325+            self.failUnlessIn('<div>Connected to introducer?: <span>yes</span></div>', html)
326+        d.addCallback(_check_introducer_connected_unguessable)
327+
328+        # introducer connected, guessable furl
329+        def _set_introducer_connected_guessable(ign):
330+            self.s.introducer_furl = "pb://someIntroducer/introducer"
331+            self.s.introducer_client = MockIntroducerClient(True)
332+            return self.GET("/")
333+        d.addCallback(_set_introducer_connected_guessable)
334+        def _check_introducer_connected_guessable(res):
335+            html = res.replace('\n', ' ')
336+            self.failUnlessIn('<div>Introducer: <span class="data-chars">pb://someIntroducer/introducer</span></div>', html)
337+            self.failUnlessIn('<div>Connected to introducer?: <span>yes</span></div>', html)
338+        d.addCallback(_check_introducer_connected_guessable)
339+        return d
340+
341+    def test_helper_status(self):
342+        d = defer.succeed(None)
343+
344+        # set helper furl to None
345+        def _set_no_helper(ign):
346+            self.s.uploader.helper_furl = None
347+            self.s.uploader.helper_connnected = False
348+            return self.GET("/")
349+        d.addCallback(_set_no_helper)
350+        def _check_no_helper(res):
351+            html = res.replace('\n', ' ')
352+            self.failUnlessIn('<li>Not running helper</li>', html)
353+        d.addCallback(_check_no_helper)
354+
355+        # enable helper, not connected
356+        def _set_helper_not_connected(ign):
357+            self.s.uploader.helper_furl = "pb://someHelper/secret"
358+            self.s.uploader.helper_connected = False
359+            return self.GET("/")
360+        d.addCallback(_set_helper_not_connected)
361+        def _check_helper_not_connected(res):
362+            html = res.replace('\n', ' ')
363+            self.failUnlessIn('<div>Helper: <span>pb://someHelper/[censored]</span></div>', html)
364+            self.failIfIn('pb://someHelper/secret', html)
365+            self.failUnlessIn('<div>Connected to helper?: <span>no</span></div>', html)
366+        d.addCallback(_check_helper_not_connected)
367+
368+        # enable helper, connected
369+        def _set_helper_connected(ign):
370+            self.s.uploader.helper_furl = "pb://someHelper/secret"
371+            self.s.uploader.helper_connected = True
372+            return self.GET("/")
373+        d.addCallback(_set_helper_connected)
374+        def _check_helper_connected(res):
375+            html = res.replace('\n', ' ')
376+            self.failUnlessIn('<div>Helper: <span>pb://someHelper/[censored]</span></div>', html)
377+            self.failIfIn('pb://someHelper/secret', html)
378+            self.failUnlessIn('<div>Connected to helper?: <span>yes</span></div>', html)
379+        d.addCallback(_check_helper_connected)
380+        return d
381+
382     def test_status(self):
383         h = self.s.get_history()
384         dl_num = h.list_all_download_statuses()[0].get_counter()
385hunk ./src/allmydata/web/root.py 201
386 
387         return ctx.tag[ul]
388 
389-    def data_introducer_furl(self, ctx, data):
390-        return self.client.introducer_furl
391+    def data_introducer_furl_prefix(self, ctx, data):
392+        ifurl = self.client.introducer_furl
393+        # trim off the secret swissnum
394+        (prefix, _, swissnum) = ifurl.rpartition("/")
395+        if not ifurl:
396+            return None
397+        if swissnum == "introducer":
398+            return ifurl
399+        else:
400+            return "%s/[censored]" % (prefix,)
401+
402     def data_connected_to_introducer(self, ctx, data):
403         if self.client.connected_to_introducer():
404             return "yes"
405hunk ./src/allmydata/web/root.py 217
406         return "no"
407 
408-    def data_helper_furl(self, ctx, data):
409+    def data_helper_furl_prefix(self, ctx, data):
410         try:
411             uploader = self.client.getServiceNamed("uploader")
412         except KeyError:
413hunk ./src/allmydata/web/root.py 223
414             return None
415         furl, connected = uploader.get_helper_info()
416-        return furl
417+        if not furl:
418+            return None
419+        # trim off the secret swissnum
420+        (prefix, _, swissnum) = furl.rpartition("/")
421+        return "%s/[censored]" % (prefix,)
422+
423     def data_connected_to_helper(self, ctx, data):
424         try:
425             uploader = self.client.getServiceNamed("uploader")
426hunk ./src/allmydata/web/welcome.xhtml 45
427 
428   <div>
429     <n:attr name="class">connected-<n:invisible n:render="string" n:data="connected_to_introducer" /></n:attr>
430-    <div>Introducer: <span class="data-chars" n:render="string" n:data="introducer_furl" /></div>
431+    <div>Introducer: <span class="data-chars" n:render="string" n:data="introducer_furl_prefix" /></div>
432     <div>Connected to introducer?: <span n:render="string" n:data="connected_to_introducer" /></div>
433   </div>
434 
435hunk ./src/allmydata/web/welcome.xhtml 51
436   <div>
437     <n:attr name="class">connected-<n:invisible n:render="string" n:data="connected_to_helper" /></n:attr>
438-    <div>Helper: <span n:render="string" n:data="helper_furl" /></div>
439+    <div>Helper: <span n:render="string" n:data="helper_furl_prefix" /></div>
440     <div>Connected to helper?: <span n:render="string" n:data="connected_to_helper" /></div>
441   </div>
442 
443}
444
445Context:
446
447[TAG allmydata-tahoe-1.9.1.dev1
448david-sarah@jacaranda.org**20120522033928
449 Ignore-this: 3df82d8713cfe8f6a8245250c0d49b60
450]
451Patch bundle hash:
4522e5996948e3fc548c646510bbcb53329b599fc5d
453
454-----------------------------134698832021294017231450416348
455Content-Disposition: form-data; name="description"
456
457Make introducer.furl unguessable and censor the introducer and helper furls' swissnums from the web welcome page [backport to ticket999-S3-backend branch]