From ede7f622b6eecff0b32523cf630786a9bfb3586d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Tue, 1 Jul 2025 14:14:11 +0200 Subject: [PATCH] introduce internal openldap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörn Friedrich Dreyer --- charts/opencloud/Chart.yaml | 2 +- charts/opencloud/README.md | 21 +++++ .../files/openldap/ldif/10_base.ldif | 24 ++++++ .../openldap/schemas/10_opencloud_schema.ldif | 39 +++++++++ charts/opencloud/templates/_helpers/tpl.yaml | 7 ++ .../templates/opencloud/deployment.yaml | 35 ++++++++ .../templates/openldap/configmap.yaml | 14 ++++ .../templates/openldap/deployment.yaml | 81 +++++++++++++++++++ charts/opencloud/templates/openldap/pvc.yaml | 24 ++++++ .../opencloud/templates/openldap/secrets.yaml | 11 +++ .../opencloud/templates/openldap/service.yaml | 23 ++++++ charts/opencloud/values.yaml | 44 ++++++++++ 12 files changed, 324 insertions(+), 1 deletion(-) create mode 100644 charts/opencloud/files/openldap/ldif/10_base.ldif create mode 100644 charts/opencloud/files/openldap/schemas/10_opencloud_schema.ldif create mode 100644 charts/opencloud/templates/openldap/configmap.yaml create mode 100644 charts/opencloud/templates/openldap/deployment.yaml create mode 100644 charts/opencloud/templates/openldap/pvc.yaml create mode 100644 charts/opencloud/templates/openldap/secrets.yaml create mode 100644 charts/opencloud/templates/openldap/service.yaml diff --git a/charts/opencloud/Chart.yaml b/charts/opencloud/Chart.yaml index df2d54b4..4d795fa8 100644 --- a/charts/opencloud/Chart.yaml +++ b/charts/opencloud/Chart.yaml @@ -10,7 +10,7 @@ maintainers: email: info@opencloud.eu url: https://opencloud.eu type: application -version: 0.2.2 +version: 0.3.0 # renovate: datasource=docker depName=opencloudeu/opencloud-rolling appVersion: latest kubeVersion: "" diff --git a/charts/opencloud/README.md b/charts/opencloud/README.md index 7c3cd563..f7109bb7 100644 --- a/charts/opencloud/README.md +++ b/charts/opencloud/README.md @@ -362,6 +362,27 @@ keycloak: | `postgres.persistence.storageClass` | Storage class | `""` | | `postgres.persistence.accessMode` | Access mode | `ReadWriteOnce` | +### LDAP Settings + +This chart optionally deploys an internal OpenLDAP server for identity management. When enabled it replaces the built in idm. + +| Parameter | Description | Default | +| ---------------------------------------- | --------------------------------------------------------------------- | ------------------ | +| `ldap.internal.enabled` | Enable internal OpenLDAP server | `false` | +| `ldap.internal.image.registry` | OpenLDAP image registry | `docker.io` | +| `ldap.internal.image.repository` | OpenLDAP image repository | `bitnami/openldap` | +| `ldap.internal.image.tag` | OpenLDAP image tag | `"2.6"` | +| `ldap.internal.image.pullPolicy` | Image pull policy | `IfNotPresent` | +| `ldap.internal.existingSecret` | Name of existing Kubernetes Secret (must contain key `adminPassword`) | `""` | +| `ldap.internal.adminPassword` | Admin password (ignored if `existingSecret` is set) | `adminpass` | +| `ldap.internal.resources` | CPU/Memory resource requests/limits | See values.yaml | +| `ldap.internal.persistence.enabled` | Enable persistence for OpenLDAP | `true` | +| `ldap.internal.persistence.size` | Size of the persistent volume | `1Gi` | +| `ldap.internal.persistence.storageClass` | Storage class | `""` | +| `ldap.internal.persistence.accessMode` | Access mode | `ReadWriteOnce` | + +> 💡 If `ldap.internal.existingSecret` is set, it must contain a key named `adminPassword`. +> If not set, a random password is generated during installation and stored in a Helm-managed secret. This secret uses the annotation `helm.sh/resource-policy: keep` to prevent it from being overwritten on upgrade. ### OnlyOffice Settings diff --git a/charts/opencloud/files/openldap/ldif/10_base.ldif b/charts/opencloud/files/openldap/ldif/10_base.ldif new file mode 100644 index 00000000..3e4e418b --- /dev/null +++ b/charts/opencloud/files/openldap/ldif/10_base.ldif @@ -0,0 +1,24 @@ +dn: dc=opencloud,dc=eu +objectClass: organization +objectClass: dcObject +dc: opencloud +o: openCloud + +dn: ou=users,dc=opencloud,dc=eu +objectClass: organizationalUnit +ou: users + +dn: cn=admin,dc=opencloud,dc=eu +objectClass: inetOrgPerson +objectClass: person +cn: admin +sn: admin +uid: ldapadmin + +dn: ou=groups,dc=opencloud,dc=eu +objectClass: organizationalUnit +ou: groups + +dn: ou=custom,ou=groups,dc=opencloud,dc=eu +objectClass: organizationalUnit +ou: custom diff --git a/charts/opencloud/files/openldap/schemas/10_opencloud_schema.ldif b/charts/opencloud/files/openldap/schemas/10_opencloud_schema.ldif new file mode 100644 index 00000000..faf24040 --- /dev/null +++ b/charts/opencloud/files/openldap/schemas/10_opencloud_schema.ldif @@ -0,0 +1,39 @@ +# This LDIF files describes the OpenCloud schema +dn: cn=opencloud,cn=schema,cn=config +objectClass: olcSchemaConfig +cn: opencloud +olcObjectIdentifier: openCloudOid 1.3.6.1.4.1.63016 +# We'll use openCloudOid:1 subarc for LDAP related stuff +# openCloudOid:1.1 for AttributeTypes and openCloudOid:1.2 for ObjectClasses +olcAttributeTypes: ( openCloudOid:1.1.1 NAME 'openCloudUUID' + DESC 'A non-reassignable and persistent account ID)' + EQUALITY uuidMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.1.16.1 SINGLE-VALUE ) +olcAttributeTypes: ( openCloudOid:1.1.2 NAME 'openCloudExternalIdentity' + DESC 'A triple separated by "$" representing the objectIdentity resource type of the Graph API ( signInType $ issuer $ issuerAssignedId )' + EQUALITY caseIgnoreMatch + SUBSTR caseIgnoreSubstringsMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 ) +olcAttributeTypes: ( openCloudOid:1.1.3 NAME 'openCloudUserEnabled' + DESC 'A boolean value indicating if the user is enabled' + EQUALITY booleanMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE) +olcAttributeTypes: ( openCloudOid:1.1.4 NAME 'openCloudUserType' + DESC 'User type (e.g. Member or Guest)' + EQUALITY caseIgnoreMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE ) +olcAttributeTypes: ( openCloudOid:1.1.5 NAME 'openCloudLastSignInTimestamp' + DESC 'The timestamp of the last sign-in' + EQUALITY generalizedTimeMatch + ORDERING generalizedTimeOrderingMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 SINGLE-VALUE ) +olcObjectClasses: ( openCloudOid:1.2.1 NAME 'openCloudObject' + DESC 'OpenCloud base objectclass' + AUXILIARY + MAY ( openCloudUUID ) ) +olcObjectClasses: ( openCloudOid:1.2.2 NAME 'openCloudUser' + DESC 'OpenCloud User objectclass' + SUP openCloudObject + AUXILIARY + MAY ( openCloudExternalIdentity $ openCloudUserEnabled $ openCloudUserType $ openCloudLastSignInTimestamp) ) diff --git a/charts/opencloud/templates/_helpers/tpl.yaml b/charts/opencloud/templates/_helpers/tpl.yaml index 83b9b613..9cb21728 100644 --- a/charts/opencloud/templates/_helpers/tpl.yaml +++ b/charts/opencloud/templates/_helpers/tpl.yaml @@ -82,6 +82,13 @@ Create a fully qualified PostgreSQL name. {{- printf "%s-postgres" (include "opencloud.fullname" .) | trunc 63 | trimSuffix "-" }} {{- end }} +{{/* +Create a fully qualified OpenLDAP name. +*/}} +{{- define "opencloud.openldap.fullname" -}} +{{- printf "%s-openldap" (include "opencloud.fullname" .) | trunc 63 | trimSuffix "-" }} +{{- end }} + {{/* Create a fully qualified MinIO name. */}} diff --git a/charts/opencloud/templates/opencloud/deployment.yaml b/charts/opencloud/templates/opencloud/deployment.yaml index 3fd7e19a..f24adcbd 100644 --- a/charts/opencloud/templates/opencloud/deployment.yaml +++ b/charts/opencloud/templates/opencloud/deployment.yaml @@ -151,6 +151,9 @@ spec: {{- if .Values.opencloud.nats.external.enabled }} {{- $exclude = append $exclude "nats" }} {{- end }} + {{- if .Values.ldap.internal.enabled }} + {{- $exclude = append $exclude "idm" }} + {{- end }} {{- if gt (len $exclude) 0 }} - name: OC_EXCLUDE_RUN_SERVICES value: {{ join "," $exclude | quote }} @@ -237,6 +240,8 @@ spec: value: "true" - name: WEB_OIDC_CLIENT_ID value: {{ .Values.global.oidc.clientId | quote}} + # this is different from the compose setup, where the sub claim is used + # users must not change their username in the idp or they will no longer be able to access their data - name: PROXY_USER_OIDC_CLAIM value: "preferred_username" - name: PROXY_USER_CS3_CLAIM @@ -260,6 +265,36 @@ spec: - name: WEB_OIDC_SCOPE value: "openid profile email groups roles" {{- end }} + + {{- if .Values.ldap.internal.enabled }} + # opencloud manages the ldap server, it is considered writable + - name: OC_LDAP_URI + value: "ldap://{{ include "opencloud.openldap.fullname" . }}:1389" + - name: OC_LDAP_INSECURE + value: "true" + - name: OC_LDAP_BIND_DN + value: "cn=admin,dc=opencloud,dc=eu" + + - name: OC_LDAP_BIND_PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "opencloud.openldap.fullname" . }} + key: adminPassword + + - name: OC_LDAP_GROUP_BASE_DN + value: "ou=groups,dc=opencloud,dc=eu" + - name: OC_LDAP_USER_BASE_DN + value: "ou=users,dc=opencloud,dc=eu" + - name: OC_LDAP_USER_FILTER + value: "(objectclass=inetOrgPerson)" + # opencloud will roll an opencloudUUID for users and groups + - name: GRAPH_LDAP_SERVER_UUID + value: "false" + # usermanagement is done in keycloak, so we disable editing user properties in opencloud + - name: FRONTEND_READONLY_USER_ATTRIBUTES + value: "user.onPremisesSamAccountName,user.displayName,user.mail,user.passwordProfile,user.accountEnabled,user.appRoleAssignments" + {{- end }} + # Admin user password - name: IDM_ADMIN_PASSWORD valueFrom: diff --git a/charts/opencloud/templates/openldap/configmap.yaml b/charts/opencloud/templates/openldap/configmap.yaml new file mode 100644 index 00000000..3224eb8f --- /dev/null +++ b/charts/opencloud/templates/openldap/configmap.yaml @@ -0,0 +1,14 @@ +{{- if .Values.ldap.internal.enabled }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "opencloud.openldap.fullname" . }}-config + labels: + {{- include "opencloud.labels" . | nindent 4 }} + app.kubernetes.io/component: openldap +data: + 10_opencloud_schema.ldif: |- +{{- .Files.Get "files/openldap/schemas/10_opencloud_schema.ldif" | nindent 4 }} + 10_base.ldif: |- +{{- .Files.Get "files/openldap/ldif/10_base.ldif" | nindent 4 }} +{{- end }} diff --git a/charts/opencloud/templates/openldap/deployment.yaml b/charts/opencloud/templates/openldap/deployment.yaml new file mode 100644 index 00000000..9da8d57e --- /dev/null +++ b/charts/opencloud/templates/openldap/deployment.yaml @@ -0,0 +1,81 @@ +{{- if .Values.ldap.internal.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "opencloud.openldap.fullname" . }} + labels: + {{- include "opencloud.labels" . | nindent 4 }} + app.kubernetes.io/component: openldap +spec: + replicas: 1 + selector: + matchLabels: + {{- include "opencloud.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: openldap + strategy: + type: Recreate + template: + metadata: + labels: + {{- include "opencloud.selectorLabels" . | nindent 8 }} + app.kubernetes.io/component: openldap + spec: + containers: + - name: openldap + image: {{ include "opencloud.image" (dict "imageValues" .Values.ldap.internal.image "global" .Values.global) | quote }} + imagePullPolicy: {{ include "opencloud.image.pullPolicy" (dict "pullPolicy" .Values.ldap.internal.image.pullPolicy "global" .Values.global) }} + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + add: + - NET_BIND_SERVICE # see https://github.com/bitnami/containers/issues/40841 + runAsNonRoot: true + seccompProfile: + type: RuntimeDefault + env: + - name: LDAP_ROOT + value: "dc=opencloud,dc=eu" + - name: LDAP_ADMIN_DN + value: "cn=admin,dc=opencloud,dc=eu" + - name: LDAP_ADMIN_USERNAME + value: "admin" + - name: LDAP_ADMIN_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Values.ldap.internal.existingSecret | default (include "opencloud.openldap.fullname" .) }} + key: adminPassword + - name: LDAP_CUSTOM_LDIF_DIR + value: "/custom-ldifs" + ports: + - name: ldap + containerPort: 1389 + - name: ldaps + containerPort: 1636 + volumeMounts: + - name: custom-ldif + mountPath: /schemas/10_opencloud_schema.ldif + subPath: 10_opencloud_schema.ldif + - name: custom-ldif + mountPath: /custom-ldifs/10_base.ldif + subPath: 10_base.ldif + #- name: custom-ldif + # mountPath: /ldifs/20_admin.ldif + # subPath: 20_admin.ldif + {{- if .Values.ldap.internal.persistence.enabled }} + - name: data + mountPath: /bitnami/openldap + {{- end }} + resources: + {{- toYaml .Values.ldap.internal.resources | nindent 12 }} + volumes: + - name: custom-ldif + configMap: + name: {{ include "opencloud.openldap.fullname" . }}-config + {{- if .Values.ldap.internal.persistence.enabled }} + - name: data + persistentVolumeClaim: + claimName: {{ include "opencloud.openldap.fullname" . }}-data + {{- end }} +{{- end }} diff --git a/charts/opencloud/templates/openldap/pvc.yaml b/charts/opencloud/templates/openldap/pvc.yaml new file mode 100644 index 00000000..c1736381 --- /dev/null +++ b/charts/opencloud/templates/openldap/pvc.yaml @@ -0,0 +1,24 @@ +{{- if .Values.ldap.internal.persistence.enabled }} +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ include "opencloud.openldap.fullname" . }}-data + annotations: + "helm.sh/resource-policy": "keep" + labels: + {{- include "opencloud.labels" . | nindent 4 }} + app.kubernetes.io/component: openldap +spec: + accessModes: + - {{ .Values.ldap.internal.persistence.accessMode | quote }} + resources: + requests: + storage: {{ .Values.ldap.internal.persistence.size | quote }} + {{- if .Values.ldap.internal.persistence.storageClass }} + {{- if (eq "-" .Values.ldap.internal.persistence.storageClass) }} + storageClassName: "" + {{- else }} + storageClassName: {{ .Values.ldap.internal.persistence.storageClass | quote }} + {{- end }} + {{- end }} +{{- end }} diff --git a/charts/opencloud/templates/openldap/secrets.yaml b/charts/opencloud/templates/openldap/secrets.yaml new file mode 100644 index 00000000..09b15848 --- /dev/null +++ b/charts/opencloud/templates/openldap/secrets.yaml @@ -0,0 +1,11 @@ +{{- if and .Values.ldap.internal.enabled (not .Values.ldap.internal.existingSecret) }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "opencloud.openldap.fullname" . }} + labels: + app.kubernetes.io/component: openldap +type: Opaque +stringData: + adminPassword: {{ .Values.ldap.internal.adminPassword }} +{{- end }} diff --git a/charts/opencloud/templates/openldap/service.yaml b/charts/opencloud/templates/openldap/service.yaml new file mode 100644 index 00000000..37aafc88 --- /dev/null +++ b/charts/opencloud/templates/openldap/service.yaml @@ -0,0 +1,23 @@ +{{- if .Values.ldap.internal.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "opencloud.openldap.fullname" . }} + labels: + {{- include "opencloud.labels" . | nindent 4 }} + app.kubernetes.io/component: openldap +spec: + type: ClusterIP + ports: + - port: 1389 + targetPort: ldap + protocol: TCP + name: ldap + - port: 1636 + targetPort: ldaps + protocol: TCP + name: ldaps + selector: + {{- include "opencloud.selectorLabels" . | nindent 4 }} + app.kubernetes.io/component: openldap +{{- end }} diff --git a/charts/opencloud/values.yaml b/charts/opencloud/values.yaml index eb8e6dda..5ed6420d 100644 --- a/charts/opencloud/values.yaml +++ b/charts/opencloud/values.yaml @@ -190,6 +190,50 @@ postgres: # Access mode accessMode: ReadWriteOnce +# ===================================================================== +# IDENTITY MANAGEMENT (LDAP) +# ===================================================================== + +ldap: + internal: + # Enable OpenLDAP + enabled: false + # OpenLDAP image settings + image: + # OpenLDAP image registry + registry: docker.io + # OpenLDAP image repository + repository: bitnami/openldap + # OpenLDAP image tag + tag: "2.6" + # Image pull policy + pullPolicy: IfNotPresent + + # Use existing secret for OpenLDAP credentials (Note: secretKeyName must be adminPassword) + existingSecret: "" + + # Admin password + # ignored if ldap.internal.existingSecret is set + adminPassword: adminpass + + # Persistence configuration + persistence: + enabled: true + # Size of the persistent volume + size: 1Gi + # Storage class + storageClass: "" + # Access mode + accessMode: ReadWriteOnce + + # Resources allocation + resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: 500m + memory: 512Mi # ===================================================================== # EXTENSIONS