Je continue mon épopée avec l’utilisation du LDAP fourni par l’OpenDirectory de Mac OS X Server. J’ai eu besoin récemment d’interfacer à celui-ci une application qui ne sait travailler qu’avec certains objets particuliers du LDAP (ici des unités d’organisation et des groupOfNames
).
J’avais repéré il y a quelques temps un article de Yoann Gini qui traitait déjà de ce sujet et proposait à la fois un script pour synchroniser un groupe de l’OpenDirectory avec un groupe de type groupOfNames
du LDAP. Je suis reparti de cette proposition fort pertinente.
Mais, j’ai essayé d’ajouter une fonction qui me manque régulièrement sur les groupes de l’OpenDirectory, à savoir la présence d’un attribut de type memberOf
dans les entrées utilisateurs, permettant ainsi de faire des requêtes de type '(memberOf=xxxx)'
. C’est particulièrement utile pour restreindre l’accès à l’authentification pour certaines applications à un certain groupe d’utilisateurs par exemple.
Etape 1 : créer sur le LDAP une branche de classe organizationalUnit
Dans mon cas, j’ai choisi, comme l’avais fait Yoann Gini, de créer un objet de type organizationalUnit au moyen d’Apache Directory Studio.
Une fois positionné à la racine de notre LDAP, il faut utiliser le menu LDAP > nouvelle entrée
:
Ensuite, sélectionnez la classe de type organizationalUnit
:
Puis entrez un nom pour cette branche (ici ou=lists
) :
Etape 2 : ajouter un groupe de type groupOfNames à la branche organizationalUnit
Positionnez-vous dans la branche nouvellement créée, et utilisez à nouveau la commande LDAP > nouvelle entrée
, en choisissant cette fois-ci la classe groupOfNames
:
Puis nommez ce groupe (ici cn=workgroup
) :
Il vous sera ensuite demandé de populer ce groupe en y affectant au minimum un premier membre, soit en rentrant manuellement son distinguished name soit en le sélectionnant avec l’interface de navigation au sein du LDAP.
Etape 3 : ajouter des utilisateurs dans l’objet de type groupOfNames au moyen de ldapSync
Mon adaptation du script de Yoann Gini est la suivante :
#!/bin/bash #-------------------------------------------# # ldapSync # #-------------------------------------------# # # # Synchronize OD Group to LDAP GroupOfNames # # (One way) # # # # fork of a script written by Yoann Gini # # (VERSION 0.1 -- Dec. 15, 2010) # # http://goo.gl/lVnjFw # # # # VERSION 0.4 -- Jan. 2, 2013 # # Licenced under # # Creative Commons 4.0 BY NC SA # # # # http://goo.gl/9Jgf7u # # Yvan Godard # # godardyvan@gmail.com # # # #-------------------------------------------# # Variables initialisation VERSION="ldapSync v0.4 - http://goo.gl/9Jgf7u - godardyvan@gmail.com" help="no" SCRIPT_DIR=$(dirname $0) SCRIPT_NAME=$(basename $0) LDAP_SERVER="ldap://127.0.0.1" LDAP_DN_BASE="" LDAP_GROUP_DN="" LDAP_ADMIN_UID="diradmin" LDAP_ADMIN_PASS="" LDAP_DN_USER_BRANCH="cn=users" DSCL_SEARCH_PATH="/Search" DSCL_GROUP_NAME="" EMAIL_REPORT="nomail" EMAIL_LEVEL=0 LOG="/var/log/ldapSync.log" LOG_ACTIVE=0 LOG_TEMP=$(mktemp /tmp/ldapSync_log.XXXXX) ERROR_CODE_MODIFY_1=0 ERROR_CODE_MODIFY_2=0 ERROR_CODE_MODIFY_3=0 ERROR_CODE_MODIFY_4=0 TMP_FOLDER=/tmp/ldapSync-$(uuidgen) COMPLETE_USER_DN=${TMP_FOLDER}/all_user_dn ACTUAL_USER_DN=${TMP_FOLDER}/actual_user_dn ACTUAL_USER_WITH_ATTRIBUTE=${TMP_FOLDER}/actual_user_with_attribute LDAP_ADD_LIST=${TMP_FOLDER}/ldap_add_list LDAP_DELETE_LIST=${TMP_FOLDER}/ldap_delete_list LDAP_ADD_MEMBEROF=${TMP_FOLDER}/ldap_add_memberof LDAP_DELETE_MEMBEROF=${TMP_FOLDER}/ldap_delete_memberof MEMBER_OF_GROUP=${TMP_FOLDER}/group_members LDIF_ATTRIBUTE_MOD_FOLDER=${TMP_FOLDER}/attribute_modif LDIF_ATTRIBUTE_MOD_LIST=${TMP_FOLDER}/attribute_modif_list_ldif LDIF_ERROR_APPLY=${TMP_FOLDER}/error_apply_ldif MEMBER_OF_GROUP=${TMP_FOLDER}/group_members LDAP_MODIFY_ADD=${TMP_FOLDER}/modif_add.ldif LDAP_MODIFY_DELETE=${TMP_FOLDER}/modif_delete.ldif SYNC_MEMBEROF="no" SYNC_MEMBEROF_ATTRIBUTE="memberOf" ATTIBUTE_USED="no" LDAP_ADD_DESCRIPTION=${TMP_FOLDER}/modif_description.ldif LDAP_GROUP_DESCRIPTION_FILE=${TMP_FOLDER}/LDAP_GROUP_DESCRIPTION_FILE help () { echo -e "\n**********************************************************************\n" echo -e " $VERSION" echo -e "\n**********************************************************************\n" echo -e "This tool is make for sync LDAP GroupOfNames with the member list of an OD Group." echo -e "This tool need to be run on a computer bind to the OD." echo -e "\nDisclamer:" echo -e "This tool is provide without any support and guarantee." echo -e "\nSynopsis:" echo -e "\t./${SCRIPT_NAME} [-h] | -d <LDAP base namespace> -p <LDAP Admin password> -g <relative DN of LDAP GroupOfNames> -G <OD Groupname>" echo -e "\t [-a <LDAP admin UID>] [-s <LDAP server>] [-u <relative DN of user banch>]" echo -e "\t [-S <DSCL Search Path>]" echo -e "\t [-e <email report option>] [-E <email address>] [-j <log file>]" echo -e "\n\t-h: prints this help then exit" echo -e "\nMandatory options:" echo -e "\t-d <LDAP base namespace>: the base DN for each LDAP entry (i.e.: 'dc=server,dc=office,dc=com')" echo -e "\t-p <LDAP admin password>: the password of the LDAP administrator (asked if missing)" echo -e "\t-g <relative DN of LDAP GroupOfNames>: the LDAP GroupOfNames to update" echo -e "\t-G <OD Groupname>: the OD group used as source" echo -e "\nOptional options:" echo -e "\t-a <LDAP admin UID>: UID of the LDAP administrator (i.e.: 'admin', default: '${LDAP_ADMIN_UID}')" echo -e "\t-s <LDAP Server>: the LDAP server URL (default: '${LDAP_SERVER}')" echo -e "\t-u <relative DN of user banch>: the relative DN of the LDAP branch that contains the users (i.e.: 'cn=allusers', default: '${LDAP_DN_USER_BRANCH}')" echo -e "\t-S <DSCL Search Path>: the DSCL Search path for OD Group (default: '${DSCL_SEARCH_PATH}')" echo -e "\t-m <sync memberOf>: add syncing attribute of type 'memberOf' in each user LDAP entry (must be 'yes' or 'no', default: '${SYNC_MEMBEROF}'')" echo -e "\t-M <memberOf attribute>: attribute to use to add the distinguished name of the groups which the user is a member" echo -e "\t (i.e.: 'resMemberOf', default: '${SYNC_MEMBEROF_ATTRIBUTE}'), use only if '-m' is used" echo -e "\t-e <email report option>: settings for sending a report by email, must be 'onerror', 'forcemail' or 'nomail' (default: '${EMAIL_REPORT}')" echo -e "\t-E <email address>: email address to send the report, must be filled if '-e forcemail' or '-e onerror' options is used" echo -e "\t-j <log file>: enables logging instead of standard output. Specify an argument for the full path to the log file" echo -e "\t (i.e.: '${LOG}') or use 'default' (${LOG})" exit 0 } error () { echo -e "\n*** Error ***" echo -e ${1} echo -e "\n"${VERSION} alldone 1 } alldone () { [ $1 -ne 0 ] && echo -e "\n**** End of process with ERROR(s) ****\n" [ $1 -eq 0 ] && echo -e "\n**** End of process OK : ${SCRIPT_NAME} ****\n" # Redirect standard outpout exec 1>&6 6>&- # Logging if needed [ ${LOG_ACTIVE} -eq 1 ] && cat ${LOG_TEMP} >> ${LOG} # Print current log to standard outpout [ ${LOG_ACTIVE} -ne 1 ] && cat ${LOG_TEMP} [ ${EMAIL_LEVEL} -ne 0 ] && [ $1 -ne 0 ] && cat ${LOG_TEMP} | mail -s "[ERROR: ${SCRIPT_NAME}] OD Group $DSCL_GROUP_NAME to $LDAP_GROUP_DN,$LDAP_DN_BASE" $EMAIL_ADDRESS [ ${EMAIL_LEVEL} -eq 2 ] && [ $1 -eq 0 ] && cat ${LOG_TEMP} | mail -s "[OK: ${SCRIPT_NAME}] OD Group $DSCL_GROUP_NAME to $LDAP_GROUP_DN,$LDAP_DN_BASE" $EMAIL_ADDRESS # Remove temp files/folder rm -R ${LOG_TEMP} rm -R ${TMP_FOLDER} exit ${1} } OPTS_COUNT=0 while getopts "ha:p:g:G:s:S:d:e:E:j:u:m:M:" OPTION do case "$OPTION" in h) help="yes" ;; a) LDAP_ADMIN_UID=${OPTARG} ;; p) LDAP_ADMIN_PASS=${OPTARG} ;; g) LDAP_GROUP_DN=${OPTARG} let OPTS_COUNT=$OPTS_COUNT+1 ;; G) DSCL_GROUP_NAME=${OPTARG} let OPTS_COUNT=$OPTS_COUNT+1 ;; d) LDAP_DN_BASE=${OPTARG} let OPTS_COUNT=$OPTS_COUNT+1 ;; s) LDAP_SERVER=${OPTARG} ;; S) DSCL_SEARCH_PATH=${OPTARG} ;; e) EMAIL_REPORT=${OPTARG} ;; E) EMAIL_ADDRESS=${OPTARG} ;; j) [ $OPTARG != "default" ] && LOG=${OPTARG} LOG_ACTIVE=1 ;; u) LDAP_DN_USER_BRANCH=${OPTARG} ;; m) SYNC_MEMBEROF=${OPTARG} ;; M) SYNC_MEMBEROF_ATTRIBUTE=${OPTARG} ATTIBUTE_USED="yes" ;; esac done if [[ ${OPTS_COUNT} != "3" ]] then help alldone 1 fi if [[ ${help} = "yes" ]] then help alldone 0 fi if [[ ${LDAP_ADMIN_PASS} = "" ]] then echo "Password for uid=$LDAP_ADMIN_UID,$LDAP_DN_USER_BRANCH,$LDAP_DN_BASE?" read -s LDAP_ADMIN_PASS fi # Create tmp folder mkdir -p ${TMP_FOLDER} # Redirect standard outpout to temp file exec 6>&1 exec >> ${LOG_TEMP} # Begin temp log echo -e "\n****************************** `date` ******************************\n\n$0 for $DSCL_GROUP_NAME to $LDAP_GROUP_DN,$LDAP_DN_BASE\n" # Test of sending email parameter and check the consistency of the parameter email address if [[ ${EMAIL_REPORT} = "forcemail" ]] then EMAIL_LEVEL=2 if [[ -z ${EMAIL_ADDRESS} ]] then echo -e "You use option '-e ${EMAIL_REPORT}' but you have not entered any email info.\n\t-> We continue the process without sending email." EMAIL_LEVEL=0 else echo "${EMAIL_ADDRESS}" | grep '^[a-zA-Z0-9._-]*@[a-zA-Z0-9._-]*\.[a-zA-Z0-9._-]*$' > /dev/null 2>&1 if [ $? -ne 0 ] then echo -e "This address '${EMAIL_REPORT}' does not seem valid.\n\t-> We continue the process without sending email." EMAIL_LEVEL=0 fi fi elif [[ ${EMAIL_REPORT} = "onerror" ]] then EMAIL_LEVEL=1 if [[ -z ${EMAIL_ADDRESS} ]] then echo -e "You use option '-e ${EMAIL_REPORT}' but you have not entered any email info.\n\t-> We continue the process without sending email." EMAIL_LEVEL=0 else echo "${EMAIL_ADDRESS}" | grep '^[a-zA-Z0-9._-]*@[a-zA-Z0-9._-]*\.[a-zA-Z0-9._-]*$' > /dev/null 2>&1 if [ $? -ne 0 ] then echo -e "This address '${EMAIL_REPORT}' does not seem valid.\n\t-> We continue the process without sending email." EMAIL_LEVEL=0 fi fi elif [[ ${EMAIL_REPORT} != "nomail" ]] then echo -e "\nOption '-e ${EMAIL_REPORT}' is not valid (must be: 'onerror', 'forcemail' or 'nomail').\n\t-> We continue the process without sending email." EMAIL_LEVEL=0 elif [[ ${EMAIL_REPORT} = "nomail" ]] then EMAIL_LEVEL=0 fi # Test ${SYNC_MEMBEROF} and ${ATTIBUTE_USED} [[ ${SYNC_MEMBEROF} != "yes" ]] && [[ ${SYNC_MEMBEROF} != "no" ]] && error "Trying to use '-m' option but paramter '${SYNC_MEMBEROF}' is forbiden, must be 'yes' or 'no'." [[ ${ATTIBUTE_USED} = "yes" ]] && [[ ${SYNC_MEMBEROF} = "no" ]] && error "Trying to use '-M' without '-m' option is forbiden." echo -e "\n**********************************************************************" echo -e " Group OpenDirectory -> LDAP GroupOfNames" echo -e "**********************************************************************\n" # LDAP connection test... echo -e "Test to bind LDAP ${LDAP_SERVER}:" ldapsearch -LLL -x -b ${LDAP_GROUP_DN},${LDAP_DN_BASE} -H ${LDAP_SERVER} > /dev/null 2>&1 if [ $? -ne 0 ] then error "Error while connecting with LDAP ${LDAP_SERVER} (${LDAP_GROUP_DN},${LDAP_DN_BASE}).\nPlease verify LDAP connection parameters, base DN and group relative DN." else echo -e "-> OK" fi # Test if OD group exists and if OD group contains members echo -e "\nCommand: '${DSCL_SEARCH_PATH} read /Groups/${DSCL_GROUP_NAME} GroupMembership'" dscl ${DSCL_SEARCH_PATH} read /Groups/${DSCL_GROUP_NAME} GroupMembership > /dev/null 2>&1 if [ $? -ne 0 ] then error "Command 'dscl ${DSCL_SEARCH_PATH} read /Groups/${DSCL_GROUP_NAME} GroupMembership' did not work properly.\nError $?." else dscl ${DSCL_SEARCH_PATH} read /Groups/${DSCL_GROUP_NAME} GroupMembership > ${MEMBER_OF_GROUP} [[ -z $(cat ${MEMBER_OF_GROUP}) ]] && error "Command 'dscl ${DSCL_SEARCH_PATH} read /Groups/${DSCL_GROUP_NAME} GroupMembership' does not find any user in the group." fi # Sync Group Description [[ -e ${LDAP_GROUP_DESCRIPTION_FILE} ]] && rm ${LDAP_GROUP_DESCRIPTION_FILE} touch ${LDAP_GROUP_DESCRIPTION_FILE} if [[ $(dscl ${DSCL_SEARCH_PATH} read /Groups/${DSCL_GROUP_NAME} Description 2> /dev/null | wc -l) -eq 1 ]]; then for item in $(dscl ${DSCL_SEARCH_PATH} read /Groups/${DSCL_GROUP_NAME} Description 2> /dev/null | perl -p -e "s/dsAttrTypeNative:Description://g" | sed '/^$/d'| sed 's/^[ \t]*//') do echo "${item}" >> ${LDAP_GROUP_DESCRIPTION_FILE} done elif [[ $(dscl ${DSCL_SEARCH_PATH} read /Groups/${DSCL_GROUP_NAME} Description 2> /dev/null | wc -l) -gt 1 ]]; then dscl ${DSCL_SEARCH_PATH} read /Groups/${DSCL_GROUP_NAME} Description 2> /dev/null | perl -p -e "s/dsAttrTypeNative:Description://g" | sed '/^$/d' | sed 's/^[ \t]*//' >> ${LDAP_GROUP_DESCRIPTION_FILE} fi LDAP_GROUP_DESCRIPTION=$(head -1 ${LDAP_GROUP_DESCRIPTION_FILE}) if [[ ! -z ${LDAP_GROUP_DESCRIPTION} ]]; then echo "dn: ${LDAP_GROUP_DN},${LDAP_DN_BASE}" > ${LDAP_GROUP_DESCRIPTION_FILE} echo "changetype: modify" >> ${LDAP_GROUP_DESCRIPTION_FILE} echo "replace: description" >> ${LDAP_GROUP_DESCRIPTION_FILE} echo "description: ${LDAP_GROUP_DESCRIPTION}" >> ${LDAP_GROUP_DESCRIPTION_FILE} echo -e "\n...Add description on ${LDAP_SERVER} on groupOfNames ${LDAP_GROUP_DN},${LDAP_DN_BASE}:" ldapmodify -D uid=${LDAP_ADMIN_UID},${LDAP_DN_USER_BRANCH},${LDAP_DN_BASE} -w ${LDAP_ADMIN_PASS} -H ${LDAP_SERVER} -x -f ${LDAP_GROUP_DESCRIPTION_FILE} [ $? -ne 0 ] && ERROR_CODE_MODIFY_4=1 OLDIFS=$IFS IFS=$'\n' for MODIF_LINE in $(cat ${LDAP_GROUP_DESCRIPTION_FILE}) do echo -e "\t${MODIF_LINE}" done IFS=$OLDIFS fi # Export actual members of OD group echo -e "Actual members of OD group:" dscl ${DSCL_SEARCH_PATH} read /Groups/${DSCL_GROUP_NAME} GroupMembership | sed 's/GroupMembership: //g' | tr ' ' '\n' | xargs -I @ echo @ | while read LINE_UID do echo uid=${LINE_UID},${LDAP_DN_USER_BRANCH},${LDAP_DN_BASE} >> ${COMPLETE_USER_DN} echo -e "\t- ${LINE_UID} (uid=${LINE_UID},${LDAP_DN_USER_BRANCH},${LDAP_DN_BASE})" done # Export actual members of groupOfNames group ldapsearch -LLL -x -b ${LDAP_GROUP_DN},${LDAP_DN_BASE} -H ${LDAP_SERVER} | grep member | awk '{print $2}' >> ${ACTUAL_USER_DN} # Calculation of changes to be applied grep -vf ${ACTUAL_USER_DN} ${COMPLETE_USER_DN} > ${LDAP_ADD_LIST} grep -vf ${COMPLETE_USER_DN} ${ACTUAL_USER_DN} > ${LDAP_DELETE_LIST} # Nothing to change [[ -z $(cat ${LDAP_DELETE_LIST}) ]] && [[ -z $(cat ${LDAP_ADD_LIST}) ]] && echo -e "-> Nothing to sync at this step!" ## We add users first to avoid error with empty groupOfNames # Add users to groupOfNames if [[ ! -z $(cat ${LDAP_ADD_LIST}) ]]; then echo "dn: ${LDAP_GROUP_DN},${LDAP_DN_BASE}" > ${LDAP_MODIFY_ADD} echo "changetype: modify" >> ${LDAP_MODIFY_ADD} echo "add: member" >> ${LDAP_MODIFY_ADD} for MEMBER in $(cat ${LDAP_ADD_LIST}) do echo "${MEMBER}" | sed 's/uid=/member: uid=/' >> ${LDAP_MODIFY_ADD} done echo -e "\n...Add users on ${LDAP_SERVER} on groupOfNames ${LDAP_GROUP_DN},${LDAP_DN_BASE}:" ldapmodify -D uid=${LDAP_ADMIN_UID},${LDAP_DN_USER_BRANCH},${LDAP_DN_BASE} -w ${LDAP_ADMIN_PASS} -H ${LDAP_SERVER} -x -f ${LDAP_MODIFY_ADD} [ $? -ne 0 ] && ERROR_CODE_MODIFY_2=1 OLDIFS=$IFS IFS=$'\n' for MODIF_LINE in $(cat ${LDAP_MODIFY_ADD}) do echo -e "\t${MODIF_LINE}" done IFS=$OLDIFS fi # Delete users in groupOfNames if [[ ! -z $(cat ${LDAP_DELETE_LIST}) ]]; then echo "dn: ${LDAP_GROUP_DN},${LDAP_DN_BASE}" > ${LDAP_MODIFY_DELETE} echo "changetype: modify" >> ${LDAP_MODIFY_DELETE} echo "delete: member" >> ${LDAP_MODIFY_DELETE} for MEMBER in $(cat ${LDAP_DELETE_LIST}) do echo "${MEMBER}" | sed 's/uid=/member: uid=/' >> ${LDAP_MODIFY_DELETE} done echo -e "\n...Delete users on ${LDAP_SERVER} on groupOfNames ${LDAP_GROUP_DN},${LDAP_DN_BASE}:" ldapmodify -D uid=${LDAP_ADMIN_UID},${LDAP_DN_USER_BRANCH},${LDAP_DN_BASE} -w ${LDAP_ADMIN_PASS} -H ${LDAP_SERVER} -x -f ${LDAP_MODIFY_DELETE} [ $? -ne 0 ] && ERROR_CODE_MODIFY_1=1 OLDIFS=$IFS IFS=$'\n' for MODIF_LINE in $(cat ${LDAP_MODIFY_DELETE}) do echo -e "\t${MODIF_LINE}" done IFS=$OLDIFS fi # Processing error code if [ ${ERROR_CODE_MODIFY_1} -ne 0 ] || [ ${ERROR_CODE_MODIFY_2} -ne 0 ] || [ ${ERROR_CODE_MODIFY_4} -ne 0 ]; then error "Error while applying LDAP groupOfNames modifications on ${LDAP_SERVER}.\nPlease verify LDAP connection parameters and result." fi # Modifications on user attribute if [[ ${SYNC_MEMBEROF} = "yes" ]] then echo -e "\n**********************************************************************" echo -e " Add or delete attribute ${SYNC_MEMBEROF_ATTRIBUTE}" echo -e "**********************************************************************\n" mkdir -p ${LDIF_ATTRIBUTE_MOD_FOLDER} # Export actual user with attribute ${SYNC_MEMBEROF_ATTRIBUTE}: ${LDAP_GROUP_DN},${LDAP_DN_BASE} ldapsearch -LLL -x -H ${LDAP_SERVER} -D uid=${LDAP_ADMIN_UID},${LDAP_DN_USER_BRANCH},${LDAP_DN_BASE} -w ${LDAP_ADMIN_PASS} -b ${LDAP_DN_USER_BRANCH},${LDAP_DN_BASE} ${SYNC_MEMBEROF_ATTRIBUTE}=${LDAP_GROUP_DN},${LDAP_DN_BASE} | grep uid= | sed 's/dn: //' >> ${ACTUAL_USER_WITH_ATTRIBUTE} # Calculation of changes to be applied on users grep -vf ${ACTUAL_USER_WITH_ATTRIBUTE} ${COMPLETE_USER_DN} > ${LDAP_ADD_MEMBEROF} grep -vf ${COMPLETE_USER_DN} ${ACTUAL_USER_WITH_ATTRIBUTE} > ${LDAP_DELETE_MEMBEROF} echo -e "Add or delete attribute '${SYNC_MEMBEROF_ATTRIBUTE}: ${LDAP_GROUP_DN},${LDAP_DN_BASE}' to users on ${LDAP_SERVER}" [[ -z $(cat ${LDAP_ADD_MEMBEROF}) ]] && [[ -z $(cat ${LDAP_DELETE_MEMBEROF}) ]] && echo -e "-> Nothing to sync at this step!" && alldone 0 # Add attribute ${SYNC_MEMBEROF_ATTRIBUTE} to users if [[ ! -z $(cat ${LDAP_ADD_MEMBEROF}) ]] then for USER_DN in $(cat ${LDAP_ADD_MEMBEROF}) do USER_UID=$(echo ${USER_DN} | awk -F'=' '{print $2}' | awk -F',' '{print $1}') # Opening LDIF file LDIF_FILE=${LDIF_ATTRIBUTE_MOD_FOLDER}/${USER_UID}.ldif echo "dn: ${USER_DN}" > ${LDIF_FILE} echo "changetype: modify" >> ${LDIF_FILE} echo "add: ${SYNC_MEMBEROF_ATTRIBUTE}" >> ${LDIF_FILE} echo "${SYNC_MEMBEROF_ATTRIBUTE}: ${LDAP_GROUP_DN},${LDAP_DN_BASE}" >> ${LDIF_FILE} echo -e "...Add: '${SYNC_MEMBEROF_ATTRIBUTE}: ${LDAP_GROUP_DN},${LDAP_DN_BASE}' to '${USER_DN}'" done fi # Delete attribute ${SYNC_MEMBEROF_ATTRIBUTE} to users if [[ ! -z $(cat ${LDAP_DELETE_MEMBEROF}) ]] then for USER_DN in $(cat ${LDAP_DELETE_MEMBEROF}) do USER_UID=$(echo ${USER_DN} | awk -F'=' '{print $2}' | awk -F',' '{print $1}') # Opening LDIF file LDIF_FILE=${LDIF_ATTRIBUTE_MOD_FOLDER}/${USER_UID}.ldif echo "dn: ${USER_DN}" > ${LDIF_FILE} echo "changetype: modify" >> ${LDIF_FILE} echo "delete: ${SYNC_MEMBEROF_ATTRIBUTE}" >> ${LDIF_FILE} echo "${SYNC_MEMBEROF_ATTRIBUTE}: ${LDAP_GROUP_DN},${LDAP_DN_BASE}" >> ${LDIF_FILE} echo -e "...Delete: '${SYNC_MEMBEROF_ATTRIBUTE}: ${LDAP_GROUP_DN},${LDAP_DN_BASE}' to '${USER_DN}'" done fi # Processing find ${LDIF_ATTRIBUTE_MOD_FOLDER} -type f -name "*.ldif" >> ${LDIF_ATTRIBUTE_MOD_LIST} if [[ ! -z $(cat ${LDIF_ATTRIBUTE_MOD_LIST}) ]] then for LDIF_MODIF_FILE in $(cat ${LDIF_ATTRIBUTE_MOD_LIST}) do ldapmodify -D uid=${LDAP_ADMIN_UID},${LDAP_DN_USER_BRANCH},${LDAP_DN_BASE} -w ${LDAP_ADMIN_PASS} -H ${LDAP_SERVER} -x -f ${LDIF_MODIF_FILE} if [ $? -ne 0 ] then ERROR_CODE_MODIFY_3=1 echo $(basename ${LDIF_MODIF_FILE}) | sed 's/.ldif//' >> ${LDIF_ERROR_APPLY} fi done fi # Processing error code if [ ${ERROR_CODE_MODIFY_3} -ne 0 ] then error "Error while applying LDAP modifications on ${LDAP_SERVER} for users:\n$(cat ${LDIF_ERROR_APPLY} | perl -p -e 's/\n/ - /g' | awk 'sub( "...$", "" )')\nPlease verify LDAP connection parameters and result for these users." fi echo -e "-> OK" fi alldone 0
Vous pouvez l’installer sur votre serveur, une fois positionné dans le dossier de votre choix, au moyen d’un simple :
wget --no-check-certificate https://raw.github.com/yvangodard/ldapSync/master/ldapSync.sh ; sudo chmod +x ldapSync.sh
Comme l’expliquais Yoann Gini sur son blog, ce script ce va se servir du service d’annuaire local au Mac pour retrouver la liste des membres d’un groupe (ici equipe) puis il va générer les noms uniques LDAP de chaque utilisateur en considérant que tout utilisateur de l’OD à un DN de type uid=username,cn=users,dc=… . Une fois la liste d’utilisateur récupérée, le script s’occupe de la comparer avec ce qui existe déjà dans la liste de membres du groupe LDAP (ici dans notre exemple cn=workgroup,ou=lists…) et va générer un fichier ldif qui contiendra les entrées à ajouter et un fichier ldif qui contiendra les entrées à supprimer du groupe final.
La syntaxe pour l’utilisation simple de ce script est la suivante :
./ldapSync.sh [-h] | -d <LDAP base namespace> -p <LDAP Admin password> -g <relative DN of LDAP GroupOfNames> -G <OD Groupname>
… soit dans notre exemple :
./ldapSync.sh -d dc=serveur,dc=myoffice,dc=com -p password -g cn=equipe,ou=lists -G workgroup
Vous pouvez appeler les options suivantes selon votre cas de figure :
-a <UID de l'administrateur LDAP>
: UID de l’utilisateur ayant les droits read/write sur le groupe (par défaut, si vous n’appelez pas cette option : ‘diradmin
‘)-s <URL du serveur LDAP>
; : URL du serveur LDAP (par défaut, si vous n’appelez pas cette option : ‘ldap://127.0.0.1
‘)-u <DN relatif de la branche Users>
: DN relatif de la branche contenant vos utilisateurs (par exemple : ‘cn=allusers
‘, par défaut, si vous n’appelez pas cette option : ‘cn=users
‘)-S <répertoire de recherche DSCL>
: répertoire de recherche DSCL pour le groupe OpenDirectory (par défaut, si vous n’appelez pas cette option : ‘/Search
‘)-e <option de rapport par email>
: paramètre d’envoi de rapport d’exécution par email, soit ‘onerror
‘, ‘forcemail
‘ ou ‘nomail
‘ (par défaut : ‘nomail
‘)-E <adresse email pour les rapports>
: adresse email à laquelle sera envoyé le rapport. Cette option doit être utilisée si les options ‘-e forcemail
‘ ou ‘-e onerror
‘ sont utilisées-j <fichier journal>
: cette option active la journalisation à la place de la sortie standard. Spécifiez en argument le chemin du fichier de log (par exemple : ‘/var/log/ldapSync.log
‘) ou utilisez ‘default
‘ (/var/log/ldapSync.log
)
Si vous souhaitez consulter l’aide associée au script :
./ldapSync.sh help
Je vous renvoie à l’article de Yoann Gini qui propose une méthode pour sécuriser un peu l’utilisation de ce script en donnant des droits particuliers à un utilisateur sur le groupe, pour éviter de devoir utiliser le compte administrateur de l’OpenDirectory dans vos entrées launchd
.
Etape 4 : modifiez le schéma de votre LDAP en ajoutant un attribut alternatif au memberOf
Si, comme moi, vous souhaitez ajouter un attribut de type memberOf
sur vos entrées ‘User
‘ de votre LDAP pour marquer directement leur appartenance à un groupOfNames
, il vous faudra préalablement modifier légèrement la structure de votre LDAP. En effet, l’attribut memberOf
existe bien dans le schéma de l’OpenDirectory, mais il s’agit d’un attribut opérationnel dans OpenLDAP, ce qui signifie que nous ne pouvons pas le rendre accessible à une classe d’utilisateurs.
Ma méthode est donc ici un peu workaround, puisque j’ajoute au schéma de notre LDAP un nouveau type d’attribut (ici ‘resMemberOf
‘) et une classe d’objet (ici ‘resPerson
‘) qui autorise ce nouvel attribut. Cela implique donc d’étendre le schéma du LDAP fourni par l’OpenDirectory.
Pour cela, créer un fichier au format .ldif contenant les modifications à apporter (ici ce fichier sera : /var/root/ldap_modif.ldif
) :
dn: cn=reslr,cn=schema,cn=config objectClass: olcSchemaConfig cn: reslr olcAttributeTypes: ( 1.3.6.1.4.1.43016.2.3.1 NAME 'resMemberOf' DESC 'Distinguished name of a group of which the object is a member' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 ) olcObjectClasses: ( 1.3.6.1.4.1.43016.2.4.1 NAME 'resPerson' DESC 'reseauenscene.fr person' AUXILIARY MAY ( resMemberOf ) )
Pensez à personnaliser :
- le CN de cette configuration (ici
cn=reslr
, en ligne 1 et 3) - le nom un nouveau type d’attribut (ici
resMemberOf
) et une classe d’objet (iciresPerson
) - les OID (1.3.6.1.4.1.43016.2.3.1 et 1.3.6.1.4.1.43016.2.4.1), car ceux que j’utilise ici sont composés avec mon propre préfixe OID assigné par l’IANA. Vous devez remplacer
1.3.6.1.4.1.43016
avec votre propre préfixe OID. Si vous ne disposez pas de votre propre numéro, vous pouvez en obtenir un (cela prend à peine quelques jours) en vous enregistrant ici.
Pour appliquer l’ajout dans votre schéma, dans votre Terminal :
ldapadd -H ldap://urldevotreldap -D uid=diradmin,cn=users,dc=.... -w passwordldapadmin -x -f /var/root/ldap_modif.ldif
Vous pouvez ensuite utiliser le Gestionnaire de Groupe de Travail (WorkgroupManager) d’OS X Server, en y activant préalablement les options avancées pour pouvoir accéder à toutes les entrées, pour vérifier que la modification a été correctement apportée. Pour cela, rendez vous dans OLCSchemaConfig
. Vous devriez y voir les modifications apportées comme ici :
Etape 5 : utilisez le script ldapSync pour augmenter les entrées Users
Maintenant que nous disposons d’un attribut alternatif à memberOf
(ici resMemberOf
), il nous faut trouver une méthode pour que les entrées Users
du LDAP fourni par notre OpenDirectory puissent être automatiquement augmentée de cet attribut.
Sur OpenLdap (en environnement GNU/Linux) on utilise traditionnellement le module overlay
pour réaliser cela dynamiquement : à chaque fois qu’un utilisateur est ajouté à un groupe, le module overlay
se charge (s’il est correctement configuré) de marquer l’appartenance à ce groupe dans l’entrée User
avec un attribut de type memberOf
.
Mais, malheureusement, je n’ai pas trouvé de méthode pour activer ce module sur Mac OS X Serveur. D’ailleurs si quelqu’un a une piste pour cela, les commentaires sont là pour ça !
Du coup, j’ai ajouté des fonctions dans le script ldapSync :
-m <sync memberOf>
: cette option, ajoute, en plus de la synchronisation vers ungroupOfNames
l’ajout d’attributs de typememberOf
pour marquer l’appartenance à ce groupe dans l’entrée User. Pour activer cette fonction utilisez ‘-m yes
‘.-M <memberOf attribute>
: attribut à utiliser, par exemple ‘-M resMemberOf
‘ (par défaut, si vous n’appelez pas cette option, l’attribut utilisé sera ‘memberOf
‘).
Si vous souhaitez ainsi utiliser cette fonction supplémentaire, vous pouvez appelez :
./ldapSync.sh -d dc=serveur,dc=myoffice,dc=com -p password -g cn=equipe,ou=lists -G workgroup -m yes -M resMemberOf
Il ne vous reste plus qu’à programmer cette tâche avec une entrée launchd
de manière périodique.