summaryrefslogtreecommitdiffstats
path: root/bitbake/bin
diff options
context:
space:
mode:
authorJoshua Watt <JPEWhacker@gmail.com>2023-11-03 08:26:31 -0600
committerRichard Purdie <richard.purdie@linuxfoundation.org>2023-11-09 17:33:03 +0000
commit1af725b2eca63fa113cedb6d77eb5c5f1de6e2f0 (patch)
treeadf200a0b03b8ee1f1a56c55e2ec657dcc7ed04a /bitbake/bin
parent6e67b000efb89c4e3121fd907a47dc7042c07bed (diff)
downloadpoky-1af725b2eca63fa113cedb6d77eb5c5f1de6e2f0.tar.gz
bitbake: hashserv: Add user permissions
Adds support for the hashserver to have per-user permissions. User management is done via a new "auth" RPC API where a client can authenticate itself with the server using a randomly generated token. The user can then be given permissions to read, report, manage the database, or manage other users. In addition to explicit user logins, the server supports anonymous users which is what all users start as before they make the "auth" RPC call. Anonymous users can be assigned a set of permissions by the server, making it unnecessary for users to authenticate to use the server. The set of Anonymous permissions defines the default behavior of the server, for example if set to "@read", Anonymous users are unable to report equivalent hashes with authenticating. Similarly, setting the Anonymous permissions to "@none" would require authentication for users to perform any action. User creation and management is entirely manual (although bitbake-hashclient is very useful as a front end). There are many different mechanisms that could be implemented to allow user self-registration (e.g. OAuth, LDAP, etc.), and implementing these is outside the scope of the server. Instead, it is recommended to implement a registration service that validates users against the necessary service, then adds them as a user in the hash equivalence server. (Bitbake rev: 69e5417413ee2414fffaa7dd38057573bac56e35) Signed-off-by: Joshua Watt <JPEWhacker@gmail.com> Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
Diffstat (limited to 'bitbake/bin')
-rwxr-xr-xbitbake/bin/bitbake-hashclient84
-rwxr-xr-xbitbake/bin/bitbake-hashserv37
2 files changed, 119 insertions, 2 deletions
diff --git a/bitbake/bin/bitbake-hashclient b/bitbake/bin/bitbake-hashclient
index a02a65b937..328c15cdec 100755
--- a/bitbake/bin/bitbake-hashclient
+++ b/bitbake/bin/bitbake-hashclient
@@ -14,6 +14,7 @@ import sys
14import threading 14import threading
15import time 15import time
16import warnings 16import warnings
17import netrc
17warnings.simplefilter("default") 18warnings.simplefilter("default")
18 19
19try: 20try:
@@ -36,10 +37,18 @@ except ImportError:
36sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(__file__)), 'lib')) 37sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(__file__)), 'lib'))
37 38
38import hashserv 39import hashserv
40import bb.asyncrpc
39 41
40DEFAULT_ADDRESS = 'unix://./hashserve.sock' 42DEFAULT_ADDRESS = 'unix://./hashserve.sock'
41METHOD = 'stress.test.method' 43METHOD = 'stress.test.method'
42 44
45def print_user(u):
46 print(f"Username: {u['username']}")
47 if "permissions" in u:
48 print("Permissions: " + " ".join(u["permissions"]))
49 if "token" in u:
50 print(f"Token: {u['token']}")
51
43 52
44def main(): 53def main():
45 def handle_stats(args, client): 54 def handle_stats(args, client):
@@ -125,9 +134,39 @@ def main():
125 print("Removed %d rows" % (result["count"])) 134 print("Removed %d rows" % (result["count"]))
126 return 0 135 return 0
127 136
137 def handle_refresh_token(args, client):
138 r = client.refresh_token(args.username)
139 print_user(r)
140
141 def handle_set_user_permissions(args, client):
142 r = client.set_user_perms(args.username, args.permissions)
143 print_user(r)
144
145 def handle_get_user(args, client):
146 r = client.get_user(args.username)
147 print_user(r)
148
149 def handle_get_all_users(args, client):
150 users = client.get_all_users()
151 print("{username:20}| {permissions}".format(username="Username", permissions="Permissions"))
152 print(("-" * 20) + "+" + ("-" * 20))
153 for u in users:
154 print("{username:20}| {permissions}".format(username=u["username"], permissions=" ".join(u["permissions"])))
155
156 def handle_new_user(args, client):
157 r = client.new_user(args.username, args.permissions)
158 print_user(r)
159
160 def handle_delete_user(args, client):
161 r = client.delete_user(args.username)
162 print_user(r)
163
128 parser = argparse.ArgumentParser(description='Hash Equivalence Client') 164 parser = argparse.ArgumentParser(description='Hash Equivalence Client')
129 parser.add_argument('--address', default=DEFAULT_ADDRESS, help='Server address (default "%(default)s")') 165 parser.add_argument('--address', default=DEFAULT_ADDRESS, help='Server address (default "%(default)s")')
130 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")
168 parser.add_argument('--password', '-p', metavar="TOKEN", help="Authenticate using token TOKEN")
169 parser.add_argument('--no-netrc', '-n', action="store_false", dest="netrc", help="Do not use .netrc")
131 170
132 subparsers = parser.add_subparsers() 171 subparsers = parser.add_subparsers()
133 172
@@ -158,6 +197,31 @@ def main():
158 clean_unused_parser.add_argument("max_age", metavar="SECONDS", type=int, help="Remove unused entries older than SECONDS old") 197 clean_unused_parser.add_argument("max_age", metavar="SECONDS", type=int, help="Remove unused entries older than SECONDS old")
159 clean_unused_parser.set_defaults(func=handle_clean_unused) 198 clean_unused_parser.set_defaults(func=handle_clean_unused)
160 199
200 refresh_token_parser = subparsers.add_parser('refresh-token', help="Refresh auth token")
201 refresh_token_parser.add_argument("--username", "-u", help="Refresh the token for another user (if authorized)")
202 refresh_token_parser.set_defaults(func=handle_refresh_token)
203
204 set_user_perms_parser = subparsers.add_parser('set-user-perms', help="Set new permissions for user")
205 set_user_perms_parser.add_argument("--username", "-u", help="Username", required=True)
206 set_user_perms_parser.add_argument("permissions", metavar="PERM", nargs="*", default=[], help="New permissions")
207 set_user_perms_parser.set_defaults(func=handle_set_user_permissions)
208
209 get_user_parser = subparsers.add_parser('get-user', help="Get user")
210 get_user_parser.add_argument("--username", "-u", help="Username")
211 get_user_parser.set_defaults(func=handle_get_user)
212
213 get_all_users_parser = subparsers.add_parser('get-all-users', help="List all users")
214 get_all_users_parser.set_defaults(func=handle_get_all_users)
215
216 new_user_parser = subparsers.add_parser('new-user', help="Create new user")
217 new_user_parser.add_argument("--username", "-u", help="Username", required=True)
218 new_user_parser.add_argument("permissions", metavar="PERM", nargs="*", default=[], help="New permissions")
219 new_user_parser.set_defaults(func=handle_new_user)
220
221 delete_user_parser = subparsers.add_parser('delete-user', help="Delete user")
222 delete_user_parser.add_argument("--username", "-u", help="Username", required=True)
223 delete_user_parser.set_defaults(func=handle_delete_user)
224
161 args = parser.parse_args() 225 args = parser.parse_args()
162 226
163 logger = logging.getLogger('hashserv') 227 logger = logging.getLogger('hashserv')
@@ -171,10 +235,26 @@ def main():
171 console.setLevel(level) 235 console.setLevel(level)
172 logger.addHandler(console) 236 logger.addHandler(console)
173 237
238 login = args.login
239 password = args.password
240
241 if login is None and args.netrc:
242 try:
243 n = netrc.netrc()
244 auth = n.authenticators(args.address)
245 if auth is not None:
246 login, _, password = auth
247 except FileNotFoundError:
248 pass
249
174 func = getattr(args, 'func', None) 250 func = getattr(args, 'func', None)
175 if func: 251 if func:
176 with hashserv.create_client(args.address) as client: 252 try:
177 return func(args, client) 253 with hashserv.create_client(args.address, login, password) as client:
254 return func(args, client)
255 except bb.asyncrpc.InvokeError as e:
256 print(f"ERROR: {e}")
257 return 1
178 258
179 return 0 259 return 0
180 260
diff --git a/bitbake/bin/bitbake-hashserv b/bitbake/bin/bitbake-hashserv
index 59b8b07f59..1085d0584e 100755
--- a/bitbake/bin/bitbake-hashserv
+++ b/bitbake/bin/bitbake-hashserv
@@ -17,6 +17,7 @@ warnings.simplefilter("default")
17sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(__file__)), "lib")) 17sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(__file__)), "lib"))
18 18
19import hashserv 19import hashserv
20from hashserv.server import DEFAULT_ANON_PERMS
20 21
21VERSION = "1.0.0" 22VERSION = "1.0.0"
22 23
@@ -36,6 +37,22 @@ The bind address may take one of the following formats:
36To bind to all addresses, leave the ADDRESS empty, e.g. "--bind :8686" or 37To bind to all addresses, leave the ADDRESS empty, e.g. "--bind :8686" or
37"--bind ws://:8686". To bind to a specific IPv6 address, enclose the address in 38"--bind ws://:8686". To bind to a specific IPv6 address, enclose the address in
38"[]", e.g. "--bind [::1]:8686" or "--bind ws://[::1]:8686" 39"[]", e.g. "--bind [::1]:8686" or "--bind ws://[::1]:8686"
40
41Note that the default Anonymous permissions are designed to not break existing
42server instances when upgrading, but are not particularly secure defaults. If
43you want to use authentication, it is recommended that you use "--anon-perms
44@read" to only give anonymous users read access, or "--anon-perms @none" to
45give un-authenticated users no access at all.
46
47Setting "--anon-perms @all" or "--anon-perms @user-admin" is not allowed, since
48this would allow anonymous users to manage all users accounts, which is a bad
49idea.
50
51If you are using user authentication, you should run your server in websockets
52mode with an SSL terminating load balancer in front of it (as this server does
53not implement SSL). Otherwise all usernames and passwords will be transmitted
54in the clear. When configured this way, clients can connect using a secure
55websocket, as in "wss://SERVER:PORT"
39 """, 56 """,
40 ) 57 )
41 58
@@ -79,6 +96,22 @@ To bind to all addresses, leave the ADDRESS empty, e.g. "--bind :8686" or
79 default=os.environ.get("HASHSERVER_DB_PASSWORD", None), 96 default=os.environ.get("HASHSERVER_DB_PASSWORD", None),
80 help="Database password ($HASHSERVER_DB_PASSWORD)", 97 help="Database password ($HASHSERVER_DB_PASSWORD)",
81 ) 98 )
99 parser.add_argument(
100 "--anon-perms",
101 metavar="PERM[,PERM[,...]]",
102 default=os.environ.get("HASHSERVER_ANON_PERMS", ",".join(DEFAULT_ANON_PERMS)),
103 help='Permissions to give anonymous users (default $HASHSERVER_ANON_PERMS, "%(default)s")',
104 )
105 parser.add_argument(
106 "--admin-user",
107 default=os.environ.get("HASHSERVER_ADMIN_USER", None),
108 help="Create default admin user with name ADMIN_USER ($HASHSERVER_ADMIN_USER)",
109 )
110 parser.add_argument(
111 "--admin-password",
112 default=os.environ.get("HASHSERVER_ADMIN_PASSWORD", None),
113 help="Create default admin user with password ADMIN_PASSWORD ($HASHSERVER_ADMIN_PASSWORD)",
114 )
82 115
83 args = parser.parse_args() 116 args = parser.parse_args()
84 117
@@ -94,6 +127,7 @@ To bind to all addresses, leave the ADDRESS empty, e.g. "--bind :8686" or
94 logger.addHandler(console) 127 logger.addHandler(console)
95 128
96 read_only = (os.environ.get("HASHSERVER_READ_ONLY", "0") == "1") or args.read_only 129 read_only = (os.environ.get("HASHSERVER_READ_ONLY", "0") == "1") or args.read_only
130 anon_perms = args.anon_perms.split(",")
97 131
98 server = hashserv.create_server( 132 server = hashserv.create_server(
99 args.bind, 133 args.bind,
@@ -102,6 +136,9 @@ To bind to all addresses, leave the ADDRESS empty, e.g. "--bind :8686" or
102 read_only=read_only, 136 read_only=read_only,
103 db_username=args.db_username, 137 db_username=args.db_username,
104 db_password=args.db_password, 138 db_password=args.db_password,
139 anon_perms=anon_perms,
140 admin_username=args.admin_user,
141 admin_password=args.admin_password,
105 ) 142 )
106 server.serve_forever() 143 server.serve_forever()
107 return 0 144 return 0