chore(idp): sys accounts

This commit is contained in:
arcnmx 2024-04-02 13:52:06 -07:00
parent db2f7d27b3
commit 34d1b400e1
10 changed files with 561 additions and 81 deletions

View file

@ -1,11 +1,70 @@
{
config,
lib,
inputs,
...
}: let
inherit (lib.options) mkOption;
inherit (inputs.self.lib.lib) mkAlmostOptionDefault mapListToAttrs;
inherit (lib.options) mkOption mkEnableOption;
inherit (lib.modules) mkOptionDefault;
inherit (lib.attrsets) mapAttrsToList;
inherit (lib.lists) filter;
cfg = config.users.ldap;
ldap'lib = config.lib.ldap;
sysaccountModule = {config, nixosConfig, name, ldap, ...}: {
options = with lib.types; {
enable = mkEnableOption "sys account" // {
default = true;
};
uid = mkOption {
type = str;
default = name;
};
passwordFile = mkOption {
type = nullOr path;
default = null;
};
object = mkOption {
type = ldap.lib.objectSettingsType;
};
};
config = {
object = {
enable = mkAlmostOptionDefault config.enable;
dn = mkOptionDefault (ldap.lib.withBaseDn "uid=${config.uid},${ldap.sysAccountDnSuffix}");
settings = {
changeType = mkAlmostOptionDefault "add";
settings = {
uid = mkOptionDefault config.uid;
objectClass' = {
name = "objectClass";
initial = true;
value = [ "account" "simplesecurityobject" ];
};
userPassword = {
initial = true;
value = mkOptionDefault "initial123";
};
passwordExpirationTime = {
initial = true;
value = mkOptionDefault "20010101031407Z";
};
};
};
};
};
};
in {
options.users.ldap = with lib.types; {
management = {
sysAccounts = mkOption {
type = attrsOf (submoduleWith {
modules = [ sysaccountModule ];
inherit (config.lib.ldap) specialArgs;
});
default = { };
};
};
domainDnSuffix = mkOption {
type = str;
default = "";
@ -14,6 +73,10 @@ in {
type = str;
default = "";
};
hostGroupDnSuffix = mkOption {
type = str;
default = "";
};
serviceDnSuffix = mkOption {
type = str;
default = "";
@ -23,4 +86,10 @@ in {
default = "";
};
};
config.users.ldap = {
management.objects = let
sysAccountObjects = mapAttrsToList (_: acc: acc.object) cfg.management.sysAccounts;
enabledObjects = filter (object: object.enable) sysAccountObjects;
in mapListToAttrs ldap'lib.mapObjectSettingsToPair enabledObjects;
};
}

View file

@ -29,6 +29,12 @@ ldapwhoami() {
command ldapwhoami "${LDAP_ARGS[@]}"
}
ldappasswd() {
local LDAP_ARGS=("$@")
ldap_args_op
command ldappasswd "${LDAP_ARGS[@]}"
}
ldapsearch() {
local LDAP_ARGS=("$@")
ldap_args_op

View file

@ -23,6 +23,80 @@ ldap_parse() {
fi
}
sysaccount_password() {
local LDAP_SYSACCOUNT_UID=$1
local LDAP_SYSACCOUNT_PASSWORD_PATH=$2
shift 2
echo "updating uid=$LDAP_SYSACCOUNT_UID,$LDAP_DNSUFFIX_SYSACCOUNT ..." >&2
if ! ldappasswd -T "$LDAP_SYSACCOUNT_PASSWORD_PATH" "uid=$LDAP_SYSACCOUNT_UID,$LDAP_DNSUFFIX_SYSACCOUNT$LDAPBASE"; then
echo "failed to use ldappasswd, falling back to modify..." >&2
ldapmodify <<EOF
dn: uid=$LDAP_SYSACCOUNT_UID,$LDAP_DNSUFFIX_SYSACCOUNT$LDAPBASE
changetype: modify
replace: userPassword
userPassword:< file://$LDAP_SYSACCOUNT_PASSWORD_PATH
-
delete: passwordExpirationTime
-
EOF
fi
}
privilege_permissions() {
local LDAP_PRIVILEGE_CN=$1 LDAP_PRIVILEGE_PERMISSION_CN
shift 1
echo "updating cn=$LDAP_PRIVILEGE_CN,$LDAP_DNSUFFIX_PRIVILEGE ..." >&2
for LDAP_PRIVILEGE_PERMISSION_CN in "$@"; do
ipa privilege-add-permission "$LDAP_PRIVILEGE_CN" --permissions="$LDAP_PRIVILEGE_PERMISSION_CN" || true
done
}
role_privileges() {
local LDAP_ROLE_CN=$1 LDAP_ROLE_PRIVILEGE_CN
shift 1
echo "updating cn=$LDAP_ROLE_CN,$LDAP_DNSUFFIX_ROLE ..." >&2
for LDAP_ROLE_PRIVILEGE_CN in "$@"; do
ipa role-add-privilege "$LDAP_ROLE_CN" --privileges="$LDAP_ROLE_PRIVILEGE_CN" || true
done
}
role_members() {
local LDAP_ROLE_CN=$1 LDAP_ROLE_MEMBER_DN LDAP_ROLE_MEMBER_CN LDAP_ROLE_MEMBER_TYPE
shift 1
echo "updating cn=$LDAP_ROLE_CN,$LDAP_DNSUFFIX_ROLE ..." >&2
for LDAP_ROLE_MEMBER_DN in "$@"; do
case $LDAP_ROLE_MEMBER_DN in
uid=*",$LDAP_DNSUFFIX_USER"*)
LDAP_ROLE_MEMBER_TYPE=users
;;
cn=*",$LDAP_DNSUFFIX_GROUP"*)
LDAP_ROLE_MEMBER_TYPE=groups
;;
fqdn=*",$LDAP_DNSUFFIX_HOST"*)
LDAP_ROLE_MEMBER_TYPE=hosts
;;
cn=*",$LDAP_DNSUFFIX_HOSTGROUP"*)
LDAP_ROLE_MEMBER_TYPE=hostgroups
;;
krbprincipalname=*",$LDAP_DNSUFFIX_SERVICE"*)
LDAP_ROLE_MEMBER_TYPE=services
;;
*)
echo "WARN: unknown role member type for $LDAP_ROLE_MEMBER_DN" >&2
ipa role-modify "$LDAP_ROLE_CN" --addattr=member="$LDAP_ROLE_MEMBER_DN" || true
continue
;;
esac
LDAP_ROLE_MEMBER_CN=${LDAP_ROLE_MEMBER_DN%%,*}
LDAP_ROLE_MEMBER_CN=${LDAP_ROLE_MEMBER_CN#*=}
ipa role-add-member "$LDAP_ROLE_CN" --${LDAP_ROLE_MEMBER_TYPE}="$LDAP_ROLE_MEMBER_CN" || true
done
}
smbsync_group() {
local LDAP_GROUP_CN=$1 SMB_GROUP_DATA SMB_GROUP_SID
shift 1

View file

@ -10,10 +10,18 @@
inherit (lib.modules) mkIf mkMerge mkOptionDefault;
inherit (lib.attrsets) attrValues;
inherit (lib.lists) filter;
inherit (lib.strings) concatStringsSep concatMapStringsSep escapeShellArgs;
inherit (lib.strings) concatStringsSep concatMapStringsSep;
inherit (config.users) ldap;
cfg = config.users.ldap.management;
ldap'lib = config.lib.ldap;
enabledObjects = filter (object: object.enable) (attrValues cfg.objects);
sysAccounts = filter (acc: acc.enable) (attrValues cfg.sysAccounts);
sysAccountPasswordFiles = concatMapStringsSep "," (acc: "${acc.uid}=${toString acc.passwordFile}") sysAccounts;
privileges = filter (priv: priv.enable) (attrValues cfg.privileges);
privilegePermissions = concatMapStringsSep "," (priv: "${priv.cn}=${concatStringsSep "." priv.permissions}") privileges;
roles = filter (role: role.enable) (attrValues cfg.roles);
rolePrivileges = concatMapStringsSep "," (role: "${role.cn}=${concatStringsSep "." role.privileges}") roles;
roleMembers = concatMapStringsSep "+" (role: "${role.cn}=${concatMapStringsSep "%" ldap'lib.withBaseDn role.members}") roles;
smbSyncUsers = filter (user: user.samba.sync.enable) (attrValues cfg.users);
smbSyncGroups = filter (group: group.samba.sync.enable) (attrValues cfg.groups);
modifyObjects = filter (object: object.changeType == "modify") enabledObjects;
@ -42,6 +50,56 @@
ldapmodify -f "$MAN_LDAP_DELETE"
'';
sysaccountScript = pkgs.writeShellScript "ldap-management-sysaccounts.sh" ''
set -eu
source ${./ldap-common.sh}
source ${./ldap-sync.sh}
IFS=',' declare -a 'SYSACCOUNT_PASSWORD_FILES=($SYSACCOUNT_PASSWORD_FILES)'
for SYSACCOUNT_PASSWORD_FILE in "''${SYSACCOUNT_PASSWORD_FILES[@]}"; do
SYSACCOUNT_UID=''${SYSACCOUNT_PASSWORD_FILE%%=*}
SYSACCOUNT_PASSWORD_PATH=''${SYSACCOUNT_PASSWORD_FILE#*=}
if [[ -n $SYSACCOUNT_PASSWORD_PATH ]]; then
sysaccount_password "$SYSACCOUNT_UID" "$SYSACCOUNT_PASSWORD_PATH"
fi
done
'';
privilegeScript = pkgs.writeShellScript "ldap-management-privileges.sh" ''
set -eu
source ${./ldap-common.sh}
source ${./ldap-sync.sh}
IFS=',' declare -a 'PRIVILEGE_PERMISSIONS=($PRIVILEGE_PERMISSIONS)'
for PRIVILEGE_PERMISSION in "''${PRIVILEGE_PERMISSIONS[@]}"; do
PRIVILEGE_CN=''${PRIVILEGE_PERMISSION%%=*}
PRIVILEGE_PERMS=''${PRIVILEGE_PERMISSION#*=}
IFS='.' declare -a 'PRIVILEGE_PERMS=($PRIVILEGE_PERMS)'
privilege_permissions "$PRIVILEGE_CN" "''${PRIVILEGE_PERMS[@]}"
done
'';
roleScript = pkgs.writeShellScript "ldap-management-rols.sh" ''
set -eu
source ${./ldap-common.sh}
source ${./ldap-sync.sh}
IFS=',' declare -a 'ROLE_PRIVILEGES=($ROLE_PRIVILEGES)'
for ROLE_PRIVILEGE in "''${ROLE_PRIVILEGES[@]}"; do
ROLE_CN=''${ROLE_PRIVILEGE%%=*}
ROLE_PRIVS=''${ROLE_PRIVILEGE#*=}
IFS='.' declare -a 'ROLE_PRIVS=($ROLE_PRIVS)'
role_privileges "$ROLE_CN" "''${ROLE_PRIVS[@]}"
done
IFS='+' declare -a 'ROLE_MEMBERS=($ROLE_MEMBERS)'
for ROLE_MEMBER in "''${ROLE_MEMBERS[@]}"; do
ROLE_CN=''${ROLE_MEMBER%%=*}
ROLE_MEMS=''${ROLE_MEMBER#*=}
IFS='%' declare -a 'ROLE_MEMS=($ROLE_MEMS)'
role_members "$ROLE_CN" "''${ROLE_MEMS[@]}"
done
'';
syncScript = pkgs.writeShellScript "ldap-management-sync.sh" ''
set -eu
@ -106,7 +164,9 @@ in {
];
serviceConfig = {
Type = mkOptionDefault "oneshot";
ExecStart = [ "${managementScript}" ];
ExecStart = [
"${managementScript}"
];
RemainAfterExit = mkOptionDefault true;
};
};
@ -117,10 +177,15 @@ in {
path = mkMerge [
path
[ pkgs.xxd ]
(mkIf config.security.ipa.enable [ pkgs.freeipa ])
];
restartTriggers = [
smbSyncGroupNames
smbSyncUserNames
sysAccountPasswordFiles
privilegePermissions
rolePrivileges
roleMembers
];
environment = mkMerge [
ldapEnv
@ -128,13 +193,29 @@ in {
(mapOptionDefaults {
LDAP_DNSUFFIX_USER = ldap.userDnSuffix;
LDAP_DNSUFFIX_GROUP = ldap.groupDnSuffix;
LDAP_DNSUFFIX_SYSACCOUNT = ldap.sysAccountDnSuffix;
LDAP_DNSUFFIX_PERMISSION = ldap.permissionDnSuffix;
LDAP_DNSUFFIX_PRIVILEGE = ldap.privilegeDnSuffix;
LDAP_DNSUFFIX_ROLE = ldap.roleDnSuffix;
LDAP_DNSUFFIX_HOST = ldap.hostDnSuffix;
LDAP_DNSUFFIX_HOSTGROUP = ldap.hostGroupDnSuffix;
LDAP_DNSUFFIX_SERVICE = ldap.serviceDnSuffix;
SMB_SYNC_GROUPS = concatStringsSep "," smbSyncGroupNames;
SMB_SYNC_USERS = concatStringsSep "," smbSyncUserNames;
SYSACCOUNT_PASSWORD_FILES = sysAccountPasswordFiles;
PRIVILEGE_PERMISSIONS = privilegePermissions;
ROLE_PRIVILEGES = rolePrivileges;
ROLE_MEMBERS = roleMembers;
})
];
serviceConfig = {
Type = mkOptionDefault "oneshot";
ExecStart = [ "${syncScript}" ];
ExecStart = mkMerge [
(mkIf (privileges != [ ]) [ "${privilegeScript}" ])
(mkIf (roles != [ ]) [ "${roleScript}" ])
[ "${syncScript}" ]
(mkIf (sysAccounts != [ ]) [ "${sysaccountScript}" ])
];
};
};
timers.ldap-management-sync = {

View file

@ -62,7 +62,7 @@
'';
in {
modify = object: let
enabledSettings' = filterAttrs (_: setting: setting.enable) object.settings;
enabledSettings' = filterAttrs (_: setting: setting.enable && !setting.initial) object.settings;
enabledSettings = ldap'lib.mkLdapModifyObjectSettings enabledSettings';
replaceSettings' = filterAttrs (_: setting: setting.modifyType == "replace") enabledSettings';
replaceSettings = ldap'lib.mkLdapModifyObjectSettings replaceSettings';
@ -114,6 +114,10 @@
value = mkOption {
type = ldapValueType;
};
initial = mkOption {
type = bool;
default = false;
};
modifyType = mkOption {
type = enum [ "replace" "add" "delete" ];
default = "replace";

View file

@ -5,7 +5,7 @@
...
}: let
inherit (inputs.self.lib.lib) mkAlmostOptionDefault mapOptionDefaults mapListToAttrs;
inherit (lib.options) mkOption;
inherit (lib.options) mkOption mkEnableOption;
inherit (lib.modules) mkIf mkMerge mkOptionDefault;
inherit (lib.attrsets) attrNames mapAttrs mapAttrsToList;
inherit (lib.lists) filter;
@ -55,8 +55,7 @@
};
sysaccount = {
location = ldap.sysAccountDnSuffix;
# TODO: targetFilter
target = "uid=*";
targetFilter = "(objectclass=account)";
};
};
in {
@ -95,13 +94,13 @@
};
members = mkOption {
type = listOf str;
default = [ ];
};
object = mkOption {
type = ldap.lib.objectSettingsType;
};
};
config = let
conf.members = mkIf (config.bindType != "permission") (mkOptionDefault [ ]);
conf.targetFilter = mkIf (config.target != null) (mkOptionDefault null);
conf.object = {
dn = mkOptionDefault (ldap.lib.withBaseDn "cn=${config.cn},${ldap.permissionDnSuffix}");
@ -129,6 +128,73 @@
};
in mkMerge [ conf target ];
};
privilegeModule = {config, name, ldap, ...}: {
options = with lib.types; {
enable = mkEnableOption "privilege" // {
default = true;
};
cn = mkOption {
type = str;
default = name;
};
permissions = mkOption {
type = listOf str;
default = [ ];
};
object = mkOption {
type = ldap.lib.objectSettingsType;
};
};
config = {
object = {
enable = mkAlmostOptionDefault config.enable;
dn = mkOptionDefault (ldap.lib.withBaseDn "cn=${config.cn},${ldap.privilegeDnSuffix}");
settings = {
changeType = mkAlmostOptionDefault "add";
settings = mapOptionDefaults {
cn = config.cn;
objectClass = [ "top" "nestedgroup" "groupofnames" ];
};
};
};
};
};
roleModule = {config, name, ldap, ...}: {
options = with lib.types; {
enable = mkEnableOption "role" // {
default = true;
};
cn = mkOption {
type = str;
default = name;
};
privileges = mkOption {
type = listOf str;
default = [ ];
};
members = mkOption {
type = listOf str;
default = [ ];
};
object = mkOption {
type = ldap.lib.objectSettingsType;
};
};
config = {
object = {
enable = mkAlmostOptionDefault config.enable;
dn = mkOptionDefault (ldap.lib.withBaseDn "cn=${config.cn},${ldap.roleDnSuffix}");
settings = {
changeType = mkAlmostOptionDefault "add";
settings = mapOptionDefaults {
cn = config.cn;
objectClass = [ "top" "nestedgroup" "groupofnames" ];
member = mkIf (config.members != [ ]) (mkOptionDefault (map ldap.lib.withBaseDn config.members));
};
};
};
};
};
in {
options.users.ldap = with lib.types; {
management = {
@ -139,6 +205,20 @@ in {
});
default = { };
};
privileges = mkOption {
type = attrsOf (submoduleWith {
modules = [ privilegeModule ];
inherit (config.lib.ldap) specialArgs;
});
default = { };
};
roles = mkOption {
type = attrsOf (submoduleWith {
modules = [ roleModule ];
inherit (config.lib.ldap) specialArgs;
});
default = { };
};
};
permissionDnSuffix = mkOption {
type = str;
@ -152,8 +232,9 @@ in {
};
config.users.ldap = {
management.objects = let
permissionObjects = mapAttrsToList (_: user: user.object) cfg.management.permissions;
enabledObjects = filter (object: object.enable) (permissionObjects);
permissionObjects = mapAttrsToList (_: perm: perm.object) cfg.management.permissions;
privilegeObjects = mapAttrsToList (_: priv: priv.object) cfg.management.privileges;
enabledObjects = filter (object: object.enable) (permissionObjects ++ privilegeObjects);
in mapListToAttrs ldap'lib.mapObjectSettingsToPair enabledObjects;
};
}