-
Notifications
You must be signed in to change notification settings - Fork 108
add service account with allow-app-sharing-role permissions #2917
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 12 commits
6bc13de
a2e1620
234baa2
d609271
01d1d5d
1bfe644
a4943bb
5f9834a
7e6204a
2a3e49b
110b0ee
a0f4efe
f180f07
6406e82
325a601
64d3e0b
cb775e0
59078cc
f799f3e
21d0880
0be3851
fedf7ae
2fb4fa8
8cb0e63
556661f
7e5c2b0
37bd636
b6e75de
1fce666
865c8d6
fad0155
8569ee8
80456c5
627c4aa
6de7c1d
48eae29
fbaec09
708f753
e7da0aa
de43a81
9810fdb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,6 @@ | ||
| import asyncio | ||
| import json | ||
| import logging | ||
| import os | ||
| import time | ||
| import urllib | ||
|
|
@@ -30,6 +31,32 @@ class KeyCloakOAuthenticator(GenericOAuthenticator): | |
|
|
||
| reset_managed_roles_on_startup = Bool(True) | ||
|
|
||
| async def set_service_account_auth_state(self, user): | ||
| service_account_auth_state = await self.authenticate_service_account() | ||
| await user.save_auth_state(service_account_auth_state) | ||
| logging.info(f'Auth state set for service account "{user.name}"') | ||
|
|
||
| async def authenticate_service_account(self): | ||
| token_info = await self._get_token_info() | ||
|
|
||
| # Get user info using the access token | ||
| user_info = await self.token_to_user(token_info) | ||
|
|
||
| # Get/set username | ||
| username = self.user_info_to_username(user_info) | ||
| username = self.normalize_username(username) | ||
|
|
||
| # Build auth model similar to OAuth flow | ||
| auth_model = { | ||
| "name": username, | ||
| "admin": True if username in self.admin_users else None, | ||
| "auth_state": self.build_auth_state_dict(token_info, user_info), | ||
| } | ||
|
|
||
| auth_model = await self.update_auth_model(auth_model) | ||
|
Comment on lines
+54
to
+70
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should add a note here and link to the JupyterHub code for posterity, incase something changes in JupyterHub, we can catch-up with that.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
|
|
||
| return auth_model["auth_state"] | ||
|
|
||
| async def update_auth_model(self, auth_model): | ||
| """Updates and returns the auth_model dict. | ||
| This function is called every time a user authenticates with JupyterHub, as in | ||
|
|
@@ -307,7 +334,7 @@ def _get_user_roles(self, user_info): | |
| ) | ||
| return set() | ||
|
|
||
| async def _get_token(self) -> str: | ||
| async def _get_token_info(self) -> str: | ||
| http = self.http_client | ||
|
|
||
| body = urllib.parse.urlencode( | ||
|
|
@@ -322,8 +349,12 @@ async def _get_token(self) -> str: | |
| method="POST", | ||
| body=body, | ||
| ) | ||
| data = json.loads(response.body) | ||
| return data["access_token"] # type: ignore[no-any-return] | ||
| token_info = json.loads(response.body) | ||
| return token_info | ||
|
|
||
| async def _get_token(self) -> str: | ||
| token_info = await self._get_token_info() | ||
| return token_info["access_token"] # type: ignore[no-any-return] | ||
|
|
||
| async def _fetch_api(self, endpoint: str, token: str): | ||
| response = await self.http_client.fetch( | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| terraform { | ||
| required_providers { | ||
| keycloak = { | ||
| source = "mrparkers/keycloak" | ||
| version = "3.7.0" | ||
| } | ||
| } | ||
| required_version = ">= 1.0" | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -67,29 +67,54 @@ data "keycloak_realm" "master" { | |
| realm = "nebari" | ||
| } | ||
|
|
||
| data "keycloak_openid_client" "realm_management" { | ||
| realm_id = var.realm_id | ||
| client_id = "realm-management" | ||
| } | ||
|
|
||
| data "keycloak_role" "main-service" { | ||
| for_each = toset(var.service-account-roles) | ||
| # Get client data for each service account client | ||
| data "keycloak_openid_client" "service_clients" { | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Before we only allowed service accounts to get roles from the realm-management client. This PR allows us to set roles by any client. This functionality was needed to be able to set the allow-app-sharing-role on the jupyterhub service account. |
||
| for_each = var.service-account-roles | ||
|
|
||
| realm_id = data.keycloak_realm.master.id | ||
| client_id = data.keycloak_openid_client.realm_management.id | ||
| name = each.key | ||
| } | ||
| realm_id = var.realm_id | ||
| client_id = each.key | ||
| depends_on = [keycloak_openid_client.main] | ||
| } | ||
|
|
||
| # Get role data for each client's roles | ||
| data "keycloak_role" "client_roles" { | ||
| for_each = { | ||
| for pair in flatten([ | ||
| for client, roles in var.service-account-roles : [ | ||
| for role in roles : { | ||
| key = "${client}-${role}" | ||
| client = client | ||
| role = role | ||
| } | ||
| ] | ||
| ]) : pair.key => pair | ||
| } | ||
|
|
||
| resource "keycloak_openid_client_service_account_role" "main" { | ||
| for_each = toset(var.service-account-roles) | ||
| realm_id = var.realm_id | ||
| client_id = data.keycloak_openid_client.service_clients[each.value.client].id | ||
| name = each.value.role | ||
| } | ||
|
|
||
| resource "keycloak_openid_client_service_account_role" "client_roles" { | ||
| for_each = { | ||
| for pair in flatten([ | ||
| for client, roles in var.service-account-roles : [ | ||
| for role in roles : { | ||
| key = "${client}-${role}" | ||
| client = client | ||
| role = role | ||
| } | ||
| ] | ||
| ]) : pair.key => pair | ||
| } | ||
|
|
||
| realm_id = var.realm_id | ||
| service_account_user_id = keycloak_openid_client.main.service_account_user_id | ||
| client_id = data.keycloak_openid_client.realm_management.id | ||
| role = data.keycloak_role.main-service[each.key].name | ||
| client_id = data.keycloak_openid_client.service_clients[each.value.client].id | ||
| role = data.keycloak_role.client_roles[each.key].name | ||
| } | ||
|
|
||
|
|
||
| resource "keycloak_role" "main" { | ||
| for_each = toset(flatten(values(var.role_mapping))) | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
flyby: tornado coroutine -> native coroutine. We don't need to use a tornado coroutine.