%

httpd : délivrer des fichiers statiques compressés (+ slowcgi)

Article publié, le et modifié le
10 minutes de lecture

Cet article contient 1997 mots.
Source brute de l'article :
Commit version : 865d51f

Description

OpenBSD intègre par défaut dans le système de base :

  • un serveur web, nommé httpd, depuis 5.7 - que j’ai présenté plus ou moins succinctement ici

  • un serveur CGI, nommé slowcgi, depuis 5.4

  • Site web : https://bsd.plumbing/

  • OpenBSD : 6.6, 6.7


Principe : si le client web informe qu’il accepte l’encodage de compression aux formats deflate, gzip, br, alors httpd passe la main à slowcgi qui délivre le contenu compressé correspondant.

Attention

Le problème est que le serveur httpd n’est pas capable de gérer la délivrance de contenu statique compressé.

L’astuce est d’utiliser le serveur CGI slowcgi, intégré lui-aussi dans le système de base, pour assumer la délivrance de ce contenu statique compressé.

En effet, par le biais de script CGI - ici, en shell - nous allons pouvoir assumer la délivrance de ces contenus statiques suivants :

  • Formats de fichiers gérés : atom, css, html, js, json, svg, txt, xml
  • aux deux formats de compression : que sont gzip, et brotli.

Installation

Il est nécessaire de télécharger mon script sbw.cgi.

Une fois téléchargé, à vous de le mettre dans le répertoire cgi-bin du chroot web. Le mieux étant d’utiliser la commande install suivante : install -o www -g bin -m 0550 sbw.cgi /var/www/cgi-bin/sbw.cgi qui nous permet de l’installer proprement avec les droits minimum, strictement nécessaire, attribué à l’utilisateur web www, au groupe bin.

Dépendances

Le script nécessite l’installation de plusieurs binaires et quelques bibliothèques à l’intérieur du chroot web pour fonctionner correctement.

Il faut donc veiller à copier :

  • le shell en premier, ainsi que les binaires cat, date et sha256, qui se trouvent tous dans le répertoire système /bin.
  • les binaires basename, logger 1 , stat, qui se trouvent être dans le répertoire système /usr/bin
  • la bibliothèque C partagée /usr/lib/libc.so.xx.0 2
  • la bibliothèque d’exécution /usr/libexec/ld.so

vers leurs répertoires respectifs dans le chroot web /var/www/.

Si tous doivent être assujettis à l’utilisateur root et groupe bin :

  • chacun des binaires doit avoir des droits 0555, par exemple :
    • pour sh : install -o root -g bin -m 0555 /bin/sh /var/www/bin/
    • pour stat : install -o root -g bin -m 0555 /usr/bin/stat /var/www/usr/bin/
  • et chacune des bibliothèques, des droits 0444 :
    • pour la libc : install -o root -g bin -m 0444 /usr/lib/libc.so.xx.0 /var/www/usr/lib/
    • pour ld.so : install -o root -g bin -m 0444 /usr/libexec/ld.so /var/www/usr/libexec/

Cela nécessite de créer avant les répertoires correspondant dans le chroot web !

Mais comme je suis quelqu’un de très gentil, retrouvez mon script de gestion des dépendances.


1 De l’intérêt du binaire logger : bien sûr que normalement, idéalement, nous n’avons pas besoin du logger, mais il a pour propos de journaliser certaines actions, qui en cas d’échec, sont intéressantes à faire écrire dans les journaux /var/log/{daemon,messages}.

De même, si la variable debug est paramétrée sur 1, dans la fonction principale, alors le logger restituera les valeurs correspondantes aux différentes variables, à fin d’analyse : s’assurer que telle variable reçoit bien une valeur, dans tel contexte, et si oui, quelle valeur !

2 La bibliothèque C partagée, entre chaque version d’OpenBSD, change aussi de nom. Ainsi, pour OpenBSD :

  • v6.7 : libc.so.96.0
  • v6.6 : libc.so.95.1

Ce détail est important et peut difficilement être scripté. Donc, lors de changement de version d’OpenBSD, il faut bien veiller à modifier le script pour lui définir le nouveau nom de la bibliothèque, sinon il ne fonctionnera pas !


Astuce

Configuration

httpd

Il est nécessaire d’ajouter dans votre contexte server, les déclarations location suivantes :

Code : httpd

location "/*.atom" { include "/etc/httpd.d/sbw.conf" }
location "/*.css" { include "/etc/httpd.d/sbw.conf" }
location "/*.html" { include "/etc/httpd.d/sbw.conf" }
location "/*.js" { include "/etc/httpd.d/sbw.conf" }
location "/*.json" { include "/etc/httpd.d/sbw.conf" }
location "/*.svg" { include "/etc/httpd.d/sbw.conf" }
location "/*.txt" { include "/etc/httpd.d/sbw.conf" }
location "/*.xml" { include "/etc/httpd.d/sbw.conf" }

Quant au cas du fichier sbw.conf, il renferme le contenu des déclarations fastcgi suivantes :

Fichier : /etc/httpd.d/sbw.conf

root "/cgi-bin/sbw.cgi"
fastcgi param realroot "/htdocs/domaine.tld/www" 
fastcgi param cachecontrol "1814400" 
fastcgi param file404 "/404.html"

Explications :

Il importe de définir au moins :

  • root pour lui signifier le chemin relatif au chroot web, du script CGI à exécuter.
  • le paramètre realroot : bien indiquer la racine vers votre espace web, au sein du chroot web.
  • les autres paramètres sont certes optionnels mais néanmoins utiles à envoyer au script CGI, surtout celui de la gestion du cache.

slowcgi

Le serveur slowgci ne nécessite aucune configuration. Il nécessite seulement d’être activé et démarré avec l’outil de contrôle rcctl.

Histoire

Des failles

En effet, l’histoire du protocole HTTP nous a révélé deux failles majeures liées à la compression à la volée, ou compression dynamique de contenu : CRIME et BREACH - qui est dérivée de la première. Ces deux failles peuvent même impacter TLS .

Même si la plupart des clients web ont été corrigés pour s’efforcer d’atténuer ces failles, il n’est clairement pas recommandé d’utiliser la méthode de compression à la volée, que savent très bien gérer la plupart des serveurs HTTP.

Parmi les parades, a été l’adoption depuis HTTP 1.1 du transfert par encodage de bloc - la fameuse entête Transfer-Encoding: chunked - de même, il est fortement recommandé de mettre en place une politique Referrer afin de n’autoriser que la délivrance de contenu compressé QUE depuis le domaine en cours, et de la refuser depuis tout autre domaine duquel du contenu est appelé (CSS, JS, fonts, json, etc.).

Relative à httpd

L’auteur Reyk Floeter se refuse à la prise en charge de contenu compressé. Et, même si une requête a été faite pour délivrer du contenu déjà compressé, ce n’est pas prêt d’être intégré.

brotli

Le format brotli est un format de compression inventé par une équipe de l’entreprise Google. Il est considéré comme étant le successeur de gzip car plus rapide et un meilleur taux de compression.

En savoir plus :

À savoir que curl gére le format brotli, depuis la sortie de sa version 7.57.0, par l’usage de l’option --compressed, voire de l’option -H - option qui permet de gérer finement les entêtes HTTP. (cf : lire son manpage)

clients non supportés

  • L’outil curl sous OpenBSD semble ne pas supporter le format de compression, bien que celui-ci soit intégré dans le code de source de l’outil.
  • De même, les clients web console que sont lynx, et w3m ne prennent pas en charge brotli, quelque soit l’OS.

Firefox

Ahhh, fichu Firefox, qui depuis la version 64, ne gére plus nativement les flux de syndication Atom et RSS.

En fait, c’est plus subtil qu’il n’y paraît. Si vous délivrez le contenu Atom et RSS avec le mime type text/xml, puisqu’après tout, tous les deux sont bel et bien des fichiers XML, alors Firefox acceptera de les lire nativement et de vous les afficher. Il ne les mettra pas en forme, mais il vous affichera le contenu.

Si vous les délivrer avec leur propre type de contenu, à savoir respectivement application/atom+xml et application/rss+xml, tous les deux normés par une RFC ou l’autre, alors Firefox vous demandera quoi en faire !

Une aberration sans nom !!!

La petite histoire

Pour la petit histoire, Xavier Cartron @prx est l’auteur original de cette idée de délivrer du contenu statique compressé.

Le travail que j’ai effectué se base sur sa première version de script CGI shell. Mais elle ne me satisfaisait pas, pour plusieurs raisons, car il se contente à délivrer au format gzip, QUE SI gzip est demandé.

Il a eu l’idée géniale d’ajouter la gestion de l’entête ETag, qui sert à fixer un identifiant par ressource délivrée. C’est dans ce contexte, que le binaire sha256 est utile.


Ayant entendu parler du format de compression brotli , j’ai donc repris/continué l’écriture afin de pouvoir délivrer du contenu statique compressé précédemment avec ce format.

Ensuite, j’ai ajouté le code nécessaire pour la gestion des entêtes nécessaires :

  • Content-Length : pour envoyer le poids du document quelque soit sa version ; c’est dans ce contexte que le binaire stat est nécessaire.
  • Last-Modified : pour récupèrer la date de modification du document à délivrer ; d’aucun considère que cette entête est plus pertinente que ETag. C’est dans ce contexte que les binaires date et stats sont utiles.
  • Transfer-Encoding : pour envoyer le document à délivrer dans le bon format de compression, si besoin est.

Puis, j’ai écrit le code nécessaire pour détecter si les dépendances utiles étaient bien dans le chroot web, autrement le script ne peut fonctionner. Si les dépendances ne sont pas installées, alors le serveur renvoie une erreur 500, avec un message HTML décryptant l’erreur.

Attention : le script n’installe pas et ne peut pas installer les dépendances, du fait d’être dans le chroot web, il ne peut voir ce qui se passe au-delà !

Puis, après quelques recherches sur le web, j’ai compris que le format de compression deflate qui peut être demandé par certains clients web, est compris dans le format de compression gzip, de là, la prise en charge.


Mais j’étais confronté à des dysfonctionnements que je n’arrivais pas à comprendre et encore moins à résoudre. Et, là deux “choses” m’ont sérieusement aider à avancer - car, à moment donné, ne m’en sentant plus les capacités, j’ai tout simplement laissé tomber, n’y arrivant pas, ne trouvant pas l’aide nécessaire pour avancer - :

  • Solène Rapenne, de l’équipe d’OpenBSD, m’a aidé à comprendre que je faisais l’erreur d’envoyer en trop des retours à la ligne, alors qu’il m’en faut un seul, au bon moment, celui entre l’envoi des différentes entêtes et l’envoi du document lui-même, qu’il soit compressé ou non.
  • l’idée d’implémenter une variable debug et d’utiliser le binaire logger pour m’assurer des différents retours.

Une astuce que m’a donné Solène est l’usage en local de cette commande : env HTTP_ACCEPT_ENCODING=br realroot=/var/www/htdocs/domaine.tld/dev/ PATH_INFO=index.html /var/www/cgi-bin/sbw.cgi | less m’expliquant qu’il est possible d’interroger localement directement le script CGI, en lui envoyant les différentes valeurs possibles lors de l’appel… via les variables d’environnements. Idée géniale !

À ce propos, étant donné que nous avons installé le script shell CGI sbw.cgi avec des droits utilisateurs 0550, vous aurez le droit à cette petite erreur : env: /var/www/cgi-bin/sbw2.cgi: Permission denied il suffit de changer le droit d’exécution pour les autres ;-) (0551, ou +x)


J’ai amélioré la détection du mime type en l’obtenant à partir du fichier appelé sur le système - qui nécessite l’usage du binaire basename - et la gestion des types de contenu des flux de syndication Atom ou RSS.

Pour finir, j’ai écrit le code nécessaire pour détecter si l’agent utilisateur était Firefox .

  • Si oui, pour récupèrer son numéro de version, car étant donné que les flux de syndication ne sont plus correctement supportés, un petit hack était nécessaire pour lui délivrer les flux Atom et RSS au format “trompeur” text/xml. Ainsi, Firefox accepte de le lire au-lieu de demander à l’ouvrir avec une autre application.

J’ai écrit la première mouture de ce hack nécessitant l’ajout des binaires grep et awk - solution qui ne me satisfaisait pas, mais fonctionnelle. Et, suite à la réflexion de l’utilisateur @eol, sur le forum de la communauté française d’“OpenBSD pour tous”, j’ai remplacé par un travail sur l’expansion des variables en shell.

Et, voilà !


Actuellement, @prx a réécrit son script shell en langage C :

  • l’avantage est qu’il n’y a besoin d’aucune dépendance, et que c’est “protégé” par les mesures de sécurité sur les appels système que sont pledge(2) et unveil(2).
  • néanmoins, il ne gère QUE la compression gzip ; de même certaines petites choses ne sont pas gérées : pas de gestion de l’entête Last-Modified, ni du support brotli ou deflate, ou du moins pas directement|automatiquement.

Documentation

En savoir plus sur la mise en place d’une politique Referrer : Referrer (header)

manpage

Wikipédia