Creer une application multi Vhost

php.jpg Quelques mots de conception. Ce n'est pas LA solution, c'est un peu brouillon, mais je vais tenter de d'expliquer ici les idées et principes que j'applique actuellement dans la réécriture de l'application tinaderp en version plateforme.

Beaucoup de concepts que j'utilise proviennent de l'observation de dotclear en version multiblog.

L'idée, c'est que j'ai une application web qui va servir à un groupe d'utilisateurs. Pour qu'elle serve à un autre groupe, depuis un autre VHOST, le plus simple aurait été de faire une base de données nommée autrement et de copier tout le VHOST d'origine dans le nouveau... puis changer les paramètres de connexion mysql.

Oui, mais comme ça, je me taperai les mises à jour sur chaque site, un par un. Et comme beaucoup, je ne supporte pas les taches fastidieuses, quitte à me compliquer la vie pour les éviter.

Voyons rapidement la sécurisation du cross vhost, puis l'organisation des fichiers.

Quelques définitions

Oui, il y a quelques termes que j'emploie, peut être à mauvais escient, qui peuvent porter à confusion:

VHOST:

C'est un VirtualHost. Il se définit principalement par son adresse URL et un dossier sur le serveur. sur ce serveur par exemple, parmis la dizaine de vhost on trouve:

Dossier sur le serveurURL d'acces
/var/www/blog-du-grouik.tinad.fr/http://blog-du-grouik.tinad.fr
/var/www/www.tinad.fr/http://www.tinad.fr

Framework

C'est le socle de l'application. La partie de code qui gère l'authentification des utilisateurs et les différents modules.

Module

Bah c'est un menu quoi

Configuration serveur

Apache en cross Vhost, un user unix par Vhost

J'organise l'application comme un système d'application: les programmes d'un coté, la "data" des usagers de l'autre. Les utilisateurs pouvant uniquement lire et exécuter les applications. et ne pouvant pas lire la data du voisin. Pour le test, je vais me faire trois vhost [1]:

  • vhost.tinad.fr
  • vhost1.tinad.fr
  • vhost2.tinad.fr

vhost.tinad.fr contient les scripts php vhost1.tinad.fr et vhost2.tinad.fr contient les fichiers servant séparément aux deux groupes (les documents déposés dans le cas d'une GED par exemple).

Le serveur apache, bien que lancé et configuré par root utilise un compte linux pour l’accès aux fichiers du site internet, généralement www-data. Dans un fichier php, on peut très bien lui demander d'aller chercher (avec un "include" ou autre) dans un fichier qui ne se trouve pas dans le répertoire du vhost. Le daemoon utilise pour ça le compte systeme www-data. Suffit juste que ce dernier ait un accès, on peut aller n'importe où sur le serveur.

Ils ont les fichiers de configuration très basique suivants:

<VirtualHost *:80>
ServerName vhost.tinad.fr
DocumentRoot /var/www/vhost.tinad.fr
</VirtualHost>
<VirtualHost *:80>
ServerName vhost1.tinad.fr
DocumentRoot /var/www/vhost1.tinad.fr
</VirtualHost>
<VirtualHost *:80>
ServerName vhost2.tinad.fr
DocumentRoot /var/www/vhost2.tinad.fr
</VirtualHost>

Juste pour l'explication de ce que j'appelle le cross vhost, Je créé le fichier /var/www/vhost1.tinad.fr/plop.html suivant:

[htm]
<h1>plop</h1>

Puis le fichier /var/www/vhost2.tinad.fr/index.php suivant:

  1. <p>le site voisin dit:</p>
  2. <?php
  3. include ("../vhost1.tinad.fr/plop.html");
  4. ?>

Résultat:

crossvhost.jpg

Le premier principe de conception va être d'utiliser cette possibilité sans que ce soit une épée de Damocles, permettant à tout script kiddie de pourrir tous les vhost à la moindre faille dans un script php.

Sécuriser le cross vhost en utilisant la gestion des droits d'accès aux fichiers du noyau linux

On va créer trois comptes système linux, un par VHOST

  1. #Creer les users (l'option -r signifie compte systeme, ne peut pas se connecter sur la machine)
  2. useradd -r vhost
  3. useradd -r vhost1
  4. useradd -r vhost2
  5. #mettre vhost1 et vhost2 dans le groupe vhost (ils auront ainsi un acces lecture et exécution)
  6. usermod -a -G vhost vhost1
  7. usermod -a -G vhost vhost2
  8. #leur donner les dossiers
  9. chown -R vhost:vhost /var/www/vhost.tinad.fr
  10. chown -R vhost1:vhost1 /var/www/vhost1.tinad.fr
  11. chown -R vhost2:vhost2 /var/www/vhost2.tinad.fr
  12. #Installer un module apache qui permet de définir l'user linux utilisé pour le vhost:
  13. apt-get install apache2-mpm-itk
  14. #on autorise le groupe et l'user seulement:
  15. chmod -R 750 /var/www/vhost*

On modifie à nouveau les fichiers de configuration des vhost comme ceci.

<VirtualHost *:80>
ServerName vhost2.tinad.fr
DocumentRoot /var/www/vhost2.tinad.fr
        <IfModule mpm_itk_module>
                AssignUserId vhost2 vhost2
        </IfModule>

</VirtualHost>

idem pour les 2 autres vhost en remplaçant par le bon user linux, le bon serverName et le bon DocumentRoot.

Résultat: lesitecoisinfail.jpg

Voila, on n'a plus accès aux sites voisins, et un acces en lecture/execution uniquement des exécutables. ça fait plus rigoureux.

Organisation des fichiers:

Dans les dossiers des vhost clients, (vhost1.tinad.fr et vhost2.tinad.fr)

je dépose juste un index.php ce cette manière, façon dotclear[2]:

  1. <?php
  2. //Un identifiant qui permet au programme de savoir à quel site il a à faire
  3. define('intra_id','0');
  4. //On renvoit tout ensuite sur l'application en elle meme
  5. require dirname(__FILE__).'/../vhost.tinad.fr/prepend.php';
  6. ?>

Et ensuite dans le prepend.php, le code du site se déroule normalement. Il va y avoir une petite galère avec ce système. Toutes les URL doivent passer par par index.php. Les css, le js, les images et les validations de formlaires. CSS et image, perso j'ai ajouté sur chaque vhost fait un lien symbolique en plus de l'index.php.

Pour le js. Je le génère directement dans le code html, pas de link. Le framework en lui même n'a pas de javascript. Par contre les différents modules oui... je présente au navigateur uniquement le JS qui sert au module (la partie de l'application) en cours d'affichage.

Pour la validation des formulaires, Pareil, je pique la méthode dotclear.

  1. <?php
  2. function xd_check_input($id=1)
  3. {
  4. /*
  5. *On génére un hash aléatoire qui sera
  6. *ajouté aux formulaires, afin d'ajouter
  7. *une vérification supplémentaire
  8. *lors du traitement de ce dernier
  9. */
  10. if(!isset($_SESSION['xd_check']))
  11. {
  12. //le générer
  13. $_SESSION['xd_check']=rand_str(25);
  14. }
  15. if ($id==1)
  16. {
  17. return "<input type=\"hidden\" name=\"xd_check\" id=\"xd_check\" value=\"".$_SESSION['xd_check']."\">";
  18. die;
  19. }
  20. else
  21. {
  22. return "<input type=\"hidden\" name=\"xd_check\" value=\"".$_SESSION['xd_check']."\">";
  23. die;
  24. }
  25. }
  26. ?>

La function précédente me permet de générer facilement un input que je place dans chaque formulaire.

Ainsi, je peux détecter au tout début du code avant même le premier header, si un formulaire est soumis:

  1. //sousmission d'un formulaire
  2. if (isset($_POST['xd_check']))
  3. {
  4. //vérifier le numero de formulaire
  5. if (($_SESSION['xd_check']!=$_POST['xd_check']) AND ($_POST['xd_check'] !=""))
  6. {
  7. erreur ('Validation du formulaire incorrecte');
  8. die;
  9. }
  10. //C'est ok, on envoie vers le code qui gere les formulaires
  11. if (isset($modules[$_GET['menu']]))
  12. {
  13. require_once (engine_path."/".$modules[$_GET['menu']]['path'].'/_act.php');
  14. }
  15.  
  16. }

Ce qui est pratique, c'est que du coup, je n'ai pas à gerer de redirection, les modifications engendrées par la validation du formulaire seront prises en compte immédiatement.

Les données de chaque groupe

Elles sont soit dans la base de données, soit, si c'est un fichier dans un dossier qui est sur leur propre Vhost. J'avais fait une explication de la technique pour les protéger d'un accès direct par leur URL.

L'organisation en framework

La racine du vhost principal contient

  • index.php
  • Un fichier php contrenant la liste des modules, noms, path, état actif ou non.
  • Un dossier inc pour toutes les functions, images etc... propre au framework et les objets communs à tous les modules
  • Un dossier par module.

Chaque dossier de module contient:

  • _act.php les actions générées suite à la validation des formulaires
  • _index.php l'affichage utilisateur
  • _js.js Le code javascript propre au module (il sera placé par le framework dans le header de la page)
  • _logo.png Bah pour le menu, le logo du module.

Conclusion.

C'est brouillon hein? Mais je suis reparti de cette manière, je recode tout dans ce principe, c'est fastidieux, mais quand ce sera fini, je serai sur une meilleure base pour tous les développements qui attendent, et pour une version multisite.

Notes

[1] Cherchez pas, je les effacerai avant de publier ce billet.

[2] Ça sert à ça aussi le logiciel libre, partager des méthodes de codage

Page top