Vue normale

Reçu hier — 14 avril 2025Pofilo.fr

[Anubis] Utiliser la preuve de travail pour bloquer les robots

14 avril 2025 à 02:00

Bonjour à tous,

Le mois dernier, je vous parlais de mon problème lié aux crawlers d’IA en bloquant l’accès à mon serveur à des pays entiers. Aujourd’hui, je vais vous montrer comment j’ai mis en place Anubis avec Traefik pour réussir à ne bloquer (que ?) les crawlers et les bots.

Contexte

Mon instance Gitea, comme toutes les forges logicielles publiques, se fait tabasser par les robots scannant ce genre d’outils pour “améliorer/nourrir” des IA. Dans un monde idéal (et j’en parlais dans mon dernier article), le fichier robots.txt est respecté et aucun abus n’a lieu, fin de l’histoire. Sauf que dans le monde de l’IA, on se fout des règles, on se fout de tout. Il suffit de voir ce genre d’article dont le titre est littéralement:

OpenAI dit que c’est fini s’ils ne peuvent pas voler les contenus Copyrightés

Donc on ne respecte pas ce fichier, et on tabasse tout le monde pour faire la course à qui a la plus grosse (IA).

Le mois dernier, j’avais donc montré comment j’avais du en arriver à bloquer des pays entiers au niveau pare-feu. La solution ne me plaisait pas car elle exclue également tous les utilisateurs légitimes de ces pays. Aussi, ça voudrait aussi dire que les entreprises scannant illégitimement le web auraient gagné.

De plus, chaque jour, le spam venait de nouveaux pays au point où je n’avais whitelisté que la France. J’en étais arrivé au point où j’avais totalement coupé mon instance Gitea, et je la démarrai juste quand j’avais besoin de commit un truc …

Mais ça, c’était en attendant une solution plus pérenne que j’ai pu mettre en place la semaine dernière.

Présentation d’Anubis

Anubis est le fruit de l’exaspération de Xe Iaso par ces crawlers d’IA qui ne respectent rien. Le code source est disponible ici.

Il s’agit un reverse-proxy écrit en Go qui exige la résolution d’un défi de preuve de travail (Proof of work en anglais). Si le défi est réussi, alors l’accès au service est autorisé et un cookie est déposé pour autoriser directement les prochaines requêtes.

L’outil est encore très jeune, mais déjà utilisé pour protéger des sites de l’UNESCO, le dépôt Git de kernel.org, le Gitlab de GNOME, etc…

Mise en place avec Traefik

Tout d’abord, voici comment ça va marcher:

  • Traefik reçoit une requête à destination de git.pofilo.fr.
  • Il la transmet au service Anubis.
  • Anubis s’assure que l’émetteur de la requête est autorisé (via la preuve de travail ou le cookie).
  • Si c’est le cas, il la transmet à Gitea via un entrypoint dédié.

Ajout de l’entrypoint

Il s’agit d’un entrypoint local à ne pas exposer sur le réseau public, c’est lui que l’instance Gitea va désormais écouter.

Dans ma conf statique de Traefik, j’ai donc:

 1entryPoints:
 2  web:
 3    address: :80
 4    http:
 5      redirections:
 6        entryPoint:
 7          to: websecure
 8          scheme: https
 9  websecure:
10    address: :443
11  anubis:
12    address: :3923

Ajout du service

Il faut désormais ajouter le service Anubis.

Dans mon cas, je le dédie à mon instance Gitea, de ce fait, je peux avoir plusieurs instances Anubis avec diverses configurations pour protéger différents services avec des règles différentes.

 1services:
 2  gitea-anubis:
 3    image: ghcr.io/techarohq/anubis:latest
 4    environment:
 5      BIND: ":8080"
 6      DIFFICULTY: "4"  # Fast successful response with most devices, 5 is way more slow
 7      SERVE_ROBOTS_TXT: "false"  # I prefer my own robots.txt from Gitea service (supposed to block every user agents)
 8      TARGET: "http://traefik:3923"
 9      ED25519_PRIVATE_KEY_HEX_FILE: "/etc/private_key_hex_file"
10      POLICY_FNAME: "/etc/botPolicies.json"
11    labels:
12      - "traefik.enable=true"
13      - "traefik.docker.network=traefik"
14      - "traefik.http.services.gitea-anubis.loadbalancer.server.port=8080"
15      - "traefik.http.routers.gitea-anubis.rule=Host(`git.pofilo.fr`)"
16      - "traefik.http.routers.gitea-anubis.entrypoints=websecure"
17        # TLS and security
18      - "traefik.http.routers.gitea-anubis.tls=true"
19      - "traefik.http.routers.gitea-anubis.tls.options=intermediate@file"  # this is the conf generated with https://ssl-config.mozilla.org/
20      - "traefik.http.routers.gitea-anubis.middlewares=nocsp-headers@file"  # this is the conf I used for headers
21    networks:
22      - traefik
23    volumes:
24      - ../path/to/private_key_hex_file:/etc/private_key_hex_file:ro
25      - ../path/to/botPolicies.json:/etc/botPolicies.json:ro
26networks:
27    name: traefik
28    external: true

Du côté de mon service Gitea, je remplace - "traefik.http.routers.gitea.entrypoints=websecure" par - "traefik.http.routers.gitea.entrypoints=anubis". Il faut également que je retire ce genre de lignes (tout est désormais géré par le routeur dédié au service Anubis):

1# TLS and security
2- "traefik.http.routers.gitea.tls=true"
3- "traefik.http.routers.gitea.tls.options=intermediate@file"
4- "traefik.http.routers.gitea.middlewares=nocsp-headers@file"

Concernant:

  • L’image Docker: personnellement je fixe les versions et j’utilise mon propre registre d’images.
  • ED25519_PRIVATE_KEY_HEX_FILE: ça permet de ne pas re-challenger les clients si je redémarre l’instance d’Anubis (le cookie expire tout de même après 1 semaine).
  • POLICY_FNAME: je vais en parler dans le prochain paragraphe.
  • Pour le reste, je ne rentre pas trop dans les détails, chacun a différents besoin et j’ai donc essayé de rester suffisament générique.

Par exemple, si on ne veut qu’une seule instance d’Anubis, pour matcher toutes les règles (et jouer sur les priorités), on pourrait utiliser une règle du type:

- traefik.http.routers.anubis.rule=PathRegexp(`.*`)

Le fichier de conf des “policies”

La version par défaut est trouvable ici et la documentation ici. Personnellement, je suis parti du principe que:

  • Mon fichier robots.txt est censé bloqué tout type de bots.
  • Je n’en ai rien à carrer de donner à manger à des IA, et encore moins quand je leur interdis déjà explicitement.
  • J’ai déjà perdu trop de temps avec ces problèmes, donc on va au plus simple et surtout plus efficace.

Au final, j’interdis globalement tout ce qui ressemble à un bot/crawler et je force le challenge à tout les autres (sauf pour l’accès au fichier robots.txt pour ceux qui le respectent encore).

 1{
 2  "bots": [
 3    {
 4      "name": "robots-txt",
 5      "path_regex": "^/robots.txt$",
 6      "action": "ALLOW"
 7    },
 8    {
 9      "name": "internal-traffic",
10      "remote_addresses": ["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"],
11      "action": "ALLOW"
12    },
13    {
14      "name": "generic-challenging",
15      "user_agent_regex": "(?i)(bot|spider|crawl|fetch|scrapy|wget|curl|python-requests|libwww|Java|Go-http-client)",
16      "action": "CHALLENGE",
17      "challenge": {
18        "difficulty": 16,
19        "report_as": 4,
20        "algorithm": "slow"
21      }
22    },
23    {
24      "name": "ai-bots",
25      "user_agent_regex": "(?i)(AI2Bot|Ai2Bot-Dolma|Amazonbot|anthropic-ai|Applebot|Applebot-Extended|Brightbot 1.0|Bytespider|CCBot|ChatGPT-User|Claude-Web|ClaudeBot|cohere-ai|cohere-training-data-crawler|Crawlspace|Diffbot|DuckAssistBot|FacebookBot|FriendlyCrawler|Google-Extended|GoogleOther|GoogleOther-Image|GoogleOther-Video|GPTBot|iaskspider/2.0|ICC-Crawler|ImagesiftBot|img2dataset|imgproxy|ISSCyberRiskCrawler|Kangaroo Bot|Meta-ExternalAgent|Meta-ExternalFetcher|OAI-SearchBot|omgili|omgilibot|PanguBot|Perplexity-User|PerplexityBot|PetalBot|Scrapy|SemrushBot-OCOB|SemrushBot-SWA|Sidetrade indexer bot|Timpibot|VelenPublicWebCrawler|Webzio-Extended|YouBot)",
26      "action": "CHALLENGE",
27      "challenge": {
28        "difficulty": 16,
29        "report_as": 4,
30        "algorithm": "slow"
31      }
32    },
33    {
34      "name": "default",
35      "user_agent_regex": ".*",
36      "action": "CHALLENGE"
37    }
38  ]
39}

Suppression des logs d’accès sur Anubis

Avec mon instance Gitea de nouveau debout, mes fichiers de logs d’accès explosent. Or avec Traefik, il n’y a pas moyen de ne pas loguer les accès pour un seul service, c’est tout ou rien.

J’ai donc mis en place une simple tâche cron qui va faire la commande sed pour supprimer tous les logs sur le service Anubis:

sed -i '/"gitea-anubis@docker"/d' /path/to/traefik-access.log && docker kill --signal="USR1" traefik > /dev/null 2>&1

Il faut également envoyer le signal USR1 à Traefik afin qu’il ré-ouvre le fichier de log de son côté.

Ce n’est pas parfait, parce que ça me supprime les IP entrantes dans les logs d’accès de Gitea (du point de vue de Traefik, le trafic provient d’Anubis via l’entrypoint dédié), mais ça évite de générer plusieurs centaines de Mo de logs pour rien chaque jour.

Conclusion

C’est très dommage de devoir faire ça, mais ça protège réellement mon serveur sans avoir à bloquer des pays entiers. Ça demande également des calculs “inutiles” (dans le sens où ils ne servent pas à quelque chose d’utile derrière) à chaque nouveau client, mais peut-on encore parler de sobriété face à ce que consomme l’entraînement des IA ?

À noter également que pour l’instant, Anubis rend le Javascript obligatoire. C’est un point à prendre en compte dans le choix de mettre Anubis devant un service.

Pour finir, dans cet article, je montre comment j’ai configuré Anubis avec Gitea, mais on voit qu’on peut très facilement l’adapter à d’autres services. J’ai par exemple rajouté Anubis sur ce site pour éviter que les IA y volent le contenu sans mon accord.

❌