Accueil > Divers (et d’été) > Informatique > Autour des produits Microsoft > Active directory > Charger le contenu de votre Active Directory dans une base Postgresql
Charger le contenu de votre Active Directory dans une base Postgresql
dimanche 13 décembre 2020, par
Le langage SQL est plus courant que les requêtes LDIF et beaucoup plus puissant que les outils des clickodromes Microsoft.
Le script shell ci-dessous permet de charger l’intégralité d’un annuaire Active Directory dans une telle base de données.
- #!/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é"

