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
1diff --git a/lib/lp/code/xmlrpc/git.py b/lib/lp/code/xmlrpc/git.py
2index 62dad82..e890605 100644
3--- a/lib/lp/code/xmlrpc/git.py
4+++ b/lib/lp/code/xmlrpc/git.py
5@@ -565,7 +565,10 @@ class GitAPI(LaunchpadXMLRPCView):
6 otherwise Zope's XML-RPC publication machinery gets confused by the
7 decorator and publishes a method that takes zero arguments.
8 """
9- user = getUtility(IPersonSet).getByName(username) if username else None
10+ user = (
11+ getUtility(IPersonSet).getByName(username)
12+ if username and username != LAUNCHPAD_SERVICES
13+ else None)
14 verified = self._verifyMacaroon(password, user=user)
15 if verified:
16 auth_params = {"macaroon": password}
17diff --git a/lib/lp/code/xmlrpc/tests/test_git.py b/lib/lp/code/xmlrpc/tests/test_git.py
18index efc1087..8a032b5 100644
19--- a/lib/lp/code/xmlrpc/tests/test_git.py
20+++ b/lib/lp/code/xmlrpc/tests/test_git.py
21@@ -2302,17 +2302,22 @@ class TestGitAPI(TestGitAPIMixin, TestCaseWithFactory):
22 job = self.factory.makeCodeImportJob(code_import=code_import)
23 issuer = getUtility(IMacaroonIssuer, "code-import-job")
24 macaroon = removeSecurityProxy(issuer).issueMacaroon(job)
25- self.assertEqual(
26- {"macaroon": macaroon.serialize(), "user": "+launchpad-services"},
27- self.assertDoesNotFault(
28- None, "authenticateWithPassword", "", macaroon.serialize()))
29- other_macaroon = Macaroon(identifier="another", key="another-secret")
30- self.assertFault(
31- faults.Unauthorized, None,
32- "authenticateWithPassword", "", other_macaroon.serialize())
33- self.assertFault(
34- faults.Unauthorized, None,
35- "authenticateWithPassword", "", "nonsense")
36+ for username in ("", "+launchpad-services"):
37+ self.assertEqual(
38+ {"macaroon": macaroon.serialize(),
39+ "user": "+launchpad-services"},
40+ self.assertDoesNotFault(
41+ None, "authenticateWithPassword",
42+ username, macaroon.serialize()))
43+ other_macaroon = Macaroon(
44+ identifier="another", key="another-secret")
45+ self.assertFault(
46+ faults.Unauthorized, None,
47+ "authenticateWithPassword",
48+ username, other_macaroon.serialize())
49+ self.assertFault(
50+ faults.Unauthorized, None,
51+ "authenticateWithPassword", username, "nonsense")
52
53 def test_authenticateWithPassword_private_snap_build(self):
54 self.useFixture(FeatureFixture(SNAP_TESTING_FLAGS))
55@@ -2325,17 +2330,22 @@ class TestGitAPI(TestGitAPIMixin, TestCaseWithFactory):
56 requester=owner, owner=owner, git_ref=ref, private=True)
57 issuer = getUtility(IMacaroonIssuer, "snap-build")
58 macaroon = removeSecurityProxy(issuer).issueMacaroon(build)
59- self.assertEqual(
60- {"macaroon": macaroon.serialize(), "user": "+launchpad-services"},
61- self.assertDoesNotFault(
62- None, "authenticateWithPassword", "", macaroon.serialize()))
63- other_macaroon = Macaroon(identifier="another", key="another-secret")
64- self.assertFault(
65- faults.Unauthorized, None,
66- "authenticateWithPassword", "", other_macaroon.serialize())
67- self.assertFault(
68- faults.Unauthorized, None,
69- "authenticateWithPassword", "", "nonsense")
70+ for username in ("", "+launchpad-services"):
71+ self.assertEqual(
72+ {"macaroon": macaroon.serialize(),
73+ "user": "+launchpad-services"},
74+ self.assertDoesNotFault(
75+ None, "authenticateWithPassword",
76+ username, macaroon.serialize()))
77+ other_macaroon = Macaroon(
78+ identifier="another", key="another-secret")
79+ self.assertFault(
80+ faults.Unauthorized, None,
81+ "authenticateWithPassword",
82+ username, other_macaroon.serialize())
83+ self.assertFault(
84+ faults.Unauthorized, None,
85+ "authenticateWithPassword", username, "nonsense")
86
87 def test_authenticateWithPassword_user_macaroon(self):
88 # A user with a suitable macaroon can authenticate using it, in
89@@ -2351,12 +2361,10 @@ class TestGitAPI(TestGitAPIMixin, TestCaseWithFactory):
90 self.assertDoesNotFault(
91 None, "authenticateWithPassword",
92 requester.name, macaroon.serialize()))
93- self.assertFault(
94- faults.Unauthorized, None,
95- "authenticateWithPassword", "", macaroon.serialize())
96- self.assertFault(
97- faults.Unauthorized, None,
98- "authenticateWithPassword", "nonexistent", macaroon.serialize())
99+ for username in ("", "+launchpad-services", "nonexistent"):
100+ self.assertFault(
101+ faults.Unauthorized, None,
102+ "authenticateWithPassword", username, macaroon.serialize())
103 other_macaroon = Macaroon(identifier="another", key="another-secret")
104 self.assertFault(
105 faults.Unauthorized, None,
106diff --git a/lib/lp/oci/model/ocirecipebuildbehaviour.py b/lib/lp/oci/model/ocirecipebuildbehaviour.py
107index 8147c77..6b82f36 100644
108--- a/lib/lp/oci/model/ocirecipebuildbehaviour.py
109+++ b/lib/lp/oci/model/ocirecipebuildbehaviour.py
110@@ -34,6 +34,7 @@ from lp.buildmaster.interfaces.buildfarmjobbehaviour import (
111 from lp.buildmaster.model.buildfarmjobbehaviour import (
112 BuildFarmJobBehaviourBase,
113 )
114+from lp.code.interfaces.codehosting import LAUNCHPAD_SERVICES
115 from lp.oci.interfaces.ocirecipebuild import IOCIFileSet
116 from lp.registry.interfaces.series import SeriesStatus
117 from lp.services.config import config
118@@ -156,7 +157,7 @@ class OCIRecipeBuildBehaviour(BuilderProxyMixin, BuildFarmJobBehaviourBase):
119 if build.recipe.git_repository.private:
120 macaroon_raw = yield self.issueMacaroon()
121 url = build.recipe.git_repository.getCodebrowseUrl(
122- username=None, password=macaroon_raw)
123+ username=LAUNCHPAD_SERVICES, password=macaroon_raw)
124 args["git_repository"] = url
125 else:
126 args["git_repository"] = (
127diff --git a/lib/lp/oci/tests/test_ocirecipebuildbehaviour.py b/lib/lp/oci/tests/test_ocirecipebuildbehaviour.py
128index a29408b..d379a12 100644
129--- a/lib/lp/oci/tests/test_ocirecipebuildbehaviour.py
130+++ b/lib/lp/oci/tests/test_ocirecipebuildbehaviour.py
131@@ -457,7 +457,7 @@ class TestAsyncOCIRecipeBuildBehaviour(
132 "fast_cleanup": Is(True),
133 "git_repository": AfterPreprocessing(urlsplit, MatchesStructure(
134 scheme=Equals(split_browse_root.scheme),
135- username=Equals(""),
136+ username=Equals("+launchpad-services"),
137 password=AfterPreprocessing(
138 Macaroon.deserialize, MatchesStructure(
139 location=Equals(config.vhost.mainsite.hostname),
140diff --git a/lib/lp/snappy/model/snapbuildbehaviour.py b/lib/lp/snappy/model/snapbuildbehaviour.py
141index b6fd2ee..f74895e 100644
142--- a/lib/lp/snappy/model/snapbuildbehaviour.py
143+++ b/lib/lp/snappy/model/snapbuildbehaviour.py
144@@ -25,6 +25,7 @@ from lp.buildmaster.interfaces.buildfarmjobbehaviour import (
145 from lp.buildmaster.model.buildfarmjobbehaviour import (
146 BuildFarmJobBehaviourBase,
147 )
148+from lp.code.interfaces.codehosting import LAUNCHPAD_SERVICES
149 from lp.registry.interfaces.series import SeriesStatus
150 from lp.services.config import config
151 from lp.services.features import getFeatureFlag
152@@ -143,7 +144,7 @@ class SnapBuildBehaviour(BuilderProxyMixin, BuildFarmJobBehaviourBase):
153 elif build.snap.git_repository.private:
154 macaroon_raw = yield self.issueMacaroon()
155 url = build.snap.git_repository.getCodebrowseUrl(
156- username=None, password=macaroon_raw)
157+ username=LAUNCHPAD_SERVICES, password=macaroon_raw)
158 args["git_repository"] = url
159 else:
160 args["git_repository"] = (
161diff --git a/lib/lp/snappy/tests/test_snapbuildbehaviour.py b/lib/lp/snappy/tests/test_snapbuildbehaviour.py
162index b5b1326..1d89091 100644
163--- a/lib/lp/snappy/tests/test_snapbuildbehaviour.py
164+++ b/lib/lp/snappy/tests/test_snapbuildbehaviour.py
165@@ -461,7 +461,7 @@ class TestAsyncSnapBuildBehaviour(StatsMixin, TestSnapBuildBehaviourBase):
166 "fast_cleanup": Is(True),
167 "git_repository": AfterPreprocessing(urlsplit, MatchesStructure(
168 scheme=Equals(split_browse_root.scheme),
169- username=Equals(""),
170+ username=Equals("+launchpad-services"),
171 password=AfterPreprocessing(
172 Macaroon.deserialize, MatchesStructure(
173 location=Equals(config.vhost.mainsite.hostname),

Subscribers

People subscribed via source and target branches

to status/vote changes: