Deblan blog

Tag #PHP

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.


API pour récupérer le contenu Open Graph d'une page web

Pour la fonctionnalité de partage de liens sur ce blog, j'ai développé un script qui récupére le contenu d'une page et analyse ses balises <meta> pour identifier ses données Open Graph.

Dans un autre contexte, j'ai rencontré un bug dans Wallabag qui l'empêche de récupérer le contenu de plusieurs pages web que je désirait lire plus tard. Avec la volonté de créer un rapport de bug, la documentation m'a amené sur la piste du projet Graby utilisé par Wallabag.

Pour comprendre et peut-être proposer un correctif, j'ai joué avec Graby et ça m'a amené à réaliser une API pour remplacer le script utilisé par le blog, en combinent Graby et fusonic/opengraph. L'objectif de l'API est donc de retourner des données générées par Graby et OpenGraph au format JSON.

Voici à quoi ressemble un retour d'appel à l'API avec les données Open Graph :

Le projet est dépendant de PHP 7.3 et c'est libre. Le code source est dispo ici.


Vérifier la date d'expiration de noms de domaine

Que ce soit à titre personnel ou dans le cadre de mon travail, je dois gérer une liste relativement importante de noms de domaine et m'assurer qu'ils sont renouvelés à temps.

Les prestataires vers qui sont achetés les noms sont divers et il n'y a pas d'homogénéité des alertes qui notifient d'une expiration prochaine.

Par conséquent, j'ai écris un projet qui a pour seul et unique objectif de me donner la date d'expiration d'un ou plusieurs noms de domaine. Cette date prendra une couleur selon de l'échéance : rouge si on est dans les 2 dernières semaines, jaune si c'est dans les 30 prochains jours ou ou vert si c'est au délà.

$ domain-expiration check google.com amazon.com facebook.com apple.com microsoft.com
+---------------+---------------------+
| Domain        | Date                |
+---------------+---------------------+
| google.com    | 2020-09-14 04:00:00 |
| apple.com     | 2021-02-20 05:00:00 |
| microsoft.com | 2021-03-05 04:00:00 |
| amazon.com    | 2022-10-31 04:00:00 |
| facebook.com  | 2028-03-30 04:00:00 |
+---------------+---------------------+

Grâce à ansi2html, on peut réaliser une conversion du rendu en HTML afin générer un mail coloré.

$ domain-expiration --ansi check [...] | ansi2html | mail \
  -a "Content-type: text/html" \
  -s "Dates d'expirations des domaines" \
  admin@example.com

Le projet est écrit avec PHP 7.3. Les dépendances sont traitées avec composer et il faut le programme whois.

$ git clone https://gitnet.fr/deblan/domain-expiration.git
$ cd domain-expiration
$ composer install
$ php7.3 ./domain-expiration check mon-site.fr

Le code n'est pas parfait mais ça fonctionne 😊


[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'].


Remote i3-wm WS : ma télécommande pour bureau Debian GNU/Linux

Il m'arrive d'avoir besoin de prendre le contrôle de ma machine via mon Android (bouger la souris, scroller et taper du texte).

J'ai quasiment toujours utilisé l'application Pointer Host. elle s'appuie sur un serveur Java lancé sur ma machine. Pour bouger la souris et générer des cliques, elle est très efficace. Cependant, l'écriture de caractères a toujours posé problème (chiffres et lettres accentuées erronés).

Depuis quelques semaines, j'ai envie de jouer avec les websockets et réécrire une appli du genre m'a semblé être un bon exercice.

Ce que je vais vous présenter est une POC. Cette application n'est pas sécurisée et est orientée pour mes besoins. Il est cependant très simple de la faire évoluer.

Voici mon cahier des charges :

  1. aucune application ne doit être installée sur mon téléphone
  2. utilisation du navigateur web pour le pilotage
  3. pouvoir bouger la souris
  4. pouvoir générer des cliques
  5. pouvoir scroller
  6. pouvoir taper des mots
  7. pouvoir lancer des raccouris claviers
  8. pouvoir gérer le volume du son
  9. pour Spotify : lancer et mettre en pause la musique, avancer et reculer dans la playlist en cours de lecture
  10. pouvoir changer de workspace dans mon gestionnaire de fenêtres i3-wm

Les outils pour scripter tout ça sont connus :

  • xdotool pour simuler un clavier et une souris (3, 4, 5, 6, 7) :
    • xdotool type "ceci va être tapé"
    • xdotool key Enter (touche Entrée)
    • xdotool click 1 (clique gauche)
  • amixer pour gérer le volume (8) :
    • amixer set Master 50% (volume à 50%)
  • playerctl pour piloter Spotify (9) :
    • playerctl -p spotify next (titre suivant)
  • i3-msg pour piloter i3-wm (10) :
    • i3-msg 'workspace "Foo"' (affichage du workspace Foo)

Websocket est un protocole réseau issu du web qui permet de créer un canal full-duplex entre un client et un serveur. Ils peuvent donc communiquer en temps réel dans une connexion TCP.

Comme à mon habitude et par esprit de contradiction (pas de NodeJS), j'ai décidé d'écrire la partie serveur en PHP 7. La partie cliente est en HTML 5 avec un peu de javascript.

Entre le serveur et le client, ce sont des messages textes qui sont échangés. J'ai décidé de les formater en JSON et seul le client va en envoyer. Ils sont toujours sous cette forme : {"type":"un type de message", [données complémentaires]}. Voici quelques exemples :

  • {"type":"workspace","value":"1. IRC"}
  • {"type":"pointer","click":"left"}
  • {"type":"media","value":"next"}
  • {"type":"volume","value":"down"}
  • {"type":"scroll","value":"up"}
  • {"type":"pointer","x":"-2","y":"3"}
  • {"type":"text","value":"Un texte"}

On peut aussi envoyer plusieurs messages dans un seul. Voici un exemple qui va permettre d'ouvrir urxvt via dmenu que je lance avec win+d :

{"type":"messages","value":[{"type":"keys","value":"win,d"},{"type":"text","value":"urxvt"},{"type":"key","value":"enter"}]}

Ces messages sont générés par le client et sont interprétés par des messageHandler une fois transmis au serveur. En voici un exemple :

Le code source de l'application est disponible sur deblan/remote-i3wm-ws et la procédure d'installation est simple :

Pour lancer le serveur websocket, il faut exécuter server/server start (@see restart et stop). Le serveur va écouter sur le port 14598. Concernant la partie cliente, vous pouvez créer un vhost Apache/Nginx qui pointera sur client/ ou lancer le serveur web built-in de PHP via php -S 0.0.0.0:15000 -t client/. Il faudra à présent vous connecter au serveur web depuis un navigateur.

Quelques captures de la partie cliente :

Remote i3-wm WS: keyboardRemote i3-wm WS: i3Remote i3-wm WS: mouseRemote i3-wm WS: media

Je vais essayer de faire une vidéo de démonstration. Depuis mon Samsung S8, ça fonctionne du feu de dieu et sur un petit Iphone 4S, c'est tout aussi fonctionnel (à part l'interface web un peu étriquée).

Edit 1

  • dbus-send a été remplacé par playerctl (merci Thomas L)
  • le type messages a été ajouté et permet d'envoyer plusieurs messages (cf l'exemple avec urxvt) et les mises à jour du code

Edit 2

  • Le code PHP du serveur a été déplacé dans server/src/resource/server.php
  • server/server est à présent un script shell et permet de lancer, relancer et stopper le serveur websocket (server/server start|restart|stop)