Merge ~cjwatson/launchpad:sync-signingkeys-options into launchpad:master
- Git
- lp:~cjwatson/launchpad
- sync-signingkeys-options
- Merge into master
Proposed by
Colin Watson
Status: | Merged |
---|---|
Approved by: | Colin Watson |
Approved revision: | f5757ce5e641a25d8a0c5e4e53e8ce532010a303 |
Merge reported by: | Otto Co-Pilot |
Merged at revision: | not available |
Proposed branch: | ~cjwatson/launchpad:sync-signingkeys-options |
Merge into: | launchpad:master |
Diff against target: |
773 lines (+357/-134) 8 files modified
database/schema/security.cfg (+1/-1) lib/lp/archivepublisher/scripts/sync_signingkeys.py (+87/-26) lib/lp/archivepublisher/signing.py (+1/-1) lib/lp/archivepublisher/tests/test_sync_signingkeys.py (+181/-10) lib/lp/services/signing/interfaces/signingkey.py (+14/-6) lib/lp/services/signing/model/signingkey.py (+24/-24) lib/lp/services/signing/tests/test_signingkey.py (+43/-65) scripts/sync-signingkeys.py (+6/-1) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Thiago F. Pappacena (community) | Approve | ||
Review via email: mp+387203@code.launchpad.net |
Commit message
Add several new options to sync-signingkeys
Description of the change
--archive and --type allow limiting processing to a single archive and/or signing key type.
--overwrite allows overwriting keys that already exist on the signing service. Use with care, and probably only in conjunction with --archive and --type.
--dry-run just reports what would be done. This may be useful if preparing for an overwriting run.
I had to do some rearrangement of ArchiveSigningK
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | diff --git a/database/schema/security.cfg b/database/schema/security.cfg |
2 | index 93dc9f8..0b3996c 100644 |
3 | --- a/database/schema/security.cfg |
4 | +++ b/database/schema/security.cfg |
5 | @@ -1207,7 +1207,7 @@ public.account = SELECT, INSERT, UPDATE |
6 | public.archive = SELECT, INSERT, UPDATE |
7 | public.archivearch = SELECT, INSERT, UPDATE, DELETE |
8 | public.archivejob = SELECT, INSERT |
9 | -public.archivesigningkey = SELECT, INSERT, UPDATE |
10 | +public.archivesigningkey = SELECT, INSERT, UPDATE, DELETE |
11 | public.binarypackagebuild = SELECT, INSERT, UPDATE |
12 | public.binarypackagefile = SELECT, INSERT, UPDATE |
13 | public.binarypackagename = SELECT, INSERT, UPDATE |
14 | diff --git a/lib/lp/archivepublisher/scripts/sync_signingkeys.py b/lib/lp/archivepublisher/scripts/sync_signingkeys.py |
15 | index e0b89d2..aa7fb53 100644 |
16 | --- a/lib/lp/archivepublisher/scripts/sync_signingkeys.py |
17 | +++ b/lib/lp/archivepublisher/scripts/sync_signingkeys.py |
18 | @@ -16,15 +16,20 @@ from datetime import datetime |
19 | import os |
20 | |
21 | from pytz import utc |
22 | +from storm.locals import Store |
23 | import transaction |
24 | from zope.component import getUtility |
25 | |
26 | from lp.archivepublisher.config import getPubConfig |
27 | from lp.archivepublisher.model.publisherconfig import PublisherConfig |
28 | from lp.services.database.interfaces import IStore |
29 | -from lp.services.scripts.base import LaunchpadScript |
30 | +from lp.services.scripts.base import ( |
31 | + LaunchpadScript, |
32 | + LaunchpadScriptFailure, |
33 | + ) |
34 | from lp.services.signing.enums import SigningKeyType |
35 | from lp.services.signing.interfaces.signingkey import IArchiveSigningKeySet |
36 | +from lp.soyuz.interfaces.archive import IArchiveSet |
37 | from lp.soyuz.model.archive import Archive |
38 | |
39 | |
40 | @@ -35,23 +40,62 @@ class SyncSigningKeysScript(LaunchpadScript): |
41 | |
42 | def add_my_options(self): |
43 | self.parser.add_option( |
44 | + "-A", "--archive", |
45 | + help=( |
46 | + "The reference of the archive to process " |
47 | + "(default: all archives).")) |
48 | + self.parser.add_option( |
49 | + "-t", "--type", |
50 | + help="The type of keys to process (default: all types).") |
51 | + |
52 | + self.parser.add_option( |
53 | "-l", "--limit", dest="limit", type=int, |
54 | help="How many archives to fetch.") |
55 | - |
56 | self.parser.add_option( |
57 | "-o", "--offset", dest="offset", type=int, |
58 | help="Offset on archives list.") |
59 | |
60 | + self.parser.add_option( |
61 | + "--overwrite", action="store_true", default=False, |
62 | + help="Overwrite keys that already exist on the signing service.") |
63 | + self.parser.add_option( |
64 | + "-n", "--dry-run", action="store_true", default=False, |
65 | + help="Report what would be done, but don't actually inject keys.") |
66 | + |
67 | def getArchives(self): |
68 | """Gets the list of archives that should be processed.""" |
69 | - archives = IStore(Archive).find( |
70 | - Archive, |
71 | - PublisherConfig.distribution_id == Archive.distributionID) |
72 | - archives = archives.order_by(Archive.id) |
73 | + if self.options.archive is not None: |
74 | + archive = getUtility(IArchiveSet).getByReference( |
75 | + self.options.archive) |
76 | + if archive is None: |
77 | + raise LaunchpadScriptFailure( |
78 | + "No archive named '%s' could be found." % |
79 | + self.options.archive) |
80 | + archives = [archive] |
81 | + else: |
82 | + archives = IStore(Archive).find( |
83 | + Archive, |
84 | + PublisherConfig.distribution_id == Archive.distributionID) |
85 | + archives = archives.order_by(Archive.id) |
86 | start = self.options.offset if self.options.offset else 0 |
87 | end = start + self.options.limit if self.options.limit else None |
88 | return archives[start:end] |
89 | |
90 | + def getKeyTypes(self): |
91 | + """Gets the list of key types that should be processed.""" |
92 | + if self.options.type is not None: |
93 | + try: |
94 | + key_type = SigningKeyType.getTermByToken( |
95 | + self.options.type).value |
96 | + except LookupError: |
97 | + raise LaunchpadScriptFailure( |
98 | + "There is no signing key type named '%s'." % |
99 | + self.options.type) |
100 | + key_types = [key_type] |
101 | + else: |
102 | + key_types = SigningKeyType.items |
103 | + return key_types |
104 | + |
105 | def getKeysPerType(self, dir): |
106 | """Returns the existing key files per type in the given directory. |
107 | |
108 | @@ -68,7 +112,7 @@ class SyncSigningKeysScript(LaunchpadScript): |
109 | os.path.join("fit", "fit.crt")), |
110 | } |
111 | found_keys_per_type = {} |
112 | - for key_type in SigningKeyType.items: |
113 | + for key_type in self.getKeyTypes(): |
114 | files = [os.path.join(dir, f) for f in keys_per_type[key_type]] |
115 | self.logger.debug("Checking files %s...", ', '.join(files)) |
116 | if all(os.path.exists(f) for f in files): |
117 | @@ -102,25 +146,39 @@ class SyncSigningKeysScript(LaunchpadScript): |
118 | |
119 | def inject(self, archive, key_type, series, priv_key_path, pub_key_path): |
120 | arch_signing_key_set = getUtility(IArchiveSigningKeySet) |
121 | - existing_signing_key = arch_signing_key_set.getSigningKey( |
122 | + existing_archive_signing_key = arch_signing_key_set.get( |
123 | key_type, archive, series, exact_match=True) |
124 | - if existing_signing_key is not None: |
125 | - self.logger.info("Signing key for %s / %s / %s already exists", |
126 | - key_type, archive.reference, |
127 | - series.name if series else None) |
128 | - return existing_signing_key |
129 | - |
130 | - with open(priv_key_path, 'rb') as fd: |
131 | - private_key = fd.read() |
132 | - with open(pub_key_path, 'rb') as fd: |
133 | - public_key = fd.read() |
134 | - |
135 | - now = datetime.now().replace(tzinfo=utc) |
136 | - description = u"%s key for %s" % (key_type.name, archive.reference) |
137 | - return arch_signing_key_set.inject( |
138 | - key_type, private_key, public_key, |
139 | - description, now, archive, |
140 | - earliest_distro_series=series) |
141 | + if existing_archive_signing_key is not None: |
142 | + if self.options.overwrite: |
143 | + self.logger.info( |
144 | + "Overwriting existing signing key for %s / %s / %s", |
145 | + key_type, archive.reference, |
146 | + series.name if series else None) |
147 | + Store.of(existing_archive_signing_key).remove( |
148 | + existing_archive_signing_key) |
149 | + else: |
150 | + self.logger.info( |
151 | + "Signing key for %s / %s / %s already exists", |
152 | + key_type, archive.reference, |
153 | + series.name if series else None) |
154 | + return existing_archive_signing_key |
155 | + |
156 | + if self.options.dry_run: |
157 | + self.logger.info( |
158 | + "Would inject signing key for %s / %s / %s", |
159 | + key_type, archive.reference, series.name if series else None) |
160 | + else: |
161 | + with open(priv_key_path, 'rb') as fd: |
162 | + private_key = fd.read() |
163 | + with open(pub_key_path, 'rb') as fd: |
164 | + public_key = fd.read() |
165 | + |
166 | + now = datetime.now().replace(tzinfo=utc) |
167 | + description = u"%s key for %s" % (key_type.name, archive.reference) |
168 | + return arch_signing_key_set.inject( |
169 | + key_type, private_key, public_key, |
170 | + description, now, archive, |
171 | + earliest_distro_series=series) |
172 | |
173 | def processArchive(self, archive): |
174 | for series, path in self.getSeriesPaths(archive).items(): |
175 | @@ -137,5 +195,8 @@ class SyncSigningKeysScript(LaunchpadScript): |
176 | self.logger.info( |
177 | "#%s - Processing keys for archive %s.", i, archive.reference) |
178 | self.processArchive(archive) |
179 | - transaction.commit() |
180 | + if self.options.dry_run: |
181 | + transaction.abort() |
182 | + else: |
183 | + transaction.commit() |
184 | self.logger.info("Finished processing archives injections.") |
185 | diff --git a/lib/lp/archivepublisher/signing.py b/lib/lp/archivepublisher/signing.py |
186 | index 1eca0a4..0425242 100644 |
187 | --- a/lib/lp/archivepublisher/signing.py |
188 | +++ b/lib/lp/archivepublisher/signing.py |
189 | @@ -453,7 +453,7 @@ class SigningUpload(CustomUpload): |
190 | return |
191 | |
192 | key_set = getUtility(IArchiveSigningKeySet) |
193 | - current_key = key_set.getSigningKey( |
194 | + current_key = key_set.get( |
195 | key_type, self.archive, None, exact_match=True) |
196 | if current_key is not None: |
197 | self.logger.info("Skipping injection for key type %s: archive " |
198 | diff --git a/lib/lp/archivepublisher/tests/test_sync_signingkeys.py b/lib/lp/archivepublisher/tests/test_sync_signingkeys.py |
199 | index eb185de..d20e676 100644 |
200 | --- a/lib/lp/archivepublisher/tests/test_sync_signingkeys.py |
201 | +++ b/lib/lp/archivepublisher/tests/test_sync_signingkeys.py |
202 | @@ -21,6 +21,7 @@ from fixtures import ( |
203 | from pytz import utc |
204 | from testtools.content import text_content |
205 | from testtools.matchers import ( |
206 | + ContainsAll, |
207 | Equals, |
208 | MatchesDict, |
209 | MatchesStructure, |
210 | @@ -31,6 +32,7 @@ from zope.component import getUtility |
211 | from lp.archivepublisher.model.publisherconfig import PublisherConfig |
212 | from lp.archivepublisher.scripts.sync_signingkeys import SyncSigningKeysScript |
213 | from lp.services.compat import mock |
214 | +from lp.services.config import config |
215 | from lp.services.config.fixture import ( |
216 | ConfigFixture, |
217 | ConfigUseFixture, |
218 | @@ -43,6 +45,7 @@ from lp.services.signing.testing.fixture import SigningServiceFixture |
219 | from lp.services.signing.tests.helpers import SigningServiceClientFixture |
220 | from lp.soyuz.model.archive import Archive |
221 | from lp.testing import TestCaseWithFactory |
222 | +from lp.testing.dbuser import dbuser |
223 | from lp.testing.layers import ZopelessDatabaseLayer |
224 | from lp.testing.script import run_script |
225 | |
226 | @@ -65,7 +68,9 @@ class TestSyncSigningKeysScript(TestCaseWithFactory): |
227 | self.useFixture(ConfigUseFixture(config_name)) |
228 | |
229 | def makeScript(self, test_args): |
230 | - script = SyncSigningKeysScript("test-sync", test_args=test_args) |
231 | + script = SyncSigningKeysScript( |
232 | + "test-sync", dbuser=config.archivepublisher.dbuser, |
233 | + test_args=test_args) |
234 | script.logger = BufferLogger() |
235 | return script |
236 | |
237 | @@ -110,6 +115,22 @@ class TestSyncSigningKeysScript(TestCaseWithFactory): |
238 | archives = list(script.getArchives()) |
239 | self.assertEqual(all_archives[2:5], archives) |
240 | |
241 | + def test_fetch_archives_with_reference(self): |
242 | + all_archives = list(self.makeArchives()) |
243 | + script = self.makeScript(["--archive", all_archives[0].reference]) |
244 | + archives = list(script.getArchives()) |
245 | + self.assertEqual([all_archives[0]], archives) |
246 | + |
247 | + def test_get_key_types(self): |
248 | + script = self.makeScript([]) |
249 | + key_types = script.getKeyTypes() |
250 | + self.assertEqual(SigningKeyType.items, key_types) |
251 | + |
252 | + def test_get_key_types_with_selection(self): |
253 | + script = self.makeScript(["--type", "UEFI"]) |
254 | + key_types = script.getKeyTypes() |
255 | + self.assertEqual([SigningKeyType.UEFI], key_types) |
256 | + |
257 | def test_get_keys_per_type(self): |
258 | keys_dir = self.signing_root_dir |
259 | |
260 | @@ -178,8 +199,7 @@ class TestSyncSigningKeysScript(TestCaseWithFactory): |
261 | 'wb') as fd: |
262 | fd.write(b"Series 1 %s" % filename) |
263 | |
264 | - script = self.makeScript([]) |
265 | - script.getArchives = mock.Mock(return_value=[archive]) |
266 | + script = self.makeScript(["--archive", archive.reference]) |
267 | script.inject = mock.Mock() |
268 | script.main() |
269 | |
270 | @@ -234,6 +254,103 @@ class TestSyncSigningKeysScript(TestCaseWithFactory): |
271 | SigningKeyType.UEFI, None), |
272 | content) |
273 | |
274 | + def test_process_archive_dry_run(self): |
275 | + signing_service_client = self.useFixture( |
276 | + SigningServiceClientFixture(self.factory)) |
277 | + |
278 | + distro = self.factory.makeDistribution() |
279 | + series1 = self.factory.makeDistroSeries(distribution=distro) |
280 | + series2 = self.factory.makeDistroSeries(distribution=distro) |
281 | + |
282 | + archive = self.factory.makeArchive(distribution=distro) |
283 | + key_dirs = self.makeArchiveSigningDir(archive, [series1, series2]) |
284 | + |
285 | + archive_root = key_dirs[None] |
286 | + |
287 | + archive_signing_key_fit = self.factory.makeArchiveSigningKey( |
288 | + archive=archive, distro_series=series1, |
289 | + signing_key=self.factory.makeSigningKey( |
290 | + key_type=SigningKeyType.FIT)) |
291 | + fingerprint_fit = archive_signing_key_fit.signing_key.fingerprint |
292 | + |
293 | + transaction.commit() |
294 | + |
295 | + # Create fake UEFI keys for the root |
296 | + for filename in ("uefi.key", "uefi.crt"): |
297 | + with open(os.path.join(archive_root, filename), 'wb') as fd: |
298 | + fd.write(b"Root %s" % filename) |
299 | + |
300 | + # Create fake OPAL and Kmod keys for series1 |
301 | + for filename in ("opal.pem", "opal.x509", "kmod.pem", "kmod.x509"): |
302 | + with open(os.path.join(key_dirs[series1], filename), 'wb') as fd: |
303 | + fd.write(b"Series 1 %s" % filename) |
304 | + |
305 | + # Create fake FIT keys for series1 |
306 | + os.makedirs(os.path.join(key_dirs[series1], "fit")) |
307 | + for filename in ("fit.key", "fit.crt"): |
308 | + with open(os.path.join(key_dirs[series1], "fit", filename), |
309 | + 'wb') as fd: |
310 | + fd.write(b"Series 1 %s" % filename) |
311 | + |
312 | + script = self.makeScript( |
313 | + ["--archive", archive.reference, "--overwrite", "--dry-run"]) |
314 | + script.main() |
315 | + |
316 | + self.assertEqual(0, signing_service_client.inject.call_count) |
317 | + |
318 | + # No changes are committed to the database. |
319 | + archive_signing_key_set = getUtility(IArchiveSigningKeySet) |
320 | + self.assertIsNone( |
321 | + archive_signing_key_set.getSigningKey( |
322 | + SigningKeyType.UEFI, archive, None, exact_match=True)) |
323 | + self.assertIsNone( |
324 | + archive_signing_key_set.getSigningKey( |
325 | + SigningKeyType.KMOD, archive, series1, exact_match=True)) |
326 | + self.assertIsNone( |
327 | + archive_signing_key_set.getSigningKey( |
328 | + SigningKeyType.OPAL, archive, series1, exact_match=True)) |
329 | + self.assertThat( |
330 | + archive_signing_key_set.getSigningKey( |
331 | + SigningKeyType.FIT, archive, series1, exact_match=True), |
332 | + MatchesStructure.byEquality(fingerprint=fingerprint_fit)) |
333 | + |
334 | + # Check the log messages. |
335 | + found_tpl = "INFO Found key files %s / %s (type=%s, series=%s)." |
336 | + overwrite_tpl = ( |
337 | + "INFO Overwriting existing signing key for %s / %s / %s") |
338 | + inject_tpl = "INFO Would inject signing key for %s / %s / %s" |
339 | + self.assertThat( |
340 | + script.logger.content.as_text().splitlines(), |
341 | + ContainsAll([ |
342 | + "INFO #0 - Processing keys for archive %s." % |
343 | + archive.reference, |
344 | + found_tpl % ( |
345 | + os.path.join(archive_root, "uefi.key"), |
346 | + os.path.join(archive_root, "uefi.crt"), |
347 | + SigningKeyType.UEFI, None), |
348 | + inject_tpl % (SigningKeyType.UEFI, archive.reference, None), |
349 | + found_tpl % ( |
350 | + os.path.join(key_dirs[series1], "kmod.pem"), |
351 | + os.path.join(key_dirs[series1], "kmod.x509"), |
352 | + SigningKeyType.KMOD, series1.name), |
353 | + inject_tpl % ( |
354 | + SigningKeyType.KMOD, archive.reference, series1.name), |
355 | + found_tpl % ( |
356 | + os.path.join(key_dirs[series1], "opal.pem"), |
357 | + os.path.join(key_dirs[series1], "opal.x509"), |
358 | + SigningKeyType.OPAL, series1.name), |
359 | + inject_tpl % ( |
360 | + SigningKeyType.OPAL, archive.reference, series1.name), |
361 | + found_tpl % ( |
362 | + os.path.join(key_dirs[series1], "fit", "fit.key"), |
363 | + os.path.join(key_dirs[series1], "fit", "fit.crt"), |
364 | + SigningKeyType.FIT, series1.name), |
365 | + overwrite_tpl % ( |
366 | + SigningKeyType.FIT, archive.reference, series1.name), |
367 | + inject_tpl % ( |
368 | + SigningKeyType.FIT, archive.reference, series1.name), |
369 | + ])) |
370 | + |
371 | def test_inject(self): |
372 | signing_service_client = self.useFixture( |
373 | SigningServiceClientFixture(self.factory)) |
374 | @@ -258,8 +375,10 @@ class TestSyncSigningKeysScript(TestCaseWithFactory): |
375 | |
376 | script = self.makeScript([]) |
377 | |
378 | - result_with_series = script.inject( |
379 | - archive, SigningKeyType.UEFI, series, priv_key_path, pub_key_path) |
380 | + with dbuser(config.archivepublisher.dbuser): |
381 | + result_with_series = script.inject( |
382 | + archive, SigningKeyType.UEFI, series, |
383 | + priv_key_path, pub_key_path) |
384 | |
385 | self.assertThat(result_with_series, MatchesStructure.byEquality( |
386 | archive=archive, |
387 | @@ -278,8 +397,10 @@ class TestSyncSigningKeysScript(TestCaseWithFactory): |
388 | u"UEFI key for %s" % archive.reference, now.replace(tzinfo=utc)), |
389 | signing_service_client.inject.call_args[0]) |
390 | |
391 | - result_no_series = script.inject( |
392 | - archive, SigningKeyType.UEFI, None, priv_key_path, pub_key_path) |
393 | + with dbuser(config.archivepublisher.dbuser): |
394 | + result_no_series = script.inject( |
395 | + archive, SigningKeyType.UEFI, None, |
396 | + priv_key_path, pub_key_path) |
397 | |
398 | self.assertThat(result_no_series, MatchesStructure.byEquality( |
399 | archive=archive, |
400 | @@ -312,12 +433,13 @@ class TestSyncSigningKeysScript(TestCaseWithFactory): |
401 | fd.write(b"Public key content") |
402 | |
403 | expected_arch_signing_key = self.factory.makeArchiveSigningKey( |
404 | - archive=archive, distro_series=series).signing_key |
405 | + archive=archive, distro_series=series) |
406 | key_type = expected_arch_signing_key.key_type |
407 | |
408 | script = self.makeScript([]) |
409 | - got_arch_key = script.inject( |
410 | - archive, key_type, series, priv_key_path, pub_key_path) |
411 | + with dbuser(config.archivepublisher.dbuser): |
412 | + got_arch_key = script.inject( |
413 | + archive, key_type, series, priv_key_path, pub_key_path) |
414 | self.assertEqual(expected_arch_signing_key, got_arch_key) |
415 | |
416 | self.assertIn( |
417 | @@ -325,6 +447,55 @@ class TestSyncSigningKeysScript(TestCaseWithFactory): |
418 | (key_type, archive.reference, series.name), |
419 | script.logger.content.as_text()) |
420 | |
421 | + def test_inject_existing_key_with_overwrite(self): |
422 | + signing_service_client = self.useFixture( |
423 | + SigningServiceClientFixture(self.factory)) |
424 | + |
425 | + now = datetime.now() |
426 | + mock_datetime = self.useFixture(MockPatch( |
427 | + 'lp.archivepublisher.scripts.sync_signingkeys.datetime')).mock |
428 | + mock_datetime.now = lambda: now |
429 | + |
430 | + distro = self.factory.makeDistribution() |
431 | + series = self.factory.makeDistroSeries(distribution=distro) |
432 | + archive = self.factory.makeArchive(distribution=distro) |
433 | + |
434 | + tmpdir = self.useFixture(TempDir()).path |
435 | + priv_key_path = os.path.join(tmpdir, "priv.key") |
436 | + pub_key_path = os.path.join(tmpdir, "pub.crt") |
437 | + with open(priv_key_path, 'wb') as fd: |
438 | + fd.write(b"Private key content") |
439 | + with open(pub_key_path, 'wb') as fd: |
440 | + fd.write(b"Public key content") |
441 | + |
442 | + self.factory.makeArchiveSigningKey( |
443 | + archive=archive, distro_series=series, |
444 | + signing_key=self.factory.makeSigningKey( |
445 | + key_type=SigningKeyType.UEFI)) |
446 | + |
447 | + script = self.makeScript(["--overwrite"]) |
448 | + with dbuser(config.archivepublisher.dbuser): |
449 | + result = script.inject( |
450 | + archive, SigningKeyType.UEFI, series, |
451 | + priv_key_path, pub_key_path) |
452 | + |
453 | + self.assertThat(result, MatchesStructure( |
454 | + archive=Equals(archive), |
455 | + earliest_distro_series=Equals(series), |
456 | + key_type=Equals(SigningKeyType.UEFI), |
457 | + signing_key=MatchesStructure.byEquality( |
458 | + key_type=SigningKeyType.UEFI, |
459 | + public_key=b"Public key content"))) |
460 | + self.assertEqual( |
461 | + [(SigningKeyType.UEFI, b"Private key content", |
462 | + b"Public key content", |
463 | + "UEFI key for %s" % archive.reference, now.replace(tzinfo=utc))], |
464 | + signing_service_client.inject.call_args) |
465 | + self.assertIn( |
466 | + "Overwriting existing signing key for %s / %s / %s" % |
467 | + (SigningKeyType.UEFI, archive.reference, series.name), |
468 | + script.logger.content.as_text()) |
469 | + |
470 | def runScript(self): |
471 | transaction.commit() |
472 | ret, out, err = run_script("scripts/sync-signingkeys.py") |
473 | diff --git a/lib/lp/services/signing/interfaces/signingkey.py b/lib/lp/services/signing/interfaces/signingkey.py |
474 | index eccf2a0..77814b8 100644 |
475 | --- a/lib/lp/services/signing/interfaces/signingkey.py |
476 | +++ b/lib/lp/services/signing/interfaces/signingkey.py |
477 | @@ -117,14 +117,22 @@ class IArchiveSigningKeySet(Interface): |
478 | False if it was updated). |
479 | """ |
480 | |
481 | - def getSigningKey(key_type, archive, distro_series, exact_match=False): |
482 | - """Get the most suitable key for a given archive / distro series |
483 | - pair. |
484 | + def get(key_type, archive, distro_series, exact_match=False): |
485 | + """Get the most suitable ArchiveSigningKey for a given context. |
486 | |
487 | :param exact_match: If True, returns the ArchiveSigningKey matching |
488 | - exactly the given key_type, archive and |
489 | - distro_series. If False, gets the best match. |
490 | - :return: The most suitable key |
491 | + exactly the given key_type, archive and distro_series. If False, |
492 | + gets the best match. |
493 | + :return: The most suitable key, or None. |
494 | + """ |
495 | + |
496 | + def getSigningKey(key_type, archive, distro_series, exact_match=False): |
497 | + """Get the most suitable SigningKey for a given context. |
498 | + |
499 | + :param exact_match: If True, returns the SigningKey matching exactly |
500 | + the given key_type, archive and distro_series. If False, gets |
501 | + the best match. |
502 | + :return: The most suitable key, or None. |
503 | """ |
504 | |
505 | def generate(key_type, description, archive, earliest_distro_series=None): |
506 | diff --git a/lib/lp/services/signing/model/signingkey.py b/lib/lp/services/signing/model/signingkey.py |
507 | index 5557e7e..0c754f9 100644 |
508 | --- a/lib/lp/services/signing/model/signingkey.py |
509 | +++ b/lib/lp/services/signing/model/signingkey.py |
510 | @@ -12,8 +12,6 @@ __all__ = [ |
511 | 'SigningKey', |
512 | ] |
513 | |
514 | -from collections import defaultdict |
515 | - |
516 | import pytz |
517 | from storm.locals import ( |
518 | Bytes, |
519 | @@ -175,46 +173,48 @@ class ArchiveSigningKeySet: |
520 | return obj |
521 | |
522 | @classmethod |
523 | - def getSigningKey(cls, key_type, archive, distro_series, |
524 | - exact_match=False): |
525 | + def get(cls, key_type, archive, distro_series, exact_match=False): |
526 | store = IStore(ArchiveSigningKey) |
527 | - # Gets all the keys of the given key_type available for the archive |
528 | - rs = store.find(ArchiveSigningKey, |
529 | - SigningKey.id == ArchiveSigningKey.signing_key_id, |
530 | - SigningKey.key_type == key_type, |
531 | - ArchiveSigningKey.key_type == key_type, |
532 | - ArchiveSigningKey.archive == archive) |
533 | + |
534 | + # Get all the keys of the given key_type available for the archive. |
535 | + archive_signing_keys = store.find( |
536 | + ArchiveSigningKey, |
537 | + ArchiveSigningKey.key_type == key_type, |
538 | + ArchiveSigningKey.archive == archive) |
539 | |
540 | if exact_match: |
541 | - rs = rs.find( |
542 | + archive_signing_keys = archive_signing_keys.find( |
543 | ArchiveSigningKey.earliest_distro_series == distro_series) |
544 | |
545 | - # prefetch related signing keys to avoid extra queries. |
546 | - signing_keys = store.find(SigningKey, [ |
547 | - SigningKey.id.is_in([i.signing_key_id for i in rs])]) |
548 | - signing_keys_by_id = {i.id: i for i in signing_keys} |
549 | - |
550 | - # Group keys per type, and per distro series |
551 | - keys_per_series = defaultdict(dict) |
552 | - for i in rs: |
553 | - signing_key = signing_keys_by_id[i.signing_key_id] |
554 | - keys_per_series[i.earliest_distro_series] = signing_key |
555 | + # Group keys per distro series. |
556 | + keys_per_series = { |
557 | + archive_signing_key.earliest_distro_series_id: archive_signing_key |
558 | + for archive_signing_key in archive_signing_keys} |
559 | |
560 | - # Let's search the most suitable per key type. |
561 | + # Find the most suitable per key type. |
562 | found_series = False |
563 | # Note that archive.distribution.series is, by default, sorted by |
564 | # "version", reversed. |
565 | for series in archive.distribution.series: |
566 | if series == distro_series: |
567 | found_series = True |
568 | - if found_series and series in keys_per_series: |
569 | - return keys_per_series[series] |
570 | + if found_series and series.id in keys_per_series: |
571 | + return keys_per_series[series.id] |
572 | # If no specific key for distro_series was found, returns |
573 | # the keys for the archive itself (or None if no key is |
574 | # available for the archive either). |
575 | return keys_per_series.get(None) |
576 | |
577 | @classmethod |
578 | + def getSigningKey(cls, key_type, archive, distro_series, |
579 | + exact_match=False): |
580 | + archive_signing_key = cls.get( |
581 | + key_type, archive, distro_series, exact_match=exact_match) |
582 | + return ( |
583 | + None if archive_signing_key is None |
584 | + else archive_signing_key.signing_key) |
585 | + |
586 | + @classmethod |
587 | def generate(cls, key_type, description, archive, |
588 | earliest_distro_series=None): |
589 | signing_key = SigningKey.generate(key_type, description) |
590 | diff --git a/lib/lp/services/signing/tests/test_signingkey.py b/lib/lp/services/signing/tests/test_signingkey.py |
591 | index 81376b1..a7d7360 100644 |
592 | --- a/lib/lp/services/signing/tests/test_signingkey.py |
593 | +++ b/lib/lp/services/signing/tests/test_signingkey.py |
594 | @@ -141,6 +141,22 @@ class TestArchiveSigningKey(TestCaseWithFactory): |
595 | client = removeSecurityProxy(getUtility(ISigningServiceClient)) |
596 | self.addCleanup(client._cleanCaches) |
597 | |
598 | + def assertGet(self, expected_archive_signing_key, |
599 | + key_type, archive, distro_series, exact_match=False): |
600 | + # get and getSigningKey return the expected results. |
601 | + arch_signing_key_set = getUtility(IArchiveSigningKeySet) |
602 | + expected_signing_key = ( |
603 | + None if expected_archive_signing_key is None |
604 | + else expected_archive_signing_key.signing_key) |
605 | + self.assertEqual( |
606 | + expected_archive_signing_key, |
607 | + arch_signing_key_set.get( |
608 | + key_type, archive, distro_series, exact_match=exact_match)) |
609 | + self.assertEqual( |
610 | + expected_signing_key, |
611 | + arch_signing_key_set.getSigningKey( |
612 | + key_type, archive, distro_series, exact_match=exact_match)) |
613 | + |
614 | @responses.activate |
615 | def test_generate_saves_correctly(self): |
616 | self.signing_service.addResponses(self) |
617 | @@ -280,9 +296,6 @@ class TestArchiveSigningKey(TestCaseWithFactory): |
618 | self.assertEqual(2, store.find(ArchiveSigningKey).count()) |
619 | |
620 | def test_get_signing_keys_without_distro_series_configured(self): |
621 | - UEFI = SigningKeyType.UEFI |
622 | - KMOD = SigningKeyType.KMOD |
623 | - |
624 | archive = self.factory.makeArchive() |
625 | distro_series = archive.distribution.series[0] |
626 | uefi_key = self.factory.makeSigningKey( |
627 | @@ -304,25 +317,16 @@ class TestArchiveSigningKey(TestCaseWithFactory): |
628 | archive, None, kmod_key) |
629 | |
630 | # Should find the keys if we ask for the archive key |
631 | - self.assertEqual( |
632 | - arch_uefi_key.signing_key, |
633 | - arch_signing_key_set.getSigningKey(UEFI, archive, None)) |
634 | - self.assertEqual( |
635 | - arch_kmod_key.signing_key, |
636 | - arch_signing_key_set.getSigningKey(KMOD, archive, None)) |
637 | + self.assertGet(arch_uefi_key, SigningKeyType.UEFI, archive, None) |
638 | + self.assertGet(arch_kmod_key, SigningKeyType.KMOD, archive, None) |
639 | |
640 | # Should find the key if we ask for archive + distro_series key |
641 | - self.assertEqual( |
642 | - arch_uefi_key.signing_key, |
643 | - arch_signing_key_set.getSigningKey(UEFI, archive, distro_series)) |
644 | - self.assertEqual( |
645 | - arch_kmod_key.signing_key, |
646 | - arch_signing_key_set.getSigningKey(KMOD, archive, distro_series)) |
647 | + self.assertGet( |
648 | + arch_uefi_key, SigningKeyType.UEFI, archive, distro_series) |
649 | + self.assertGet( |
650 | + arch_kmod_key, SigningKeyType.KMOD, archive, distro_series) |
651 | |
652 | def test_get_signing_key_exact_match(self): |
653 | - UEFI = SigningKeyType.UEFI |
654 | - KMOD = SigningKeyType.KMOD |
655 | - |
656 | archive = self.factory.makeArchive() |
657 | distro_series1 = archive.distribution.series[0] |
658 | distro_series2 = archive.distribution.series[1] |
659 | @@ -342,38 +346,27 @@ class TestArchiveSigningKey(TestCaseWithFactory): |
660 | archive, None, kmod_key) |
661 | |
662 | # Should get the UEFI key for distro_series1 |
663 | - self.assertEqual( |
664 | - series1_uefi_key.signing_key, |
665 | - arch_signing_key_set.getSigningKey( |
666 | - UEFI, archive, distro_series1, exact_match=True) |
667 | - ) |
668 | + self.assertGet( |
669 | + series1_uefi_key, |
670 | + SigningKeyType.UEFI, archive, distro_series1, exact_match=True) |
671 | # Should get the archive's KMOD key. |
672 | - self.assertEqual( |
673 | - arch_kmod_key.signing_key, |
674 | - arch_signing_key_set.getSigningKey( |
675 | - KMOD, archive, None, exact_match=True) |
676 | - ) |
677 | + self.assertGet( |
678 | + arch_kmod_key, |
679 | + SigningKeyType.KMOD, archive, None, exact_match=True) |
680 | # distro_series1 has no KMOD key. |
681 | - self.assertEqual( |
682 | + self.assertGet( |
683 | None, |
684 | - arch_signing_key_set.getSigningKey( |
685 | - KMOD, archive, distro_series1, exact_match=True) |
686 | - ) |
687 | + SigningKeyType.KMOD, archive, distro_series1, exact_match=True) |
688 | # distro_series2 has no key at all. |
689 | - self.assertEqual( |
690 | + self.assertGet( |
691 | None, |
692 | - arch_signing_key_set.getSigningKey( |
693 | - KMOD, archive, distro_series2, exact_match=True) |
694 | - ) |
695 | + SigningKeyType.KMOD, archive, distro_series2, exact_match=True) |
696 | |
697 | def test_get_signing_keys_with_distro_series_configured(self): |
698 | - UEFI = SigningKeyType.UEFI |
699 | - KMOD = SigningKeyType.KMOD |
700 | - |
701 | archive = self.factory.makeArchive() |
702 | series = archive.distribution.series |
703 | - uefi_key = self.factory.makeSigningKey(key_type=UEFI) |
704 | - kmod_key = self.factory.makeSigningKey(key_type=KMOD) |
705 | + uefi_key = self.factory.makeSigningKey(key_type=SigningKeyType.UEFI) |
706 | + kmod_key = self.factory.makeSigningKey(key_type=SigningKeyType.KMOD) |
707 | |
708 | # Fill the database with keys from other archives to make sure we |
709 | # are filtering it out |
710 | @@ -395,34 +388,19 @@ class TestArchiveSigningKey(TestCaseWithFactory): |
711 | |
712 | # If no distroseries is specified, it should give back no KMOD key, |
713 | # since we don't have a default |
714 | - self.assertEqual( |
715 | - arch_uefi_key.signing_key, |
716 | - arch_signing_key_set.getSigningKey(UEFI, archive, None)) |
717 | - self.assertEqual( |
718 | - None, |
719 | - arch_signing_key_set.getSigningKey(KMOD, archive, None)) |
720 | + self.assertGet(arch_uefi_key, SigningKeyType.UEFI, archive, None) |
721 | + self.assertGet(None, SigningKeyType.KMOD, archive, None) |
722 | |
723 | # For the most recent series, use the KMOD key we've set for the |
724 | # previous one |
725 | - self.assertEqual( |
726 | - arch_uefi_key.signing_key, |
727 | - arch_signing_key_set.getSigningKey(UEFI, archive, series[0])) |
728 | - self.assertEqual( |
729 | - arch_kmod_key.signing_key, |
730 | - arch_signing_key_set.getSigningKey(KMOD, archive, series[0])) |
731 | + self.assertGet(arch_uefi_key, SigningKeyType.UEFI, archive, series[0]) |
732 | + self.assertGet(arch_kmod_key, SigningKeyType.KMOD, archive, series[0]) |
733 | |
734 | # For the previous series, we have a KMOD key configured |
735 | - self.assertEqual( |
736 | - arch_uefi_key.signing_key, |
737 | - arch_signing_key_set.getSigningKey(UEFI, archive, series[1])) |
738 | - self.assertEqual( |
739 | - arch_kmod_key.signing_key, |
740 | - arch_signing_key_set.getSigningKey(KMOD, archive, series[1])) |
741 | + self.assertGet(arch_uefi_key, SigningKeyType.UEFI, archive, series[1]) |
742 | + self.assertGet(arch_kmod_key, SigningKeyType.KMOD, archive, series[1]) |
743 | |
744 | # For the old series, we have an old KMOD key configured |
745 | - self.assertEqual( |
746 | - arch_uefi_key.signing_key, |
747 | - arch_signing_key_set.getSigningKey(UEFI, archive, series[2])) |
748 | - self.assertEqual( |
749 | - old_arch_kmod_key.signing_key, |
750 | - arch_signing_key_set.getSigningKey(KMOD, archive, series[2])) |
751 | + self.assertGet(arch_uefi_key, SigningKeyType.UEFI, archive, series[2]) |
752 | + self.assertGet( |
753 | + old_arch_kmod_key, SigningKeyType.KMOD, archive, series[2]) |
754 | diff --git a/scripts/sync-signingkeys.py b/scripts/sync-signingkeys.py |
755 | index 4cdb5cb..84310b0 100755 |
756 | --- a/scripts/sync-signingkeys.py |
757 | +++ b/scripts/sync-signingkeys.py |
758 | @@ -3,9 +3,14 @@ |
759 | # GNU Affero General Public License version 3 (see the file LICENSE). |
760 | |
761 | """Script to inject archive keys into signing service.""" |
762 | + |
763 | import _pythonpath |
764 | + |
765 | from lp.archivepublisher.scripts.sync_signingkeys import SyncSigningKeysScript |
766 | +from lp.services.config import config |
767 | + |
768 | |
769 | if __name__ == '__main__': |
770 | - script = SyncSigningKeysScript('sync-signingkeys') |
771 | + script = SyncSigningKeysScript( |
772 | + 'sync-signingkeys', dbuser=config.archivepublisher.dbuser) |
773 | script.lock_and_run() |
LGTM