Deblan blog

Tag #Apache

Outil de création d’un espace web Apache et PHP

Dans mon activité personnelle et professionnelle, je suis amené à créer des espaces d’hébergement de sites web principalement écrits en PHP.

Il y a quelques années, j’ai écris un script en shell qui posait des questions et générait des fichiers de configuration pour Apache et PHP puis relançait ces services. Il a ensuite évolué et générait également les utilisateurs unix et affinait les permissions. Le principal problème du script est que d’un serveur à l’autre, il fallait mettre des coups de hache dans le code pour l’adapter.

Cette semaine, j’ai entamé une refonte complète du code. Au fur et à mesure du développement, j’ai rendu pas mal de choses configurables et je pense qu’il est fonctionnel sur des environnements relativement différents des miens.

Je vous présente donc vhost-manager, c'est un projet libre et est toujours orienté vers la génération de vhost Apache et de pools PHP FPM. Il faut make, gcc, wget pour l'installer et sh, whiptail et php sont nécessaires à son utilisation.

vhost-manager

Le code source est disponible ici. Le projet se configure via un fichier de variables et j'ai conservé le principe des questions/réponses pour générer les fichiers.


[tips] PHP FPM : récupérer la vraie adresse IP du visiteur

Mon serveur web fonctionne par couches. La première couche est gérée par Nginx et traite les requetes HTTP des internautes. Nginx gère les problématiques de cache sur les assets, c'est à dire les images, les fichiers javascripts et enfin les fichiers CSS. Ensuite, il transmet les requêtes au serveur web Apache qui va délivrer le site web concerné et faire appel au process manager de PHP (FPM) pour exécuter PHP.

En l'état, il n'est pas possible de connaître l'adresse IP de l'internaute via la variable globale $_SERVER en utilisant l'index REMOTE_ADDR. En effet, si j'affiche le contenu de $_SERVER['REMOTE_ADDR'], j'aurai comme résultat 127.0.0.1 qui est l'IP locale de Nginx.

Cependant, j'ai configuré Nginx de tel sorte qu'il ajoute les entêtes HTTP X-Forwarded-For et X-Real-IP. Apache les transmet ensuite via les index HTTP_X_FORWARDED_FOR et HTTP_X_REAL_IP.

proxy_set_header X-Real-IP  $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;

Ces index contiennent l'IP de l'internaute mais ne sont pas utilisés par les applications hébergées. Typiquement, Nextcloud détectait l'adresse IP 127.0.0.1. Lors d'un brute force, tous les comptes utilisateurs étaient impactés par la sécurité enclanchée par Nextcloud (soit 30s d'attente avant la validation des identifiants de connexion).

J'ai fouiné sur la toile et plusieurs solutions sont évoquées. J'ai un peu tout essayé et au delà des modules Apache Rpaf et RemoteIP, pas grand chose de probant. Rpaf est déjà installé et permet à Apache de logger la bonne IP dans les logs d'accès. RemoteIP n'a pas fonctionné dans mon environnement.

Pour résoudre le problème, j'ai appliqué ce qui a été rédigé dans ce post : inclure un script PHP de façon automatique pour l'ensemble des sites web du serveur.

j'ai créé un fichier PHP comme suit :

<?php

$trustedProxies = [
    '127.0.0.1',
];

$remote = $_SERVER['REMOTE_ADDR'];

$headers = [
    'HTTP_X_FORWARDED_FOR' => 'REMOTE_ADDR',
    'HTTP_X_REAL_IP' => 'REMOTE_HOST',
];

if (in_array($remote, $trustedProxies)) {
    foreach ($headers as $header => $value) {
        $_SERVER[$value] = $_SERVER[$header];
    }
}

Puis j'ai alimenté les php.ini de cette façon :

auto_prepend_file = /etc/php/fpm/real-ip.php

Après avoir relancé les services, j'ai pu constater que l'adresse IP de l'internaute est bien présente dans $_SERVER['REMOTE_ADDR'].


Installer plusieurs versions de PHP sur un serveur web

Il arrive qu'il soit nécessaire d'installer des versions de PHP spécifiques pour des projets qu'on héberge. Le must du must est de passer par son gestionnaire de paquet mais ce n'est pas toujours possible. Il convient donc de récupérer le ou les versions nécessaires et de les compiler.

Une des problématiques réside dans la sécurisation du serveur web. Il est important de considérer que se passer de son gestionnaire de paquet est dangereux car on ne bénéficie pas des mises à jour de sécurité et qu'il est impératif de gérer les droits pour que les projets impactés s'exécutent avec des utilisateurs qui leur sont dédiés.

La procédure d'installation n'est pas compliquée mais ça demande d'être minutieux.

Pour gérer la récupération du code source et de sa compilation, j'aime beaucoup travailler avec PhpFarm. On va donc récupérer le projet et simplement balancer un peu de config.

Pour cet exemple, je veux d'installer PHP 5.4.28. L'ensemble des commandes est fait en root.

aptitude update
aptitude install git make
cd /usr/local
git clone git://git.code.sf.net/p/phpfarm/code phpfarm
cd phpfarm/src

Je vous propose de modifier les options de compilation afin d'avoir quelques outils souvent très utilisés. Il suffit d'éditer le fichier src/options.sh et d'éditer la liste des paramètres :

configoptions="\
--enable-bcmath \
--enable-calendar \
--enable-exif \
--enable-ftp \
--enable-mbstring \
--enable-pcntl \
--enable-soap \
--enable-sockets \
--enable-sqlite-utf8 \
--enable-wddx \
--enable-zip \
--with-openssl \
--with-zlib \
--with-gettext \
--enable-libxml \
--with-zlib \
--with-xsl \
--with-jpeg-dir \
--with-png-dir \
--with-gd \
--with-ttf \
--with-freetype-dir \
--with-pdo-mysql \
--with-mysql \
$gcov"

À présent, il suffit de demander à PhpFarm de télécharger et compiler notre version de PHP.

./compile.sh 5.4.28

Il manquera sans doute des lib de dev. Par exemple, s'il remonte une erreur concerant jpeg, faite une recherche du type aptitude search jpeg | grep dev et installez ce qui convient (le paquet libjpeg8-dev dans ce cas).

Une fois la compilation achevée, il faut configurer le serveur web (apache2 dans mon cas). Je rappel que je veux que mes projets aient un utilisateur dédié. Je vais faire une configuration similaire à celle-ci.

a2enmod actions suexec
mkdir -p /var/www/service-web/bin/cgi
echo /usr/local/phpfarm/inst/bin /var/www/service-web/bin/cgi none bind >> /etc/fstab
mount /usr/local/phpfarm/inst/bin

Si j'ai un projet "blog", mon utilisateur dédié sera "webblog". J'ai également un groupe "webgroup" pour tout mes projets.

addgroup webgroup
useradd -G webgroup -s /bin/false -M webblog
chown -R webblog:webgroup /chemin/vers/projet/blog
mkdir /var/www/service-web/bin/cgi/webblog
cd /var/www/service-web/bin/cgi/webblog
cat > php-cgi <<EOS
#!/bin/sh
exec /usr/local/phpfarm/inst/bin/php-cgi-5.4.28
EOS
chown -R webblog:webgroup .
chmod u+x php-cgi

Il manque plus que la modification du VirtualHost :

<VirtualHost *:80>
	[...]
	SuexecUserGroup webblog webgroup
	ScriptAlias /phpfarm-bin/ /var/www/service-web/bin/cgi/webblog/
	AddHandler application/x-httpd-php5 php
	Action application/x-httpd-php5 /phpfarm-bin/php-cgi
	[...]
</VirtualHost>

Faites une petite prière et relancez apache :

service apache2 restart

Comme je n'utilise que du FastCGI (ou des CGI), je ne sais pas comment la configuration réagit si PHP (en module apache) est activé.


Statistiques de son site web

En règle générale, les personnes apprecient avoir un retour de leur travail. Quand on a un site web, il paraît évident de ressortir quelques chiffres sur les visteurs : quelles pages lisent-ils ? D'où viennent t-ils ? Combien de temps restent-ils ? etc.

Il existe une grande quantité d'outils qui permettent de générer des rapports. Le plus connu du grand public est sans doute Google Analytics. Mon problème c'est que j'ai du mal à confier des choses à des sociétés externes. Quels sont mes outils d'analyse sur mes sites ? En voici quelques uns.

Open Web Analytics (OWA) est un outil open source. Il est très similaire à Google Analytics et s'installe sur son hébergement. Il dispose de pas mal de fonctionnalités : statistiques sur la consultation des pages, les domaines de référence, l'analyse des requêtes sur les moteurs de recherche, création de campagnes, etc. Il est complet et permet de gérer plusieurs sites. Il fonctionne via l'ajoût de scripts javascript et/ou PHP au sein des pages.

Open Web Analytics

Webalizer est un générateur de rapport html qui se repose sur les logs Apache2. Il va sortir des chiffres sur la consultation des pages, les url de référence et de sortie, quelques données sur la bande passante utilisée et quelques infos sur les clients.

Il se lance en ligne de commande. Voila une commande type qui va générer un rapport nommé "Mon blog", dans le répertoire "stats" via le fichier de log "mon_site.log" :

webalizer -n "Mon blog" -o stats mon_blog.log

Webalizer est packagé sur Debian.

Webalizer

Le dernier outil est goaccess. C'est un programme qui s'exécute en ligne de commande et le rendu se fait dans console (interface ncurses).

goaccess

goaccess -f mon_blog.log

Goaccess est packagé sur Debian.

Pour lire un fichier de log apache2, vous devez disposez d'un accès root (comportement de base).

C'est primordiale de confronter les données générées par ces outils.


Domaines de développement - Apache2

Aujourd'hui j'ai eu besoin d'une configuration particulière d'Apache : j'ai décidé que les sous-domaines de deblan.org de type "dev-XXX" devaient être gérés de tel sorte que "XXX" corresponde à un sous-répertoire de dev.deblan.org. Ainsi, dev-01.deblan.org est l'équivalent de dev.deblan.org/01/.

Jusque là, rien de bien méchant sauf que j'ai une contrainte : je souhaite que ce soit automatique (ou le plus possible). Donc je ne souhaite pas ajouter/modifier des Virtualhost, je veux qu'un sous-domaine puisse être supprimé dans me prendre la tête.

La solution est au final simple (mais un peu tordue) :

Etape 1. Créer un fichier de mapping

L'idée est la suivante : comme tout ne sera pas automatique, je m'autorise l'édition d'un seul fichier pour traiter la correspondance d'un nom et de son répertoire de destination (en quelque sorte son DocumentRoot).

Ce fichier sera placé à la racine du compte associé à dev.deblan.org. Sur mon serveur, c'est le chemin suivant :

/services/web/www/dev-xx.deblan.org/vhost.map

Ce fichier contient ces informations :

dev.deblan.org /var/www/service-web/www/dev-xx.deblan.org/public_html/
dev-01.deblan.org /var/www/service-web/www/dev-xx.deblan.org/public_html/01
dev-foo.deblan.org /var/www/service-web/www/dev-xx.deblan.org/public_html/foo

On a donc à gauche le nom de domaine et à droite le répertoire web associé.

A travers une connexion au compte dev.deblan.org (via SSH ou en FTP par exemple), je peux modifier ce fichier.

Etape 2. Création du VirtualHost d'Apache
<VirtualHost *:80>
	# On définit le domaine de base
	ServerName dev.deblan.org
	# On indique les alias de domaine à prendre en compte 
	ServerAlias dev-*.deblan.org

	# La partie la plus importante : les règles qui permettront de traiter ma demande
	RewriteEngine on

	# C'est ici que tout se passe
	RewriteMap lowercase int:tolower
	# On charge le fichier pour le mapping
	RewriteMap vhost txt:/services/web/www/dev-xx.deblan.org/vhost.map
	# On prend le soin de travailler de tout mettre en minuscules (donc attention à la casse !)
	RewriteCond ${lowercase:%{SERVER_NAME}} ^(.+)$
	RewriteCond ${vhost:%1} ^(/.*)$
	# On "simule" le DocumentRoot du domaine
	RewriteRule ^/(.*)$ %1/$1

	[...]
</VirtualHost>
Etape 3. Création d'un script de mise à jour d'Apache

Le fichier de mapping n'est chargé qu'une seule fois et c'est quand on relance ou reload Apache. Il faut donc, à des temps réguliers, vérifier si le fichier de mapping a été modifié et si on doit relancer Apache.

Il existe plusieurs méthodes, moi je vous propose un truc très simple :

(Fichier /root/apache2/reload)

#!/bin/sh

# On indique le fichier de mapping
MAPPING=/services/web/www/dev-xx.deblan.org/vhost.map

# On indique le fichier qui va contenir le retour de "ls -l" sur le fichier de mapping
LS_FILE=/root/apache2/ls

[ -f "$LS_FILE" ] || touch "$LS_FILE"

LS=$(ls -l "$MAPPING")

# Si il y une différence entre le "nouveau" ls et l'ancien ls, alors le fichier à été modifié
# Donc on relance apache et on met à jour $LS_FILE
if [ "$LS" != "$(cat "$LS_FILE")" ]; then
	/usr/sbin/service apache2 reload
	echo "$LS" > "$LS_FILE"
fi

Il ne vous reste plus qu'à placer ça dans un crontab avec par exemple cette ligne de conf :

*/1 * * * * /root/apache2/reload 2>&1 >/dev/null

Ici le script est exécuté toutes les minutes.

Normalement tout devrait fonctionner :)

Si vous avez d'autres solutions, je suis prenneur !