Feature Specification: Resource ldap

Created: 2026-10-07

Status: Implemented

Input: Reconstructed from the code of core/resources/ldap/ and its wiring in core/setups/ and core/effects/ at 093e68b6f, pull requests #1184, #1205 and #1236, and issue #527.

User Scenarios & Testing (mandatory)

The ldap resource gives a component an LDAP directory for its users, through the ldap contract and the lldap provider. The shared rules are in the resource family spec.

User Story 1 - Give a component a directory to authenticate against (Priority: P1)

Without a directory, nobody can log in: the single sign-on provider and other components read their users from it. A request names a directory group and a consumer name. The consumer node gets the directory coordinates and a bind password file in the ldap contract result.

Independent Test: a contract test of ldap with lldap, a VM test of the OpenBao LDAP engine, and unit tests of both sides.

Acceptance Scenarios:

  1. Given a routed request, When the consumer side evaluates, Then the result holds the group and all coordinates: with TLS the LDAPS URL and the CA, without TLS the plaintext URL and no CA.
  2. Given a directory domain and no base DN, When the policy evaluates, Then the base DN is one dc= part per label after the first, for example dc=example,dc=org for ldap.example.org.
  3. Given no routed provider, When the deployment evaluates, Then there is no consumer side, no provider side and no daemon.
  4. Given the OpenBao LDAP engine, When the consumer binds as authelia-bind, Then it can set the password of a user outside lldap_admin and of no lldap_admin member.

User Story 2 - Run the directory and seed its accounts (Priority: P1)

An empty directory has no admin and no groups. The provider node runs lldap, and a provisioning job applies the groups, the seeded users and their memberships. The administrator of the deployment gets single sign-on, admin UI access and directory admin rights. The password of admin resets to the generated bind password at each start.

Independent Test: VM tests of the seed isolation, the release of the administrator mail address and the provisioning, and unit tests of the grant and the coordinates.

Acceptance Scenarios:

  1. Given an administrator, When the provisioning runs, Then the administrator is a member of authelia-users, fediversity-admins and lldap_admin.
  2. Given the administrator mail address held by another account, When the provisioning runs, Then it deletes that account. When admin or authelia-bind holds it, the provisioning logs this, keeps the account, and the apply of the administrator fails.
  3. Given a seeded object that exists outside the state, When the provisioning runs, Then it adopts it. An object that the deployment did not seed stays.

User Story 3 - Serve the directory over LDAPS (Priority: P2)

On plaintext LDAP, the bind password and the user passwords that authelia checks cross the network in clear text. A group with an lldap node must therefore have a private CA. The node serves LDAPS with a leaf for its host from the central OpenBao PKI, through the openbao-pki provider of the ssl contract, and each consumer verifies against the PKI CA.

Independent Test: the ldap contract test binds over LDAPS against a test CA. The LDAP engine VM test moves a mount from plaintext to LDAPS and rotates a password through it. The application VM tests bind authelia over LDAPS with a PKI leaf.

Acceptance Scenarios:

  1. Given a private CA, When the provider node evaluates, Then lldap gets the leaf for its host as credentials, and a renewal restarts it.
  2. Given an lldap node and no private CA, When the group evaluates, Then the evaluation fails and names the node and the private CA.
  3. Given a node that runs authelia or the vars backend, When it evaluates, Then it trusts the PKI CA of the lldap host.

User Story 4 - Publish the directory coordinates (Priority: P2)

A consumer in another group cannot read the configuration of the provider group, so the provider node publishes the URL, the user base DN and the management bind DN to OpenBao KV.

Independent Test: a unit test of the coordinates.


User Story 5 - Send password reset mail (Priority: P3)

A user resets a password without an operator. lldap sends reset mail through the smtp resource, from a no-reply address on the users domain. The link goes to the UI behind forward auth, which lets only the reset routes through without a session. This is the only reset for an lldap_admin member, such as the administrator.

Independent Test: a VM test of the reset mail, and a unit test of the mail wiring.

Acceptance Scenarios:

  1. Given a user without a session, When the user follows the reset link, Then the reset succeeds, and the login and API routes still send the user to authelia.
  2. Given an empty relay password, When lldap starts, Then the start fails.

Edge Cases

  • The relay connection uses STARTTLS. lldap trusts only the public web PKI roots, so a relay with a private CA fails.
  • A relay with an empty public URL fails evaluation. Without a forward-auth entry point (no oidc provider in the group), the public URL is the plaintext listener, and the reset token travels in clear.
  • A mail address for the administrator without an administrator fails evaluation.
  • lldap makes no private CA, and a self-signed leaf gives consumers nothing to verify. A bootstrap node or a valkey node gives the group a private CA.
  • The API is not ready: the provisioning waits for a bounded time, fails, and retries later. A failed admin login fails before the apply. A failed seeded user does not stop the others.
  • Until the leaf exists, lldap does not start, on the plaintext port either. A renewal, in the last third of the leaf lifetime, closes the open consumer connections.
  • A stop with an open TLS database connection hangs until a short timeout kills it.
  • The PKI CA is not on the engine node yet: the LDAPS mount has no CA, and its bind fails until the CA arrives.

Requirements (mandatory)

Functional Requirements

  • FR-001: Without an explicit base DN, the policy MUST derive it from the directory domain: drop the first label, one dc= part per remaining label.
  • FR-002: Users MUST be under ou=people and groups under ou=groups of the base DN.
  • FR-003: The policy MUST give a consumer side and a provider side only when a provider is routed.
  • FR-004: The policy MUST run the daemon only when a provider is routed.
  • FR-005: The policy MUST generate a random bind password as a secret, readable by every account on the provider node.
  • FR-006: The provider MUST give each want the URL, the CA, the host, the port, the base DNs, the bind DN and the bind password file. With TLS, the URL and port MUST be LDAPS and the CA the PKI CA. Without TLS, they MUST be plaintext, with no CA.
  • FR-007: The provider node MUST open the web port. With TLS, it MUST open the LDAPS port and MUST NOT open the plaintext port. The plaintext listener then still answers on the loopback interface, where the provisioning connects. Without TLS, the node MUST open the plaintext port.
  • FR-008: The provisioning MUST create fediversity-admins and each group of a seeded user, and MUST put authelia-bind in lldap_password_manager, not in lldap_admin.
  • FR-009: The provisioning MUST delete a seeded user, group or membership that leaves the configuration, and MUST NOT change one that is not in its state.
  • FR-009a: The provisioning MUST adopt a seeded user, group or membership that exists outside its state.
  • FR-010: The release of the administrator mail address MUST NOT delete admin or authelia-bind.
  • FR-011: The provider node MUST publish the coordinates to OpenBao KV when it has the OpenBao vars backend.
  • FR-012: The request MUST hold only data. The consumer side MUST give the result to the ldap contract of the consumer node.
  • FR-013: The setups MUST read the administrator from the group data, and evaluation MUST fail when a group with an lldap node does not name one.
  • FR-014: The forward-auth entry point MUST let only the reset routes through without a session, and MUST limit the rate of reset requests per client address.
  • FR-015: In a group with a private CA, the provider node MUST serve LDAPS with a leaf for its host from the openbao-pki ssl provider, and the result CA MUST be the PKI CA. Evaluation MUST fail for an lldap node without a private CA. TLS is on by default. A deployment MAY turn it off, and the listener and consumers then stay on plaintext.
  • FR-016: Each consumer MUST connect to the result URL and verify LDAPS against the PKI CA: authelia through the system trust, the LDAP engine by giving the CA to the mount and its bind checks.
  • FR-017: The LDAP engine MUST point a mount at a published URL with the same host and another scheme or port, and MUST NOT treat this as a second live deployment.

Key Entities

  • ldap contract result: the URL, the CA (none for plaintext), the host, the port, the base DNs, the bind DN and the bind password file.
  • Directory coordinates: the URL, the user base DN and the management bind DN (admin).

Success Criteria (mandatory)

Measurable Outcomes

  • SC-001: The ldap contract test, and the VM tests of the provisioning, the LDAP engine, the seed isolation, the admin release and the reset mail pass.
  • SC-002: The unit tests of the grant, the coordinates, the mail wiring, the TLS routing and both sides pass.
  • SC-003: After a deployment, the administrator logs in through single sign-on and opens the lldap web UI.

Assumptions

  • The daemon is the lldap component. The main consumer is authelia through oidc.
  • Without the LDAP engine, the consumer binds as admin with full rights. Only test topologies run so, and a separate bind account would not help there, because the consumer can read the admin password in KV. A unit test checks that authelia binds as authelia-bind with the engine and as admin without it.

Known Deviations

None.