feat(idp): sync on a timer

This commit is contained in:
arcnmx 2024-03-30 12:20:49 -07:00
parent 3f7e25f5f5
commit 931af35f09
3 changed files with 196 additions and 101 deletions

View file

@ -0,0 +1,42 @@
ldap_args_binddn() {
if [[ -n ${LDAPBINDDN-} ]]; then
LDAP_ARGS+=(
-x
-y "$LDAPBINDPW_FILE"
)
if [[ -n ${LDAPBINDPW-} ]]; then
LDAP_ARGS+=(
-w "$LDAPBINDPW"
)
else
LDAP_ARGS+=(
-y "$LDAPBINDPW_FILE"
)
fi
fi
}
ldap_args_op() {
ldap_args_binddn
if [[ -z ${LDAPBINDDN-} ]]; then
LDAP_ARGS+=(-Q)
fi
}
ldapwhoami() {
local LDAP_ARGS=("$@")
ldap_args_binddn
command ldapwhoami "${LDAP_ARGS[@]}"
}
ldapsearch() {
local LDAP_ARGS=("$@")
ldap_args_op
command ldapsearch -LLL -o ldif_wrap=no "${LDAP_ARGS[@]}"
}
ldapmodify() {
local LDAP_ARGS=("$@")
ldap_args_op
command ldapmodify "${LDAP_ARGS[@]}"
}

View file

@ -0,0 +1,69 @@
ldap_parse() {
local LDAP_ATTR=$1 LDAP_LIMIT LDAP_LINE LDAP_COUNT=0
shift 1
local LDAP_LIMIT=${1-1}
while read -r LDAP_LINE; do
if [[ $LDAP_LIMIT -eq 0 ]]; then
break
fi
if [[ $LDAP_LINE = "$LDAP_ATTR:: "* ]]; then
printf '%s\n' "$LDAP_LINE" | cut -d ' ' -f 2- | base64 -d
elif [[ $LDAP_LINE = "$LDAP_ATTR: "* ]]; then
printf '%s\n' "$LDAP_LINE" | cut -d ' ' -f 2-
else
continue
fi
LDAP_COUNT=$((LDAP_COUNT+1))
LDAP_LIMIT=$((LDAP_LIMIT-1))
done
if [[ $LDAP_COUNT -eq 0 ]]; then
echo "$LDAP_ATTR not found" >&2
return 1
fi
}
smbsync_group() {
local LDAP_GROUP_CN=$1 SMB_GROUP_DATA SMB_GROUP_SID
shift 1
echo "updating cn=$LDAP_GROUP_CN,$LDAP_DNSUFFIX_GROUP ..." >&2
SMB_GROUP_DATA=$(ldapsearch -z1 \
-b "$LDAP_DNSUFFIX_GROUP$LDAPBASE" \
"(&(cn=$LDAP_GROUP_CN)(objectClass=posixgroup))" \
objectClass ipaNTSecurityIdentifier
)
SMB_GROUP_SID=$(ldap_parse ipaNTSecurityIdentifier <<< "$SMB_GROUP_DATA")
ldapmodify <<EOF
dn: cn=$LDAP_GROUP_CN,$LDAP_DNSUFFIX_GROUP$LDAPBASE
changetype: modify
replace: sambaSID
sambaSID: $SMB_GROUP_SID
-
EOF
}
smbsync_user() {
local LDAP_USER_UID=$1 SMB_USER_DATA SMB_USER_SID SMB_USER_NTPASS
shift 1
echo "updating uid=$LDAP_USER_UID,$LDAP_DNSUFFIX_USER ..." >&2
SMB_USER_DATA=$(ldapsearch -z1 \
-b "$LDAP_DNSUFFIX_USER$LDAPBASE" \
"(&(uid=$LDAP_USER_UID)(objectClass=posixaccount))" \
objectClass ipaNTSecurityIdentifier ipaNTHash ipaUserAuthType memberOf
)
SMB_USER_SID=$(ldap_parse ipaNTSecurityIdentifier <<< "$SMB_USER_DATA")
SMB_USER_NTPASS=$(ldap_parse ipaNTHash <<< "$SMB_USER_DATA" | xxd -p)
SMB_USER_NTPASS=${SMB_USER_NTPASS^^}
ldapmodify <<EOF
dn: uid=$LDAP_USER_UID,$LDAP_DNSUFFIX_USER$LDAPBASE
changetype: modify
replace: sambaSID
sambaSID: $SMB_USER_SID
-
replace: sambaNTPassword
sambaNTPassword: $SMB_USER_NTPASS
-
EOF
}

View file

@ -7,7 +7,7 @@
}: let
inherit (inputs.self.lib.lib) mapOptionDefaults;
inherit (lib.options) mkEnableOption;
inherit (lib.modules) mkIf mkOptionDefault;
inherit (lib.modules) mkIf mkMerge mkOptionDefault;
inherit (lib.attrsets) attrValues;
inherit (lib.lists) filter;
inherit (lib.strings) concatStringsSep concatMapStringsSep escapeShellArgs;
@ -29,92 +29,10 @@
deletions = pkgs.writeText "ldap-management-delete.ldap" (
concatMapStringsSep "\n" (object: object.changeText) deleteObjects
);
objectClassAttr = "objectClass";
sidAttr = "ipaNTSecurityIdentifier";
ntHashAttr = "ipaNTHash";
authTypeAttr = "ipaUserAuthType";
userSearchAttrs = [ objectClassAttr sidAttr authTypeAttr ntHashAttr ];
groupSearchAttrs = [ objectClassAttr sidAttr ];
managementScript = pkgs.writeShellScript "ldap-management.sh" ''
managementScript = pkgs.writeShellScript "ldap-management-init.sh" ''
set -eu
ldapsearch() {
command ldapsearch -QLLL -o ldif_wrap=no "$@"
}
ldapmodify() {
command ldapmodify -Q "$@"
}
ldap_parse() {
local LDAP_ATTR=$1 LDAP_LIMIT LDAP_LINE LDAP_COUNT=0
shift 1
local LDAP_LIMIT=''${1-1}
while read -r LDAP_LINE; do
if [[ $LDAP_LIMIT -eq 0 ]]; then
break
fi
if [[ $LDAP_LINE = "$LDAP_ATTR:: "* ]]; then
printf '%s\n' "$LDAP_LINE" | cut -d ' ' -f 2- | base64 -d
elif [[ $LDAP_LINE = "$LDAP_ATTR: "* ]]; then
printf '%s\n' "$LDAP_LINE" | cut -d ' ' -f 2-
else
continue
fi
LDAP_COUNT=$((LDAP_COUNT+1))
LDAP_LIMIT=$((LDAP_LIMIT-1))
done
if [[ $LDAP_COUNT -eq 0 ]]; then
echo "$LDAP_ATTR not found" >&2
return 1
fi
}
smbsync_group() {
local LDAP_GROUP_CN=$1 SMB_GROUP_DATA SMB_GROUP_SID
shift 1
echo "updating cn=''${LDAP_GROUP_CN},${ldap.groupDnSuffix} ..." >&2
SMB_GROUP_DATA=$(ldapsearch -z1 \
-b "${ldap.groupDnSuffix}${ldap.base}" \
"(&(cn=$LDAP_GROUP_CN)(${objectClassAttr}=posixgroup))" \
${escapeShellArgs groupSearchAttrs}
)
SMB_GROUP_SID=$(ldap_parse ${sidAttr} <<< "$SMB_GROUP_DATA")
ldapmodify <<EOF
dn: cn=$LDAP_GROUP_CN,${ldap.groupDnSuffix}${ldap.base}
changetype: modify
replace: sambaSID
sambaSID: $SMB_GROUP_SID
-
EOF
}
smbsync_user() {
local LDAP_USER_UID=$1 SMB_USER_DATA SMB_USER_SID SMB_USER_NTPASS
shift 1
echo "updating uid=''${LDAP_USER_UID},${ldap.userDnSuffix} ..." >&2
SMB_USER_DATA=$(ldapsearch -z1 \
-b "${ldap.userDnSuffix}${ldap.base}" \
"(&(uid=$LDAP_USER_UID)(${objectClassAttr}=posixaccount))" \
${escapeShellArgs userSearchAttrs}
)
SMB_USER_SID=$(ldap_parse ${sidAttr} <<< "$SMB_USER_DATA")
SMB_USER_NTPASS=$(ldap_parse ${ntHashAttr} <<< "$SMB_USER_DATA" | xxd -p)
SMB_USER_NTPASS=''${SMB_USER_NTPASS^^}
ldapmodify <<EOF
dn: uid=$LDAP_USER_UID,${ldap.userDnSuffix}${ldap.base}
changetype: modify
replace: sambaSID
sambaSID: $SMB_USER_SID
-
replace: sambaNTPassword
sambaNTPassword: $SMB_USER_NTPASS
-
EOF
}
source ${./ldap-common.sh}
ldapwhoami
@ -123,6 +41,14 @@
ldapmodify -c -f "$MAN_LDAP_MODIFY" || true
ldapmodify -f "$MAN_LDAP_DELETE"
'';
syncScript = pkgs.writeShellScript "ldap-management-sync.sh" ''
set -eu
source ${./ldap-common.sh}
source ${./ldap-sync.sh}
ldapwhoami
IFS=',' declare -a 'SMB_SYNC_GROUPS=($SMB_SYNC_GROUPS)'
for SMB_GROUP_CN in "''${SMB_SYNC_GROUPS[@]}"; do
@ -137,27 +63,85 @@ in {
options.users.ldap.management = with lib.types; {
enable = mkEnableOption "LDAP object management";
};
config = mkIf cfg.enable {
systemd.services.ldap-management = rec {
wantedBy = [ "multi-user.target" ];
wants = [ "krb5-host.service" ];
after = wants;
path = [ config.services.openldap.package pkgs.coreutils pkgs.xxd ];
environment = mapOptionDefaults {
config.systemd = let
path = [ config.services.openldap.package pkgs.coreutils ];
krb5-host = "krb5-host.service";
ldap-management-init = "ldap-management-init.service";
ldapEnv = mapOptionDefaults {
# man 5 ldap.conf
LDAPBASE = ldap.base;
LDAPURI = "ldaps://ldap.int.${config.networking.domain}";
};
ldapAuth = mkMerge [
(mkIf config.security.krb5.enable (mapOptionDefaults {
LDAPSASL_MECH = "GSSAPI";
LDAPSASL_AUTHCID = "dn:fqdn=${config.networking.fqdn},${ldap.hostDnSuffix}${ldap.base}";
# LDAPBINDDN?
SMB_SYNC_GROUPS = concatStringsSep "," (map (group: group.name) smbSyncGroups);
SMB_SYNC_USERS = concatStringsSep "," (map (user: user.uid) smbSyncUsers);
}))
(mkIf (config.users.ldap.bind.distinguishedName != "") (mapOptionDefaults {
LDAPBINDDN = config.users.ldap.bind.distinguishedName;
LDAPBINDPW_FILE = config.users.ldap.bind.passwordFile;
}))
];
smbSyncGroupNames = map (group: group.name) smbSyncGroups;
smbSyncUserNames = map (user: user.uid) smbSyncUsers;
in mkIf cfg.enable {
services.ldap-management-init = {
inherit path;
wants = [ krb5-host ];
after = [ krb5-host ];
wantedBy = [ "multi-user.target" ];
restartTriggers = [
"${additions}"
"${modifications}"
"${deletions}"
];
environment = mkMerge [
ldapEnv
ldapAuth
(mapOptionDefaults {
MAN_LDAP_ADD = "${additions}";
MAN_LDAP_MODIFY = "${modifications}";
MAN_LDAP_DELETE = "${deletions}";
};
})
];
serviceConfig = {
Type = mkOptionDefault "oneshot";
ExecStart = [ "${managementScript}" ];
RemainAfterExit = mkOptionDefault true;
};
};
services.ldap-management-sync = {
wants = [ krb5-host ];
requires = [ ldap-management-init ];
after = [ krb5-host ldap-management-init ];
path = mkMerge [
path
[ pkgs.xxd ]
];
restartTriggers = [
smbSyncGroupNames
smbSyncUserNames
];
environment = mkMerge [
ldapEnv
ldapAuth
(mapOptionDefaults {
LDAP_DNSUFFIX_USER = ldap.userDnSuffix;
LDAP_DNSUFFIX_GROUP = ldap.groupDnSuffix;
SMB_SYNC_GROUPS = concatStringsSep "," smbSyncGroupNames;
SMB_SYNC_USERS = concatStringsSep "," smbSyncUserNames;
})
];
serviceConfig = {
Type = mkOptionDefault "oneshot";
ExecStart = [ "${syncScript}" ];
};
};
timers.ldap-management-sync = {
wantedBy = [ "timers.target" ];
timerConfig = mapOptionDefaults {
OnBootSec = "1m";
OnUnitInactiveSec = "30m";
};
};
};