LLDAP Block

Table of Contents

Global Setup
SSL
Restrict Access By IP
Manage Groups
Manage Users
Troubleshooting
Tests
Options Reference

Defined in /modules/blocks/lldap.nix.

This block sets up an LLDAP service for user and group management across services.

Global Setup

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.ldap.jwtSecret.request;
shb.sops.secret."lldap/user_password".request = config.shb.ldap.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.ldap.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";

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 false.

{
  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.ensureUsers option.

  • Existing users will be updated, their password included, if they are mentioned in the shb.lldap.ensureUsers option.

  • Existing users not declared in the shb.lldap.ensureUsers will 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 =
    shb.ldap.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: false

Example: true

Declared 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 restor 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.service

Type: 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

And restore the database with:

$ restore restore latest

Type: string

Default: "restore"

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: false

Declared 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: true

Declared by:

<selfhostblocks/modules/blocks/lldap.nix>
shb.lldap.enforceUserMemberships

Remove users from groups not set declaratively.

Type: boolean

Default: false

Declared by:

<selfhostblocks/modules/blocks/lldap.nix>
shb.lldap.enforceUsers

Delete users not set declaratively.

Type: boolean

Default: false

Declared 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: true

Declared by:

<selfhostblocks/modules/blocks/lldap.nix>
shb.lldap.ensureGroupFields.<name>.isList

Is field a list.

Type: boolean

Default: false

Declared by:

<selfhostblocks/modules/blocks/lldap.nix>
shb.lldap.ensureGroupFields.<name>.isVisible

Is field visible in UI.

Type: boolean

Default: true

Declared 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 ensureGroupFields option.

Type: attribute set 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: true

Declared by:

<selfhostblocks/modules/blocks/lldap.nix>
shb.lldap.ensureUserFields.<name>.isList

Is field a list.

Type: boolean

Default: false

Declared by:

<selfhostblocks/modules/blocks/lldap.nix>
shb.lldap.ensureUserFields.<name>.isVisible

Is field visible in UI.

Type: boolean

Default: true

Declared 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 enforceUsers option is true, the groups users belong to must be present in the ensureGroups option.

Non-default options must be added to the ensureGroupFields option.

Type: attribute set 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: null

Declared 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: null

Declared by:

<selfhostblocks/modules/blocks/lldap.nix>
shb.lldap.ensureUsers.<name>.displayName

Display name.

Type: null or string

Default: null

Declared 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: null

Declared by:

<selfhostblocks/modules/blocks/lldap.nix>
shb.lldap.ensureUsers.<name>.gravatar_avatar

Get avatar from Gravatar using the email.

Type: null or string

Default: null

Declared 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: null

Declared 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: null

Declared 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: 3890

Declared by:

<selfhostblocks/modules/blocks/lldap.nix>
shb.lldap.ldapUserPassword

LDAP admin user secret.

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: 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: null

Example: "192.168.1.1/24"

Declared by:

<selfhostblocks/modules/blocks/lldap.nix>
shb.lldap.ssl

Path to SSL files

Type: null or (anything)

Default: null

Declared 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.certs SSL block.

Type: 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 .service suffix.

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: 17170

Declared by:

<selfhostblocks/modules/blocks/lldap.nix>