Ce qui donne aux contributions de ce développeur une saveur particulière, c'est que ce dernier n'est autre qu'un employé de la société CloudFlare, qui utilise massivement le serveur nginx. Au vu du trafic considérable que cette société spécialisée dans le CDN [1] réalise, on peut présumer d'une certaine qualité et stabilité dans le code produit par ce furieux développeur.
Outre la beauté de ses réalisations, toutes disponibles sur son compte GitHub [2], le lecteur programmeur pourra lire avec une certaine admiration le code de ses modules, non seulement produit avec le plus grand soin, mais également remarquablement documenté et truffé d'exemples. On est loin des Lennarteries. Le lecteur assidu n'en est pas à sa première rencontre avec agentzh, puisque c'est ce dernier qui a produit le fabuleux nginx-lua dont nous vantions les mérites dans l'article précédent.
Je vous propose de parcourir une sélection de quelques modules sortis des doigts magiques d'agentzh et d'y appliquer quelques exemples pratiques d'utilisation qui remplaceront avantageusement d'inutiles cascades phpesques.
Les modules que nous utiliserons dans la suite de cet article font tous partie de la suite OpenResty [3], évidemment maintenue par... agentzh. OpenResty n'est pas un fork de nginx, il s'agit simplement d'une méthode de distribution d'un paquet nginx sous forme de source contenant toutes les extensions tierce partie jugées intéressantes pour transformer le serveur web en serveur d'applications.
Les heureux utilisateurs de pkgsrc [4] pourront utiliser la variable PKG_OPTIONS.nginx dans le fichier /etc/mk.conf afin d'y ajouter les modules souhaités, ces derniers ayant été inclus en mars 2014 par votre serviteur.
1. ngx_echo
Le premier module que nous allons manipuler est le très pratique et versatile ngx_echo [5]. Comme on peut l'imaginer à son nom, la première utilité du module echo est de renvoyer du texte, à la façon de la commande UNIX echo(1) :
$ cat nginx-test.conf
location /modtest {
echo "foo.";
}
Résultat :
imil@tatooine:~$ curl -s http://coruscant/modtest
foo.
Tout comme la commande echo(1), on peut ajouter le paramètre -n afin d'éviter un retour chariot au prochain affichage :
$ cat nginx-test.conf
location /modtest {
set $a "foo... ";
set $b "bar";
echo -n $a;
echo "$b from $remote_addr";
}
Car oui, bien évidemment, la commande echo permet d'afficher toutes les variables disponibles dans nginx :
imil@tatooine:~$ curl -s http://coruscant/modtest
foo... bar from 192.168.1.1
Au-delà de ses simples capacités d'affichage, le module echo dispose d'un arsenal de fonctions aux applications multiples. Découvrons par exemple le fonctionnement de echo_before_body et echo_after_body :
location /modtest {
echo_before_body "je passe avant le corps";
echo_after_body "et moi je passe après";
proxy_pass http://localhost/lecorps;
}
location /lecorps {
echo "je suis le corps.";
}
Résultat :
imil@tatooine:~$ curl -s http://coruscant/modtest
je passe avant le corps
je suis le corps.
et moi je passe après
Le module echo ne propose pas seulement des fonctions, il met également en place des variables accessibles par nginx :
location /modtest {
echo_reset_timer;
echo $echo_client_request_headers;
echo "Temps passé: $echo_timer_elapsed";
}
Résultat :
imil@tatooine:~$ curl -s http://coruscant/modtest
GET /modtest HTTP/1.1
User-Agent: curl/7.26.0
Host: coruscant
Accept: */*
Temps passé: 0.000
Ici, on a utilisé :
- echo_reset_timer qui met à un compteur de temps ;
- la variable $echo_client_request_headers qui contient les en-têtes émis par le client ;
- la variable $echo_timer_elapsed dans laquelle on retrouve le temps écoulé depuis le dernier echo_reset_timer.
Encore plus fort, le module ngx_echo permet de réaliser des boucles basiques et même du traitement simplifié de chaînes !
location /modtest {
echo_foreach_split ‘\n’ $echo_client_request_headers;
echo "En-tete: $echo_it";
echo_end;
}
Résultat :
imil@tatooine:~$ curl -s http://coruscant/modtest
En-tete: GET /modtest HTTP/1.1
En-tete: User-Agent: curl/7.26.0
En-tete: Host: coruscant
En-tete: Accept: */*
En-tete:
ngx_echo dispose également d'une fonction echo_exec qui, au lieu de réaliser une redirection classique à travers le protocole HTTP, utilise les capacités internes du serveur nginx, gagnant ainsi quelques précieuses microsecondes :
location /modtest {
echo_exec /ailleurs page=casimir;
}
location /ailleurs {
echo "La vérité est ici: $arg_page";
}
Résultat :
imil@tatooine:~$ curl -s http://coruscant/modtest
La vérité est ici: casimir
On remarque qu'il est possible de passer des paramètres à echo_exec qu'on récupère de façon tout à fait classique dans nginx.
Un dernier exemple de fonctions utiles fournies par le module ngx_echo :
location /modtest {
echo_sleep 2;
echo_duplicate 100 ".";
echo "\nFausse erreur !";
echo_status 502;
}
Résultat :
imil@tatooine:~$ curl -s http://coruscant/modtest
..........
Fausse erreur...
imil@tatooine:~$ curl -I -s http://coruscant/modtest
HTTP/1.1 502 Bad Gateway
Server: nginx/1.5.12
Date: Sun, 23 Mar 2014 10:33:36 GMT
Content-Type: application/octet-stream
Connection: keep-alive
Ici, on a fait croire au client qu'il a fait réaliser au serveur un traitement lourd qui a échoué :
- On attend deux secondes grâce à la fonction echo_sleep ;
- On répète 100 fois une chaîne de caractères via echo_duplicate ;
- On renvoie un message quelconque ;
- On renvoie un code de retour HTTP 502.
Je vous invite à visiter la page du module [5], afin de découvrir les multiples autres capacités de ngx_echo qui mériterait un article à lui tout seul.
2. set-misc
Cette petite boîte à outils étend les possibilités d'affectation de variables. En outre, set-misc ajoute des capacités d'échappement, « déséchappement », encodage et décodage hexadécimal, MD5, SHA1, base32, Base64, ainsi que bien d'autres facilités.
Voyons par exemple comment échapper une chaîne de caractères passée en paramètre get :
location /modtest {
set_escape_uri $requete $arg_q;
echo $requete;
}
Résultat :
imil@tatooine:~$ curl -s http://coruscant/modtest?q="un paramètre non échappé"
un%20param%e8tre%20non%20%e9chapp%e9
La fonction set_if_empty fournit un moyen rapide de s'assurer qu'une variable est non vide :
location /modtest {
set $mavar $arg_meilleur;
set_if_empty $mavar "GLMF !";
echo $mavar;
}
Résultat :
imil@tatooine:~$ curl -s http://coruscant/modtest?meilleur=Bisounours
Bisounours
imil@tatooine:~$ curl -s http://coruscant/modtest
GLMF !
set_quote_sql_str, set_quote_pgsql_str, set_quote_json_str s'assurent respectivement qu'une chaîne de caractères est mise entre quotes selon les règles de MySQL, PostgreSQL et JSON.
La série de fonctions set_encode_* peut se révéler très utile pour encoder ou décoder simplement des valeurs, par exemple :
location /modtest {
set_encode_base64 $encode $arg_id;
echo $encode;
}
Résultat :
imil@tatooine:~$ curl -s http://coruscant/modtest?id=login
bG9naW4=
On peut ainsi encoder et décoder des valeurs hexadécimales, base32 ou base64, ou de la même façon, générer des signatures SHA1 ou MD5 :
location /modtest {
set_sha1 $sig $arg_id;
echo $sig;
}
Résultat :
imil@tatooine:~$ curl -s http://coruscant/modtest?id=login
2736fab291f04e69b62d490c3c09361f5b82461a
Quelques autres fonctions sont destinées à la génération aléatoire, ou pseudo-aléatoire de chaînes de caractères. Par exemple, pour produire une suite de 64 caractères aléatoires, on utilise la fonction set_secure_random_alphanum comme ceci :
location /modtest {
set_secure_random_alphanum $rnd 64;
echo $rnd;
}
Résultat :
imil@tatooine:~$ curl -s http://coruscant/modtest
ojrxwQ1hdH9yJNElUnimCBDOgDvKCKPQyMZff4T2k6MVolG8UuEBmuNzpdG3ZZLc
On remarquera également dans la liste des possibilités de set-misc des fonctions comme set_formatted_gmt_time et set_formatted_local_time, qui renseigneront une variable avec une chaîne de caractères représentant une date suivant le format standard de la fonction C strftime.
3. array-var
Le module array-var ajoute à nginx la capacité de traiter des tableaux de variables. Les fonctions sont au nombre de quatre :
- array_split découpe une variable en fonction d'un séparateur et place le résultat dans une variable représentant un tableau ;
- array_join réalise exactement l'inverse, on construira une chaîne de caractères constituée de plusieurs autres qu'on rejoindra avec un caractère ;
- array_map va décorer les résultats présents dans le tableau d'une chaîne de notre choix ;
- array_map_op quant à lui, décorera le tableau à l'aide de fonctions tierces.
Voyons tout cela dans un exemple :
location /modtest {
array_split ‘,’ $arg_vals to=$vals;
array_map_op set_sha1 $vals;
array_map "<$array_it>" $vals;
array_join ‘\n’ $vals;
echo $vals;
}
Résultat :
imil@tatooine:~$ curl -s http://coruscant/
modtest?vals=GLMF,ca,roulaize
<3ca77e0bcb9bc9346058204c7528e04f4e57427f>
<1c42c72cf95aa1b76609b585b34baf6b501d713e>
<ee6efb18d32b7e610ae0d4243566a6b88c6229c7>
Ici, on découpe les valeurs passées dans la variable get vals suivant le séparateur , puis on applique la fonction set_sha1 du module set-misc vu plus haut ; on décore la chaîne avec les caractères <>, on rejoint les éléments du tableau avec un retour chariot et on affiche le tout.
4. headers-more
Le module headers-more est une amélioration notable du module standard headers fourni avec nginx. headers-more permet d'ajouter, effacer ou placer un en-tête, il bénéficie également des capacités suivantes :
- Mise à zéro des en-têtes builtin tels que Content-Type, Content-Length ou Server ;
- Spécification d'un code HTTP grâce à une option -s ;
- Modification du type de contenu à l'aide de l'option -t.
Fonctionnalité non négligeable, headers-more a aussi la possibilité de modifier les en-têtes entrants, la preuve en images en commençant sur le serveur :
location /modtest {
more_set_input_headers ‘User-Agent: Kurt/10.8’ ‘X-Fake-Me: blebleble’;
proxy_pass http://exar/;
}
Un client :
imil@tatooine:~$ curl -s http://coruscant/modtest
Capture du trafic vers le serveur web cible du proxy_pass :
Host: exar
Connection: close
User-Agent: Kurt/10.8
Accept: */*
X-Fake-Me: blebleble
Dans l'autre sens, voici les en-têtes renvoyés par défaut par une location ne renvoyant qu'une chaîne de caractères à l'aide de la fonction echo :
imil@tatooine:~$ curl -I -s http://coruscant/modtest
HTTP/1.1 200 OK
Server: nginx/1.5.12
Date: Sun, 23 Mar 2014 13:49:03 GMT
Content-Type: application/octet-stream
Connection: keep-alive
Modifions les en-têtes à l'aide de more-headers :
location /modtest {
more_set_headers ‘Content-Type: text/plain’ ‘X-Powered-By: Goldorak’;
echo "foo";
}
Et de constater :
imil@tatooine:~$ curl -I -s http://coruscant/modtest
HTTP/1.1 200 OK
Server: nginx/1.5.12
Date: Sun, 23 Mar 2014 14:06:55 GMT
Content-Type: text/plain
Connection: keep-alive
X-Powered-By: Goldorak
À l'aide de la fonction more_clear_headers, on pourra également faire disparaître certains en-têtes :
more_clear_headers ‘Server’;
Résultat :
imil@tatooine:~$ curl -I -s http://coruscant/modtest
HTTP/1.1 200 OK
Date: Sun, 23 Mar 2014 14:09:40 GMT
Content-Type: text/plain
Connection: keep-alive
X-Powered-By: Goldorak
Nous n'avons plus d'en-tête Server.
5. encrypted-session
Le module encrypted-session fournit des capacités de chiffrement / déchiffrement pour les variables nginx. Son utilisation est classique :
- On définit une clé de chiffrement,
- On définit éventuellement un vecteur d'initialisation (IV) ; par défaut il vaut deadbeefdeadbeef,
- On définit éventuellement un temps d'expiration de la session ; par défaut elle vaut un jour,
- On chiffre la donnée.
En clair (oh-oh), cela donne :
location /modtest {
encrypted_session_key "laclesuperbalaisepourglmfblableh";
set_secure_random_lcalpha $rnd 32;
set_encrypt_session $session $rnd;
set_encode_base32 $session;
add_header Set-Cookie ‘token=$session’;
echo "foo";
}
On note dans cet exemple l'utilisation de la fonction set_secure_random_lcalpha issue du module set-misc et permettant de générer une suite aléatoire de 32 caractères alphabétiques, ainsi que la fonction set_encode_base32 qui créera une chaîne au format base32 avec la valeur de la variable chiffrée $session. Enfin, on ajoute un en-tête Set-Cookie contenant un jeton de valeur $session. Le résultat est le suivant :
HTTP/1.1 200 OK
Server: nginx/1.5.12
Date: Sun, 23 Mar 2014 14:42:44 GMT
Content-Type: application/octet-stream
Connection: keep-alive
Set-Cookie: token=bf99rjkobnho3o943eel1ram6hfj5edt8736q3d5ak1aub4l
b6tr1cka4sieqmi7v7cknuak7gqk6tc2khdlp8fekr8vhb0fia8apj8=
Le fonctionnement du déchiffrement est exactement inverse ; dans le cas présent, on convertirait la chaîne initialement en base32 à l'aide de set_decode_base32 et on récupérerait la valeur déchiffrée grâce à la commande set_decrypt_session.
6. form-input
Nous achevons cette mise en bouche avec le module form-input. Ce dernier lit les requêtes post et put afin de transformer les arguments passés dans le corps du message en variables nginx. On peut aisément imaginer utiliser ce type de fonctionnalité pour filtrer des tentatives d'intrusion.
Exemple d'utilisation :
location /modtest {
set_form_input $val;
set_if_empty $val "rien !";
echo "Input: $val";
}
On teste :
imil@tatooine:~$ curl --data "val=plop" -s http://coruscant/
modtest
Input: plop
imil@tatooine:~$ curl -s http://coruscant/modtest
Input: rien !
À noter qu'on peut également utiliser la syntaxe suivante :
set_form_input $variable val;
Dans celle-ci, on déclare que le champ concerné dans l'input form est val et la variable servant à stocker la valeur du champ est $variable.
Le module met à notre disposition une seconde fonction, set_form_input_multi, qui enregistrera dans un tableau toutes les valeurs d'un champ, par exemple :
location /modtest {
set_form_input_multi $val;
array_join ‘ ‘ $val;
echo "Input: $val";
}
Résultat :
imil@tatooine:~$ curl --data "val=plop&val=bar&val=foo" -s http://coruscant/modtest
Input: plop bar foo
On reconnaît la fonction array_join présentée plus haut dans la section array-var.
7. Plus de musique, moins de bruit
On le comprend à la lecture des possibilités offertes à nginx par ces multiples extensions, on atteint un degré de contrôle a priori sans précédent au sein d'un serveur web et, de surcroît, d'un proxy inverse. En interagissant de la sorte, et aussi facilement, avec l'intégralité des composants du protocole HTTP, on s'imagine probablement factoriser un grand nombre de scripts et applicatifs précédemment écrits pour contrôler l'interaction client / serveur.
Le travail formidable d'agentzh ne constitue cependant qu'une partie des innombrables ajouts [6] disponibles pour le serveur nginx, et de véritables perles peuplent la page du site du serveur dédiée aux modules tierce partie.
Références
[1] Définition de Content Delivery Network : http://fr.wikipedia.org/wiki/Content_delivery_network
[2] Compte GitHub de Yichun Zhang : https://github.com/agentzh/
[3] Site officiel de OpenResty : http://openresty.org/
[4] Site officiel du projet pkgsrc (portable package build system) : http://pkgsrc.org/
[5] Page de HttpEchoModule : http://wiki.nginx.org/HttpEchoModule
[6] Liste de modules non officiellement supportés par nginx : http://wiki.nginx.org/3rdPartyModules