SVN synchronisation groupes LDAP

De EjnTricks

La gestion des droits utilisateurs se base sur les fichiers internes de SVN. La mise en place de l'authentification est décrite dans l'article SVN authentification LDAP. Or comment faire pour que les comptes déclarés dans l'annuaire LDAP soit automatiquement repris dans ces fichiers ? Il n'y a malheureusement pas de solution miracle et il est nécessaire de compléter le fichier conf/authz, pour une installation sur Ubuntu, pour donner la composition des groupes ainsi que leur droit d'accès.

Heureusement, une personne a bien voulu écrire un petit script pour faciliter la tache. Les deux pages suivantes ont été consultées pour la mise en place de cette synchronisation:

Hand-icon.png Votre avis

Current user rating: 92/100 (4 votes)

 You need to enable JavaScript to vote


Command-icon.png Script

Le script nécessite une connexion à l'annuaire LDAP. Etant écrit en python, il est nécessaire d'installer le package python-ldap, avec la traditionnelle commande apt-get sous Ubuntu:

#sudo apt-get install python-ldap
Lecture des listes de paquets... Fait
Construction de l'arbre des dépendances
Lecture des informations d'état... Fait
Paquets suggérés :
  python-ldap-doc
Les NOUVEAUX paquets suivants seront installés :
  python-ldap
0 mis à jour, 1 nouvellement installés, 0 à enlever et 0 non mis à jour.
Il est nécessaire de prendre 87,7 ko dans les archives.
Après cette opération, 434 ko d'espace disque supplémentaires seront utilisés.
Réception de : 1 http://fr.archive.ubuntu.com/ubuntu/ oneiric/main python-ldap amd64 2.3.13-1 [87,7 kB]
87,7 ko réceptionnés en 0s (323 ko/s)
Sélection du paquet python-ldap précédemment désélectionné.
(Lecture de la base de données... 145590 fichiers et répertoires déjà installés.)
Dépaquetage de python-ldap (à partir de .../python-ldap_2.3.13-1_amd64.deb) ...
Paramétrage de python-ldap (2.3.13-1) ...

Source

Les sources originales sont:

Process-Icon.png Utilisation

L'utilisation de ce script est assez simple. Plusieurs paramètres sont obligatoires, d'autre optionnels dans la ligne de commande:

Argument court Argument long Valeur
-h --help Affiche l'aide en ligne du script.
-d --bind-dn Compte utilisateur utilisé pour se connecter à l'annuaire LDAP lors de la recherche des groupes.
-p --bind-password Le mot de passe pour le compte d'accès à l'annuaire LDAP.
-l --url L'URL pour accéder à l'annuaire, de type scheme://hostname:port.
-b --base-dn Contient l'OU racine à partir de laquelle les recherches de groupe seront effectuées.
-g --group-query Contient le filtre de recherche des groupes à synchroniser.
-m --group-member-attribute Le nom de l'attribut contenant la référence aux utilisateurs.

La valeur par défaut member est opérationnel dans le cadre d'une synchronisation sur OpenLDAP.

-u --user-query Requête permettant de rechercher les utilisateurs une fois identifié dans les groupes retournés par la recherche.

Le filtre par défaut est objectClass=user. Mais ceci n'est pas trop pertinent, la classe des utilisateurs n'étant pas forcément user.

-i --userid_attribute L'attribut utilisé pour récupérer l'identifiant de la personne. Cette valeur sera placée dans la composition des gorupes au niveau du fichier authz de SVN.

La valeur par défaut est cn, mais cela va placer l'id complet de l'annuaire LDAP.

-c --cacert-path Utiliser dans le cadre de connexion sur un annuaire sécurisé. Contient l'emplacement du certificat.
-z --authz-path Permet de spécifier le fichier cible pour la génération de la composition des groupes.

Si il n'est pas fourni, le résultat est écrit dans la console. Il est alors possible de rediriger la console vers un fichier.

-q --quiet Mode silence.

L'exemple suivant permet de synchroniser les groupes avec les paramètres:

Paramètre Valeur Description
-d cn=admin,dc=ejnserver,dc=fr Compte administrateur pour interroger l'annuaire.
-p <MOT DE PASSE ADMIN> Remplacer par le mot de passe du compte fourni pour l'argument d.
-l ldap://localhost:389 URL d'accès à l'annuaire. Le script est exécuté sur le même serveur que l'annuaire.
-b ou=groups,dc=ejnserver,dc=fr La recherche des groupes s'effectue sous l'OU ou=groups,dc=ejnserver,dc=fr.
-g (&(objectClass=groupOfNames)(cn=svn-repos1*)) Seuls les groupes de class groupOfNames et dont le nom commence par svn-repos1 sont synchronisés.
-u (objectClass=*) Le filtre de recherche sur les utilisateur est obligatoire. Si il n'est pas fourni aucun utilisateur ne sera synchronisé. Ce type de filtre permet de prendre en compte tous les utilisateurs référencés dans les groupes. En effet, chaque entrée a forcément un attribut de nom objectClass.
-i uid La valeur de l'attribut uid sera utilisée pour identifier les utilisateurs.
-z /tmp/authz Le résultat sera stocké dans le fichier /tmp/authz
#python sync_ldap_groups_to_svn_authz.py \
-d "cn=admin,dc=ejnserver,dc=fr"  \
-p "<MOT DE PASSE>"  \
-l "ldap://localhost:389"  \
-b "ou=groups,dc=ejnserver,dc=fr"  \
-g "(&(objectClass=groupOfNames)(cn=svn-repos1*))"  \
-u "(objectClass=*)"  \
-i "uid"  \
-z /tmp/authz

Le résultat, dans le fichier /tmp/authz est le suivant:

[groups]

### Start generated content: LDAP Groups to Subversion Authz Groups Bridge (2011/11/12 12:08:58) ###
svnrepos1contrib = etienne
svnrepos1reader = etienne

################################################################################
###########   LDAP Groups to Subversion Authz Groups Bridge (Legend)  ##########
################################################################################
### svnrespos1contrib = cn=svn-repos1-contrib,ou=groups,dc=ejnserver,dc=fr
### svnrepos1reader = cn=svn-repos1-reader,ou=groups,dc=ejnserver,dc=fr
################################################################################

### End generated content: LDAP Groups to Subversion Authz Groups Bridge ###

A noter les tirets dans les noms des groupes est supprimé au niveau des noms des groupes SVN. Seule la section groups est créé. Mais dans le cadre de l'intégration dans un SVN, le fichier à créer ne doit pas être dans /tmp, mais au niveau du fichier authz de chacun des repositories. La commande sera alors du type, pour un repository repos1:

#python sync_ldap_groups_to_svn_authz.py \
-d "cn=admin,dc=ejnserver,dc=fr"  \
-p "<MOT DE PASSE>"  \
-l "ldap://localhost:389"  \
-b "ou=groups,dc=ejnserver,dc=fr"  \
-g "(&(objectClass=groupOfNames)(cn=svn-repos1*))"  \
-u "(objectClass=*)"  \
-i "uid"  \
-z /var/opt/svn/repos1/conf/authz

Si le fichier existe déjà, il sera complété.

Update icon.png Patch

Lors de l'exécution, un fichier temporaire est créé dans le répertoire /tmp sur une machine Linux. Or il est fréquent que le répertoire /tmp ne soit pas monté sur le même filesystem que l'emplacement de SVN, par exemple /var/opt/svn sur une machine Ubuntu. Or lors de la copie du fichier temporaire vers le fichier cible une erreur se produit:

#./run_sync_ldap_groups.sh
Successfully bound to ldap://localhost:389...
2 groups found.
Traceback (most recent call last):
  File "sync_ldap_groups_to_svn_authz.py", line 572, in <module>
    main()
  File "sync_ldap_groups_to_svn_authz.py", line 567, in main
    print_group_model(groups, memberships)
  File "sync_ldap_groups_to_svn_authz.py", line 358, in print_group_model
    os.rename(tmp_authz_path, authz_path)
OSError: [Errno 18] Invalid cross-device link

L'erreur provient de la commande os.rename(tmp_authz_path, authz_path) dans la fonction print_group_model.

Diff

De nombreuses discussions sur ce problème (python) sont disponibles sur internet. Il est souvent question de modifier l'appel à la fonction os.rename par shutil.move. Un fichier diff est fourni à l'adresse suivante: https://bitbucket.org/jcscoobyrs/jw-tools/issue/4/osrename-fails-if-tmp-and-authz-are-on

L'exécution est alors réalisée avec succès:

#./run_sync_ldap_groups.sh
Successfully bound to ldap://localhost:389...
2 groups found.

Source

Le script final est donc le suivant:

Icon-Configuration-Settings.png Configuration

Non présence de la section groups

Comme vu, le script va écrire la section groups dans le fichier authz. Or si celle-ci est déjà présente, elle sera complétée. Il est donc important de vérifier le contenu du fichier original. Par exemple, pour le fichier origine suivant:

### This file is an example authorization file for svnserve.
### Its format is identical to that of mod_authz_svn authorization
### files.
### As shown below each section defines authorizations for the path and
### (optional) repository specified by the section name.
### The authorizations follow. An authorization line can refer to:
###  - a single user,
###  - a group of users defined in a special [groups] section,
###  - an alias defined in a special [aliases] section,
###  - all authenticated users, using the '$authenticated' token,
###  - only anonymous users, using the '$anonymous' token,
###  - anyone, using the '*' wildcard.
###
### A match can be inverted by prefixing the rule with '~'. Rules can
### grant read ('r') access, read-write ('rw') access, or no access
### ('').

[aliases]
# joe = /C=XZ/ST=Dessert/L=Snake City/O=Snake Oil, Ltd./OU=Research Institute/CN=Joe Average

[groups]
# harry_and_sally = harry,sally
# harry_sally_and_joe = harry,sally,&joe

# [/foo/bar]
# harry = rw
# &joe = r
# * =

# [repository:/baz/fuz]
# @harry_and_sally = rw
# * = r

[/]
@svnrepos1contrib = rw
@svnrepos1reader = r
* =

La section groups est déjà présente. Elle sera donc complétée et le résultat donnera la fichier suivant:

### This file is an example authorization file for svnserve.
### Its format is identical to that of mod_authz_svn authorization
### files.
### As shown below each section defines authorizations for the path and
### (optional) repository specified by the section name.
### The authorizations follow. An authorization line can refer to:
###  - a single user,
###  - a group of users defined in a special [groups] section,
###  - an alias defined in a special [aliases] section,
###  - all authenticated users, using the '$authenticated' token,
###  - only anonymous users, using the '$anonymous' token,
###  - anyone, using the '*' wildcard.
###
### A match can be inverted by prefixing the rule with '~'. Rules can
### grant read ('r') access, read-write ('rw') access, or no access
### ('').

[aliases]
# joe = /C=XZ/ST=Dessert/L=Snake City/O=Snake Oil, Ltd./OU=Research Institute/CN=Joe Average

[groups]
# harry_and_sally = harry,sally
# harry_sally_and_joe = harry,sally,&joe

# [/foo/bar]
# harry = rw
# &joe = r
# * =

# [repository:/baz/fuz]
# @harry_and_sally = rw
# * = r

[/]
@svnrepos1contrib = rw
@svnrepos1reader = r
* =

### Start generated content: LDAP Groups to Subversion Authz Groups Bridge (2011/11/12 13:48:54) ###
svnrepos1contrib = etienne
svnrepos1reader = etienne

################################################################################
###########   LDAP Groups to Subversion Authz Groups Bridge (Legend)  ##########
################################################################################
### svnrepos1contrib = cn=svn-repos1-contrib,ou=groups,dc=ejnserver,dc=fr
### svnrepos1reader = cn=svn-repos1-reader,ou=groups,dc=ejnserver,dc=fr
################################################################################

### End generated content: LDAP Groups to Subversion Authz Groups Bridge ###

Donc, les compositions des groupes ne sont pas situées au bon endroit. Il faut impérativement que cette section ne soit pas disponible dans le fichier original. La composition doit donc être:

### This file is an example authorization file for svnserve.
### Its format is identical to that of mod_authz_svn authorization
### files.
### As shown below each section defines authorizations for the path and
### (optional) repository specified by the section name.
### The authorizations follow. An authorization line can refer to:
###  - a single user,
###  - a group of users defined in a special [groups] section,
###  - an alias defined in a special [aliases] section,
###  - all authenticated users, using the '$authenticated' token,
###  - only anonymous users, using the '$anonymous' token,
###  - anyone, using the '*' wildcard.
###
### A match can be inverted by prefixing the rule with '~'. Rules can
### grant read ('r') access, read-write ('rw') access, or no access
### ('').

[aliases]
# joe = /C=XZ/ST=Dessert/L=Snake City/O=Snake Oil, Ltd./OU=Research Institute/CN=Joe Average

#[groups]
# harry_and_sally = harry,sally
# harry_sally_and_joe = harry,sally,&joe

# [/foo/bar]
# harry = rw
# &joe = r
# * =

# [repository:/baz/fuz]
# @harry_and_sally = rw
# * = r

[/]
@svnrepos1contrib = rw
@svnrepos1reader = r
* =

Icon ACL.pngDroits du fichier

Lorsque le script est exécuté avec un compte root, les permissions sur ce fichier (pour une machine Ubuntu) sont les suivantes:

drw-r----- 2 www-data subversion 4096 2011-11-12 13:52 ./
drw-r----- 6 www-data subversion 4096 2011-10-22 18:35 ../
-rw------- 1 root     subversion 1082 2011-11-12 13:52 authz
-rw-r----- 1 root     subversion 1130 2011-11-12 13:47 authz.bak
-rw-r----- 1 www-data subversion  330 2011-10-22 18:38 passwd
-rw-r----- 1 www-data subversion 2277 2011-10-22 18:41 svnserve.conf

Les droits ne sont donc pas suffisant et les programme, type WebSVN ou ViewVC, ne pourront pas lire les fichiers. Il est donc nécessaire de modifier le propriété et les droits d'accès avec un commande du type:

#chmod 640 /var/opt/svn/repos1/conf/authz
#chown www-data:subversion /var/opt/svn/repos1/conf/authz

Scheduled-Tasks-icon.png Planification

Cette étude permet donc d'avoir un outil qui va synchroniser les groupes LDAP dans les configurations SVN. Afin d'exécuter ce script périodiquement, tous les jours par exemple, il suffit de placer un script (ou un lien) dans la planification de anacron.

Dans le cadre de cette étude, le script python et le shell run_sync_ldap_groups.sh ont été placés dans le répertoire /var/opt/ldap_svn. Le shell permet d'exécuter le script python pour chacun des repository mis en place et nécessaitant une synchronisation. Pour les repository repos1 et repos2, son contenu est le suivant:

#!/bin/sh

pwd="<MOT PASSE ADMIN>"
bindDn="cn=admin,dc=ejnserver,dc=fr"
ldapUrl="ldap://localhost:389"
baseDn="ou=groups,dc=ejnserver,dc=fr"
userQuery="(objectClass=*)"
userAttr="uid"

python /var/opt/ldap_svn/sync_ldap_groups_to_svn_authz.py \
-d "$bindDn"  \
-p "$pwd"  \
-l "$ldapUrl"  \
-b "$baseDn"  \
-g "(&(objectClass=groupOfNames)(cn=svn-repos1*))"  \
-u "$userQuery"  \
-i "$userAttr"  \
-z /var/opt/svn/repos1/conf/authz

chmod 640 /var/opt/svn/repos1/conf/authz
chown www-data:subversion /var/opt/svn/repos1/conf/authz

python /var/opt/ldap_svn/sync_ldap_groups_to_svn_authz.py \
-d "$bindDn"  \
-p "$pwd"  \
-l "$ldapUrl"  \
-b "$baseDn"  \
-g "(&(objectClass=groupOfNames)(cn=svn-repos2*))"  \
-u "$userQuery"  \
-i "$userAttr"  \
-z /var/opt/svn/repos2/conf/authz

chmod 640 /var/opt/svn/repos2/conf/authz
chown www-data:subversion /var/opt/svn/repos2/conf/authz

Pour la planification journalière, un simple lien est mis dans le répertoire /etc/cron.daily:

#sudo ln -s /var/opt/ldap_svn/run_sync_ldap_groups.sh /etc/cron.daily/run_sync_ldap_groups