Skip to content

OpenLDAP Directory

OpenLDAP provides a supplementary LDAP directory for services that require LDAP-based authentication or group lookups. Access is Tailscale-only. The web UI (phpLDAPadmin) is at https://openldap.shared.cwiq.io.

Connection Details

Property Value
LDAP URI ldap://openldap-shared-cwiq-io:389
Base DN dc=cwiq,dc=io
Admin DN cn=admin,dc=cwiq,dc=io
People OU ou=People,dc=cwiq,dc=io
Groups OU ou=Groups,dc=cwiq,dc=io
phpLDAPadmin https://openldap.shared.cwiq.io
Access Tailscale-only (openldap-shared-cwiq-io)

Tailscale-only access

OpenLDAP is not accessible from the public internet. All clients must connect via Tailscale using the MagicDNS hostname openldap-shared-cwiq-io. LDAP binds from outside the tailnet will be refused.

Directory Structure

dc=cwiq,dc=io
├── ou=People                     ← user accounts (inetOrgPerson)
├── ou=Groups
│   ├── ou=cwiq                   ← CWIQ org groups
│   │   ├── cn=admins
│   │   ├── cn=team-leaders
│   │   └── cn=members
│   └── ou=default                ← default org groups
│       ├── cn=admins
│       ├── cn=team-leaders
│       └── cn=members
└── ou=ServiceAccounts            ← service bind accounts

Org-scoped groups live under ou=Groups,dc=cwiq,dc=io. Each organization slug gets its own sub-OU with the three standard groups (admins, team-leaders, members).

Docker Stack

Container Image Ports Purpose
openldap-slapd bitnami/openldap:2.6.9 389, 636 LDAP directory (slapd)
openldap-phpldapadmin osixia/phpldapadmin:0.9.0 internal 8080 Web management UI
openldap-nginx nginx:1.27-alpine 443, 80 SSL termination

All images pull through Nexus proxy (nexus.shared.cwiq.io:8444).

Common LDAP Operations

Test Connection

# Verify LDAP server is responding (anonymous bind)
ldapsearch -x \
  -H ldap://openldap-shared-cwiq-io:389 \
  -b "" -s base namingContexts

Search All Users

ldapsearch -x \
  -H ldap://openldap-shared-cwiq-io:389 \
  -D "cn=admin,dc=cwiq,dc=io" -W \
  -b "ou=People,dc=cwiq,dc=io" \
  "(objectClass=inetOrgPerson)" uid cn mail

Search All Groups

ldapsearch -x \
  -H ldap://openldap-shared-cwiq-io:389 \
  -D "cn=admin,dc=cwiq,dc=io" -W \
  -b "ou=Groups,dc=cwiq,dc=io" \
  "(objectClass=groupOfNames)" cn member

Search Specific User

ldapsearch -x \
  -H ldap://openldap-shared-cwiq-io:389 \
  -D "cn=admin,dc=cwiq,dc=io" -W \
  -b "ou=People,dc=cwiq,dc=io" \
  "(uid=john.doe)"

Service Operations

Check Container Status

ssh openldap@openldap-shared-cwiq-io
sudo -u openldap docker compose -f /data/openldap/docker-compose.yml ps

View Logs

sudo -u openldap docker compose -f /data/openldap/docker-compose.yml logs -f

Restart

ssh ansible@ansible-shared-cwiq-io
ansible-helper
ansible-playbook openldap/restart.yml

Stop

ansible-playbook openldap/stop.yml

Deployment

All configuration is in group_vars/all.yml.template. The template must be copied to inventory/shared/group_vars/all.yml and all CHANGE_ME password values set before running the playbook.

Key Configuration Variables

Variable Description
openldap_domain LDAP domain (cwiq.iodc=cwiq,dc=io)
openldap_base_dn Base DN (dc=cwiq,dc=io)
openldap_admin_username LDAP admin username
openldap_admin_password Admin password (must change from CHANGE_ME)
openldap_org_names Org slugs — each gets a sub-OU under ou=Groups
openldap_users Seed users
openldap_org_groups Org-scoped groups
openldap_service_accounts Service bind accounts

Deploy

ssh ansible@ansible-shared-cwiq-io
ansible-helper

# Provision server
ansible-playbook openldap/setup.yml -i openldap/inventory/shared.yml

# Deploy SSL certificate (from cert-server)
ansible-playbook cert-server/ssl-deploy-all.yml --limit openldap-shared-cwiq-io

# Deploy OpenLDAP stack
ansible-playbook openldap/deploy-openldap.yml -i openldap/inventory/shared.yml

SSL Certificate

The SSL certificate (openldap.shared.cwiq.io) is managed by cert-server:

  • Key type: ECDSA
  • Cert path on server: /data/ssl/openldap.shared.cwiq.io/
  • Reload command: docker restart openldap-nginx
  • Renewal: automatic via ssl-renew-deploy.timer on the cert-server