From 76a07d5edfc2718b46135360c386425a06230e34 Mon Sep 17 00:00:00 2001 From: ibizaman Date: Fri, 17 May 2024 15:11:25 -0700 Subject: [PATCH] wip lldap declarative --- flake.nix | 4 + modules/blocks/ldap.nix | 170 +++++++++++++++++++++++++++++++++++++++- test/vm/ldap.nix | 42 ++++++++++ 3 files changed, 215 insertions(+), 1 deletion(-) diff --git a/flake.nix b/flake.nix index 7e51ba8..506e847 100644 --- a/flake.nix +++ b/flake.nix @@ -24,6 +24,10 @@ url = "https://github.com/NixOS/nixpkgs/pull/317107.patch"; hash = "sha256-6SfqnPLPxJHckXNU03HA0X03u9Ynn3baQ2HHMq5FkIc="; }) + (originPkgs.fetchpatch { + url = "https://patch-diff.githubusercontent.com/raw/NixOS/nixpkgs/pull/304721.patch"; + sha256 = "sha256-AOTtqbsDkykJKeE/e0Y2IscSHZ56vLEbl7lzxd+QfKo="; + }) ]; patchedNixpkgs = originPkgs.applyPatches { name = "nixpkgs-patched"; diff --git a/modules/blocks/ldap.nix b/modules/blocks/ldap.nix index d9ee75b..fc9a745 100644 --- a/modules/blocks/ldap.nix +++ b/modules/blocks/ldap.nix @@ -6,6 +6,24 @@ let contracts = pkgs.callPackage ../contracts {}; fqdn = "${cfg.subdomain}.${cfg.domain}"; + + lldap-cli-auth = pkgs.callPackage ({ stdenvNoCC, makeWrapper, lldap-cli }: stdenvNoCC.mkDerivation { + name = "lldap-cli"; + + src = lldap-cli; + + nativeBuildInputs = [ + makeWrapper + ]; + + # No quotes around the value for LLDAP_PASSWORD because we want the value to not be enclosed in quotes. + installPhase = '' + makeWrapper ${pkgs.lldap-cli}/bin/lldap-cli $out/bin/lldap-cli \ + --set LLDAP_USERNAME "admin" \ + --set LLDAP_PASSWORD $(cat ${cfg.ldapUserPasswordFile}) \ + --set LLDAP_HTTPURL "http://${config.services.lldap.settings.http_host}:${toString config.services.lldap.settings.http_port}" + ''; + }) {}; in { options.shb.ldap = { @@ -69,8 +87,59 @@ in type = lib.types.bool; default = false; }; - }; + groups = lib.mkOption { + description = "LDAP Groups to manage declaratively."; + default = {}; + example = lib.literalExpression '' + { + family = {}; + } + ''; + type = lib.types.attrsOf (lib.types.submodule { + options = {}; + }); + }; + + users = lib.mkOption { + description = "LDAP Users to manage declaratively."; + default = {}; + type = lib.types.attrsOf (lib.types.submodule { + options = { + email = lib.mkOption { + description = "Email address."; + type = lib.types.str; + }; + + displayName = lib.mkOption { + description = "Display name."; + type = lib.types.str; + }; + + firstName = lib.mkOption { + description = "First name."; + type = lib.types.str; + }; + + lastName = lib.mkOption { + description = "Last name."; + type = lib.types.str; + }; + + groups = lib.mkOption { + description = "Groups this user is member of. The group must exist."; + type = lib.types.listOf lib.types.str; + default = []; + }; + + passwordFile = lib.mkOption { + type = lib.types.path; + description = "File containing the user's password."; + }; + }; + }); + }; + }; config = lib.mkIf cfg.enable { services.nginx = { @@ -126,6 +195,105 @@ in }; }; + environment.systemPackages = [ + lldap-cli-auth + ]; + + # $ lldap-cli schema attribute user list + # + # Name Type Is list Is visible Is editable + # ---- ---- ------- ---------- ----------- + # avatar JpegPhoto false true true + # creation_date DateTime false true false + # display_name String false true true + # first_name String false true true + # last_name String false true true + # mail String false true true + # user_id String false true false + # uuid String false true false + + + # $ lldap-cli schema attribute group list + # + # Name Type Is list Is visible Is editable + # ---- ---- ------- ---------- ----------- + # creation_date DateTime false true false + # display_name String false true true + # group_id Integer false true false + # uuid String false true false + + systemd.services.lldap.postStart = + let + configFile = (pkgs.formats.toml {}).generate "lldap_config.toml" config.services.lldap.settings; + + login = ['' + set -euo pipefail + + sleep 3 + + export LLDAP_USERNAME=admin + export LLDAP_PASSWORD=$(cat ${cfg.ldapUserPasswordFile}) + export LLDAP_HTTPURL=http://${config.services.lldap.settings.http_host}:${toString config.services.lldap.settings.http_port} + + eval $(${pkgs.lldap-cli}/bin/lldap-cli login) + + set -x + '']; + + deleteGroups = ['' + allUids=(${lib.concatStringsSep " " ( + (lib.mapAttrsToList (uid: g: uid) cfg.groups) + ++ [ "lldap_admin" "lldap_password_manager" "lldap_strict_readonly" ]) + }) + echo All managed groups are: $allUids + echo Other groups will be deleted + for uid in $(${pkgs.lldap-cli}/bin/lldap-cli group list); do + if [[ ! " ''${allUids[*]} " =~ [[:space:]]''${uid}[[:space:]] ]]; then + ${pkgs.lldap-cli}/bin/lldap-cli group del $uid + fi + done + '']; + + createGroups = lib.mapAttrsToList (uid: g: '' + ${pkgs.lldap-cli}/bin/lldap-cli group add ${uid} + '') cfg.groups; + + deleteUsers = ['' + allUids=(${lib.concatStringsSep " " ( + (lib.mapAttrsToList (uid: u: uid) cfg.users) + ++ [ "admin" ]) + }) + for uid in $(${pkgs.lldap-cli}/bin/lldap-cli user list uid); do + if [[ ! " ''${allUids[*]} " =~ [[:space:]]''${uid}[[:space:]] ]]; then + ${pkgs.lldap-cli}/bin/lldap-cli user del $uid + fi + done + '']; + + createUsers = lib.mapAttrsToList (uid: u: '' + ${pkgs.lldap-cli}/bin/lldap-cli user add ${uid} "${u.email}" + ${pkgs.lldap-cli}/bin/lldap-cli user update set ${uid} password "$(cat ${u.passwordFile})" + ${pkgs.lldap-cli}/bin/lldap-cli user update set ${uid} mail "${u.email}" + ${pkgs.lldap-cli}/bin/lldap-cli user update set ${uid} display_name "${u.displayName}" + # ${pkgs.lldap-cli}/bin/lldap-cli user update set ${uid} first_name "${u.firstName}" + # ${pkgs.lldap-cli}/bin/lldap-cli user update set ${uid} last_name "${u.lastName}" + '') cfg.users; + + addToGroups = lib.mapAttrsToList (uid: u: lib.concatMapStringsSep "\n" (g: '' + ${pkgs.lldap-cli}/bin/lldap-cli user group add \ + ${uid} \ + ${g} + '') u.groups) cfg.users; + in + lib.concatStringsSep "\n\n" ( + login + ++ deleteGroups + ++ createGroups + ++ deleteUsers + ++ createUsers + ++ addToGroups + ); + shb.backup.instances.lldap = { sourceDirectories = [ "/var/lib/lldap" diff --git a/test/vm/ldap.nix b/test/vm/ldap.nix index a6724ca..721f47e 100644 --- a/test/vm/ldap.nix +++ b/test/vm/ldap.nix @@ -27,6 +27,20 @@ in ldapUserPasswordFile = pkgs.writeText "user_password" "securepw"; jwtSecretFile = pkgs.writeText "jwt_secret" "securejwtsecret"; debug = true; + + # groups = { + # "test-group" = {}; + # }; + # users = { + # "42" = { + # email = "my@test.com"; + # displayName = "My Test"; + # firstName = "My"; + # lastName = "My"; + # groups = [ "test-group" ]; + # passwordFile = pkgs.writeText "userpw" "userpw78"; + # }; + # }; }; networking.firewall.allowedTCPPorts = [ 80 ]; # nginx port }; @@ -78,6 +92,34 @@ in assert data['user']['displayName'] == "Administrator" assert data['user']['groups'][0]['displayName'] == "lldap_admin" + + # with subtest("check user exists"): + # ids = server.succeed("lldap-cli user list uid").splitlines() + # print(ids) + # if "42" not in ids: + # raise Exception("Did not find user for email my@test.com") + + # groups = server.succeed("lldap-cli user group list 42").splitlines() + # print(groups) + # if "test-group" not in groups: + # raise Exception("Did not find group for email my@test.com") + + # with subtest("service updates attributes"): + # server.succeed("lldap-cli user update set 42 mail other@test.com") + + # emails = server.succeed("lldap-cli user list email").splitlines() + # print("after update", emails) + # if "my@test.com" in emails: + # raise Exception("Did not find user for email other@test.com") + + # server.succeed("systemctl restart lldap") + + # emails = server.succeed("lldap-cli user list email").splitlines() + # print("after restart", emails) + # if "my@test.com" not in emails: + # raise Exception("Did not find user for email my@test.com") + + # print(server.succeed("systemctl cat lldap | grep Post")) ''; }; }