LDAP Channel
Point your existing LDAP apps at Scribe. They’ll keep working — same protocol, same tools, same queries — but now they’re reading from PostgreSQL instead of hitting your directory server directly.
Quick start
Section titled “Quick start”Configure one or more listeners:
channels.ldap { listen = [ { port = 10389 } # Plain LDAP { port = 10636, ssl { # LDAPS ca = "certs/ca.pem" cert = "certs/server.pem" key = "certs/server-key.pem" }} ]}Test with any LDAP client:
ldapsearch -H ldap://localhost:10389 -x -D "u:alice" -W \ -b "ou=users,dc=example,dc=com" "(cn=john*)" cn mailFor all listener settings (host binding, client certificates, socket options, connection limits), see Configuration Reference.
Authentication
Section titled “Authentication”Scribe authenticates LDAP bind requests locally using the unified auth infrastructure. Several bind DN formats are supported:
| Bind DN format | Example | How it works |
|---|---|---|
bearer:<token> | bearer:eyJhbGci... | JWT or opaque token — password is ignored |
dn:<dn> | dn:uid=alice,ou=users,dc=example,dc=com | Explicit DN + password. DN must be under auth.ldap.base |
u:<username> | u:alice | Username + password, DN resolved via search under auth.ldap.base |
| Plain value | alice or uid=alice,... | Disambiguated via DN.isValidDN — DN-like strings bind by DN (with base enforcement), others resolve as username |
# Bind with bearer tokenldapsearch -H ldap://localhost:10389 -x \ -D "bearer:eyJhbGciOiJSUzI1NiJ9..." -w "ignored" \ -b "ou=users,dc=example,dc=com" "(cn=*)"
# Bind with usernameldapsearch -H ldap://localhost:10389 -x \ -D "u:alice" -W \ -b "ou=users,dc=example,dc=com" "(cn=*)"Authentication methods are tried in order (e.g., bearer, then ropc, then ldap). Both channels.ldap.auth and channels.identity-hub.auth inherit from global auth {} and can override enabled, methods, and rules per channel. See Authentication for method configuration.
Proxy Authorization
Section titled “Proxy Authorization”The LDAP channel supports Proxy Authorization V2 (RFC 4370) for delegation — a service account binds, then acts on behalf of a user:
ldapsearch -H ldap://localhost:10389 -x \ -D "cn=service,dc=example,dc=com" -W \ -e "authzid=dn:uid=alice,ou=users,dc=example,dc=com" \ -b "ou=users,dc=example,dc=com" "(cn=*)"ProxyAuth accepts the same formats as bind: bearer:<token>, dn:<dn>, u:<username>, or plain values. DN and username formats (including plain values) require auth.ldap configuration so Scribe can verify DNs and resolve usernames; without it, ProxyAuth with those formats returns AUTHORIZATION_DENIED. Use bearer:<token> when LDAP auth is not configured. Bearer tokens with a dn claim are subject to auth.ldap.base scope validation — the DN must be a strict descendant of the base.
Delegation pattern
Section titled “Delegation pattern”Proxy authorization to backend LDAP
- Bind establishes the service identity (who’s calling)
- ProxyAuth establishes the subject identity (on whose behalf)
- The backend receives operations with a
ProxyAuthcontrol containing the resolved effective DN
All authentication happens locally within Scribe. The upstream LDAP server only sees the resolved DN.
Searching
Section titled “Searching”Standard LDAP search with filters, scopes, and attribute selection:
# Subtree search with filter and specific attributesldapsearch -H ldap://localhost:10389 -x -D "..." -W \ -b "ou=users,dc=example,dc=com" \ -s sub "(|(cn=john*)(mail=*@example.com))" cn mail uidPagination
Section titled “Pagination”Use Simple Paged Results for large result sets:
ldapsearch -H ldap://localhost:10389 -x -D "..." -W \ -b "ou=users,dc=example,dc=com" \ -E pr=100/noprompt "(objectClass=inetOrgPerson)"VLV (Virtual List View) is also supported.
Sorting
Section titled “Sorting”Server-Side Sorting works on indexed attributes:
ldapsearch -H ldap://localhost:10389 -x -D "..." -W \ -b "ou=users,dc=example,dc=com" \ -E sss=-sn "(objectClass=inetOrgPerson)"Auth gating by operation type
Section titled “Auth gating by operation type”Access rules are evaluated for all operation types. The exact checks differ:
| Operation type | Auth gating |
|---|---|
| Search | (1) Auth required flag (if configured), (2) access rules, (3) ProxyAuth control validation |
| Add, Compare, Delete, Modify, ModifyDN | Access rules only |
| Extended | StartTLS always forwarded (RFC 4513 pre-bind transport). Others: access rules + ProxyAuth |
Search has the strictest gating: when channels.ldap.auth.enabled is true, unauthenticated Search requests are rejected before access rules are evaluated. Non-Search operations (add, modify, etc.) rely solely on access rules. Extended operations: StartTLS is always forwarded to the backend (RFC 4513 requires pre-bind transport negotiation); other extended operations are subject to access rules and ProxyAuth validation.
Bind failure delay: failed bind attempts use auth.failure-delay (default 2s) before responding, mitigating brute-force attacks. Does not apply when auth is delegated to the backend.
Search delegation
Section titled “Search delegation”Some requests can’t be answered from the local cache — schema queries, unsupported filters, directory browser operations. When that happens, Scribe forwards the request to the upstream LDAP server.
Set channels.ldap.prevent-delegation = true to fail these requests instead of forwarding them. See Configuration Reference for delegation reasons and telemetry.
Common issues
Section titled “Common issues”| Symptom | Check |
|---|---|
Can't contact LDAP server | Listener configured? Port not blocked? |
| ResultCode 49 (INVALID_CREDENTIALS) | Bind DN and password correct? For bearer tokens: token valid and not expired? |
| ResultCode 53 (AUTHORIZATION_DENIED) | Empty bearer token, or ProxyAuth with DN/username when auth.ldap is not configured |
| ResultCode 4 (SIZE_LIMIT_EXCEEDED) | Use pagination (-E pr=100/noprompt) or refine the filter |
See Signals for metrics, Logging and Traces for trace debugging, Error Handling for the complete ResultCode mapping.