From ad5817ace0933114dd72cd1bdc7d7d175451357a Mon Sep 17 00:00:00 2001 From: Tasko Olevski Date: Mon, 20 Nov 2023 22:54:32 +0100 Subject: [PATCH 01/16] feat: add data service keycloak client --- helm-chart/renku/templates/_helpers.tpl | 4 +++ helm-chart/renku/templates/secrets.yaml | 10 ++++++ .../templates/setup-job-keycloak-realms.yaml | 19 ++++++++++ scripts/init-realm/utils.py | 36 +++++++++++++++++++ 4 files changed, 69 insertions(+) diff --git a/helm-chart/renku/templates/_helpers.tpl b/helm-chart/renku/templates/_helpers.tpl index 7930555279..b6c0a05df9 100644 --- a/helm-chart/renku/templates/_helpers.tpl +++ b/helm-chart/renku/templates/_helpers.tpl @@ -164,3 +164,7 @@ app.kubernetes.io/managed-by: {{ .Release.Service }} {{- define "renku.keycloak.realm" -}} {{ .Values.global.keycloak.realm | default "Renku" }} {{- end -}} + +{{- define "renku.dataService.keycloak.clientId" -}} +data-service +{{- end -}} diff --git a/helm-chart/renku/templates/secrets.yaml b/helm-chart/renku/templates/secrets.yaml index 0b39dfac76..c9b8a42110 100644 --- a/helm-chart/renku/templates/secrets.yaml +++ b/helm-chart/renku/templates/secrets.yaml @@ -1,4 +1,11 @@ --- +{{- $data_service_kc_client_secret := randAlphaNum 64 | b64enc | quote }} + +{{- $renku_secret := lookup "v1" "Secret" .Release.Namespace (include "renku.fullname" .) }} +{{- if $renku_secret }} +{{- $data_service_kc_client_secret = index $renku_secret.data "dataServiceKeycloakClientSecret" }} +{{- end -}} + apiVersion: v1 kind: Secret metadata: @@ -8,11 +15,14 @@ metadata: chart: {{ template "renku.chart" . }} release: {{ .Release.Name }} heritage: {{ .Release.Service }} + annotations: + "helm.sh/hook": "pre-install,pre-upgrade,pre-rollback" type: Opaque data: {{- if .Values.tests.users_json }} users.json: {{ .Values.tests.users_json | toJson | b64enc | quote }} {{- end }} + dataServiceKeycloakClientSecret: {{ $data_service_kc_client_secret }} {{- if and (eq .Values.redis.install true) (eq .Values.redis.createSecret true) }} diff --git a/helm-chart/renku/templates/setup-job-keycloak-realms.yaml b/helm-chart/renku/templates/setup-job-keycloak-realms.yaml index 0efc5d88fc..b1a8bfd4ca 100644 --- a/helm-chart/renku/templates/setup-job-keycloak-realms.yaml +++ b/helm-chart/renku/templates/setup-job-keycloak-realms.yaml @@ -86,6 +86,8 @@ spec: key: oidcClientSecret - name: RENKU_KC_CLIENT_PUBLIC value: "false" + - name: RENKU_KC_CLIENT_OAUTH_FLOW + value: "authorization_code" - name: CLI_KC_CLIENT_ID value: renku-cli - name: CLI_KC_CLIENT_SECRET @@ -97,6 +99,8 @@ spec: value: "true" - name: CLI_KC_CLIENT_ATTRIBUTES value: '{"access.token.lifespan": "86400", "oauth2.device.authorization.grant.enabled": true, "oauth2.device.polling.interval": "5"}' + - name: CLI_KC_CLIENT_OAUTH_FLOW + value: "device_code" - name: UI_KC_CLIENT_ID value: "renku-ui" - name: UI_KC_CLIENT_SECRET @@ -106,6 +110,8 @@ spec: key: uiserverClientSecret - name: UI_KC_CLIENT_PUBLIC value: "false" + - name: UI_KC_CLIENT_OAUTH_FLOW + value: "authorization_code" - name: NOTEBOOKS_KC_CLIENT_ID value: {{ .Values.notebooks.oidc.clientId | default "renku-jupyterserver" | quote }} - name: NOTEBOOKS_KC_CLIENT_SECRET @@ -115,12 +121,25 @@ spec: key: notebooksClientSecret - name: NOTEBOOKS_KC_CLIENT_PUBLIC value: "false" + - name: NOTEBOOKS_KC_CLIENT_OAUTH_FLOW + value: "authorization_code" - name: SWAGGER_KC_CLIENT_ID value: swagger - name: SWAGGER_KC_CLIENT_PUBLIC value: "true" - name: SWAGGER_KC_CLIENT_ATTRIBUTES value: '{"pkce.code.challenge.method": "S256"}' + - name: DATASERVICE_KC_CLIENT_ID + value: {{ include "renku.dataService.keycloak.clientId" . | quote }} + - name: DATASERVICE_KC_CLIENT_SECRET + valueFrom: + secretKeyRef: + name: {{ include "renku.fullname" . }} + key: dataServiceKeycloakClientSecret + - name: DATASERVICE_KC_CLIENT_PUBLIC + value: "false" + - name: DATASERVICE_KC_CLIENT_OAUTH_FLOW + value: "client_credentials" - name: PYTHONUNBUFFERED value: "0" {{- include "certificates.env.python" . | nindent 12 }} diff --git a/scripts/init-realm/utils.py b/scripts/init-realm/utils.py index ff009e5930..4e8226425e 100644 --- a/scripts/init-realm/utils.py +++ b/scripts/init-realm/utils.py @@ -1,6 +1,7 @@ import json import os from dataclasses import dataclass, field +from enum import Enum from typing import Any, Dict, List, Optional @@ -36,6 +37,31 @@ def to_dict(self) -> Optional[Dict[str, Any]]: } +class OAuthFlow(Enum): + device: str = "device" + authorization_code: str = "authorization_code" + client_credentials: str = "client_credentials" + + def get_keycloak_payload(self, disable_other_flows: bool = True) -> Dict[str, Any]: + output = { + "oauth2DeviceAuthorizationGrantEnabled": False, + "serviceAccountsEnabled": False, + "standardFlowEnabled": False, + } if disable_other_flows else {} + match self: + case OAuthFlow.authorization_code: + output["standardFlowEnabled"] = True + case OAuthFlow.device: + output["oauth2DeviceAuthorizationGrantEnabled"] = True + case OAuthFlow.client_credentials: + output["serviceAccountsEnabled"] = True + return output + + @classmethod + def from_env(cls, prefix: str = ""): + return cls(os.environ.get(f"{prefix}OAUTH_FLOW")) + + @dataclass class OIDCClient: """Stores the configuration needed to create an OIDC client application in Keycloak. These @@ -44,6 +70,8 @@ class OIDCClient: id: str base_url: str + oauth_flow: OAuthFlow + disable_other_oauth_flows: bool = True secret: Optional[str] = field(default=None, repr=False) attributes: Dict[str, Any] = field(default_factory=lambda: {}) public_client: bool = False @@ -81,6 +109,7 @@ def to_dict(self) -> Dict[str, Any]: } if self.secret is not None: output["secret"] = self.secret + output.update(**self.oauth_flow.get_keycloak_payload(self.disable_other_oauth_flows)) return output @classmethod @@ -91,6 +120,10 @@ def from_env(cls, prefix: str = "RENKU_KC_CLIENT_") -> "OIDCClient": base_url=os.environ.get(f"{prefix}BASE_URL", os.environ["RENKU_BASE_URL"]), attributes=json.loads(os.environ.get(f"{prefix}ATTRIBUTES", "{}")), public_client=os.environ.get(f"{prefix}PUBLIC", "false").lower() == "true", + oauth_flow=OAuthFlow.from_env(prefix), + disable_other_oauth_flows=os.environ.get( + f"{prefix}DISABLE_OTHER_OAUTH_FLOWS", "true" + ).lower() == "true", ) @@ -140,6 +173,7 @@ class OIDCClientsConfig: ui: OIDCClient notebooks: OIDCClient swagger: OIDCClient + data_service: OIDCClient @classmethod def from_env(cls) -> "OIDCClientsConfig": @@ -149,6 +183,7 @@ def from_env(cls) -> "OIDCClientsConfig": ui=OIDCClient.from_env(prefix="UI_KC_CLIENT_"), notebooks=OIDCClient.from_env(prefix="NOTEBOOKS_KC_CLIENT_"), swagger=OIDCClient.from_env(prefix="SWAGGER_KC_CLIENT_"), + data_service=OIDCClient.from_env(prefix="DATASERVICE_KC_CLIENT_"), ) def to_list(self) -> List[Dict[str, Any]]: @@ -158,4 +193,5 @@ def to_list(self) -> List[Dict[str, Any]]: self.ui.to_dict(), self.notebooks.to_dict(), self.swagger.to_dict(), + self.data_service.to_dict(), ] From e4053c5d35265e377107c30f57aac45600ca1f2c Mon Sep 17 00:00:00 2001 From: Tasko Olevski Date: Tue, 21 Nov 2023 11:36:07 +0100 Subject: [PATCH 02/16] chore: add service account roles --- .../templates/setup-job-keycloak-realms.yaml | 2 + scripts/init-realm/init-realm.py | 44 +++++++++++++++---- scripts/init-realm/utils.py | 6 +++ 3 files changed, 43 insertions(+), 9 deletions(-) diff --git a/helm-chart/renku/templates/setup-job-keycloak-realms.yaml b/helm-chart/renku/templates/setup-job-keycloak-realms.yaml index b1a8bfd4ca..ecc0c91f5c 100644 --- a/helm-chart/renku/templates/setup-job-keycloak-realms.yaml +++ b/helm-chart/renku/templates/setup-job-keycloak-realms.yaml @@ -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 }} diff --git a/scripts/init-realm/init-realm.py b/scripts/init-realm/init-realm.py index ea784e6a9c..19f6121b58 100644 --- a/scripts/init-realm/init-realm.py +++ b/scripts/init-realm/init-realm.py @@ -30,7 +30,7 @@ KeycloakPostError, ) -from utils import DemoUserConfig, OIDCClientsConfig, OIDCGitlabClient +from utils import DemoUserConfig, OIDCClientsConfig, OIDCGitlabClient, OIDCClient, OAuthFlow logging.basicConfig(level=logging.INFO) @@ -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 @@ -99,9 +99,26 @@ 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']}'...") @@ -109,12 +126,21 @@ def _check_and_create_client(keycloak_admin, new_client, force: bool): 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") @@ -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) diff --git a/scripts/init-realm/utils.py b/scripts/init-realm/utils.py index 4e8226425e..b5d2c322f8 100644 --- a/scripts/init-realm/utils.py +++ b/scripts/init-realm/utils.py @@ -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): @@ -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 = { @@ -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", "[]")), ) From 5fd0f90d8cc2f2e18ee299d70db081ceab011f75 Mon Sep 17 00:00:00 2001 From: Tasko Olevski Date: Tue, 21 Nov 2023 11:40:53 +0100 Subject: [PATCH 03/16] squashme: update to python 3.10 --- scripts/init-realm/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/init-realm/Dockerfile b/scripts/init-realm/Dockerfile index 2fa09059cf..8ed86a2417 100644 --- a/scripts/init-realm/Dockerfile +++ b/scripts/init-realm/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.9-alpine +FROM python:3.10-alpine COPY requirements.txt init-realm.py utils.py /app/ WORKDIR /app From db463c07aa4f022413f4bd02d83a65e7d2b3c133 Mon Sep 17 00:00:00 2001 From: Tasko Olevski Date: Tue, 21 Nov 2023 22:50:34 +0100 Subject: [PATCH 04/16] squashme: minor fix --- .../templates/setup-job-keycloak-realms.yaml | 4 +- scripts/init-realm/init-realm.py | 29 +++-- scripts/init-realm/utils.py | 118 ++++++++++++------ 3 files changed, 99 insertions(+), 52 deletions(-) diff --git a/helm-chart/renku/templates/setup-job-keycloak-realms.yaml b/helm-chart/renku/templates/setup-job-keycloak-realms.yaml index ecc0c91f5c..84735d221c 100644 --- a/helm-chart/renku/templates/setup-job-keycloak-realms.yaml +++ b/helm-chart/renku/templates/setup-job-keycloak-realms.yaml @@ -100,7 +100,7 @@ spec: - name: CLI_KC_CLIENT_ATTRIBUTES value: '{"access.token.lifespan": "86400", "oauth2.device.authorization.grant.enabled": true, "oauth2.device.polling.interval": "5"}' - name: CLI_KC_CLIENT_OAUTH_FLOW - value: "device_code" + value: "device" - name: UI_KC_CLIENT_ID value: "renku-ui" - name: UI_KC_CLIENT_SECRET @@ -127,6 +127,8 @@ spec: value: swagger - name: SWAGGER_KC_CLIENT_PUBLIC value: "true" + - name: SWAGGER_KC_CLIENT_OAUTH_FLOW + value: "authorization_code" - name: SWAGGER_KC_CLIENT_ATTRIBUTES value: '{"pkce.code.challenge.method": "S256"}' - name: DATASERVICE_KC_CLIENT_ID diff --git a/scripts/init-realm/init-realm.py b/scripts/init-realm/init-realm.py index 19f6121b58..d6e568b259 100644 --- a/scripts/init-realm/init-realm.py +++ b/scripts/init-realm/init-realm.py @@ -21,6 +21,7 @@ import json import time import logging +import os from typing import Dict, List from keycloak import KeycloakAdmin @@ -116,7 +117,7 @@ def _check_and_create_client(keycloak_admin, new_client: OIDCClient, force: bool raise if set(existing_roles) != set(new_client.service_account_roles): roles_changed = True - changed = _check_existing(realm_client, new_client, "client", "clientId") + changed = _check_existing(realm_client, new_client.to_dict(), "client", "clientId") if not force or (not changed and not roles_changed): return @@ -124,22 +125,28 @@ def _check_and_create_client(keycloak_admin, new_client: OIDCClient, force: bool logging.info(f"Recreating modified client '{realm_client['clientId']}'...") keycloak_admin.delete_client(realm_client["id"]) - keycloak_admin.create_client(new_client) + created_client_id = keycloak_admin.create_client(new_client.to_dict()) - if isinstance(service_account_user) and service_account_user.get("id"): + if isinstance(service_account_user, dict) 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) + realm_management_client_id = keycloak_admin.get_client_id("realm-management") + realm_management_roles = keycloak_admin.get_client_roles(realm_management_client_id) + matching_roles = [{"name": role["name"], "id": role["id"]} for role in realm_management_roles if role["name"] in new_client.service_account_roles ] + keycloak_admin.assign_client_role(service_account_user["id"], realm_management_client_id, matching_roles) logging.info("done") else: logging.info("not found") - logging.info("Creating {} client...".format(new_client["clientId"])) - keycloak_admin.create_client(new_client) + logging.info("Creating {} client...".format(new_client.id)) + created_client_id = keycloak_admin.create_client(new_client.to_dict()) 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) + service_account_user = keycloak_admin.get_client_service_account_user(created_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) + realm_management_client_id = keycloak_admin.get_client_id("realm-management") + realm_management_roles = keycloak_admin.get_client_roles(realm_management_client_id) + matching_roles = [{"name": role["name"], "id": role["id"]} for role in realm_management_roles if role["name"] in new_client.service_account_roles ] + keycloak_admin.assign_client_role(service_account_user["id"], realm_management_client_id, matching_roles) logging.info("done") @@ -264,11 +271,11 @@ def _check_and_create_user(keycloak_admin, new_user): keycloak_admin.connection.realm_name = args.realm -for client in OIDCClientsConfig.from_env(): +for client in OIDCClientsConfig.from_env().to_list(): _check_and_create_client(keycloak_admin, client, args.force) -gitlab_oidc_client = OIDCGitlabClient.from_env() -if gitlab_oidc_client is not None: +if os.environ.get("INTERNAL_GITLAB_ENABLED", "false").lower() == "true": + gitlab_oidc_client = OIDCGitlabClient.from_env() _check_and_create_client(keycloak_admin, gitlab_oidc_client, args.force) # Create renku-admin realm role diff --git a/scripts/init-realm/utils.py b/scripts/init-realm/utils.py index b5d2c322f8..6e3ec0acf9 100644 --- a/scripts/init-realm/utils.py +++ b/scripts/init-realm/utils.py @@ -1,5 +1,6 @@ import json import os +from copy import deepcopy from dataclasses import dataclass, field from enum import Enum from typing import Any, Dict, List, Optional @@ -42,17 +43,25 @@ class OAuthFlow(Enum): authorization_code: str = "authorization_code" client_credentials: str = "client_credentials" - def get_keycloak_payload(self, disable_other_flows: bool = True) -> Dict[str, Any]: - output = { - "oauth2DeviceAuthorizationGrantEnabled": False, - "serviceAccountsEnabled": False, - "standardFlowEnabled": False, - } if disable_other_flows else {} + def get_keycloak_payload( + self, + existing_payload: Dict[str, Any] | None = None, + disable_other_flows: bool = True + ) -> Dict[str, Any]: + output = deepcopy(existing_payload) if existing_payload else {} + if disable_other_flows: + output.update( + serviceAccountsEnabled=False, + standardFlowEnabled=False, + ) match self: case OAuthFlow.authorization_code: output["standardFlowEnabled"] = True case OAuthFlow.device: - output["oauth2DeviceAuthorizationGrantEnabled"] = True + if isinstance(output.get("attributes"), dict): + output["attributes"]["oauth2.device.authorization.grant.enabled"] = True + else: + output["attributes"] = {"oauth2.device.authorization.grant.enabled": True} case OAuthFlow.client_credentials: output["serviceAccountsEnabled"] = True return output @@ -90,6 +99,49 @@ def __post_init__(self): ) def to_dict(self) -> Dict[str, Any]: + default_protocol_mappers = [] + if self.oauth_flow == OAuthFlow.client_credentials: + default_protocol_mappers.extend([ + { + "name": "Client ID", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": False, + "config": { + "user.session.note": "clientId", + "id.token.claim": True, + "access.token.claim": True, + "claim.name": "clientId", + "jsonType.label": "String" + } + }, + { + "name": "Client Host", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": False, + "config": { + "user.session.note": "clientHost", + "id.token.claim": True, + "access.token.claim": True, + "claim.name": "clientHost", + "jsonType.label": "String" + } + }, + { + "name": "Client IP Address", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": False, + "config": { + "user.session.note": "clientAddress", + "id.token.claim": True, + "access.token.claim": True, + "claim.name": "clientAddress", + "jsonType.label": "String" + } + } + ]) output = { "clientId": self.id, "baseUrl": self.base_url, @@ -97,7 +149,7 @@ def to_dict(self) -> Dict[str, Any]: "attributes": self.attributes, "redirectUris": [self.base_url + "/*"], "webOrigins": [self.base_url + "/*"], - "protocolMappers": [ + "protocolMappers": default_protocol_mappers + [ { "name": "renku audience for renku cli", "protocol": "openid-connect", @@ -114,7 +166,7 @@ def to_dict(self) -> Dict[str, Any]: } if self.secret is not None: output["secret"] = self.secret - output.update(**self.oauth_flow.get_keycloak_payload(self.disable_other_oauth_flows)) + output = self.oauth_flow.get_keycloak_payload(output, self.disable_other_oauth_flows) return output @classmethod @@ -134,39 +186,25 @@ def from_env(cls, prefix: str = "RENKU_KC_CLIENT_") -> "OIDCClient": @dataclass -class OIDCGitlabClient: - """A Keycloak OIDC client used by the internal Renku Gitlab deployment (if this deployment is enabled).""" - - internal_gitlab_enabled: bool = False - oidc_client_secret: Optional[str] = field(default=None, repr=False) - oidc_client_id: str = "gitlab" - renku_base_url: Optional[str] = None - - def __post_init__(self): - if self.internal_gitlab_enabled and not (self.oidc_client_secret or self.renku_base_url): - raise ValueError( - "The internal Gitlab is enabled, but the Renku base URL and/or the Keycloak OIDC client secret are not defined." - ) - self.renku_base_url = self.renku_base_url.rstrip("/") +class OIDCGitlabClient(OIDCClient): + """A Keycloak OIDC client used by the internal Renku Gitlab deployment.""" @classmethod def from_env(cls, prefix: str = "INTERNAL_GITLAB_") -> "OIDCGitlabClient": return cls( - internal_gitlab_enabled=os.environ.get(f"{prefix}ENABLED", "false").lower() == "true", - oidc_client_secret=os.environ.get(f"{prefix}OIDC_CLIENT_SECRET"), - oidc_client_id=os.environ.get(f"{prefix}OIDC_CLIENT_ID", "gitlab"), - renku_base_url=os.environ.get(f"RENKU_BASE_URL"), + secret=os.environ.get(f"{prefix}OIDC_CLIENT_SECRET"), + id=os.environ.get(f"{prefix}OIDC_CLIENT_ID", "gitlab"), + base_url=os.environ.get("RENKU_BASE_URL"), + oauth_flow=OAuthFlow.authorization_code, ) def to_dict(self) -> Optional[Dict[str, Any]]: - if not self.internal_gitlab_enabled: - return None return { - "clientId": self.oidc_client_id, - "baseUrl": f"{self.renku_base_url}/gitlab", - "secret": self.oidc_client_secret, + "clientId": self.id, + "baseUrl": f"{self.base_url}", + "secret": self.secret, "redirectUris": [ - f"{self.renku_base_url}/gitlab/users/auth/oauth2_generic/callback", + f"{self.base_url}/users/auth/oauth2_generic/callback", ], "webOrigins": [], } @@ -192,12 +230,12 @@ def from_env(cls) -> "OIDCClientsConfig": data_service=OIDCClient.from_env(prefix="DATASERVICE_KC_CLIENT_"), ) - def to_list(self) -> List[Dict[str, Any]]: + def to_list(self) -> List[OIDCClient]: return [ - self.renku.to_dict(), - self.cli.to_dict(), - self.ui.to_dict(), - self.notebooks.to_dict(), - self.swagger.to_dict(), - self.data_service.to_dict(), + self.renku, + self.cli, + self.ui, + self.notebooks, + self.swagger, + self.data_service, ] From cc50c91bcb217def0f9b080b91ef3622ae2a5b2e Mon Sep 17 00:00:00 2001 From: Tasko Olevski Date: Wed, 22 Nov 2023 00:30:18 +0100 Subject: [PATCH 05/16] chore: squashme minor changes --- scripts/init-realm/init-realm.py | 12 ++++++++---- scripts/init-realm/utils.py | 3 +++ 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/scripts/init-realm/init-realm.py b/scripts/init-realm/init-realm.py index d6e568b259..c57dda83a0 100644 --- a/scripts/init-realm/init-realm.py +++ b/scripts/init-realm/init-realm.py @@ -80,6 +80,7 @@ def _check_and_create_client(keycloak_admin, new_client: OIDCClient, force: bool 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] + realm_management_client_id = keycloak_admin.get_client_id("realm-management") if new_client.id in client_ids: logging.info("found") realm_client = realm_clients[client_ids.index(new_client.id)] @@ -105,17 +106,19 @@ def _check_and_create_client(keycloak_admin, new_client: OIDCClient, force: bool existing_roles = [] if new_client.oauth_flow == OAuthFlow.client_credentials: try: - service_account_user = keycloak_admin.get_client_service_account_user(new_client.id) + service_account_user = keycloak_admin.get_client_service_account_user(realm_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", []) + existing_roles = keycloak_admin.get_client_roles_of_user(service_account_user["id"], realm_management_client_id) except KeycloakGetError as err: if err.response_code != 404: raise - if set(existing_roles) != set(new_client.service_account_roles): + existing_roles_names = [role["name"] for role in existing_roles] + if set(existing_roles_names) != set(new_client.service_account_roles): + logging.warning(f"Roles changed existing roles {set(existing_roles_names)} != new roles {set(new_client.service_account_roles)}") roles_changed = True changed = _check_existing(realm_client, new_client.to_dict(), "client", "clientId") @@ -129,9 +132,9 @@ def _check_and_create_client(keycloak_admin, new_client: OIDCClient, force: bool if isinstance(service_account_user, dict) and service_account_user.get("id"): logging.info(f"Reassigning service account roles {new_client.service_account_roles}") - realm_management_client_id = keycloak_admin.get_client_id("realm-management") realm_management_roles = keycloak_admin.get_client_roles(realm_management_client_id) matching_roles = [{"name": role["name"], "id": role["id"]} for role in realm_management_roles if role["name"] in new_client.service_account_roles ] + logging.info(f"Found and assigning matching roles: {matching_roles}") keycloak_admin.assign_client_role(service_account_user["id"], realm_management_client_id, matching_roles) logging.info("done") @@ -146,6 +149,7 @@ def _check_and_create_client(keycloak_admin, new_client: OIDCClient, force: bool realm_management_client_id = keycloak_admin.get_client_id("realm-management") realm_management_roles = keycloak_admin.get_client_roles(realm_management_client_id) matching_roles = [{"name": role["name"], "id": role["id"]} for role in realm_management_roles if role["name"] in new_client.service_account_roles ] + logging.info(f"Found and assigning matching roles: {matching_roles}") keycloak_admin.assign_client_role(service_account_user["id"], realm_management_client_id, matching_roles) logging.info("done") diff --git a/scripts/init-realm/utils.py b/scripts/init-realm/utils.py index 6e3ec0acf9..63501d58bf 100644 --- a/scripts/init-realm/utils.py +++ b/scripts/init-realm/utils.py @@ -111,6 +111,7 @@ def to_dict(self) -> Dict[str, Any]: "user.session.note": "clientId", "id.token.claim": True, "access.token.claim": True, + "userinfo.token.claim": True, "claim.name": "clientId", "jsonType.label": "String" } @@ -124,6 +125,7 @@ def to_dict(self) -> Dict[str, Any]: "user.session.note": "clientHost", "id.token.claim": True, "access.token.claim": True, + "userinfo.token.claim": True, "claim.name": "clientHost", "jsonType.label": "String" } @@ -137,6 +139,7 @@ def to_dict(self) -> Dict[str, Any]: "user.session.note": "clientAddress", "id.token.claim": True, "access.token.claim": True, + "userinfo.token.claim": True, "claim.name": "clientAddress", "jsonType.label": "String" } From f89ca945d9063e7bae6b4e325b512bd266cb2720 Mon Sep 17 00:00:00 2001 From: Tasko Olevski Date: Wed, 22 Nov 2023 17:20:34 +0100 Subject: [PATCH 06/16] squashme: enable event tracking in keycloak --- scripts/init-realm/init-realm.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/scripts/init-realm/init-realm.py b/scripts/init-realm/init-realm.py index c57dda83a0..fa63ddb2c5 100644 --- a/scripts/init-realm/init-realm.py +++ b/scripts/init-realm/init-realm.py @@ -271,6 +271,26 @@ def _check_and_create_user(keycloak_admin, new_user): ) logging.info("done") +realm = keycloak_admin.get_realm(args.realm) +event_retention_seconds = 86400 +if not realm.get("eventsEnabled"): + logging.info( + f"Enabling user events tracking for realm with retention {event_retention_seconds}" + ) + keycloak_admin.update_realm(args.realm, {"eventsEnabled": False, "eventsExpiration": event_retention_seconds}) +if not realm.get("adminEventsEnabled"): + logging.info( + f"Enabling admin events tracking for realm with retention {event_retention_seconds}" + ) + keycloak_admin.update_realm( + args.realm, + { + "adminEventsEnabled": True, + "adminEventsDetailsEnabled": True, + "attributes": {"adminEventsExpiration": event_retention_seconds}, + }, + ) + # Switching to the newly created realm keycloak_admin.connection.realm_name = args.realm From 54d2411fc0df820a90de1501164846905a23d7ea Mon Sep 17 00:00:00 2001 From: Tasko Olevski Date: Fri, 24 Nov 2023 14:39:59 +0100 Subject: [PATCH 07/16] chore: add cronjobs --- .../keycloak-users-sync-cronjob.yaml | 99 +++++++++++++++++++ helm-chart/renku/values.yaml | 5 + 2 files changed, 104 insertions(+) create mode 100644 helm-chart/renku/templates/keycloak-users-sync-cronjob.yaml diff --git a/helm-chart/renku/templates/keycloak-users-sync-cronjob.yaml b/helm-chart/renku/templates/keycloak-users-sync-cronjob.yaml new file mode 100644 index 0000000000..e9eeabd1a7 --- /dev/null +++ b/helm-chart/renku/templates/keycloak-users-sync-cronjob.yaml @@ -0,0 +1,99 @@ +apiVersion: batch/v1 +kind: CronJob +metadata: + name: {{ include "renku.fullname" . }}-keycloak-sync-events +spec: + schedule: "*/2 * * * *" + concurrencyPolicy: Forbid + jobTemplate: + spec: + successfulJobsHistoryLimit: 1 + failedJobsHistoryLimit: 3 + concurrencyPolicy: Forbid + template: + spec: + initContainers: + {{- include "certificates.initContainer" . | nindent 12 }} + containers: + - name: keycloak-sync + image: "{{ .Values.dataService.keycloakSync.image.repository }}:{{ .Values.dataService.keycloakSync.image.tag }}" + imagePullPolicy: IfNotPresent + env: + - name: DB_HOST + value: {{ template "postgresql.fullname" . }} + - name: DB_USER + value: {{ .Values.global.db.common.username }} + - name: DB_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Values.global.db.common.passwordSecretName }} + key: password + - name: KEYCLOAK_URL + value: {{ include "renku.keycloakUrl" . | quote}} + - name: KEYCLOAK_REALM + value: {{ include "renku.keycloak.realm" . | quote}} + - name: KEYCLOAK_CLIENT_ID + value: {{ include "renku.dataService.keycloak.clientId" . | quote }} + - name: KEYCLOAK_CLIENT_SECRET + valueFrom: + secretKeyRef: + name: {{ include "renku.fullname" . }} + key: dataServiceKeycloakClientSecret + - name: TOTAL_USER_SYNC + value: "false" + {{- include "certificates.env.python" . | nindent 16 }} + volumeMounts: + {{- include "certificates.volumeMounts.python" . | nindent 16 }} + restartPolicy: Never + volumes: + {{- include "certificates.volumes" . | nindent 12 }} +--- +apiVersion: batch/v1 +kind: CronJob +metadata: + name: {{ include "renku.fullname" . }}-keycloak-sync-total +spec: + schedule: "0 3 * * *" + concurrencyPolicy: Forbid + jobTemplate: + spec: + successfulJobsHistoryLimit: 1 + failedJobsHistoryLimit: 3 + concurrencyPolicy: Forbid + template: + spec: + initContainers: + {{- include "certificates.initContainer" . | nindent 12 }} + containers: + - name: keycloak-sync + image: "{{ .Values.dataService.keycloakSync.image.repository }}:{{ .Values.dataService.keycloakSync.image.tag }}" + imagePullPolicy: IfNotPresent + env: + - name: DB_HOST + value: {{ template "postgresql.fullname" . }} + - name: DB_USER + value: {{ .Values.global.db.common.username }} + - name: DB_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Values.global.db.common.passwordSecretName }} + key: password + - name: KEYCLOAK_URL + value: {{ include "renku.keycloakUrl" . | quote}} + - name: KEYCLOAK_REALM + value: {{ include "renku.keycloak.realm" . | quote}} + - name: KEYCLOAK_CLIENT_ID + value: {{ include "renku.dataService.keycloak.clientId" . | quote }} + - name: KEYCLOAK_CLIENT_SECRET + valueFrom: + secretKeyRef: + name: {{ include "renku.fullname" . }} + key: dataServiceKeycloakClientSecret + - name: TOTAL_USER_SYNC + value: "true" + {{- include "certificates.env.python" . | nindent 16 }} + volumeMounts: + {{- include "certificates.volumeMounts.python" . | nindent 16 }} + restartPolicy: Never + volumes: + {{- include "certificates.volumes" . | nindent 12 }} diff --git a/helm-chart/renku/values.yaml b/helm-chart/renku/values.yaml index f7c2e059ad..4cc165bfc5 100644 --- a/helm-chart/renku/values.yaml +++ b/helm-chart/renku/values.yaml @@ -1497,6 +1497,11 @@ dataService: repository: renku/renku-data-service tag: "0.3.0" pullPolicy: IfNotPresent + keycloakSync: + image: + repository: renku/keycloak-sync + tag: "0.2.2" + pullPolicy: IfNotPresent service: type: ClusterIP port: 80 From cf450b9f3ab087214fc1ba0237ad96e5240c87b7 Mon Sep 17 00:00:00 2001 From: Tasko Olevski Date: Fri, 24 Nov 2023 14:47:55 +0100 Subject: [PATCH 08/16] squashme: minor fix --- .../templates/keycloak-users-sync-cronjob.yaml | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/helm-chart/renku/templates/keycloak-users-sync-cronjob.yaml b/helm-chart/renku/templates/keycloak-users-sync-cronjob.yaml index e9eeabd1a7..4ac04e18d9 100644 --- a/helm-chart/renku/templates/keycloak-users-sync-cronjob.yaml +++ b/helm-chart/renku/templates/keycloak-users-sync-cronjob.yaml @@ -5,11 +5,10 @@ metadata: spec: schedule: "*/2 * * * *" concurrencyPolicy: Forbid + successfulJobsHistoryLimit: 1 + failedJobsHistoryLimit: 3 jobTemplate: spec: - successfulJobsHistoryLimit: 1 - failedJobsHistoryLimit: 3 - concurrencyPolicy: Forbid template: spec: initContainers: @@ -43,7 +42,7 @@ spec: value: "false" {{- include "certificates.env.python" . | nindent 16 }} volumeMounts: - {{- include "certificates.volumeMounts.python" . | nindent 16 }} + {{- include "certificates.volumeMounts.system" . | nindent 16 }} restartPolicy: Never volumes: {{- include "certificates.volumes" . | nindent 12 }} @@ -55,11 +54,10 @@ metadata: spec: schedule: "0 3 * * *" concurrencyPolicy: Forbid + successfulJobsHistoryLimit: 1 + failedJobsHistoryLimit: 3 jobTemplate: spec: - successfulJobsHistoryLimit: 1 - failedJobsHistoryLimit: 3 - concurrencyPolicy: Forbid template: spec: initContainers: @@ -93,7 +91,7 @@ spec: value: "true" {{- include "certificates.env.python" . | nindent 16 }} volumeMounts: - {{- include "certificates.volumeMounts.python" . | nindent 16 }} + {{- include "certificates.volumeMounts.system" . | nindent 16 }} restartPolicy: Never volumes: {{- include "certificates.volumes" . | nindent 12 }} From 37748690ca0937a09fb49217f4fd65f526185db4 Mon Sep 17 00:00:00 2001 From: Tasko Olevski Date: Fri, 24 Nov 2023 16:46:08 +0100 Subject: [PATCH 09/16] squashme: modify network policy --- .../renku/templates/keycloak-users-sync-cronjob.yaml | 10 ++++++++++ helm-chart/renku/templates/network-policies.yaml | 6 ++++++ 2 files changed, 16 insertions(+) diff --git a/helm-chart/renku/templates/keycloak-users-sync-cronjob.yaml b/helm-chart/renku/templates/keycloak-users-sync-cronjob.yaml index 4ac04e18d9..cfd6b1bec0 100644 --- a/helm-chart/renku/templates/keycloak-users-sync-cronjob.yaml +++ b/helm-chart/renku/templates/keycloak-users-sync-cronjob.yaml @@ -2,6 +2,11 @@ apiVersion: batch/v1 kind: CronJob metadata: name: {{ include "renku.fullname" . }}-keycloak-sync-events + labels: + app: keycloak-sync + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + chart: {{ template "renku.chart" . }} spec: schedule: "*/2 * * * *" concurrencyPolicy: Forbid @@ -51,6 +56,11 @@ apiVersion: batch/v1 kind: CronJob metadata: name: {{ include "renku.fullname" . }}-keycloak-sync-total + labels: + app: keycloak-sync + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + chart: {{ template "renku.chart" . }} spec: schedule: "0 3 * * *" concurrencyPolicy: Forbid diff --git a/helm-chart/renku/templates/network-policies.yaml b/helm-chart/renku/templates/network-policies.yaml index b2c77647ef..431f5fbd6d 100644 --- a/helm-chart/renku/templates/network-policies.yaml +++ b/helm-chart/renku/templates/network-policies.yaml @@ -69,6 +69,12 @@ spec: namespaceSelector: matchLabels: kubernetes.io/metadata.name: {{ .Release.Namespace }} + - podSelector: + matchLabels: + app: keycloak-sync + namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: {{ .Release.Namespace }} ports: - protocol: TCP port: 5432 From ed5ed5da88142288d6534da9b0a6728eaf068b57 Mon Sep 17 00:00:00 2001 From: Tasko Olevski Date: Fri, 24 Nov 2023 17:04:20 +0100 Subject: [PATCH 10/16] squashme: minor fix --- helm-chart/renku/templates/keycloak-users-sync-cronjob.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/helm-chart/renku/templates/keycloak-users-sync-cronjob.yaml b/helm-chart/renku/templates/keycloak-users-sync-cronjob.yaml index cfd6b1bec0..c109f4875a 100644 --- a/helm-chart/renku/templates/keycloak-users-sync-cronjob.yaml +++ b/helm-chart/renku/templates/keycloak-users-sync-cronjob.yaml @@ -15,6 +15,9 @@ spec: jobTemplate: spec: template: + metadata: + labels: + app: keycloak-sync spec: initContainers: {{- include "certificates.initContainer" . | nindent 12 }} @@ -69,6 +72,9 @@ spec: jobTemplate: spec: template: + metadata: + labels: + app: keycloak-sync spec: initContainers: {{- include "certificates.initContainer" . | nindent 12 }} From 26267d0e8570945702a91889e61dc00040711619 Mon Sep 17 00:00:00 2001 From: Tasko Olevski Date: Sat, 25 Nov 2023 15:00:33 +0100 Subject: [PATCH 11/16] squashme: minor fix --- scripts/init-realm/init-realm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/init-realm/init-realm.py b/scripts/init-realm/init-realm.py index fa63ddb2c5..ec3bac706c 100644 --- a/scripts/init-realm/init-realm.py +++ b/scripts/init-realm/init-realm.py @@ -277,7 +277,7 @@ def _check_and_create_user(keycloak_admin, new_user): logging.info( f"Enabling user events tracking for realm with retention {event_retention_seconds}" ) - keycloak_admin.update_realm(args.realm, {"eventsEnabled": False, "eventsExpiration": event_retention_seconds}) + keycloak_admin.update_realm(args.realm, {"eventsEnabled": True, "eventsExpiration": event_retention_seconds}) if not realm.get("adminEventsEnabled"): logging.info( f"Enabling admin events tracking for realm with retention {event_retention_seconds}" From 9726fd40205f75bcc9b2191dd81c983e034d32c1 Mon Sep 17 00:00:00 2001 From: Tasko Olevski Date: Mon, 27 Nov 2023 09:51:07 +0100 Subject: [PATCH 12/16] squashme: minor fix --- helm-chart/renku/templates/data-service/deployment.yaml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/helm-chart/renku/templates/data-service/deployment.yaml b/helm-chart/renku/templates/data-service/deployment.yaml index edfd12883c..ea8f827b2f 100644 --- a/helm-chart/renku/templates/data-service/deployment.yaml +++ b/helm-chart/renku/templates/data-service/deployment.yaml @@ -56,6 +56,13 @@ spec: value: {{ (printf "%s://%s/auth/" (include "renku.http" .) .Values.global.renku.domain) | quote }} - name: KEYCLOAK_TOKEN_SIGNATURE_ALGS value: "RS256" + - name: KEYCLOAK_CLIENT_ID + value: {{ include "renku.dataService.keycloak.clientId" . | quote }} + - name: KEYCLOAK_CLIENT_SECRET + valueFrom: + secretKeyRef: + name: {{ include "renku.fullname" . }} + key: dataServiceKeycloakClientSecret - name: SERVER_DEFAULTS value: /etc/renku-data-service/server_options/server_defaults.json - name: SERVER_OPTIONS From d6b10e7cc6ea1cb2c1d3704086df72ac781625a9 Mon Sep 17 00:00:00 2001 From: Tasko Olevski Date: Wed, 28 Feb 2024 15:13:48 +0100 Subject: [PATCH 13/16] fix: secrets rendering --- chartpress.yaml | 1 + helm-chart/renku/templates/secrets.yaml | 11 ++++++++--- helm-chart/renku/values.yaml | 4 ++-- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/chartpress.yaml b/chartpress.yaml index cc91c46588..1b55b12f21 100644 --- a/chartpress.yaml +++ b/chartpress.yaml @@ -19,6 +19,7 @@ charts: - helm-chart - acceptance-tests - scripts/init-realm + - scripts/init-db images: tests: buildArgs: diff --git a/helm-chart/renku/templates/secrets.yaml b/helm-chart/renku/templates/secrets.yaml index c9b8a42110..e8bd0640bb 100644 --- a/helm-chart/renku/templates/secrets.yaml +++ b/helm-chart/renku/templates/secrets.yaml @@ -1,9 +1,12 @@ --- -{{- $data_service_kc_client_secret := randAlphaNum 64 | b64enc | quote }} +{{- $data_service_kc_client_secret := (randAlphaNum 64) | b64enc | quote }} {{- $renku_secret := lookup "v1" "Secret" .Release.Namespace (include "renku.fullname" .) }} -{{- if $renku_secret }} -{{- $data_service_kc_client_secret = index $renku_secret.data "dataServiceKeycloakClientSecret" }} +{{- if and $renku_secret $renku_secret.data }} +{{- $data_service_kc_client_secret_test := index $renku_secret.data "dataServiceKeycloakClientSecret" }} +{{- if $data_service_kc_client_secret_test }} +{{- $data_service_kc_client_secret = $data_service_kc_client_secret_test }} +{{- end -}} {{- end -}} apiVersion: v1 @@ -16,6 +19,8 @@ metadata: release: {{ .Release.Name }} heritage: {{ .Release.Service }} annotations: + # If "keep" resource policy is removed the secret is deleted post upgrade see https://github.com/helm/helm/issues/8420 + "helm.sh/resource-policy": keep "helm.sh/hook": "pre-install,pre-upgrade,pre-rollback" type: Opaque data: diff --git a/helm-chart/renku/values.yaml b/helm-chart/renku/values.yaml index 446db1fd48..c6834ecbb2 100644 --- a/helm-chart/renku/values.yaml +++ b/helm-chart/renku/values.yaml @@ -1480,12 +1480,12 @@ initDb: dataService: image: repository: renku/renku-data-service - tag: "0.4.0" + tag: "0.5.0" pullPolicy: IfNotPresent keycloakSync: image: repository: renku/keycloak-sync - tag: "0.2.2" + tag: "0.5.0" pullPolicy: IfNotPresent service: type: ClusterIP From a858a41e9f85b7aa7e99d6f9ef92a988e5c22c1a Mon Sep 17 00:00:00 2001 From: Tasko Olevski Date: Wed, 28 Feb 2024 17:44:07 +0100 Subject: [PATCH 14/16] squashme: update changelog --- CHANGELOG.rst | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 96bef26669..a8ea12d115 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -3,7 +3,9 @@ 0.49.0 ------ -This release contains bug fixes to renku core service related to project migration. +This release adds initial support for Renku native projects. Please note that the Renku native +projects feature is still being developed and it is not accessible to users only to Renku developers. +The release also contains bug fixes to renku core service related to project migration. User-Facing Changes ~~~~~~~~~~~~~~~~~~~ @@ -12,10 +14,18 @@ User-Facing Changes - **Core Service**: Fix issue with having to run project migration twice to migrate the Dockerfile/project template. +Internal Changes +~~~~~~~~~~~~~~~~ + +**New Features** + +- **Data services**: Initial support for Renku native projects + Individual Components ~~~~~~~~~~~~~~~~~~~~~ - `renku-python 2.9.2 `_ +- `renku-data-services 0.5.0 `_ 0.48.1 ------ From e5760111b4734a8bbdb67d5c7450218d798c134c Mon Sep 17 00:00:00 2001 From: Jakub Chrobasik Date: Thu, 29 Feb 2024 11:26:08 +0100 Subject: [PATCH 15/16] chore: a fake commit to tests README.md --- acceptance-tests/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/acceptance-tests/README.md b/acceptance-tests/README.md index daa66cce9b..e2f231b8c7 100644 --- a/acceptance-tests/README.md +++ b/acceptance-tests/README.md @@ -204,7 +204,6 @@ The test are built using the Page Object Pattern (e.g. https://www.pluralsight.com/guides/getting-started-with-page-object-pattern-for-your-selenium-tests) which in short is about wrapping an UI page into a class/object and using it in the test script. - As mentioned above there's a `target/tests-execution.log` file where tests debug statements from tests execution are written. ## Project organization From 8a8191f9e7deeafe29f2d35a9f6a534142c9369d Mon Sep 17 00:00:00 2001 From: Tasko Olevski Date: Mon, 4 Mar 2024 09:31:34 +0100 Subject: [PATCH 16/16] squashme: accept code suggestions Co-authored-by: Laura --- CHANGELOG.rst | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index a8ea12d115..3d77da89c8 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -3,9 +3,11 @@ 0.49.0 ------ -This release adds initial support for Renku native projects. Please note that the Renku native -projects feature is still being developed and it is not accessible to users only to Renku developers. -The release also contains bug fixes to renku core service related to project migration. +The release contains bug fixes to renku core service related to project migration. + +This release also contains initial support for next generation 'Renku 1.0' functionality. However, +Renku 1.0 is still in early development and is not yet accessible to users. For more information, +see our [roadmap](https://github.com/SwissDataScienceCenter/renku-design-docs/blob/main/roadmap.md). User-Facing Changes ~~~~~~~~~~~~~~~~~~~ @@ -19,7 +21,7 @@ Internal Changes **New Features** -- **Data services**: Initial support for Renku native projects +- **Data services**: Initial support for Renku 1.0 projects (alpha release) Individual Components ~~~~~~~~~~~~~~~~~~~~~