Skip to main content

LDAP and Active Directory

Use LDAP when a directory is authoritative but cannot provide OIDC. Jiandu searches for exactly one entry using an escaped username, then verifies the submitted password by binding as that entry. It stores only a provider-scoped immutable subject and local presentation metadata—not the password, search-bind credential, email address, or directory roles.

OIDC is preferable when available because users authenticate at the identity provider and Jiandu does not handle their directory password. LDAP remains a sound compatibility option when the TLS, search, and immutable-attribute boundaries below are explicit.

1. Choose encrypted transport​

Use exactly one of these modes:

Directory endpointJiandu settingWhen to choose it
LDAP over TLS, normally port 636"url": "ldaps://directory.example.net:636", "start_tls": falseSimplest dedicated TLS listener
StartTLS, normally port 389"url": "ldap://directory.example.net:389", "start_tls": trueExisting LDAP listener explicitly upgraded before credentials are sent

Plain ldap:// without StartTLS is rejected. ldaps:// with start_tls: true is also rejected because it attempts two incompatible TLS modes. The URL may contain only scheme, host, and optional port—no embedded credentials, base DN, query, or fragment.

Certificate verification cannot be disabled. Jiandu loads native system trust roots, so install a private directory CA in the operating system/container trust store and restart the process. The certificate must cover the hostname in url; using an IP address when only a DNS name is certified will fail.

2. Choose the search and identity attributes​

PurposeOpenLDAP-style choiceActive Directory choiceCaveat
Search baseou=people,dc=example,dc=netou=Users,dc=example,dc=netSearch is subtree-scoped; make the base as narrow as practical
UsernameuidsAMAccountName or userPrincipalNameMust identify exactly one entry under the base
Immutable subjectentryUUIDobjectGUIDNever use username, mail, or DN: they can change
Display namedisplayName or cndisplayNameOptional; falls back to submitted username
Admission groupscommonly memberOfmemberOfValues are compared exactly as returned, usually full DNs

Jiandu supports Active Directory's binary objectGUID encoding directly. Microsoft documents objectGUID as a single-valued system attribute that is not normally modified; see the attribute specification.

The optional additional_user_filter must be one complete parenthesized LDAP filter. Jiandu ANDs it with the escaped username assertion. For example, input alex with username attribute uid and (objectClass=person) produces the logical filter:

(&(uid=alex)(objectClass=person))

Submitted LDAP metacharacters are escaped. Search results are limited to two and anything other than exactly one entry fails closed, so duplicate usernames cannot resolve nondeterministically.

3. Decide whether a search bind is needed​

If anonymous users may search the narrow base and read only the selected subject, name, and group attributes, omit both bind fields. Otherwise create a read-only service account with no password reset or directory-write privileges, store its password in a protected file, and configure both fields together:

umask 077
install -d -m 0700 /etc/jiandu/secrets
read -r -s ldap_bind_password
printf '%s\n' "$ldap_bind_password" > /etc/jiandu/secrets/ldap-bind-password
unset ldap_bind_password
chmod 0600 /etc/jiandu/secrets/ldap-bind-password

Jiandu requires exact Unix mode 0600, one regular link, and no more than 4 KiB. The search account binds first; after one entry is found, Jiandu binds as that entry with the person's submitted password. A successful service bind alone never authenticates a person.

OpenLDAP example​

/etc/jiandu/jiandu.json
{
"schema_version": 1,
"auth": {
"providers": [
{
"kind": "ldap",
"id": "openldap",
"display_name": "Household directory",
"url": "ldaps://directory.example.net:636",
"start_tls": false,
"bind_dn": "cn=jiandu,ou=services,dc=example,dc=net",
"bind_password_path": "/etc/jiandu/secrets/ldap-bind-password",
"user_base_dn": "ou=people,dc=example,dc=net",
"username_attribute": "uid",
"subject_attribute": "entryUUID",
"display_name_attribute": "displayName",
"additional_user_filter": "(objectClass=inetOrgPerson)",
"admission_policy": "required_group",
"group_attribute": "memberOf",
"admission_group": "cn=jiandu-members,ou=groups,dc=example,dc=net"
}
]
}
}

Many OpenLDAP deployments expose memberOf only when the memberOf overlay is enabled and existing membership data has been maintained accordingly. If your directory does not return it on the user entry, either configure a suitable user attribute, use invitation-only admission, or prefer an OIDC provider that can perform group policy. Jiandu does not perform a second reverse-membership query.

For StartTLS, change only the transport pair:

"url": "ldap://directory.example.net:389",
"start_tls": true

Active Directory example​

/etc/jiandu/jiandu.json
{
"schema_version": 1,
"auth": {
"providers": [
{
"kind": "ldap",
"id": "active-directory",
"display_name": "Active Directory",
"url": "ldaps://dc01.example.net:636",
"start_tls": false,
"bind_dn": "CN=Jiandu Search,OU=Service Accounts,DC=example,DC=net",
"bind_password_path": "/etc/jiandu/secrets/ad-bind-password",
"user_base_dn": "OU=Users,DC=example,DC=net",
"username_attribute": "sAMAccountName",
"subject_attribute": "objectGUID",
"display_name_attribute": "displayName",
"additional_user_filter": "(&(objectCategory=person)(objectClass=user))",
"admission_policy": "required_group",
"group_attribute": "memberOf",
"admission_group": "CN=Jiandu Members,OU=Security Groups,DC=example,DC=net"
}
]
}
}

Use userPrincipalName instead when members should enter [email protected]; the submitted value is matched against the configured attribute exactly. Active Directory's memberOf normally lists direct membership and returns distinguished names. Nested-group expansion is not performed by Jiandu, so use a directly assigned admission group or an attribute maintained by the directory.

Do not use a Global Catalog port (3268/3269) merely because it is available. A Global Catalog returns only replicated attributes and has forest-wide search semantics; use it only after verifying that the chosen immutable/group attributes are present and the search base is intentionally scoped.

Invitation-only variant​

For the safest first test, remove group_attribute and admission_group and use:

"admission_policy": "invitation_only"

LDAP does not support open admission. After the identity has consumed a matching invitation, later sign-ins resolve the same immutable subject without another invitation. If you later switch to required_group, the exact group check applies to every sign-in, including existing identities.

Validate and diagnose​

Test TLS and the service search from the same host/container trust environment as Jiandu before restart. Keep passwords out of command arguments; interactive ldapsearch -W is preferable:

ldapsearch -x -W \
-H ldaps://directory.example.net:636 \
-D 'cn=jiandu,ou=services,dc=example,dc=net' \
-b 'ou=people,dc=example,dc=net' \
'(&(uid=alex)(objectClass=inetOrgPerson))' \
entryUUID displayName memberOf

jiandu --config /etc/jiandu/jiandu.json --check-config
curl --fail --silent https://documents.example.net/api/v1/auth/methods \
| jq '.ldapProviders'
SymptomCheck
Provider absent after restartJSON is effective, provider ID is valid, and all configured providers initialized
Temporary/unavailable errorDNS, TCP reachability, system CA trust, hostname verification, service bind, or directory timeout
Every password is invalidSearch base/filter/username attribute returns exactly one entry and the user DN allows simple bind
Admission deniedExact returned group DN or valid invitation; direct versus nested membership
A renamed directory user becomes a new personsubject_attribute was mutable or stopped being returned; restore the immutable attribute mapping before linking anything

Jiandu bounds the whole directory operation to 20 seconds, connections to 5 seconds, and directory search time to 5 seconds. Local Owner/password recovery remains usable during a directory outage.