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.
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/
- pour sh :
- 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/
- pour la libc :
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 !
ldd
sur le binaire en question.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 :
- https://brotli.org/
- Est-ce que votre client web le supporte : https://caniuse.com/#search=brotli
À 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 binairelogger
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)