IdP internal LDAP
This runbook documents how the QNAP NAS LDAP server is used as an identity provider for the internal ZITADEL instance (auth.reids.net.au), including schema assumptions, ZITADEL console settings, TLS trust path, and troubleshooting steps.
Identity provider series
- IdP dual overview
- IdP dual architecture
- IdP internal deployment
- IdP internal console
- IdP internal SMTP
- IdP internal LDAP - you are here
- IdP internal OIDC
- IdP internal OAUTH2 proxy
- IdP internal backup and restore
High-level design
- QNAP NAS runs the LDAP server and presents a wildcard TLS certificate for
*.reids.net.auon LDAPS. - Kubernetes runs ZITADEL #2 in the
identity-internalnamespace. - Cluster-wide TLS trust for the wildcard certificate is provided by trust-manager and Gatekeeper:
cert-manager/wildcard-reids-tls(secret managed by Flux + SOPS) → trust-managerBundle→wildcard-reids-bundleConfigMapintrust=enablednamespaces → Gatekeeper volume and volumeMount →/etc/ssl/certs/wildcard-reids.crtinside Pods.
- ZITADEL connects to LDAP over LDAPS and uses it as an external identity provider, trusting the NAS certificate via
SSL_CERT_FILE=/etc/ssl/certs/wildcard-reids.crt. - ZITADEL automatically creates or links users on first login, based on LDAP attributes.
User browser → ZITADEL login → choose "nas" IdP
→ ZITADEL ↔ LDAPS (QNAP)
→ LDAP bind + search + password check
→ ZITADEL account link / auto-provision
QNAP LDAP server
Directory layout
The QNAP LDAP server is configured with:
- Base DN:
dc=reids,dc=net,dc=au - People OU:
ou=people,dc=reids,dc=net,dc=au - Example user:
dn: uid=USERNAME,ou=people,dc=reids,dc=net,dc=au
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: inetOrgPerson
cn: USERNAME
sn: USERNAME
uid: USERNAME
mail: USERNAME@DOMAIN
Bind user
A dedicated bind account is used by ZITADEL:
- DN:
uid=zitadel-bind,ou=people,dc=reids,dc=net,dc=au - Password: stored and rotated on the NAS; used only for LDAP binds and searches.
- Permissions: must be able to search
ou=people,…and read the attributes listed later.
QNAP logs for LDAP are written to nc.log and rotated copies in /share/CACHEDEV1_DATA/.logs (path may vary). Relevant entries look like:
[LDAP Server] Created user "zitadel-bind".
[LDAP Server] Changed the password of LDAP user "zitadel-bind".
[LDAP Server] Created user "USERNAME".
These confirm user creation and password changes, but do not log failed binds in detail.
Testing LDAP from the workstation
Use ldapsearch to validate connectivity, bind DN and filters before touching ZITADEL.
Plain LDAP
ldapsearch \
-H ldap://nas.reids.net.au:389 \
-D "uid=zitadel-bind,ou=people,dc=reids,dc=net,dc=au" \
-W \
-b "dc=reids,dc=net,dc=au" \
-s base \
"(objectClass=*)"
Common result codes:
49 Invalid credentials→ DN or password wrong.0 Success→ bind is working.
LDAPS
ldapsearch \
-H ldaps://nas.reids.net.au:636 \
-D "uid=zitadel-bind,ou=people,dc=reids,dc=net,dc=au" \
-W \
-b "dc=reids,dc=net,dc=au" \
-s base \
"(objectClass=*)"
To test the exact search ZITADEL will perform for user USERNAME:
ldapsearch \
-H ldaps://nas.reids.net.au:636 \
-D "uid=zitadel-bind,ou=people,dc=reids,dc=net,dc=au" \
-W \
-b "ou=people,dc=reids,dc=net,dc=au" \
"(&(objectClass=inetOrgPerson)(uid=USERNAME))"
You should see one entry with all attributes.
ZITADEL Helm configuration (TLS trust)
ZITADEL needs to trust the same wildcard certificate that the NAS presents for LDAPS.
The canonical wildcard certificate/key pair is stored and managed by Flux + SOPS as cert-manager/wildcard-reids-tls. From there, the cluster-wide wildcard TLS trust with trust-manager and Gatekeeper path takes over:
- trust-manager
Bundlewildcard-reids-bundlereadscert-manager/wildcard-reids-tlsand writes aConfigMap wildcard-reids-bundleinto every namespace withtrust=enabled. - Gatekeeper
Assignmutations ensure a volume and volumeMount are injected into Pods in trusted namespaces, exposing/etc/ssl/certs/wildcard-reids.crtinside each container.
ZITADEL does not need to mount the wildcard-reids-tls secret directly any more. It only needs to opt in to the shared trust bundle by pointing Go's TLS stack at the injected file.
Key snippet in k8s/prod/50-helm-release-zitadel.yaml:
spec:
values:
# Environment variables for database passwords
env:
- name: ZITADEL_DATABASE_POSTGRES_ADMIN_PASSWORD
valueFrom:
secretKeyRef:
name: zitadel-db-secret
key: POSTGRES_PASSWORD
- name: ZITADEL_DATABASE_POSTGRES_USER_PASSWORD
valueFrom:
secretKeyRef:
name: zitadel-db-secret
key: POSTGRES_PASSWORD
- name: SSL_CERT_FILE
value: /etc/ssl/certs/wildcard-reids.crt
With this in place:
- trust-manager and Gatekeeper provide the file
/etc/ssl/certs/wildcard-reids.crt. SSL_CERT_FILEtells the ZITADEL process to use that file as its CA bundle, so LDAPS connections to the NAS succeed without relaxing verification.
Refer to the dedicated runbook "Cluster-wide wildcard TLS trust with trust-manager and Gatekeeper" for the full trust path and verification steps.
ZITADEL LDAP identity provider configuration
All configuration below is done in the Default Settings → Identity Providers section for the reids-internal instance.
Connection
- Name:
nas - Servers:
ldaps://nas.reids.net.au:636 - BaseDn:
dc=reids,dc=net,dc=au - BindDn:
uid=zitadel-bind,ou=people,dc=reids,dc=net,dc=au - Bind password: set via the console (matches QNAP LDAP user)
Ensure StartTLS is disabled when using ldaps://…. Using both at once causes errors such as:
LDAP Result Code 200 "Network Error": tls: failed to verify certificate
User binding
- Userbase:
ou=people,dc=reids,dc=net,dc=au
User filters
This is where most early problems appeared.
The rule from ZITADEL is:
"User filters: attributes of the user which are 'or'-joined in the query.
Used value is the input of the login name."
We want "whatever the user types as Login Name" to map directly to the LDAP uid attribute.
Final configuration:
- User filters:
uid
No additional filters are used. Earlier experiments like uid=LoginName or multiple filters produced errors such as:
LDAP Result Code 201 "Filter Compile Error": ldap: finished compiling filter with extra at end: ob))user does not exist or too many entries returned
These were caused by ZITADEL building invalid search filters from the extra text.
User Object Classes
- User Object Classes:
inetOrgPerson
This matches the QNAP user entries, which include inetOrgPerson among other object classes.
LDAP attributes mapping
These control how LDAP attributes are projected into ZITADEL user fields.
Final working configuration:
- ID attribute:
uid - Displayname attribute:
displayName - Email attribute:
mail - Family name attribute:
sn - Preferred username attribute:
uid - Other attribute fields left empty.
The critical one is ID attribute = uid. This is used as the stable external identity key.
Provider activation and login policy
- Create the provider as above.
- In the provider list, enable the availability tick so it is active.
- Under Login Behavior and Security, ensure:
External Login allowedis enabled.- At least one of:
Account creation allowedAccount linking allowedis enabled.
For the internal instance it is typical to enable account creation for LDAP users so they are auto-provisioned on first login.
Login flow and first-time user behaviour
When everything is configured:
- User opens the internal login page (for example via a client app's OIDC flow).
- The login screen shows an extra button labelled
nas(or whatever Name you chose). - Clicking it redirects to the LDAP login screen.
- User enters:
- Login Name:
USERNAME - Password: LDAP password for
uid=USERNAME,…
- Login Name:
- If bind and search succeed, ZITADEL looks for an existing linked account.
- If none exists and account creation is enabled, you see the
"External User Not Found"form pre-filled with:- Given name
- Family name
- Username (derived from
uid) - Email (from
mail)
- Submitting the form creates a ZITADEL account linked to the LDAP identity.
Troubleshooting notes
Invalid credentials (Result Code 49)
If ZITADEL shows:
LDAP Result Code 49 "Invalid Credentials":
check the following:
- Re-run
ldapsearchwith the same BindDn and password from your workstation. - Confirm the bind DN uses
uid=…and notcn=…. - Make sure the NAS logs (
nc.log) show the password change you expect.
User does not exist or too many entries returned
This error typically appeared when:
- User filters were misconfigured (
uid=LoginName, or multiple filters that match extra entries). - BaseDn or Userbase did not match the actual tree.
Fix by:
- Ensuring Userbase is
ou=people,dc=reids,dc=net,dc=au. - Using a single user filter of
uid. - Testing the equivalent filter with
ldapsearch:
ldapsearch \
-H ldaps://nas.reids.net.au:636 \
-D "uid=zitadel-bind,ou=people,dc=reids,dc=net,dc=au" \
-W \
-b "ou=people,dc=reids,dc=net,dc=au" \
"(&(objectClass=inetOrgPerson)(uid=**USERNAME**))"
TLS and certificate issues
If you see errors like:
LDAP Result Code 200 "Network Error": tls: failed to verify certificate: x509: certificate signed by unknown authority
check:
- NAS is presenting the wildcard certificate you expect.
- ZITADEL container sets
SSL_CERT_FILE=/etc/ssl/certs/wildcard-reids.crt. - The
wildcard-reids-bundleConfigMapexists in theidentity-internalnamespace and contains a full chain (leaf + intermediate + root as needed). - Gatekeeper
Assignmutations are present and injecting thewildcard-reids-bundlevolume and/etc/ssl/certs/wildcard-reids.crtmount into Pods inidentity-internal.
If the file /etc/ssl/certs/wildcard-reids.crt is missing inside a ZITADEL Pod, troubleshoot using the "Cluster-wide wildcard TLS trust with trust-manager and Gatekeeper" runbook.
Verification checklist
Use this checklist if you need to re-build or audit the setup.
- QNAP LDAP:
- Base DN is
dc=reids,dc=net,dc=au. -
ou=peopleexists and contains users withobjectClass: inetOrgPerson. -
uid=zitadel-bind,ou=people,…exists and password is known.
- Base DN is
- TLS:
- LDAPS works from a workstation using
ldapsearchon port 636. -
wildcard-reids-bundleConfigMapexists inidentity-internal. - A ZITADEL Pod in
identity-internalhas/etc/ssl/certs/wildcard-reids.crtpresent.
- LDAPS works from a workstation using
- ZITADEL Helm:
- Internal instance deployed in
identity-internalnamespace. -
SSL_CERT_FILE=/etc/ssl/certs/wildcard-reids.crtis set in the ZITADEL Deployment (env).
- Internal instance deployed in
- ZITADEL LDAP provider:
- Servers:
ldaps://nas.reids.net.au:636. - BaseDn:
dc=reids,dc=net,dc=au. - BindDn:
uid=zitadel-bind,ou=people,dc=reids,dc=net,dc=au. - Userbase:
ou=people,dc=reids,dc=net,dc=au. - User filters:
uid. - User Object Classes:
inetOrgPerson. - ID attribute:
uid.
- Servers:
- Login policy:
- External Login allowed.
- Either account creation or account linking allowed.
- End-to-end:
- Logging in as
USERNAMEvia thenasIdP succeeds and either:- Links to an existing ZITADEL account, or
- Shows the
"External User Not Found"form with pre-filled details and completes successfully.
- Logging in as