8 KiB
checker-ldap
LDAP directory checker for happyDomain.
Probes a domain's LDAP deployment end-to-end: SRV discovery
(_ldap._tcp, _ldaps._tcp), transport security (StartTLS per RFC 2830,
implicit TLS on port 636), RootDSE introspection (supportedSASLMechanisms,
supportedControl, supportedLDAPVersion, namingContexts, vendor
fingerprint), anonymous exposure (anonymous bind + baseObject search),
plaintext-bind refusal posture, and -- when credentials are supplied --
an authenticated bind with an optional baseObject read on a base DN.
TLS certificate chain / SAN / expiry / cipher posture is out of scope -- the dedicated TLS checker handles that. This checker only confirms that a TLS session can be established and records the negotiated TLS version and cipher for context.
We publish each probed endpoint as a DiscoveryEntry of type
tls.endpoint.v1 so that checker-tls (or any other consumer of that
contract) can run TLS posture checks against them without redoing the
SRV lookup. For _ldap._tcp targets we emit STARTTLS: "ldap" with
RequireSTARTTLS: true, so a misconfigured server that later drops
StartTLS shows up as a CRIT, not a WARN. For _ldaps._tcp we emit
direct-TLS endpoints (STARTTLS: "").
The TLS checker's resulting observations (under the tls_probes key)
are folded back into our rule aggregation and HTML report via the SDK's
ObservationGetter.GetRelated / ReportContext.Related path: a bad
certificate on an LDAP endpoint shows up on the LDAP service page, not
only in a separate TLS view.
What it checks
For each of _ldap._tcp (with fallback to port 389) and _ldaps._tcp
(fallback to port 636):
- Reachability: TCP connect on each resolved A/AAAA address, per IP family, timing captured.
- Transport security:
- On
_ldap._tcp: whether the server advertises StartTLS in its RootDSEsupportedExtension(OID 1.3.6.1.4.1.1466.20037), whether the StartTLS upgrade succeeds, and whether cleartext simple binds are refused withconfidentialityRequired(resultCode 13) per RFC 4513 §5.1.2. - On
_ldaps._tcp: whether the implicit TLS handshake succeeds.
- On
- RootDSE introspection:
supportedLDAPVersion-- flags a legacy LDAPv2 advertisement.supportedSASLMechanisms-- warns when only PLAIN/LOGIN are offered and when no strong mechanism (SCRAM-*, EXTERNAL, GSSAPI) is present.supportedControl,supportedExtension,namingContexts,vendorName,vendorVersion-- captured for the report.
- Anonymous exposure:
- Anonymous bind attempted; result noted.
- When anonymous bind succeeds and at least one naming context is
advertised, a
baseObjectsearch is issued on the first naming context. Any returned entry is flagged asldap.anon.search_allowed-- the DIT is enumerable without credentials.
- Credential test (optional): when
bind_dnandbind_passwordare supplied, a simple bind is performed only on a TLS-protected channel. When the bind succeeds andbase_dnis supplied, abaseObjectsearch is performed on that DN to confirm the account has read access to the intended subtree.
Most common failure scenarios (addressed in the report)
- No encrypted endpoint reachable →
ldap.no_encrypted_endpoint/ CRIT. Operator must enable either LDAPS or StartTLS. - StartTLS not offered on 389 →
ldap.starttls.missing/ CRIT. Server-specific remediation included (OpenLDAP, 389-ds). - StartTLS advertised but upgrade fails →
ldap.starttls.handshake_failed/ CRIT. Hints to run the TLS checker for cipher/cert details. - Cleartext bind accepted on 389 without StartTLS →
ldap.plain_bind.accepted/ CRIT. Remediation viaolcSecurityon OpenLDAP,require_tlson 389-ds. - LDAPS handshake fails on 636 →
ldap.ldaps.handshake_failed/ CRIT. - Anonymous search exposes DIT →
ldap.anon.search_allowed/ WARN. - Only PLAIN/LOGIN SASL offered →
ldap.sasl.plain_only/ WARN. - LDAPv2 still advertised →
ldap.legacy_v2/ WARN. - RootDSE unreadable on an otherwise working endpoint →
ldap.rootdse.unreadable/ WARN. - Provided bind DN / password fail →
ldap.bind.failed/ CRIT -- surfaces credential / lockout issues immediately.
Options
| Id | Required | Description |
|---|---|---|
domain |
yes | Auto-filled from the service scope (domain name). |
timeout |
no | Per-endpoint timeout in seconds (default: 10). |
bind_dn |
no | DN to bind as. Used only when bind_password is also set. |
bind_password |
no | Secret. Bound only after TLS is established; never sent over cleartext. |
base_dn |
no | Base DN to test read access against. Requires a successful authenticated bind. |
Rules
| Code | Description | Severity |
|---|---|---|
ldap.has_srv |
Verifies that _ldap._tcp / _ldaps._tcp SRV records are published and resolvable. | WARNING |
ldap.endpoint_reachable |
Verifies that every discovered LDAP endpoint accepts a TCP connection. | CRITICAL |
ldap.has_encrypted_transport |
Verifies that at least one reachable endpoint offers an encrypted channel (LDAPS or StartTLS). | CRITICAL |
ldap.starttls_supported |
Verifies that StartTLS is offered and succeeds on every reachable plain LDAP endpoint. | CRITICAL |
ldap.ldaps_handshake |
Verifies that the direct TLS handshake succeeds on every LDAPS endpoint. | CRITICAL |
ldap.starttls_on_ldaps |
Flags servers that needlessly advertise StartTLS on the implicit-TLS LDAPS port. | INFO |
ldap.ipv6_reachable |
Verifies at least one endpoint is reachable over IPv6. | INFO |
ldap.refuses_plain_bind |
Verifies the directory refuses authentication attempts over a cleartext channel. | CRITICAL |
ldap.anonymous_search_blocked |
Flags directories that allow anonymous search of the naming context (information disclosure). | WARNING |
ldap.rootdse_readable |
Verifies the RootDSE is readable over TLS and advertises naming contexts. | WARNING |
ldap.sasl_mechanisms |
Reviews the supportedSASLMechanisms posture (presence of strong mechanisms, absence of password-equivalent ones). | WARNING |
ldap.protocol_version |
Flags servers that still advertise the deprecated LDAPv2 protocol. | WARNING |
ldap.bind_credentials |
Verifies the supplied bind credentials are accepted by the directory (only runs when bind_dn is set). | CRITICAL |
ldap.base_dn_read |
Verifies the bound account can read the supplied base DN (only runs when base_dn is set and bind succeeded). | CRITICAL |
ldap.tls_quality |
Folds the downstream TLS checker findings (certificate chain, hostname match, expiry) onto the LDAP service. | CRITICAL |
License
MIT (see LICENSE and NOTICE).