feat(auth,roundcube): members of sp.admins group become admins

This commit is contained in:
Alexander Tomokhov
2024-12-27 07:49:31 +04:00
parent 69c69dfb46
commit c127145425
3 changed files with 133 additions and 105 deletions

View File

@@ -1,9 +1,8 @@
{ { config
config, , lib
lib, , options
options, , pkgs
pkgs, , ...
...
}: }:
let let
inherit (lib) inherit (lib)
@@ -55,7 +54,8 @@ let
# paths, no new bind mount is added. Adding subpaths caused problems on ofborg. # paths, no new bind mount is added. Adding subpaths caused problems on ofborg.
hasPrefixInList = hasPrefixInList =
list: newPath: any (path: hasPrefix (builtins.toString path) (builtins.toString newPath)) list; list: newPath: any (path: hasPrefix (builtins.toString path) (builtins.toString newPath)) list;
mergePaths = foldl' ( mergePaths = foldl'
(
merged: newPath: merged: newPath:
let let
# If the new path is a prefix to some existing path, we need to filter it out # If the new path is a prefix to some existing path, we need to filter it out
@@ -126,13 +126,16 @@ let
filterPresent = filterAttrs (_: v: v.present); filterPresent = filterAttrs (_: v: v.present);
isGroupEmpty = g: ! g ? members || g ? members && g.members == [ ]; isGroupNonOverwritable = g: false
|| ! g ? members
|| g ? members && g.members == [ ]
|| g ? members && g.members == [ "sp.admins" ];
provisionStateJson = pkgs.writeText "provision-state.json" ( provisionStateJson = pkgs.writeText "provision-state.json" (
builtins.toJSON { builtins.toJSON {
inherit (cfg.provision) persons systems; inherit (cfg.provision) persons systems;
groups = groups =
lib.attrsets.filterAttrs (_n: v: ! isGroupEmpty v) cfg.provision.groups; lib.attrsets.filterAttrs (_n: v: ! isGroupNonOverwritable v) cfg.provision.groups;
} }
); );
@@ -175,21 +178,27 @@ let
fi fi
''; '';
emptyGroupsNames = groupsToCreateAndPopulate =
builtins.attrNames lib.attrsets.filterAttrs (_n: isGroupNonOverwritable) cfg.provision.groups;
(lib.attrsets.filterAttrs (_n: isGroupEmpty) cfg.provision.groups);
createEmptyGroups = '' createGroups = ''
readonly CLIENT_HOME=$RUNTIME_DIRECTORY/client_home for group_name in ${lib.strings.concatStringsSep " " (builtins.attrNames groupsToCreateAndPopulate)}
mkdir -p $CLIENT_HOME
HOME=$CLIENT_HOME KANIDM_PASSWORD="$KANIDM_IDM_ADMIN_PASSWORD" ${cfg.package}/bin/kanidm login --name idm_admin --url "${cfg.provision.instanceUrl}" --skip-hostname-verification
for group_name in ${lib.strings.concatLines emptyGroupsNames}
do do
HOME=$CLIENT_HOME ${cfg.package}/bin/kanidm group create "$group_name" --name idm_admin --url "${cfg.provision.instanceUrl}" --skip-hostname-verification ${cfg.package}/bin/kanidm group create "$group_name"
done done
# rm -r $CLIENT_HOME
''; '';
populateGroup = group_name: group: ''
for member_name in ${lib.strings.concatStringsSep " " group.members}
do
${cfg.package}/bin/kanidm group add-members "${group_name}" "$member_name"
done
'';
createAndPopulateGroups =
lib.concatLines ([ createGroups ]
++ (lib.mapAttrsToList populateGroup groupsToCreateAndPopulate));
postStartScript = pkgs.writeShellScript "post-start" '' postStartScript = pkgs.writeShellScript "post-start" ''
set -euo pipefail set -euo pipefail
@@ -209,7 +218,19 @@ let
${recoverIdmAdmin} ${recoverIdmAdmin}
${maybeRecoverAdmin} ${maybeRecoverAdmin}
${createEmptyGroups}
readonly CLIENT_HOME=$RUNTIME_DIRECTORY/client_home
mkdir -p "$CLIENT_HOME"
export HOME="$CLIENT_HOME"
export KANIDM_NAME=idm_admin
export KANIDM_URL="${cfg.provision.instanceUrl}"
export KANIDM_SKIP_HOSTNAME_VERIFICATION="true"
KANIDM_PASSWORD="$KANIDM_IDM_ADMIN_PASSWORD" ${cfg.package}/bin/kanidm login
${createAndPopulateGroups}
unset HOME
unset KANIDM_NAME
unset KANIDM_URL
unset KANIDM_SKIP_HOSTNAME_VERIFICATION
KANIDM_PROVISION_IDM_ADMIN_TOKEN=$KANIDM_IDM_ADMIN_PASSWORD \ KANIDM_PROVISION_IDM_ADMIN_TOKEN=$KANIDM_IDM_ADMIN_PASSWORD \
${getExe pkgs.kanidm-provision} \ ${getExe pkgs.kanidm-provision} \
@@ -451,9 +472,11 @@ in
config.members = concatLists ( config.members = concatLists (
flip mapAttrsToList cfg.provision.persons ( flip mapAttrsToList cfg.provision.persons (
person: personCfg: person: personCfg:
optional ( optional
(
personCfg.present && builtins.elem groupSubmod.config._module.args.name personCfg.groups personCfg.present && builtins.elem groupSubmod.config._module.args.name personCfg.groups
) person )
person
) )
); );
}) })
@@ -646,9 +669,12 @@ in
++ entityList "oauth2" cfg.provision.systems.oauth2; ++ entityList "oauth2" cfg.provision.systems.oauth2;
# Accumulate entities by name. Track corresponding entity types for later duplicate check. # Accumulate entities by name. Track corresponding entity types for later duplicate check.
entitiesByName = foldl' ( entitiesByName = foldl'
(
acc: { type, name }: acc // { ${name} = (acc.${name} or [ ]) ++ [ type ]; } acc: { type, name }: acc // { ${name} = (acc.${name} or [ ]) ++ [ type ]; }
) { } entities; )
{ }
entities;
assertGroupsKnown = assertGroupsKnown =
opt: groups: opt: groups:

View File

@@ -67,7 +67,8 @@ in
}; };
provision = { provision = {
enable = true; enable = true;
autoRemove = false; autoRemove = true; # if false, obsolete oauth2 scopeMaps remain
groups."sp.admins".present = true;
}; };
enableClient = true; enableClient = true;
clientSettings = { clientSettings = {

View File

@@ -71,33 +71,34 @@ in
systemd.slices.roundcube.description = "Roundcube service slice"; systemd.slices.roundcube.description = "Roundcube service slice";
services.kanidm.provision = lib.mkIf is-auth-enabled { services.kanidm.provision = lib.mkIf is-auth-enabled {
groups.roundcube_users.present = true; groups = {
"sp.roundcube.admins".members = [ "sp.admins" ];
"sp.roundcube.users".present = true;
};
systems.oauth2.roundcube = { systems.oauth2.roundcube = {
displayName = "Roundcube"; displayName = "Roundcube";
originUrl = "https://${cfg.subdomain}.${domain}/index.php/login/oauth"; originUrl = "https://${cfg.subdomain}.${domain}/index.php/login/oauth";
originLanding = "https://${cfg.subdomain}.${domain}/"; originLanding = "https://${cfg.subdomain}.${domain}/";
basicSecretFile = pkgs.writeText "bs-roundcube" "VERYSTRONGSECRETFORROUNDCUBE"; basicSecretFile = pkgs.writeText "bs-roundcube" "VERYSTRONGSECRETFORROUNDCUBE"; # FIXME
# when true, name is passed to a service instead of name@domain # when true, name is passed to a service instead of name@domain
preferShortUsername = false; preferShortUsername = false;
allowInsecureClientDisablePkce = true; # FIXME is it needed? allowInsecureClientDisablePkce = true; # FIXME is it needed?
scopeMaps.roundcube_users = [ scopeMaps = {
"sp.roundcube.users" = [
"email" "email"
"profile"
"openid" "openid"
"profile"
]; ];
# scopeMaps."sp.roundcube.users" = [ };
# "email" removeOrphanedClaimMaps = true;
# "openid"
# "dovecotprofile"
# ];
# add more scopes when a user is a member of specific group # add more scopes when a user is a member of specific group
# claimMaps.groups = { supplementaryScopeMaps."sp.roundcube.admins" = [ "admin" ];
# joinType = "array"; claimMaps.groups = {
# valuesByGroup = { joinType = "array";
# "sp.roundcube.admins" = [ "admin" ]; valuesByGroup = {
# }; "sp.roundcube.admins" = [ "admin" "test" ];
# }; };
};
}; };
}; };
}; };