LLDAP Block
Defined in /modules/blocks/lldap.nix.
This block sets up an LLDAP service for user and group management across services.
Features
Integration with the dashboard contract for displaying user facing application in a dashboard. Manual
Usage
Initial Configuration
shb.lldap = {
enable = true;
subdomain = "ldap";
domain = "example.com";
dcdomain = "dc=example,dc=com";
ldapPort = 3890;
webUIListenPort = 17170;
jwtSecret.result = config.shb.sops.secret."lldap/jwt_secret".result;
ldapUserPassword.result = config.shb.sops.secret."lldap/user_password".result;
};
shb.sops.secret."lldap/jwt_secret".request = config.shb.lldap.jwtSecret.request;
shb.sops.secret."lldap/user_password".request = config.shb.lldap.ldapUserPassword.request;
This assumes secrets are setup with SOPS as mentioned in the secrets setup section of the manual.
SSL
Using SSL is an important security practice, like always. Using the SSL block, the configuration to add to the one above is:
shb.certs.certs.letsencrypt.${domain}.extraDomains = [
"${config.shb.lldap.subdomain}.${config.shb.lldap.domain}"
];
shb.lldap.ssl = config.shb.certs.certs.letsencrypt.${config.shb.lldap.domain};
Restrict Access By IP
For added security, you can restrict access to the LLDAP UI by adding the following line:
shb.lldap.restrictAccessIPRange = "192.168.50.0/24";
Application Dashboard
Integration with the dashboard contract is provided by the dashboard option.
For example using the Homepage service:
{
shb.homepage.servicesGroups.Admin.services.LLDAP = {
sortOrder = 2;
dashboard.request = config.shb.lldap.dashboard.request;
};
}
Manage Groups
The following snippet will create group named “family” if it does not exist yet. Also, all other groups will be deleted and only the “family” group will remain.
Note that the lldap_admin, lldap_password_manager and lldap_strict_readonly groups, which are internal to LLDAP, will always exist.
If you want existing groups not declared in the shb.lldap.ensureGroups to be deleted,
set shb.lldap.enforceGroups to true.
{
shb.lldap.ensureGroups = {
family = {};
};
}
Changing the configuration to the following will add a new group “friends”:
{
shb.lldap.ensureGroups = {
family = {};
friends = {};
};
}
Switching back the configuration to the previous one will delete the group “friends”:
{
shb.lldap.ensureGroups = {
family = {};
};
}
Custom fields can be added to groups as long as they are added to the ensureGroupFields field:
shb.lldap = {
ensureGroupFields = {
mygroupattribute = {
attributeType = "STRING";
};
};
ensureGroups = {
family = {
mygroupattribute = "Managed by NixOS";
};
};
};
Manage Users
The following snippet creates a user and makes it a member of the “family” group.
Note the following behavior:
New users will be created following the
shb.lldap.ensureUsersoption.Existing users will be updated, their password included, if they are mentioned in the
shb.lldap.ensureUsersoption.Existing users not declared in the
shb.lldap.ensureUserswill be left as-is.User memberships to groups not declared in their respective
shb.lldap.ensureUsers.<name>.groups.
If you want existing users not declared in the shb.lldap.ensureUsers to be deleted,
set shb.lldap.enforceUsers to true.
If you want memberships to groups not declared in the respective
shb.lldap.ensureUsers.<name>.groups option to be deleted,
set shb.lldap.enforceUserMemberships true.
{
shb.lldap.ensureUsers = {
dad = {
email = "dad@example.com";
displayName = "Dad";
firstName = "First Name";
lastName = "Last Name";
groups = [ "family" ];
password.result = config.shb.sops.secret."dad".result;
};
};
shb.sops.secret."dad".request =
config.shb.lldap.ensureUsers.dad.password.request;
}
The password field assumes usage of the sops block to provide secrets although any blocks providing the secrets contract works too.
The user is still editable through the UI. That being said, any change will be overwritten next time the configuration is applied.
Troubleshooting
To increase logging verbosity and see the trace of the GraphQL queries, add:
shb.lldap.debug = true;
Note that verbosity is truly verbose here so you will want to revert this at some point.
To see the logs, then run journalctl -u lldap.service.
Setting the debug option to true will also
add an shb.mitmdump instance in front of the LLDAP web UI port
which prints all requests and responses headers and body
to the systemd service mitmdump-lldap.service. Note the you won’t
see the query done using something like ldapsearch since those
go through the LDAP port.
Tests
Specific integration tests are defined in /test/blocks/lldap.nix.
Options Reference
-
shb.lldap.enable -
Whether to enable the LDAP service.
Type: boolean
Default:
falseExample:
trueDeclared by:
<selfhostblocks/modules/blocks/lldap.nix> -
shb.lldap.backup -
Backup configuration.
Type: submodule
Declared by:
<selfhostblocks/modules/blocks/lldap.nix> -
shb.lldap.backup.request -
Request part of the backup contract.
Options set by the requester module enforcing how to backup files.
Type: submodule
Default:
""Declared by:
<selfhostblocks/modules/blocks/lldap.nix> -
shb.lldap.backup.request.excludePatterns -
File patterns to exclude.
Type: list of string
Default:
[ ]Declared by:
<selfhostblocks/modules/blocks/lldap.nix> -
shb.lldap.backup.request.hooks -
Hooks to run around the backup.
Type: submodule
Default:
{ }Declared by:
<selfhostblocks/modules/blocks/lldap.nix> -
shb.lldap.backup.request.hooks.afterBackup -
Hooks to run after backup.
Type: list of string
Default:
[ ]Declared by:
<selfhostblocks/modules/blocks/lldap.nix> -
shb.lldap.backup.request.hooks.beforeBackup -
Hooks to run before backup.
Type: list of string
Default:
[ ]Declared by:
<selfhostblocks/modules/blocks/lldap.nix> -
shb.lldap.backup.request.sourceDirectories -
Directories to backup.
Type: non-empty (list of string)
Default:
[ "/var/lib/private/lldap" ]Example:
"/var/lib/vaultwarden"Declared by:
<selfhostblocks/modules/blocks/lldap.nix> -
shb.lldap.backup.request.user -
Unix user doing the backups.
Type: string
Default:
"root"Example:
"vaultwarden"Declared by:
<selfhostblocks/modules/blocks/lldap.nix> -
shb.lldap.backup.result -
Result part of the backup contract.
Options set by the provider module that indicates the name of the backup and restore scripts.
Type: submodule
Default:
""Declared by:
<selfhostblocks/modules/blocks/lldap.nix> -
shb.lldap.backup.result.backupService -
Name of service backing up the database.
This script can be ran manually to backup the database:
$ systemctl start backup.serviceType: string
Default:
"backup.service"Declared by:
<selfhostblocks/modules/blocks/lldap.nix> -
shb.lldap.backup.result.restoreScript -
Name of script that can restore the database. One can then list snapshots with:
$ restore snapshots <snapshot 1> <metadata> <snapshot 2> <metadata>And restore the database with:
$ restore restore <snapshot 1>Type: string
Default:
"restore"Declared by:
<selfhostblocks/modules/blocks/lldap.nix> -
shb.lldap.dashboard -
Dashboard contract consumer
Type: submodule
Default:
{ }Declared by:
<selfhostblocks/modules/blocks/lldap.nix> -
shb.lldap.dashboard.request -
Request part of the dashboard contract.
Type: submodule
Default:
{ }Declared by:
<selfhostblocks/modules/blocks/lldap.nix> -
shb.lldap.dashboard.request.externalUrl -
URL at which the service can be accessed.
This URL should go through the reverse proxy.
Type: string
Default:
"https://\${config.shb.lldap.subdomain}.\${config.shb.lldap.domain}"Example:
"https://jellyfin.example.com"Declared by:
<selfhostblocks/modules/blocks/lldap.nix> -
shb.lldap.dashboard.request.internalUrl -
URL at which the service can be accessed directly.
This URL should bypass the reverse proxy. It can be used for example to ping the service and making sure it is up and running correctly.
Type: null or string
Default:
"http://127.0.0.1:17170"Example:
"http://127.0.0.1:8081"Declared by:
<selfhostblocks/modules/blocks/lldap.nix> -
shb.lldap.dashboard.result -
Result part of the dashboard contract.
No option is provided here.
Type: submodule
Default:
{ }Declared by:
<selfhostblocks/modules/blocks/lldap.nix> -
shb.lldap.dcdomain -
dc domain to serve.
Type: string
Example:
"dc=mydomain,dc=com"Declared by:
<selfhostblocks/modules/blocks/lldap.nix> -
shb.lldap.debug -
Enable debug logging.
Type: boolean
Default:
falseDeclared by:
<selfhostblocks/modules/blocks/lldap.nix> -
shb.lldap.domain -
Domain under which the LDAP service will be served.
Type: string
Example:
"mydomain.com"Declared by:
<selfhostblocks/modules/blocks/lldap.nix> -
shb.lldap.enforceGroups -
Remove groups not set declaratively.
Type: boolean
Default:
falseDeclared by:
<selfhostblocks/modules/blocks/lldap.nix> -
shb.lldap.enforceUserMemberships -
Remove users from groups not set declaratively.
Type: boolean
Default:
falseDeclared by:
<selfhostblocks/modules/blocks/lldap.nix> -
shb.lldap.enforceUsers -
Delete users not set declaratively.
Type: boolean
Default:
falseDeclared by:
<selfhostblocks/modules/blocks/lldap.nix> -
shb.lldap.ensureGroupFields -
Extra fields for groups
Type: attribute set of (submodule)
Default:
{ }Declared by:
<selfhostblocks/modules/blocks/lldap.nix> -
shb.lldap.ensureGroupFields.<name>.attributeType -
Attribute type.
Type: one of “STRING”, “INTEGER”, “JPEG”, “DATE_TIME”
Declared by:
<selfhostblocks/modules/blocks/lldap.nix> -
shb.lldap.ensureGroupFields.<name>.isEditable -
Is field editable.
Type: boolean
Default:
trueDeclared by:
<selfhostblocks/modules/blocks/lldap.nix> -
shb.lldap.ensureGroupFields.<name>.isList -
Is field a list.
Type: boolean
Default:
falseDeclared by:
<selfhostblocks/modules/blocks/lldap.nix> -
shb.lldap.ensureGroupFields.<name>.isVisible -
Is field visible in UI.
Type: boolean
Default:
trueDeclared by:
<selfhostblocks/modules/blocks/lldap.nix> -
shb.lldap.ensureGroupFields.<name>.name -
Name of the field.
Type: string
Default:
"‹name›"Declared by:
<selfhostblocks/modules/blocks/lldap.nix> -
shb.lldap.ensureGroups -
Create the groups defined here on service startup.
Non-default options must be added to the
ensureGroupFieldsoption.Type: attribute set of (open submodule of (JSON value))
Default:
{ }Declared by:
<selfhostblocks/modules/blocks/lldap.nix> -
shb.lldap.ensureGroups.<name>.name -
Name of the group.
Type: string
Default:
"‹name›"Declared by:
<selfhostblocks/modules/blocks/lldap.nix> -
shb.lldap.ensureUserFields -
Extra fields for users
Type: attribute set of (submodule)
Default:
{ }Declared by:
<selfhostblocks/modules/blocks/lldap.nix> -
shb.lldap.ensureUserFields.<name>.attributeType -
Attribute type.
Type: one of “STRING”, “INTEGER”, “JPEG”, “DATE_TIME”
Declared by:
<selfhostblocks/modules/blocks/lldap.nix> -
shb.lldap.ensureUserFields.<name>.isEditable -
Is field editable.
Type: boolean
Default:
trueDeclared by:
<selfhostblocks/modules/blocks/lldap.nix> -
shb.lldap.ensureUserFields.<name>.isList -
Is field a list.
Type: boolean
Default:
falseDeclared by:
<selfhostblocks/modules/blocks/lldap.nix> -
shb.lldap.ensureUserFields.<name>.isVisible -
Is field visible in UI.
Type: boolean
Default:
trueDeclared by:
<selfhostblocks/modules/blocks/lldap.nix> -
shb.lldap.ensureUserFields.<name>.name -
Name of the field.
Type: string
Default:
"‹name›"Declared by:
<selfhostblocks/modules/blocks/lldap.nix> -
shb.lldap.ensureUsers -
Create the users defined here on service startup.
If
enforceUsersoption istrue, the groups users belong to must be present in theensureGroupsoption.Non-default options must be added to the
ensureGroupFieldsoption.Type: attribute set of (open submodule of (JSON value))
Default:
{ }Declared by:
<selfhostblocks/modules/blocks/lldap.nix> -
shb.lldap.ensureUsers.<name>.avatar_file -
Avatar file. Must be a valid path to jpeg file (ignored if avatar_url specified)
Type: null or string
Default:
nullDeclared by:
<selfhostblocks/modules/blocks/lldap.nix> -
shb.lldap.ensureUsers.<name>.avatar_url -
Avatar url. must be a valid URL to jpeg file (ignored if gravatar_avatar specified)
Type: null or string
Default:
nullDeclared by:
<selfhostblocks/modules/blocks/lldap.nix> -
shb.lldap.ensureUsers.<name>.displayName -
Display name.
Type: null or string
Default:
nullDeclared by:
<selfhostblocks/modules/blocks/lldap.nix> -
shb.lldap.ensureUsers.<name>.email -
Email.
Type: string
Declared by:
<selfhostblocks/modules/blocks/lldap.nix> -
shb.lldap.ensureUsers.<name>.firstName -
First name.
Type: null or string
Default:
nullDeclared by:
<selfhostblocks/modules/blocks/lldap.nix> -
shb.lldap.ensureUsers.<name>.gravatar_avatar -
Get avatar from Gravatar using the email.
Type: null or string
Default:
nullDeclared by:
<selfhostblocks/modules/blocks/lldap.nix> -
shb.lldap.ensureUsers.<name>.groups -
Groups the user would be a member of (all the groups must be specified in group config files).
Type: list of string
Default:
[ ]Declared by:
<selfhostblocks/modules/blocks/lldap.nix> -
shb.lldap.ensureUsers.<name>.id -
Username.
Type: string
Default:
"‹name›"Declared by:
<selfhostblocks/modules/blocks/lldap.nix> -
shb.lldap.ensureUsers.<name>.lastName -
Last name.
Type: null or string
Default:
nullDeclared by:
<selfhostblocks/modules/blocks/lldap.nix> -
shb.lldap.ensureUsers.<name>.password -
Password.
Type: submodule
Declared by:
<selfhostblocks/modules/blocks/lldap.nix> -
shb.lldap.ensureUsers.<name>.password.request -
Request part of the secret contract.
Options set by the requester module enforcing some properties the secret should have.
Type: submodule
Default:
""Declared by:
<selfhostblocks/modules/blocks/lldap.nix> -
shb.lldap.ensureUsers.<name>.password.request.group -
Linux group owning the secret file.
Type: string
Default:
"lldap"Declared by:
<selfhostblocks/modules/blocks/lldap.nix> -
shb.lldap.ensureUsers.<name>.password.request.mode -
Mode of the secret file.
Type: string
Default:
"0440"Declared by:
<selfhostblocks/modules/blocks/lldap.nix> -
shb.lldap.ensureUsers.<name>.password.request.owner -
Linux user owning the secret file.
Type: string
Default:
"lldap"Declared by:
<selfhostblocks/modules/blocks/lldap.nix> -
shb.lldap.ensureUsers.<name>.password.request.restartUnits -
Systemd units to restart after the secret is updated.
Type: list of string
Default:
[ "lldap.service" ]Declared by:
<selfhostblocks/modules/blocks/lldap.nix> -
shb.lldap.ensureUsers.<name>.password.result -
Result part of the secret contract.
Options set by the provider module that indicates where the secret can be found.
Type: submodule
Default:
{ path = "/run/secrets/secret"; }Declared by:
<selfhostblocks/modules/blocks/lldap.nix> -
shb.lldap.ensureUsers.<name>.password.result.path -
Path to the file containing the secret generated out of band.
This path will exist after deploying to a target host, it is not available through the nix store.
Type: absolute path
Default:
"/run/secrets/secret"Declared by:
<selfhostblocks/modules/blocks/lldap.nix> -
shb.lldap.ensureUsers.<name>.weser_avatar -
Convert avatar retrieved by gravatar or the URL.
Type: null or string
Default:
nullDeclared by:
<selfhostblocks/modules/blocks/lldap.nix> -
shb.lldap.jwtSecret -
JWT secret.
Type: submodule
Declared by:
<selfhostblocks/modules/blocks/lldap.nix> -
shb.lldap.jwtSecret.request -
Request part of the secret contract.
Options set by the requester module enforcing some properties the secret should have.
Type: submodule
Default:
""Declared by:
<selfhostblocks/modules/blocks/lldap.nix> -
shb.lldap.jwtSecret.request.group -
Linux group owning the secret file.
Type: string
Default:
"lldap"Declared by:
<selfhostblocks/modules/blocks/lldap.nix> -
shb.lldap.jwtSecret.request.mode -
Mode of the secret file.
Type: string
Default:
"0440"Declared by:
<selfhostblocks/modules/blocks/lldap.nix> -
shb.lldap.jwtSecret.request.owner -
Linux user owning the secret file.
Type: string
Default:
"lldap"Declared by:
<selfhostblocks/modules/blocks/lldap.nix> -
shb.lldap.jwtSecret.request.restartUnits -
Systemd units to restart after the secret is updated.
Type: list of string
Default:
[ "lldap.service" ]Declared by:
<selfhostblocks/modules/blocks/lldap.nix> -
shb.lldap.jwtSecret.result -
Result part of the secret contract.
Options set by the provider module that indicates where the secret can be found.
Type: submodule
Default:
{ path = "/run/secrets/secret"; }Declared by:
<selfhostblocks/modules/blocks/lldap.nix> -
shb.lldap.jwtSecret.result.path -
Path to the file containing the secret generated out of band.
This path will exist after deploying to a target host, it is not available through the nix store.
Type: absolute path
Default:
"/run/secrets/secret"Declared by:
<selfhostblocks/modules/blocks/lldap.nix> -
shb.lldap.ldapPort -
Port on which the server listens for the LDAP protocol.
Type: 16 bit unsigned integer; between 0 and 65535 (both inclusive)
Default:
3890Declared by:
<selfhostblocks/modules/blocks/lldap.nix> -
shb.lldap.ldapUserPassword -
LDAP admin user secret. Must be >= 8 characters.
Type: submodule
Declared by:
<selfhostblocks/modules/blocks/lldap.nix> -
shb.lldap.ldapUserPassword.request -
Request part of the secret contract.
Options set by the requester module enforcing some properties the secret should have.
Type: submodule
Default:
""Declared by:
<selfhostblocks/modules/blocks/lldap.nix> -
shb.lldap.ldapUserPassword.request.group -
Linux group owning the secret file.
Type: string
Default:
"lldap"Declared by:
<selfhostblocks/modules/blocks/lldap.nix> -
shb.lldap.ldapUserPassword.request.mode -
Mode of the secret file.
Type: string
Default:
"0440"Declared by:
<selfhostblocks/modules/blocks/lldap.nix> -
shb.lldap.ldapUserPassword.request.owner -
Linux user owning the secret file.
Type: string
Default:
"lldap"Declared by:
<selfhostblocks/modules/blocks/lldap.nix> -
shb.lldap.ldapUserPassword.request.restartUnits -
Systemd units to restart after the secret is updated.
Type: list of string
Default:
[ "lldap.service" ]Declared by:
<selfhostblocks/modules/blocks/lldap.nix> -
shb.lldap.ldapUserPassword.result -
Result part of the secret contract.
Options set by the provider module that indicates where the secret can be found.
Type: submodule
Default:
{ path = "/run/secrets/secret"; }Declared by:
<selfhostblocks/modules/blocks/lldap.nix> -
shb.lldap.ldapUserPassword.result.path -
Path to the file containing the secret generated out of band.
This path will exist after deploying to a target host, it is not available through the nix store.
Type: absolute path
Default:
"/run/secrets/secret"Declared by:
<selfhostblocks/modules/blocks/lldap.nix> -
shb.lldap.mount -
Mount configuration. This is an output option.
Use it to initialize a block implementing the “mount” contract. For example, with a zfs dataset:
shb.zfs.datasets."ldap" = { poolName = "root"; } // config.shb.lldap.mount;Type: open submodule of anything (read only)
Default:
{ path = "/var/lib/lldap"; }Declared by:
<selfhostblocks/modules/blocks/lldap.nix> -
shb.lldap.mount.path -
Path to be mounted.
Type: string
Declared by:
<selfhostblocks/modules/blocks/lldap.nix> -
shb.lldap.restrictAccessIPRange -
Set a local network range to restrict access to the UI to only those IPs.
Type: null or string
Default:
nullExample:
"192.168.1.1/24"Declared by:
<selfhostblocks/modules/blocks/lldap.nix> -
shb.lldap.ssl -
Path to SSL files
Type: null or (open submodule of anything)
Default:
nullDeclared by:
<selfhostblocks/modules/blocks/lldap.nix> -
shb.lldap.ssl.paths -
Paths where the files for the certificate will be located.
This option is the contract output of the
shb.certs.certsSSL block.Type: open submodule of anything
Declared by:
<selfhostblocks/modules/blocks/lldap.nix> -
shb.lldap.ssl.paths.cert -
Path to the cert file.
Type: absolute path
Declared by:
<selfhostblocks/modules/blocks/lldap.nix> -
shb.lldap.ssl.paths.key -
Path to the key file.
Type: absolute path
Declared by:
<selfhostblocks/modules/blocks/lldap.nix> -
shb.lldap.ssl.systemdService -
Systemd oneshot service used to generate the certificate. Ends with the
.servicesuffix.Use this if downstream services must wait for the certificates to be generated before starting.
Type: string
Example:
"cert-generator.service"Declared by:
<selfhostblocks/modules/blocks/lldap.nix> -
shb.lldap.subdomain -
Subdomain under which the LDAP service will be served.
Type: string
Example:
"grafana"Declared by:
<selfhostblocks/modules/blocks/lldap.nix> -
shb.lldap.webUIListenPort -
Port on which the web UI is exposed.
Type: 16 bit unsigned integer; between 0 and 65535 (both inclusive)
Default:
17170Declared by:
<selfhostblocks/modules/blocks/lldap.nix>