diff --git a/cypress-tests/cypress/e2e/v2/dataConnectors.cy.ts b/cypress-tests/cypress/e2e/v2/dataConnectors.cy.ts index 03709c75ae..d68e773c74 100644 --- a/cypress-tests/cypress/e2e/v2/dataConnectors.cy.ts +++ b/cypress-tests/cypress/e2e/v2/dataConnectors.cy.ts @@ -10,8 +10,11 @@ import { deleteDataConnector, } from "../../support/utils/dataConnectors"; import { login } from "../../support/utils/general"; +import { verifySearchIndexing } from "../../support/utils/search"; const sessionId = ["dataConnectors", getRandomString()]; +const searchDataConnectorType = "type:DataConnector"; +const searchDataConnectorSlug = "slug:"; describe("Data Connectors", () => { const randomString = getRandomString(); @@ -19,7 +22,7 @@ describe("Data Connectors", () => { const projectSlug = `project-for-data-connector-tests-${randomString}`; let userNamespace: string; let dataConnectorName: string; - let projectId: string; + let projectId: string | undefined; let groupName: string; let groupSlug: string; @@ -43,7 +46,7 @@ describe("Data Connectors", () => { }); after(() => { - deleteProject(projectId); + if (projectId) deleteProject(projectId); }); beforeEach(() => { @@ -121,8 +124,6 @@ describe("Data Connectors", () => { cy.getDataCy("data-connector-menu-dropdown").click(); cy.getDataCy("data-connector-delete").click(); }); - - // Confirm deletion by typing the slug cy.getDataCy("delete-confirmation-input").type(dataConnectorName); cy.getDataCy("delete-data-connector-modal-button").click(); @@ -248,7 +249,6 @@ describe("Data Connectors", () => { projectId, ); - // ? Currently, data connectors newly linked might not appear immediately visitCurrentProject(); cy.getDataCy("data-connector-box") .find(`[data-cy=data-connector-name]`) @@ -279,70 +279,54 @@ describe("Data Connectors", () => { cy.getDataCy("data-connector-edit-close-button").click(); // Verify the data connector is present with the edited name - visitCurrentProject(); cy.getDataCy("data-connector-box") .find(`[data-cy=data-connector-name]`) .contains(newName); }); - it("Link an existing data connector to a project", () => { - // Create a data connector not linked to a project + it("Link and unlink an existing data connector to a project", () => { + // Create a data connector not linked to a project and check it has been indexed const dataConnectorIdentifier = `${userNamespace}/${dataConnectorName}`; createDataConnector(dataConnectorIdentifier); + verifySearchIndexing( + `${searchDataConnectorType} ${searchDataConnectorSlug}${dataConnectorName}`, + 1, + ); - // Now link the data connector to the project + // Link the data connector to the project visitCurrentProject(); cy.getDataCy("add-data-connector").click(); - cy.getDataCy("project-data-controller-mode-link").click(); - - // Enter the data connector identifier - cy.get("#data-connector-identifier") + cy.getDataCy("data-connector-search-input") .should("be.empty") .type(dataConnectorIdentifier); - cy.getDataCy("link-data-connector-button").click(); + cy.getDataCy("data-connector-search-body") + .contains("[data-cy=link-data-connector-list-item]", dataConnectorName) + .find(`[data-cy=data-connector-link-button]`) + .click(); // Verify the data connector is linked to the project - visitCurrentProject(); + cy.getDataCy("project-data-connector-connect-header") + .find('button[data-bs-dismiss="modal"]') + .click(); cy.getDataCy("data-connector-box") .find(`[data-cy=data-connector-name]`) .contains(dataConnectorName); - }); - it("Unlink a data connector from a project", () => { - // Create a data connector not linked to a project - const dataConnectorIdentifier = `${userNamespace}/${dataConnectorName}`; - createDataConnector(dataConnectorIdentifier); - - // Now link the data connector to the project - visitCurrentProject(); - cy.getDataCy("add-data-connector").click(); - cy.getDataCy("project-data-controller-mode-link").click(); - - // Enter the data connector identifier - cy.get("#data-connector-identifier") - .should("be.empty") - .type(dataConnectorIdentifier); - cy.getDataCy("link-data-connector-button").click(); - - // ? Currently, data connectors newly linked might not appear immediately - visitCurrentProject(); + // Unlink the data connector from the project cy.getDataCy("data-connector-box") .find(`[data-cy=data-connector-name]`) .contains(dataConnectorName) .click(); - cy.getDataCy("data-connector-view").within(() => { cy.getDataCy("data-connector-title") .should("be.visible") .contains(dataConnectorName); cy.getDataCy("data-connector-menu-dropdown").click(); - cy.getDataCy("data-connector-unlink").should("be.visible").click(); + cy.getDataCy("data-connector-unlink").click(); }); - cy.getDataCy("delete-data-connector-modal-button").click(); // Verify the data connector is no longer linked to the project - visitCurrentProject(); cy.getDataCy("data-connector-box") .contains(`[data-cy=data-connector-name]`, dataConnectorName) .should("not.exist"); @@ -359,31 +343,38 @@ describe("Data Connectors", () => { slug: otherProjectName, visibility: "private", }).then((response) => { - const otherProjectId = response.body.id; + const otherProjectId = response.body.id ?? ""; // Defer-delete the other project (which will also delete the data connector) cy.defer(() => { - deleteProject(otherProjectId); + if (otherProjectId) deleteProject(otherProjectId); }); - // Create a data connector in the other project + // Create a data connector in the other project and check it has been indexed const dataConnectorIdentifier = `${userNamespace}/${otherProjectName}/${dataConnectorName}`; createDataConnector(dataConnectorIdentifier, otherProjectId); + verifySearchIndexing( + `${searchDataConnectorType} ${searchDataConnectorSlug}${dataConnectorName}`, + 1, + ); // Navigate to the main project visitCurrentProject(); // Link the data connector from the other project to the main project cy.getDataCy("add-data-connector").click(); - cy.getDataCy("project-data-controller-mode-link").click(); - - cy.get("#data-connector-identifier") + cy.getDataCy("data-connector-search-input") .should("be.empty") .type(dataConnectorIdentifier); - cy.getDataCy("link-data-connector-button").click(); + cy.getDataCy("data-connector-search-body") + .contains("[data-cy=link-data-connector-list-item]", dataConnectorName) + .find(`[data-cy=data-connector-link-button]`) + .click(); // Verify the data connector is linked to the main project - visitCurrentProject(); + cy.getDataCy("project-data-connector-connect-header") + .find('button[data-bs-dismiss="modal"]') + .click(); cy.getDataCy("data-connector-box") .find(`[data-cy=data-connector-name]`) .contains(dataConnectorName) @@ -395,12 +386,11 @@ describe("Data Connectors", () => { .should("be.visible") .contains(dataConnectorName); cy.getDataCy("data-connector-menu-dropdown").click(); - cy.getDataCy("data-connector-unlink").should("be.visible").click(); + cy.getDataCy("data-connector-unlink").click(); }); cy.getDataCy("delete-data-connector-modal-button").click(); // Verify the data connector is no longer linked to the main project - visitCurrentProject(); cy.getDataCy("data-connector-box") .contains(`[data-cy=data-connector-name]`, dataConnectorName) .should("not.exist"); @@ -468,21 +458,28 @@ describe("Data Connectors", () => { // Create a data connector owned by the group const dataConnectorIdentifier = `${groupSlug}/${dataConnectorName}`; createDataConnector(dataConnectorIdentifier); + verifySearchIndexing( + `${searchDataConnectorType} ${searchDataConnectorSlug}${dataConnectorName}`, + 1, + ); // Navigate to the user's project visitCurrentProject(); // Link the group data connector to the user's project cy.getDataCy("add-data-connector").click(); - cy.getDataCy("project-data-controller-mode-link").click(); - - // Enter the data connector identifier - cy.get("#data-connector-identifier") + cy.getDataCy("data-connector-search-input") .should("be.empty") .type(dataConnectorIdentifier); - cy.getDataCy("link-data-connector-button").click(); + cy.getDataCy("data-connector-search-body") + .contains("[data-cy=link-data-connector-list-item]", dataConnectorName) + .find(`[data-cy=data-connector-link-button]`) + .click(); // Verify the data connector is linked to the project + cy.getDataCy("project-data-connector-connect-header") + .find('button[data-bs-dismiss="modal"]') + .click(); cy.getDataCy("data-connector-box") .find(`[data-cy=data-connector-name]`) .contains(dataConnectorName) @@ -493,8 +490,8 @@ describe("Data Connectors", () => { cy.getDataCy("data-connector-title") .should("be.visible") .contains(dataConnectorName); - cy.getDataCy("data-connector-menu-dropdown").should("be.visible").click(); - cy.getDataCy("data-connector-unlink").should("be.visible").click(); + cy.getDataCy("data-connector-menu-dropdown").click(); + cy.getDataCy("data-connector-unlink").click(); }); cy.getDataCy("delete-data-connector-modal-button").click(); diff --git a/cypress-tests/cypress/support/utils/dataConnectors.ts b/cypress-tests/cypress/support/utils/dataConnectors.ts index 8173f4e612..aca69a98cb 100644 --- a/cypress-tests/cypress/support/utils/dataConnectors.ts +++ b/cypress-tests/cypress/support/utils/dataConnectors.ts @@ -46,11 +46,9 @@ export function createDataConnector( visibility: body.visibility || "private", description: body.description || "Test data connector description", storage: { - storage_type: "s3", configuration: { type: "s3", provider: "AWS", - region: "us-east-1", }, source_path: "giab", target_path: body.slug || "/", diff --git a/helm-chart/renku/requirements.yaml b/helm-chart/renku/requirements.yaml index 8ad7da5bf5..df4087a427 100644 --- a/helm-chart/renku/requirements.yaml +++ b/helm-chart/renku/requirements.yaml @@ -13,7 +13,7 @@ dependencies: condition: redis.install - name: amalthea-sessions repository: "https://swissdatasciencecenter.github.io/helm-charts/" - version: "0.27.2" + version: "0.28.0" - name: dlf-chart repository: "https://swissdatasciencecenter.github.io/datashim/" version: "0.3.9-renku-2" diff --git a/helm-chart/renku/templates/data-service/deployment.yaml b/helm-chart/renku/templates/data-service/deployment.yaml index cb2d99dfb1..b8142582b9 100644 --- a/helm-chart/renku/templates/data-service/deployment.yaml +++ b/helm-chart/renku/templates/data-service/deployment.yaml @@ -75,6 +75,8 @@ spec: value: /secrets/encryptionKey/encryptionKey - name: SECRETS_SERVICE_PUBLIC_KEY_PATH value: /secrets/publicKey/publicKey + - name: INTERNAL_AUTHN_SECRET_KEY_PATH + value: /secrets/internalSecretKey/secretKey - name: K8S_NAMESPACE value: {{ .Release.Namespace | quote }} - name: MAX_PINNED_PROJECTS @@ -130,6 +132,8 @@ spec: value: {{ .Values.dataService.imageBuilders.enabled | quote }} - name: BUILD_OUTPUT_IMAGE_PREFIX value: {{ .Values.dataService.imageBuilders.outputImagePrefix | default "" | quote }} + - name: BUILD_OUTPUT_PRIVATE_IMAGE_PREFIX + value: {{ .Values.dataService.imageBuilders.outputPrivateImagePrefix | default "" | quote }} - name: BUILD_BUILDER_IMAGE value: {{ .Values.dataService.imageBuilders.builderImage | default "" | quote }} - name: BUILD_RUN_IMAGE @@ -138,6 +142,10 @@ spec: value: {{ .Values.dataService.imageBuilders.strategyName | default "" | quote }} - name: BUILD_PUSH_SECRET_NAME value: {{ .Values.dataService.imageBuilders.pushSecretName | default "" | quote }} + - name: BUILD_PUSH_PRIVATE_SECRET_NAME + value: {{ .Values.dataService.imageBuilders.pushPrivateSecretName | default "" | quote }} + - name: BUILD_PULL_PRIVATE_SECRET_NAME + value: {{ .Values.dataService.imageBuilders.pullPrivateSecretName | default "" | quote }} - name: BUILD_RUN_RETENTION_AFTER_FAILED_SECONDS value: {{ .Values.dataService.imageBuilders.buildRunRetentionAfterFailedSeconds | default "" | quote }} - name: BUILD_RUN_RETENTION_AFTER_SUCCEEDED_SECONDS @@ -159,7 +167,7 @@ spec: - name: V1_SESSIONS_ENABLED value: {{ .Values.ui.client.supportLegacySessions | default false | quote }} - name: ENABLE_INTERNAL_GITLAB - value: {{ .Values.enableInternalGitlab | default false | quote }} + value: "false" - name: POSTHOG_ENABLED value: {{ .Values.posthog.enabled | quote }} - name: LOG_FORMAT_STYLE @@ -215,6 +223,9 @@ spec: - mountPath: "/secrets/publicKey" name: secret-service-public-key readOnly: true + - mountPath: "/secrets/internalSecretKey" + name: internal-authn-key + readOnly: true {{- if .Values.dataService.remoteClustersKubeconfigSecretName }} - name: remote-cluster-kubeconfigs mountPath: "/secrets/kube_configs" @@ -271,6 +282,12 @@ spec: items: - key: publicKey path: publicKey + - name: internal-authn-key + secret: + secretName: {{ template "renku.fullname" . }}-internal-authn + items: + - key: secretKey + path: secretKey {{- if .Values.dataService.remoteClustersKubeconfigSecretName }} - name: remote-cluster-kubeconfigs secret: diff --git a/helm-chart/renku/templates/data-service/deployment_k8s_watcher.yaml b/helm-chart/renku/templates/data-service/deployment_k8s_watcher.yaml index 1b058de08c..584221c0ba 100644 --- a/helm-chart/renku/templates/data-service/deployment_k8s_watcher.yaml +++ b/helm-chart/renku/templates/data-service/deployment_k8s_watcher.yaml @@ -69,6 +69,15 @@ spec: value: {{ .Values.dataService.k8sWatcher.sentry.environment | quote }} - name: SENTRY_SAMPLE_RATE value: {{ .Values.dataService.k8sWatcher.sentry.sampleRate | quote }} + - name: AUTHZ_DB_HOST + value: {{ include "renku.fullname" . }}-authz + - name: AUTHZ_DB_KEY + valueFrom: + secretKeyRef: + name: {{ template "renku.fullname" . }}-authz + key: SPICEDB_GRPC_PRESHARED_KEY + - name: AUTHZ_DB_GRPC_PORT + value: "50051" {{- if .Values.dataService.remoteClustersKubeconfigSecretName }} - name: K8S_CONFIGS_ROOT value: "/secrets/kube_configs" diff --git a/helm-chart/renku/templates/gateway/configmap.yaml b/helm-chart/renku/templates/gateway/configmap.yaml index 922168ffc8..d48880f9a2 100644 --- a/helm-chart/renku/templates/gateway/configmap.yaml +++ b/helm-chart/renku/templates/gateway/configmap.yaml @@ -36,7 +36,7 @@ data: audience: renku authorizedParty: renku-cli revproxy: - enableInternalGitlab: {{ .Values.enableInternalGitlab | default false }} + enableInternalGitlab: "false" renkuBaseUrl: {{ include "renku.baseUrl" . | quote }} externalGitlabUrl: {{ .Values.global.gitlab.url | default "" | quote }} k8sNamespace: {{ .Release.Namespace }} @@ -47,7 +47,7 @@ data: uiserver: {{ printf "http://%s" (include "ui-server.fullname" .) | quote }} search: {{ printf "http://%s-search-api" .Release.Name | quote }} login: - enableInternalGitlab: {{ .Values.enableInternalGitlab | default false }} + enableInternalGitlab: "false" renkuBaseUrl: {{ include "renku.baseUrl" . | quote }} loginRoutesBasePath: "/api/auth" defaultAppRedirectURL: {{ include "renku.baseUrl" . | quote }} @@ -60,14 +60,6 @@ data: scopes: ["profile", "email", "openid", "microprofile-jwt"] callbackURI: {{ printf "%s/api/auth/callback" (include "renku.baseUrl" .) }} usePKCE: false - {{- if .Values.enableInternalGitlab }} - gitlab: - issuer: {{ .Values.global.gitlab.url | quote }} - clientID: {{ .Values.gateway.gitlabClientId | default .Values.global.gateway.gitlabClientId | quote }} - scopes: ["openid", "api", "read_user", "read_repository"] - callbackURI: {{ printf "%s/api/auth/callback" (include "renku.baseUrl" .) }} - usePKCE: false - {{- end }} oldGitLabLogout: {{ .Values.gateway.oldGitLabLogout | default false }} logoutGitLabUponRenkuLogout: {{ .Values.gateway.logoutGitLabUponRenkuLogout | default true }} redis: diff --git a/helm-chart/renku/templates/gateway/deployment-revproxy.yaml b/helm-chart/renku/templates/gateway/deployment-revproxy.yaml index a9715de9e0..4b6a33f8b1 100644 --- a/helm-chart/renku/templates/gateway/deployment-revproxy.yaml +++ b/helm-chart/renku/templates/gateway/deployment-revproxy.yaml @@ -71,13 +71,6 @@ spec: secretKeyRef: name: {{ cat (include "renku.fullname" .) "-gateway" | nospace }} key: oidcClientSecret - {{- if .Values.enableInternalGitlab }} - - name: GATEWAY_LOGIN_PROVIDERS_GITLAB_CLIENTSECRET - valueFrom: - secretKeyRef: - name: {{ cat (include "renku.fullname" .) "-gateway" | nospace }} - key: gitlabClientSecret - {{- end }} - name: GATEWAY_LOGIN_TOKENENCRYPTION_SECRETKEY valueFrom: secretKeyRef: @@ -93,18 +86,6 @@ spec: secretKeyRef: name: {{ cat (include "renku.fullname" .) "-gateway" | nospace }} key: cookieHashKey - {{- if .Values.enableInternalGitlab }} - - name: GATEWAY_LOGIN_PROVIDERS_GITLAB_COOKIEENCODINGKEY - valueFrom: - secretKeyRef: - name: {{ cat (include "renku.fullname" .) "-gateway" | nospace }} - key: cookieEncodingKey - - name: GATEWAY_LOGIN_PROVIDERS_GITLAB_COOKIEHASHKEY - valueFrom: - secretKeyRef: - name: {{ cat (include "renku.fullname" .) "-gateway" | nospace }} - key: cookieHashKey - {{- end }} - name: GATEWAY_MONITORING_SENTRY_DSN value: {{ .Values.gateway.sentry.dsn }} - name: GATEWAY_POSTHOG_ENABLED diff --git a/helm-chart/renku/values.yaml b/helm-chart/renku/values.yaml index a660a97c81..8e0630f07b 100644 --- a/helm-chart/renku/values.yaml +++ b/helm-chart/renku/values.yaml @@ -14,6 +14,7 @@ global: # secretServicePrivateKey: ... RSA Private Key in PKCS8 PEM format (`ssh-keygen -m PKCS8 -t rsa -b 4096`) # secretServicePreviousPrivateKey: ... Previous Private key in PEM format, only set this when rotating keys # dataServiceEncryptionKey: 32 byte random string + # dataServiceInternalAuthnKey: 64 byte random string gitlab: ## Name of the postgres database to be used by Gitlab postgresDatabase: gitlabhq_production @@ -548,7 +549,7 @@ ui: replicaCount: 1 image: repository: renku/renku-ui - tag: "4.21.0" + tag: "4.22.0" pullPolicy: IfNotPresent ## Optionally specify an array of imagePullSecrets. ## Secrets must be manually created in the namespace. @@ -740,7 +741,7 @@ ui: keepCookies: [] image: repository: renku/renku-ui-server - tag: "4.21.0" + tag: "4.22.0" pullPolicy: IfNotPresent imagePullSecrets: [] nameOverride: "" @@ -822,7 +823,8 @@ dlf-chart: enabled: false dataset-operator-chart: enabled: true -csi-rclone: {} +csi-rclone: + {} # This section is only relevant if you are installing csi-rclone as part of Renku ## Name of the csi storage class to use for RClone/Cloudstorage. Should be unique per cluster. # storageClassName: csi-rclone @@ -991,7 +993,7 @@ notebooks: gitHttpsProxy: image: name: renku/sidecars - tag: 0.26.2 + tag: "0.28.0" args: ["gitproxy", "proxy"] port: 65480 healthPort: 65481 @@ -1090,7 +1092,7 @@ gateway: secretKey: image: repository: renku/renku-gateway - tag: "1.9.0" + tag: "1.10.0" pullPolicy: IfNotPresent service: type: ClusterIP @@ -1182,12 +1184,12 @@ dataService: existingPriorityClass: "" image: repository: renku/renku-data-service - tag: "0.72.2" + tag: "0.73.0" pullPolicy: IfNotPresent k8sWatcher: image: repository: renku/data-service-k8s-watcher - tag: "0.72.2" + tag: "0.73.0" pullPolicy: IfNotPresent resources: {} sentry: @@ -1198,7 +1200,7 @@ dataService: dataTasks: image: repository: renku/data-service-data-tasks - tag: "0.72.2" + tag: "0.73.0" pullPolicy: IfNotPresent resources: {} enableResourceRequestTracking: false @@ -1237,6 +1239,8 @@ dataService: enabled: false ## The container image prefix for images built from code outputImagePrefix: harbor.dev.renku.ch/renku-build/ + ## The container image prefix for images built from private code + outputPrivateImagePrefix: harbor.dev.renku.ch/renku-private-build/ ## The builder image (see https://buildpacks.io/docs/for-platform-operators/concepts/builder/) builderImage: "ghcr.io/swissdatasciencecenter/renku-frontend-buildpacks/selector:0.5.1" ## The run image (see https://buildpacks.io/docs/for-platform-operators/concepts/base-images/) @@ -1244,7 +1248,8 @@ dataService: ## The name of the BuildStrategy to use for image builds. strategyName: renku-buildpacks-v3 ## Configuration overrides for specific target platforms - platformOverrides: {} + platformOverrides: + {} # linux/arm64: # builderImage: "ghcr.io/swissdatasciencecenter/renku-frontend-buildpacks/cuda-selector:0.5.1" # runImage: "ghcr.io/swissdatasciencecenter/renku-frontend-buildpacks/cuda-run-image:0.5.1" @@ -1263,6 +1268,10 @@ dataService: # value: arm64 ## The name of the secret used to push images built from code. pushSecretName: renku-build-docker-secret + ## The name of the secret used to push images built from private code. + pushPrivateSecretName: renku-build-private-docker-secret + ## The name of the secret used to pull images built from private code. + pullPrivateSecretName: renku-pull-private-docker-secret ## The TTL for BuildRuns buildRunRetentionAfterFailedSeconds: 86400 buildRunRetentionAfterSucceededSeconds: 86400 @@ -1328,7 +1337,7 @@ authz: secretsStorage: image: repository: renku/secrets-storage - tag: "0.72.2" + tag: "0.73.0" pullPolicy: IfNotPresent service: type: ClusterIP @@ -1347,18 +1356,6 @@ secretsStorage: tolerations: [] affinity: {} -# When this is set to false the gateway and data service will ignore the Gitlab -# that can be integrated with Renku and will not ask users to log into this Gitlab. -# NOTE: This flag has no effect on the core service and knowledge graph. Therefore, -# setting this to false should only be done if the enableV1Services flag is also false. -# When this is set to false the gateway will not inject the internal gitlab tokens and -# the data service will not require them and if tokens are passed it will just ignore them. -# Setting this to false in existing Renku deployment will result in code repositories -# that use the internal Gitlab not functioning properly. If you still want to set this -# to false and keep operating with an internal Gitlab you should create an Integration -# with the internal Gitlab and ask users to activate the connection. -enableInternalGitlab: false - podSecurityContext: {} securityContext: runAsUser: 1000 diff --git a/helm-chart/values.yaml.changelog.md b/helm-chart/values.yaml.changelog.md index 65003727a8..c84f8896e5 100644 --- a/helm-chart/values.yaml.changelog.md +++ b/helm-chart/values.yaml.changelog.md @@ -5,6 +5,10 @@ For changes that require manual steps other than changing values, please check o Please follow this convention when adding a new row * ` - **:
` +## Upgrading to Renku 2.8.0 + +* DELETE `enableInternalGitlab`, it is now not possible to configure Renku to use an "internal" GitLab instance. Admins can set up a GitLab integration instead. + ## Upgrading to Renku 2.15.0 * DELETE `global.gateway.cliClientSecret` the client is public and has no secret in Keycloak 25. diff --git a/scripts/platform-init/platform-init.py b/scripts/platform-init/platform-init.py index bfe2fecf60..5536ee7103 100644 --- a/scripts/platform-init/platform-init.py +++ b/scripts/platform-init/platform-init.py @@ -1,6 +1,7 @@ -from base64 import b64decode, b64encode +from base64 import b64decode, b64encode, urlsafe_b64encode import yaml import logging +import random from typing import cast from kubernetes import client as k8s_client, config as k8s_config from dataclasses import dataclass, field @@ -23,6 +24,7 @@ class Config: secret_service_private_key: str | None = field(repr=False) encryption_key: str | None = field(repr=False) previous_secret_service_private_key: str | None = field(repr=False) + internal_authn_secret_key : str | None = field(repr=False) @classmethod def from_env(cls): @@ -40,6 +42,7 @@ def from_env(cls): ), secret_service_private_key_secret_name=f"{renku_fullname}-secret-service-private-key", secret_service_public_key_secret_name=f"{renku_fullname}-secret-service-public-key", + internal_authn_secret_key=config_map.get("dataServiceInternalAuthnKey"), ) @@ -257,6 +260,50 @@ def init_secret_and_data_service_encryption(config: Config): ), ) +def init_data_service_internal_authentication_secret_key(config: Config): + """Initialize symmetric signing key for internal authentication in data service.""" + logging.info("Initializing data service internal secret key") + v1 = k8s_client.CoreV1Api() + + internal_secret_key = f"{config.renku_fullname}-internal-authn" + internal_secret_key_name = "secretKey" + existing_internal_secret_key = _get_k8s_secret( + config.k8s_namespace, internal_secret_key, internal_secret_key_name + ) + + if existing_internal_secret_key is None and config.internal_authn_secret_key is None: + # generate a random string + rand = random.SystemRandom() + key = urlsafe_b64encode (rand.randbytes(64)) + v1.create_namespaced_secret( + config.k8s_namespace, + k8s_client.V1Secret( + api_version="v1", + data={internal_secret_key_name: b64encode(key).decode()}, + kind="Secret", + metadata={ + "name": internal_secret_key, + "namespace": config.k8s_namespace, + }, + type="Opaque", + ), + ) + elif existing_internal_secret_key is None and config.internal_authn_secret_key is not None: + key = config.internal_authn_secret_key.encode() + v1.create_namespaced_secret( + config.k8s_namespace, + k8s_client.V1Secret( + api_version="v1", + data={internal_secret_key_name: b64encode(key).decode()}, + kind="Secret", + metadata={ + "name": internal_secret_key, + "namespace": config.k8s_namespace, + }, + type="Opaque", + ), + ) + def main(): config = Config.from_env() @@ -265,6 +312,7 @@ def main(): logging.info("Initializing Renku platform") set_secret_service_secrets(config) init_secret_and_data_service_encryption(config) + init_data_service_internal_authentication_secret_key(config) if __name__ == "__main__":