Merge ~cjwatson/launchpad:bpb-snapbuild-archive-macaroons into launchpad:master

Proposed by Colin Watson
Status: Merged
Approved by: Colin Watson
Approved revision: 1d34374fcc2ca8d545f3f9e18c55897e351abc09
Merge reported by: Otto Co-Pilot
Merged at revision: not available
Proposed branch: ~cjwatson/launchpad:bpb-snapbuild-archive-macaroons
Merge into: launchpad:master
Diff against target: 295 lines (+142/-28)
4 files modified
lib/lp/snappy/model/snapbuild.py (+29/-10)
lib/lp/snappy/tests/test_snapbuild.py (+40/-1)
lib/lp/soyuz/model/binarypackagebuild.py (+36/-16)
lib/lp/soyuz/tests/test_binarypackagebuild.py (+37/-1)
Reviewer Review Type Date Requested Status
Thiago F. Pappacena (community) Approve
Review via email: mp+400548@code.launchpad.net

Commit message

Allow verifying SnapBuild/BPB macaroons for archives

Description of the change

As part of replacing `Archive.buildd_secret`, it'll be useful to have tokens that can be used to access an archive but only for the lifetime of a relevant build. Since these tokens won't be owned by users and so don't need to support manual revocation, it should be safe to reuse our existing macaroon infrastructure for this application.

To start with, extend the binary-package-build and snap-build macaroon issuers to support verification using an archive as the context. This currently requires the context archive to be either the build's archive or one of its archive dependencies.

Nothing actually verifies these tokens yet, so there should be no functional change as a result of this commit.

To post a comment you must log in.
Revision history for this message
Thiago F. Pappacena (pappacena) wrote :

LGTM

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/lib/lp/snappy/model/snapbuild.py b/lib/lp/snappy/model/snapbuild.py
2index 3a9475b..11d5091 100644
3--- a/lib/lp/snappy/model/snapbuild.py
4+++ b/lib/lp/snappy/model/snapbuild.py
5@@ -26,6 +26,7 @@ from storm.locals import (
6 Desc,
7 Int,
8 JSON,
9+ Or,
10 Reference,
11 Select,
12 SQL,
13@@ -96,8 +97,10 @@ from lp.snappy.model.snapbuildjob import (
14 SnapBuildJob,
15 SnapBuildJobType,
16 )
17+from lp.soyuz.interfaces.archive import IArchive
18 from lp.soyuz.interfaces.component import IComponentSet
19 from lp.soyuz.model.archive import Archive
20+from lp.soyuz.model.archivedependency import ArchiveDependency
21 from lp.soyuz.model.distroarchseries import DistroArchSeries
22
23
24@@ -656,7 +659,8 @@ class SnapBuildMacaroonIssuer(MacaroonIssuerBase):
25
26 def checkVerificationContext(self, context, **kwargs):
27 """See `MacaroonIssuerBase`."""
28- if not IGitRepository.providedBy(context):
29+ if (not IGitRepository.providedBy(context) and
30+ not IArchive.providedBy(context)):
31 raise BadMacaroonContext(context)
32 return context
33
34@@ -664,10 +668,10 @@ class SnapBuildMacaroonIssuer(MacaroonIssuerBase):
35 **kwargs):
36 """See `MacaroonIssuerBase`.
37
38- For verification, the context is an `IGitRepository`. We check that
39- the repository is needed to build the `ISnapBuild` that is the
40- context of the macaroon, and that the context build is currently
41- building.
42+ For verification, the context is an `IGitRepository` or an
43+ `IArchive`. We check that the repository or archive is needed to
44+ build the `ISnapBuild` that is the context of the macaroon, and that
45+ the context build is currently building.
46 """
47 # Circular import.
48 from lp.snappy.model.snap import Snap
49@@ -687,9 +691,24 @@ class SnapBuildMacaroonIssuer(MacaroonIssuerBase):
50 build_id = int(caveat_value)
51 except ValueError:
52 return False
53- return not IStore(SnapBuild).find(
54- SnapBuild,
55+ clauses = [
56 SnapBuild.id == build_id,
57- SnapBuild.snap_id == Snap.id,
58- Snap.git_repository == context,
59- SnapBuild.status == BuildStatus.BUILDING).is_empty()
60+ SnapBuild.status == BuildStatus.BUILDING,
61+ ]
62+ if IGitRepository.providedBy(context):
63+ clauses.extend([
64+ SnapBuild.snap_id == Snap.id,
65+ Snap.git_repository == context,
66+ ])
67+ elif IArchive.providedBy(context):
68+ clauses.append(
69+ Or(
70+ SnapBuild.archive == context,
71+ SnapBuild.archive_id.is_in(Select(
72+ Archive.id,
73+ where=And(
74+ ArchiveDependency.archive == Archive.id,
75+ ArchiveDependency.dependency == context)))))
76+ else:
77+ return False
78+ return not IStore(SnapBuild).find(SnapBuild, *clauses).is_empty()
79diff --git a/lib/lp/snappy/tests/test_snapbuild.py b/lib/lp/snappy/tests/test_snapbuild.py
80index ee67203..0ada77b 100644
81--- a/lib/lp/snappy/tests/test_snapbuild.py
82+++ b/lib/lp/snappy/tests/test_snapbuild.py
83@@ -40,6 +40,7 @@ from lp.registry.enums import (
84 PersonVisibility,
85 TeamMembershipPolicy,
86 )
87+from lp.registry.interfaces.pocket import PackagePublishingPocket
88 from lp.registry.interfaces.series import SeriesStatus
89 from lp.services.authserver.xmlrpc import AuthServerAPIView
90 from lp.services.config import config
91@@ -935,7 +936,7 @@ class TestSnapBuildMacaroonIssuer(MacaroonTestMixin, TestCaseWithFactory):
92 caveat_id="lp.snap-build %s" % build.id),
93 ])))
94
95- def test_verifyMacaroon_good(self):
96+ def test_verifyMacaroon_good_repository(self):
97 [ref] = self.factory.makeGitRefs(
98 information_type=InformationType.USERDATA)
99 build = self.factory.makeSnapBuild(
100@@ -946,6 +947,30 @@ class TestSnapBuildMacaroonIssuer(MacaroonTestMixin, TestCaseWithFactory):
101 macaroon = issuer.issueMacaroon(build)
102 self.assertMacaroonVerifies(issuer, macaroon, ref.repository)
103
104+ def test_verifyMacaroon_good_direct_archive(self):
105+ build = self.factory.makeSnapBuild(
106+ snap=self.factory.makeSnap(private=True),
107+ archive=self.factory.makeArchive(private=True))
108+ build.updateStatus(BuildStatus.BUILDING)
109+ issuer = removeSecurityProxy(
110+ getUtility(IMacaroonIssuer, "snap-build"))
111+ macaroon = issuer.issueMacaroon(build)
112+ self.assertMacaroonVerifies(issuer, macaroon, build.archive)
113+
114+ def test_verifyMacaroon_good_indirect_archive(self):
115+ build = self.factory.makeSnapBuild(
116+ snap=self.factory.makeSnap(private=True),
117+ archive=self.factory.makeArchive(private=True))
118+ dependency = self.factory.makeArchive(
119+ distribution=build.archive.distribution, private=True)
120+ build.archive.addArchiveDependency(
121+ dependency, PackagePublishingPocket.RELEASE)
122+ build.updateStatus(BuildStatus.BUILDING)
123+ issuer = removeSecurityProxy(
124+ getUtility(IMacaroonIssuer, "snap-build"))
125+ macaroon = issuer.issueMacaroon(build)
126+ self.assertMacaroonVerifies(issuer, macaroon, dependency)
127+
128 def test_verifyMacaroon_good_no_context(self):
129 [ref] = self.factory.makeGitRefs(
130 information_type=InformationType.USERDATA)
131@@ -1059,3 +1084,17 @@ class TestSnapBuildMacaroonIssuer(MacaroonTestMixin, TestCaseWithFactory):
132 self.assertMacaroonDoesNotVerify(
133 ["Caveat check for 'lp.snap-build %s' failed." % build.id],
134 issuer, macaroon, other_repository)
135+
136+ def test_verifyMacaroon_wrong_archive(self):
137+ build = self.factory.makeSnapBuild(
138+ snap=self.factory.makeSnap(private=True),
139+ archive=self.factory.makeArchive(private=True))
140+ other_archive = self.factory.makeArchive(
141+ distribution=build.archive.distribution, private=True)
142+ build.updateStatus(BuildStatus.BUILDING)
143+ issuer = removeSecurityProxy(
144+ getUtility(IMacaroonIssuer, "snap-build"))
145+ macaroon = issuer.issueMacaroon(build)
146+ self.assertMacaroonDoesNotVerify(
147+ ["Caveat check for 'lp.snap-build %s' failed." % build.id],
148+ issuer, macaroon, other_archive)
149diff --git a/lib/lp/soyuz/model/binarypackagebuild.py b/lib/lp/soyuz/model/binarypackagebuild.py
150index 238221c..b863798 100644
151--- a/lib/lp/soyuz/model/binarypackagebuild.py
152+++ b/lib/lp/soyuz/model/binarypackagebuild.py
153@@ -1,4 +1,4 @@
154-# Copyright 2009-2020 Canonical Ltd. This software is licensed under the
155+# Copyright 2009-2021 Canonical Ltd. This software is licensed under the
156 # GNU Affero General Public License version 3 (see the file LICENSE).
157
158 __metaclass__ = type
159@@ -96,6 +96,7 @@ from lp.soyuz.enums import (
160 PackagePublishingStatus,
161 )
162 from lp.soyuz.interfaces.archive import (
163+ IArchive,
164 InvalidExternalDependencies,
165 validate_external_dependencies,
166 )
167@@ -1423,7 +1424,8 @@ class BinaryPackageBuildMacaroonIssuer(MacaroonIssuerBase):
168
169 def checkVerificationContext(self, context, **kwargs):
170 """See `MacaroonIssuerBase`."""
171- if not ILibraryFileAlias.providedBy(context):
172+ if (not ILibraryFileAlias.providedBy(context) and
173+ not IArchive.providedBy(context)):
174 raise BadMacaroonContext(context)
175 return context
176
177@@ -1431,16 +1433,18 @@ class BinaryPackageBuildMacaroonIssuer(MacaroonIssuerBase):
178 **kwargs):
179 """See `MacaroonIssuerBase`.
180
181- For verification, the context is an `ILibraryFileAlias`. We check
182- that the file is one of those required to build the
183- `IBinaryPackageBuild` that is the context of the macaroon, and that
184- the context build is currently building.
185+ For verification, the context is an `ILibraryFileAlias` or an
186+ `IArchive`. We check that the file or archive is one of those
187+ required to build the `IBinaryPackageBuild` that is the context of
188+ the macaroon, and that the context build is currently building.
189 """
190- # Circular import.
191+ # Circular imports.
192+ from lp.soyuz.model.archive import Archive
193+ from lp.soyuz.model.archivedependency import ArchiveDependency
194 from lp.soyuz.model.sourcepackagerelease import SourcePackageRelease
195
196 # Binary package builds only support free-floating macaroons for
197- # librarian authentication, not ones bound to a user.
198+ # librarian or archive authentication, not ones bound to a user.
199 if user:
200 return False
201 verified.user = NO_USER
202@@ -1449,12 +1453,28 @@ class BinaryPackageBuildMacaroonIssuer(MacaroonIssuerBase):
203 build_id = int(caveat_value)
204 except ValueError:
205 return False
206- return not IStore(BinaryPackageBuild).find(
207- BinaryPackageBuild,
208+ clauses = [
209 BinaryPackageBuild.id == build_id,
210- BinaryPackageBuild.source_package_release_id ==
211- SourcePackageRelease.id,
212- SourcePackageReleaseFile.sourcepackagereleaseID ==
213- SourcePackageRelease.id,
214- SourcePackageReleaseFile.libraryfile == context,
215- BinaryPackageBuild.status == BuildStatus.BUILDING).is_empty()
216+ BinaryPackageBuild.status == BuildStatus.BUILDING,
217+ ]
218+ if ILibraryFileAlias.providedBy(context):
219+ clauses.extend([
220+ BinaryPackageBuild.source_package_release_id ==
221+ SourcePackageRelease.id,
222+ SourcePackageReleaseFile.sourcepackagereleaseID ==
223+ SourcePackageRelease.id,
224+ SourcePackageReleaseFile.libraryfile == context,
225+ ])
226+ elif IArchive.providedBy(context):
227+ clauses.append(
228+ Or(
229+ BinaryPackageBuild.archive == context,
230+ BinaryPackageBuild.archive_id.is_in(Select(
231+ Archive.id,
232+ where=And(
233+ ArchiveDependency.archive == Archive.id,
234+ ArchiveDependency.dependency == context)))))
235+ else:
236+ return False
237+ return not IStore(BinaryPackageBuild).find(
238+ BinaryPackageBuild, *clauses).is_empty()
239diff --git a/lib/lp/soyuz/tests/test_binarypackagebuild.py b/lib/lp/soyuz/tests/test_binarypackagebuild.py
240index 0c7b95a..b51f2b7 100644
241--- a/lib/lp/soyuz/tests/test_binarypackagebuild.py
242+++ b/lib/lp/soyuz/tests/test_binarypackagebuild.py
243@@ -1,4 +1,4 @@
244-# Copyright 2009-2019 Canonical Ltd. This software is licensed under the
245+# Copyright 2009-2021 Canonical Ltd. This software is licensed under the
246 # GNU Affero General Public License version 3 (see the file LICENSE).
247
248 """Test Build features."""
249@@ -975,6 +975,28 @@ class TestBinaryPackageBuildMacaroonIssuer(
250 macaroon = issuer.issueMacaroon(build)
251 self.assertMacaroonVerifies(issuer, macaroon, sprf.libraryfile)
252
253+ def test_verifyMacaroon_good_direct_archive(self):
254+ build = self.factory.makeBinaryPackageBuild(
255+ archive=self.factory.makeArchive(private=True))
256+ build.updateStatus(BuildStatus.BUILDING)
257+ issuer = removeSecurityProxy(
258+ getUtility(IMacaroonIssuer, "binary-package-build"))
259+ macaroon = issuer.issueMacaroon(build)
260+ self.assertMacaroonVerifies(issuer, macaroon, build.archive)
261+
262+ def test_verifyMacaroon_good_indirect_archive(self):
263+ build = self.factory.makeBinaryPackageBuild(
264+ archive=self.factory.makeArchive(private=True))
265+ dependency = self.factory.makeArchive(
266+ distribution=build.archive.distribution, private=True)
267+ build.archive.addArchiveDependency(
268+ dependency, PackagePublishingPocket.RELEASE)
269+ build.updateStatus(BuildStatus.BUILDING)
270+ issuer = removeSecurityProxy(
271+ getUtility(IMacaroonIssuer, "binary-package-build"))
272+ macaroon = issuer.issueMacaroon(build)
273+ self.assertMacaroonVerifies(issuer, macaroon, dependency)
274+
275 def test_verifyMacaroon_wrong_location(self):
276 build = self.factory.makeBinaryPackageBuild(
277 archive=self.factory.makeArchive(private=True))
278@@ -1046,3 +1068,17 @@ class TestBinaryPackageBuildMacaroonIssuer(
279 ["Caveat check for 'lp.principal.binary-package-build %s' "
280 "failed." % build.id],
281 issuer, macaroon, lfa)
282+
283+ def test_verifyMacaroon_wrong_archive(self):
284+ build = self.factory.makeBinaryPackageBuild(
285+ archive=self.factory.makeArchive(private=True))
286+ archive = self.factory.makeArchive(
287+ distribution=build.archive.distribution, private=True)
288+ build.updateStatus(BuildStatus.BUILDING)
289+ issuer = removeSecurityProxy(
290+ getUtility(IMacaroonIssuer, "binary-package-build"))
291+ macaroon = issuer.issueMacaroon(build)
292+ self.assertMacaroonDoesNotVerify(
293+ ["Caveat check for 'lp.principal.binary-package-build %s' "
294+ "failed." % build.id],
295+ issuer, macaroon, archive)

Subscribers

People subscribed via source and target branches

to status/vote changes: