Anubis, l'outil qui bloque la récolte agressive
Le web est un vaste terrain de jeu. Que ce soit des humains ou des robots, il y a une quantité de données et de flux qui se croisent et se bousculent.
Un peu de contexte#
Être visible et accessible sur Internet est formidable, mais cela signifie aussi qu’il est important de prendre des mesures de sécurité pour éviter les soucis.
Que vous fassiez appel à un professionnel pour héberger vos applications et services ou que vous rendiez votre serveur en ligne public, vous allez rencontrer des attaques automatisées et parfois ciblées. Ces attaques sont fréquentes, mais heureusement, il existe des outils pour les limiter et de temps en temps les contrer.
Des entreprises comme Cloudflare , Sucuri ou Akamai proposent des solutions pour se protéger contre des attaques courantes, comme le déni de service ou les pare-feu applicatifs (WAF), si vous êtes déjà en ligne. En France, des prestataires comme OVHcloud ou Scaleway offrent des services similaires, avec des options payantes selon l’offre de service.
Cependant, que vous soyez en ligne ou en local et que vous gériez vous-même l’infrastructure, vous devrez gérer la sécurité et les attaques qui en découlent. L’arrivée de l’IA ne simplifie pas les choses, car les robots peuvent apprendre et s’améliorer en permanence, contrairement aux humains. Même si vous avez configuré votre serveur web avec toutes les protections habituelles, comme les headers, les méthodes autorisées et les listes de blocage, cela ne suffit plus aujourd’hui.
J’omets volontairement tous les robots de récolte de données, ce n’est pas nouveau et même présent depuis le début d’internet. Las de cette situation suite à une interruption de son Gitea , Xe a décidé de créer un outil de blocage des robots , principalement ceux dédiés à l’entrainement des modèles pour les IA.
Anubis n’est pas un dieu#
Anubis, un outil développé en Go, présente un défi que le navigateur doit surmonter pour accéder et afficher la page sous-jacente. En cas d’échec, l’accès à la page est refusé. Pour plus de détails, consultez le site officiel de l’application à l’adresse https://anubis.techaro.lol/ .
Anubis: Web AI Firewall Utility | AnubisWeigh the soul of incoming HTTP requests to protect your website!> Anubis is a man-in-the-middle HTTP proxy that requires clients to either solve or have solved a proof-of-work challenge before they can access the site. This is a very simple way to block the most common AI scrapers because they are not able to execute JavaScript to solve the challenge.
Contexte d’utilisation#
Pour ma part, j’expose ce site web depuis un serveur en ligne ; je fais confiance à l’hébergeur pour me protéger des attaques type DDoS. Cependant, il n’est pas en mesure et ce n’est pas dans ces attributions que de me protéger des attaques plus travaillées (scraping et autres joyeusetés). Je vais alors mettre en place Anubis dans ma pile docker compose pour l’intégrer entre Traefik et ce site.
Toutes les applications que j’exposerai sur la toile seront protégées par Anubis.
Mise en place d’Anubis#
Il n’existe pas de middleware Anubis officiel pour Traefik, par conséquent plusieurs configurations s’imposent. Je vais continuer d’utiliser une configuration “dynamique” (rappel à cette adresse , rubrique “Qu’est-ce que la configuration dynamique ?”) pour effectuer la mise en place. J’utiliserai le port d’origine d’Anubis (TCP/3923). L’arborescence des fichiers est la suivante :
/opt/docker
.
├── conf
│ ├── acme.json
│ ├── anubis_private_key
│ ├── conftraefik.yml
│ ├── ghost.config.production.json
│ ├── traefikdynamic
│ │ ├── anubis.yml
│ │ ├── ghost.yml
├── docker-compose.yml
├── logs
│ ├── ghost
│ ├── traefik.logLe fichier docker-compose.yml à la racine de /opt/docker comporte la déclaration des services (les applications) que nous allons déployer. Le dossier conf contient les configurations des applications, tandis que le dossier logs est destiné aux journaux.
Déploiement de Traefik, Anubis et Ghost#
Voici le fichier docker-compose.yml qui défini la pile technique dont nous avons besoin :
---
services:
traefik:
container_name: traefik
image: traefik:chaource
networks:
- front
- backenddc
restart: always
ports:
- "443:443/tcp"
- "443:443/udp"
- "80:80/tcp"
environment:
- TZ=Europe/Paris
volumes:
- ./conf/conftraefik.yml:/etc/traefik/traefik.yml:ro
- ./conf/traefikdynamic:/dynamic
- ./logs/traefik.log:/etc/traefik/traefik.log
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./conf/acme.json:/acme.json
anubis:
container_name: anubis
image: ghcr.io/techarohq/anubis:main
restart: unless-stopped
environment:
BIND: ":8080"
TARGET: "http://traefik:3923"
DIFFICULTY: "4"
ED25519_PRIVATE_KEY_HEX_FILE: "/etc/anubis_private_key"
networks:
- backenddc
ports:
- 8080:8080
volumes:
- ./conf/anubis_private_key:/etc/anubis_private_key:ro
healthcheck:
test: ["CMD", "anubis", "--healthcheck"]
interval: 5s
timeout: 30s
retries: 5
start_period: 500ms
ghostdb:
cap_add: [SYS_NICE]
image: mysql:8.0-debian
container_name: ghostdb
restart: unless-stopped
networks:
- backendghost
volumes:
- ghost_sqldata:/var/lib/mysql
command: --innodb_use_native_aio=0
environment:
MYSQL_ROOT_PASSWORD: password
MYSQL_USER: user
MYSQL_PASSWORD: password
MYSQL_DATABASE: ghostappdb
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
timeout: 20s
retries: 10
ghostapp:
image: ghost:5.130
container_name: ghostapp
restart: unless-stopped
cap_add:
- SYS_PTRACE
networks:
- backendghost
- front
volumes:
- ./conf/ghost.config.production.json:/var/lib/ghost/config.production.json:ro
- ./logs/ghost:/var/lib/ghost/logs
- ghost_app:/var/lib/ghost/content
environment:
TZ: Europe/Paris
depends_on:
ghostdb:
condition: service_healthy
volumes:
ghost_app:
ghost_sqldata:
networks:
front:
driver: bridge
backenddc:
backendghost:Configuration de Traefik#
Passons aux fichiers de configuration de Traefik :
Fichier conftraefik.yml (le fichier de configuration principal) :
global:
sendAnonymousUsage: true
checkNewVersion: false
api:
dashboard: true
# debug: true
log:
level: INFO
filePath: "/etc/traefik/traefik.log"
format: common
maxSize: 12
maxAge: 60
compress: true
providers:
docker:
endpoint: unix:///var/run/docker.sock
exposedByDefault: false
watch: true
file:
directory: "/dynamic"
watch: true
entryPoints:
web:
address: ":80"
websecure:
address: ":443"
http2:
maxConcurrentStreams: 250
http3:
advertisedPort: 443
transport:
keepAliveMaxRequests: 120
keepAliveMaxTime: 20s
# Anubis
anubis:
address: ":3923"
certificatesResolvers:
letsencrypt:
acme:
email: contact@domain.local
caServer: https://acme-staging-v02.api.letsencrypt.org/directory
# caServer: https://acme-v02.api.letsencrypt.org/directory
storage: "/etc/traefik/acme.json"
keyType: EC256
httpChallenge:
entryPoint: webLe mode de configuration choisi pour Traefik est “dynamique”, la configuration est chargée à chaud dès qu’une modification est faite. Dans le dossier /opt/docker/conf/traefikdynamic, je vais créer deux fichiers, un pour Anubis et un autre pour Ghost.
Fichier /opt/docker/conf/traefikdynamic/anubis.yml :
http:
services:
svc-anubis:
loadBalancer:
servers:
- url: "http://anubis:8080"
routers:
rt-anubis:
entryPoints:
- "websecure"
rule: "(Host(`exemple.fr`) || Host(`blog.exemple.fr`))"
service: "svc-anubis"
middlewares:
- mw-headers-anubis
tls:
certResolver: letsencrypt
domains:
- main: "exemple.fr"
sans:
- "*.exemple.fr"
mw-headers-anubis:
headers:
hostsProxyHeaders:
- "X-Forwarded-Host"
- "X-Forwarded-Proto"
- "X-Forwarded-For"
customRequestHeaders:
X-Forwarded-Proto: "https"Fichier /opt/docker/conf/traefikdynamic/ghost.yml :
http:
services:
svc-ghost:
loadBalancer:
servers:
- url: "http://ghostapp:2368"
routers:
rt-ghost:
entryPoints:
#- "websecure"
- "anubis"
rule: "Host(`blog.exemple.fr`)"
service: "svc-ghost"
middlewares:
- mw-headers-ghost
mw-headers-ghost:
headers:
hostsProxyHeaders:
- "X-Forwarded-Host"
- "X-Forwarded-Proto"
- "X-Forwarded-For"
customRequestHeaders:
X-Forwarded-Proto: "https"
X-Forwarded-Host: "blog.exemple.fr"Il y a un routeur et un service par application ; Traefik écoute sur les ports TCP/80, TCP/443 et UDP/443 (HTTP3 QUIC) et réceptionnera les requêtes depuis l’extérieur. Le port d’Anubis (3923) ne doit pas être exposé sur le web, seulement en interne. Ensuite, selon le nom de domaine ciblé, Traefik redirigera vers le routeur Anubis, puisqu’il écoute sur le port “websecure” (TCP/443 et UDP/443). Le routeur rt-ghost écoute sur le port “anubis”, et c’est là que la magie opère : une redirection du flux sera faite si les conditions sont remplies (challenge réussi, nom de domaine (rule) correspondant entre les routeurs Anubis et Ghost).
J’ai ajouté des middleware, notamment pour les headers. En faisant les essais sur mon site, je me suis heurté à des problèmes de redirection, des changements d’URL et de protocole qui empêchaient le bon fonctionnement. Avec la définition des headers “Host”, “Proto” et “For”, il n’y a plus de place au doute (et ça marche).
Configuration de Ghost#
Une configuration simple :
{
"name": "J.HOMMET.NET",
"url": "http://ghostapp:2368",
"database": {
"client": "mysql",
"connection": {
"host": "ghostdb",
"user": "user",
"port": "3306",
"password": "password",
"database": "ghostappdb"
}
},
"server": {
"port": 2368,
"host": "0.0.0.0"
},
"privacy": {
"useUpdateCheck": false,
"useGravatar": false,
"useRpcPing": true,
"useStructuredData": true
},
"logging": {
"path": "/var/lib/ghost/logs/",
"useLocalTime": true,
"level": "info",
"rotation": {
"enabled": true,
"count": 15,
"period": "1d"
},
"transports": ["stdout", "file"]
},
"paths": {
"contentPath": "/var/lib/ghost/content"
}
}Démarrez les services, regardez les logs de démarrage des conteneurs et testez l’accès à vos services via un navigateur web.
docker compose up -d
docker compose logs -fSi tout est en ordre, lors de l’accès à votre service, vous devriez voir cette fenêtre :
“It works” comme dirait Apache 2 😀.
Dans cet article, j’utilise une seule instance Anubis pour différents services. Il agit comme un “catch ’em all”. Vous pourriez avoir une instance Anubis par service, permettant d’affiner et de spécialiser la configuration pour chaque service. Toutefois, ce mode de fonctionnement pourrait être consommateur de performance et demande une administration plus accrue, qui n’est pas forcément utile ni pratique.