Skip to content

Commit

Permalink
chore: add service account roles
Browse files Browse the repository at this point in the history
  • Loading branch information
olevski committed Nov 21, 2023
1 parent b11ab84 commit 47c1a49
Show file tree
Hide file tree
Showing 3 changed files with 43 additions and 9 deletions.
2 changes: 2 additions & 0 deletions helm-chart/renku/templates/setup-job-keycloak-realms.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,8 @@ spec:
value: "false"
- name: DATASERVICE_KC_CLIENT_OAUTH_FLOW
value: "client_credentials"
- name: "DATASERVICE_KC_CLIENT_SERVICE_ACCOUNT_ROLES"
value: '["view-users", "query-users", "view-events"]'
- name: PYTHONUNBUFFERED
value: "0"
{{- include "certificates.env.python" . | nindent 12 }}
Expand Down
44 changes: 35 additions & 9 deletions scripts/init-realm/init-realm.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
KeycloakPostError,
)

from utils import DemoUserConfig, OIDCClientsConfig, OIDCGitlabClient
from utils import DemoUserConfig, OIDCClientsConfig, OIDCGitlabClient, OIDCClient, OAuthFlow

logging.basicConfig(level=logging.INFO)

Expand Down Expand Up @@ -70,18 +70,18 @@ def _fix_json_values(data: Dict) -> Dict:
return json.loads(json.dumps(data).replace('"true"', "true").replace('"false"', "false"))


def _check_and_create_client(keycloak_admin, new_client, force: bool):
def _check_and_create_client(keycloak_admin, new_client: OIDCClient, force: bool):
"""
Check if a client exists. Create it if not. Alert if
it exists but with different details than what is provided.
"""

logging.info("Checking if {} client exists...".format(new_client["clientId"]))
logging.info("Checking if {} client exists...".format(new_client.id))
realm_clients = keycloak_admin.get_clients()
client_ids = [c["clientId"] for c in realm_clients]
if new_client["clientId"] in client_ids:
if new_client.id in client_ids:
logging.info("found")
realm_client = realm_clients[client_ids.index(new_client["clientId"])]
realm_client = realm_clients[client_ids.index(new_client.id)]

# We have to separately query the secret as it is not part of
# the original response
Expand All @@ -99,22 +99,48 @@ def _check_and_create_client(keycloak_admin, new_client, force: bool):
if "attributes" in realm_client:
realm_client["attributes"] = _fix_json_values(realm_client["attributes"])

roles_changed = False
service_account_user = None
existing_roles = []
if new_client.oauth_flow == OAuthFlow.client_credentials:
try:
service_account_user = keycloak_admin.get_client_service_account_user(new_client.id)
except KeycloakGetError as err:
if err.response_code != 404:
raise
if isinstance(service_account_user, dict):
try:
existing_roles = service_account_user.get("realRoles", [])
except KeycloakGetError as err:
if err.response_code != 404:
raise
if set(existing_roles) != set(new_client.service_account_roles):
roles_changed = True
changed = _check_existing(realm_client, new_client, "client", "clientId")

if not force or not changed:
if not force or (not changed and not roles_changed):
return

logging.info(f"Recreating modified client '{realm_client['clientId']}'...")

keycloak_admin.delete_client(realm_client["id"])
keycloak_admin.create_client(new_client)

if isinstance(service_account_user) and service_account_user.get("id"):
logging.info(f"Reassigning service account roles {new_client.service_account_roles}")
keycloak_admin.assign_realm_roles(service_account_user["id"], new_client.service_account_roles)

logging.info("done")

else:
logging.info("not found")
logging.info("Creating {} client...".format(new_client["clientId"]))
keycloak_admin.create_client(new_client)
if new_client.oauth_flow == OAuthFlow.client_credentials and new_client.service_account_roles:
service_account_user = keycloak_admin.get_client_service_account_user(new_client.id)
logging.info(f"Assigning service account roles {new_client.service_account_roles}")
keycloak_admin.assign_realm_roles(service_account_user["id"], new_client.service_account_roles)

logging.info("done")


Expand Down Expand Up @@ -238,10 +264,10 @@ def _check_and_create_user(keycloak_admin, new_user):
keycloak_admin.connection.realm_name = args.realm


for new_client in OIDCClientsConfig.from_env().to_list():
_check_and_create_client(keycloak_admin, new_client, args.force)
for client in OIDCClientsConfig.from_env():
_check_and_create_client(keycloak_admin, client, args.force)

gitlab_oidc_client = OIDCGitlabClient.from_env().to_dict()
gitlab_oidc_client = OIDCGitlabClient.from_env()
if gitlab_oidc_client is not None:
_check_and_create_client(keycloak_admin, gitlab_oidc_client, args.force)

Expand Down
6 changes: 6 additions & 0 deletions scripts/init-realm/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ class OIDCClient:
disable_other_oauth_flows: bool = True
secret: Optional[str] = field(default=None, repr=False)
attributes: Dict[str, Any] = field(default_factory=lambda: {})
service_account_roles: List[str] = field(default_factory=list)
public_client: bool = False

def __post_init__(self):
Expand All @@ -83,6 +84,10 @@ def __post_init__(self):
f"The OIDC client configuration for client {self.id} is not valid, "
"the client is marked as not public but a secret is not provided."
)
if self.oauth_flow != OAuthFlow.client_credentials and len(self.service_account_roles) > 0:
raise ValueError(
f"Service account roles can only be specified for the {OAuthFlow.client_credentials.value} flow"
)

def to_dict(self) -> Dict[str, Any]:
output = {
Expand Down Expand Up @@ -124,6 +129,7 @@ def from_env(cls, prefix: str = "RENKU_KC_CLIENT_") -> "OIDCClient":
disable_other_oauth_flows=os.environ.get(
f"{prefix}DISABLE_OTHER_OAUTH_FLOWS", "true"
).lower() == "true",
service_account_roles=json.loads(os.environ.get(f"{prefix}SERVICE_ACCOUNT_ROLES", "[]")),
)


Expand Down

0 comments on commit 47c1a49

Please sign in to comment.