Blog & Astuces

Empêcher l'accès à un service nginx depuis certains pays

Tuto

Dans ce tutoriel, nous allons voir comment empêcher l'accès à un service nginx depuis certains pays, ou de le rendre accessible que depuis certains pays.

Ce tutoriel fait également suite à un tutoriel sur la mise en place d'un service DNS over TLS/HTTPS en expliquant comment rendre accessible le service depuis certains pays uniquement pour améliorer la sécurité du service.

Pré-requis

  • Un serveur Linux
  • Un service nginx à protéger

Introduction

Pour mettre en place cette protection, nous allons devoir dans un premier temps aller récupérer une base de données de Geo-IP et mettre en place un script qui va mettre à jour cette base de données une fois par semaine.

En effet, les bases de données sont souvent mises à jour pour identifier de nouvelles IP. La base de données permet de situer précisément une IP, mais nous allons nous contenter d'utiliser une base de données qui n'identifie que le pays.

Ensuite, nous allons mettre en place de la configuration nginx qui va permettre de lire cette base de données et effectuer le blocage selon une liste de pays.

Téléchargement de la base de données

La base de données est disponible à l'adresse suivante : https://www.miyuru.lk/geoiplegacy

Nous allons nous contenter de récupérer la base de données "IPv6/IPv4" de "Maxmind - Country" qui est téléchargeable ici : https://dl.miyuru.lk/geoip/maxmind/country/maxmind.dat.gz

Une fois téléchargée, il faut extraire l'archive et placer le fichier .dat dans un dossier accessible à nginx. Si vous utilisez Docker, ajouter la base de données parmi la liste des volumes d'un fichier docker-compose.yml par exemple :

version: "3"
services:
  nginx:
    (reste du fichier)
    volumes:
      - ./maxmind.dat:/etc/geoipdb/maxmind.dat

Mise en place du script de mise à jour automatique

Nous allons ensuite mettre en place le script qui permet de mettre à jour automatiquement la base de données.

Créez un fichier update-geoip-legacy dans le dossier /etc/cron.weekly avec ce contenu :

#!/bin/bash
mkdir -p /CHEMIN/VERS/LE/DOSSIER/OU/STOCKER/LA/BDD
cd /CHEMIN/VERS/LE/DOSSIER/OU/STOCKER/LA/BDD
curl -sS -L https://dl.miyuru.lk/geoip/maxmind/country/maxmind.dat.gz > db.gz
[ -f maxmind.dat ] && mv maxmind.dat maxmind.dat.old
gunzip -N db.gz
docker container restart nginx

Modifiez le /CHEMIN/VERS/LE/DOSSIER/OU/STOCKER/LA/BDD avec le chemin où vous souhaitez stocker la base de données, par exemple le dossier où vous avez créé le fichier docker-compose.yml de votre instance de nginx.

Voici ce que fait le script :

  • Dans un premier temps, on crée le dossier s'il n'existe pas
  • On se rend dans le dossier
  • Ensuite, nous téléchargeons la base de données depuis le site. La base de données est au format archive gz
  • Nous faisons ensuite un backup de l'ancienne base de données vers le fichier .old
  • Ensuite, nous extrayons le fichier .dat de la base de données depuis l'archive dans le dossier
  • Pour finir, on redémarre le container nginx. A modifier selon la façon dont est mise en place votre instance de nginx

Sauvegardez le fichier, puis rendez-le exécutable avec la commande : sudo chmod +x update-geoip-legacy.

Et voilà, normalement la base de données se mettra à jour automatiquement chaque semaine.

Mise en place de la configuration nginx

Nous allons ensuite mettre en place la configuration qui permettra de bloquer les IPs qui ne sont pas dans la liste des pays autorisés.

Au tout début de votre fichier de configuration, ajoutez les lignes suivantes :

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

Elles permettent de charger le module de blocage geoip, pour les directives stream et http.

Puis toujours dans votre configuration, ajoutez la ligne suivante dans la directive http ou stream :

geoip_country /etc/geoipdb/maxmind.dat;

Changez le chemin selon où se situe la base de données, ou selon le chemin indiqué dans le mapping des volumes dans le docker-compose.yml.

Ensuite, il faut créer la map qui listera les pays autorisés ou bloqués. Ajouter le contenu suivant à la suite de la ligne précédente :

map $geoip_country_code $status_access {
  default blocked;
  FR dns;
  BE dns;
}

Ici par exemple, nous autorisons uniquement l'accès au service depuis une IP française ou belge.

Il est possible de faire l'inverse, en mettant en place la configuration suivante :

map $geoip_country_code $status_access {
  default dns;
  FR blocked;
  BE blocked;
}

Ici on autorise tout le monde sauf les IP françaises ou belges.

Ensuite il y a différents cas selon si votre service est dans une directive http ou stream.

Si vous êtes dans une directive http, vous pouvez ajouter une condition dans la directive server pour bloquer l'accès à votre service :

if ($status_access = blocked) {
  return 403;
}

Ici, nous renvoyons une erreur 403

Si vous êtes dans une directive stream, il faut créer deux nouveaux upstream pour le cas bloqué ou non :

upstream dns {
    zone dns 64k;
    server 127.0.0.1:53;
}

upstream blocked {
  server 127.0.0.1:9997;
}

Puis ajouter dans la directive http un nouveau serveur qui écoutera sur le port 9997 (port qui pourra être changé) pour bloquer les connexions :

server {
    listen 9997;
    return 403;
}

Quant au cas OK, vous devrez rediriger vers votre service classique, ici par exemple c'est un service DNS.

Exemples de configuration : cas DoT et DoH

Ici, je vais montrer quelques exemples de configuration, à la suite du tutoriel sur la mise en place d'un service DNS over TLS et DNS over HTTPS avec Pi-Hole

Cas DoT

Le fichier de configuration qui a été mis en place dans le précédent tutoriel ressemblera à cela :

load_module "modules/ngx_stream_geoip_module.so";

events {
   worker_connections 1024;
}

worker_processes [NOMBRE_DE_COEURS_DE_PROCESSEUR];
worker_cpu_affinity auto;

http {
   server_tokens off;

   server {
       listen 9998;
       return 444;
   }
}

stream {
   geoip_country /etc/geoipdb/maxmind.dat;

   log_format basic '$remote_addr [$time_local] '
                 '$protocol $status sent=$bytes_sent received=$bytes_received '
                 '$session_time';
   access_log /dev/stdout basic buffer=16k;
   error_log /var/log/nginx/error.log error;

   limit_conn_zone $binary_remote_addr zone=addr:10M;

   map $geoip_country_code $status_access {
       default blocked;
       FR dns;
       BE dns;
   }

   upstream dns {
       zone dns 64k;
       server 127.0.0.1:53;
   }

   upstream blocked {
      server 127.0.0.1:9998;
   }

   # DoT server for decryption
   server {
        listen 853 ssl;
        ssl_protocols TLSv1.2 TLSv1.3;
        ssl_handshake_timeout 30s;
        ssl_session_timeout 4h;
        ssl_session_cache shared:DNS:10m;
        ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
        ssl_certificate /etc/letsencrypt/live/[VOTRE_NOM_DE_DOMAINE]/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/[VOTRE_NOM_DE_DOMAINE]/privkey.pem;
        proxy_pass dns;
        limit_conn addr 18;
        limit_conn_log_level info;
    }
}

Ici, nous avons ajouté la ligne load_module pour ajouter le module, puis on a chargé la base de données avec la directive geoip_country. Puis la map qui liste les blocages d'IP a été ajoutée comme vu auparavant. Ensuite, on a ajouté le nouvel upstream blocked qui redirige vers le port 9998 qui est bloqué et renvoie une erreur 444. Le serveur d'écoute sur le port 9998 a été ajouté dans la directive http.

Ne pas oublier d'ajouter le fichier de base de données maxmind dans la liste des volumes du docker-compose.yml !

Cas DoH

De la même manière, la configuration deviendra :

load_module modules/ngx_stream_js_module.so;
load_module "modules/ngx_stream_geoip_module.so";

events {
    worker_connections 1024;
}

worker_processes [NOMBRE_DE_COEURS_DE_PROCESSEUR];
worker_cpu_affinity auto;

http {
    server {
        listen 9997;
        return 403;
    }

    upstream dohloop {
        zone dohloop 64k;
        server 127.0.0.1:8053;
    }

    server {
        listen         443 ssl;
        listen         [::]:443 ssl;
        server_name    [VOTRE_NOM_DE_DOMAINE];
        ssl_certificate /etc/letsencrypt/live/[VOTRE_NOM_DE_DOMAINE]/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/[VOTRE_NOM_DE_DOMAINE]/privkey.pem;

        http2 on;

        location /dns-query {
           proxy_http_version 1.1;
           proxy_set_header Connection "";
           proxy_pass http://dohloop;
        }
    }
}

stream {
   js_import /etc/nginx/njs.d/dns/dns.js;
   geoip_country /etc/geoipdb/maxmind.dat;
   limit_conn_zone $binary_remote_addr zone=addr:10M;

   map $geoip_country_code $status_access {
       default blocked;
       FR dns;
       BE dns;
   }

   upstream dns {
       zone dns 64k;
       server 127.0.0.1:53;
   }

   upstream blocked {
      server 127.0.0.1:9997;
   }

   # DoH server for decryption
   server {
      listen 127.0.0.1:8053;
      js_filter dns.filter_doh_request;
      proxy_pass dns;
   }
}

Les mêmes configurations ont été ajoutées par rapport au DoT :

  • Ajout de la ligne load_module
  • Ajout du serveur écoutant le port 9997 et renvoyant une erreur 403
  • Ajout de la ligne geoip_country pour importer la base de données
  • Ajout de la map listant les pays bloqués et autorisés
  • Mise en place d'un nouvel upstream blocked

De la même manière, ne pas oublier d'ajouter le fichier de base de données maxmind dans la liste des volumes du docker-compose.yml !

Conclusion

Dans ce tutoriel, nous avons vu comment bloquer l'accès à un service exposé par un serveur nginx depuis certains pays, afin d'améliorer la sécurité. Pour le mettre en place, nous avons téléchargé la base de données GeoIP, mis en place un script qui la met à jour chaque semaine puis nous avons modifié la configuration du serveur nginx pour mettre en place ce blocage.

Commentaires