699 lines
24 KiB
Diff
699 lines
24 KiB
Diff
From 75d2190cde1df122027496bbfcb9eda7d5aab102 Mon Sep 17 00:00:00 2001
|
|
From: ibizaman <ibizaman@tiserbox.com>
|
|
Date: Wed, 13 Aug 2025 08:14:38 +0200
|
|
Subject: [PATCH 1/2] lldap-bootstrap: init 0.6.2
|
|
|
|
---
|
|
pkgs/by-name/ll/lldap-bootstrap/package.nix | 57 +++++++++++++++++++++
|
|
1 file changed, 57 insertions(+)
|
|
create mode 100644 pkgs/by-name/ll/lldap-bootstrap/package.nix
|
|
|
|
diff --git a/pkgs/by-name/ll/lldap-bootstrap/package.nix b/pkgs/by-name/ll/lldap-bootstrap/package.nix
|
|
new file mode 100644
|
|
index 00000000000000..8b9a915b18f4d9
|
|
--- /dev/null
|
|
+++ b/pkgs/by-name/ll/lldap-bootstrap/package.nix
|
|
@@ -0,0 +1,57 @@
|
|
+{
|
|
+ curl,
|
|
+ fetchFromGitHub,
|
|
+ jq,
|
|
+ jo,
|
|
+ lib,
|
|
+ lldap,
|
|
+ lldap-bootstrap,
|
|
+ makeWrapper,
|
|
+ stdenv,
|
|
+}:
|
|
+let
|
|
+ version = "0.6.2";
|
|
+in
|
|
+stdenv.mkDerivation {
|
|
+ pname = "lldap-bootstrap";
|
|
+ inherit version;
|
|
+
|
|
+ src = fetchFromGitHub {
|
|
+ owner = "lldap";
|
|
+ repo = "lldap";
|
|
+ rev = "v${version}";
|
|
+ hash = "sha256-UBQWOrHika8X24tYdFfY8ETPh9zvI7/HV5j4aK8Uq+Y=";
|
|
+ };
|
|
+
|
|
+ dontBuild = true;
|
|
+
|
|
+ nativeBuildInputs = [ makeWrapper ];
|
|
+
|
|
+ installPhase = ''
|
|
+ mkdir -p $out/bin
|
|
+ cp ./scripts/bootstrap.sh $out/bin/lldap-bootstrap
|
|
+
|
|
+ wrapProgram $out/bin/lldap-bootstrap \
|
|
+ --set LLDAP_SET_PASSWORD_PATH ${lldap}/bin/lldap_set_password \
|
|
+ --prefix PATH : ${
|
|
+ lib.makeBinPath [
|
|
+ curl
|
|
+ jq
|
|
+ jo
|
|
+ ]
|
|
+ }
|
|
+ '';
|
|
+
|
|
+ meta = {
|
|
+ description = "Bootstrap script for LLDAP";
|
|
+ homepage = "https://github.com/lldap/lldap";
|
|
+ changelog = "https://github.com/lldap/lldap/blob/v${lldap-bootstrap.version}/CHANGELOG.md";
|
|
+ license = lib.licenses.gpl3Only;
|
|
+ platforms = lib.platforms.linux;
|
|
+ maintainers = with lib.maintainers; [
|
|
+ bendlas
|
|
+ ibizaman
|
|
+ ];
|
|
+ mainProgram = "lldap-bootstrap";
|
|
+ };
|
|
+}
|
|
|
|
From 6d55fbc42a2d71b6d7ad29c84c1c0774971f9f6a Mon Sep 17 00:00:00 2001
|
|
From: ibizaman <ibizaman@tiserbox.com>
|
|
Date: Wed, 13 Aug 2025 08:15:12 +0200
|
|
Subject: [PATCH 2/2] lldap: add ensure options
|
|
|
|
---
|
|
nixos/modules/services/databases/lldap.nix | 372 ++++++++++++++++++++-
|
|
nixos/tests/lldap.nix | 143 +++++++-
|
|
2 files changed, 498 insertions(+), 17 deletions(-)
|
|
|
|
diff --git a/nixos/modules/services/databases/lldap.nix b/nixos/modules/services/databases/lldap.nix
|
|
index 3738dba506a707..6b5d2be44f4373 100644
|
|
--- a/nixos/modules/services/databases/lldap.nix
|
|
+++ b/nixos/modules/services/databases/lldap.nix
|
|
@@ -8,6 +8,84 @@
|
|
let
|
|
cfg = config.services.lldap;
|
|
format = pkgs.formats.toml { };
|
|
+
|
|
+ inherit (lib) mkOption types;
|
|
+
|
|
+ ensureFormat = pkgs.formats.json { };
|
|
+ ensureGenerate =
|
|
+ let
|
|
+ filterNulls = lib.filterAttrsRecursive (n: v: v != null);
|
|
+
|
|
+ filteredSource =
|
|
+ source: if builtins.isList source then map filterNulls source else filterNulls source;
|
|
+ in
|
|
+ name: source: ensureFormat.generate name (filteredSource source);
|
|
+
|
|
+ ensureFieldsOptions = name: {
|
|
+ name = mkOption {
|
|
+ type = types.str;
|
|
+ description = "Name of the field.";
|
|
+ default = name;
|
|
+ };
|
|
+
|
|
+ attributeType = mkOption {
|
|
+ type = types.enum [
|
|
+ "STRING"
|
|
+ "INTEGER"
|
|
+ "JPEG"
|
|
+ "DATE_TIME"
|
|
+ ];
|
|
+ description = "Attribute type.";
|
|
+ };
|
|
+
|
|
+ isEditable = mkOption {
|
|
+ type = types.bool;
|
|
+ description = "Is field editable.";
|
|
+ default = true;
|
|
+ };
|
|
+
|
|
+ isList = mkOption {
|
|
+ type = types.bool;
|
|
+ description = "Is field a list.";
|
|
+ default = false;
|
|
+ };
|
|
+
|
|
+ isVisible = mkOption {
|
|
+ type = types.bool;
|
|
+ description = "Is field visible in UI.";
|
|
+ default = true;
|
|
+ };
|
|
+ };
|
|
+
|
|
+ allUserGroups = lib.flatten (lib.mapAttrsToList (n: u: u.groups) cfg.ensureUsers);
|
|
+ # The three hardcoded groups are always created when the service starts.
|
|
+ allGroups = lib.mapAttrsToList (n: g: g.name) cfg.ensureGroups ++ [
|
|
+ "lldap_admin"
|
|
+ "lldap_password_manager"
|
|
+ "lldap_strict_readonly"
|
|
+ ];
|
|
+ userGroupNotInEnsuredGroup = lib.sortOn lib.id (
|
|
+ lib.unique (lib.subtractLists allGroups allUserGroups)
|
|
+ );
|
|
+ someUsersBelongToNonEnsuredGroup = (lib.lists.length userGroupNotInEnsuredGroup) > 0;
|
|
+
|
|
+ generateEnsureConfigDir =
|
|
+ name: source:
|
|
+ let
|
|
+ genOne =
|
|
+ name: sourceOne:
|
|
+ pkgs.writeTextDir "configs/${name}.json" (
|
|
+ builtins.readFile (ensureGenerate "configs/${name}.json" sourceOne)
|
|
+ );
|
|
+ in
|
|
+ "${
|
|
+ pkgs.symlinkJoin {
|
|
+ inherit name;
|
|
+ paths = lib.mapAttrsToList genOne source;
|
|
+ }
|
|
+ }/configs";
|
|
+
|
|
+ quoteVariable = x: "\"${x}\"";
|
|
in
|
|
{
|
|
options.services.lldap = with lib; {
|
|
@@ -15,6 +93,8 @@ in
|
|
|
|
package = mkPackageOption pkgs "lldap" { };
|
|
|
|
+ bootstrap-package = mkPackageOption pkgs "lldap-bootstrap" { };
|
|
+
|
|
environment = mkOption {
|
|
type = with types; attrsOf str;
|
|
default = { };
|
|
@@ -169,6 +249,198 @@ in
|
|
If that is okay for you and you want to silence the warning, set this option to `true`.
|
|
'';
|
|
};
|
|
+
|
|
+ ensureUsers = mkOption {
|
|
+ description = ''
|
|
+ Create the users defined here on service startup.
|
|
+
|
|
+ If `enforceEnsure` 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.
|
|
+ '';
|
|
+ default = { };
|
|
+ type = types.attrsOf (
|
|
+ types.submodule (
|
|
+ { name, ... }:
|
|
+ {
|
|
+ freeformType = ensureFormat.type;
|
|
+
|
|
+ options = {
|
|
+ id = mkOption {
|
|
+ type = types.str;
|
|
+ description = "Username.";
|
|
+ default = name;
|
|
+ };
|
|
+
|
|
+ email = mkOption {
|
|
+ type = types.str;
|
|
+ description = "Email.";
|
|
+ };
|
|
+
|
|
+ password_file = mkOption {
|
|
+ type = types.str;
|
|
+ description = "File containing the password.";
|
|
+ };
|
|
+
|
|
+ displayName = mkOption {
|
|
+ type = types.nullOr types.str;
|
|
+ default = null;
|
|
+ description = "Display name.";
|
|
+ };
|
|
+
|
|
+ firstName = mkOption {
|
|
+ type = types.nullOr types.str;
|
|
+ default = null;
|
|
+ description = "First name.";
|
|
+ };
|
|
+
|
|
+ lastName = mkOption {
|
|
+ type = types.nullOr types.str;
|
|
+ default = null;
|
|
+ description = "Last name.";
|
|
+ };
|
|
+
|
|
+ avatar_file = mkOption {
|
|
+ type = types.nullOr types.str;
|
|
+ default = null;
|
|
+ description = "Avatar file. Must be a valid path to jpeg file (ignored if avatar_url specified)";
|
|
+ };
|
|
+
|
|
+ avatar_url = mkOption {
|
|
+ type = types.nullOr types.str;
|
|
+ default = null;
|
|
+ description = "Avatar url. must be a valid URL to jpeg file (ignored if gravatar_avatar specified)";
|
|
+ };
|
|
+
|
|
+ gravatar_avatar = mkOption {
|
|
+ type = types.nullOr types.str;
|
|
+ default = null;
|
|
+ description = "Get avatar from Gravatar using the email.";
|
|
+ };
|
|
+
|
|
+ weser_avatar = mkOption {
|
|
+ type = types.nullOr types.str;
|
|
+ default = null;
|
|
+ description = "Convert avatar retrieved by gravatar or the URL.";
|
|
+ };
|
|
+
|
|
+ groups = mkOption {
|
|
+ type = types.listOf types.str;
|
|
+ default = [ ];
|
|
+ description = "Groups the user would be a member of (all the groups must be specified in group config files).";
|
|
+ };
|
|
+ };
|
|
+ }
|
|
+ )
|
|
+ );
|
|
+ };
|
|
+
|
|
+ ensureGroups = mkOption {
|
|
+ description = ''
|
|
+ Create the groups defined here on service startup.
|
|
+
|
|
+ Non-default options must be added to the `ensureGroupFields` option.
|
|
+ '';
|
|
+ default = { };
|
|
+ type = types.attrsOf (
|
|
+ types.submodule (
|
|
+ { name, ... }:
|
|
+ {
|
|
+ freeformType = ensureFormat.type;
|
|
+
|
|
+ options = {
|
|
+ name = mkOption {
|
|
+ type = types.str;
|
|
+ description = "Name of the group.";
|
|
+ default = name;
|
|
+ };
|
|
+ };
|
|
+ }
|
|
+ )
|
|
+ );
|
|
+ };
|
|
+
|
|
+ ensureUserFields = mkOption {
|
|
+ description = "Extra fields for users";
|
|
+ default = { };
|
|
+ type = types.attrsOf (
|
|
+ types.submodule (
|
|
+ { name, ... }:
|
|
+ {
|
|
+ options = ensureFieldsOptions name;
|
|
+ }
|
|
+ )
|
|
+ );
|
|
+ };
|
|
+
|
|
+ ensureGroupFields = mkOption {
|
|
+ description = "Extra fields for groups";
|
|
+ default = { };
|
|
+ type = types.attrsOf (
|
|
+ types.submodule (
|
|
+ { name, ... }:
|
|
+ {
|
|
+ options = ensureFieldsOptions name;
|
|
+ }
|
|
+ )
|
|
+ );
|
|
+ };
|
|
+
|
|
+ ensureAdminUsername = mkOption {
|
|
+ type = types.str;
|
|
+ default = "admin";
|
|
+ description = ''
|
|
+ Username of the default admin user with which to connect to the LLDAP service.
|
|
+
|
|
+ By default, it is `"admin"`.
|
|
+ Extra admin users can be added using the `services.lldap.ensureUsers` option and adding them to the correct groups.
|
|
+ '';
|
|
+ };
|
|
+
|
|
+ ensureAdminPassword = mkOption {
|
|
+ type = types.nullOr types.str;
|
|
+ defaultText = "config.services.lldap.settings.ldap_user_pass";
|
|
+ default = cfg.settings.ldap_user_pass or null;
|
|
+ description = ''
|
|
+ Password of an admin user with which to connect to the LLDAP service.
|
|
+
|
|
+ By default, it is the same as the password for the default admin user 'admin'.
|
|
+ If using a password from another user, it must be managed manually.
|
|
+
|
|
+ Unsecure. Use `services.lldap.ensureAdminPasswordFile` option instead.
|
|
+ '';
|
|
+ };
|
|
+
|
|
+ ensureAdminPasswordFile = mkOption {
|
|
+ type = types.nullOr types.str;
|
|
+ defaultText = "config.services.lldap.settings.ldap_user_pass_file";
|
|
+ default = cfg.settings.ldap_user_pass_file or null;
|
|
+ description = ''
|
|
+ Path to the file containing the password of an admin user with which to connect to the LLDAP service.
|
|
+
|
|
+ By default, it is the same as the password for the default admin user 'admin'.
|
|
+ If using a password from another user, it must be managed manually.
|
|
+ '';
|
|
+ };
|
|
+
|
|
+ enforceUsers = mkOption {
|
|
+ description = "Delete users not managed declaratively.";
|
|
+ type = types.bool;
|
|
+ default = false;
|
|
+ };
|
|
+
|
|
+ enforceUserMemberships = mkOption {
|
|
+ description = "Remove users from groups they do not belong to declaratively.";
|
|
+ type = types.bool;
|
|
+ default = false;
|
|
+ };
|
|
+
|
|
+ enforceGroups = mkOption {
|
|
+ description = "Delete groups not managed declaratively.";
|
|
+ type = types.bool;
|
|
+ default = false;
|
|
+ };
|
|
};
|
|
|
|
config = lib.mkIf cfg.enable {
|
|
@@ -183,25 +455,77 @@ in
|
|
(cfg.settings.ldap_user_pass_file or null) == null || (cfg.settings.ldap_user_pass or null) == null;
|
|
message = "lldap: Both `ldap_user_pass` and `ldap_user_pass_file` settings should not be set at the same time. Set one to `null`.";
|
|
}
|
|
+ {
|
|
+ assertion =
|
|
+ cfg.ensureUsers != { }
|
|
+ || cfg.ensureGroups != { }
|
|
+ || cfg.ensureUserFields != { }
|
|
+ || cfg.ensureGroupFields != { }
|
|
+ || cfg.enforceUsers
|
|
+ || cfg.enforceUserMemberships
|
|
+ || cfg.enforceGroups
|
|
+ -> cfg.ensureAdminPassword != null || cfg.ensureAdminPasswordFile != null;
|
|
+ message = ''
|
|
+ lldap: Some ensure options are set but no admin user password is set.
|
|
+ Add a default password to the `ldap_user_pass` or `ldap_user_pass_file` setting and set `force_ldap_user_pass_reset` to `true` to manage the admin user declaratively
|
|
+ or create an admin user manually and set its password in `ensureAdminPasswordFile` option.
|
|
+ '';
|
|
+ }
|
|
+ {
|
|
+ assertion = cfg.enforceUserMemberships -> !someUsersBelongToNonEnsuredGroup;
|
|
+ message = ''
|
|
+ lldap: Some users belong to groups not present in the ensureGroups attr,
|
|
+ add the following groups or remove them from the groups a user belong to:
|
|
+ ${lib.concatMapStringsSep quoteVariable ", " userGroupNotInEnsuredGroup}
|
|
+ '';
|
|
+ }
|
|
+ (
|
|
+ let
|
|
+ getNames = source: lib.flatten (lib.mapAttrsToList (x: v: v.name) source);
|
|
+ allNames = getNames cfg.ensureUserFields ++ getNames cfg.ensureGroupFields;
|
|
+ validFieldName = name: lib.match "[a-zA-Z0-9-]+" name != null;
|
|
+ in
|
|
+ {
|
|
+ assertion = lib.all validFieldName allNames;
|
|
+ message = ''
|
|
+ lldap: The following custom user or group fields have invalid names. Valid characters are: a-z, A-Z, 0-9, and dash (-).
|
|
+ The offending fields are: ${
|
|
+ lib.concatMapStringsSep quoteVariable ", " (lib.filter (x: !(validFieldName x)) allNames)
|
|
+ }
|
|
+ '';
|
|
+ }
|
|
+ )
|
|
];
|
|
|
|
warnings =
|
|
- lib.optionals (cfg.settings.ldap_user_pass or null != null) [
|
|
+ (lib.optionals (cfg.ensureAdminPassword != null) [
|
|
+ ''
|
|
+ lldap: Unsecure option `ensureAdminPassword` is used. Prefer `ensureAdminPasswordFile` instead.
|
|
+ ''
|
|
+ ])
|
|
+ ++ (lib.optionals (cfg.settings.ldap_user_pass or null != null) [
|
|
''
|
|
lldap: Unsecure `ldap_user_pass` setting is used. Prefer `ldap_user_pass_file` instead.
|
|
''
|
|
- ]
|
|
- ++
|
|
- lib.optionals
|
|
- (cfg.settings.force_ldap_user_pass_reset == false && cfg.silenceForceUserPassResetWarning == false)
|
|
- [
|
|
- ''
|
|
- lldap: The `force_ldap_user_pass_reset` setting is set to `false` which 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` or `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`.
|
|
- ''
|
|
- ];
|
|
+ ])
|
|
+ ++ (lib.optionals
|
|
+ (cfg.settings.force_ldap_user_pass_reset == false && cfg.silenceForceUserPassResetWarning == false)
|
|
+ [
|
|
+ ''
|
|
+ lldap: The `force_ldap_user_pass_reset` setting is set to `false` which 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` or `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`.
|
|
+ ''
|
|
+ ]
|
|
+ )
|
|
+ ++ (lib.optionals (!cfg.enforceUserMemberships && someUsersBelongToNonEnsuredGroup) [
|
|
+ ''
|
|
+ Some users belong to groups not managed by the configuration here,
|
|
+ make sure the following groups exist or the service will not start properly:
|
|
+ ${lib.concatStringsSep ", " (map (x: "\"${x}\"") userGroupNotInEnsuredGroup)}
|
|
+ ''
|
|
+ ]);
|
|
|
|
systemd.services.lldap = {
|
|
description = "Lightweight LDAP server (lldap)";
|
|
@@ -224,6 +548,28 @@ in
|
|
+ ''
|
|
${lib.getExe cfg.package} run --config-file ${format.generate "lldap_config.toml" cfg.settings}
|
|
'';
|
|
+ postStart = ''
|
|
+ export LLDAP_URL=http://127.0.0.1:${toString cfg.settings.http_port}
|
|
+ export LLDAP_ADMIN_USERNAME=${cfg.ensureAdminUsername}
|
|
+ export LLDAP_ADMIN_PASSWORD=${
|
|
+ if cfg.ensureAdminPassword != null then cfg.ensureAdminPassword else ""
|
|
+ }
|
|
+ export LLDAP_ADMIN_PASSWORD_FILE=${
|
|
+ if cfg.ensureAdminPasswordFile != null then cfg.ensureAdminPasswordFile else ""
|
|
+ }
|
|
+ export USER_CONFIGS_DIR=${lib.traceVal (generateEnsureConfigDir "users" cfg.ensureUsers)}
|
|
+ export GROUP_CONFIGS_DIR=${generateEnsureConfigDir "groups" cfg.ensureGroups}
|
|
+ export USER_SCHEMAS_DIR=${
|
|
+ generateEnsureConfigDir "userFields" (lib.mapAttrs (n: v: [ v ]) cfg.ensureUserFields)
|
|
+ }
|
|
+ export GROUP_SCHEMAS_DIR=${
|
|
+ generateEnsureConfigDir "groupFields" (lib.mapAttrs (n: v: [ v ]) cfg.ensureGroupFields)
|
|
+ }
|
|
+ export DO_CLEANUP_USERS=${if cfg.enforceUsers then "true" else "false"}
|
|
+ export DO_CLEANUP_USER_MEMBERSHIPS=${if cfg.enforceUserMemberships then "true" else "false"}
|
|
+ export DO_CLEANUP_GROUPS=${if cfg.enforceGroups then "true" else "false"}
|
|
+ ${lib.getExe cfg.bootstrap-package}
|
|
+ '';
|
|
serviceConfig = {
|
|
StateDirectory = "lldap";
|
|
StateDirectoryMode = "0750";
|
|
diff --git a/nixos/tests/lldap.nix b/nixos/tests/lldap.nix
|
|
index 8e38d4bdefa31d..47d32c7a2a7bbc 100644
|
|
--- a/nixos/tests/lldap.nix
|
|
+++ b/nixos/tests/lldap.nix
|
|
@@ -1,6 +1,9 @@
|
|
{ ... }:
|
|
let
|
|
adminPassword = "mySecretPassword";
|
|
+ alicePassword = "AlicePassword";
|
|
+ bobPassword = "BobPassword";
|
|
+ charliePassword = "CharliePassword";
|
|
in
|
|
{
|
|
name = "lldap";
|
|
@@ -26,7 +29,7 @@ in
|
|
{
|
|
services.lldap.settings = {
|
|
ldap_user_pass = lib.mkForce null;
|
|
- ldap_user_pass_file = lib.mkForce (toString (pkgs.writeText "adminPasswordFile" adminPassword));
|
|
+ ldap_user_pass_file = toString (pkgs.writeText "adminPasswordFile" adminPassword);
|
|
force_ldap_user_pass_reset = "always";
|
|
};
|
|
};
|
|
@@ -40,13 +43,110 @@ in
|
|
force_ldap_user_pass_reset = false;
|
|
};
|
|
};
|
|
+
|
|
+ withAlice.configuration =
|
|
+ { ... }:
|
|
+ {
|
|
+ services.lldap = {
|
|
+ enforceUsers = true;
|
|
+ enforceUserMemberships = true;
|
|
+ enforceGroups = true;
|
|
+
|
|
+ # This password was set in the "differentAdminPassword" specialisation.
|
|
+ ensureAdminPasswordFile = toString (pkgs.writeText "adminPasswordFile" adminPassword);
|
|
+
|
|
+ ensureUsers = {
|
|
+ alice = {
|
|
+ email = "alice@example.com";
|
|
+ password_file = toString (pkgs.writeText "alicePasswordFile" alicePassword);
|
|
+ groups = [ "mygroup" ];
|
|
+ };
|
|
+ };
|
|
+
|
|
+ ensureGroups = {
|
|
+ mygroup = { };
|
|
+ };
|
|
+ };
|
|
+ };
|
|
+
|
|
+ withBob.configuration =
|
|
+ { ... }:
|
|
+ {
|
|
+ services.lldap = {
|
|
+ enforceUsers = true;
|
|
+ enforceUserMemberships = true;
|
|
+ enforceGroups = true;
|
|
+
|
|
+ # This time we check that ensureAdminPasswordFile correctly defaults to `settings.ldap_user_pass_file`
|
|
+ settings = {
|
|
+ ldap_user_pass = lib.mkForce "password";
|
|
+ force_ldap_user_pass_reset = "always";
|
|
+ };
|
|
+
|
|
+ ensureUsers = {
|
|
+ bob = {
|
|
+ email = "bob@example.com";
|
|
+ password_file = toString (pkgs.writeText "bobPasswordFile" bobPassword);
|
|
+ groups = [ "bobgroup" ];
|
|
+ displayName = "Bob";
|
|
+ };
|
|
+ };
|
|
+
|
|
+ ensureGroups = {
|
|
+ bobgroup = { };
|
|
+ };
|
|
+ };
|
|
+ };
|
|
+
|
|
+ withAttributes.configuration =
|
|
+ { ... }:
|
|
+ {
|
|
+ services.lldap = {
|
|
+ enforceUsers = true;
|
|
+ enforceUserMemberships = true;
|
|
+ enforceGroups = true;
|
|
+
|
|
+ settings = {
|
|
+ ldap_user_pass = lib.mkForce adminPassword;
|
|
+ force_ldap_user_pass_reset = "always";
|
|
+ };
|
|
+
|
|
+ ensureUsers = {
|
|
+ charlie = {
|
|
+ email = "charlie@example.com";
|
|
+ password_file = toString (pkgs.writeText "charliePasswordFile" charliePassword);
|
|
+ groups = [ "othergroup" ];
|
|
+ displayName = "Charlie";
|
|
+ myattribute = 2;
|
|
+ };
|
|
+ };
|
|
+
|
|
+ ensureGroups = {
|
|
+ othergroup = {
|
|
+ mygroupattribute = "Managed by NixOS";
|
|
+ };
|
|
+ };
|
|
+
|
|
+ ensureUserFields = {
|
|
+ myattribute = {
|
|
+ attributeType = "INTEGER";
|
|
+ };
|
|
+ };
|
|
+
|
|
+ ensureGroupFields = {
|
|
+ mygroupattribute = {
|
|
+ attributeType = "STRING";
|
|
+ };
|
|
+ };
|
|
+ };
|
|
+ };
|
|
};
|
|
};
|
|
|
|
testScript =
|
|
{ nodes, ... }:
|
|
let
|
|
- specializations = "${nodes.machine.system.build.toplevel}/specialisation";
|
|
+ specialisations = "${nodes.machine.system.build.toplevel}/specialisation";
|
|
in
|
|
''
|
|
machine.wait_for_unit("lldap.service")
|
|
@@ -56,6 +156,9 @@ in
|
|
machine.succeed("curl --location --fail http://localhost:17170/")
|
|
|
|
adminPassword="${adminPassword}"
|
|
+ alicePassword="${alicePassword}"
|
|
+ bobPassword="${bobPassword}"
|
|
+ charliePassword="${charliePassword}"
|
|
|
|
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}'
|
|
@@ -70,18 +173,50 @@ in
|
|
raise Exception("Expected failure, had success")
|
|
return response
|
|
|
|
+ def parse_ldapsearch_output(output):
|
|
+ return {n:v for (n, v) in (x.split(': ', 2) for x in output.splitlines() if x != "")}
|
|
+
|
|
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')
|
|
+ machine.succeed('${specialisations}/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')
|
|
+ machine.succeed('${specialisations}/differentAdminPassword/bin/switch-to-configuration test')
|
|
try_login("admin", "password", expect_success=False)
|
|
try_login("admin", adminPassword, expect_success=True)
|
|
+
|
|
+ with subtest("with alice"):
|
|
+ machine.succeed('${specialisations}/withAlice/bin/switch-to-configuration test')
|
|
+ try_login("alice", "password", expect_success=False)
|
|
+ try_login("alice", alicePassword, expect_success=True)
|
|
+ try_login("bob", "password", expect_success=False)
|
|
+ try_login("bob", bobPassword, expect_success=False)
|
|
+
|
|
+ with subtest("with bob"):
|
|
+ machine.succeed('${specialisations}/withBob/bin/switch-to-configuration test')
|
|
+ try_login("alice", "password", expect_success=False)
|
|
+ try_login("alice", alicePassword, expect_success=False)
|
|
+ try_login("bob", "password", expect_success=False)
|
|
+ try_login("bob", bobPassword, expect_success=True)
|
|
+
|
|
+ with subtest("with attributes"):
|
|
+ machine.succeed('${specialisations}/withAttributes/bin/switch-to-configuration test')
|
|
+
|
|
+ response = machine.succeed(f'ldapsearch -LLL -H ldap://localhost:3890 -D uid=admin,ou=people,dc=example,dc=com -b "dc=example,dc=com" -w {adminPassword} "(uid=charlie)"')
|
|
+ print(response)
|
|
+ charlie = parse_ldapsearch_output(response)
|
|
+ if charlie.get('myattribute') != "2":
|
|
+ raise Exception(f'Unexpected value for attribute "myattribute": {charlie.get('myattribute')}')
|
|
+
|
|
+ response = machine.succeed(f'ldapsearch -LLL -H ldap://localhost:3890 -D uid=admin,ou=people,dc=example,dc=com -b "dc=example,dc=com" -w {adminPassword} "(cn=othergroup)"')
|
|
+ print(response)
|
|
+ othergroup = parse_ldapsearch_output(response)
|
|
+ if othergroup.get('mygroupattribute') != "Managed by NixOS":
|
|
+ raise Exception(f'Unexpected value for attribute "mygroupattribute": {othergroup.get('mygroupattribute')}')
|
|
'';
|
|
}
|