LDAP / Active Directory
Federate logins against an LDAP v3 directory or Active Directory. Passwords of federated users never land in CertAutoPilot's database — the backend verifies them by binding to the directory. This page deepens the LDAP summary on the Auth & RBAC page.
LDAP requires an Enterprise license. Without it, saving the LDAP settings is blocked and logins silently skip the directory and use local authentication only (silent by design, so login errors don't leak license state).
How authentication works
There is one bind mode: search-then-bind with a service account. On every login attempt (LDAP is tried first when configured; local auth is the fallback):
- Filters use a
%splaceholder — it is replaced with the LDAP-escaped username (user filter) or the user's full DN (group filter). Values are escaped, so filter injection via the login form is not possible. - Directory searches carry a 30-second time limit; a fresh connection is opened per login (no pooling).
- If the directory rejects the user (not found or wrong password), the login falls through to local authentication — so a local admin account keeps working even when LDAP is up.
Settings reference
Settings → LDAP. Viewing and saving require the owner org role; saving additionally requires the Enterprise license. There is no separate enable switch — LDAP is active once a config with a non-empty Host is saved (clear the host to disable). Config is stored in the settings collection under ldap_config; the bind password is never returned to the browser (the form shows a "has password" state and keeps the stored value when you leave the field blank).
| Field (JSON) | UI label | Notes |
|---|---|---|
host | Host | Directory hostname / IP. Non-empty = LDAP active. |
port | Port | 389 (plain / STARTTLS) or 636 (LDAPS). |
use_tls | Use TLS | LDAPS — TLS from the first byte. Mutually exclusive with STARTTLS. |
use_start_tls | Use StartTLS | Plain connect, then upgrade. |
skip_verify | Skip TLS Verify | Disables certificate verification. Avoid — see TLS. |
tls_ca_cert | TLS CA Certificate | PEM, optional — see the warning under TLS. |
bind_dn | Bind DN | Service account DN, e.g. cn=admin,dc=example,dc=com. |
bind_password | Bind Password | Service account password. Write-only. |
base_dn | Base DN | Search root for users, e.g. dc=example,dc=com. |
user_filter | User Filter | Must contain %s, e.g. (&(objectClass=person)(uid=%s)). |
group_filter | Group Filter | Must contain %s (user DN), e.g. (&(objectClass=groupOfNames)(member=%s)). Empty = skip group lookup. |
group_base_dn | Group Base DN | Search root for groups; defaults to Base DN when empty. |
username_attribute | Username | Attribute mapped to the CertAutoPilot username, e.g. uid or sAMAccountName. |
email_attribute | e.g. mail. Empty result falls back to <username>@local.ldap. | |
display_name_attribute | Display Name | e.g. cn or displayName. |
group_attribute | Group | Attribute read from matched group entries, e.g. cn. |
TLS
- LDAPS (
use_tls, port 636) is preferred. STARTTLS on 389 is supported. Minimum: fix the directory rather than running plain LDAP — but note that plain LDAP is not refused; with both TLS switches off the connection is unencrypted. skip_verifydisables certificate verification and logs a MitM warning on every connection. Don't ship it to production.
The TLS CA Certificate field is stored with the config, but the current connection logic verifies the server certificate against the host's system trust store only (with SNI set to the configured host). For a private CA, add the CA to the OS trust store of every backend host (or container image) instead of relying on this field.
Auto-provisioning & roles
The first successful directory login creates the user automatically:
auth_source: ldap, username / email / display name from the configured attributes, and the user's directory DN recorded on the account.- Org role: viewer (fixed default, assigned only when the user has no roles yet).
After provisioning, roles are managed locally in Settings → Users, exactly like local users. Promotions in the directory do not change CertAutoPilot roles:
- Group lookup runs on every login and the matched groups are returned by the directory, but there is no group → role mapping — groups are not persisted on the user and do not influence roles. Assign org / project roles in the UI.
- Roles are not re-evaluated on subsequent logins; whatever you set locally sticks.
The provisioning upsert re-activates the account on every successful directory login. To lock an LDAP user out reliably, disable or remove them in the directory (the user bind then fails), not just in CertAutoPilot.
Test tools
Use these before and after saving — both accept the form's current (unsaved) values and fall back to the stored bind password when the field is blank:
- Test Connection (
POST /settings/ldap/test) — verifies the service-account bind only. Returns success or the directory's bind error. - Filter test (search icon next to User Filter and Group Filter,
POST /settings/ldap/test-filter) — runs the filter with a test value you supply and lists matching entries. Results expose a safe attribute allowlist only:dn,cn,uid,sAMAccountName,mail,memberOf,objectClass, plus your configured attribute names.
Active Directory notes
- User filter:
(&(objectClass=user)(sAMAccountName=%s)), with Username =sAMAccountName. To let users log in with their UPN instead, filter on(userPrincipalName=%s). - Group filter:
(member=%s)(the placeholder is the user's full DN).memberOfshows up in filter-test results, which helps sanity-check membership. - Attribute mapping: Email =
mail, Display Name =displayNameorcn.
Interaction with TOTP and lockout
- TOTP applies to LDAP users the same as local users — the directory never sees the OTP code. With the org policy set to Required, LDAP users are forced through enrollment on their next login.
- The account-lockout check runs for LDAP users too, but failed directory binds do not increment the local failure counter — brute-force protection for federated accounts belongs in the directory's own lockout policy.
Troubleshooting
Service bind fails ("Test Connection" errors)
Wrong Bind DN / password, or the service account is disabled or password-expired in the directory. AD frequently wants the DN form (cn=svc-cap,ou=service,dc=corp,dc=example,dc=com); test exactly what you saved.
"filter must contain %s"
Both filters are templates — the user filter substitutes the username, the group filter the user DN. {{ .username }}-style placeholders are not supported.
User can authenticate but lands as viewer
Expected — auto-provisioning always assigns viewer. Grant the real role under Settings → Users; there is no directory-group-based role mapping.
TLS handshake fails against a private CA
The connection verifies against the host system trust store, not the form's CA field. Install the CA on the backend host, or (temporarily, never in production) set Skip TLS Verify to confirm that trust is the only problem.