Nginx et Lua, découverte d’OpenResty

Nginx est un serveur HTTP et reverse proxy utilisé par de nombreux sites. OpenResty est une surcouche construite avec de nombreux modules par défaut, ils permettent par exemple la personnalisation via des scripts Lua ou des accès simplifiés à des bases de données. Le projet est supporté par Cloudflare depuis 2011.

J’ai eu l’occasion de l’utiliser en tant que reverse proxy sur l’API d’Arte et je vous invite à regarder l’excellent retour d’expérience de François Dume au forum PHP.

Pour l’installation, il n’existe pas de package disponible donc il faut le build à la main. Tout est détaillé sur le site officiel et ne prend que quelques minutes.

Mise en place d’un reverse proxy

Le reverse proxy permet de se placer en tant qu’intermédiaire entre un utilisateur et d’autres serveurs. Il peut effectuer de nombreuses tâches telles que la gestion du cache, l’authentification ou encore la compression de données.

Nous allons voir comment mettre en place rapidement ce type de proxy. Je suppose ici que votre serveur nginx est déjà configuré. Les clés de configuration qui nous intéressent sont toutes préfixées par « proxy_ ». On passe des informations utiles au serveur interne en rajoutant des headers pour mieux identifier la requête et on cache ceux pouvant révéler des informations importantes.

location / {
    proxy_pass http://locahost/myapp;
    proxy_set_header        Host                     $host; 
    proxy_set_header        X-Real-IP             $remote_addr;
    proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_hide_header      X-Powered-By;
}

Si vous souhaitez gérer le SSL, de nombreux paramétrages sont possibles et préfixés par « proxy_ssl », notamment la comparaison du certificat envoyé avec un certificat valide au format PEM via la directive “proxy_ssl_trusted_certificate cert.pem”.

Il est aussi possible de filtrer la requête, par exemple limiter l’utilisation de certains verbes HTTP.

if ($request_method !~ ^(GET|POST)$ ) {
    return 444;
}

Le cache

On peut mettre en place un cache HTTP pour réduire le nombre d’accès réels à une ressource et augmenter le temps de réponse, de plus cela allège le serveur distant puisque la requête est gérée entièrement par le serveur nginx. Il faut d’abord définir une zone mémoire et ensuite l’utiliser avec la commande proxy_cache :

proxy_cache_path /data/nginx/cache keys_zone=one:10m;
proxy_cache one;
proxy_cache_valid 1h;

Utilisation du LUA

Openresty embarque le module HttpLuaModule permettant l’exécution de script Lua. Plusieurs directives permettent de lancer un script à différents moments de la requête, elles sont toutes de la forme « *_by_lua ». Les fonctions disponibles dans ces directives sont limitées :

  • init_by_lua: le code sera exécuté au démarrage quand le serveur nginx lit la configuration. Il est utile pour déclarer des variables globales ou précharger des modules ;

  • set_by_lua: permet d’effectuer un traitement et de récupérer le résultat dans un variable. Le code exécuté bloque la boucle d’événement de nginx et doit donc être rapide ;

  • rewrite_by_lua: le code est exécuté pour chaque requête et après l’exécution du module HttpRewrite ;

  • access_by_lua: le code est exécuté pour chaque requête et après l’exécution du module HttpAccess ;

  • header_filter_by_lua: utilisé uniquement pour filtrer les headers de la requête ;

  • content_by_lua: utilisé lorsqu’un script renvoi du contenu via par exemple ngx.say ;

  • body_filter_by_lua: le code est exécuté après réception de données de réponse et permet de modifier le contenu renvoyé au client. Il peut être lancé plusieurs fois par requête selon le volume de données ;

  • log_by_lua: le code est exécuté après l’écriture dans le access log.

Voici un exemple d’utilisation d’une de ces directives, logguer quand une application répond en plus d’une seconde :

location / {
  log_by_lua '
    if tonumber(ngx.var.upstream_response_time) >= 1 then
      ngx.log(ngx.WARN, "[WARN] Ngx upstream response time: " .. ngx.var.upstream_response_time .. "s from " .. ngx.var.upstream_addr);
    end
  ';
}

Le script Lua est directement écrit dans la configuration nginx. Il est conseillé de mettre le code dans un fichier pour faciliter la maintenance grâce aux directives « *_by_lua_file ».

Authentification

Si l’on cherche à protéger plusieurs applications derrière un unique système d’authentification, il existe plusieurs solutions.

La directive « auth_request » permet d’effectuer une sous requête sur une url et si le code retour est différent de 200, le serveur retourne un access denied.

On peut construire un système d’authentification plus complexe grâce au Lua, par exemple pour authentifier un utilisateur via oauth avec un access token passé en paramètre :

local token = ngx.var.arg_access_token
local checkToken = ngx.location.capture('/oauth/check?access_token=' .. token)
if checkToken.status == ngx.HTTP_OK then
...

La fonction capture permet d’effectuer des sous-requêtes nginx vers des serveurs internes.

Pour effectuer des requêtes vers l’extérieur, il est nécessaire de créer un proxy pass :

location /_hello_google { 
    proxy_pass http://google.fr; 
}

Il est aussi possible d’utiliser un driver http (non testé).

Throttling

Un autre cas intéressant est celui de la limitation des requêtes. Un module nginx de throttling existe mais permet seulement d’identifier par IP. Avec l’utilisation du Lua, il est possible de construire son propre module de throttling en le couplant à l’authentification Oauth par exemple. De nombreux drivers backend sont disponibles pour répondre aux problématiques de stockage (MySQL, PostgreSQL, Memcached, Redis).

count = red:get('throttle_' .. throttleCacheKey) -- Récupération d’une valeur dans Redis
if tonumber(count) > userRateLimit then
    ngx.header.content_type = 'application/json';
    ngx.status = 429
    ngx.say('{"message": "API rate limit exceeded."}');
     ngx.exit(429);
end

Expérimenter

Pour vous faciliter la configuration du serveur, il existe des configurations vagrant et docker avec openresty :

Tester

Il est possible de tester unitairement vos scripts Lua avec cette librairie Perl mais je ne m’y suis pas aventuré. Vous pouvez néanmoins utiliser des outils tels que casperJS pour écrire des tests d’intégrations.

Voir plus loin

En effectuant mes recherches, je suis tombé sur Lapis, le serveur web et l’application ne font plus qu’un ! L’ensemble des fonctionnalités que l’ont peut espérer d’un framework web sont présentes : templating, session, CSRF, ORM. Toutefois seul PostgreSQL est supporté et il faut être bien familiarisé avec le langage Lua. Il est aussi possible de développer en utilisant Moonscript qui est un langage compilant en Lua (similaire au Coffeescript pour Javascript).

Une autre utilisation intéressante est le monitoring, New relic propose des exemples d’intégration de son SDK grâce au Lua.

Je n’ai fait qu’une brève présentation des possibilités d’OpenResty et si vous souhaitez en apprendre plus et utiliser le Lua pour améliorer votre serveur Web, je vous conseille ces différentes lectures :

blog comments powered by Disqus