OpenLDAP + phpLDAPadmin + Keycloak stack for centralized identity management on Kubernetes.
- OpenLDAP 2.6.x - Directory service with
startcodex/openldapimage - phpLDAPadmin - Web-based LDAP administration
- Keycloak - Identity Provider with SSO (OAuth2, OIDC, SAML)
- LDAP Federation - Auto-configure Keycloak to use LDAP as user store
- Bootstrap - Automatic creation of OUs (users, groups, services)
- Production Ready - NetworkPolicy, PodDisruptionBudget, ServiceMonitor
- Flexible Services - ClusterIP, NodePort, LoadBalancer support
# Add repository
helm repo add ldap-stack https://start-codex.github.io/ldap-stack-helm-chart
helm repo update
# Install
helm install my-ldap ldap-stack/ldap-stack \
--set openldap.config.organisation="My Company" \
--set openldap.config.domain="mycompany.com" \
--set openldap.config.adminPassword="secure-password" \
--set keycloak.admin.username="admin" \
--set keycloak.admin.password="secure-password"helm install ldap ldap-stack/ldap-stack \
--namespace identity --create-namespace \
--set openldap.config.organisation="ACME Corp" \
--set openldap.config.domain="acme.com" \
--set openldap.config.adminPassword="ldap-secret" \
--set keycloak.admin.username="admin" \
--set keycloak.admin.password="keycloak-secret"helm install ldap ldap-stack/ldap-stack \
--set openldap.config.organisation="ACME Corp" \
--set openldap.config.domain="acme.com" \
--set openldap.config.adminPassword="ldap-secret" \
--set openldap.bootstrap.enabled=true \
--set openldap.bootstrap.createDefaultOUs=true \
--set keycloak.admin.username="admin" \
--set keycloak.admin.password="keycloak-secret"helm install ldap ldap-stack/ldap-stack \
--set openldap.config.organisation="ACME Corp" \
--set openldap.config.domain="acme.com" \
--set openldap.config.adminPassword="ldap-secret" \
--set keycloak.admin.username="admin" \
--set keycloak.admin.password="keycloak-secret" \
--set keycloak.realm.import.enabled=true \
--set ldapFederation.enabled=truehelm install ldap ldap-stack/ldap-stack \
--set openldap.config.organisation="ACME Corp" \
--set openldap.config.domain="acme.com" \
--set openldap.config.adminPassword="ldap-secret" \
--set keycloak.admin.username="admin" \
--set keycloak.admin.password="keycloak-secret" \
--set keycloak.devMode=false \
--set keycloak.production.hostname="auth.acme.com" \
--set keycloak.production.database.host="postgres.database.svc" \
--set keycloak.production.database.password="db-secret" \
--set networkPolicy.enabled=true \
--set podDisruptionBudget.enabled=true \
--set metrics.serviceMonitor.enabled=true| Parameter | Description |
|---|---|
openldap.config.organisation |
Organization name |
openldap.config.domain |
LDAP domain (e.g., mycompany.com) |
openldap.config.adminPassword |
LDAP admin password |
keycloak.admin.username |
Keycloak admin username |
keycloak.admin.password |
Keycloak admin password |
| Parameter | Description | Default |
|---|---|---|
openldap.enabled |
Enable OpenLDAP | true |
openldap.image.repository |
Image repository | startcodex/openldap |
openldap.image.tag |
Image tag | 2.1.0 |
openldap.service.type |
Service type | ClusterIP |
openldap.service.ldapPort |
LDAP port | 389 |
openldap.service.ldapsPort |
LDAPS port | 636 |
openldap.persistence.enabled |
Enable persistence | true |
openldap.persistence.data.size |
Data PVC size | 1Gi |
openldap.bootstrap.enabled |
Enable bootstrap | false |
openldap.bootstrap.createDefaultOUs |
Create default OUs | true |
| Parameter | Description | Default |
|---|---|---|
phpldapadmin.enabled |
Enable phpLDAPadmin | true |
phpldapadmin.image.repository |
Image repository | phpldapadmin/phpldapadmin |
phpldapadmin.image.tag |
Image tag | latest |
phpldapadmin.ldap.loginAttr |
Login attribute (uid for username, DN for full DN) |
uid |
phpldapadmin.ldap.alertRootDN |
Block rootdn login | false |
phpldapadmin.extraEnv |
Extra environment variables | [] |
phpldapadmin.service.type |
Service type | ClusterIP |
phpldapadmin.service.port |
Service port | 8080 |
phpldapadmin.ingress.enabled |
Enable Ingress | false |
| Parameter | Description | Default |
|---|---|---|
keycloak.enabled |
Enable Keycloak | true |
keycloak.devMode |
Run in dev mode | true |
keycloak.service.type |
Service type | ClusterIP |
keycloak.service.port |
Service port | 8080 |
keycloak.ingress.enabled |
Enable Ingress | false |
keycloak.realm.import.enabled |
Enable realm import | false |
keycloak.realm.import.realmJson |
Inline realm JSON | "" |
| Parameter | Description | Default |
|---|---|---|
ldapFederation.enabled |
Auto-configure LDAP federation | false |
ldapFederation.realmName |
Realm name | master |
ldapFederation.editMode |
Edit mode | WRITABLE |
| Parameter | Description | Default |
|---|---|---|
networkPolicy.enabled |
Enable NetworkPolicies | false |
podDisruptionBudget.enabled |
Enable PDB | false |
podDisruptionBudget.maxUnavailable |
Max unavailable pods | 1 |
metrics.serviceMonitor.enabled |
Enable ServiceMonitor | false |
# phpLDAPadmin
kubectl port-forward svc/<release>-phpldapadmin 8080:8080
# Keycloak
kubectl port-forward svc/<release>-keycloak 8081:8080
# OpenLDAP
kubectl port-forward svc/<release>-openldap 389:389keycloak:
service:
type: NodePort
nodePort: 30808
phpldapadmin:
service:
type: NodePort
nodePort: 30080keycloak:
service:
type: LoadBalancer
loadBalancerIP: "10.0.0.100"keycloak:
ingress:
enabled: true
className: nginx
hosts:
- host: auth.mycompany.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: keycloak-tls
hosts:
- auth.mycompany.comEnable auto-configuration:
keycloak:
realm:
import:
enabled: true
ldapFederation:
enabled: true
editMode: "WRITABLE"- Access Keycloak Admin Console
- Go to User Federation > Add provider > LDAP
- Configure:
| Setting | Value |
|---|---|
| Connection URL | ldap://<release>-openldap:389 |
| Bind DN | cn=admin,dc=<domain> |
| Users DN | ou=users,dc=<domain> |
| Username LDAP attribute | uid |
| RDN LDAP attribute | uid |
| UUID LDAP attribute | entryUUID |
| User object classes | inetOrgPerson, posixAccount |
keycloak:
devMode: false
production:
hostname: "auth.mycompany.com"
database:
vendor: postgres
host: "postgres.database.svc"
port: 5432
database: keycloak
username: keycloak
password: "db-password"networkPolicy:
enabled: true
openldap:
allowFromNamespaces:
- sonarqube
- gitea
keycloak:
ingressNamespace: "ingress-nginx"
allowFromNamespaces:
- defaultmetrics:
serviceMonitor:
enabled: true
labels:
release: prometheus
interval: "30s"Chart 1.4.x replaces osixia/phpldapadmin (abandoned, Debian 10 EOL, 122 critical CVEs) with phpldapadmin/phpldapadmin (leenooks, Alpine, PHP 8.4, actively maintained).
The new phpLDAPadmin uses uid attribute for login by default. This means:
- Regular users: Login with their
uid(e.g.,julio.caicedo) and LDAP password. Works out of the box. - Admin (rootdn): The default
cn=admin,dc=example,dc=comis a virtual rootdn with no real LDAP entry. The new phpLDAPadmin requires the DN to exist as an entry to complete login. Login with DN will authenticate but then fail with "DN doesn't exist".
Create a real admin user entry in LDAP:
kubectl exec -i <openldap-pod> -- ldapadd -x -H ldap://localhost:389 \
-D "cn=admin,dc=example,dc=com" -w <admin-password> <<EOF
dn: cn=LDAP Admin,ou=users,dc=example,dc=com
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: top
cn: LDAP Admin
sn: Admin
givenName: LDAP
uid: admin
uidNumber: 999
gidNumber: 999
homeDirectory: /nonexistent
loginShell: /usr/sbin/nologin
EOF
# Set the password (same as rootdn or a dedicated one)
kubectl exec <openldap-pod> -- ldappasswd -x -H ldap://localhost:389 \
-D "cn=admin,dc=example,dc=com" -w <admin-password> \
-s <new-password> "cn=LDAP Admin,ou=users,dc=example,dc=com"Then login with admin as USER ID and the password you set.
The service port changes from 80 to 8080 (container port). If you expose phpLDAPadmin via LoadBalancer or Ingress on port 80, set:
phpldapadmin:
service:
port: 80 # external port, maps to container 8080kubectl get pods -n <namespace>
kubectl logs -f <pod-name>kubectl exec -it <openldap-pod> -- ldapsearch -x -H ldap://localhost:389 \
-b "dc=mycompany,dc=com" \
-D "cn=admin,dc=mycompany,dc=com" \
-w <password># OpenLDAP admin password
kubectl get secret <release>-openldap-credentials -o jsonpath="{.data.admin-password}" | base64 -d
# Keycloak admin password
kubectl get secret <release>-keycloak-credentials -o jsonpath="{.data.admin-password}" | base64 -dhelm uninstall <release> -n <namespace>
# Remove PVCs
kubectl delete pvc -l app.kubernetes.io/instance=<release> -n <namespace>Apache License 2.0