Merge lp:~wgrant/launchpad/neuter-poppy-db into lp:launchpad

Proposed by William Grant
Status: Merged
Approved by: Curtis Hovey
Approved revision: no longer in the source branch.
Merged at revision: 15023
Proposed branch: lp:~wgrant/launchpad/neuter-poppy-db
Merge into: lp:launchpad
Diff against target: 527 lines (+2/-374)
10 files modified
daemons/poppy-sftp.tac (+1/-16)
database/schema/security.cfg (+0/-6)
lib/lp/poppy/tests/test_poppy.py (+0/-36)
lib/lp/poppy/tests/test_twistedconfigreset.py (+0/-31)
lib/lp/poppy/tests/test_twistedftp.py (+0/-111)
lib/lp/poppy/twistedconfigreset.py (+0/-49)
lib/lp/poppy/twistedftp.py (+1/-60)
lib/lp/services/gpg/handler.py (+0/-14)
lib/lp/services/gpg/interfaces.py (+0/-10)
lib/lp/services/gpg/tests/test_gpghandler.py (+0/-41)
To merge this branch: bzr merge lp:~wgrant/launchpad/neuter-poppy-db
Reviewer Review Type Date Requested Status
Curtis Hovey (community) code Approve
Review via email: mp+99258@code.launchpad.net

Commit message

Rip out poppy's disabled GPG verification and DB access.

Description of the change

Rip out poppy's GPG verification and DB access. The current implementation is disabled (due to bug #798957) and would be better off reimplemented as a service client.

To post a comment you must log in.
Revision history for this message
Curtis Hovey (sinzui) wrote :

Thank you.

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'daemons/poppy-sftp.tac'
--- daemons/poppy-sftp.tac 2012-02-01 15:08:19 +0000
+++ daemons/poppy-sftp.tac 2012-03-26 07:12:35 +0000
@@ -18,15 +18,10 @@
1818
19from zope.interface import implements19from zope.interface import implements
2020
21from lp.services.config import (21from lp.services.config import config
22 config,
23 dbconfig,
24 )
25from lp.services.daemons import readyservice22from lp.services.daemons import readyservice
26from lp.services.scripts import execute_zcml_for_scripts
2723
28from lp.poppy import get_poppy_root24from lp.poppy import get_poppy_root
29from lp.poppy.twistedconfigreset import GPGHandlerConfigResetJob
30from lp.poppy.twistedftp import (25from lp.poppy.twistedftp import (
31 FTPServiceFactory,26 FTPServiceFactory,
32 )27 )
@@ -38,10 +33,6 @@
38from lp.services.twistedsupport.loggingsupport import set_up_oops_reporting33from lp.services.twistedsupport.loggingsupport import set_up_oops_reporting
3934
4035
41# Use a unique db user per policy and Bug #732510.
42dbconfig.override(dbuser='poppy_sftp')
43
44
45def make_portal():36def make_portal():
46 """Create and return a `Portal` for the SSH service.37 """Create and return a `Portal` for the SSH service.
4738
@@ -124,11 +115,5 @@
124 banner=config.poppy.banner)115 banner=config.poppy.banner)
125svc.setServiceParent(application)116svc.setServiceParent(application)
126117
127# We need Zope for looking up the GPG utilities.
128execute_zcml_for_scripts()
129
130# Set up the GPGHandler job
131GPGHandlerConfigResetJob().setServiceParent(application)
132
133# Service that announces when the daemon is ready118# Service that announces when the daemon is ready
134readyservice.ReadyService().setServiceParent(application)119readyservice.ReadyService().setServiceParent(application)
135120
=== modified file 'database/schema/security.cfg'
--- database/schema/security.cfg 2012-03-21 19:01:20 +0000
+++ database/schema/security.cfg 2012-03-26 07:12:35 +0000
@@ -958,12 +958,6 @@
958type=user958type=user
959groups=fiera959groups=fiera
960960
961[poppy_sftp]
962# This really should be inheriting from something more specific to the
963# soyuz realm.
964groups=launchpad_main
965type=user
966
967[ppa-apache-log-parser]961[ppa-apache-log-parser]
968groups=script962groups=script
969public.archive = SELECT963public.archive = SELECT
970964
=== modified file 'lib/lp/poppy/tests/test_poppy.py'
--- lib/lp/poppy/tests/test_poppy.py 2012-02-01 14:30:51 +0000
+++ lib/lp/poppy/tests/test_poppy.py 2012-03-26 07:12:35 +0000
@@ -5,7 +5,6 @@
55
6__metaclass__ = type6__metaclass__ = type
77
8import ftplib
9import os8import os
10import shutil9import shutil
11import stat10import stat
@@ -32,8 +31,6 @@
32from lp.services.config import config31from lp.services.config import config
33from lp.services.daemons.tachandler import TacTestSetup32from lp.services.daemons.tachandler import TacTestSetup
34from lp.testing import TestCaseWithFactory33from lp.testing import TestCaseWithFactory
35from lp.testing.dbuser import switch_dbuser
36from lp.testing.keyserver import KeyServerTac
37from lp.testing.layers import (34from lp.testing.layers import (
38 ZopelessAppServerLayer,35 ZopelessAppServerLayer,
39 ZopelessDatabaseLayer,36 ZopelessDatabaseLayer,
@@ -194,7 +191,6 @@
194 """Set up poppy in a temp dir."""191 """Set up poppy in a temp dir."""
195 super(TestPoppy, self).setUp()192 super(TestPoppy, self).setUp()
196 self.root_dir = self.makeTemporaryDirectory()193 self.root_dir = self.makeTemporaryDirectory()
197 switch_dbuser('poppy_sftp')
198 self.server = self.server_factory(self.root_dir, self.factory)194 self.server = self.server_factory(self.root_dir, self.factory)
199 self.useFixture(self.server)195 self.useFixture(self.server)
200196
@@ -373,38 +369,6 @@
373 self.root_dir, upload_dirs[index], "test")).read()369 self.root_dir, upload_dirs[index], "test")).read()
374 self.assertEqual(content, expected_contents[index])370 self.assertEqual(content, expected_contents[index])
375371
376 # XXX: deryck, 2012-01-26, Bug 798957
377 # PoppyFileWriter.close has been disabled, so disable test, too.
378 def disabled_test_bad_gpg_on_changesfile(self):
379 """Check that we get a rejection error when uploading .changes files
380 with invalid GPG signatures.
381 """
382 # Start up the poppy server.
383 self.server.waitForStartUp()
384
385 transport = self.server.getTransport()
386 if transport.external_url().startswith("sftp"):
387 # We're not GPG-checking sftp uploads right now.
388 return
389
390 # Start up the test keyserver.
391 tac = KeyServerTac()
392 tac.setUp()
393 self.addCleanup(tac.tearDown)
394
395 fake_file = StringIO.StringIO("fake contents")
396
397 # We can't use bzrlib's transport here because it uploads a
398 # renamed file before renaming it on the server.
399 f = ftplib.FTP()
400 f.connect(host="localhost", port=config.poppy.ftp_port)
401 f.login()
402 self.assertRaises(
403 ftplib.error_perm,
404 f.storbinary,
405 'STOR ' + 'foo_source.changes',
406 fake_file)
407
408372
409def test_suite():373def test_suite():
410 tests = unittest.TestLoader().loadTestsFromName(__name__)374 tests = unittest.TestLoader().loadTestsFromName(__name__)
411375
=== removed file 'lib/lp/poppy/tests/test_twistedconfigreset.py'
--- lib/lp/poppy/tests/test_twistedconfigreset.py 2012-01-01 02:58:52 +0000
+++ lib/lp/poppy/tests/test_twistedconfigreset.py 1970-01-01 00:00:00 +0000
@@ -1,31 +0,0 @@
1# Copyright 2011 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""Tests for GPGHandler config reset job."""
5
6__metaclass__ = type
7
8
9from lp.poppy.twistedconfigreset import GPGHandlerConfigResetJob
10from lp.testing import TestCase
11from lp.testing.layers import ZopelessLayer
12
13
14class TestGPGHandlerConfigResetJob(TestCase):
15
16 layer = ZopelessLayer
17
18 def test_gpghandler_config_reset_job_setup(self):
19 # Does the gpghandler job get setup correctly.
20
21 job_instance = GPGHandlerConfigResetJob()
22 job_instance.startService()
23 self.assertIsNot(None, job_instance._gpghandler_job)
24 self.assertTrue(job_instance._gpghandler_job.running)
25
26 # It should be scheduled for every 12 hours.
27 self.assertEqual(12 * 3600, job_instance._gpghandler_job.interval)
28
29 # We should be able to stop the job.
30 job_instance.stopService()
31 self.assertFalse(job_instance._gpghandler_job.running)
320
=== removed file 'lib/lp/poppy/tests/test_twistedftp.py'
--- lib/lp/poppy/tests/test_twistedftp.py 2012-01-27 19:00:36 +0000
+++ lib/lp/poppy/tests/test_twistedftp.py 1970-01-01 00:00:00 +0000
@@ -1,111 +0,0 @@
1# Copyright 2011 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""Tests for Twisted Poppy FTP."""
5
6__metaclass__ = type
7
8import os
9
10from testtools.deferredruntest import AsynchronousDeferredRunTest
11import transaction
12from twisted.protocols import ftp
13from zope.component import getUtility
14
15from lp.poppy.twistedftp import PoppyFileWriter
16from lp.registry.interfaces.gpg import (
17 GPGKeyAlgorithm,
18 IGPGKeySet,
19 )
20from lp.services.config import config
21from lp.services.database.isolation import check_no_transaction
22from lp.services.gpg.interfaces import IGPGHandler
23from lp.testing import TestCaseWithFactory
24from lp.testing.gpgkeys import gpgkeysdir
25from lp.testing.keyserver import KeyServerTac
26from lp.testing.layers import ZopelessDatabaseLayer
27
28
29class TestPoppyFileWriter(TestCaseWithFactory):
30
31 layer = ZopelessDatabaseLayer
32 run_tests_with = AsynchronousDeferredRunTest.make_factory(timeout=20)
33
34 def setUp(self):
35 TestCaseWithFactory.setUp(self)
36
37 # Start the test keyserver. Starting up and tearing this down
38 # for each test is expensive, but we don't have a way of having
39 # cross-test persistent fixtures yet. See bug 724349.
40 self.tac = KeyServerTac()
41 self.tac.setUp()
42 self.addCleanup(self.tac.tearDown)
43
44 # Load a key.
45 gpg_handler = getUtility(IGPGHandler)
46 key_path = os.path.join(gpgkeysdir, 'ftpmaster@canonical.com.pub')
47 key_data = open(key_path).read()
48 key = gpg_handler.importPublicKey(key_data)
49 assert key is not None
50
51 # Make a new user and add the above key to it.
52 user = self.factory.makePerson()
53 key_set = getUtility(IGPGKeySet)
54 user_key = key_set.new(
55 ownerID=user.id, keyid=key.keyid, fingerprint=key.fingerprint,
56 algorithm=GPGKeyAlgorithm.items[key.algorithm],
57 keysize=key.keysize, can_encrypt=key.can_encrypt,
58 active=True)
59 # validateGPG runs in its own transaction.
60 transaction.commit()
61
62 # Locate the directory with test files.
63 self.test_files_dir = os.path.join(
64 config.root, "lib/lp/soyuz/scripts/tests/upload_test_files/")
65
66 def test_changes_file_with_valid_GPG(self):
67 valid_changes_file = os.path.join(
68 self.test_files_dir, "etherwake_1.08-1_source.changes")
69
70 def callback(result):
71 self.assertIs(None, result)
72
73 with open(valid_changes_file) as opened_file:
74 file_writer = PoppyFileWriter(opened_file)
75 d = file_writer.close()
76 d.addBoth(callback)
77 return d
78
79 # XXX: deryck, 2012-01-26, Bug 798957
80 # Disable as we search for a better fix to bug.
81 def disabled_test_changes_file_with_invalid_GPG(self):
82 invalid_changes_file = os.path.join(
83 self.test_files_dir, "broken_source.changes")
84
85 def error_callback(failure):
86 self.assertTrue(failure.check, ftp.PermissionDeniedError)
87 self.assertIn(
88 "Changes file must be signed with a valid GPG signature",
89 failure.getErrorMessage())
90
91 def success_callback(result):
92 self.fail("Success when there should have been failure.")
93
94 with open(invalid_changes_file) as opened_file:
95 file_writer = PoppyFileWriter(opened_file)
96 d = file_writer.close()
97 d.addCallbacks(success_callback, error_callback)
98 return d
99
100 def test_aborts_transaction(self):
101 valid_changes_file = os.path.join(
102 self.test_files_dir, "etherwake_1.08-1_source.changes")
103
104 def callback(result):
105 check_no_transaction()
106
107 with open(valid_changes_file) as opened_file:
108 file_writer = PoppyFileWriter(opened_file)
109 d = file_writer.close()
110 d.addBoth(callback)
111 return d
1120
=== removed file 'lib/lp/poppy/twistedconfigreset.py'
--- lib/lp/poppy/twistedconfigreset.py 2011-12-09 00:20:44 +0000
+++ lib/lp/poppy/twistedconfigreset.py 1970-01-01 00:00:00 +0000
@@ -1,49 +0,0 @@
1# Copyright 2011 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""A Twisted job to touch the GPGHandler config files."""
5
6__metaclass__ = type
7__all__ = [
8 'GPGHandlerConfigResetJob',
9 ]
10
11from twisted.application.service import Service
12from twisted.internet import task
13from twisted.internet.error import AlreadyCancelled
14from zope.component import getUtility
15from zope.component.interfaces import ComponentLookupError
16
17from lp.services.gpg.interfaces import IGPGHandler
18
19
20class GPGHandlerConfigResetJob(Service):
21 """Manages twisted job to touch the files in the gpgconfig directory."""
22
23 def startService(self):
24 self._gpghandler_job = None
25 # start the GPGHandler job
26 self._scheduleGPGHandlerJob()
27 Service.startService(self)
28
29 def stopService(self):
30 self._stopGPGHandlerJob()
31 Service.stopService(self)
32
33 def _scheduleGPGHandlerJob(self, touch_interval=12 * 3600):
34 # Create a job to touch the GPGHandler home directory every so often
35 # so that it does not get cleaned up by any reaper scripts which look
36 # at time last modified.
37
38 self._stopGPGHandlerJob()
39 self._gpghandler_job = task.LoopingCall(
40 getUtility(IGPGHandler).touchConfigurationDirectory)
41 return self._gpghandler_job.start(touch_interval)
42
43 def _stopGPGHandlerJob(self):
44 try:
45 if self._gpghandler_job and self._gpghandler_job.running:
46 self._gpghandler_job.stop()
47 except AlreadyCancelled:
48 # So we're already cancelled, meh.
49 pass
500
=== modified file 'lib/lp/poppy/twistedftp.py'
--- lib/lp/poppy/twistedftp.py 2012-01-26 15:52:04 +0000
+++ lib/lp/poppy/twistedftp.py 2012-03-26 07:12:35 +0000
@@ -34,13 +34,7 @@
34from lp.poppy import get_poppy_root34from lp.poppy import get_poppy_root
35from lp.poppy.filesystem import UploadFileSystem35from lp.poppy.filesystem import UploadFileSystem
36from lp.poppy.hooks import Hooks36from lp.poppy.hooks import Hooks
37from lp.registry.interfaces.gpg import IGPGKeySet
38from lp.services.config import config37from lp.services.config import config
39from lp.services.database import read_transaction
40from lp.services.gpg.interfaces import (
41 GPGVerificationError,
42 IGPGHandler,
43 )
4438
4539
46class PoppyAccessCheck:40class PoppyAccessCheck:
@@ -87,15 +81,7 @@
87 """81 """
88 filename = os.sep.join(file_segments)82 filename = os.sep.join(file_segments)
89 self._create_missing_directories(filename)83 self._create_missing_directories(filename)
90 path = self._path(file_segments)84 return super(PoppyAnonymousShell, self).openForWriting(file_segments)
91 try:
92 fObj = path.open("w")
93 except (IOError, OSError), e:
94 return ftp.errnoToFailure(e.errno, path)
95 except:
96 # Push any other error up to Twisted to deal with.
97 return defer.fail()
98 return defer.succeed(PoppyFileWriter(fObj))
9985
100 def makeDirectory(self, path):86 def makeDirectory(self, path):
101 """Make a directory using the secure `UploadFileSystem`."""87 """Make a directory using the secure `UploadFileSystem`."""
@@ -151,51 +137,6 @@
151 "Only IFTPShell interface is supported by this realm")137 "Only IFTPShell interface is supported by this realm")
152138
153139
154class PoppyFileWriter(ftp._FileWriter):
155 """An `IWriteFile` that checks for signed changes files."""
156
157 # XXX: deryck, 2012-01-26, Bug 798957
158 # Disable close() as we search for a better fix to bug.
159 def disabled_close(self):
160 """Called after the file has been completely downloaded."""
161 if self.fObj.name.endswith(".changes"):
162 error = self.validateGPG(self.fObj.name)
163 if error is not None:
164 # PermissionDeniedError is one of the few ftp exceptions
165 # that lets us pass an error string back to the client.
166 return defer.fail(ftp.PermissionDeniedError(error))
167 return defer.succeed(None)
168
169 @read_transaction
170 def validateGPG(self, signed_file):
171 """Check the GPG signature in the file referenced by signed_file.
172
173 Return an error string if there's a problem, or None.
174 """
175 try:
176 sig = getUtility(IGPGHandler).getVerifiedSignatureResilient(
177 file(signed_file, "rb").read())
178 except GPGVerificationError, error:
179 log = logging.getLogger("poppy-sftp")
180 log.info("GPGVerificationError, extra debug output follows:")
181 for attr in ("args", "code", "signatures", "source"):
182 if hasattr(error, attr):
183 log.info("%s: %s", attr, getattr(error, attr))
184 return ("Changes file must be signed with a valid GPG "
185 "signature: %s" % error)
186
187 key = getUtility(IGPGKeySet).getByFingerprint(sig.fingerprint)
188 if key is None:
189 return (
190 "Signing key %s not registered in launchpad."
191 % sig.fingerprint)
192
193 if key.active == False:
194 return "Changes file is signed with a deactivated key"
195
196 return None
197
198
199class FTPServiceFactory(service.Service):140class FTPServiceFactory(service.Service):
200 """A factory that makes an `FTPService`"""141 """A factory that makes an `FTPService`"""
201142
202143
=== modified file 'lib/lp/services/gpg/handler.py'
--- lib/lp/services/gpg/handler.py 2011-12-30 08:13:14 +0000
+++ lib/lp/services/gpg/handler.py 2012-03-26 07:12:35 +0000
@@ -128,20 +128,6 @@
128 if os.path.exists(filename):128 if os.path.exists(filename):
129 os.remove(filename)129 os.remove(filename)
130130
131 def touchConfigurationDirectory(self):
132 """See IGPGHandler."""
133 os.utime(self.home, None)
134 for file in os.listdir(self.home):
135 try:
136 os.utime(os.path.join(self.home, file), None)
137 except OSError as e:
138 if e.errno == errno.ENOENT:
139 # The file has been deleted.
140 pass
141 else:
142 # Some other unexpected error.
143 raise e
144
145 def verifySignature(self, content, signature=None):131 def verifySignature(self, content, signature=None):
146 """See IGPGHandler."""132 """See IGPGHandler."""
147 try:133 try:
148134
=== modified file 'lib/lp/services/gpg/interfaces.py'
--- lib/lp/services/gpg/interfaces.py 2011-12-09 00:20:44 +0000
+++ lib/lp/services/gpg/interfaces.py 2012-03-26 07:12:35 +0000
@@ -282,16 +282,6 @@
282 """282 """
283 #FIXME RBC: this should be a zope test cleanup thing per SteveA.283 #FIXME RBC: this should be a zope test cleanup thing per SteveA.
284284
285 def touchConfigurationDirectory():
286 """Touch the home directory and all files within.
287
288 This function is called so that the configuration directory does not
289 get cleaned up by any reaper scripts which look at time last modified.
290 It is only required in the case where a long running daemon uses an
291 IGPGHandler instance such that the lifetime of the daemon exceeds the
292 reaping (ie tmp clean up) interval.
293 """
294
295285
296class IPymeSignature(Interface):286class IPymeSignature(Interface):
297 """pyME signature container."""287 """pyME signature container."""
298288
=== modified file 'lib/lp/services/gpg/tests/test_gpghandler.py'
--- lib/lp/services/gpg/tests/test_gpghandler.py 2011-12-30 08:13:14 +0000
+++ lib/lp/services/gpg/tests/test_gpghandler.py 2012-03-26 07:12:35 +0000
@@ -1,22 +1,7 @@
1# Copyright 2009-2011 Canonical Ltd. This software is licensed under the1# Copyright 2009-2011 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4from calendar import timegm
5from datetime import (
6 datetime,
7 timedelta,
8 )
9from math import floor
10import os
11from time import time
12
13from pytz import UTC
14from testtools.matchers import (
15 LessThan,
16 Not,
17 )
18from zope.component import getUtility4from zope.component import getUtility
19from zope.security.proxy import removeSecurityProxy
205
21from lp.services.gpg.interfaces import (6from lp.services.gpg.interfaces import (
22 GPGKeyDoesNotExistOnServer,7 GPGKeyDoesNotExistOnServer,
@@ -156,32 +141,6 @@
156 """Do we have the expected test keyring files"""141 """Do we have the expected test keyring files"""
157 self.assertEqual(len(list(test_keyrings())), 1)142 self.assertEqual(len(list(test_keyrings())), 1)
158143
159 def testHomeDirectoryJob(self):
160 """Does the job to touch the home work."""
161 gpghandler = getUtility(IGPGHandler)
162 naked_gpghandler = removeSecurityProxy(gpghandler)
163
164 # Get a list of all the files in the home directory.
165 files_to_check = [os.path.join(naked_gpghandler.home, f)
166 for f in os.listdir(naked_gpghandler.home)]
167 files_to_check.append(naked_gpghandler.home)
168 self.assertTrue(len(files_to_check) > 1)
169
170 # Set the last modified times to 12 hours ago
171 nowless12 = (datetime.now(UTC) - timedelta(hours=12)).utctimetuple()
172 lm_time = timegm(nowless12)
173 for fname in files_to_check:
174 os.utime(fname, (lm_time, lm_time))
175
176 # Touch the files and re-check the last modified times have been
177 # updated to "now".
178 now = floor(time())
179 gpghandler.touchConfigurationDirectory()
180 for fname in files_to_check:
181 file_time = os.path.getmtime(fname)
182 self.assertThat(
183 file_time, Not(LessThan(now)), fname)
184
185 def test_retrieveKey_raises_GPGKeyDoesNotExistOnServer(self):144 def test_retrieveKey_raises_GPGKeyDoesNotExistOnServer(self):
186 # GPGHandler.retrieveKey() raises GPGKeyDoesNotExistOnServer145 # GPGHandler.retrieveKey() raises GPGKeyDoesNotExistOnServer
187 # when called for a key that does not exist on the key server.146 # when called for a key that does not exist on the key server.