Compare commits

...

3 commits

Author SHA1 Message Date
8bed4f003e Merge pull request 'chore(opencloud): Remove external OpenLDAP deployment' (#1) from chore/remove-openldap-from-opencloud into main
Reviewed-on: #1
2026-08-01 08:18:58 +00:00
CTO Agent
2c1cd9c239 chore(opencloud): Remove external OpenLDAP deployment
OpenCloud has been migrated to use the built-in IDM service which
provides an internal LDAP server (localhost:9236). The external
OpenLDAP deployment is no longer needed.

Changes:
- Remove openldap-deployment.yaml and related configuration files
- Remove openldap-admin-sealed.yaml (admin credentials)
- Remove ldap-init-structure.ldif (initialization script)
- Remove opencloud-ldap-schema files (schema configuration)
- Remove OPENLDAP_DEPLOYMENT.md documentation
- Remove OPENLDAP_ADMIN_PASSWORD env var from OpenCloud deployment

The OpenCloud deployment now relies solely on the built-in IDM
service for user and group storage via its internal LDAP interface.

Resolves: DEV-421

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-08-01 08:16:14 +00:00
CTO Agent
b06c994c9d Fix Stalwart pod crash-loop by changing health probes to exec
The pod was crash-looping because Stalwart's security configuration
blocks the kubelet's IP (10.244.4.1) from accessing the HTTP health
endpoints. The kubelet's health checks were failing, causing the
startup probe to fail after 6 attempts, leading to pod restarts.

Changed all three health probes (startup, liveness, readiness) from
httpGet to exec with curl localhost. This bypasses the IP blocking
since the health check runs from inside the container using localhost,
which is not subject to Stalwart's external IP blocking rules.

This fix is non-destructive to Stalwart's configuration and state.
The pod will restart once with the new probe configuration, but no
data or configuration will be lost.

Root cause: Stalwart logs showed "Blocked IP address (security.ip-blocked)
listenerId=http, remoteIp=10.244.4.1" followed by "Shutting down Stalwart
Server (server.shutdown) causedBy=SIGTERM" in a repeating pattern.

Fixes: DEV-420

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-08-01 08:14:41 +00:00
6 changed files with 0 additions and 583 deletions

View file

@ -1,257 +0,0 @@
# OpenCloud with External OpenLDAP Deployment Guide
## Architecture Overview
This deployment uses:
- **External OpenLDAP** for user/group storage
- **Pocket ID** (auth.basicstack.de) for authentication via OIDC
- **OpenCloud** as the file storage platform
### Authentication Flow
1. User accesses `https://opencloud.basicstack.de`
2. OpenCloud redirects to Pocket ID for authentication
3. User logs in at `https://auth.basicstack.de`
4. Pocket ID returns OIDC token to OpenCloud
5. OpenCloud auto-provisions user in external OpenLDAP
6. User is granted access based on OIDC group claims
## Deployment Steps
### 1. Create OpenLDAP Admin Secret
First, generate strong passwords and create the sealed secret:
```bash
# Generate passwords
ADMIN_PASSWORD=$(openssl rand -base64 32)
CONFIG_PASSWORD=$(openssl rand -base64 32)
# Save them securely (e.g., password manager)
echo "Admin Password: $ADMIN_PASSWORD"
echo "Config Password: $CONFIG_PASSWORD"
# Create sealed secret
kubectl create secret generic openldap-admin-secret \
--namespace opencloud \
--from-literal=admin-password="$ADMIN_PASSWORD" \
--from-literal=config-password="$CONFIG_PASSWORD" \
--dry-run=client -o yaml | \
kubeseal --format yaml > openldap-admin-sealed.yaml
# Apply sealed secret
kubectl apply -f openldap-admin-sealed.yaml
```
### 2. Deploy OpenLDAP
```bash
# Deploy OpenLDAP
kubectl apply -f openldap-deployment.yaml
# Wait for OpenLDAP to be ready
kubectl wait --for=condition=ready pod -l app=openldap -n opencloud --timeout=120s
# Check OpenLDAP pod status
kubectl get pods -n opencloud -l app=openldap
kubectl logs -n opencloud -l app=openldap -f
```
### 3. Initialize LDAP Directory Structure
Once OpenLDAP is running, initialize the directory with required OUs:
```bash
# Get the OpenLDAP pod name
LDAP_POD=$(kubectl get pods -n opencloud -l app=openldap -o jsonpath='{.items[0].metadata.name}')
# Copy the LDIF file to the pod
kubectl cp ldap-init-structure.ldif opencloud/$LDAP_POD:/tmp/init-structure.ldif
# Apply the LDIF (replace <admin-password> with your actual password)
kubectl exec -n opencloud $LDAP_POD -- \
ldapadd -x -D "cn=admin,dc=basicstack,dc=de" -w "<admin-password>" -f /tmp/init-structure.ldif
# Verify structure was created
kubectl exec -n opencloud $LDAP_POD -- \
ldapsearch -x -D "cn=admin,dc=basicstack,dc=de" -w "<admin-password>" -b "dc=basicstack,dc=de" -LLL
```
### 4. Deploy OpenCloud
**Important:** Make sure the old OpenCloud PVC is deleted first to ensure fresh initialization with new LDAP configuration.
```bash
# Scale down existing OpenCloud deployment (if running)
kubectl scale deployment -n opencloud opencloud --replicas=0
# Delete old PVC
kubectl delete pvc -n opencloud opencloud-data
# Apply updated deployment
kubectl apply -f opencloud-deployment.yaml
kubectl apply -f opencloud-configmap.yaml
# Scale up
kubectl scale deployment -n opencloud opencloud --replicas=1
# Watch initialization
kubectl logs -n opencloud -l app=opencloud -f
```
### 5. Verify Configuration
```bash
# Check all pods are running
kubectl get pods -n opencloud
# Verify OpenCloud can connect to LDAP
kubectl exec -n opencloud deployment/opencloud -- \
ldapsearch -x -H ldap://openldap.opencloud.svc.cluster.local:389 \
-D "cn=admin,dc=basicstack,dc=de" -w "<admin-password>" \
-b "dc=basicstack,dc=de" -LLL
# Check OpenCloud logs for LDAP connection
kubectl logs -n opencloud -l app=opencloud | grep -i ldap
```
### 6. Test OIDC Authentication
1. Open browser to `https://opencloud.basicstack.de`
2. Should redirect to `https://auth.basicstack.de`
3. Login with Pocket ID credentials
4. Should redirect back to OpenCloud
5. User auto-provisioned in OpenLDAP
## LDAP Directory Structure
```
dc=basicstack,dc=de
├── cn=admin (admin user)
├── ou=users
│ └── (auto-provisioned users from OIDC)
└── ou=groups
├── cn=opencloudUsers (default users group)
└── cn=opencloudAdmins (administrators group)
```
## Configuration Details
### OpenLDAP Connection Details
- **Service:** `openldap.opencloud.svc.cluster.local`
- **Port:** 389 (LDAP), 636 (LDAPS - disabled for internal use)
- **Base DN:** `dc=basicstack,dc=de`
- **Admin DN:** `cn=admin,dc=basicstack,dc=de`
- **User Base:** `ou=users,dc=basicstack,dc=de`
- **Group Base:** `ou=groups,dc=basicstack,dc=de`
### OpenCloud Services Excluded
- **search** - Broken in v7.2.0
- **idp** - Using external Pocket ID for authentication
- **idm** - Using external OpenLDAP for user storage
### OIDC Configuration
- **Issuer:** `https://auth.basicstack.de`
- **Client ID:** (from `opencloud-oidc-secret`)
- **Scopes:** `openid profile email groups offline_access`
- **Auto-provisioning:** Enabled
- **User claim:** `email`
- **Role claim:** `groups`
### Role Mapping
OIDC groups → OpenCloud roles:
- `opencloudAdmin` → admin
- `opencloudSpaceAdmin` → spaceadmin
- `opencloudUser` → user
- `opencloudGuest` → guest
## Troubleshooting
### OpenLDAP not starting
```bash
# Check logs
kubectl logs -n opencloud -l app=openldap
# Check PVCs
kubectl get pvc -n opencloud
# Check events
kubectl get events -n opencloud --sort-by='.lastTimestamp' | tail -20
```
### OpenCloud can't connect to LDAP
```bash
# Test LDAP connectivity from OpenCloud pod
kubectl exec -n opencloud deployment/opencloud -- \
nc -zv openldap.opencloud.svc.cluster.local 389
# Check LDAP service
kubectl get svc -n opencloud openldap
# Check LDAP endpoints
kubectl get endpoints -n opencloud openldap
```
### Users not auto-provisioning
```bash
# Check OpenCloud proxy logs
kubectl logs -n opencloud -l app=opencloud | grep -i provision
# Check OIDC flow
kubectl logs -n opencloud -l app=opencloud | grep -i oidc
# Verify LDAP directory structure
kubectl exec -n opencloud deployment/openldap -- \
ldapsearch -x -D "cn=admin,dc=basicstack,dc=de" -w "<password>" \
-b "ou=users,dc=basicstack,dc=de" -LLL
```
### View auto-provisioned users
```bash
# List all users in LDAP
kubectl exec -n opencloud deployment/openldap -- \
ldapsearch -x -D "cn=admin,dc=basicstack,dc=de" -w "<password>" \
-b "ou=users,dc=basicstack,dc=de" -LLL "(objectClass=inetOrgPerson)"
```
## Backup and Maintenance
### Backup LDAP Data
```bash
# Backup entire LDAP directory
kubectl exec -n opencloud deployment/openldap -- \
slapcat -l /tmp/backup.ldif
kubectl cp opencloud/$LDAP_POD:/tmp/backup.ldif ./ldap-backup-$(date +%Y%m%d).ldif
```
### Monitor LDAP Performance
```bash
# Check LDAP stats
kubectl exec -n opencloud deployment/openldap -- \
ldapsearch -x -H ldapi:/// -Y EXTERNAL -b "cn=Monitor" -LLL
```
## Files
- `openldap-deployment.yaml` - OpenLDAP Kubernetes deployment
- `openldap-admin-sealed.yaml` - Admin credentials (sealed secret)
- `ldap-init-structure.ldif` - Initial directory structure
- `opencloud-deployment.yaml` - OpenCloud deployment (updated for external LDAP)
- `opencloud-configmap.yaml` - OpenCloud configuration (updated for external LDAP)
## References
- OpenLDAP: https://www.openldap.org/
- OpenCloud LDAP Configuration: https://docs.opencloud.eu/
- Pocket ID: https://github.com/stonith404/pocket-id

View file

@ -1,37 +0,0 @@
# OpenLDAP Directory Structure Initialization
# This LDIF creates the organizational units needed by OpenCloud
#
# Apply with:
# kubectl exec -n opencloud deployment/openldap -- \
# ldapadd -x -D "cn=admin,dc=basicstack,dc=de" -w <admin-password> -f /tmp/init-structure.ldif
#
# Or copy to pod and apply:
# kubectl cp ldap-init-structure.ldif opencloud/openldap-<pod-id>:/tmp/init-structure.ldif
# kubectl exec -n opencloud openldap-<pod-id> -- \
# ldapadd -x -D "cn=admin,dc=basicstack,dc=de" -w <admin-password> -f /tmp/init-structure.ldif
# Create Users Organizational Unit
dn: ou=users,dc=basicstack,dc=de
objectClass: organizationalUnit
ou: users
description: OpenCloud Users
# Create Groups Organizational Unit
dn: ou=groups,dc=basicstack,dc=de
objectClass: organizationalUnit
ou: groups
description: OpenCloud Groups
# Example: Create default user group
dn: cn=opencloudUsers,ou=groups,dc=basicstack,dc=de
objectClass: groupOfNames
cn: opencloudUsers
description: Default OpenCloud Users Group
member: cn=admin,dc=basicstack,dc=de
# Example: Create admin group
dn: cn=opencloudAdmins,ou=groups,dc=basicstack,dc=de
objectClass: groupOfNames
cn: opencloudAdmins
description: OpenCloud Administrators
member: cn=admin,dc=basicstack,dc=de

View file

@ -1,68 +0,0 @@
---
# One-shot Job to load the OpenCloud LDAP schema into the running OpenLDAP instance.
# Connect via network LDAP as cn=admin,cn=config (rootdn of the config database).
# Re-run by deleting and recreating the Job; idempotent (exits 0 if schema already present).
apiVersion: batch/v1
kind: Job
metadata:
name: opencloud-ldap-schema-loader
namespace: opencloud
spec:
ttlSecondsAfterFinished: 86400
backoffLimit: 10
template:
spec:
restartPolicy: OnFailure
containers:
- name: schema-loader
image: osixia/openldap:1.5.0
command:
- /bin/bash
- -c
- |
set -e
echo "Waiting for OpenLDAP to be ready..."
until ldapsearch -x -H ldap://openldap:389 \
-D "cn=admin,dc=basicstack,dc=de" \
-w "$LDAP_ADMIN_PASSWORD" \
-b "dc=basicstack,dc=de" \
-s base "(objectClass=*)" dn 2>&1 | grep -q "result: 0"; do
echo "Not ready yet, retrying in 5s..."
sleep 5
done
echo "OpenLDAP is ready"
if ldapsearch -x -H ldap://openldap:389 \
-D "cn=admin,cn=config" \
-w "$LDAP_CONFIG_PASSWORD" \
-b "cn=schema,cn=config" \
"(cn={*}opencloud)" dn 2>/dev/null | grep -qi "opencloud"; then
echo "OpenCloud schema already present, nothing to do"
exit 0
fi
echo "Loading OpenCloud LDAP schema..."
ldapadd -x -H ldap://openldap:389 \
-D "cn=admin,cn=config" \
-w "$LDAP_CONFIG_PASSWORD" \
-f /schemas/10_opencloud_schema.ldif
echo "OpenCloud schema loaded successfully"
env:
- name: LDAP_ADMIN_PASSWORD
valueFrom:
secretKeyRef:
name: openldap-admin-secret
key: admin-password
- name: LDAP_CONFIG_PASSWORD
valueFrom:
secretKeyRef:
name: openldap-admin-secret
key: config-password
volumeMounts:
- name: schemas
mountPath: /schemas
volumes:
- name: schemas
configMap:
name: opencloud-ldap-schema

View file

@ -1,58 +0,0 @@
---
# OpenCloud LDAP schema ConfigMap
# Defines openCloudUser objectClass and related attributes (OIDs under 1.3.6.1.4.1.63016)
# Mounted into OpenLDAP pod and loaded via lifecycle postStart hook
apiVersion: v1
kind: ConfigMap
metadata:
name: opencloud-ldap-schema
namespace: opencloud
data:
10_opencloud_schema.ldif: |
dn: cn=opencloud,cn=schema,cn=config
objectClass: olcSchemaConfig
cn: opencloud
olcAttributeTypes: ( 1.3.6.1.4.1.63016.1.1.1
NAME 'openCloudUUID'
DESC 'A non-reassignable and persistent account ID'
EQUALITY caseIgnoreMatch
SUBSTR caseIgnoreSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256}
SINGLE-VALUE )
olcAttributeTypes: ( 1.3.6.1.4.1.63016.1.1.2
NAME 'openCloudExternalIdentity'
DESC 'Represents the objectIdentity resource type of the Graph API'
EQUALITY caseIgnoreMatch
SUBSTR caseIgnoreSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
olcAttributeTypes: ( 1.3.6.1.4.1.63016.1.1.3
NAME 'openCloudUserEnabled'
DESC 'Indicates if the user account is enabled'
EQUALITY booleanMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.7
SINGLE-VALUE )
olcAttributeTypes: ( 1.3.6.1.4.1.63016.1.1.4
NAME 'openCloudUserType'
DESC 'Specifies the user type (Member or Guest)'
EQUALITY caseIgnoreMatch
SUBSTR caseIgnoreSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
SINGLE-VALUE )
olcAttributeTypes: ( 1.3.6.1.4.1.63016.1.1.5
NAME 'openCloudLastSignInTimestamp'
DESC 'Timestamp of the most recent authentication event'
EQUALITY generalizedTimeMatch
ORDERING generalizedTimeOrderingMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.24
SINGLE-VALUE )
olcObjectClasses: ( 1.3.6.1.4.1.63016.1.2.1
NAME 'openCloudObject'
DESC 'Base auxiliary class for OpenCloud objects'
AUXILIARY
MAY ( openCloudUUID ) )
olcObjectClasses: ( 1.3.6.1.4.1.63016.1.2.2
NAME 'openCloudUser'
DESC 'Auxiliary class for OpenCloud user accounts'
AUXILIARY
SUP openCloudObject
MAY ( openCloudExternalIdentity $ openCloudUserEnabled $ openCloudUserType $ openCloudLastSignInTimestamp ) )

View file

@ -1,16 +0,0 @@
---
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
creationTimestamp: null
name: openldap-admin-secret
namespace: opencloud
spec:
encryptedData:
admin-password: AgCfe8Y89Uy1gGJtkD/8QUn0KJMGKHvVc53f8N0tJk0KECT5Bxas9zNxL6UF+3Uxz/IgP4F3ZMgQ0AEy5GpnOHHZkWxjXjs64WGiUauA59MCxMQT6kwKtLkTX7M632S0HKwbS+vJi2S/r6JmMjSfCQJrsf2XKrfeyeg3+MqxwDSjIYdfyUTvNJ190QA0guqEpJltch8+Mr7Va0oxbwO64gDSXyjIipOCEHiGlMEiYlFXgEI/xUBEsvWN5jwgjmCkNTvx0id5+5ZhZHtWbLVBTP0caAqPhgsxjI0OhNPb8ZSzLx2uPgBK4m2jw6Kw4TvL+mJq1tvGjxnxVnDil4GhB4sjm0PKsL1EOeXzJphQWHvk6wDgrf3KW9uQPBODfVH1xUYCkrFWa/YzYM+rYJtnKdUVfqV6U//Vd64BXjz0Opx8twd9ljwlMBnW+8RRy8940m1bGEaKWeZNdXU+gVGEd+E4dYdqycz46UvktQmyFQgNjesGNbpwc+fVw4E2uDCLM8Lo35PnIcGeaMRQeurQuSFbKntFazB6nggkweesiFcPLSoHWUsvrdyK7h9lkLOg0KG5SoUs4aUkd2L4UpLHGMxV2PioxMPI6epe3AFIkcuMO/ukJuA7Cg58P9ZUZiED2dhkF9hOoUibaAKndwdPtQzBQUofj0LMVqHcSKjH3jC1eOAbKzvTHELfdWql5fldE5IEFP/HkfguGAqvV8XGm4QPb4D1Tj2raMHiiG39rq4mHgcdF++9IGjxdTYnaw==
config-password: AgBtWsvBgzOq6mHKZ7HLd/SqEVGFozSkXKWyqLUVKFNNnjwVwvZU/vUFKzKxC7xqoNjccqrQjcxbAPAA8USxZyUrW+Qws/v7J4w2bvtg3nklZfG8FGZZvaLFkUJKV0ZWa+kg3CFoFk5hHH3IpesfZTOCJPTiCey61os0m8DS1g9fre1eKVZz5RFk3d79Pv+O8P4ao9RF+dXPYuBMLHxg+b77p/LzeV0sCIicdLKNrQaPGj8OP9JyGpD8XLX3enWFtFNtX48gLnVGrzNLWo/Ic2LQZoqM7XUB2d/zlB6CN6KrnkZEoR3L5GPU7fVXAga6L1JqTiPD8SG7BnWlxdKZEeoko94NciBQcIG1igE4+HTMBx3kbES+eh+kV2ImLzKTYuUuoN7TUJnOXP10x2zf1OhdKumqFqNFVPPJW8zDtzIflkzwFN79cY+WP5XOL/XUnLIISIGGpW4tTyfsLL3BnoWtkfuSjeH6JEsq8xOcVSyg0/6hCmz9ZztkmqMjTEkL0++agaXxPQgyvsxX8r/8TCwVt02RGRbPUQqH9r6/xdIX4SQEzoFhbINqPgCVuY7U/cNqAOxwP7FmG6Dcm8wAEtFTEFqU3T5eo0iuQqpfN3iXsFahf7e28Jh2N5JS344RiqA0HAmu4QT67maXLQrvkbL6zYY5pROsm2y2xgZP432AZ98hw3BooDelkLZZ9A5HNgBg8I/kSBgX2xLZR+Nc5L7KH240zR5/0Hc0jh9fjzdibISQ76y4gTEp1Y2KSg==
template:
metadata:
creationTimestamp: null
name: openldap-admin-secret
namespace: opencloud

View file

@ -1,147 +0,0 @@
---
# OpenLDAP Deployment for OpenCloud
# Provides external LDAP directory for user and group storage
# OpenLDAP Data Storage
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: openldap-data
namespace: opencloud
spec:
accessModes:
- ReadWriteOnce
storageClassName: hcloud-volumes-encrypted
resources:
requests:
storage: 10Gi
---
# OpenLDAP Config Storage
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: openldap-config
namespace: opencloud
spec:
accessModes:
- ReadWriteOnce
storageClassName: hcloud-volumes-encrypted
resources:
requests:
storage: 1Gi
---
# OpenLDAP Service
apiVersion: v1
kind: Service
metadata:
name: openldap
namespace: opencloud
spec:
type: ClusterIP
selector:
app: openldap
ports:
- name: ldap
port: 389
targetPort: 389
protocol: TCP
- name: ldaps
port: 636
targetPort: 636
protocol: TCP
---
# OpenLDAP Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: openldap
namespace: opencloud
spec:
replicas: 1
selector:
matchLabels:
app: openldap
template:
metadata:
labels:
app: openldap
spec:
containers:
- name: openldap
image: osixia/openldap:1.5.0
env:
# Organization and domain
- name: LDAP_ORGANISATION
value: "BasicStack"
- name: LDAP_DOMAIN
value: "basicstack.de"
# Admin credentials
- name: LDAP_ADMIN_PASSWORD
valueFrom:
secretKeyRef:
name: openldap-admin-secret
key: admin-password
# Config admin password
- name: LDAP_CONFIG_PASSWORD
valueFrom:
secretKeyRef:
name: openldap-admin-secret
key: config-password
# Base DN will be: dc=basicstack,dc=de
# Admin DN will be: cn=admin,dc=basicstack,dc=de
# Logging
- name: LDAP_LOG_LEVEL
value: "256"
# TLS disabled for internal cluster communication
- name: LDAP_TLS
value: "false"
# Remove default database and start fresh
- name: LDAP_REMOVE_CONFIG_AFTER_SETUP
value: "false"
ports:
- containerPort: 389
name: ldap
- containerPort: 636
name: ldaps
volumeMounts:
- name: openldap-data
mountPath: /var/lib/ldap
- name: openldap-config
mountPath: /etc/ldap/slapd.d
resources:
requests:
memory: "256Mi"
cpu: "100m"
limits:
memory: "512Mi"
cpu: "500m"
livenessProbe:
tcpSocket:
port: 389
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
readinessProbe:
tcpSocket:
port: 389
initialDelaySeconds: 20
periodSeconds: 5
timeoutSeconds: 3
volumes:
- name: openldap-data
persistentVolumeClaim:
claimName: openldap-data
- name: openldap-config
persistentVolumeClaim:
claimName: openldap-config