ipwatch
This commit is contained in:
194
backend/app/services/opnsense_client.py
Normal file
194
backend/app/services/opnsense_client.py
Normal file
@@ -0,0 +1,194 @@
|
||||
"""
|
||||
Client API OPNsense pour IPWatch
|
||||
Gère les communications avec l'API REST OPNsense (Kea DHCP)
|
||||
"""
|
||||
import httpx
|
||||
import ipaddress
|
||||
from typing import Optional, Dict, Any, List
|
||||
from backend.app.core.config import config_manager
|
||||
|
||||
|
||||
class OPNsenseAPIError(Exception):
|
||||
"""Erreur retournée par l'API OPNsense (validation, etc.)"""
|
||||
def __init__(self, message: str, validations: dict = None):
|
||||
self.validations = validations or {}
|
||||
super().__init__(message)
|
||||
|
||||
|
||||
class OPNsenseClient:
|
||||
"""Client pour l'API OPNsense avec authentification Basic (api_key:api_secret)"""
|
||||
|
||||
def __init__(self):
|
||||
config = config_manager.config.opnsense
|
||||
self.base_url = f"{config.protocol}://{config.host}"
|
||||
self.auth = (config.api_key, config.api_secret)
|
||||
self.verify_ssl = config.verify_ssl
|
||||
self.enabled = config.enabled
|
||||
print(f"[OPNsense] Client initialisé: {self.base_url} (ssl_verify={self.verify_ssl})")
|
||||
|
||||
def _get_client(self) -> httpx.AsyncClient:
|
||||
"""Crée un client HTTP async configuré"""
|
||||
return httpx.AsyncClient(
|
||||
base_url=self.base_url,
|
||||
auth=self.auth,
|
||||
verify=self.verify_ssl,
|
||||
timeout=30.0
|
||||
)
|
||||
|
||||
def _check_result(self, data: Dict[str, Any], action: str):
|
||||
"""Vérifie que le résultat OPNsense n'est pas 'failed'"""
|
||||
if data.get("result") == "failed":
|
||||
validations = data.get("validations", {})
|
||||
msg = f"{action} échoué"
|
||||
if validations:
|
||||
details = "; ".join(f"{k}: {v}" for k, v in validations.items())
|
||||
msg = f"{action} échoué: {details}"
|
||||
print(f"[OPNsense] VALIDATION ERREUR: {msg}")
|
||||
raise OPNsenseAPIError(msg, validations)
|
||||
|
||||
async def test_connection(self) -> Dict[str, Any]:
|
||||
"""Teste la connexion à l'API OPNsense"""
|
||||
print(f"[OPNsense] Test connexion: GET {self.base_url}/api/core/firmware/status")
|
||||
async with self._get_client() as client:
|
||||
response = await client.get("/api/core/firmware/status")
|
||||
print(f"[OPNsense] Réponse test: {response.status_code}")
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
async def search_subnets(self) -> Dict[str, Any]:
|
||||
"""Liste les subnets Kea DHCPv4"""
|
||||
print(f"[OPNsense] Recherche subnets: GET {self.base_url}/api/kea/dhcpv4/search_subnet")
|
||||
async with self._get_client() as client:
|
||||
response = await client.get("/api/kea/dhcpv4/search_subnet")
|
||||
print(f"[OPNsense] Réponse search_subnet: {response.status_code}")
|
||||
if response.status_code != 200:
|
||||
print(f"[OPNsense] Corps réponse erreur: {response.text[:500]}")
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
rows = data.get("rows", [])
|
||||
print(f"[OPNsense] {len(rows)} subnet(s) trouvé(s)")
|
||||
for row in rows:
|
||||
print(f"[OPNsense] - {row.get('subnet')}: uuid={row.get('uuid')}")
|
||||
return data
|
||||
|
||||
async def find_subnet_for_ip(self, ip_address: str) -> Optional[str]:
|
||||
"""Trouve le subnet UUID correspondant à une adresse IP"""
|
||||
print(f"[OPNsense] Recherche subnet pour IP {ip_address}")
|
||||
ip_obj = ipaddress.ip_address(ip_address)
|
||||
data = await self.search_subnets()
|
||||
rows = data.get("rows", [])
|
||||
for row in rows:
|
||||
subnet_cidr = row.get("subnet", "")
|
||||
try:
|
||||
network = ipaddress.ip_network(subnet_cidr, strict=False)
|
||||
if ip_obj in network:
|
||||
uuid = row.get("uuid")
|
||||
print(f"[OPNsense] Subnet trouvé: {subnet_cidr} -> uuid={uuid}")
|
||||
return uuid
|
||||
except ValueError:
|
||||
continue
|
||||
print(f"[OPNsense] Aucun subnet trouvé pour {ip_address}")
|
||||
return None
|
||||
|
||||
async def search_reservations(self) -> Dict[str, Any]:
|
||||
"""Liste toutes les réservations DHCP Kea"""
|
||||
print(f"[OPNsense] Recherche réservations: GET {self.base_url}/api/kea/dhcpv4/search_reservation")
|
||||
async with self._get_client() as client:
|
||||
response = await client.get("/api/kea/dhcpv4/search_reservation")
|
||||
print(f"[OPNsense] Réponse search_reservation: {response.status_code}")
|
||||
if response.status_code != 200:
|
||||
print(f"[OPNsense] Corps réponse erreur: {response.text[:500]}")
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
rows = data.get("rows", [])
|
||||
print(f"[OPNsense] {len(rows)} réservation(s) trouvée(s)")
|
||||
return data
|
||||
|
||||
async def get_reservation(self, uuid: str) -> Dict[str, Any]:
|
||||
"""Récupère une réservation par UUID"""
|
||||
print(f"[OPNsense] Get réservation: {uuid}")
|
||||
async with self._get_client() as client:
|
||||
response = await client.get(f"/api/kea/dhcpv4/get_reservation/{uuid}")
|
||||
print(f"[OPNsense] Réponse get_reservation: {response.status_code}")
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
async def add_reservation(self, data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Crée une nouvelle réservation DHCP Kea"""
|
||||
payload = {"reservation": data}
|
||||
print(f"[OPNsense] Ajout réservation: POST {self.base_url}/api/kea/dhcpv4/add_reservation")
|
||||
print(f"[OPNsense] Payload: {payload}")
|
||||
async with self._get_client() as client:
|
||||
response = await client.post(
|
||||
"/api/kea/dhcpv4/add_reservation",
|
||||
json=payload
|
||||
)
|
||||
print(f"[OPNsense] Réponse add_reservation: {response.status_code}")
|
||||
print(f"[OPNsense] Corps réponse: {response.text[:500]}")
|
||||
response.raise_for_status()
|
||||
result = response.json()
|
||||
self._check_result(result, "Ajout réservation")
|
||||
return result
|
||||
|
||||
async def set_reservation(self, uuid: str, data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Met à jour une réservation existante"""
|
||||
payload = {"reservation": data}
|
||||
print(f"[OPNsense] Mise à jour réservation {uuid}: POST {self.base_url}/api/kea/dhcpv4/set_reservation/{uuid}")
|
||||
print(f"[OPNsense] Payload: {payload}")
|
||||
async with self._get_client() as client:
|
||||
response = await client.post(
|
||||
f"/api/kea/dhcpv4/set_reservation/{uuid}",
|
||||
json=payload
|
||||
)
|
||||
print(f"[OPNsense] Réponse set_reservation: {response.status_code}")
|
||||
print(f"[OPNsense] Corps réponse: {response.text[:500]}")
|
||||
response.raise_for_status()
|
||||
result = response.json()
|
||||
self._check_result(result, "Mise à jour réservation")
|
||||
return result
|
||||
|
||||
async def del_reservation(self, uuid: str) -> Dict[str, Any]:
|
||||
"""Supprime une réservation"""
|
||||
print(f"[OPNsense] Suppression réservation: {uuid}")
|
||||
async with self._get_client() as client:
|
||||
response = await client.post(f"/api/kea/dhcpv4/del_reservation/{uuid}")
|
||||
print(f"[OPNsense] Réponse del_reservation: {response.status_code}")
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
async def reconfigure_kea(self) -> Dict[str, Any]:
|
||||
"""Applique les changements Kea (reconfigure le service)"""
|
||||
print(f"[OPNsense] Reconfiguration Kea: POST {self.base_url}/api/kea/service/reconfigure")
|
||||
async with self._get_client() as client:
|
||||
response = await client.post("/api/kea/service/reconfigure")
|
||||
print(f"[OPNsense] Réponse reconfigure: {response.status_code}")
|
||||
if response.status_code != 200:
|
||||
print(f"[OPNsense] Corps réponse erreur: {response.text[:500]}")
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
async def find_reservation_by_ip(self, ip_address: str) -> Optional[Dict[str, Any]]:
|
||||
"""Cherche une réservation existante par adresse IP"""
|
||||
print(f"[OPNsense] Recherche réservation par IP: {ip_address}")
|
||||
result = await self.search_reservations()
|
||||
rows = result.get("rows", [])
|
||||
for row in rows:
|
||||
if row.get("ip_address") == ip_address:
|
||||
print(f"[OPNsense] Réservation trouvée: uuid={row.get('uuid')}")
|
||||
return row
|
||||
print(f"[OPNsense] Aucune réservation existante pour {ip_address}")
|
||||
return None
|
||||
|
||||
async def find_reservation_by_mac(self, mac_address: str) -> Optional[Dict[str, Any]]:
|
||||
"""Cherche une réservation existante par adresse MAC"""
|
||||
mac_normalized = mac_address.lower().replace("-", ":")
|
||||
print(f"[OPNsense] Recherche réservation par MAC: {mac_normalized}")
|
||||
result = await self.search_reservations()
|
||||
rows = result.get("rows", [])
|
||||
for row in rows:
|
||||
row_mac = (row.get("hw_address") or "").lower().replace("-", ":")
|
||||
if row_mac == mac_normalized:
|
||||
print(f"[OPNsense] Réservation trouvée par MAC: uuid={row.get('uuid')}")
|
||||
return row
|
||||
print(f"[OPNsense] Aucune réservation pour MAC {mac_normalized}")
|
||||
return None
|
||||
Reference in New Issue
Block a user