Vendredi 23 novembre 2018

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
...

Lua Authentification/cookies

Writing an nginx authentication module in Lua

Les dossiers de configuration nginx en mode su

sudo -s 
# renommer le dossier de configuration **default.conf**
mv /etc/nginx/conf.d/default.conf /etc/nginx/conf.d/default.conf.sav
# dossier configuration vhost
mkdir /etc/nginx/conf.d/xinyiczen.xyz.d

configuration nginx de base

nano /etc/nginx/conf.d/xinyiczen.xyz.conf
server {
    listen 80;
    listen [::]:80;
    server_name xinyiczen.xyz *.xinyiczen.xyz;

        location /.well-known/acme-challenge {
            default_type "text/plain";
            root         /tmp/letsencrypt-auto;
        }

        location / {
                return 301 https://$server_name$request_uri;
        }
}
server {
    listen 443 ssl;
    listen [::]:443 ssl;
    server_name xinyiczen.xyz *.xinyiczen.xyz;
    ssl_certificate /etc/ssl/private/xinyiczen.xyz.crt.pem;
    ssl_certificate_key /etc/ssl/private/xinyiczen.xyz.key.pem;
    ssl_session_timeout 5m;
    ssl_session_cache shared:SSL:50m;
    ssl_prefer_server_ciphers on;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers ALL:!aNULL:!eNULL:!LOW:!EXP:!RC4:!3DES:+HIGH:+MEDIUM;

    add_header Strict-Transport-Security "max-age=31536000;includeSubDomains";

    ssl_dhparam /etc/ssl/private/dh4096.pem;

    ## CSP nginx
    add_header Content-Security-Policy "default-src 'self' *.xinyiczen.xyz; img-src 'self' data: https: *.xinyiczen.xyz";
    # autoriser uniquement les Iframes provenant du même domaine
    add_header X-Frame-Options SAMEORIGIN;
    # activer la protection contre les attaques Xss et MIME
    add_header X-XSS-Protection "1; mode=block";
    add_header X-Content-Type-Options nosniff;

    include conf.d/xinyiczen.xyz.d/*.conf;

    access_log /var/log/nginx/xinyiczen.xyz-access.log;
    error_log /var/log/nginx/xinyiczen.xyz-error.log;

}

Configuration lua

mkdir /var/www/lua
nano /etc/nginx/conf.d/xinyiczen.xyz.d/lua.conf
location /lua {
 	alias /var/www/lua/;
	access_by_lua_file /etc/nginx/conf.d/test.lua;

	client_max_body_size 10G;
	index index.php index.html;
	try_files $uri $uri/ index.php;
        location ~ \.php$ {
           fastcgi_split_path_info ^(.+\.php)(/.+)$;
           fastcgi_pass unix:/run/php/php7.0-fpm.sock;
           fastcgi_index index.php;
           include fastcgi_params;
           fastcgi_param SCRIPT_FILENAME $request_filename;
        }

}

fichier test.lua

nano /etc/nginx/conf.d/test.lua
-- test 
local secretkey = "dqhNnp9M6h5BB77i3Q9V"
 if ngx.var.cookie_uid == nil or ngx.var.cookie_nickname == nil or ngx.var.cookie_token == nil then
   ngx.req.set_header ("Check-Login", "NULL")
   return
 end
 local ctoken = ngx.md5 ("uid:" .. ngx.var.cookie_uid .. "&nickname:" .. ngx.var.cookie_nickname .. "&secretkey:" ..secretkey)
 ngx.req.set_header ("ctoken", ctoken)
 if ctoken == ngx.var.cookie_token then
   ngx.req.set_header ("Check-Login", "Yes")
 else
   ngx.req.set_header ("Check-Login", "No")
 end
return 

Relancer serveur

service nginx restart

fichier test.php

nano /var/www/lua/test.php
<pre><?php
echo "\$_SERVER:\r\n";
unset ($_SERVER['SCRIPT_FILENAME']);
unset ($_SERVER['DOCUMENT_ROOT']);
unset ($_SERVER['PATH']);
unset ($_SERVER['hostname']);
unset ($_SERVER['GATEWAY_INTERFACE']);
print_r ($_SERVER);
echo "\r\n\$_COOKIE:\r\n";
print_r ($_COOKIE);
?>

Tests via curl

Accès aux tests : https://xinyiczen.xyz/lua/test.php
Les différents essais réalisés depuis un poste du réseau
Le token 43b7d1aa6ed1ea30bc41214c19cfef88 : md5(uid:7890&nickname:yan&secretkey:dqhNnp9M6h5BB77i3Q9V)
Le test :

curl -b "uid=7890; nickname=yan; token=43b7d1aa6ed1ea30bc41214c19cfef88" "https://xinyiczen.xyz/lua/test.php"
<pre>$_SERVER:
Array
(
    [USER] => www-data
    [HOME] => /var/www
    [HTTP_CHECK_LOGIN] => Yes
    [HTTP_CTOKEN] => 43b7d1aa6ed1ea30bc41214c19cfef88
    [HTTP_COOKIE] => uid=7890; nickname=yan; token=43b7d1aa6ed1ea30bc41214c19cfef88
    [HTTP_ACCEPT] => */*
    [HTTP_USER_AGENT] => curl/7.51.0
    [HTTP_HOST] => xinyiczen.xyz
    [REDIRECT_STATUS] => 200
    [SERVER_NAME] => xinyiczen.xyz
    [SERVER_PORT] => 443
    [SERVER_ADDR] => 46.166.168.130
    [REMOTE_PORT] => 51398
    [REMOTE_ADDR] => 78.230.171.39
    [SERVER_SOFTWARE] => nginx/1.10.1
    [HTTPS] => on
    [REQUEST_SCHEME] => https
    [SERVER_PROTOCOL] => HTTP/1.1
    [DOCUMENT_URI] => /lua/test.php
    [REQUEST_URI] => /lua/test.php
    [SCRIPT_NAME] => /lua/test.php
    [CONTENT_LENGTH] => 
    [CONTENT_TYPE] => 
    [REQUEST_METHOD] => GET
    [QUERY_STRING] => 
    [FCGI_ROLE] => RESPONDER
    [PHP_SELF] => /lua/test.php
    [REQUEST_TIME_FLOAT] => 1480964985.391
    [REQUEST_TIME] => 1480964985
)

$_COOKIE:
Array
(
    [uid] => 7890
    [nickname] => yan
    [token] => 43b7d1aa6ed1ea30bc41214c19cfef88
)

php et ssowat

Une fois l’utilisateur authentifié par SSOwat, ses credentials sont passés à l’application Web avec les headers “standard” PHP_AUTH_USER et PHP_AUTH_PW.
Tu n’as donc qu’à implémenter l’authentification basique HTTP (https://fr.wikipedia.org/wiki/Authentification_HTTP#M.C3.A9thode_Basic) dans ton app pour avoir le support derrière SSOwat.

Identification HTTP avec PHP
$_SERVER est un tableau contenant des informations comme les en-têtes, dossiers et chemins du script.
Les entrées de ce tableau sont créées par le serveur web. Il n’y a aucune garantie que tous les serveurs les rempliront tous ; certains en oublieront quelques-unes et en rajouteront de nouvelles non mentionnées ici. Cependant, un grand nombre de ces variables fait partie des » spécifications CGI/1.1, et vous pouvez donc vous attendre à les retrouver.