Merge lp:~cjwatson/launchpad/refactor-archive-signing into lp:launchpad
- refactor-archive-signing
- Merge into devel
Proposed by
Colin Watson
Status: | Merged |
---|---|
Merged at revision: | 18584 |
Proposed branch: | lp:~cjwatson/launchpad/refactor-archive-signing |
Merge into: | lp:launchpad |
Prerequisite: | lp:~cjwatson/launchpad/archive-signing-run-parts |
Diff against target: |
583 lines (+274/-30) 11 files modified
lib/lp/archivepublisher/archivesigningkey.py (+6/-4) lib/lp/archivepublisher/customupload.py (+17/-0) lib/lp/archivepublisher/debian_installer.py (+5/-1) lib/lp/archivepublisher/dist_upgrader.py (+6/-2) lib/lp/archivepublisher/publishing.py (+11/-10) lib/lp/archivepublisher/signing.py (+5/-5) lib/lp/archivepublisher/tests/test_customupload.py (+94/-2) lib/lp/archivepublisher/tests/test_debian_installer.py (+23/-2) lib/lp/archivepublisher/tests/test_dist_upgrader.py (+23/-2) lib/lp/archivepublisher/tests/test_publisher.py (+50/-1) lib/lp/archivepublisher/tests/test_signing.py (+34/-1) |
To merge this branch: | bzr merge lp:~cjwatson/launchpad/refactor-archive-signing |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
William Grant | code | Approve | |
Review via email: mp+336377@code.launchpad.net |
Commit message
Extend custom uploads and Release file signing to use the new ISignableArchive interface.
Description of the change
This replaces ubuntu-
To post a comment you must log in.
Revision history for this message
William Grant (wgrant) : | # |
review:
Approve
(code)
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'lib/lp/archivepublisher/archivesigningkey.py' |
2 | --- lib/lp/archivepublisher/archivesigningkey.py 2018-01-26 13:42:52 +0000 |
3 | +++ lib/lp/archivepublisher/archivesigningkey.py 2018-01-26 13:42:53 +0000 |
4 | @@ -119,19 +119,21 @@ |
5 | "No signing key available for %s" % |
6 | self.archive.displayname) |
7 | |
8 | - def signRepository(self, suite, log=None): |
9 | + def signRepository(self, suite, suffix='', log=None): |
10 | """See `ISignableArchive`.""" |
11 | suite_path = os.path.join(self._archive_root_path, 'dists', suite) |
12 | - release_file_path = os.path.join(suite_path, 'Release') |
13 | + release_file_path = os.path.join(suite_path, 'Release' + suffix) |
14 | if not os.path.exists(release_file_path): |
15 | raise AssertionError( |
16 | "Release file doesn't exist in the repository: %s" % |
17 | release_file_path) |
18 | |
19 | self._makeSignatures([ |
20 | - (release_file_path, os.path.join(suite_path, 'Release.gpg'), |
21 | + (release_file_path, |
22 | + os.path.join(suite_path, 'Release.gpg' + suffix), |
23 | SigningMode.DETACHED, suite), |
24 | - (release_file_path, os.path.join(suite_path, 'InRelease'), |
25 | + (release_file_path, |
26 | + os.path.join(suite_path, 'InRelease' + suffix), |
27 | SigningMode.CLEAR, suite), |
28 | ], log=log) |
29 | |
30 | |
31 | === modified file 'lib/lp/archivepublisher/customupload.py' |
32 | --- lib/lp/archivepublisher/customupload.py 2018-01-26 13:42:52 +0000 |
33 | +++ lib/lp/archivepublisher/customupload.py 2018-01-26 13:42:53 +0000 |
34 | @@ -26,6 +26,7 @@ |
35 | Version as make_version, |
36 | VersionError, |
37 | ) |
38 | +from lp.archivepublisher.interfaces.archivesigningkey import ISignableArchive |
39 | from lp.services.librarian.utils import copy_and_close |
40 | from lp.soyuz.interfaces.queue import ( |
41 | CustomUploadError, |
42 | @@ -268,6 +269,20 @@ |
43 | if not os.path.isdir(parentdir): |
44 | os.makedirs(parentdir, 0o755) |
45 | |
46 | + def shouldSign(self, filename): |
47 | + """Returns True if the given filename should be signed.""" |
48 | + return False |
49 | + |
50 | + def sign(self, archive, suite, filename): |
51 | + """Sign a file. |
52 | + |
53 | + For now, we always write a detached signature to the input file name |
54 | + plus ".gpg". |
55 | + """ |
56 | + signable_archive = ISignableArchive(archive) |
57 | + if signable_archive.can_sign: |
58 | + signable_archive.signFile(suite, filename, log=self.logger) |
59 | + |
60 | def installFiles(self, archive, suite): |
61 | """Install the files from the custom upload to the archive.""" |
62 | assert self.tmpdir is not None, "Must extract tarfile first" |
63 | @@ -316,6 +331,8 @@ |
64 | else: |
65 | shutil.copy(sourcepath, destpath) |
66 | os.chmod(destpath, 0o644) |
67 | + if self.shouldSign(destpath): |
68 | + self.sign(archive, suite, destpath) |
69 | |
70 | extracted = True |
71 | |
72 | |
73 | === modified file 'lib/lp/archivepublisher/debian_installer.py' |
74 | --- lib/lp/archivepublisher/debian_installer.py 2016-06-07 17:07:35 +0000 |
75 | +++ lib/lp/archivepublisher/debian_installer.py 2018-01-26 13:42:53 +0000 |
76 | @@ -1,4 +1,4 @@ |
77 | -# Copyright 2009-2016 Canonical Ltd. This software is licensed under the |
78 | +# Copyright 2009-2018 Canonical Ltd. This software is licensed under the |
79 | # GNU Affero General Public License version 3 (see the file LICENSE). |
80 | |
81 | """The processing of debian installer tarballs.""" |
82 | @@ -76,3 +76,7 @@ |
83 | |
84 | def shouldInstall(self, filename): |
85 | return filename.startswith('%s/' % self.version) |
86 | + |
87 | + def shouldSign(self, filename): |
88 | + """Sign checksums files.""" |
89 | + return filename.endswith('SUMS') |
90 | |
91 | === modified file 'lib/lp/archivepublisher/dist_upgrader.py' |
92 | --- lib/lp/archivepublisher/dist_upgrader.py 2016-06-07 17:07:35 +0000 |
93 | +++ lib/lp/archivepublisher/dist_upgrader.py 2018-01-26 13:42:53 +0000 |
94 | @@ -1,4 +1,4 @@ |
95 | -# Copyright 2009-2016 Canonical Ltd. This software is licensed under the |
96 | +# Copyright 2009-2018 Canonical Ltd. This software is licensed under the |
97 | # GNU Affero General Public License version 3 (see the file LICENSE). |
98 | |
99 | """The processing of dist-upgrader tarballs.""" |
100 | @@ -81,7 +81,7 @@ |
101 | return None |
102 | |
103 | def shouldInstall(self, filename): |
104 | - """ Install files from a dist-upgrader tarball. |
105 | + """Install files from a dist-upgrader tarball. |
106 | |
107 | It raises DistUpgraderBadVersion if if finds a directory name that |
108 | could not be treated as a valid Debian version. |
109 | @@ -100,3 +100,7 @@ |
110 | except BadUpstreamError as exc: |
111 | raise DistUpgraderBadVersion(self.tarfile_path, exc) |
112 | return version and not filename.startswith('current') |
113 | + |
114 | + def shouldSign(self, filename): |
115 | + """Sign *.tar.gz files.""" |
116 | + return filename.endswith('.tar.gz') |
117 | |
118 | === modified file 'lib/lp/archivepublisher/publishing.py' |
119 | --- lib/lp/archivepublisher/publishing.py 2018-01-26 13:42:52 +0000 |
120 | +++ lib/lp/archivepublisher/publishing.py 2018-01-26 13:42:53 +0000 |
121 | @@ -57,9 +57,7 @@ |
122 | build_source_stanza_fields, |
123 | build_translations_stanza_fields, |
124 | ) |
125 | -from lp.archivepublisher.interfaces.archivesigningkey import ( |
126 | - IArchiveSigningKey, |
127 | - ) |
128 | +from lp.archivepublisher.interfaces.archivesigningkey import ISignableArchive |
129 | from lp.archivepublisher.model.ftparchive import FTPArchiveHandler |
130 | from lp.archivepublisher.utils import ( |
131 | get_ppa_reference, |
132 | @@ -1245,20 +1243,23 @@ |
133 | if distroseries.publish_by_hash: |
134 | self._updateByHash(suite, "Release.new") |
135 | |
136 | - os.rename( |
137 | - os.path.join(suite_dir, "Release.new"), |
138 | - os.path.join(suite_dir, "Release")) |
139 | - |
140 | - if self.archive.signing_key is not None: |
141 | + signable_archive = ISignableArchive(self.archive) |
142 | + if signable_archive.can_sign: |
143 | # Sign the repository. |
144 | self.log.debug("Signing Release file for %s" % suite) |
145 | - IArchiveSigningKey(self.archive).signRepository(suite) |
146 | + signable_archive.signRepository(suite, suffix=".new", log=self.log) |
147 | core_files.add("Release.gpg") |
148 | core_files.add("InRelease") |
149 | else: |
150 | - # Skip signature if the archive signing key is undefined. |
151 | + # Skip signature if the archive is not set up for signing. |
152 | self.log.debug("No signing key available, skipping signature.") |
153 | |
154 | + for name in ("Release", "Release.gpg", "InRelease"): |
155 | + if name in core_files: |
156 | + os.rename( |
157 | + os.path.join(suite_dir, "%s.new" % name), |
158 | + os.path.join(suite_dir, name)) |
159 | + |
160 | # Make sure all the timestamps match, to make it easier to insert |
161 | # caching headers on mirrors. |
162 | self._syncTimestamps(suite, core_files) |
163 | |
164 | === modified file 'lib/lp/archivepublisher/signing.py' |
165 | --- lib/lp/archivepublisher/signing.py 2018-01-26 13:42:52 +0000 |
166 | +++ lib/lp/archivepublisher/signing.py 2018-01-26 13:42:53 +0000 |
167 | @@ -28,9 +28,6 @@ |
168 | |
169 | from lp.archivepublisher.config import getPubConfig |
170 | from lp.archivepublisher.customupload import CustomUpload |
171 | -from lp.archivepublisher.interfaces.archivesigningkey import ( |
172 | - IArchiveSigningKey, |
173 | - ) |
174 | from lp.services.osutils import remove_if_exists |
175 | from lp.soyuz.interfaces.queue import CustomUploadError |
176 | |
177 | @@ -379,12 +376,15 @@ |
178 | with DirectoryHash(versiondir, self.temproot) as hasher: |
179 | hasher.add_dir(versiondir) |
180 | for checksum_path in hasher.checksum_paths: |
181 | - if archive.signing_key is not None: |
182 | - IArchiveSigningKey(archive).signFile(suite, checksum_path) |
183 | + if self.shouldSign(checksum_path): |
184 | + self.sign(archive, suite, checksum_path) |
185 | |
186 | def shouldInstall(self, filename): |
187 | return filename.startswith("%s/" % self.version) |
188 | |
189 | + def shouldSign(self, filename): |
190 | + return filename.endswith("SUMS") |
191 | + |
192 | |
193 | class UefiUpload(SigningUpload): |
194 | """Legacy UEFI Signing custom upload. |
195 | |
196 | === modified file 'lib/lp/archivepublisher/tests/test_customupload.py' |
197 | --- lib/lp/archivepublisher/tests/test_customupload.py 2012-05-28 13:13:53 +0000 |
198 | +++ lib/lp/archivepublisher/tests/test_customupload.py 2018-01-26 13:42:53 +0000 |
199 | @@ -1,4 +1,4 @@ |
200 | -# Copyright 2009-2012 Canonical Ltd. This software is licensed under the |
201 | +# Copyright 2009-2018 Canonical Ltd. This software is licensed under the |
202 | # GNU Affero General Public License version 3 (see the file LICENSE). |
203 | |
204 | """Tests for `CustomUploads`.""" |
205 | @@ -13,13 +13,40 @@ |
206 | import tempfile |
207 | import unittest |
208 | |
209 | +from fixtures import MonkeyPatch |
210 | +from testtools.deferredruntest import AsynchronousDeferredRunTest |
211 | +from testtools.matchers import ( |
212 | + Equals, |
213 | + MatchesDict, |
214 | + Not, |
215 | + PathExists, |
216 | + ) |
217 | +from twisted.internet import defer |
218 | +from zope.component import getUtility |
219 | + |
220 | +from lp.archivepublisher.config import getPubConfig |
221 | from lp.archivepublisher.customupload import ( |
222 | CustomUpload, |
223 | CustomUploadTarballBadFile, |
224 | CustomUploadTarballBadSymLink, |
225 | CustomUploadTarballInvalidFileType, |
226 | ) |
227 | -from lp.testing import TestCase |
228 | +from lp.archivepublisher.interfaces.archivesigningkey import ( |
229 | + IArchiveSigningKey, |
230 | + ) |
231 | +from lp.archivepublisher.interfaces.publisherconfig import IPublisherConfigSet |
232 | +from lp.archivepublisher.tests.test_run_parts import RunPartsMixin |
233 | +from lp.services.gpg.interfaces import IGPGHandler |
234 | +from lp.services.osutils import write_file |
235 | +from lp.soyuz.enums import ArchivePurpose |
236 | +from lp.testing import ( |
237 | + TestCase, |
238 | + TestCaseWithFactory, |
239 | + ) |
240 | +from lp.testing.fakemethod import FakeMethod |
241 | +from lp.testing.gpgkeys import gpgkeysdir |
242 | +from lp.testing.keyserver import InProcessKeyServerFixture |
243 | +from lp.testing.layers import LaunchpadZopelessLayer |
244 | |
245 | |
246 | class TestCustomUpload(unittest.TestCase): |
247 | @@ -198,3 +225,68 @@ |
248 | self.custom_processor.extract) |
249 | finally: |
250 | shutil.rmtree(self.tarfile_path) |
251 | + |
252 | + |
253 | +class TestSigning(TestCaseWithFactory, RunPartsMixin): |
254 | + |
255 | + layer = LaunchpadZopelessLayer |
256 | + run_tests_with = AsynchronousDeferredRunTest.make_factory(timeout=10) |
257 | + |
258 | + def setUp(self): |
259 | + super(TestSigning, self).setUp() |
260 | + self.temp_dir = self.makeTemporaryDirectory() |
261 | + self.distro = self.factory.makeDistribution() |
262 | + db_pubconf = getUtility(IPublisherConfigSet).getByDistribution( |
263 | + self.distro) |
264 | + db_pubconf.root_dir = unicode(self.temp_dir) |
265 | + self.archive = self.factory.makeArchive( |
266 | + distribution=self.distro, purpose=ArchivePurpose.PRIMARY) |
267 | + |
268 | + def test_sign_without_signing_key(self): |
269 | + filename = os.path.join( |
270 | + getPubConfig(self.archive).archiveroot, "file") |
271 | + self.assertIsNone(self.archive.signing_key) |
272 | + custom_processor = CustomUpload() |
273 | + custom_processor.sign(self.archive, "suite", filename) |
274 | + self.assertThat("%s.gpg" % filename, Not(PathExists())) |
275 | + |
276 | + @defer.inlineCallbacks |
277 | + def test_sign_with_signing_key(self): |
278 | + filename = os.path.join( |
279 | + getPubConfig(self.archive).archiveroot, "file") |
280 | + write_file(filename, "contents") |
281 | + self.assertIsNone(self.archive.signing_key) |
282 | + self.useFixture(InProcessKeyServerFixture()).start() |
283 | + key_path = os.path.join(gpgkeysdir, 'ppa-sample@canonical.com.sec') |
284 | + yield IArchiveSigningKey(self.archive).setSigningKey( |
285 | + key_path, async_keyserver=True) |
286 | + self.assertIsNotNone(self.archive.signing_key) |
287 | + custom_processor = CustomUpload() |
288 | + custom_processor.sign(self.archive, "suite", filename) |
289 | + with open(filename) as cleartext_file: |
290 | + cleartext = cleartext_file.read() |
291 | + with open("%s.gpg" % filename) as signature_file: |
292 | + signature = getUtility(IGPGHandler).getVerifiedSignature( |
293 | + cleartext, signature_file.read()) |
294 | + self.assertEqual( |
295 | + self.archive.signing_key.fingerprint, signature.fingerprint) |
296 | + |
297 | + def test_sign_with_external_run_parts(self): |
298 | + self.enableRunParts(distribution_name=self.distro.name) |
299 | + filename = os.path.join( |
300 | + getPubConfig(self.archive).archiveroot, "file") |
301 | + write_file(filename, "contents") |
302 | + self.assertIsNone(self.archive.signing_key) |
303 | + run_parts_fixture = self.useFixture(MonkeyPatch( |
304 | + "lp.archivepublisher.archivesigningkey.run_parts", FakeMethod())) |
305 | + custom_processor = CustomUpload() |
306 | + custom_processor.sign(self.archive, "suite", filename) |
307 | + args, kwargs = run_parts_fixture.new_value.calls[0] |
308 | + self.assertEqual((self.distro.name, "sign.d"), args) |
309 | + self.assertThat(kwargs["env"], MatchesDict({ |
310 | + "INPUT_PATH": Equals(filename), |
311 | + "OUTPUT_PATH": Equals("%s.gpg" % filename), |
312 | + "MODE": Equals("detached"), |
313 | + "DISTRIBUTION": Equals(self.distro.name), |
314 | + "SUITE": Equals("suite"), |
315 | + })) |
316 | |
317 | === modified file 'lib/lp/archivepublisher/tests/test_debian_installer.py' |
318 | --- lib/lp/archivepublisher/tests/test_debian_installer.py 2016-06-07 17:07:35 +0000 |
319 | +++ lib/lp/archivepublisher/tests/test_debian_installer.py 2018-01-26 13:42:53 +0000 |
320 | @@ -1,4 +1,4 @@ |
321 | -# Copyright 2012-2016 Canonical Ltd. This software is licensed under the |
322 | +# Copyright 2012-2018 Canonical Ltd. This software is licensed under the |
323 | # GNU Affero General Public License version 3 (see the file LICENSE). |
324 | |
325 | """Test debian-installer custom uploads. |
326 | @@ -8,7 +8,9 @@ |
327 | """ |
328 | |
329 | import os |
330 | +from textwrap import dedent |
331 | |
332 | +from testtools.matchers import DirContains |
333 | from zope.component import getUtility |
334 | |
335 | from lp.archivepublisher.config import getPubConfig |
336 | @@ -18,13 +20,14 @@ |
337 | ) |
338 | from lp.archivepublisher.debian_installer import DebianInstallerUpload |
339 | from lp.archivepublisher.interfaces.publisherconfig import IPublisherConfigSet |
340 | +from lp.archivepublisher.tests.test_run_parts import RunPartsMixin |
341 | from lp.services.tarfile_helpers import LaunchpadWriteTarFile |
342 | from lp.soyuz.enums import ArchivePurpose |
343 | from lp.testing import TestCaseWithFactory |
344 | from lp.testing.layers import ZopelessDatabaseLayer |
345 | |
346 | |
347 | -class TestDebianInstaller(TestCaseWithFactory): |
348 | +class TestDebianInstaller(RunPartsMixin, TestCaseWithFactory): |
349 | |
350 | layer = ZopelessDatabaseLayer |
351 | |
352 | @@ -161,6 +164,24 @@ |
353 | self.assertEqual( |
354 | 0o755, os.stat(self.getInstallerPath(directory)).st_mode & 0o777) |
355 | |
356 | + def test_sign_with_external_run_parts(self): |
357 | + self.enableRunParts(distribution_name=self.distro.name) |
358 | + with open(os.path.join( |
359 | + self.parts_directory, self.distro.name, "sign.d", |
360 | + "10-sign"), "w") as f: |
361 | + f.write(dedent("""\ |
362 | + #! /bin/sh |
363 | + touch "$OUTPUT_PATH" |
364 | + """)) |
365 | + os.fchmod(f.fileno(), 0o755) |
366 | + self.openArchive() |
367 | + self.addFile("images/list", "a list") |
368 | + self.addFile("images/SHA256SUMS", "a checksum") |
369 | + self.process() |
370 | + self.assertThat( |
371 | + self.getInstallerPath("images"), |
372 | + DirContains(["list", "SHA256SUMS", "SHA256SUMS.gpg"])) |
373 | + |
374 | def test_getSeriesKey_extracts_architecture(self): |
375 | # getSeriesKey extracts the architecture from an upload's filename. |
376 | self.openArchive() |
377 | |
378 | === modified file 'lib/lp/archivepublisher/tests/test_dist_upgrader.py' |
379 | --- lib/lp/archivepublisher/tests/test_dist_upgrader.py 2016-06-07 17:07:35 +0000 |
380 | +++ lib/lp/archivepublisher/tests/test_dist_upgrader.py 2018-01-26 13:42:53 +0000 |
381 | @@ -1,4 +1,4 @@ |
382 | -# Copyright 2012-2016 Canonical Ltd. This software is licensed under the |
383 | +# Copyright 2012-2018 Canonical Ltd. This software is licensed under the |
384 | # GNU Affero General Public License version 3 (see the file LICENSE). |
385 | |
386 | """Test dist-upgrader custom uploads. |
387 | @@ -8,7 +8,9 @@ |
388 | """ |
389 | |
390 | import os |
391 | +from textwrap import dedent |
392 | |
393 | +from testtools.matchers import DirContains |
394 | from zope.component import getUtility |
395 | |
396 | from lp.archivepublisher.config import getPubConfig |
397 | @@ -21,6 +23,7 @@ |
398 | DistUpgraderUpload, |
399 | ) |
400 | from lp.archivepublisher.interfaces.publisherconfig import IPublisherConfigSet |
401 | +from lp.archivepublisher.tests.test_run_parts import RunPartsMixin |
402 | from lp.services.tarfile_helpers import LaunchpadWriteTarFile |
403 | from lp.soyuz.enums import ArchivePurpose |
404 | from lp.testing import TestCaseWithFactory |
405 | @@ -33,7 +36,7 @@ |
406 | self.archiveroot = archiveroot |
407 | |
408 | |
409 | -class TestDistUpgrader(TestCaseWithFactory): |
410 | +class TestDistUpgrader(RunPartsMixin, TestCaseWithFactory): |
411 | |
412 | layer = ZopelessDatabaseLayer |
413 | |
414 | @@ -109,6 +112,24 @@ |
415 | self.tarfile.add_file("foobar/foobar/dapper.tar.gz", "") |
416 | self.assertRaises(DistUpgraderBadVersion, self.process) |
417 | |
418 | + def test_sign_with_external_run_parts(self): |
419 | + self.enableRunParts(distribution_name=self.distro.name) |
420 | + with open(os.path.join( |
421 | + self.parts_directory, self.distro.name, "sign.d", |
422 | + "10-sign"), "w") as f: |
423 | + f.write(dedent("""\ |
424 | + #! /bin/sh |
425 | + touch "$OUTPUT_PATH" |
426 | + """)) |
427 | + os.fchmod(f.fileno(), 0o755) |
428 | + self.openArchive("20060302.0120") |
429 | + self.tarfile.add_file("20060302.0120/list", "a list") |
430 | + self.tarfile.add_file("20060302.0120/foo.tar.gz", "a tarball") |
431 | + self.process() |
432 | + self.assertThat( |
433 | + os.path.join(self.getUpgraderPath(), "20060302.0120"), |
434 | + DirContains(["list", "foo.tar.gz", "foo.tar.gz.gpg"])) |
435 | + |
436 | def test_getSeriesKey_extracts_architecture(self): |
437 | # getSeriesKey extracts the architecture from an upload's filename. |
438 | self.openArchive("20060302.0120") |
439 | |
440 | === modified file 'lib/lp/archivepublisher/tests/test_publisher.py' |
441 | --- lib/lp/archivepublisher/tests/test_publisher.py 2018-01-26 13:42:52 +0000 |
442 | +++ lib/lp/archivepublisher/tests/test_publisher.py 2018-01-26 13:42:53 +0000 |
443 | @@ -67,6 +67,7 @@ |
444 | I18nIndex, |
445 | Publisher, |
446 | ) |
447 | +from lp.archivepublisher.tests.test_run_parts import RunPartsMixin |
448 | from lp.archivepublisher.utils import RepositoryIndexFile |
449 | from lp.registry.interfaces.distribution import IDistributionSet |
450 | from lp.registry.interfaces.distroseries import IDistroSeries |
451 | @@ -2925,7 +2926,7 @@ |
452 | os.rename(temporary_dists, original_dists) |
453 | |
454 | |
455 | -class TestPublisherRepositorySignatures(TestPublisherBase): |
456 | +class TestPublisherRepositorySignatures(RunPartsMixin, TestPublisherBase): |
457 | """Testing `Publisher` signature behaviour.""" |
458 | |
459 | run_tests_with = AsynchronousDeferredRunTest.make_factory(timeout=10) |
460 | @@ -3065,6 +3066,54 @@ |
461 | self.assertThat( |
462 | sync_args[1], ContainsAll(['Release', 'Release.gpg', 'InRelease'])) |
463 | |
464 | + def testRepositorySignatureWithExternalRunParts(self): |
465 | + """Check publisher behaviour when signing repositories. |
466 | + |
467 | + When a 'sign.d' run-parts directory is configured for the archive, |
468 | + it is used to sign the Release file. |
469 | + """ |
470 | + cprov = getUtility(IPersonSet).getByName('cprov') |
471 | + self.assertIsNone(cprov.archive.signing_key) |
472 | + self.enableRunParts(distribution_name=cprov.archive.distribution.name) |
473 | + sign_directory = os.path.join( |
474 | + self.parts_directory, cprov.archive.distribution.name, 'sign.d') |
475 | + with open(os.path.join(sign_directory, '10-sign'), 'w') as sign_script: |
476 | + sign_script.write(dedent("""\ |
477 | + #! /bin/sh |
478 | + echo "$MODE signature of $INPUT_PATH ($DISTRIBUTION/$SUITE)" \\ |
479 | + >"$OUTPUT_PATH" |
480 | + """)) |
481 | + os.fchmod(sign_script.fileno(), 0o755) |
482 | + |
483 | + self.setupPublisher(cprov.archive) |
484 | + self.archive_publisher._syncTimestamps = FakeMethod() |
485 | + |
486 | + self._publishArchive(cprov.archive) |
487 | + |
488 | + # Release exists. |
489 | + self.assertThat(self.release_file_path, PathExists()) |
490 | + |
491 | + # Release.gpg and InRelease exist with suitable fake signatures. |
492 | + # Note that the signatures are made before Release.new is renamed to |
493 | + # to Release. |
494 | + self.assertThat( |
495 | + self.release_file_signature_path, |
496 | + FileContains( |
497 | + "detached signature of %s.new (%s/breezy-autotest)\n" % |
498 | + (self.release_file_path, cprov.archive.distribution.name))) |
499 | + self.assertThat( |
500 | + self.inline_release_file_path, |
501 | + FileContains( |
502 | + "clear signature of %s.new (%s/breezy-autotest)\n" % |
503 | + (self.release_file_path, cprov.archive.distribution.name))) |
504 | + |
505 | + # The publisher synchronises the various Release file timestamps. |
506 | + self.assertEqual(1, self.archive_publisher._syncTimestamps.call_count) |
507 | + sync_args = self.archive_publisher._syncTimestamps.extract_args()[0] |
508 | + self.assertEqual(self.distroseries.name, sync_args[0]) |
509 | + self.assertThat( |
510 | + sync_args[1], ContainsAll(['Release', 'Release.gpg', 'InRelease'])) |
511 | + |
512 | |
513 | class TestPublisherLite(TestCaseWithFactory): |
514 | """Lightweight unit tests for the publisher.""" |
515 | |
516 | === modified file 'lib/lp/archivepublisher/tests/test_signing.py' |
517 | --- lib/lp/archivepublisher/tests/test_signing.py 2018-01-26 13:42:52 +0000 |
518 | +++ lib/lp/archivepublisher/tests/test_signing.py 2018-01-26 13:42:53 +0000 |
519 | @@ -13,9 +13,11 @@ |
520 | from testtools.deferredruntest import AsynchronousDeferredRunTest |
521 | from testtools.matchers import ( |
522 | Contains, |
523 | + Equals, |
524 | FileContains, |
525 | Matcher, |
526 | MatchesAll, |
527 | + MatchesDict, |
528 | Mismatch, |
529 | Not, |
530 | StartsWith, |
531 | @@ -36,6 +38,7 @@ |
532 | SigningUpload, |
533 | UefiUpload, |
534 | ) |
535 | +from lp.archivepublisher.tests.test_run_parts import RunPartsMixin |
536 | from lp.services.osutils import write_file |
537 | from lp.services.tarfile_helpers import LaunchpadWriteTarFile |
538 | from lp.soyuz.enums import ArchivePurpose |
539 | @@ -215,7 +218,7 @@ |
540 | return os.path.join(pubconf.archiveroot, "dists", self.suite, "main") |
541 | |
542 | |
543 | -class TestSigning(TestSigningHelpers): |
544 | +class TestSigning(RunPartsMixin, TestSigningHelpers): |
545 | |
546 | def getSignedPath(self, loader_type, arch): |
547 | return os.path.join(self.getDistsPath(), "signed", |
548 | @@ -934,6 +937,36 @@ |
549 | "1.0/signed.tar.gz", |
550 | ]])) |
551 | |
552 | + def test_checksumming_tree_signed_with_external_run_parts(self): |
553 | + # Checksum files can be signed using an external run-parts helper. |
554 | + # We disable subprocess.call because there's just too much going on, |
555 | + # so we can't test this completely, but we can at least test that |
556 | + # run_parts is called. |
557 | + self.enableRunParts(distribution_name=self.distro.name) |
558 | + run_parts_fixture = self.useFixture(MonkeyPatch( |
559 | + "lp.archivepublisher.archivesigningkey.run_parts", FakeMethod())) |
560 | + self.setUpUefiKeys() |
561 | + self.setUpKmodKeys() |
562 | + self.setUpOpalKeys() |
563 | + self.openArchive("test", "1.0", "amd64") |
564 | + self.tarfile.add_file("1.0/empty.efi", "") |
565 | + self.tarfile.add_file("1.0/empty.ko", "") |
566 | + self.tarfile.add_file("1.0/empty.opal", "") |
567 | + self.process_emulate() |
568 | + sha256file = os.path.join(self.getSignedPath("test", "amd64"), |
569 | + "1.0", "SHA256SUMS") |
570 | + self.assertTrue(os.path.exists(sha256file)) |
571 | + self.assertEqual(1, run_parts_fixture.new_value.call_count) |
572 | + args, kwargs = run_parts_fixture.new_value.calls[-1] |
573 | + self.assertEqual((self.distro.name, "sign.d"), args) |
574 | + self.assertThat(kwargs["env"], MatchesDict({ |
575 | + "INPUT_PATH": Equals(sha256file), |
576 | + "OUTPUT_PATH": Equals("%s.gpg" % sha256file), |
577 | + "MODE": Equals("detached"), |
578 | + "DISTRIBUTION": Equals(self.distro.name), |
579 | + "SUITE": Equals(self.suite), |
580 | + })) |
581 | + |
582 | |
583 | class TestUefi(TestSigningHelpers): |
584 |