
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!