From bba4da2142033a6337d7b1fcfc633c5f8b0b235d Mon Sep 17 00:00:00 2001 From: Tim Herbert Date: Fri, 22 May 2026 22:39:12 +0200 Subject: [PATCH] fix: idm authentication #92 --- charts/opencloud/README.md | 27 +++- .../templates/opencloud/migration-job.yaml | 150 ++++++++++++++++++ charts/opencloud/values.yaml | 6 + 3 files changed, 182 insertions(+), 1 deletion(-) create mode 100644 charts/opencloud/templates/opencloud/migration-job.yaml diff --git a/charts/opencloud/README.md b/charts/opencloud/README.md index c5f675a..6133878 100644 --- a/charts/opencloud/README.md +++ b/charts/opencloud/README.md @@ -376,7 +376,32 @@ storageUsersMountID The chart maps these keys to the correct runtime ENV vars for each OpenCloud service, including per-service LDAP bind passwords (e.g., `USERS_LDAP_BIND_PASSWORD` ← `idmRevaServicePassword`). -### Keycloak Settings +### Credential Migration Job + +When upgrading from an older chart version that stored credentials in a config PVC (e.g., `/config/opencloud.yaml`), a one-time migration job can extract the existing passwords and write them into the `*-init` Secret so that OpenCloud can restart without regenerating credentials. + +The job runs as a `post-upgrade` Helm hook. It: +1. Reads `idp.ldap.bind_password`, `idm.service_user_passwords.idm_password`, and `idm.service_user_passwords.reva_password` from the legacy config file on the PVC. +2. Patches those values into the init Secret (`opencloud.initSecrets.existingSecret` or the auto-generated `-init`). +3. Triggers a rolling restart of the OpenCloud deployment. + +The job and its RBAC resources (ServiceAccount, Role, RoleBinding) are cleaned up automatically at the start of the **next** `helm upgrade` (`before-hook-creation` delete policy), so they remain available for troubleshooting after each run. + +| Parameter | Description | Default | +| --------- | ----------- | ------- | +| `opencloud.migration.configPvcClaimName` | Name of the legacy config PVC to read credentials from | `-opencloud-config` | + +**Example:** + +```yaml +opencloud: + migration: + configPvcClaimName: "my-old-opencloud-config" +``` + +> **Note:** This job only needs to run once. After a successful migration, the credentials live in the init Secret and the legacy PVC is no longer required. + + By default the chart deploys an internal Keycloak. It can be disabled and replaced with an external OIDC provider. diff --git a/charts/opencloud/templates/opencloud/migration-job.yaml b/charts/opencloud/templates/opencloud/migration-job.yaml new file mode 100644 index 0000000..1eb50db --- /dev/null +++ b/charts/opencloud/templates/opencloud/migration-job.yaml @@ -0,0 +1,150 @@ +{{- if .Values.opencloud.enabled }} +{{- $jobName := printf "%s-credential-migration" (include "opencloud.opencloud.fullname" .) -}} +{{- $saName := printf "%s-credential-migration" (include "opencloud.opencloud.fullname" .) -}} +{{- $targetSecret := .Values.opencloud.initSecrets.existingSecret | default (printf "%s-init" (include "opencloud.opencloud.fullname" .)) -}} +{{- $targetDeployment := include "opencloud.opencloud.fullname" . -}} +{{- $legacyClaim := (.Values.opencloud.migration).configPvcClaimName | default (printf "%s-config" (include "opencloud.opencloud.fullname" .)) -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ $saName }} + labels: + {{- include "opencloud.labels" . | nindent 4 }} + app.kubernetes.io/component: opencloud + annotations: + "helm.sh/hook": post-upgrade + "helm.sh/hook-delete-policy": before-hook-creation + "helm.sh/hook-weight": "-2" +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ $saName }} + labels: + {{- include "opencloud.labels" . | nindent 4 }} + app.kubernetes.io/component: opencloud + annotations: + "helm.sh/hook": post-upgrade + "helm.sh/hook-delete-policy": before-hook-creation + "helm.sh/hook-weight": "-2" +rules: + - apiGroups: [""] + resources: ["secrets"] + resourceNames: ["{{ $targetSecret }}"] + verbs: ["get", "patch"] + - apiGroups: ["apps"] + resources: ["deployments"] + resourceNames: ["{{ $targetDeployment }}"] + verbs: ["get", "patch"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ $saName }} + labels: + {{- include "opencloud.labels" . | nindent 4 }} + app.kubernetes.io/component: opencloud + annotations: + "helm.sh/hook": post-upgrade + "helm.sh/hook-delete-policy": before-hook-creation + "helm.sh/hook-weight": "-2" +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ $saName }} +subjects: + - kind: ServiceAccount + name: {{ $saName }} + namespace: {{ .Release.Namespace }} +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ $jobName }} + labels: + {{- include "opencloud.labels" . | nindent 4 }} + app.kubernetes.io/component: opencloud + annotations: + "helm.sh/hook": post-upgrade + "helm.sh/hook-delete-policy": before-hook-creation +spec: + backoffLimit: 1 + template: + metadata: + labels: + {{- include "opencloud.selectorLabels" . | nindent 8 }} + app.kubernetes.io/component: opencloud + spec: + serviceAccountName: {{ $saName }} + restartPolicy: Never + containers: + - name: migrate-credentials + image: docker.io/alpine/k8s:1.31.13 + imagePullPolicy: IfNotPresent + command: + - /bin/sh + - -ec + - | + CONFIG_FILE="/config/opencloud.yaml" + + IDP_LDAP_BIND_PASSWORD="$(awk ' + /^idp:[[:space:]]*$/ { in_idp=1; next } + in_idp && /^[^[:space:]]/ { in_idp=0 } + in_idp && /^[[:space:]]+ldap:[[:space:]]*$/ { in_ldap=1; next } + in_ldap && /^[[:space:]]{2}[^[:space:]]/ && $0 !~ /^[[:space:]]+ldap:/ { in_ldap=0 } + in_ldap && /^[[:space:]]+bind_password:[[:space:]]*/ { + sub(/^[[:space:]]+bind_password:[[:space:]]*/, "") + gsub(/^"|"$/, "") + print + exit + } + ' "${CONFIG_FILE}")" + + IDM_SERVICE_PASSWORD="$(awk ' + /^idm:[[:space:]]*$/ { in_idm=1; next } + in_idm && /^[^[:space:]]/ { in_idm=0 } + in_idm && /^[[:space:]]+service_user_passwords:[[:space:]]*$/ { in_pw=1; next } + in_pw && /^[[:space:]]{2}[^[:space:]]/ && $0 !~ /^[[:space:]]+service_user_passwords:/ { in_pw=0 } + in_pw && /^[[:space:]]+idm_password:[[:space:]]*/ { + sub(/^[[:space:]]+idm_password:[[:space:]]*/, "") + gsub(/^"|"$/, "") + print + exit + } + ' "${CONFIG_FILE}")" + + IDM_REVA_PASSWORD="$(awk ' + /^idm:[[:space:]]*$/ { in_idm=1; next } + in_idm && /^[^[:space:]]/ { in_idm=0 } + in_idm && /^[[:space:]]+service_user_passwords:[[:space:]]*$/ { in_pw=1; next } + in_pw && /^[[:space:]]{2}[^[:space:]]/ && $0 !~ /^[[:space:]]+service_user_passwords:/ { in_pw=0 } + in_pw && /^[[:space:]]+reva_password:[[:space:]]*/ { + sub(/^[[:space:]]+reva_password:[[:space:]]*/, "") + gsub(/^"|"$/, "") + print + exit + } + ' "${CONFIG_FILE}")" + + [ -n "${IDM_REVA_PASSWORD}" ] || (echo "Missing idm.service_user_passwords.reva_password" && exit 1) + [ -n "${IDM_SERVICE_PASSWORD}" ] || (echo "Missing idm.service_user_passwords.idm_password" && exit 1) + [ -n "${IDP_LDAP_BIND_PASSWORD}" ] || (echo "Missing idp.ldap.bind_password" && exit 1) + + b64_idm_reva="$(printf '%s' "${IDM_REVA_PASSWORD}" | base64 | tr -d '\n')" + b64_idm_service="$(printf '%s' "${IDM_SERVICE_PASSWORD}" | base64 | tr -d '\n')" + b64_idp_ldap="$(printf '%s' "${IDP_LDAP_BIND_PASSWORD}" | base64 | tr -d '\n')" + + kubectl -n "{{ .Release.Namespace }}" patch secret "{{ $targetSecret }}" --type='json' -p="[{\"op\":\"add\",\"path\":\"/data/idmRevaServicePassword\",\"value\":\"${b64_idm_reva}\"}]" + kubectl -n "{{ .Release.Namespace }}" patch secret "{{ $targetSecret }}" --type='json' -p="[{\"op\":\"add\",\"path\":\"/data/idmServicePassword\",\"value\":\"${b64_idm_service}\"}]" + kubectl -n "{{ .Release.Namespace }}" patch secret "{{ $targetSecret }}" --type='json' -p="[{\"op\":\"add\",\"path\":\"/data/idmIdpServicePassword\",\"value\":\"${b64_idp_ldap}\"}]" + + kubectl -n "{{ .Release.Namespace }}" rollout restart deployment "{{ $targetDeployment }}" + volumeMounts: + - name: config-pvc + mountPath: /config + readOnly: true + volumes: + - name: config-pvc + persistentVolumeClaim: + claimName: {{ $legacyClaim }} +{{- end }} diff --git a/charts/opencloud/values.yaml b/charts/opencloud/values.yaml index 46a25cd..f546475 100644 --- a/charts/opencloud/values.yaml +++ b/charts/opencloud/values.yaml @@ -496,6 +496,12 @@ opencloud: storageClass: "" # Access mode (ReadWriteOnce or ReadWriteMany for multiple replicas) accessMode: ReadWriteOnce + # Migration configuration for migrating credentials from legacy config PVC to new secrets + migration: + # Name of the legacy config PVC to read credentials from. + # Defaults to "-config" if not set. + configPvcClaimName: "" + # Configuration files config: theme: "owncloud"