Files
ipwatch/backend/app/routers/opnsense.py
2026-02-07 16:57:37 +01:00

165 lines
6.6 KiB
Python

"""
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)}")