Un petit post pour donner un retour sur l’installation et administration de Trac. Ce projet propose d’habiller un dépôt subversion avec un wiki, une gestion de tickets etc. Le tout est réalisé en python (chouette une application web sans PHP). Dans un cadre professionnel, j’ai du mettre en œuvre une solution de gestion de versions pour différents projets. J’ai retenu subversion, trac et apache sous FreeBSD comme briques de cette infrastructure. J’ai choisi FreeBSD car les ports sont très proches des versions courantes des logiciels (et pour des application web c’est important) et surtout Jail est intégré au système de base (stabilité des mises à jour). Au niveau de l’existant, je disposais d’un serveur OpenLDAP (avec les comptes utilisateurs dedans). j’ai décidé de faire mon Jean-Pierre Troll et de n’utiliser aucun des systèmes “hype” de virtualisation (non pas que je les aimes pas, c’est juste que c’était un peu overkill par rapport à mes besoins). La manière de mutualiser le système a directement été dictée par la façon dont sont structurés les systèmes d’informations dans les universités. Petit rappel : chaque université dispose d’un CRI (Centre de Ressources Informatique) qui s’occupe des services mutualisés (mail, web, annuaire et dans notre cas subversion / trac). Ces services sont utilisés par différentes composantes. J’ai donc décidé de dédié un environnement confiné à chaque composante. Un environnement confiné est un Jail FreeBSD. C’est presque comme une machine virtuelle à part qu’on a une seule instance du noyau en exécution (raccourci très très grossier, que les gurus me pardonnent). Il en résulte un confinement un peu plus poreux qu’avec des systèmes comme XEN (para-virtualisation) ou VMWare (virtualisation complète). la mise en œuvre basique d’un Jail FreeBSD est détaillée ici. Je considère donc que vous avez un FreeBSD rutilant, un serveur OpenLDAP qui fonctionne et un Jail dans lequel déployer les instances de Subversion / Trac. Pour toutes les manipulation l’identifiant choisi est le mail et pas le login UNIX (le mail est bien pus générique). Let’s go !
Round 1 : Apache
Apache est LE composant principal de l’installation. C’est lui qui va servir de base pour l’authentification des utilisateurs sur Trac et Subversion. Apache va aussi réaliser l’affichage du site Trac via le mod_python. Nous allons utiliser le module LDAP fourni avec Apache pour l’authentification. un coup de portinstall :
portinstall www/apache22
Ne pas oublier d’activer LDAP dans apr* et l’authentification LDAP dans apache.
portinstall www/mod_python3
portinstall trac
portinstall mod_security
Le mod_security est facultatif. Nous avons vu qu’un Jail était dédié à une composante. Or une composante peut avoir plusieurs activités (recherche, enseignements, valorisation etc.). L’incidence au niveau informatique est que l’on peut avoir des URL différentes pour ces activités. Nous allons donc utiliser les virtualhost Apache. Considérons le virtualhost trac01.garnett.fr (ça change de je te foo sur le bar). Pour l’authentification LDAP, un précédent post donne la démarche. Une fois cette étape validée, on peut passer à la suite. J’exclu volontairement la partie SSL entre le virtualhost et le serveur LDAP pour plus de lisibilité.
Round 2 : Subversion
Toutes les interactions avec le serveur Subversion se feront sur HTTPS. L’accès en HTTPS à un serveur Apache est largement documenté sur Internet. Nous avons donc la configuration suivante :
<Virtualhost *:443>
<Location /svn>
DAV svn
SVNParentPath /usr/local/svn-trac/svn_of_trac01
AuthzSVNAccessFile /usr/local/svn-trac/acls/acl-repo
AuthType Basic
AuthName "SVN"
AuthBasicProvider "ldap"
AuthLDAPURL "ldap://mon_ldap/dc=example,dc=org?mail?sub?(objectClass=inetOrgPerson)"
authzldapauthoritative Off
require valid-user
</Location>
</Virtualhost>
Cette section active DAV (DAV svn) pour la location /svn. Elle donne aussi le répertoire parent des dépôts subversions (SVNParentPath /usr/local/svn-trac/svn_of_trac01). Ce répertoire doit être readable par Apache. Les sous répertoires qui constituent donc les projets doivent donc être readable et writable par Apache. Il reste un détail : les ACLs sur les différents projets (AuthzSVNAccessFile /usr/local/svn-trac/acls/acl-repo). Ce fichier va contenir les différents droits sur les dêpots. Voyons un exemple :
[repo01:/]
nico@bidule.fr = rw
toto@bidule.fr = r
tutu@machin.fr = r
Nico à tous les droits tandis que toto et tutu ont juste la lecture sur la racine du projet repo01 situé dans /usr/local/svn-trac/svn_of_trac01. J’ai décidé de restreindre l’accès a ce fichier en chrootant un utilisateur dédié dans le répertoire /usr/local/svn-trac/acls. Très simple, il faut ajouter dans /etc/ssh/sshd_config :
[...]
AllowUsers mon_user
[...]
Subsystem sftp internal-sftp
[...]
Match User mon_user
ForceCommand internal-sftp
ChrootDirectory /usr/local/svn-trac/acls
Attention aux espaces dans le fichier de configuration, notamment sur le “ForceCommand internal-sftp”. Le moindre espace terminal fait merder le tout. Pour le chroot, tout les répertoires de niveau supérieurs doivent appartenir à root. Cette manipulation permet à un informaticien de composante de mettre à jour ses ACLs de manière autonome sans compromettre l’intégrité du serveur (Jail + chroot SFTP).
Pour tester le dêpot depuis une machine lambda :
# svn co https://trac01.garnett.fr/svn/repo01 --username nico@garnett.fr
Round 3 : Trac
La partie la plus simple. J’ai repris la documentation du site officiel sur l’installation avec le mod_python. La seule différence est que je force l’utilisateur à s’authentifier sur HTTPS et l’incite à rester sur un canal chiffré après authentification. Je donne ma configuration en commentant ce qui diffère par rapport à la documentation originale :
<VirtualHost *:80>
ServerName trac01.garnett.fr
DocumentRoot /usr/local/www-trac/trac01
ErrorLog /var/log/httpd-error-www-trac01.log
CustomLog /var/log/httpd-access-www-trac01.log combined
Options none
RedirectMatch /([^/]+)/login https://trac01.garnett.fr/$1/login
<Location />
SetHandler mod_python
SetEnv PYTHON_EGG_CACHE "/usr/local/www-cache/eggs"
PythonInterpreter main_interpreter
PythonHandler trac.web.modpython_frontend
PythonOption TracEnvParentDir /usr/local/www-trac/trac01
PythonOption TracUriRoot /
</Location>
</VirtualHost>
<Virtualhost *:443>
ServerName trac01.garnett.fr
DocumentRoot /usr/local/www-trac/trac01
ErrorLog /var/log/httpd-error-www-tractrac01-ssl.log
CustomLog /var/log/httpd-access-www-tractrac01-ssl.log combined
Options none
SSLEngine On
SSLCertificateFile /usr/local/etc/apache22/ssl/trac01/server.crt
SSLCertificateKeyFile /usr/local/etc/apache22/ssl/trac01/server.key
<Location />
SetHandler mod_python
SetEnv PYTHON_EGG_CACHE "/usr/local/www-cache/eggs"
PythonInterpreter main_interpreter
PythonHandler trac.web.modpython_frontend
PythonOption TracEnvParentDir /usr/local/www-trac/trac01
PythonOption TracUriRoot /
</Location>
<LocationMatch "/[^/]+/login">
SSLRequireSSL
SetHandler mod_python
SetEnv PYTHON_EGG_CACHE "/usr/local/www-cache/eggs"
PythonInterpreter main_interpreter
PythonOption TracEnvParentDir /usr/local/www-trac/trac01
AuthType Basic
AuthName "Trac"
AuthBasicProvider "ldap"
AuthLDAPURL "ldap://mon_ldap/dc=example,dc=org?mail?sub?(objectClass=inetOrgPerson)"
authzldapauthoritative Off
require valid-user
</LocationMatch>
</VirtualHost>
La différence part rapport à la documentation originale est le passage sur HTTPS pendant et après l’authentification. La directive RedirectMatch permet ce genre de chose. La regexp “/([^/]+)/login https://trac01.garnett.fr/$1/login” redirige l’URL http://trac01.garnett.fr//login vers https://trac01.garnett.fr//login. Sauf si le client réecrit l’URL dans son navigateur, il est en HTTPS jusqu’à la fin de sa session. J’ai fait comme ça car un cookie transite de temps en temps entre le navigateur et le serveur une fois le client authentifié.
Conclusion
La solution ne serait pas complète sans un moyen simple d’ajouter des utilisateurs externes (qui n’ont pas de mail @garnett.fr). Cela peut être fait de manière super simple avec phpldapadmin et ce post qui proposait de merger une branche répliquée avec une branche writable pour enrichir un annuaire dédié avec des comptes locaux à l’application. Cette partie fera l’objet d’un autre post.