Merge ~cjwatson/launchpad:git-auth-launchpad-services into launchpad:master

Proposed by Colin Watson
Status: Merged
Approved by: Colin Watson
Approved revision: 063b8ea607e6f615bec7d3d42eaa503b56f2bce8
Merge reported by: Otto Co-Pilot
Merged at revision: not available
Proposed branch: ~cjwatson/launchpad:git-auth-launchpad-services
Merge into: launchpad:master
Diff against target: 173 lines (+46/-33)
6 files modified
lib/lp/code/xmlrpc/git.py (+4/-1)
lib/lp/code/xmlrpc/tests/test_git.py (+36/-28)
lib/lp/oci/model/ocirecipebuildbehaviour.py (+2/-1)
lib/lp/oci/tests/test_ocirecipebuildbehaviour.py (+1/-1)
lib/lp/snappy/model/snapbuildbehaviour.py (+2/-1)
lib/lp/snappy/tests/test_snapbuildbehaviour.py (+1/-1)
Reviewer Review Type Date Requested Status
Cristian Gonzalez (community) Approve
Review via email: mp+407532@code.launchpad.net

Commit message

Authenticate using +launchpad-services rather than empty username

Description of the change

For private snap and OCI recipe builds, we previously authenticated to git using the empty username and a non-user-bound macaroon. This is syntactically valid and works with current versions of git, but it's the sort of edge case in the URL specification that's easy to mishandle.

curl 7.58.0 (used by git's HTTP transport in Ubuntu 18.04) fails to send an Authorization header in this case, while curl 7.68.0 (used by git's HTTP transport in Ubuntu 20.04) gets it right. I bisected this to https://github.com/curl/curl/commit/46e164069d, which is a substantial refactoring of URL parsing that would be unreasonable to try to backport. Using a reserved username instead is safer.

To post a comment you must log in.
Revision history for this message
Cristian Gonzalez (cristiangsp) wrote :

Looks good!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/lib/lp/code/xmlrpc/git.py b/lib/lp/code/xmlrpc/git.py
index 62dad82..e890605 100644
--- a/lib/lp/code/xmlrpc/git.py
+++ b/lib/lp/code/xmlrpc/git.py
@@ -565,7 +565,10 @@ class GitAPI(LaunchpadXMLRPCView):
565 otherwise Zope's XML-RPC publication machinery gets confused by the565 otherwise Zope's XML-RPC publication machinery gets confused by the
566 decorator and publishes a method that takes zero arguments.566 decorator and publishes a method that takes zero arguments.
567 """567 """
568 user = getUtility(IPersonSet).getByName(username) if username else None568 user = (
569 getUtility(IPersonSet).getByName(username)
570 if username and username != LAUNCHPAD_SERVICES
571 else None)
569 verified = self._verifyMacaroon(password, user=user)572 verified = self._verifyMacaroon(password, user=user)
570 if verified:573 if verified:
571 auth_params = {"macaroon": password}574 auth_params = {"macaroon": password}
diff --git a/lib/lp/code/xmlrpc/tests/test_git.py b/lib/lp/code/xmlrpc/tests/test_git.py
index efc1087..8a032b5 100644
--- a/lib/lp/code/xmlrpc/tests/test_git.py
+++ b/lib/lp/code/xmlrpc/tests/test_git.py
@@ -2302,17 +2302,22 @@ class TestGitAPI(TestGitAPIMixin, TestCaseWithFactory):
2302 job = self.factory.makeCodeImportJob(code_import=code_import)2302 job = self.factory.makeCodeImportJob(code_import=code_import)
2303 issuer = getUtility(IMacaroonIssuer, "code-import-job")2303 issuer = getUtility(IMacaroonIssuer, "code-import-job")
2304 macaroon = removeSecurityProxy(issuer).issueMacaroon(job)2304 macaroon = removeSecurityProxy(issuer).issueMacaroon(job)
2305 self.assertEqual(2305 for username in ("", "+launchpad-services"):
2306 {"macaroon": macaroon.serialize(), "user": "+launchpad-services"},2306 self.assertEqual(
2307 self.assertDoesNotFault(2307 {"macaroon": macaroon.serialize(),
2308 None, "authenticateWithPassword", "", macaroon.serialize()))2308 "user": "+launchpad-services"},
2309 other_macaroon = Macaroon(identifier="another", key="another-secret")2309 self.assertDoesNotFault(
2310 self.assertFault(2310 None, "authenticateWithPassword",
2311 faults.Unauthorized, None,2311 username, macaroon.serialize()))
2312 "authenticateWithPassword", "", other_macaroon.serialize())2312 other_macaroon = Macaroon(
2313 self.assertFault(2313 identifier="another", key="another-secret")
2314 faults.Unauthorized, None,2314 self.assertFault(
2315 "authenticateWithPassword", "", "nonsense")2315 faults.Unauthorized, None,
2316 "authenticateWithPassword",
2317 username, other_macaroon.serialize())
2318 self.assertFault(
2319 faults.Unauthorized, None,
2320 "authenticateWithPassword", username, "nonsense")
23162321
2317 def test_authenticateWithPassword_private_snap_build(self):2322 def test_authenticateWithPassword_private_snap_build(self):
2318 self.useFixture(FeatureFixture(SNAP_TESTING_FLAGS))2323 self.useFixture(FeatureFixture(SNAP_TESTING_FLAGS))
@@ -2325,17 +2330,22 @@ class TestGitAPI(TestGitAPIMixin, TestCaseWithFactory):
2325 requester=owner, owner=owner, git_ref=ref, private=True)2330 requester=owner, owner=owner, git_ref=ref, private=True)
2326 issuer = getUtility(IMacaroonIssuer, "snap-build")2331 issuer = getUtility(IMacaroonIssuer, "snap-build")
2327 macaroon = removeSecurityProxy(issuer).issueMacaroon(build)2332 macaroon = removeSecurityProxy(issuer).issueMacaroon(build)
2328 self.assertEqual(2333 for username in ("", "+launchpad-services"):
2329 {"macaroon": macaroon.serialize(), "user": "+launchpad-services"},2334 self.assertEqual(
2330 self.assertDoesNotFault(2335 {"macaroon": macaroon.serialize(),
2331 None, "authenticateWithPassword", "", macaroon.serialize()))2336 "user": "+launchpad-services"},
2332 other_macaroon = Macaroon(identifier="another", key="another-secret")2337 self.assertDoesNotFault(
2333 self.assertFault(2338 None, "authenticateWithPassword",
2334 faults.Unauthorized, None,2339 username, macaroon.serialize()))
2335 "authenticateWithPassword", "", other_macaroon.serialize())2340 other_macaroon = Macaroon(
2336 self.assertFault(2341 identifier="another", key="another-secret")
2337 faults.Unauthorized, None,2342 self.assertFault(
2338 "authenticateWithPassword", "", "nonsense")2343 faults.Unauthorized, None,
2344 "authenticateWithPassword",
2345 username, other_macaroon.serialize())
2346 self.assertFault(
2347 faults.Unauthorized, None,
2348 "authenticateWithPassword", username, "nonsense")
23392349
2340 def test_authenticateWithPassword_user_macaroon(self):2350 def test_authenticateWithPassword_user_macaroon(self):
2341 # A user with a suitable macaroon can authenticate using it, in2351 # A user with a suitable macaroon can authenticate using it, in
@@ -2351,12 +2361,10 @@ class TestGitAPI(TestGitAPIMixin, TestCaseWithFactory):
2351 self.assertDoesNotFault(2361 self.assertDoesNotFault(
2352 None, "authenticateWithPassword",2362 None, "authenticateWithPassword",
2353 requester.name, macaroon.serialize()))2363 requester.name, macaroon.serialize()))
2354 self.assertFault(2364 for username in ("", "+launchpad-services", "nonexistent"):
2355 faults.Unauthorized, None,2365 self.assertFault(
2356 "authenticateWithPassword", "", macaroon.serialize())2366 faults.Unauthorized, None,
2357 self.assertFault(2367 "authenticateWithPassword", username, macaroon.serialize())
2358 faults.Unauthorized, None,
2359 "authenticateWithPassword", "nonexistent", macaroon.serialize())
2360 other_macaroon = Macaroon(identifier="another", key="another-secret")2368 other_macaroon = Macaroon(identifier="another", key="another-secret")
2361 self.assertFault(2369 self.assertFault(
2362 faults.Unauthorized, None,2370 faults.Unauthorized, None,
diff --git a/lib/lp/oci/model/ocirecipebuildbehaviour.py b/lib/lp/oci/model/ocirecipebuildbehaviour.py
index 8147c77..6b82f36 100644
--- a/lib/lp/oci/model/ocirecipebuildbehaviour.py
+++ b/lib/lp/oci/model/ocirecipebuildbehaviour.py
@@ -34,6 +34,7 @@ from lp.buildmaster.interfaces.buildfarmjobbehaviour import (
34from lp.buildmaster.model.buildfarmjobbehaviour import (34from lp.buildmaster.model.buildfarmjobbehaviour import (
35 BuildFarmJobBehaviourBase,35 BuildFarmJobBehaviourBase,
36 )36 )
37from lp.code.interfaces.codehosting import LAUNCHPAD_SERVICES
37from lp.oci.interfaces.ocirecipebuild import IOCIFileSet38from lp.oci.interfaces.ocirecipebuild import IOCIFileSet
38from lp.registry.interfaces.series import SeriesStatus39from lp.registry.interfaces.series import SeriesStatus
39from lp.services.config import config40from lp.services.config import config
@@ -156,7 +157,7 @@ class OCIRecipeBuildBehaviour(BuilderProxyMixin, BuildFarmJobBehaviourBase):
156 if build.recipe.git_repository.private:157 if build.recipe.git_repository.private:
157 macaroon_raw = yield self.issueMacaroon()158 macaroon_raw = yield self.issueMacaroon()
158 url = build.recipe.git_repository.getCodebrowseUrl(159 url = build.recipe.git_repository.getCodebrowseUrl(
159 username=None, password=macaroon_raw)160 username=LAUNCHPAD_SERVICES, password=macaroon_raw)
160 args["git_repository"] = url161 args["git_repository"] = url
161 else:162 else:
162 args["git_repository"] = (163 args["git_repository"] = (
diff --git a/lib/lp/oci/tests/test_ocirecipebuildbehaviour.py b/lib/lp/oci/tests/test_ocirecipebuildbehaviour.py
index a29408b..d379a12 100644
--- a/lib/lp/oci/tests/test_ocirecipebuildbehaviour.py
+++ b/lib/lp/oci/tests/test_ocirecipebuildbehaviour.py
@@ -457,7 +457,7 @@ class TestAsyncOCIRecipeBuildBehaviour(
457 "fast_cleanup": Is(True),457 "fast_cleanup": Is(True),
458 "git_repository": AfterPreprocessing(urlsplit, MatchesStructure(458 "git_repository": AfterPreprocessing(urlsplit, MatchesStructure(
459 scheme=Equals(split_browse_root.scheme),459 scheme=Equals(split_browse_root.scheme),
460 username=Equals(""),460 username=Equals("+launchpad-services"),
461 password=AfterPreprocessing(461 password=AfterPreprocessing(
462 Macaroon.deserialize, MatchesStructure(462 Macaroon.deserialize, MatchesStructure(
463 location=Equals(config.vhost.mainsite.hostname),463 location=Equals(config.vhost.mainsite.hostname),
diff --git a/lib/lp/snappy/model/snapbuildbehaviour.py b/lib/lp/snappy/model/snapbuildbehaviour.py
index b6fd2ee..f74895e 100644
--- a/lib/lp/snappy/model/snapbuildbehaviour.py
+++ b/lib/lp/snappy/model/snapbuildbehaviour.py
@@ -25,6 +25,7 @@ from lp.buildmaster.interfaces.buildfarmjobbehaviour import (
25from lp.buildmaster.model.buildfarmjobbehaviour import (25from lp.buildmaster.model.buildfarmjobbehaviour import (
26 BuildFarmJobBehaviourBase,26 BuildFarmJobBehaviourBase,
27 )27 )
28from lp.code.interfaces.codehosting import LAUNCHPAD_SERVICES
28from lp.registry.interfaces.series import SeriesStatus29from lp.registry.interfaces.series import SeriesStatus
29from lp.services.config import config30from lp.services.config import config
30from lp.services.features import getFeatureFlag31from lp.services.features import getFeatureFlag
@@ -143,7 +144,7 @@ class SnapBuildBehaviour(BuilderProxyMixin, BuildFarmJobBehaviourBase):
143 elif build.snap.git_repository.private:144 elif build.snap.git_repository.private:
144 macaroon_raw = yield self.issueMacaroon()145 macaroon_raw = yield self.issueMacaroon()
145 url = build.snap.git_repository.getCodebrowseUrl(146 url = build.snap.git_repository.getCodebrowseUrl(
146 username=None, password=macaroon_raw)147 username=LAUNCHPAD_SERVICES, password=macaroon_raw)
147 args["git_repository"] = url148 args["git_repository"] = url
148 else:149 else:
149 args["git_repository"] = (150 args["git_repository"] = (
diff --git a/lib/lp/snappy/tests/test_snapbuildbehaviour.py b/lib/lp/snappy/tests/test_snapbuildbehaviour.py
index b5b1326..1d89091 100644
--- a/lib/lp/snappy/tests/test_snapbuildbehaviour.py
+++ b/lib/lp/snappy/tests/test_snapbuildbehaviour.py
@@ -461,7 +461,7 @@ class TestAsyncSnapBuildBehaviour(StatsMixin, TestSnapBuildBehaviourBase):
461 "fast_cleanup": Is(True),461 "fast_cleanup": Is(True),
462 "git_repository": AfterPreprocessing(urlsplit, MatchesStructure(462 "git_repository": AfterPreprocessing(urlsplit, MatchesStructure(
463 scheme=Equals(split_browse_root.scheme),463 scheme=Equals(split_browse_root.scheme),
464 username=Equals(""),464 username=Equals("+launchpad-services"),
465 password=AfterPreprocessing(465 password=AfterPreprocessing(
466 Macaroon.deserialize, MatchesStructure(466 Macaroon.deserialize, MatchesStructure(
467 location=Equals(config.vhost.mainsite.hostname),467 location=Equals(config.vhost.mainsite.hostname),

Subscribers

People subscribed via source and target branches

to status/vote changes: