""" Endpoints API pour l'intégration OPNsense (Kea DHCP) """ import traceback from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.orm import Session from pydantic import BaseModel from typing import Optional from backend.app.core.database import get_db from backend.app.core.config import config_manager from backend.app.models.ip import IP from backend.app.services.opnsense_client import OPNsenseClient, OPNsenseAPIError router = APIRouter(prefix="/api/opnsense", tags=["OPNsense"]) class DHCPReservationRequest(BaseModel): """Schéma pour créer/mettre à jour une réservation DHCP""" ip_address: str hw_address: str hostname: str = "" description: str = "Ajouté par IPWatch" def get_opnsense_client() -> OPNsenseClient: """Retourne un client OPNsense configuré""" config = config_manager.config.opnsense print(f"[OPNsense Router] Config: enabled={config.enabled}, host={config.host}, api_key={'***' + config.api_key[-8:] if config.api_key else 'VIDE'}") if not config.enabled: raise HTTPException(status_code=503, detail="Intégration OPNsense désactivée") if not config.host or not config.api_key: raise HTTPException(status_code=503, detail="Configuration OPNsense incomplète") return OPNsenseClient() @router.get("/status") async def opnsense_status(): """Teste la connexion à l'API OPNsense""" client = get_opnsense_client() try: result = await client.test_connection() return {"status": "connected", "data": result} except Exception as e: print(f"[OPNsense Router] Erreur status: {type(e).__name__}: {e}") traceback.print_exc() raise HTTPException(status_code=502, detail=f"Connexion OPNsense échouée: {type(e).__name__}: {str(e)}") @router.get("/dhcp/reservations") async def list_reservations(): """Liste toutes les réservations DHCP Kea""" client = get_opnsense_client() try: result = await client.search_reservations() return result except Exception as e: print(f"[OPNsense Router] Erreur list_reservations: {type(e).__name__}: {e}") traceback.print_exc() raise HTTPException(status_code=502, detail=f"Erreur récupération réservations: {type(e).__name__}: {str(e)}") @router.get("/dhcp/reservation/{ip_address}") async def get_reservation_by_ip(ip_address: str): """Cherche une réservation DHCP par adresse IP""" client = get_opnsense_client() try: reservation = await client.find_reservation_by_ip(ip_address) if reservation: return {"found": True, "reservation": reservation} return {"found": False, "reservation": None} except Exception as e: print(f"[OPNsense Router] Erreur get_reservation_by_ip: {type(e).__name__}: {e}") traceback.print_exc() raise HTTPException(status_code=502, detail=f"Erreur recherche réservation: {type(e).__name__}: {str(e)}") @router.post("/dhcp/reservation") async def upsert_reservation( request: DHCPReservationRequest, db: Session = Depends(get_db) ): """ Crée ou met à jour une réservation DHCP Kea pour une IP. Après succès, met à jour dhcp_synced=True dans la BDD. """ print(f"[OPNsense Router] === UPSERT RESERVATION ===") print(f"[OPNsense Router] IP: {request.ip_address}, MAC: {request.hw_address}, Hostname: {request.hostname}") client = get_opnsense_client() try: # Étape 0 : Résoudre le subnet UUID print(f"[OPNsense Router] Étape 0: Résolution du subnet pour {request.ip_address}...") subnet_uuid = await client.find_subnet_for_ip(request.ip_address) if not subnet_uuid: raise HTTPException(status_code=400, detail=f"Aucun subnet Kea trouvé pour l'IP {request.ip_address}") reservation_data = { "subnet": subnet_uuid, "ip_address": request.ip_address, "hw_address": request.hw_address, "hostname": request.hostname, "description": request.description } print(f"[OPNsense Router] Données réservation: {reservation_data}") # Étape 1 : Chercher si une réservation existe déjà print(f"[OPNsense Router] Étape 1: Recherche réservation existante...") existing = await client.find_reservation_by_ip(request.ip_address) if existing: # Mise à jour de la réservation existante uuid = existing.get("uuid") print(f"[OPNsense Router] Étape 2: Mise à jour réservation existante uuid={uuid}") if not uuid: raise HTTPException(status_code=500, detail="UUID de réservation introuvable") result = await client.set_reservation(uuid, reservation_data) action = "updated" else: # Création d'une nouvelle réservation print(f"[OPNsense Router] Étape 2: Création nouvelle réservation") result = await client.add_reservation(reservation_data) action = "created" print(f"[OPNsense Router] Étape 2 terminée: action={action}, result={result}") # Étape 3 : Appliquer les changements dans Kea print(f"[OPNsense Router] Étape 3: Reconfiguration Kea...") await client.reconfigure_kea() print(f"[OPNsense Router] Étape 3 terminée: Kea reconfiguré") # Étape 4 : Mettre à jour dhcp_synced dans la BDD print(f"[OPNsense Router] Étape 4: Mise à jour BDD dhcp_synced=True") ip_record = db.query(IP).filter(IP.ip == request.ip_address).first() if ip_record: ip_record.dhcp_synced = True db.commit() db.refresh(ip_record) print(f"[OPNsense Router] Étape 4 terminée: BDD mise à jour") else: print(f"[OPNsense Router] ATTENTION: IP {request.ip_address} non trouvée en BDD") print(f"[OPNsense Router] === SUCCÈS: {action} ===") return { "status": "success", "action": action, "ip_address": request.ip_address, "result": result } except HTTPException: raise except OPNsenseAPIError as e: print(f"[OPNsense Router] === ERREUR VALIDATION ===") print(f"[OPNsense Router] Message: {str(e)}") print(f"[OPNsense Router] Validations: {e.validations}") raise HTTPException(status_code=422, detail=str(e)) except Exception as e: print(f"[OPNsense Router] === ERREUR ===") print(f"[OPNsense Router] Type: {type(e).__name__}") print(f"[OPNsense Router] Message: {str(e)}") traceback.print_exc() raise HTTPException(status_code=502, detail=f"Erreur OPNsense: {type(e).__name__}: {str(e)}")