First commit

This commit is contained in:
2026-07-20 11:05:07 +02:00
commit b592ab669f
159 changed files with 10294 additions and 0 deletions
+56
View File
@@ -0,0 +1,56 @@
"""Diffuse chaque BanEvent (création et mise à jour géoloc) aux clients
WebSocket via le channel layer, et déclenche la géolocalisation asynchrone
à la création. Journalise aussi les verrouillages django-axes (protection
/admin/, cf. AXES_* dans config/settings/base.py) pour que fail2ban
(generic/fail2ban/jail.d/emitter-admin-auth.conf) puisse réagir."""
import logging
from typing import Any
from asgiref.sync import async_to_sync
from axes.signals import user_locked_out
from channels.layers import get_channel_layer
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.http import HttpRequest
from .consumers import GROUP_NAME
from .models import BanEvent, NodeRegistry
from .serializers import event_payload
from .tasks import geolocate_ip
fail2ban_axes_logger = logging.getLogger('fail2ban.axes')
@receiver(post_save, sender=BanEvent)
def broadcast_ban_event(sender: type[BanEvent], instance: BanEvent, created: bool, **kwargs: Any) -> None:
channel_layer = get_channel_layer()
if channel_layer is not None:
# Se déclenche deux fois par event (création, puis mise à jour
# géoloc par geolocate_ip ci-dessous) : lookup unique indexé, sans
# conséquence à tourner deux fois (résultat identique).
node_alias = NodeRegistry.objects.filter(node_id=instance.node).values_list('alias', flat=True).first()
async_to_sync(channel_layer.group_send)(
GROUP_NAME,
{'type': 'ban.event', 'payload': event_payload(instance, node_alias)},
)
if created:
geolocate_ip.delay(instance.pk)
@receiver(user_locked_out)
def log_axes_lockout(sender: Any, request: HttpRequest, username: str | None, ip_address: str, **kwargs: Any) -> None:
"""Un seul point de vérité pour le seuil : axes décide (AXES_FAILURE_LIMIT)
qu'il y a un problème, cette ligne fait réagir fail2ban (ban réseau +
propagation SYNC_BAN aux autres noeuds, comme toute autre jail cf.
jail.local [DEFAULT] action). maxretry=1 côté jail : chaque ligne ici
EST déjà la décision de verrouillage, pas la peine de recompter.
Format de message imposé par filter.d/app-auth.conf (générique,
réutilisable par toute appli du VPS pas seulement ce projet) :
"Verrouillage <NOM_APPLI> après échecs répétés depuis <IP> (...)",
<NOM_APPLI> = "émetteur" ici, un seul mot (\\S+ côté filtre)."""
fail2ban_axes_logger.warning(
'Verrouillage émetteur après échecs répétés depuis %s (utilisateur tenté : %s)',
ip_address, username or '?',
)