lldap: add options to set important secrets

This commit is contained in:
ibizaman
2025-08-24 21:14:11 +02:00
committed by Herwig Hochleitner
parent 1bda23e1fb
commit 42b6d62699
2 changed files with 134 additions and 9 deletions
@@ -102,12 +102,83 @@ in
default = "sqlite://./users.db?mode=rwc";
example = "postgres://postgres-user:password@postgres-server/my-database";
};
ldap_user_pass_file = mkOption {
type = types.nullOr types.str;
default = null;
description = ''
Path to a file containing the default admin password.
If you want to update the default admin password through this setting,
you must set `force_ldap_user_pass_reset` to `true`.
Otherwise changing this setting will have no effect
unless this is the very first time LLDAP is started and its database is still empty.
'';
};
force_ldap_user_pass_reset = mkOption {
type = types.oneOf [
types.bool
(types.enum [ "always" ])
];
default = false;
description = ''
Force reset of the admin password.
Set this setting to `"always"` to update the admin password when `ldap_user_pass_file` changes.
Setting to `"always"` also means any password update in the UI will be overwritten next time the service restarts.
The difference between `true` and `"always"` is the former is intended for a one time fix
while the latter is intended for a declarative workflow. In practice, the result
is the same: the password gets reset. The only practical difference is the former
outputs a warning message while the latter outputs an info message.
'';
};
jwt_secret_file = mkOption {
type = types.nullOr types.str;
default = null;
description = ''
Path to a file containing the JWT secret.
'';
};
};
};
# TOML does not allow null values, so we use null to omit those fields
apply = lib.filterAttrsRecursive (_: v: v != null);
};
silenceForceUserPassResetWarning = mkOption {
type = types.bool;
default = false;
description = ''
Disable warning when the admin password is set declaratively with the `ldap_user_pass_file` setting
but the `force_ldap_user_pass_reset` is set to `false`.
This can lead to the admin password to drift from the one given declaratively.
If that is okay for you and you want to silence the warning, set this option to `true`.
'';
};
};
config = lib.mkIf cfg.enable {
warnings =
lib.optionals
(
(cfg.settings.ldap_user_pass_file or null) != null
&& cfg.settings.force_ldap_user_pass_reset == false
&& cfg.silenceForceUserPassResetWarning == false
)
[
''
lldap: The default admin password is declared with the setting `ldap_user_pass_file`, but `force_ldap_user_pass_reset` is set to `false`.
This means the admin password can be changed through the UI and will drift from the one defined in your nix config.
It also means changing the setting `ldap_user_pass_file` will have no effect on the admin password.
Either set `force_ldap_user_pass_reset` to `"always"` or silence this warning by setting the option `services.lldap.silenceForceUserPassResetWarning` to `true`.
''
];
systemd.services.lldap = {
description = "Lightweight LDAP server (lldap)";
wants = [ "network-online.target" ];
+63 -9
View File
@@ -1,4 +1,7 @@
{ ... }:
let
adminPassword = "mySecretPassword";
in
{
name = "lldap";
@@ -7,23 +10,74 @@
{
services.lldap = {
enable = true;
settings = {
verbose = true;
ldap_base_dn = "dc=example,dc=com";
};
};
environment.systemPackages = [ pkgs.openldap ];
specialisation = {
differentAdminPassword.configuration =
{ ... }:
{
services.lldap.settings = {
ldap_user_pass_file = toString (pkgs.writeText "adminPasswordFile" adminPassword);
force_ldap_user_pass_reset = "always";
};
};
changeAdminPassword.configuration =
{ ... }:
{
services.lldap.settings = {
ldap_user_pass_file = toString (pkgs.writeText "adminPasswordFile" "password");
force_ldap_user_pass_reset = false;
};
};
};
};
testScript = ''
machine.wait_for_unit("lldap.service")
machine.wait_for_open_port(3890)
machine.wait_for_open_port(17170)
testScript =
{ nodes, ... }:
let
specializations = "${nodes.machine.system.build.toplevel}/specialisation";
in
''
machine.wait_for_unit("lldap.service")
machine.wait_for_open_port(3890)
machine.wait_for_open_port(17170)
machine.succeed("curl --location --fail http://localhost:17170/")
machine.succeed("curl --location --fail http://localhost:17170/")
print(
machine.succeed('ldapsearch -H ldap://localhost:3890 -D uid=admin,ou=people,dc=example,dc=com -b "ou=people,dc=example,dc=com" -w password')
)
'';
adminPassword="${adminPassword}"
def try_login(user, password, expect_success=True):
cmd = f'ldapsearch -H ldap://localhost:3890 -D uid={user},ou=people,dc=example,dc=com -b "ou=people,dc=example,dc=com" -w {password}'
code, response = machine.execute(cmd)
print(cmd)
print(response)
if expect_success:
if code != 0:
raise Exception(f"Expected success, had failure {code}")
else:
if code == 0:
raise Exception("Expected failure, had success")
return response
with subtest("default admin password"):
try_login("admin", "password", expect_success=True)
try_login("admin", adminPassword, expect_success=False)
with subtest("different admin password"):
machine.succeed('${specializations}/differentAdminPassword/bin/switch-to-configuration test')
try_login("admin", "password", expect_success=False)
try_login("admin", adminPassword, expect_success=True)
with subtest("change admin password has no effect"):
machine.succeed('${specializations}/differentAdminPassword/bin/switch-to-configuration test')
try_login("admin", "password", expect_success=False)
try_login("admin", adminPassword, expect_success=True)
'';
}