Merge ~pappacena/launchpad:inject-lp-signing-generated-keys into launchpad:master
- Git
- lp:~pappacena/launchpad
- inject-lp-signing-generated-keys
- Merge into master
Proposed by
Thiago F. Pappacena
Status: | Merged |
---|---|
Approved by: | Thiago F. Pappacena |
Approved revision: | 00e44a212547fc747080ec3266508faee1a25fcc |
Merge reported by: | Otto Co-Pilot |
Merged at revision: | not available |
Proposed branch: | ~pappacena/launchpad:inject-lp-signing-generated-keys |
Merge into: | launchpad:master |
Diff against target: |
433 lines (+255/-6) 6 files modified
lib/lp/archivepublisher/signing.py (+77/-1) lib/lp/archivepublisher/tests/test_signing.py (+111/-3) lib/lp/services/signing/interfaces/signingkey.py (+4/-1) lib/lp/services/signing/model/signingkey.py (+6/-1) lib/lp/services/signing/tests/helpers.py (+10/-0) lib/lp/services/signing/tests/test_signingkey.py (+47/-0) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Colin Watson (community) | Approve | ||
Review via email: mp+382779@code.launchpad.net |
Commit message
Adding the possibility to inject into lp-signing the locally generated signing keys.
It is possible to control which key types to inject when auto-generating them by setting the feature flag `archivepublish
Description of the change
To post a comment you must log in.
Revision history for this message
Colin Watson (cjwatson) : | # |
review:
Needs Information
Revision history for this message
Thiago F. Pappacena (pappacena) wrote : | # |
Revision history for this message
Colin Watson (cjwatson) : | # |
review:
Needs Fixing
Revision history for this message
Thiago F. Pappacena (pappacena) wrote : | # |
cjwatson, I'll push some of the requested changes, but I would like further clarification on some of your previous comments.
Revision history for this message
Colin Watson (cjwatson) : | # |
Revision history for this message
Thiago F. Pappacena (pappacena) wrote : | # |
Pushed the requested changes. It might be good to have another round of review on the duplicated ArchiveSigningKey check.
Revision history for this message
Colin Watson (cjwatson) : | # |
review:
Needs Fixing
Revision history for this message
Thiago F. Pappacena (pappacena) wrote : | # |
Pushed the requested changes (with some comments, cjwatson).
Revision history for this message
Thiago F. Pappacena (pappacena) : | # |
Revision history for this message
Colin Watson (cjwatson) : | # |
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | diff --git a/lib/lp/archivepublisher/signing.py b/lib/lp/archivepublisher/signing.py | |||
2 | index 9a175db..1eca0a4 100644 | |||
3 | --- a/lib/lp/archivepublisher/signing.py | |||
4 | +++ b/lib/lp/archivepublisher/signing.py | |||
5 | @@ -18,6 +18,7 @@ __all__ = [ | |||
6 | 18 | "UefiUpload", | 18 | "UefiUpload", |
7 | 19 | ] | 19 | ] |
8 | 20 | 20 | ||
9 | 21 | from datetime import datetime | ||
10 | 21 | from functools import partial | 22 | from functools import partial |
11 | 22 | import os | 23 | import os |
12 | 23 | import shutil | 24 | import shutil |
13 | @@ -27,6 +28,7 @@ import tarfile | |||
14 | 27 | import tempfile | 28 | import tempfile |
15 | 28 | import textwrap | 29 | import textwrap |
16 | 29 | 30 | ||
17 | 31 | from pytz import utc | ||
18 | 30 | import scandir | 32 | import scandir |
19 | 31 | from zope.component import getUtility | 33 | from zope.component import getUtility |
20 | 32 | 34 | ||
21 | @@ -42,6 +44,8 @@ from lp.soyuz.interfaces.queue import CustomUploadError | |||
22 | 42 | 44 | ||
23 | 43 | PUBLISHER_USES_SIGNING_SERVICE = ( | 45 | PUBLISHER_USES_SIGNING_SERVICE = ( |
24 | 44 | 'archivepublisher.signing_service.enabled') | 46 | 'archivepublisher.signing_service.enabled') |
25 | 47 | PUBLISHER_SIGNING_SERVICE_INJECTS_KEYS = ( | ||
26 | 48 | 'archivepublisher.signing_service.injection.enabled') | ||
27 | 45 | 49 | ||
28 | 46 | 50 | ||
29 | 47 | class SigningUploadPackError(CustomUploadError): | 51 | class SigningUploadPackError(CustomUploadError): |
30 | @@ -59,6 +63,10 @@ class SigningServiceError(Exception): | |||
31 | 59 | pass | 63 | pass |
32 | 60 | 64 | ||
33 | 61 | 65 | ||
34 | 66 | class SigningKeyConflict(Exception): | ||
35 | 67 | pass | ||
36 | 68 | |||
37 | 69 | |||
38 | 62 | class SigningUpload(CustomUpload): | 70 | class SigningUpload(CustomUpload): |
39 | 63 | """Signing custom upload. | 71 | """Signing custom upload. |
40 | 64 | 72 | ||
41 | @@ -260,7 +268,7 @@ class SigningUpload(CustomUpload): | |||
42 | 260 | # tend to make the publisher rather upset. | 268 | # tend to make the publisher rather upset. |
43 | 261 | if self.logger is not None: | 269 | if self.logger is not None: |
44 | 262 | self.logger.warning("%s Failed (cmd='%s')" % | 270 | self.logger.warning("%s Failed (cmd='%s')" % |
46 | 263 | (description, " ".join(cmdl))) | 271 | (description, " ".join(cmdl))) |
47 | 264 | return status | 272 | return status |
48 | 265 | 273 | ||
49 | 266 | def findSigningHandlers(self): | 274 | def findSigningHandlers(self): |
50 | @@ -421,6 +429,56 @@ class SigningUpload(CustomUpload): | |||
51 | 421 | return [None for k in keynames] | 429 | return [None for k in keynames] |
52 | 422 | return keynames | 430 | return keynames |
53 | 423 | 431 | ||
54 | 432 | def injectIntoSigningService( | ||
55 | 433 | self, key_type, private_key_file, public_key_file): | ||
56 | 434 | """Injects the given key pair into signing service for current | ||
57 | 435 | archive. | ||
58 | 436 | |||
59 | 437 | Note that this injection should only be used for freshly | ||
60 | 438 | autogenerated keys, always injecting the key for the archive in | ||
61 | 439 | general (not setting earliest_distro_series). | ||
62 | 440 | """ | ||
63 | 441 | if key_type not in SigningKeyType: | ||
64 | 442 | raise ValueError("%s is not a valid key type to inject" % key_type) | ||
65 | 443 | |||
66 | 444 | feature_flag = ( | ||
67 | 445 | getFeatureFlag(PUBLISHER_SIGNING_SERVICE_INJECTS_KEYS) or '') | ||
68 | 446 | key_types_to_inject = [i.strip() for i in feature_flag.split()] | ||
69 | 447 | |||
70 | 448 | if key_type.name not in key_types_to_inject: | ||
71 | 449 | if self.logger: | ||
72 | 450 | self.logger.info( | ||
73 | 451 | "Skipping injection for key type %s: not in %s", | ||
74 | 452 | key_type, key_types_to_inject) | ||
75 | 453 | return | ||
76 | 454 | |||
77 | 455 | key_set = getUtility(IArchiveSigningKeySet) | ||
78 | 456 | current_key = key_set.getSigningKey( | ||
79 | 457 | key_type, self.archive, None, exact_match=True) | ||
80 | 458 | if current_key is not None: | ||
81 | 459 | self.logger.info("Skipping injection for key type %s: archive " | ||
82 | 460 | "already has a key on lp-signing.", key_type) | ||
83 | 461 | raise SigningKeyConflict( | ||
84 | 462 | "Archive %s already has a signing key type %s on lp-signing." | ||
85 | 463 | % (self.archive.reference, key_type)) | ||
86 | 464 | |||
87 | 465 | if self.logger: | ||
88 | 466 | self.logger.info( | ||
89 | 467 | "Injecting key_type %s for archive %s into signing service", | ||
90 | 468 | key_type, self.archive.name) | ||
91 | 469 | |||
92 | 470 | with open(private_key_file, 'rb') as fd: | ||
93 | 471 | private_key = fd.read() | ||
94 | 472 | with open(public_key_file, 'rb') as fd: | ||
95 | 473 | public_key = fd.read() | ||
96 | 474 | |||
97 | 475 | now = datetime.now().replace(tzinfo=utc) | ||
98 | 476 | description = ( | ||
99 | 477 | u"%s key for %s" % (key_type.name, self.archive.reference)) | ||
100 | 478 | key_set.inject( | ||
101 | 479 | key_type, private_key, public_key, | ||
102 | 480 | description, now, self.archive, earliest_distro_series=None) | ||
103 | 481 | |||
104 | 424 | def generateKeyCommonName(self, owner, archive, suffix=''): | 482 | def generateKeyCommonName(self, owner, archive, suffix=''): |
105 | 425 | # PPA <owner> <archive> <suffix> | 483 | # PPA <owner> <archive> <suffix> |
106 | 426 | # truncate <owner> <archive> to ensure the overall form is shorter | 484 | # truncate <owner> <archive> to ensure the overall form is shorter |
107 | @@ -454,6 +512,15 @@ class SigningUpload(CustomUpload): | |||
108 | 454 | if os.path.exists(cert_filename): | 512 | if os.path.exists(cert_filename): |
109 | 455 | os.chmod(cert_filename, 0o644) | 513 | os.chmod(cert_filename, 0o644) |
110 | 456 | 514 | ||
111 | 515 | signing_key_type = getattr(SigningKeyType, key_type.upper()) | ||
112 | 516 | try: | ||
113 | 517 | self.injectIntoSigningService( | ||
114 | 518 | signing_key_type, key_filename, cert_filename) | ||
115 | 519 | except SigningKeyConflict: | ||
116 | 520 | os.unlink(key_filename) | ||
117 | 521 | os.unlink(cert_filename) | ||
118 | 522 | raise | ||
119 | 523 | |||
120 | 457 | def generateUefiKeys(self): | 524 | def generateUefiKeys(self): |
121 | 458 | """Generate new UEFI Keys for this archive.""" | 525 | """Generate new UEFI Keys for this archive.""" |
122 | 459 | self.generateKeyCrtPair("UEFI", self.uefi_key, self.uefi_cert) | 526 | self.generateKeyCrtPair("UEFI", self.uefi_key, self.uefi_cert) |
123 | @@ -541,6 +608,15 @@ class SigningUpload(CustomUpload): | |||
124 | 541 | if os.path.exists(x509_filename): | 608 | if os.path.exists(x509_filename): |
125 | 542 | os.chmod(x509_filename, 0o644) | 609 | os.chmod(x509_filename, 0o644) |
126 | 543 | 610 | ||
127 | 611 | signing_key_type = getattr(SigningKeyType, key_type.upper()) | ||
128 | 612 | try: | ||
129 | 613 | self.injectIntoSigningService( | ||
130 | 614 | signing_key_type, pem_filename, x509_filename) | ||
131 | 615 | except SigningKeyConflict: | ||
132 | 616 | os.unlink(pem_filename) | ||
133 | 617 | os.unlink(x509_filename) | ||
134 | 618 | raise | ||
135 | 619 | |||
136 | 544 | def generateKmodKeys(self): | 620 | def generateKmodKeys(self): |
137 | 545 | """Generate new Kernel Signing Keys for this archive.""" | 621 | """Generate new Kernel Signing Keys for this archive.""" |
138 | 546 | config = self.generateOpensslConfig("Kmod", self.openssl_config_kmod) | 622 | config = self.generateOpensslConfig("Kmod", self.openssl_config_kmod) |
139 | diff --git a/lib/lp/archivepublisher/tests/test_signing.py b/lib/lp/archivepublisher/tests/test_signing.py | |||
140 | index 87cf391..e51bb25 100644 | |||
141 | --- a/lib/lp/archivepublisher/tests/test_signing.py | |||
142 | +++ b/lib/lp/archivepublisher/tests/test_signing.py | |||
143 | @@ -7,14 +7,19 @@ from __future__ import absolute_import, print_function, unicode_literals | |||
144 | 7 | 7 | ||
145 | 8 | __metaclass__ = type | 8 | __metaclass__ = type |
146 | 9 | 9 | ||
147 | 10 | from datetime import datetime | ||
148 | 10 | import os | 11 | import os |
149 | 11 | import re | 12 | import re |
150 | 12 | import shutil | 13 | import shutil |
151 | 13 | import stat | 14 | import stat |
152 | 14 | import tarfile | 15 | import tarfile |
153 | 15 | 16 | ||
155 | 16 | from fixtures import MonkeyPatch | 17 | from fixtures import ( |
156 | 18 | MockPatch, | ||
157 | 19 | MonkeyPatch, | ||
158 | 20 | ) | ||
159 | 17 | from mock import call | 21 | from mock import call |
160 | 22 | from pytz import utc | ||
161 | 18 | import scandir | 23 | import scandir |
162 | 19 | from testtools.matchers import ( | 24 | from testtools.matchers import ( |
163 | 20 | Contains, | 25 | Contains, |
164 | @@ -42,12 +47,15 @@ from lp.archivepublisher.interfaces.archivegpgsigningkey import ( | |||
165 | 42 | ) | 47 | ) |
166 | 43 | from lp.archivepublisher.interfaces.publisherconfig import IPublisherConfigSet | 48 | from lp.archivepublisher.interfaces.publisherconfig import IPublisherConfigSet |
167 | 44 | from lp.archivepublisher.signing import ( | 49 | from lp.archivepublisher.signing import ( |
168 | 50 | PUBLISHER_SIGNING_SERVICE_INJECTS_KEYS, | ||
169 | 45 | PUBLISHER_USES_SIGNING_SERVICE, | 51 | PUBLISHER_USES_SIGNING_SERVICE, |
170 | 52 | SigningKeyConflict, | ||
171 | 46 | SigningUpload, | 53 | SigningUpload, |
172 | 47 | UefiUpload, | 54 | UefiUpload, |
173 | 48 | ) | 55 | ) |
174 | 49 | from lp.archivepublisher.tests.test_run_parts import RunPartsMixin | 56 | from lp.archivepublisher.tests.test_run_parts import RunPartsMixin |
175 | 50 | from lp.services.features.testing import FeatureFixture | 57 | from lp.services.features.testing import FeatureFixture |
176 | 58 | from lp.services.log.logger import BufferLogger | ||
177 | 51 | from lp.services.osutils import write_file | 59 | from lp.services.osutils import write_file |
178 | 52 | from lp.services.signing.enums import SigningMode | 60 | from lp.services.signing.enums import SigningMode |
179 | 53 | from lp.services.signing.proxy import SigningKeyType | 61 | from lp.services.signing.proxy import SigningKeyType |
180 | @@ -1851,7 +1859,8 @@ class TestSigningUploadWithSigningService(TestSigningHelpers): | |||
181 | 1851 | 1859 | ||
182 | 1852 | self.openArchive("test", "1.0", "amd64") | 1860 | self.openArchive("test", "1.0", "amd64") |
183 | 1853 | for filename in filenames: | 1861 | for filename in filenames: |
185 | 1854 | self.tarfile.add_file(filename, b"data - %s" % filename) | 1862 | self.tarfile.add_file( |
186 | 1863 | filename, b"data - %s" % filename.encode("UTF-8")) | ||
187 | 1855 | 1864 | ||
188 | 1856 | self.tarfile.close() | 1865 | self.tarfile.close() |
189 | 1857 | self.buffer.close() | 1866 | self.buffer.close() |
190 | @@ -1965,7 +1974,8 @@ class TestSigningUploadWithSigningService(TestSigningHelpers): | |||
191 | 1965 | 1974 | ||
192 | 1966 | self.openArchive("test", "1.0", "amd64") | 1975 | self.openArchive("test", "1.0", "amd64") |
193 | 1967 | for filename in filenames: | 1976 | for filename in filenames: |
195 | 1968 | self.tarfile.add_file(filename, b"data - %s" % filename) | 1977 | self.tarfile.add_file( |
196 | 1978 | filename, b"data - %s" % filename.encode("UTF-8")) | ||
197 | 1969 | 1979 | ||
198 | 1970 | self.tarfile.close() | 1980 | self.tarfile.close() |
199 | 1971 | self.buffer.close() | 1981 | self.buffer.close() |
200 | @@ -2009,3 +2019,101 @@ class TestSigningUploadWithSigningService(TestSigningHelpers): | |||
201 | 2009 | self.assertEqual( | 2019 | self.assertEqual( |
202 | 2010 | [(os.path.join(upload.tmpdir_used, "1.0/empty.efi"),)], | 2020 | [(os.path.join(upload.tmpdir_used, "1.0/empty.efi"),)], |
203 | 2011 | upload.signUefi.extract_args()) | 2021 | upload.signUefi.extract_args()) |
204 | 2022 | |||
205 | 2023 | def test_fallback_injects_key(self): | ||
206 | 2024 | self.useFixture(FeatureFixture({PUBLISHER_USES_SIGNING_SERVICE: ''})) | ||
207 | 2025 | self.useFixture(FeatureFixture({ | ||
208 | 2026 | PUBLISHER_SIGNING_SERVICE_INJECTS_KEYS: 'SIPL OPAL'})) | ||
209 | 2027 | |||
210 | 2028 | now = datetime.now() | ||
211 | 2029 | mock_datetime = self.useFixture(MockPatch( | ||
212 | 2030 | 'lp.archivepublisher.signing.datetime')).mock | ||
213 | 2031 | mock_datetime.now = lambda: now | ||
214 | 2032 | |||
215 | 2033 | logger = BufferLogger() | ||
216 | 2034 | upload = SigningUpload(logger=logger) | ||
217 | 2035 | |||
218 | 2036 | # Setup PPA to ensure it auto-generates keys. | ||
219 | 2037 | self.setUpPPA() | ||
220 | 2038 | |||
221 | 2039 | filenames = ["1.0/empty.efi", "1.0/empty.opal"] | ||
222 | 2040 | |||
223 | 2041 | self.openArchive("test", "1.0", "amd64") | ||
224 | 2042 | for filename in filenames: | ||
225 | 2043 | self.tarfile.add_file( | ||
226 | 2044 | filename, b"data - %s" % filename.encode("UTF-8")) | ||
227 | 2045 | self.tarfile.close() | ||
228 | 2046 | self.buffer.close() | ||
229 | 2047 | |||
230 | 2048 | upload.process(self.archive, self.path, self.suite) | ||
231 | 2049 | self.assertTrue(upload.autokey) | ||
232 | 2050 | |||
233 | 2051 | # Read the key file content | ||
234 | 2052 | with open(upload.opal_pem, 'rb') as fd: | ||
235 | 2053 | private_key = fd.read() | ||
236 | 2054 | with open(upload.opal_x509, 'rb') as fd: | ||
237 | 2055 | public_key = fd.read() | ||
238 | 2056 | |||
239 | 2057 | # Check if we called lp-signing's /inject endpoint correctly | ||
240 | 2058 | self.assertEqual(1, self.signing_service_client.inject.call_count) | ||
241 | 2059 | self.assertEqual( | ||
242 | 2060 | (SigningKeyType.OPAL, private_key, public_key, | ||
243 | 2061 | u"OPAL key for %s" % self.archive.reference, | ||
244 | 2062 | now.replace(tzinfo=utc)), | ||
245 | 2063 | self.signing_service_client.inject.call_args[0]) | ||
246 | 2064 | |||
247 | 2065 | log_content = logger.content.as_text() | ||
248 | 2066 | self.assertIn( | ||
249 | 2067 | "INFO Injecting key_type OPAL for archive %s into signing " | ||
250 | 2068 | "service" % (self.archive.name), | ||
251 | 2069 | log_content) | ||
252 | 2070 | |||
253 | 2071 | self.assertIn( | ||
254 | 2072 | "INFO Skipping injection for key type UEFI: " | ||
255 | 2073 | "not in [u'SIPL', u'OPAL']", | ||
256 | 2074 | log_content) | ||
257 | 2075 | |||
258 | 2076 | def test_fallback_skips_key_injection_for_existing_keys(self): | ||
259 | 2077 | self.useFixture(FeatureFixture({PUBLISHER_USES_SIGNING_SERVICE: ''})) | ||
260 | 2078 | self.useFixture(FeatureFixture({ | ||
261 | 2079 | PUBLISHER_SIGNING_SERVICE_INJECTS_KEYS: 'UEFI'})) | ||
262 | 2080 | |||
263 | 2081 | now = datetime.now() | ||
264 | 2082 | mock_datetime = self.useFixture(MockPatch( | ||
265 | 2083 | 'lp.archivepublisher.signing.datetime')).mock | ||
266 | 2084 | mock_datetime.now = lambda: now | ||
267 | 2085 | |||
268 | 2086 | # Setup PPA to ensure it auto-generates keys. | ||
269 | 2087 | self.setUpPPA() | ||
270 | 2088 | |||
271 | 2089 | signing_key = self.factory.makeSigningKey(key_type=SigningKeyType.UEFI) | ||
272 | 2090 | self.factory.makeArchiveSigningKey( | ||
273 | 2091 | archive=self.archive, signing_key=signing_key) | ||
274 | 2092 | |||
275 | 2093 | logger = BufferLogger() | ||
276 | 2094 | upload = SigningUpload(logger=logger) | ||
277 | 2095 | |||
278 | 2096 | filenames = ["1.0/empty.efi"] | ||
279 | 2097 | |||
280 | 2098 | self.openArchive("test", "1.0", "amd64") | ||
281 | 2099 | for filename in filenames: | ||
282 | 2100 | self.tarfile.add_file( | ||
283 | 2101 | filename, b"data - %s" % filename.encode("UTF-8")) | ||
284 | 2102 | self.tarfile.close() | ||
285 | 2103 | self.buffer.close() | ||
286 | 2104 | |||
287 | 2105 | self.assertRaises(SigningKeyConflict, | ||
288 | 2106 | upload.process, self.archive, self.path, self.suite) | ||
289 | 2107 | self.assertTrue(upload.autokey) | ||
290 | 2108 | |||
291 | 2109 | # Make sure we deleted the locally generated keys. | ||
292 | 2110 | self.assertFalse(os.path.exists(upload.uefi_cert)) | ||
293 | 2111 | self.assertFalse(os.path.exists(upload.uefi_key)) | ||
294 | 2112 | |||
295 | 2113 | # Make sure we didn't call lp-signing's /inject endpoint | ||
296 | 2114 | self.assertEqual(0, self.signing_service_client.inject.call_count) | ||
297 | 2115 | log_content = logger.content.as_text() | ||
298 | 2116 | self.assertIn( | ||
299 | 2117 | "INFO Skipping injection for key type %s: archive " | ||
300 | 2118 | "already has a key on lp-signing." % SigningKeyType.UEFI, | ||
301 | 2119 | log_content) | ||
302 | diff --git a/lib/lp/services/signing/interfaces/signingkey.py b/lib/lp/services/signing/interfaces/signingkey.py | |||
303 | index 587bbb0..eccf2a0 100644 | |||
304 | --- a/lib/lp/services/signing/interfaces/signingkey.py | |||
305 | +++ b/lib/lp/services/signing/interfaces/signingkey.py | |||
306 | @@ -117,10 +117,13 @@ class IArchiveSigningKeySet(Interface): | |||
307 | 117 | False if it was updated). | 117 | False if it was updated). |
308 | 118 | """ | 118 | """ |
309 | 119 | 119 | ||
311 | 120 | def getSigningKey(key_type, archive, distro_series): | 120 | def getSigningKey(key_type, archive, distro_series, exact_match=False): |
312 | 121 | """Get the most suitable key for a given archive / distro series | 121 | """Get the most suitable key for a given archive / distro series |
313 | 122 | pair. | 122 | pair. |
314 | 123 | 123 | ||
315 | 124 | :param exact_match: If True, returns the ArchiveSigningKey matching | ||
316 | 125 | exactly the given key_type, archive and | ||
317 | 126 | distro_series. If False, gets the best match. | ||
318 | 124 | :return: The most suitable key | 127 | :return: The most suitable key |
319 | 125 | """ | 128 | """ |
320 | 126 | 129 | ||
321 | diff --git a/lib/lp/services/signing/model/signingkey.py b/lib/lp/services/signing/model/signingkey.py | |||
322 | index 0d5e137..0479b61 100644 | |||
323 | --- a/lib/lp/services/signing/model/signingkey.py | |||
324 | +++ b/lib/lp/services/signing/model/signingkey.py | |||
325 | @@ -167,7 +167,8 @@ class ArchiveSigningKeySet: | |||
326 | 167 | return obj | 167 | return obj |
327 | 168 | 168 | ||
328 | 169 | @classmethod | 169 | @classmethod |
330 | 170 | def getSigningKey(cls, key_type, archive, distro_series): | 170 | def getSigningKey(cls, key_type, archive, distro_series, |
331 | 171 | exact_match=False): | ||
332 | 171 | store = IStore(ArchiveSigningKey) | 172 | store = IStore(ArchiveSigningKey) |
333 | 172 | # Gets all the keys of the given key_type available for the archive | 173 | # Gets all the keys of the given key_type available for the archive |
334 | 173 | rs = store.find(ArchiveSigningKey, | 174 | rs = store.find(ArchiveSigningKey, |
335 | @@ -176,6 +177,10 @@ class ArchiveSigningKeySet: | |||
336 | 176 | ArchiveSigningKey.key_type == key_type, | 177 | ArchiveSigningKey.key_type == key_type, |
337 | 177 | ArchiveSigningKey.archive == archive) | 178 | ArchiveSigningKey.archive == archive) |
338 | 178 | 179 | ||
339 | 180 | if exact_match: | ||
340 | 181 | rs = rs.find( | ||
341 | 182 | ArchiveSigningKey.earliest_distro_series == distro_series) | ||
342 | 183 | |||
343 | 179 | # prefetch related signing keys to avoid extra queries. | 184 | # prefetch related signing keys to avoid extra queries. |
344 | 180 | signing_keys = store.find(SigningKey, [ | 185 | signing_keys = store.find(SigningKey, [ |
345 | 181 | SigningKey.id.is_in([i.signing_key_id for i in rs])]) | 186 | SigningKey.id.is_in([i.signing_key_id for i in rs])]) |
346 | diff --git a/lib/lp/services/signing/tests/helpers.py b/lib/lp/services/signing/tests/helpers.py | |||
347 | index e01730f..5675bc6 100644 | |||
348 | --- a/lib/lp/services/signing/tests/helpers.py | |||
349 | +++ b/lib/lp/services/signing/tests/helpers.py | |||
350 | @@ -39,8 +39,12 @@ class SigningServiceClientFixture(fixtures.Fixture): | |||
351 | 39 | self.sign = mock.Mock() | 39 | self.sign = mock.Mock() |
352 | 40 | self.sign.side_effect = self._sign | 40 | self.sign.side_effect = self._sign |
353 | 41 | 41 | ||
354 | 42 | self.inject = mock.Mock() | ||
355 | 43 | self.inject.side_effect = self._inject | ||
356 | 44 | |||
357 | 42 | self.generate_returns = [] | 45 | self.generate_returns = [] |
358 | 43 | self.sign_returns = [] | 46 | self.sign_returns = [] |
359 | 47 | self.inject_returns = [] | ||
360 | 44 | 48 | ||
361 | 45 | def _generate(self, key_type, description): | 49 | def _generate(self, key_type, description): |
362 | 46 | key = bytes(PrivateKey.generate().public_key) | 50 | key = bytes(PrivateKey.generate().public_key) |
363 | @@ -59,6 +63,12 @@ class SigningServiceClientFixture(fixtures.Fixture): | |||
364 | 59 | self.sign_returns.append((key_type, data)) | 63 | self.sign_returns.append((key_type, data)) |
365 | 60 | return data | 64 | return data |
366 | 61 | 65 | ||
367 | 66 | def _inject(self, key_type, private_key, public_key, description, | ||
368 | 67 | created_at): | ||
369 | 68 | data = {'fingerprint': text_type(self.factory.getUniqueHexString(40))} | ||
370 | 69 | self.inject_returns.append(data) | ||
371 | 70 | return data | ||
372 | 71 | |||
373 | 62 | def _setUp(self): | 72 | def _setUp(self): |
374 | 63 | self.useFixture(ZopeUtilityFixture(self, ISigningServiceClient)) | 73 | self.useFixture(ZopeUtilityFixture(self, ISigningServiceClient)) |
375 | 64 | 74 | ||
376 | diff --git a/lib/lp/services/signing/tests/test_signingkey.py b/lib/lp/services/signing/tests/test_signingkey.py | |||
377 | index 3610303..8613dd7 100644 | |||
378 | --- a/lib/lp/services/signing/tests/test_signingkey.py | |||
379 | +++ b/lib/lp/services/signing/tests/test_signingkey.py | |||
380 | @@ -244,6 +244,53 @@ class TestArchiveSigningKey(TestCaseWithFactory): | |||
381 | 244 | arch_kmod_key.signing_key, | 244 | arch_kmod_key.signing_key, |
382 | 245 | arch_signing_key_set.getSigningKey(KMOD, archive, distro_series)) | 245 | arch_signing_key_set.getSigningKey(KMOD, archive, distro_series)) |
383 | 246 | 246 | ||
384 | 247 | def test_get_signing_key_exact_match(self): | ||
385 | 248 | UEFI = SigningKeyType.UEFI | ||
386 | 249 | KMOD = SigningKeyType.KMOD | ||
387 | 250 | |||
388 | 251 | archive = self.factory.makeArchive() | ||
389 | 252 | distro_series1 = archive.distribution.series[0] | ||
390 | 253 | distro_series2 = archive.distribution.series[1] | ||
391 | 254 | uefi_key = self.factory.makeSigningKey( | ||
392 | 255 | key_type=SigningKeyType.UEFI) | ||
393 | 256 | kmod_key = self.factory.makeSigningKey( | ||
394 | 257 | key_type=SigningKeyType.KMOD) | ||
395 | 258 | |||
396 | 259 | arch_signing_key_set = getUtility(IArchiveSigningKeySet) | ||
397 | 260 | |||
398 | 261 | # Create a key for the first distro series | ||
399 | 262 | series1_uefi_key = arch_signing_key_set.create( | ||
400 | 263 | archive, distro_series1, uefi_key) | ||
401 | 264 | |||
402 | 265 | # Create a key for the archive | ||
403 | 266 | arch_kmod_key = arch_signing_key_set.create( | ||
404 | 267 | archive, None, kmod_key) | ||
405 | 268 | |||
406 | 269 | # Should get the UEFI key for distro_series1 | ||
407 | 270 | self.assertEqual( | ||
408 | 271 | series1_uefi_key.signing_key, | ||
409 | 272 | arch_signing_key_set.getSigningKey( | ||
410 | 273 | UEFI, archive, distro_series1, exact_match=True) | ||
411 | 274 | ) | ||
412 | 275 | # Should get the archive's KMOD key. | ||
413 | 276 | self.assertEqual( | ||
414 | 277 | arch_kmod_key.signing_key, | ||
415 | 278 | arch_signing_key_set.getSigningKey( | ||
416 | 279 | KMOD, archive, None, exact_match=True) | ||
417 | 280 | ) | ||
418 | 281 | # distro_series1 has no KMOD key. | ||
419 | 282 | self.assertEqual( | ||
420 | 283 | None, | ||
421 | 284 | arch_signing_key_set.getSigningKey( | ||
422 | 285 | KMOD, archive, distro_series1, exact_match=True) | ||
423 | 286 | ) | ||
424 | 287 | # distro_series2 has no key at all. | ||
425 | 288 | self.assertEqual( | ||
426 | 289 | None, | ||
427 | 290 | arch_signing_key_set.getSigningKey( | ||
428 | 291 | KMOD, archive, distro_series2, exact_match=True) | ||
429 | 292 | ) | ||
430 | 293 | |||
431 | 247 | def test_get_signing_keys_with_distro_series_configured(self): | 294 | def test_get_signing_keys_with_distro_series_configured(self): |
432 | 248 | UEFI = SigningKeyType.UEFI | 295 | UEFI = SigningKeyType.UEFI |
433 | 249 | KMOD = SigningKeyType.KMOD | 296 | KMOD = SigningKeyType.KMOD |
Pushed the requested changes. Let me know if you need another round of review, cjwatson.