I will need a LDAP server as part of the authentication system of my TMC-SM deployment. This LDAP server is a separate application workload and can be used by other components as well such as Ops Manager, TKGI, etc. I will be deploying the LDAP server as a kubernetes workload in a TKGI cluster.

Prepare the TKGI cluster

The only thing I needed to do to prepare the TKGI cluster, is to create a default StorageClass. That’s because there was no default at all, and I need to store the LDAP data in a persistent volume, so that the data will not be wiped out in case of pod restarts.

In the Ops Manager VM, which I use as my jumpbox for my TKGI cluster, I created a YAML file for the StorageClass definition:

cat > sc.yaml <<EOF
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: default-sc
  annotations:
      storageclass.kubernetes.io/is-default-class: "true"
provisioner: csi.vsphere.vmware.com
allowVolumeExpansion: true
parameters:
  datastoreurl: "ds:///vmfs/volumes/67f5ab40-e58896f2-b0e5-b49691d2ecdc/"
  
EOF

Then, run kubectl apply on the yaml:

k apply -f sc.yaml
storageclass.storage.k8s.io/default-sc created

Create a namespace for the LDAP app

To keep things organized, I need to create a namespace named “openldap” for the LDAP workload.

k create ns openldap

Create a certificate and key for LDAP TLS

These are needed so that the LDAP server can use TLS/SSL. I will create a self-signed certificate.

Create a SSL config with Subject Alternative Name (SAN). The config file is named ldap-cert.conf.

cat > ldap-cert.conf <<EOF
[ req ]
default_bits       = 2048
distinguished_name = req_distinguished_name
req_extensions     = req_ext
x509_extensions    = v3_ext
prompt             = no

[ req_distinguished_name ]
C  = US
ST = California
L  = San Francisco
O  = Deep Hackmode Inc
OU = Engineering
CN = ldap.deephackmode.io

[ req_ext ]
subjectAltName = @alt_names

[ v3_ext ]
subjectAltName = @alt_names
basicConstraints = CA:FALSE
keyUsage = digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth

[ alt_names ]
DNS.1 = ldap.deephackmode.io
EOF

Generate the Cert and Key:


openssl req -x509 -nodes -days 365 \
  -newkey rsa:2048 \
  -keyout ldap.deephackmode.io.key \
  -out ldap.deephackmode.io.crt \
  -config ldap-cert.conf

This gives 2 files:

  • ldap.deephackmode.io.crt – TLS cert
  • ldap.deephackmode.io.key – Private key

Create the TLS secret

This is to store the cert and key in a kubernetes secret that can be used by the workload app (LDAP).


kubectl -n openldap create secret tls ldap-tls \
  --cert=ldap.deephackmode.io.crt \
  --key=ldap.deephackmode.io.key

Create the Deployment definition YAML

Create the definition YAML for the kubernetes deployment, pvc’s and service.


cat > openldap.yaml <<EOF
apiVersion: v1
kind: Service
metadata:
  name: openldap-service
spec:
  selector:
    app: openldap
  ports:
    - name: ldap
      port: 389
      targetPort: 389
    - name: ldaps
      port: 636
      targetPort: 636
  type: LoadBalancer

---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: ldap-data
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: ldap-config
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 512Mi
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: openldap
spec:
  replicas: 1
  selector:
    matchLabels:
      app: openldap
  template:
    metadata:
      labels:
        app: openldap
    spec:
      hostname: ldap
      containers:
        - name: openldap
          image: osixia/openldap:latest
          ports:
            - containerPort: 389
            - containerPort: 636
          env:
            - name: LDAP_TLS
              value: "true"
            - name: LDAP_TLS_CRT_FILENAME
              value: "tls.crt"
            - name: LDAP_TLS_KEY_FILENAME
              value: "tls.key"
            - name: LDAP_TLS_CA_CRT_FILENAME
              value: "tls.crt"
            - name: LDAP_TLS_VERIFY_CLIENT
              value: "try"
            - name: LDAP_DOMAIN
              value: "deephackmode.io"
            - name: LDAP_ORGANISATION
              value: "Deep Hack Mode Inc"
            - name: LDAP_TLS_DH_PARAM_FILENAME
              value: "false"
          volumeMounts:
            - name: certs
              mountPath: /container/service/slapd/assets/certs
            - name: certs-target
              mountPath: /certs-readonly
              readOnly: true
            - name: ldap-data
              mountPath: /var/lib/ldap
            - name: ldap-config
              mountPath: /etc/ldap/slapd.d
      initContainers:
        - name: copy-certs
          image: busybox
          command: ["sh", "-c", "cp /certs-readonly/* /certs-writable/"]
          volumeMounts:
            - name: certs
              mountPath: /certs-writable
            - name: certs-target
              mountPath: /certs-readonly
              readOnly: true
      volumes:
        - name: certs
          emptyDir: {}
        - name: certs-target
          secret:
            secretName: ldap-tls
        - name: ldap-data
          persistentVolumeClaim:
            claimName: ldap-data
        - name: ldap-config
          persistentVolumeClaim:
            claimName: ldap-config


EOF

Create the LDAP deployment

Create the kubernetes resources from the YAML:

k -n openldap apply -f openldap.yaml

Tail the container logs to verify when the server is ready.

k -n openldap logs openldap-778b799856-49nf5 -f
...
***  INFO   | 2025-06-01 18:55:18 | Add image bootstrap ldif...
***  INFO   | 2025-06-01 18:55:18 | Add custom bootstrap ldif...
***  INFO   | 2025-06-01 18:55:18 | Add TLS config...
Generating DH parameters, 2048 bit long safe prime, generator 2
This is going to take a long time

It might be stuck in that line for like two minutes. It should continue eventually.

...
***  INFO   | 2025-06-01 18:56:40 | Disable replication config...
***  INFO   | 2025-06-01 18:56:40 | Stop OpenLDAP...
***  INFO   | 2025-06-01 18:56:40 | Configure ldap client TLS configuration...
***  INFO   | 2025-06-01 18:56:40 | Remove config files...
***  INFO   | 2025-06-01 18:56:40 | First start is done...
***  INFO   | 2025-06-01 18:56:40 | Remove file /container/environment/99-default/default.startup.yaml
***  INFO   | 2025-06-01 18:56:40 | Environment files will be proccessed in this order :
Caution: previously defined variables will not be overriden.
/container/environment/99-default/default.yaml

To see how this files are processed and environment variables values,
run this container with '--loglevel debug'
***  INFO   | 2025-06-01 18:56:40 | Running /container/run/process/slapd/run...
683ca268 @(#) $OpenLDAP: slapd 2.4.57+dfsg-1~bpo10+1 (Jan 30 2021 06:59:51) $
        Debian OpenLDAP Maintainers <pkg-openldap-devel@lists.alioth.debian.org>
683ca268 slapd starting

Install and run the ldapsearch utility

In the jumpbox “numbat”, which I use to examine my TKGM clusters, install the ldap-utils package to make the ldapsearch cli available for use.

sudo apt update
sudo apt install ldap-utils

Run ldapsearch using TLS/SSL and the cert file ldap.deephackmode.io.crt as the CA file.

ubuntu@numbat:~$ env LDAPTLS_CACERT=ldap.deephackmode.io.crt ldapsearch -H 'ldaps://ldap.deephackmode.io' -b dc=deephackmode,dc=io -D "cn=admin,dc=deephackmode,dc=io" -w admin
# extended LDIF
#
# LDAPv3
# base <dc=deephackmode,dc=io> with scope subtree
# filter: (objectclass=*)
# requesting: ALL
#

# deephackmode.io
dn: dc=deephackmode,dc=io
objectClass: top
objectClass: dcObject
objectClass: organization
o: Deep Hackmode Inc
dc: deephackmode

# search result
search: 2
result: 0 Success

# numResponses: 2
# numEntries: 1
ubuntu@numbat:~$

Populate the LDAP directory

Create a LDIF file with example user and group records for our use case. The userPassword value that is hardcoded in this LDIF is the same in all the user records where it is added, and the password is the literal word incorrect.


cat > ldap.ldif <<EOF
# groups, deephackmode.io
dn: ou=groups,dc=deephackmode,dc=io
objectClass: organizationalUnit
ou: groups

# users, deephackmode.io
dn: ou=users,dc=deephackmode,dc=io
objectClass: organizationalUnit
ou: users

# alexisv, users, deephackmode.io
dn: cn=alexisv,ou=users,dc=deephackmode,dc=io
objectClass: simpleSecurityObject
objectClass: inetOrgPerson
objectClass: posixAccount
cn: alexisv
sn: Villalon
mail: alexisv@deephackmode.io
description: alexisv
userPassword:: e1NTSEF9RC9IZ2FDV2t6SG03alVxRVl1SmJ1QVRWNVBBdTV6NXM=
uid: alexisv
uidNumber: 1000
gidNumber: 1000
homeDirectory: /home/alexisv

# testuser, users, deephackmode.io
dn: cn=testuser,ou=users,dc=deephackmode,dc=io
objectClass: simpleSecurityObject
objectClass: inetOrgPerson
objectClass: posixAccount
cn: testuser
sn: User
mail: testuser@deephackmode.io
description: Test User
userPassword:: e1NTSEF9RC9IZ2FDV2t6SG03alVxRVl1SmJ1QVRWNVBBdTV6NXM=
uid: testuser
uidNumber: 1001
gidNumber: 1000
homeDirectory: /home/testuser

# tmc-admins, groups, deephackmode.io
dn: cn=tmc-admins,ou=groups,dc=deephackmode,dc=io
objectClass: groupOfNames
objectClass: top
cn: tmc-admins
description: tmc sm admins
member: cn=alexisv,ou=users,dc=deephackmode,dc=io

# tmc-members, groups, deephackmode.io
dn: cn=tmc-members,ou=groups,dc=deephackmode,dc=io
objectClass: groupOfNames
objectClass: top
cn: tmc-members
description: tmc sm members
member: cn=alexisv,ou=users,dc=deephackmode,dc=io

# cluster-admins, groups, deephackmode.io
dn: cn=cluster-admins,ou=groups,dc=deephackmode,dc=io
objectClass: groupOfNames
objectClass: top
cn: cluster-admins
description: pks.clusters.admin
member: cn=alexisv,ou=users,dc=deephackmode,dc=io

# cluster-managers, groups, deephackmode.io
dn: cn=cluster-managers,ou=groups,dc=deephackmode,dc=io
objectClass: groupOfNames
objectClass: top
cn: cluster-managers
description: pks.clusters.manage
member: cn=alexisv,ou=users,dc=deephackmode,dc=io

# cluster-devs, groups, deephackmode.io
dn: cn=cluster-devs,ou=groups,dc=deephackmode,dc=io
objectClass: groupOfNames
objectClass: top
cn: cluster-devs
description: developers
member: cn=alexisv,ou=users,dc=deephackmode,dc=io

EOF

A few ldapsearch examples

Run ldapsearch with filter for a specific user record:

ubuntu@numbat:~$ env LDAPTLS_CACERT=ldap.deephackmode.io.crt ldapsearch -H 'ldaps://ldap.deephackmode.io' -b ou=users,dc=deephackmode,dc=io -D "cn=admin,dc=deephackmode,dc=io" -w admin cn=alexisv
# extended LDIF
#
# LDAPv3
# base <ou=users,dc=deephackmode,dc=io> with scope subtree
# filter: cn=alexisv
# requesting: ALL
#

# alexisv, users, deephackmode.io
dn: cn=alexisv,ou=users,dc=deephackmode,dc=io
objectClass: simpleSecurityObject
objectClass: inetOrgPerson
objectClass: posixAccount
cn: alexisv
sn: Villalon
mail: alexisv@deephackmode.io
description: alexisv
userPassword:: e1NTSEF9RC9IZ2FDV2t6SG03alVxRVl1SmJ1QVRWNVBBdTV6NXM=
uid: alexisv
uidNumber: 1000
gidNumber: 1000
homeDirectory: /home/alexisv

# search result
search: 2
result: 0 Success

# numResponses: 2
# numEntries: 1
ubuntu@numbat:~$

Run ldapsearch to retrieve all groups with a specific member, and only output the cn:

ubuntu@numbat:~$ env LDAPTLS_CACERT=ldap.deephackmode.io.crt ldapsearch -H 'ldaps://ldap.deephackmode.io' -b ou=groups,dc=deephackmode,dc=io -D "cn=admin,dc=deephackmode,dc=io" -w admin 'member=cn=alexisv,ou=users,dc=deephackmode,dc=io' cn
# extended LDIF
#
# LDAPv3
# base <ou=groups,dc=deephackmode,dc=io> with scope subtree
# filter: member=cn=alexisv,ou=users,dc=deephackmode,dc=io
# requesting: cn
#

# tmc-admins, groups, deephackmode.io
dn: cn=tmc-admins,ou=groups,dc=deephackmode,dc=io
cn: tmc-admins

# tmc-members, groups, deephackmode.io
dn: cn=tmc-members,ou=groups,dc=deephackmode,dc=io
cn: tmc-members

# cluster-devs, groups, deephackmode.io
dn: cn=cluster-devs,ou=groups,dc=deephackmode,dc=io
cn: cluster-devs

# cluster-admins, groups, deephackmode.io
dn: cn=cluster-admins,ou=groups,dc=deephackmode,dc=io
cn: cluster-admins

# cluster-managers, groups, deephackmode.io
dn: cn=cluster-managers,ou=groups,dc=deephackmode,dc=io
cn: cluster-managers

# search result
search: 2
result: 0 Success

# numResponses: 6
# numEntries: 5
ubuntu@numbat:~$

A few useful LDAP Operations

Add users (from STDIN):

$ env LDAPTLS_CACERT=ldap.deephackmode.io.crt ldapadd -H 'ldaps://ldap.deephackmode.io' -D "cn=admin,dc=deephackmode,dc=io" -w admin
dn: cn=ldapadmin,ou=users,dc=deephackmode,dc=io
objectClass: simpleSecurityObject
objectClass: inetOrgPerson
objectClass: posixAccount
cn: ldapadmin
sn: ldapadmin
mail: ldapadmin@deephackmode.io
description: ldapadmin
userPassword:: e1NTSEF9RC9IZ2FDV2t6SG03alVxRVl1SmJ1QVRWNVBBdTV6NXM=
uid: ldapadmin
uidNumber: 1001
gidNumber: 1001
homeDirectory: /home/ldapadmin
 
adding new entry "cn=ldapadmin,ou=users,dc=deephackmode,dc=io"
 
dn: cn=ldapuser,ou=users,dc=deephackmode,dc=io
objectClass: simpleSecurityObject
objectClass: inetOrgPerson
objectClass: posixAccount
cn: ldapuser
sn: ldapuser
mail: ldapuser@deephackmode.io
description: ldapuser
userPassword:: e1NTSEF9RC9IZ2FDV2t6SG03alVxRVl1SmJ1QVRWNVBBdTV6NXM=
uid: ldapuser
uidNumber: 1002
gidNumber: 1002
homeDirectory: /home/ldapuser
 
adding new entry "cn=ldapuser,ou=users,dc=deephackmode,dc=io"
 
$

Change password of a user

$ env LDAPTLS_CACERT=ldap.deephackmode.io.crt ldappasswd -H 'ldaps://ldap.deephackmode.io' -D "cn=admin,dc=deephackmode,dc=io" -w admin -S "cn=ldapuser,ou=users,dc=deephackmode,dc=io"
New password:
Re-enter new password:

Add a group (from STDIN):

$ env LDAPTLS_CACERT=ldap.deephackmode.io.crt ldapadd -H 'ldaps://ldap.deephackmode.io' -D "cn=admin,dc=deephackmode,dc=io" -w admin
dn: cn=testgroup,ou=groups,dc=deephackmode,dc=io
objectClass: groupOfNames
objectClass: top
cn: testgroup
description: testgroup
member: cn=alexisv,ou=users,dc=deephackmode,dc=io

adding new entry "cn=testgroup,ou=groups,dc=deephackmode,dc=io"

$

Add a user to a group (from STDIN):

$ env LDAPTLS_CACERT=ldap.deephackmode.io.crt ldapmodify -H 'ldaps://ldap.deephackmode.io' -D "cn=admin,dc=deephackmode,dc=io" -w admin
dn: cn=testgroup,ou=groups,dc=deephackmode,dc=io
changetype: modify
add: member
member: cn=testuser,ou=users,dc=deephackmode,dc=io

modifying entry "cn=testgroup,ou=groups,dc=deephackmode,dc=io"

$

At this point, the LDAP server now has the users and groups that I can use. It also can communicate via TLS/SSL. This should now be ready for the TMC-SM installation.