#!/bin/sh # ImportActiveDirectory # # (c) 2020 Paul Courbis https://www.courbis.fr # # Pas de redistribution sans autorisation # Ce script créé une table full_ad dans un serveur Postgres contenant # le contenu de l'Active Directory sous la forme de 4 champs : # # dn DN de l'objet # codage clear si la valeur est en clair # base64 si la valeur est encodée en base64 # key nom de l'attribut # value valeur de l'attribut # # Deux attributs fictifs sont rajoutés : # # father_node DN de l'objet père (pour parcourir l'arbre) # self_node DN de l'objet auquel le DN père est retiré # # Les données du précédent import (si elles existaient) sont conservées # dans la table full_ad_old ce qui permet des comparaisons par exemple # Au premier run, la table full_ad_old existera mais sera vide # Dans un deuxieme temps une table ad_objects est crée dynamiquement # # La clef principale est object_dn qui contient le DN des objets # La table comporte autant de colonnes que d'attributs exitants # dans l'AD. Les champs sont mis à NULL si aucune valeur ne correspond # pour un DN # # Pour chaque champ on trouve en fait une ou deux colonnes : # # -une colonne xxxx qui contient la valeur telle que récupérée # -une colone multi_xxxx typée qui contient les données parsées # sous forme multivaluée pour les champs qui le sont potentiellement # # Ainsi on va trouver un champ dn[1] pour chaque ligne qui contiendra # la première partie du DN de l'objet # FIXME : # - reconnaître d'autres types que les entiers # - faire la distinction entre les vrais entiers et des entiers longs # - le type NUMERIC(20) est à améliorer # - décoder les données base64 subsistantes dans des blobs # - mettre les fichiers temporaires dans /tmp et les détruires en fin # # Nécessite l'installation de : # # - gawk # - ldap-utils # - coreutils # - postgresql . ./Common.sh # Le fichier Common.sh doit contenir les déclaration de : # A défaut commenter la ligne ./Common.sh et renseigner et # décommenter les lignes ci dessous # PATH Path des commandes utilisées #export PATH="/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games:/opt/mssql-tools/bin/" # LDAP_SRV url du serveur LDAP ou LDAP-SSL ldap://xxxx.xxxx #export LDAP_SRV=ldaps://dc.acme.com # ROOT_DN DN de la racine de l'ad #export ROOT_DN="DC=acme,DC=local" # LDAP_USER DN de l'utilisateur habilité à requêter l'AD #export LDAP_USER="CN=Super admin,OU=Big Chiefs, DC=acme,DC=local" # LDAP_PASS Mot de passe de l'utilisateur habilité à requêter l'AD #export LDAP_PASS="Th4t1sMyP@ssw0rd!" # PGDATABASE Nom de la base Postgres (doit être créée en mode UTF-8) #export PGDATABASE="active_directory" # PGHOST Optionnel - Hôte du serveur Postgres #export PGHOST="127.0.0.1" # PGPORT Optionnel - Port de connexion #export PGPORT="5432" # PGUSER Optionnel - Utilisateur de connexion #export PGUSER="postgres" # Le cas échéant mettre le mot de passe dans ~/.pgpass (voir man psql) # Raccourcis pour interroger la base Postgres() { echo "$@;" | sed 's/; *;$/;/g' | psql -A -t | tr '|' '\t' } echo "Récuparation du ldif..." ldapsearch -o ldif-wrap=no -LLL -H "$LDAP_SRV" -b "$ROOT_DN" -D "$LDAP_USER" -w "$LDAP_PASS" 'objectclass=*' -E pr=2147483647/noprompt > full_ad.ldif echo "Décodage..." cat full_ad.ldif | sed 's/#.*//g' | grep -v '^$' | sed 's/\(::*\) */\t\1\t/' | awk -F ' ' ' BEGIN { DN=""; CONTENT="" } # Decodage Base64 function decoder( valeur ) { cmd = "echo '"'"'" valeur "'"'"' | base64 -d" cmd | getline result close( cmd ) return result } # Le DN sera décodé si nécessaire et stocké pour servir de clé à chaque attribut qui suit /^dn\t::\t/ { $3=decoder( $3 ); $2=":"; DN=$3 } /^dn\t:\t/ { DN=$3 } # Autres champs devant être décodés /^cn\t::\t/ { $3=decoder( $3 ); $2=":" } /^sn\t::\t/ { $3=decoder( $3 ); $2=":" } /^givenName\t::\t/ { $3=decoder( $3 ); $2=":" } /^title\t::\t/ { $3=decoder( $3 ); $2=":" } /^distinguishedName\t::\t/ { $3=decoder( $3 ); $2=":" } /^displayName\t::\t/ { $3=decoder( $3 ); $2=":" } /^memberOf\t::\t/ { $3=decoder( $3 ); $2=":" } /^member\t::\t/ { $3=decoder( $3 ); $2=":" } /^sAMAccountName\t::\t/ { $3=decoder( $3 ); $2=":" } /^ou\t::\t/ { $3=decoder( $3 ); $2=":" } /^description\t::\t/ { $3=decoder( $3 ); $2=":" } /^showInAddressBook\t::\t/ { $3=decoder( $3 ); $2=":" } /^protocolSettings\t::\t/ { $3=decoder( $3 ); $2=":" } /^name\t::\t/ { $3=decoder( $3 ); $2=":" } /^comment\t::\t/ { $3=decoder( $3 ); $2=":" } /^altRecipientBL\t::\t/ { $3=decoder( $3 ); $2=":" } /^department\t::\t/ { $3=decoder( $3 ); $2=":" } /^mail\t::\t/ { $3=decoder( $3 ); $2=":" } /^managedBy\t::\t/ { $3=decoder( $3 ); $2=":" } /^managedObjects\t::\t/ { $3=decoder( $3 ); $2=":" } /^publicDelegates\t::\t/ { $3=decoder( $3 ); $2=":" } /^msExchCoManagedByLink\t::\t/ { $3=decoder( $3 ); $2=":" } /^msExchDelegateListBL\t::\t/ { $3=decoder( $3 ); $2=":" } /^msExchCoManagedObjectsBL\t::\t/ { $3=decoder( $3 ); $2=":" } /^msExchDelegateListLink\t::\t/ { $3=decoder( $3 ); $2=":" } /^msExchOABGeneratingMailboxBL\t::\t/ { $3=decoder( $3 ); $2=":" } /^msExchUserBL\t::\t/ { $3=decoder( $3 ); $2=":" } # Pour tous les attributs on sort # # Le DN # Le codage # La clé (ie: le nom de cet attribut) # Sa valeur { printf( "%s\t%s\t%s\t%s\n", DN, $2, $1, $3 ); } ' > full_ad.txt # Création des requêtes SQL d'import cat full_ad.txt | sed "s/'/''/g" | sed 's/\(.*\)\t\(.*\)\t\(.*\)\t\(.*\)$/insert into full_ad_new values ( '"'"'\1'"'"', '"'"'\2'"'"', '"'"'\3'"'"', '"'"'\4'"'"' );/' > full_ad.sql echo "Chargement en base..." ( cat << EOF -- Creation de la table si nécessaire create table if not exists full_ad ( dn text, codage text, key text, value text ) with oids; -- Destruction de la table d'inport si elle existe -- On importe dans une table temporaire pour minimiser -- le temps d'indisponibilité des données drop table if exists full_ad_new; -- Creation de la table d'inmport sur le modèle de la -- table de production select * into full_ad_new from full_ad where 1=2; EOF ) | psql # Importation des données cat full_ad.sql | psql | grep -v INSERT ( cat << EOF -- Mise en clair du type de codage du champ value update full_ad_new set codage = 'clear' where codage = ':'; update full_ad_new set codage = 'base64' where codage = '::'; -- Ajout d'un pseudo champ father_node pour permettre de parcourir l'arbre drop table if exists fathers_tempo; select distinct dn, codage, 'father_node', substring( dn from position(',' in dn)+1 for length(dn) - position(',' in dn) ) into fathers_tempo from full_ad_new where position(',' in dn) > 0 and codage = 'clear'; insert into full_ad_new (select * from fathers_tempo); drop table if exists fathers_tempo; -- AJout d'un pseudo champ self_node pour avoir le nom de l'objet drop table if exists self_tempo; select distinct dn, codage, 'self_node', substring( dn from 1 for position(',' in dn)-1 ) into self_tempo from full_ad_new where position(',' in dn) > 0 and codage = 'clear'; insert into full_ad_new (select * from self_tempo); drop table if exists self_tempo; -- Creation des nouveaux index drop index if exists full_ad_i1_new; drop index if exists full_ad_i2_new; create index full_ad_i1_new on full_ad_new ( dn ); create index full_ad_i2_new on full_ad_new ( dn, key ); vacuum FULL VERBOSE ANALYZE full_ad_new; EOF ) | psql ################################################################# ################## Création table dénormalisée ################## ################################################################# Postgres "drop table if exists ad_objects_new;" echo -n "Creation de la table pivot" >&2 ( echo "create table ad_objects_new" echo "(" echo -n " object_dn text" Postgres "select key, max(position ( ',' in value)) from full_ad_new group by key order by key;" | while read key multi do echo "," FIELD=`echo $key | tr '-' '_'` ISNUMERIC=`Postgres "select distinct REGEXP_REPLACE( REPLACE( value, '_', '' ), '^[0-9][0-9]*$', '__INTEGER__') from full_ad_new where key = '$key'" | grep -v '^__INTEGER__$' | wc -l` TYPE="text" if [ "$ISNUMERIC" = "0" ] then TYPE="numeric(20)" fi echo -n " $FIELD $TYPE default null" if [ "$multi" != 0 ] then echo "," echo -n " multi_$FIELD text[] default null" fi echo -n "." >&2 done echo echo ") with oids;" ) | psql echo >&2 Postgres "insert into ad_objects_new ( object_dn ) (select distinct dn from full_ad_new );" echo -n "Remplissage de la table pivot" >&2 Postgres "select key, max(position ( ',' in value)) from full_ad_new group by key order by key;" | while read key multi do FIELD=`echo $key | tr '-' '_'` ISNUMERIC=`Postgres "select distinct REGEXP_REPLACE( REPLACE( value, '_', '' ), '^[0-9][0-9]*$', '__INTEGER__') from full_ad_new where key = '$key'" | grep -v '^__INTEGER__$' |wc -l` VALUE="f.value" if [ "$ISNUMERIC" = "0" ] then VALUE="cast( f.value as numeric(20))" fi Postgres "update ad_objects_new o set $FIELD = $VALUE from full_ad_new f where o.object_dn = f.dn and f.key = '$key';" | grep -v UPDATE if [ "$multi" != 0 ] then Postgres "update ad_objects_new o set multi_$FIELD = string_to_array(f.value, ',') from full_ad_new f where o.object_dn = f.dn and f.key = '$key';" | grep -v UPDATE fi echo -n "." >&2 done echo "" >&2 Postgres "drop index if exists ad_objects_i1_new;" Postgres "create index ad_objects_i1_new on ad_objects_new (object_dn);" Postgres "vacuum FULL VERBOSE ANALYZE ad_objects_new;" ################################################################# ################################################################# ################################################################# echo "Renommage des tables temporaires" TABLES="full_ad ad_objects" # Nettoyage des anciennes tables for table in $TABLES do Postgres "drop table if exists $table""_old" done # Le renommage des tables est fait dans une transaction ( echo "begin;" for table in $TABLES do echo "alter table if exists $table rename to $table""_old;" echo "alter table if exists $table""_new rename to $table;" done echo "commit;" ) | psql # Renommage des index for table in $TABLES do echo '\d '"$table" | psql | sed '1,/^Indexes:/d' | grep -v '^$' | sed 's/^ *"//g' | sed 's/".*//g' | while read index do CUR=`echo "$index" | sed 's/_new$//g'` OLD=`echo "$index" | sed 's/_new$/_old/g'` Postgres "drop index if exists $OLD" Postgres "alter index if exists $CUR rename to $OLD" Postgres "alter index if exists $index rename to $CUR" done done echo "Terminé"