No alternative text provided

Auto-Generate OpenLDAP Secrets Without Git Exposure

TL;DR: Stop storing secrets in Git! Learn how to generate a secure OpenLDAP admin password using a Kubernetes Job, and get a quick intro to Kubernetes RBAC concepts.


πŸ” Context

This article is a follow-up to this post, where I set up a fully self-hosted email forwarding service using OpenLDAP and Docker Mailserver (DMS).

One thing I didn't like in the initial setup was having to store the LDAP admin password in plain text inside my Git repository β€” definitely not a good idea if you want to follow GitOps best practices. Especially when you're using ArgoCD to manage your infrastructure declaratively.

Thankfully, the jp-gouin/helm-openldap chart supports using a pre-existing secret (via the global.existingSecret value).

The challenge? We need to create that secret securely β€” ideally with a random password β€” and only if it doesn't already exist.

Let's do that with a Kubernetes Job πŸ”§


πŸ§ͺ What's a Kubernetes Job?

A Kubernetes Job is a one-time task that runs to completion. It's perfect for batch jobs like generating a secret, doing a one-off migration, or cleaning up resources.

In our case, we'll use a Job to:

  • Check if the ldap-passwords secret already exists
  • If not, generate random admin passwords
  • Create the secret using kubectl

🎯 Why not use kubectl, Vault, or Sealed Secrets?

  • kubectl? Sure, but we'd need to run it manually
  • Vault? Overkill for this small cluster
  • Sealed Secrets? Still ends up in Git… encrypted, but still there

Instead, we'll go for a fully in-cluster generation logic with a Job. Clean and Git-free ✨


πŸ› οΈ Required Kubernetes Resources

To allow a pod (the Job) to create secrets, we need to grant it permission. This is done through RBAC (Role-Based Access Control).

πŸ” Breakdown of RBAC:

  • ServiceAccount: Identity for your Job to use
  • Role: Defines what permissions exist in a namespace (e.g. can create secrets)
  • RoleBinding: Assigns a Role to a ServiceAccount

πŸ” YAML Definitions

1apiVersion: v1
2kind: ServiceAccount
3metadata:
4  name: ldap-admin-key-generator
5---
6apiVersion: rbac.authorization.k8s.io/v1
7kind: Role
8metadata:
9  name: ldap-admin-key-generator
10rules:
11  - apiGroups: [""]
12    resources: ["secrets"]
13    verbs: ["get", "create", "update", "patch"]
14---
15apiVersion: rbac.authorization.k8s.io/v1
16kind: RoleBinding
17metadata:
18  name: ldap-admin-key-generator
19roleRef:
20  apiGroup: rbac.authorization.k8s.io
21  kind: Role
22  name: ldap-admin-key-generator
23subjects:
24  - kind: ServiceAccount
25    name: ldap-admin-key-generator

πŸš€ The Job to Generate LDAP Passwords

Here's our actual Job:

1apiVersion: batch/v1
2kind: Job
3metadata:
4  name: ldap-admin-key-generator
5spec:
6  template:
7    spec:
8      restartPolicy: Never
9      serviceAccountName: ldap-admin-key-generator
10      containers:
11        - name: key-generator
12          image: bitnami/kubectl:latest
13          command:
14            - /bin/sh
15            - -c
16            - |
17              if kubectl get secret ldap-passwords -o jsonpath='{.data.LDAP_ADMIN_PASSWORD}' 2>/dev/null | grep . >/dev/null && \
18                 kubectl get secret ldap-passwords -o jsonpath='{.data.LDAP_CONFIG_ADMIN_PASSWORD}' 2>/dev/null | grep . >/dev/null; then
19                  echo "Secret already exists with valid keys, skipping generation"
20                  exit 0
21              fi
22
23              LDAP_ADMIN_PASSWORD="$(openssl rand -hex 16 | tr -d '\n')"
24              LDAP_CONFIG_ADMIN_PASSWORD="$(openssl rand -hex 16 | tr -d '\n')"
25
26              kubectl create secret generic ldap-passwords \
27                --from-literal=LDAP_ADMIN_PASSWORD="$LDAP_ADMIN_PASSWORD" \
28                --from-literal=LDAP_CONFIG_ADMIN_PASSWORD="$LDAP_CONFIG_ADMIN_PASSWORD" \
29                --dry-run=client -o yaml | kubectl apply -f -
30
31              echo "Generated and applied new values for ldap-passwords secret"

πŸ”— Referencing the Secret in Helm Chart

Now that we have a secret named ldap-passwords, we can use it with the helm-openldap chart:

1# values.yaml
2
3global:
4  existingSecret: ldap-passwords

🧾 Bonus: Retrieve the Password

1kubectl --namespace ldap get secret ldap-passwords -o jsonpath='{.data.LDAP_ADMIN_PASSWORD}' | base64 -d

🧠 Final Thoughts

This pattern β€” generate a secret with a Job and secure it with RBAC β€” can be reused for many other use cases. It's simple, secure, and GitOps-friendly πŸ’ͺ

Let me know if you want a follow-up article that turns this into a Helm chart or a reusable Kustomize setup!


πŸ–– Happy self-hosting!