NGINX / OpenResty, mise en place de la géolocalisation et exploitation avec des scripts LUA

NGINX / OpenResty, mise en place de la géolocalisation et exploitation avec des scripts LUA

Chez un de nos clients, nous avons un reverse proxy NGINX qui nous sert actuellement pour plusieurs usages :

  • Reverse proxy
  • Restriction d’utilisation par IP
  • Gestion de whitelist/Blacklist
  • Serveur d’éléments statiques (images, pdf, css, js, police…)

Nous sommes en train d’étudier 2 nouvelles fonctionnalités : La géolocalisation des IP et, via Open Resty, l’extension par le langage LUA

Géolocalisation des IP dynamique sur NGINX

Le besoin est le suivant :

Nous sommes régulièrement victime d’aspiration de données sur les parties du site exposées au grand public. Or nous avons remarqué que la consultation légitime est essentiellement en France, et, inversement, l’aspiration de données provient surtout d’IP de quelques pays (Brésil, Chine, Russie pour les principaux).

La provenance de l’IP est donc un indicateur intéressant pour déterminer si une IP est suspecte ou pas. On pourra derrière envisager un traitement particulier pour ces IP (restriction sur le volume d’appel, mise en place de captcha plus systématique…).

NGINX permet cette géolocalisation

Installation

Je l’ai testé sur une ancienne instance NGINX sur Redhat. La procédure sera à adapter selon le contexte.

1. Installer le module geoip

sudo yum install nginx-module-geoip

2. Récupérer les bases de données (MaxMind)

sudo mkdir /etc/nginx/geoip
cd /etc/nginx/geoip
sudo wget http://geolite.maxmind.com/download/geoip/database/GeoLiteCountry/GeoIP....
gunzip GeoIP.dat.gz
sudo gunzip GeoIP.dat.gz
sudo wget http://geolite.maxmind.com/download/geoip/database/GeoLiteCity.dat.gz
sudo gunzip GeoLiteCity.dat.gz

3. Configurer NGINX

Puis ajouter ceci en début de fichier /etc/nginx/nginx.conf :

load_module modules/ngx_http_geoip_module.so;
load_module modules/ngx_stream_geoip_module.so;

Puis

http {
    geoip_country /etc/nginx/geoip/GeoIP.dat; # the country IP database
    geoip_city /etc/nginx/geoip/GeoLiteCity.dat; # the city IP database    geoip_proxy           10.75.100.22;
    geoip_proxy_recursive on;

[…]

# Faire apparaitre dans les logs :

    log_format custom '"$http_x_forwarded_for" - $remote_user [$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent" $gzip_ratio $hostname $geoip_org $geoip_country_code $remote_addr';
    access_log /var/log/nginx/access.log custom;

# utiliser les infos de géoloc

server {
[…]
    location / {
   if ($geoip_country_code ~ "BR") {
     rewrite ^(.*) https://www.youtube.com/watch?v=GMs3LaxYK4c redirect;
     […]
   }
[…]

Références

http://nginx.org/en/docs/http/ngx_http_geoip_module.html#geoip_proxy_recursive

https://www.howtoforge.com/tutorial/how-to-use-geoip-with-nginx-on-ubuntu-16.04/#-find-out-if-nginx-has-support-for-geoip

 

Extension d’NGINX avec LUA

Une fois les IP géolocalisées, j’ai plusieurs pistes pour exploiter cette donnée.

L’idée est d’éviter les robots en règle générale. La contrainte est que la FFF ne veut pas de captcha. De plus dans notre cas, on se trouve face à des attaquants qui changent d’IP à chaque appel et tournent sur un très grand nombre d’IP (nous blacklistons actuellement plus de 15000 IP et 2500 plages d’IP), il faut donc présenter une captcha d’emblée.

Le compromis trouvé est de ne mettre en place ce procédé un peu barbare que sur les pays qui fournissent l’essentiel de ces IP suspectes et qui n’est pas notre public privilégié.

J’ai envisagé 2 solutions :

  • Gérer une liste d’IP ayant réussi le test du captcha dans une base redis et consulter cette liste depuis NGINX pour laisser passer la requête ou pour la rediriger sinon vers la page de captcha.
  • Créer un cookie en cas de réussite du captcha contenant un hash(timestamp ;IP ;UA ;secret)+timestamp et laisser passer un utilisateur ayant ce cookie après vérification de l’IP et du UA. (Je penche pour cette solution pour des raisons de performance)

Pour ces deux solutions, je n’ai pas trouvé d’autre solution qu’utiliser la version NGINX du projet Open Resty compilé avec le module LUA.

J’ai fait un POC en utilisant une image Docker

 

Voici la configuration du service dans le docker-compose.yml du projet pour le POC réalisé :

    nginx:

        image: openresty/openresty:alpine-fat
        container_name: nginx_fff
        ports:
          - 1118:80
        volumes:
          - /home/vagrant/workspace/Plateform/plateforme-fff/config/nginx/nginx.conf:/usr/local/openresty/nginx/conf/nginx.conf
          - /home/vagrant/workspace/Plateform/plateforme-fff/config/nginx/geoip/:/usr/local/openresty/nginx/conf/geoip/
          - /home/vagrant/workspace/Plateform/plateforme-fff/logs/nginx/:/usr/local/openresty/nginx/logs/nginx/
          - /home/vagrant/workspace/Plateform/plateforme-fff/config/nginx/html/:/usr/local/openresty/nginx/html/
        labels:
          traefik.frontend.rule: "Host:www-docker.fff.fr"
        networks:
          - traefik-network

Ensuite l’utilisation de LUA est assez simple.

Exemple pour REDIS (exemple directement repris de https://github.com/openresty/lua-resty-redis ) :

        location /test {
            content_by_lua_block {
                default_type 'text/plain';
                local redis = require "resty.redis"
                local red = redis:new()

                red:set_timeout(1000) -- 1 sec

                -- or connect to a unix domain socket file listened
                -- by a redis server:
                --     local ok, err = red:connect("unix:/path/to/redis.sock")

                local ok, err = red:connect("172.22.0.6", 6379)

                if not ok then
                    ngx.say("failed to connect: ", err)
                    return
                end

                ok, err = red:set("dog", "an animal")

                if not ok then
                    ngx.say("failed to set dog: ", err)
                    return
                end

                ngx.say("set result: ", ok)

[…]

Ou encore un test de hashage :

        location /verif_cookie {
            default_type 'text/plain';

            content_by_lua_block {
                local cookiecaptchafffcontent = ngx.var.cookie_captchafffcontent
                local cookiecaptchaffftime = ngx.var.cookie_captchaffftime
                local content = ngx.var.cookie_captchaffftime .. ";" ..  ngx.var.http_x_forwarded_for .. ";" ..  ngx.var.http_user_agent .. ";ggd-sh4$6*fg4h"
                ngx.say(content)

                local resty_sha256 = require "resty.sha256"
                local str = require "resty.string"
                local sha256 = resty_sha256:new()
                ngx.say(sha256:update(content))
                local digest = sha256:final()
                ngx.say("sha256: ", str.to_hex(digest))
                ngx.say("cookie captcha: ", cookiecaptchafffcontent)
            }
        }

Références

https://github.com/openresty/lua-nginx-module

https://github.com/openresty/lua-resty-redis

https://github.com/openresty/lua-resty-string

Conclusion

NGINX est un logiciel puissant largement utilisé en tant que reverse proxy et serveur web.

On a une forte modularité qui permet de facilement étoffer les fonctionnalités, ici avec les exemples de modules lua du projet OpenResty et ngx_http_geoip_module.

Beaucoup de modules sont disponibles ici : http://nginx.org/en/docs/

OpenResty étend encore les possibilités avec LUA : https://github.com/openresty/

Attention à bien penser l’usage de ces modules pour conserver des bonnes performances sur ce composant, de bien lire la documentation afin d’opter pour des solutions natives performantes avant de tout recoder en LUA.

 

Guillaume G.

Ajouter un commentaire