risingwavelabs/risingwave · error · PsqlError
LDAP search failed
Error message
LDAP search failed
What it means
After binding as the search user, search_and_bind runs an LDAP subtree search for the user's entry using the configured base_dn and search_filter. Any error from the search operation (network drop, malformed filter, insufficient search permissions, base_dn not found) is wrapped as this StartupError. It is a directory-query failure, not an authentication failure.
Solutions
- Test the search manually: ldapsearch -H ... -D bind_dn -w pass -b base_dn '(uid=testuser)' dn
- Escape special characters in interpolated usernames (RFC 4515 filter escaping)
- Verify base_dn exists and matches the directory naming context
- Grant the bind account read/search permissions on the user OU
Example fix
// before (unescaped interpolation breaks filter)
search_filter = '(uid={username})'
// after (escaped via library interpolation; correct attribute for AD)
search_filter = '(sAMAccountName={username})' Defensive patterns
Strategy: try-catch
Validate before calling
// verify the search works manually before configuring # ldapsearch -H ldaps://ldap.corp:636 -D "$BIND_DN" -w "$BIND_PASS" \ # -b 'ou=users,dc=corp,dc=com' '(sAMAccountName=testuser)' dn
Try / catch
catch PsqlError::StartupError 'LDAP search failed'; differentiate filter-syntax errors (fix escaping) from ACL/base_dn errors (fix directory config) using the inner error text
Prevention
- LDAP-escape all interpolated usernames (RFC 4515) before building filters
- Copy base_dn directly from the directory's naming context, never by hand
- Grant the bind account explicit read/search ACLs on the user subtree
- Keep an ldapsearch-based integration test for the search phase
When it happens
Trigger: ldap.search(base_dn, Scope::Subtree, &search_filter, vec!["dn"]) awaits or resolves with Err — bad filter syntax, wrong base_dn, search privileges missing for the bind account
Common situations: Search filter template with unescaped special characters in username ((uid=j.o'brien) breaks filter syntax); base_dn typo'd or points at a non-existent subtree; bind account lacks read/search ACLs on the user subtree; AD requires the filter to use sAMAccountName instead of uid.
Understand the failure class
Background: Database query failed: Internal Server Error 500s wrapping SQL, Prisma, and connection failures — what to check first — this error's family across 16 libraries.
Related errors
- Failed to add CA certificate
- Failed to add native CA certificate
- Failed to check if table exists
- Failed to connect to LDAP server
- failed to create
AI-assisted analysis of risingwavelabs/risingwave@6469eb736d (2026-09-11).
Data as JSON: /api/errors/d07189feb8d2c387.
Report an issue: GitHub.
Appendix: source
Thrown at src/utils/pgwire/src/ldap_auth.rs:516
// Build search filter
let search_filter = if let Some(filter_template) = &self.config.search_filter {
// Use custom filter template with $username placeholder
// SECURITY: Escape username to prevent LDAP filter injection
let escaped_username = ldap_escape(username);
filter_template.replace("$username", &escaped_username)
} else {
// Default filter using search_attribute (defaults to "uid" if not configured)
// SECURITY: Escape username to prevent LDAP filter injection
let escaped_username = ldap_escape(username);
let attr = self.config.search_attribute.as_deref().unwrap_or("uid");
format!("({}={})", attr, escaped_username)
};
let rs = ldap
.search(base_dn, Scope::Subtree, &search_filter, vec!["dn"])
.await
.map_err(|e| {
PsqlError::StartupError(anyhow!(e).context("LDAP search failed").into())
})?;
// If no user found, authentication fails
let search_entries: Vec<SearchEntry> =
rs.0.into_iter().map(SearchEntry::construct).collect();
if search_entries.is_empty() {
return Ok(false);
}
// Attempt to bind with the user's DN and password
let user_dn = &search_entries[0].dn;
let bind_result = ldap
.simple_bind(user_dn, password)
.await
.map_err(|e| PsqlError::StartupError(anyhow!(e).context("LDAP bind failed").into()));
// Explicitly unbind the connectionView on GitHub (pinned to 6469eb736d)