1 { config, lib, pkgs, utils, ... }:
4 cfg = config.services.lldap;
5 format = pkgs.formats.toml { };
8 options.services.lldap = with lib; {
9 enable = mkEnableOption "lldap, a lightweight authentication server that provides an opinionated, simplified LDAP interface for authentication";
11 package = mkPackageOption pkgs "lldap" { };
13 environment = mkOption {
14 type = with types; attrsOf str;
17 LLDAP_JWT_SECRET_FILE = "/run/lldap/jwt_secret";
18 LLDAP_LDAP_USER_PASS_FILE = "/run/lldap/user_password";
21 Environment variables passed to the service.
22 Any config option name prefixed with `LLDAP_` takes priority over the one in the configuration file.
26 environmentFile = mkOption {
27 type = types.nullOr types.path;
30 Environment file as defined in {manpage}`systemd.exec(5)` passed to the service.
36 Free-form settings written directly to the `lldap_config.toml` file.
37 Refer to <https://github.com/lldap/lldap/blob/main/lldap_config.docker_template.toml> for supported values.
42 type = types.submodule {
43 freeformType = format.type;
45 ldap_host = mkOption {
47 description = "The host address that the LDAP server will be bound to.";
51 ldap_port = mkOption {
53 description = "The port on which to have the LDAP server.";
57 http_host = mkOption {
59 description = "The host address that the HTTP server will be bound to.";
63 http_port = mkOption {
65 description = "The port on which to have the HTTP server, for user login and administration.";
71 description = "The public URL of the server, for password reset links.";
72 default = "http://localhost";
75 ldap_base_dn = mkOption {
77 description = "Base DN for LDAP.";
78 example = "dc=example,dc=com";
81 ldap_user_dn = mkOption {
83 description = "Admin username";
87 ldap_user_email = mkOption {
89 description = "Admin email.";
90 default = "admin@example.com";
93 database_url = mkOption {
95 description = "Database URL.";
96 default = "sqlite://./users.db?mode=rwc";
97 example = "postgres://postgres-user:password@postgres-server/my-database";
104 config = lib.mkIf cfg.enable {
105 systemd.services.lldap = {
106 description = "Lightweight LDAP server (lldap)";
107 wants = [ "network-online.target" ];
108 after = [ "network-online.target" ];
109 wantedBy = [ "multi-user.target" ];
110 # lldap defaults to a hardcoded `jwt_secret` value if none is provided, which is bad, because
111 # an attacker could create a valid admin jwt access token fairly trivially.
112 # Because there are 3 different ways `jwt_secret` can be provided, we check if any one of them is present,
113 # and if not, bootstrap a secret in `/var/lib/lldap/jwt_secret_file` and give that to lldap.
114 script = lib.optionalString (!cfg.settings ? jwt_secret) ''
115 if [[ -z "$LLDAP_JWT_SECRET_FILE" ]] && [[ -z "$LLDAP_JWT_SECRET" ]]; then
116 if [[ ! -e "./jwt_secret_file" ]]; then
117 ${lib.getExe pkgs.openssl} rand -base64 -out ./jwt_secret_file 32
119 export LLDAP_JWT_SECRET_FILE="./jwt_secret_file"
122 ${lib.getExe cfg.package} run --config-file ${format.generate "lldap_config.toml" cfg.settings}
125 StateDirectory = "lldap";
126 StateDirectoryMode = "0750";
127 WorkingDirectory = "%S/lldap";
132 EnvironmentFile = lib.mkIf (cfg.environmentFile != null) cfg.environmentFile;
134 inherit (cfg) environment;