Merge ~ines-almeida/launchpad-buildd:update-close-session-for-fetch-service into launchpad-buildd:master

Proposed by Ines Almeida
Status: Merged
Approved by: Ines Almeida
Approved revision: a541938ade2b790b098c674fc5219ac1e721e70e
Merge reported by: Otto Co-Pilot
Merged at revision: not available
Proposed branch: ~ines-almeida/launchpad-buildd:update-close-session-for-fetch-service
Merge into: launchpad-buildd:master
Diff against target: 268 lines (+142/-10)
6 files modified
lpbuildd/proxy.py (+19/-3)
lpbuildd/snap.py (+3/-0)
lpbuildd/target/build_snap.py (+9/-1)
lpbuildd/tests/test_snap.py (+35/-1)
lpbuildd/tests/test_util.py (+53/-1)
lpbuildd/util.py (+23/-4)
Reviewer Review Type Date Requested Status
Jürgen Gmach Approve
Review via email: mp+462875@code.launchpad.net

Commit message

Enable revoking a token for the fetch service

Handle calling the revocation_endpoint sent by Launchpad, both in the cases where we want to revoke the token after the snap pull phase, and at the end of the build

Description of the change

This handles calling the revocation endpoint against the fetch service, but not yet closing the session (that will be handled in another MP: https://code.launchpad.net/~ines-almeida/launchpad-buildd/+git/launchpad-buildd/+merge/463119)

To post a comment you must log in.
Revision history for this message
Ines Almeida (ines-almeida) wrote :

In a meeting, decided that we will most likely need to have 2 endpoints for ending the session:
 - One for revoking the token
 - Another one to close the session

This MP is blocked until we settle that out.

Revision history for this message
Ines Almeida (ines-almeida) wrote :

Given we will have 2 endpoints (one for token revocation, another for session closing), I decided to separate this into 2 MPs:
 - This one will handle adding the logic to call the revocation endpoint to the fetch service
 - The next one will handle closing the fetch service session and saving the data (to be opened)

Revision history for this message
Ines Almeida (ines-almeida) wrote :

This MP has been updated after some conversations. The topic of how to authenticate the call to revoke the token is still on not decided on, so that part might change.

Revision history for this message
Jürgen Gmach (jugmac00) wrote :

Thanks! I added some comments. This is almost done, but let's try to use consistent, descriptive and RFC compliant naming for the hosts.

Do you think we need a feature flag for this? I do not think so, but you might have more insight.

Revision history for this message
Ines Almeida (ines-almeida) wrote :

Thanks you for the comments!

I don't think we need feature flag here simply because the feature flag in the launchpad code base should already do the same. The fetch service code in buildd will only be reached if the snap object has a `use_fetch_service` bool True - which will only be possible if the launchpad feature flag is ON.

As mentioned, I am only planning on merging this once the auth for the token revocation is defined in the meeting about the auth.

Revision history for this message
Jürgen Gmach (jugmac00) wrote :

@Ines I would love to have this merged asap, as both my and Simone's work base on this, and having this pre-requisite MPs juggling around is pretty cumbersome.

The code path for the fetch service cannot be triggered yet, so I do not see any issues with merging this - even without the auth being specced out fully.

Revision history for this message
Jürgen Gmach (jugmac00) :
review: Approve
44d08b2... by Ines Almeida

Fix small linting issue in test_snap file

a541938... by Ines Almeida

Enable revoking a token for the fetch service

Handle calling the revocation_endpoint sent by Launchpad, both in the cases where we want to revoke the token after the snap pull phase, and at the end of the build

Revision history for this message
Ines Almeida (ines-almeida) wrote :

Updated the commit history to separate the linting fix from everything else.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/lpbuildd/proxy.py b/lpbuildd/proxy.py
2index ea33dda..1a05ff3 100644
3--- a/lpbuildd/proxy.py
4+++ b/lpbuildd/proxy.py
5@@ -214,8 +214,20 @@ class BuilderProxyFactory(http.HTTPFactory):
6
7
8 class BuildManagerProxyMixin:
9+ @property
10+ def _use_fetch_service(self):
11+ return hasattr(self, "use_fetch_service") and getattr(
12+ self, "use_fetch_service"
13+ )
14+
15 def startProxy(self):
16- """Start the local builder proxy, if necessary."""
17+ """Start the local builder proxy, if necessary.
18+
19+ This starts an internal proxy that stands before the Builder
20+ Proxy/Fetch Service, so build systems, which do not comply
21+ with standard `http(s)_proxy` environment variables, would
22+ still work with the builder proxy.
23+ """
24 if not self.proxy_url:
25 return []
26 proxy_port = self._builder._config.get("builder", "proxyport")
27@@ -231,7 +243,7 @@ class BuildManagerProxyMixin:
28 return ["--proxy-url", f"http://{proxy_host}:{proxy_port}/"]
29
30 def stopProxy(self):
31- """Stop the local builder proxy, if necessary."""
32+ """Stop the internal local proxy (see `startProxy`), if necessary."""
33 if self.proxy_service is None:
34 return
35 self.proxy_service.disownServiceParent()
36@@ -243,6 +255,10 @@ class BuildManagerProxyMixin:
37 return
38 self._builder.log("Revoking proxy token...\n")
39 try:
40- revoke_proxy_token(self.proxy_url, self.revocation_endpoint)
41+ revoke_proxy_token(
42+ self.proxy_url,
43+ self.revocation_endpoint,
44+ self._use_fetch_service,
45+ )
46 except RevokeProxyTokenError as e:
47 self._builder.log(f"{e}\n")
48diff --git a/lpbuildd/snap.py b/lpbuildd/snap.py
49index 02390d4..c5b3205 100644
50--- a/lpbuildd/snap.py
51+++ b/lpbuildd/snap.py
52@@ -38,6 +38,7 @@ class SnapBuildManager(BuildManagerProxyMixin, DebianBuildManager):
53 self.branch = extra_args.get("branch")
54 self.git_repository = extra_args.get("git_repository")
55 self.git_path = extra_args.get("git_path")
56+ self.use_fetch_service = extra_args.get("use_fetch_service")
57 self.proxy_url = extra_args.get("proxy_url")
58 self.revocation_endpoint = extra_args.get("revocation_endpoint")
59 self.build_source_tarball = extra_args.get(
60@@ -100,6 +101,8 @@ class SnapBuildManager(BuildManagerProxyMixin, DebianBuildManager):
61 if self.target_architectures:
62 for arch in self.target_architectures:
63 args.extend(["--target-arch", arch])
64+ if self.use_fetch_service:
65+ args.append("--use_fetch_service")
66 args.append(self.name)
67 self.runTargetSubProcess("buildsnap", *args)
68
69diff --git a/lpbuildd/target/build_snap.py b/lpbuildd/target/build_snap.py
70index d9052f2..82470d5 100644
71--- a/lpbuildd/target/build_snap.py
72+++ b/lpbuildd/target/build_snap.py
73@@ -102,6 +102,12 @@ class BuildSnap(
74 action="store_true",
75 help="disable proxy access after the pull phase has finished",
76 )
77+ parser.add_argument(
78+ "--use_fetch_service",
79+ default=False,
80+ action="store_true",
81+ help="use the fetch service instead of the builder proxy",
82+ )
83 parser.add_argument("name", help="name of snap to build")
84
85 def install_svn_servers(self):
86@@ -231,7 +237,9 @@ class BuildSnap(
87 logger.info("Revoking proxy token...")
88 try:
89 revoke_proxy_token(
90- self.args.upstream_proxy_url, self.args.revocation_endpoint
91+ self.args.upstream_proxy_url,
92+ self.args.revocation_endpoint,
93+ self.args.use_fetch_service,
94 )
95 except RevokeProxyTokenError as e:
96 logger.info(str(e))
97diff --git a/lpbuildd/tests/test_snap.py b/lpbuildd/tests/test_snap.py
98index 9bef60a..bb0af3a 100644
99--- a/lpbuildd/tests/test_snap.py
100+++ b/lpbuildd/tests/test_snap.py
101@@ -283,7 +283,8 @@ class TestSnapBuildManagerIteration(TestCase):
102 "/build/test-snap/test-snap_0_all.snap", b"I am a snap package."
103 )
104 self.buildmanager.backend.add_file(
105- "/build/test-snap/test-snap+somecomponent_0.comp", b"I am a component."
106+ "/build/test-snap/test-snap+somecomponent_0.comp",
107+ b"I am a component.",
108 )
109
110 # After building the package, reap processes.
111@@ -754,6 +755,13 @@ class TestSnapBuildManagerIteration(TestCase):
112 yield self.startBuild(args, expected_options)
113
114 @defer.inlineCallbacks
115+ def test_iterate_use_fetch_service(self):
116+ # The build manager can be told to use the fetch service as its proxy.
117+ args = {"use_fetch_service": True}
118+ expected_options = ["--use_fetch_service"]
119+ yield self.startBuild(args, expected_options)
120+
121+ @defer.inlineCallbacks
122 def test_iterate_disable_proxy_after_pull(self):
123 self.builder._config.set("builder", "proxyport", "8222")
124 args = {
125@@ -874,3 +882,29 @@ class TestSnapBuildManagerIteration(TestCase):
126 # XXX cjwatson 2023-02-07: Ideally we'd check the timeout as well,
127 # but the version of responses in Ubuntu 20.04 doesn't store it
128 # anywhere we can get at it.
129+
130+ @responses.activate
131+ def test_revokeProxyToken_fetch_service(self):
132+ session_id = "123"
133+
134+ responses.add(
135+ "DELETE",
136+ f"http://control.fetch-service.example/{session_id}/token",
137+ )
138+
139+ self.buildmanager.use_fetch_service = True
140+ self.buildmanager.revocation_endpoint = (
141+ f"http://control.fetch-service.example/{session_id}/token"
142+ )
143+ self.buildmanager.proxy_url = (
144+ "http://session_id:token@proxy.fetch-service.example"
145+ )
146+
147+ self.buildmanager.revokeProxyToken()
148+
149+ self.assertEqual(1, len(responses.calls))
150+ request = responses.calls[0].request
151+ self.assertEqual(
152+ f"http://control.fetch-service.example/{session_id}/token",
153+ request.url,
154+ )
155diff --git a/lpbuildd/tests/test_util.py b/lpbuildd/tests/test_util.py
156index 005d42b..ab65f5f 100644
157--- a/lpbuildd/tests/test_util.py
158+++ b/lpbuildd/tests/test_util.py
159@@ -1,9 +1,17 @@
160 # Copyright 2017 Canonical Ltd. This software is licensed under the
161 # GNU Affero General Public License version 3 (see the file LICENSE).
162
163+import base64
164+
165+import responses
166 from testtools import TestCase
167
168-from lpbuildd.util import get_arch_bits, set_personality, shell_escape
169+from lpbuildd.util import (
170+ get_arch_bits,
171+ revoke_proxy_token,
172+ set_personality,
173+ shell_escape,
174+)
175
176
177 class TestShellEscape(TestCase):
178@@ -59,3 +67,47 @@ class TestSetPersonality(TestCase):
179 ["linux64", "sbuild"],
180 set_personality(["sbuild"], "amd64", series="trusty"),
181 )
182+
183+
184+class TestRevokeToken(TestCase):
185+ @responses.activate
186+ def test_revoke_proxy_token(self):
187+ """Proxy token revocation uses the right authentication"""
188+
189+ proxy_url = "http://username:password@proxy.example"
190+ revocation_endpoint = "http://proxy-auth.example/tokens/build_id"
191+ token = base64.b64encode(b"username:password").decode()
192+
193+ responses.add(responses.DELETE, revocation_endpoint)
194+
195+ revoke_proxy_token(proxy_url, revocation_endpoint)
196+ self.assertEqual(1, len(responses.calls))
197+ request = responses.calls[0].request
198+ self.assertEqual(
199+ "http://proxy-auth.example/tokens/build_id", request.url
200+ )
201+ self.assertEqual(f"Basic {token}", request.headers["Authorization"])
202+
203+ @responses.activate
204+ def test_revoke_fetch_service_token(self):
205+ """Proxy token revocation for the fetch service"""
206+
207+ proxy_url = "http://session_id:token@proxy.fetch-service.example"
208+ revocation_endpoint = (
209+ "http://control.fetch-service.example/session_id/token"
210+ )
211+
212+ responses.add(responses.DELETE, revocation_endpoint)
213+
214+ revoke_proxy_token(
215+ proxy_url,
216+ revocation_endpoint,
217+ use_fetch_service=True,
218+ )
219+
220+ self.assertEqual(1, len(responses.calls))
221+ request = responses.calls[0].request
222+ self.assertEqual(
223+ "http://control.fetch-service.example/session_id/token",
224+ request.url,
225+ )
226diff --git a/lpbuildd/util.py b/lpbuildd/util.py
227index eb35f01..664f92b 100644
228--- a/lpbuildd/util.py
229+++ b/lpbuildd/util.py
230@@ -68,15 +68,34 @@ class RevokeProxyTokenError(Exception):
231 return f"Unable to revoke token for {self.username}: {self.exception}"
232
233
234-def revoke_proxy_token(proxy_url, revocation_endpoint):
235+def revoke_proxy_token(
236+ proxy_url, revocation_endpoint, use_fetch_service=False
237+):
238 """Revoke builder proxy token.
239
240+ If not using the fetch service:
241+ The proxy_url for the current Builder Proxy has the following format:
242+ http://{username}:{password}@{host}:{port}
243+
244+ We use the username-password combo from the proxy_url for
245+ authentication to revoke its token.
246+
247+ If using the fetch service:
248+ The call to revoke a token does not require authentication.
249+
250+ XXX ines-almeida 2024-04-15: this might change depending on
251+ conversations about fetch service authentication. We might decide to
252+ instead use the token itself as the authentication.
253+
254 :raises RevokeProxyTokenError: if attempting to revoke the token failed.
255 """
256 url = urlparse(proxy_url)
257+
258+ auth = None
259+ if not use_fetch_service:
260+ auth = (url.username, url.password)
261+
262 try:
263- requests.delete(
264- revocation_endpoint, auth=(url.username, url.password), timeout=15
265- )
266+ requests.delete(revocation_endpoint, auth=auth, timeout=15)
267 except requests.RequestException as e:
268 raise RevokeProxyTokenError(url.username, e)

Subscribers

People subscribed via source and target branches