Feature Specification: Component lldap
Created: 2026-10-07
Status: Implemented
Input: Reconstructed from the code at 093e68b6f (core/components/shared/lldap/default.nix, core/components/operator/operator-facing-options.csv, core/setups/hosts-common.nix, core/setups/groups/hosting-provider.nix, core/setups/groups/operator.nix, core/resources/ldap/, core/resources/oidc/), pull requests #1383, #1755, #1822, #1835, #1852 and #1874.
User Scenarios & Testing (mandatory)
User Story 1 - Keep the users of a group in one directory (Priority: P1)
Each group needs one user directory. The lldap component marks a node as
the directory of its group. It declares provides = [ "ldap" ], so the node
answers the ldap requests of the group (see
ldap). The setup sets the daemon and the
lldap-provision unit, which applies the seeded users and groups on this node
with OpenTofu.
Why this priority: the SSO portal and the applications read users from this directory.
Independent Test: contracts-ldap-lldap, test-ldap-engine,
test-lldap-seed-isolation and test-lldap-admin-release.
nix-unit-setups-ldap-directory and nix-unit-setups-ldap-grant cover the
wiring decisions.
Acceptance Scenarios:
- Given a group with an
lldapmachine, When the setups evaluate, Then the"directory".ldaprequest hasdomain = "ldap.<domain>",bindInstanceName = "ldap-bind-<group>"andhostset to thelldapmachine. - Given a group with an
autheliamachine, When the setups evaluate, Then thelldapnode gets a want for the bind groupauthelia-users.
User Story 2 - Let users manage their own account (Priority: P2)
A user changes a password or a display name in the lldap web UI. The
component requests "lldap-ui".oidc with forward-auth on the subdomain
users. Authelia protects the UI with one_factor and an empty
allowedGroups, so every signed-in user reaches it. The upstream is
127.0.0.1:17170. Only the routes of the password reset skip the protection,
so a user without a session can set a password from a reset link.
Why this priority: self-service is useful, but the directory works without it.
Independent Test: test-lldap-mail does a reset through the
forward-auth vhost and checks that the other routes keep the protection.
nix-unit-testers-oidc-routing covers the routing of oidc requests.
Acceptance Scenarios:
- Given a deployment with an
lldapmachine, When the setups evaluate, Then the"lldap-ui".oidcrequest hasforwardAuth.enable = true,subdomain = "users"andadminUI = false. - Given a user without a session, When the user requests a reset route on
users.<domain>, Then nginx sends the request to lldap. When the user requests a login route,/auth/refreshor/api/, Then nginx sends the user to Authelia.
User Story 3 - Send password reset mail (Priority: P3)
lldap sends password reset mail. When the group has an smtp machine, the
setup sets fediversity.lldap.smtp and adds a want for an lldap sender on
the relay (see smtp).
Why this priority: reset mail is a convenience. An admin can reset a password without it.
Independent Test: test-lldap-mail and nix-unit-setups-lldap-mail.
Acceptance Scenarios:
- Given a group with
lldapandsmtpmachines, When the setups evaluate, Thenfediversity.lldap.smtpis set on thelldapnode.
Edge Cases
- A group without an
lldapmachine useshost = "ldap.<domain>"in its"directory".ldaprequest. - lldap gets
publicUrl = https://users.<domain>when the node serves the forward-auth vhost of the UI. Else it getshttp://<ldap host>:<httpPort>, the direct listener. - The admin user is the group name (
adminUser = group). This keeps the name off theRestrictedNameslist of pixelfed. - lldap derives the OPAQUE server key from a seed in OpenBao KV, not from a key file in
/var/lib/lldap. Thus a node that is built again has the same key, and the passwords in a restored database work. lldap keeps a hash of the key in the database and does not start when the key changes. A database that has a key from a different source, for example a database from before the seed, starts once withLLDAP_FORCE_UPDATE_PRIVATE_KEY=true, and then each user resets the password.
Requirements (mandatory)
Functional Requirements
- FR-001: The component MUST declare
provides = [ "ldap" ], so the node answers theldaprequests of its group. - FR-002: The implementation MUST request
"spiffe".spiffe.enable = true, so the node logs in to OpenBao with its SVID. - FR-003: The implementation MUST request
"lldap-ui".oidcwithenable = falseandforwardAuthon subdomainusers, upstream127.0.0.1:17170, policyone_factorandallowedGroups = [ ]. - FR-003a: The implementation MUST give the request the
unauthenticatedLocationsfromreset-routes.nix, and MUST add thelldap-resetrate limit zone to the nginx config of the node. - FR-004: The lldap UI MUST NOT be an admin UI (
adminUI = false), sodeployAdminUIsdoes not gate it. - FR-005: The setup MUST set the daemon, the
lldap-provisionunit and the bind accounts from theldapresource. - FR-006: The implementation MUST request
postgresqlwith database, consumer and userlldap, and give the URL to the daemon inLLDAP_DATABASE_URL, because the daemon runs as aDynamicUser. When the result is present, thesettingsmodule MUST removedatabase_urlfrom the configuration file. When the result is absent, the daemon MUST keep its local SQLite database. - FR-007: The implementation MUST request
garagethroughmkBackupsGarageRequestwith the bucketlldap-backups, and astreamingBackupof thepostgresdatabase to that bucket with the restic provider, asroot. - FR-008: The
ldapresource MUST generate the seed of the OPAQUE server key once, in thegenerateFilesinstancekeySeedInstanceName(the setup setsldap-key-<group>), as an environment file that setsLLDAP_KEY_SEED. The daemon MUST read that file as anEnvironmentFileand setkey_file = "".
Key Entities
"directory".ldap: the request of the setup. It carriesdomain,bindInstanceName,adminUser,emailandhost."lldap-ui".oidc: the forward-auth request for the self-service UI.reset-routes.nix: the nginx locations of the reset that skip the forward-auth protection, and the zone that limits the rate of/auth/reset/.fediversity.lldap.smtp: the mail account for password reset mail.operator-facing-options.csv: rows withapplication = lldap. 34 options have the audiencefediversityand 2 have the audienceoperator.
Success Criteria (mandatory)
Measurable Outcomes
- SC-001:
contracts-ldap-lldap,test-ldap-engine,test-lldap-seed-isolation,test-lldap-admin-releaseandtest-lldap-mailpass. - SC-002:
nix-unit-setups-ldap-directory,nix-unit-setups-ldap-grantandnix-unit-setups-lldap-mailpass.
Assumptions
- The
hosting-providerandoperatorgroups both declare anlldapmachine. - The component has no icon.
applications-catalogshows it without artwork. - The provider implementation (
lldap-provider.nix,provision.nix) is incore/resources/ldap/, not in this component.
Known Deviations
None.