Automatiser la création de filtres d'URL HTTP pour Fail2ban avec Ansible
Introduction
Sur un serveur web, il peut arriver que l'on souhaite bannir certaines URL pour plusieurs motifs (faille logiciel 0-day, endpoint critique exposé etc...). Pour cela, Fail2ban est un bon moyen de bannir les adresses IP de toute tentative d'accès à une URL. Nous allons voir comment automatiser la création de ces règles avec Ansible.
Les filtres Fail2ban
Avant d'automatiser, il faut comprendre le bon fonctionnement des filtres dans Fail2ban.
Partons du principe que l'on a un serveur web Nginx avec Fail2ban installé sur le serveur et que l'on souhaite bannir pendant 1 an, au bout de 5 tentatives, les requêtes HTTP entrantes avec la méthode GET sur l'endpoint /robots.txt ainsi que les requêtes POST sur l'endpoint /api/v2
Pour ce faire, il est nécessaire de créer 2 fichiers. Un fichier que l'on va nommer http-custom.conf
qui sera situé dans le répertoire /etc/fail2ban/filter.d
. Ce fichier ressemblera à cela :
[Definition]
failregex = ^<HOST>.*GET.*/robots.txt.*$
^<HOST>.*POST.*/api/v2.*$
ignoreregex =
La directive failregex
prend en entrée une ou plusieurs regex qui, lorsqu'elle(s) matchera dans le fichier de log Nginx, ira bannir l'adresse IP du client au bout du nombre de tentatives que nous choisissons.
Le deuxième fichier que nous allons créer sera nommé http.conf
qui se trouvera dans le répertoire /etc/fail2ban/jail.d
et aura ce contenu :
[http-custom]
enabled = true
filter = http-custom
logpath = /var/log/nginx/access.log
maxretry = 5
findtime = 300
bantime = 31536000
banaction = iptables-allports
port = anyport
Le rôle Ansible
Automatisons tout cela avec un rôle Ansible plutôt court et principalement basé sur des Templates Jinja2. Une seule task sera créée, uniquement pour la copie du Template sur le(s) serveur(s) distant.
Voici à quoi devrait ressembler votre rôle Ansible avec le playbook fail2ban.yml
.
└── fail2ban.yml/
├── fail2ban/
├── ├── defaults
├── │ └── main.yml
├── ├── handlers
├── │ └── main.yml
├── ├── tasks
├── │ └── main.yml
└── └── templates/
├── ├── etc
├── │ ├── fail2ban
├── │ │ ├── filter.d
├── │ │ │ ├── http-custom.conf.j2
├── │ │ └── jail.d
└── │ │ ├── http.conf.j2
Les variables
Afin d'automatiser toutes les ressources qui seront crées, nous allons nous appuyer sur une liste de variables YAML qui nous permettra de créer autant de filtre que nous souhaitons, spécifier de quel serveur web il s'agit etc.... Le choix a été fait de cette manière car cela permet notamment de connaître l'état des filtres en place.
Créons notre fichier defaults/main.yml
:
Vous avez compris le principe. Vous pouvez ajouter autant de patterns d'URL que vous le souhaitez, autant de méthodes HTTP etc...
Les templates Jinja2
La pierre angulaire de notre rôle se situe dans le fichier http-custom.conf
et dans la regex qui est contenue dedans.
Place à la création du Template Jinja2 se trouvant dans templates/etc/fail2ban/filter.d/http-custom.conf.j2
#jinja2: lstrip_blocks: True, trim_blocks: False
[Definition]
failregex = {% for http in fail2ban.http -%}
{% set outer_loop = loop -%}
{% for pattern in http.patterns %}{% if not outer_loop.first or (outer_loop.first and not loop.first) %}{% filter indent(12) %}
^<HOST>.*{{ http.method }}.*{{ pattern }}.*${% endfilter %}{% else %}^<HOST>.*{{ http.method }}.*{{ pattern }}.*${% endif -%}
{% endfor -%}
{% endfor %}
ignoreregex =
Ce Template est assez complexe car la syntaxe de Fail2ban est très restrictive. En effet, si l'on souhaite avoir plusieurs regex dans la directive failregex, il faut absolument que chacune des regex soit alignée à l'espace près ! Il faut également 2 boucles for afin de parcourir la liste YAML qui est imbriquée.
La première ligne #jinja2: lstrip_block: True, trim_block: False
est essentielle car Ansible dispose du moteur Jinja2 de manière un peu différente. Cette ligne sera donc interprétée et ne figurera pas dans le Template final qui sera déployé.
Le deuxième Template Jinja2 est le fichier qui va venir créer un "jail" qui définit les règles de banissement pour un filtre donné. Il s'agit du fichier templates/etc/fail2ban/jail.d/http.conf.j2
[http-custom]
enabled = true
filter = http-custom
{% if fail2ban.webserver == "apache" -%}
logpath = /var/log/apache2/access.log
{% elif fail2ban.webserver == "nginx" -%}
logpath = /var/log/nginx/access.log
{% endif -%}
maxretry = {{ fail2ban.max_retries }}
findtime = 300
bantime = {{ fail2ban.ban_days * 86400 }}
banaction = iptables-allports
port = anyport
La task
Afin de copier les Templates Jinja2 sur le serveur distant, une task est nécessaire :tasks/main.yml
- name: Create custom HTTP filter
template:
src: "{{ item }}.j2"
dest: "/{{ item }}"
owner: root
group: root
mode: '0644'
loop:
- etc/fail2ban/jail.d/http.conf
- etc/fail2ban/filter.d/http-custom.conf
notify: Restart fail2ban
when: fail2ban.http is defined
Cette task s'exécutera uniquement si la liste définie dans notre fichier de variables n'est pas vide, et viendra relancer le service Fail2ban afin d'appliquer les changements.
Le Handler
Voici le Handler qui définit la relance du service précedemement énoncée.handlers/main.yml
---
- name: Restart fail2ban
service:
name: fail2ban
state: restarted
Voilà tout y est ! Vous pouvez maintenant utiliser ce rôle Ansible pour déployer des filtres en masse.