summaryrefslogtreecommitdiffstats
path: root/bitbake
diff options
context:
space:
mode:
authorJoshua Watt <JPEWhacker@gmail.com>2023-11-03 08:26:32 -0600
committerRichard Purdie <richard.purdie@linuxfoundation.org>2023-11-09 17:33:03 +0000
commit8cfb94c06cdfe3e6f0ec1ce0154951108bc3df94 (patch)
tree046a6d0d98b0b1bfb1467b2d3e4bbc29b181eb9b /bitbake
parent1af725b2eca63fa113cedb6d77eb5c5f1de6e2f0 (diff)
downloadpoky-8cfb94c06cdfe3e6f0ec1ce0154951108bc3df94.tar.gz
bitbake: hashserv: Add become-user API
Adds API that allows a user admin to impersonate another user in the system. This makes it easier to write external services that have external authentication, since they can use a common user account to access the server, then impersonate the logged in user. (Bitbake rev: 71e2f5b52b686f34df364ae1f2fc058f45cd5e18) Signed-off-by: Joshua Watt <JPEWhacker@gmail.com> Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
Diffstat (limited to 'bitbake')
-rwxr-xr-xbitbake/bin/bitbake-hashclient3
-rw-r--r--bitbake/lib/hashserv/client.py42
-rw-r--r--bitbake/lib/hashserv/server.py18
-rw-r--r--bitbake/lib/hashserv/tests.py39
4 files changed, 97 insertions, 5 deletions
diff --git a/bitbake/bin/bitbake-hashclient b/bitbake/bin/bitbake-hashclient
index 328c15cdec..cfbc197e52 100755
--- a/bitbake/bin/bitbake-hashclient
+++ b/bitbake/bin/bitbake-hashclient
@@ -166,6 +166,7 @@ def main():
166 parser.add_argument('--log', default='WARNING', help='Set logging level') 166 parser.add_argument('--log', default='WARNING', help='Set logging level')
167 parser.add_argument('--login', '-l', metavar="USERNAME", help="Authenticate as USERNAME") 167 parser.add_argument('--login', '-l', metavar="USERNAME", help="Authenticate as USERNAME")
168 parser.add_argument('--password', '-p', metavar="TOKEN", help="Authenticate using token TOKEN") 168 parser.add_argument('--password', '-p', metavar="TOKEN", help="Authenticate using token TOKEN")
169 parser.add_argument('--become', '-b', metavar="USERNAME", help="Impersonate user USERNAME (if allowed) when performing actions")
169 parser.add_argument('--no-netrc', '-n', action="store_false", dest="netrc", help="Do not use .netrc") 170 parser.add_argument('--no-netrc', '-n', action="store_false", dest="netrc", help="Do not use .netrc")
170 171
171 subparsers = parser.add_subparsers() 172 subparsers = parser.add_subparsers()
@@ -251,6 +252,8 @@ def main():
251 if func: 252 if func:
252 try: 253 try:
253 with hashserv.create_client(args.address, login, password) as client: 254 with hashserv.create_client(args.address, login, password) as client:
255 if args.become:
256 client.become_user(args.become)
254 return func(args, client) 257 return func(args, client)
255 except bb.asyncrpc.InvokeError as e: 258 except bb.asyncrpc.InvokeError as e:
256 print(f"ERROR: {e}") 259 print(f"ERROR: {e}")
diff --git a/bitbake/lib/hashserv/client.py b/bitbake/lib/hashserv/client.py
index 82400fe5aa..4457f8e50d 100644
--- a/bitbake/lib/hashserv/client.py
+++ b/bitbake/lib/hashserv/client.py
@@ -18,10 +18,11 @@ class AsyncClient(bb.asyncrpc.AsyncClient):
18 MODE_GET_STREAM = 1 18 MODE_GET_STREAM = 1
19 19
20 def __init__(self, username=None, password=None): 20 def __init__(self, username=None, password=None):
21 super().__init__('OEHASHEQUIV', '1.1', logger) 21 super().__init__("OEHASHEQUIV", "1.1", logger)
22 self.mode = self.MODE_NORMAL 22 self.mode = self.MODE_NORMAL
23 self.username = username 23 self.username = username
24 self.password = password 24 self.password = password
25 self.saved_become_user = None
25 26
26 async def setup_connection(self): 27 async def setup_connection(self):
27 await super().setup_connection() 28 await super().setup_connection()
@@ -29,8 +30,13 @@ class AsyncClient(bb.asyncrpc.AsyncClient):
29 self.mode = self.MODE_NORMAL 30 self.mode = self.MODE_NORMAL
30 await self._set_mode(cur_mode) 31 await self._set_mode(cur_mode)
31 if self.username: 32 if self.username:
33 # Save off become user temporarily because auth() resets it
34 become = self.saved_become_user
32 await self.auth(self.username, self.password) 35 await self.auth(self.username, self.password)
33 36
37 if become:
38 await self.become_user(become)
39
34 async def send_stream(self, msg): 40 async def send_stream(self, msg):
35 async def proc(): 41 async def proc():
36 await self.socket.send(msg) 42 await self.socket.send(msg)
@@ -92,7 +98,14 @@ class AsyncClient(bb.asyncrpc.AsyncClient):
92 async def get_outhash(self, method, outhash, taskhash, with_unihash=True): 98 async def get_outhash(self, method, outhash, taskhash, with_unihash=True):
93 await self._set_mode(self.MODE_NORMAL) 99 await self._set_mode(self.MODE_NORMAL)
94 return await self.invoke( 100 return await self.invoke(
95 {"get-outhash": {"outhash": outhash, "taskhash": taskhash, "method": method, "with_unihash": with_unihash}} 101 {
102 "get-outhash": {
103 "outhash": outhash,
104 "taskhash": taskhash,
105 "method": method,
106 "with_unihash": with_unihash,
107 }
108 }
96 ) 109 )
97 110
98 async def get_stats(self): 111 async def get_stats(self):
@@ -120,6 +133,7 @@ class AsyncClient(bb.asyncrpc.AsyncClient):
120 result = await self.invoke({"auth": {"username": username, "token": token}}) 133 result = await self.invoke({"auth": {"username": username, "token": token}})
121 self.username = username 134 self.username = username
122 self.password = token 135 self.password = token
136 self.saved_become_user = None
123 return result 137 return result
124 138
125 async def refresh_token(self, username=None): 139 async def refresh_token(self, username=None):
@@ -128,13 +142,19 @@ class AsyncClient(bb.asyncrpc.AsyncClient):
128 if username: 142 if username:
129 m["username"] = username 143 m["username"] = username
130 result = await self.invoke({"refresh-token": m}) 144 result = await self.invoke({"refresh-token": m})
131 if self.username and result["username"] == self.username: 145 if (
146 self.username
147 and not self.saved_become_user
148 and result["username"] == self.username
149 ):
132 self.password = result["token"] 150 self.password = result["token"]
133 return result 151 return result
134 152
135 async def set_user_perms(self, username, permissions): 153 async def set_user_perms(self, username, permissions):
136 await self._set_mode(self.MODE_NORMAL) 154 await self._set_mode(self.MODE_NORMAL)
137 return await self.invoke({"set-user-perms": {"username": username, "permissions": permissions}}) 155 return await self.invoke(
156 {"set-user-perms": {"username": username, "permissions": permissions}}
157 )
138 158
139 async def get_user(self, username=None): 159 async def get_user(self, username=None):
140 await self._set_mode(self.MODE_NORMAL) 160 await self._set_mode(self.MODE_NORMAL)
@@ -149,12 +169,23 @@ class AsyncClient(bb.asyncrpc.AsyncClient):
149 169
150 async def new_user(self, username, permissions): 170 async def new_user(self, username, permissions):
151 await self._set_mode(self.MODE_NORMAL) 171 await self._set_mode(self.MODE_NORMAL)
152 return await self.invoke({"new-user": {"username": username, "permissions": permissions}}) 172 return await self.invoke(
173 {"new-user": {"username": username, "permissions": permissions}}
174 )
153 175
154 async def delete_user(self, username): 176 async def delete_user(self, username):
155 await self._set_mode(self.MODE_NORMAL) 177 await self._set_mode(self.MODE_NORMAL)
156 return await self.invoke({"delete-user": {"username": username}}) 178 return await self.invoke({"delete-user": {"username": username}})
157 179
180 async def become_user(self, username):
181 await self._set_mode(self.MODE_NORMAL)
182 result = await self.invoke({"become-user": {"username": username}})
183 if username == self.username:
184 self.saved_become_user = None
185 else:
186 self.saved_become_user = username
187 return result
188
158 189
159class Client(bb.asyncrpc.Client): 190class Client(bb.asyncrpc.Client):
160 def __init__(self, username=None, password=None): 191 def __init__(self, username=None, password=None):
@@ -182,6 +213,7 @@ class Client(bb.asyncrpc.Client):
182 "get_all_users", 213 "get_all_users",
183 "new_user", 214 "new_user",
184 "delete_user", 215 "delete_user",
216 "become_user",
185 ) 217 )
186 218
187 def _get_async_client(self): 219 def _get_async_client(self):
diff --git a/bitbake/lib/hashserv/server.py b/bitbake/lib/hashserv/server.py
index f5baa6be78..ca419a1abf 100644
--- a/bitbake/lib/hashserv/server.py
+++ b/bitbake/lib/hashserv/server.py
@@ -255,6 +255,7 @@ class ServerClient(bb.asyncrpc.AsyncServerConnection):
255 "auth": self.handle_auth, 255 "auth": self.handle_auth,
256 "get-user": self.handle_get_user, 256 "get-user": self.handle_get_user,
257 "get-all-users": self.handle_get_all_users, 257 "get-all-users": self.handle_get_all_users,
258 "become-user": self.handle_become_user,
258 } 259 }
259 ) 260 )
260 261
@@ -707,6 +708,23 @@ class ServerClient(bb.asyncrpc.AsyncServerConnection):
707 708
708 return {"username": username} 709 return {"username": username}
709 710
711 @permissions(USER_ADMIN_PERM, allow_anon=False)
712 async def handle_become_user(self, request):
713 username = str(request["username"])
714
715 user = await self.db.lookup_user(username)
716 if user is None:
717 raise bb.asyncrpc.InvokeError(f"User {username} doesn't exist")
718
719 self.user = user
720
721 self.logger.info("Became user %s", username)
722
723 return {
724 "username": self.user.username,
725 "permissions": self.return_perms(self.user.permissions),
726 }
727
710 728
711class Server(bb.asyncrpc.AsyncServer): 729class Server(bb.asyncrpc.AsyncServer):
712 def __init__( 730 def __init__(
diff --git a/bitbake/lib/hashserv/tests.py b/bitbake/lib/hashserv/tests.py
index f92f37c459..311b7b7772 100644
--- a/bitbake/lib/hashserv/tests.py
+++ b/bitbake/lib/hashserv/tests.py
@@ -728,6 +728,45 @@ class HashEquivalenceCommonTests(object):
728 self.assertEqual(user["username"], "test-user") 728 self.assertEqual(user["username"], "test-user")
729 self.assertEqual(user["permissions"], permissions) 729 self.assertEqual(user["permissions"], permissions)
730 730
731 def test_auth_become_user(self):
732 admin_client = self.start_auth_server()
733
734 user = admin_client.new_user("test-user", ["@read", "@report"])
735 user_info = user.copy()
736 del user_info["token"]
737
738 with self.auth_perms() as client, self.assertRaises(InvokeError):
739 client.become_user(user["username"])
740
741 with self.auth_perms("@user-admin") as client:
742 become = client.become_user(user["username"])
743 self.assertEqual(become, user_info)
744
745 info = client.get_user()
746 self.assertEqual(info, user_info)
747
748 # Verify become user is preserved across disconnect
749 client.disconnect()
750
751 info = client.get_user()
752 self.assertEqual(info, user_info)
753
754 # test-user doesn't have become_user permissions, so this should
755 # not work
756 with self.assertRaises(InvokeError):
757 client.become_user(user["username"])
758
759 # No self-service of become
760 with self.auth_client(user) as client, self.assertRaises(InvokeError):
761 client.become_user(user["username"])
762
763 # Give test user permissions to become
764 admin_client.set_user_perms(user["username"], ["@user-admin"])
765
766 # It's possible to become yourself (effectively a noop)
767 with self.auth_perms("@user-admin") as client:
768 become = client.become_user(client.username)
769
731 770
732class TestHashEquivalenceUnixServer(HashEquivalenceTestSetup, HashEquivalenceCommonTests, unittest.TestCase): 771class TestHashEquivalenceUnixServer(HashEquivalenceTestSetup, HashEquivalenceCommonTests, unittest.TestCase):
733 def get_server_addr(self, server_idx): 772 def get_server_addr(self, server_idx):