selfhostblocks/patches/0001-lldap-add-options-to-set-important-secrets.patch
2025-08-19 00:55:07 +02:00

197 lines
7.4 KiB
Diff

From 3b7d45e30f3dbf329abfb6ba659d10357761e2ab Mon Sep 17 00:00:00 2001
From: ibizaman <ibizaman@tiserbox.com>
Date: Wed, 16 Jul 2025 03:04:44 +0200
Subject: [PATCH 1/4] lldap: add options to set important secrets
---
nixos/modules/services/databases/lldap.nix | 71 +++++++++++++++++++++
nixos/tests/lldap.nix | 72 +++++++++++++++++++---
2 files changed, 134 insertions(+), 9 deletions(-)
diff --git a/nixos/modules/services/databases/lldap.nix b/nixos/modules/services/databases/lldap.nix
index a9fbe8f7e11a..1607754d7d9c 100644
--- a/nixos/modules/services/databases/lldap.nix
+++ b/nixos/modules/services/databases/lldap.nix
@@ -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" ];
diff --git a/nixos/tests/lldap.nix b/nixos/tests/lldap.nix
index c2e48525a5f3..aea6b2058727 100644
--- a/nixos/tests/lldap.nix
+++ b/nixos/tests/lldap.nix
@@ -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/")
+
+ 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)
- machine.succeed("curl --location --fail http://localhost:17170/")
+ 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)
- 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')
- )
- '';
+ 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)
+ '';
}
--
2.49.0