from typing import List from fastapi import APIRouter, Body, HTTPException, Path, status from pydantic import BaseModel, Field from models.firewalld import ( FirewallPort, FirewallService, FirewallSource, FirewallTarget, FirewallZone, ) from services.firewalld_service import FirewalldService router = APIRouter() firewalld = FirewalldService() class ErrorResponse(BaseModel): """Standard error response model""" detail: str = Field(..., description="Error description") class SuccessResponse(BaseModel): """Standard success response model""" message: str = Field(..., description="Success message") @router.get( "/zones", response_model=List[str], responses={500: {"model": ErrorResponse}}, summary="Get all available zones", ) async def get_zones() -> List[str]: """ Retrieve all available firewalld zones. Returns: List[str]: List of zone names """ try: return firewalld.get_zones() except Exception as e: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to get zones: {str(e)}", ) @router.get( "/zones/default", response_model=str, responses={500: {"model": ErrorResponse}}, summary="Get default zone", ) async def get_default_zone() -> str: """ Get the current default zone. Returns: str: Name of the default zone """ try: return firewalld.get_default_zone() except Exception as e: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to get default zone: {str(e)}", ) @router.put( "/zones/default/{zone}", response_model=SuccessResponse, responses={404: {"model": ErrorResponse}, 500: {"model": ErrorResponse}}, summary="Set default zone", ) async def set_default_zone( zone: str = Path(..., description="Zone name to set as default"), ) -> SuccessResponse: """ Set the default zone. Args: zone (str): Name of the zone to set as default Returns: SuccessResponse: Success confirmation message """ try: firewalld.set_default_zone(zone) return SuccessResponse(message=f"Default zone set to {zone}") except ValueError: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Zone '{zone}' not found" ) except Exception as e: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to set default zone: {str(e)}", ) @router.get( "/zones/{zone}", response_model=FirewallZone, responses={404: {"model": ErrorResponse}, 500: {"model": ErrorResponse}}, summary="Get zone configuration", ) async def get_zone(zone: str = Path(..., description="Zone name")) -> FirewallZone: """ Get detailed configuration for a specific zone. Args: zone (str): Name of the zone Returns: FirewallZone: Zone configuration details """ try: zone_info = firewalld.get_zone_settings(zone) return FirewallZone( name=zone, target=FirewallTarget.DEFAULT, interfaces=[], **zone_info ) except ValueError: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Zone '{zone}' not found" ) except Exception as e: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to get zone settings: {str(e)}", ) @router.get( "/zones/{zone}/services", response_model=List[str], responses={500: {"model": ErrorResponse}}, summary="Get services in zone", ) async def get_services(zone: str = Path(..., description="Zone name")) -> List[str]: """ Get all services in a zone. Args: zone (str): Name of the zone Returns: List[str]: List of service names """ try: return firewalld.get_services(zone) except Exception as e: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to get services: {str(e)}", ) @router.post( "/zones/{zone}/services", response_model=SuccessResponse, responses={404: {"model": ErrorResponse}, 500: {"model": ErrorResponse}}, summary="Add service to zone", ) async def add_service( zone: str = Path(..., description="Zone name"), rule: FirewallService = Body(..., description="Service configuration"), ) -> SuccessResponse: """ Add a service to a zone. Args: zone (str): Name of the zone rule (FirewallService): Service configuration including service name and optional timeout Returns: SuccessResponse: Success confirmation message """ try: firewalld.add_service(zone, rule.name, rule.timeout) return SuccessResponse(message=f"Service {rule.name} added to zone {zone}") except ValueError: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Zone '{zone}' not found" ) except Exception as e: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to add service: {str(e)}", ) @router.delete( "/zones/{zone}/services/{service}", response_model=SuccessResponse, responses={404: {"model": ErrorResponse}, 500: {"model": ErrorResponse}}, summary="Remove service from zone", ) async def remove_service( zone: str = Path(..., description="Zone name"), service: str = Path(..., description="Service name"), ) -> SuccessResponse: """ Remove a service from a zone. Args: zone (str): Name of the zone service (str): Name of the service Returns: SuccessResponse: Success confirmation message """ try: firewalld.remove_service(zone, service) return SuccessResponse(message=f"Service {service} removed from zone {zone}") except ValueError: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Zone '{zone}' or service '{service}' not found", ) except Exception as e: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to remove service: {str(e)}", ) @router.get( "/zones/{zone}/ports", response_model=List[str], responses={500: {"model": ErrorResponse}}, summary="Get ports in zone", ) async def get_ports(zone: str = Path(..., description="Zone name")) -> List[str]: """ Get all ports in a zone. Args: zone (str): Name of the zone Returns: List[str]: List of port configurations """ try: return firewalld.get_ports(zone) except Exception as e: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to get ports: {str(e)}", ) @router.post( "/zones/{zone}/ports", response_model=SuccessResponse, responses={ 400: {"model": ErrorResponse}, 404: {"model": ErrorResponse}, 500: {"model": ErrorResponse}, }, summary="Add port to zone", ) async def add_port( zone: str = Path(..., description="Zone name"), rule: FirewallPort = Body(..., description="Port configuration"), ) -> SuccessResponse: """ Add a port to a zone. Args: zone (str): Name of the zone rule (FirewallPort): Port configuration including port number, protocol and optional timeout Returns: SuccessResponse: Success confirmation message """ try: firewalld.add_port(zone, rule.port, rule.protocol.value, rule.timeout) return SuccessResponse( message=f"Port {rule.port}/{rule.protocol.value} added to zone {zone}" ) except ValueError as e: raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e)) except Exception as e: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to add port: {str(e)}", ) @router.delete( "/zones/{zone}/ports", response_model=SuccessResponse, responses={404: {"model": ErrorResponse}, 500: {"model": ErrorResponse}}, summary="Remove port from zone", ) async def remove_port( zone: str = Path(..., description="Zone name"), rule: FirewallPort = Body(..., description="Port configuration"), ) -> SuccessResponse: """ Remove a port from a zone. Args: zone (str): Name of the zone rule (FirewallPort): Port configuration including port number and protocol Returns: SuccessResponse: Success confirmation message """ try: firewalld.remove_port(zone, rule.port, rule.protocol.value) return SuccessResponse( message=f"Port {rule.port}/{rule.protocol.value} removed from zone {zone}" ) except ValueError: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Zone '{zone}' not found" ) except Exception as e: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to remove port: {str(e)}", ) @router.get( "/zones/{zone}/sources", response_model=List[str], responses={500: {"model": ErrorResponse}}, summary="Get sources in zone", ) async def get_sources(zone: str = Path(..., description="Zone name")) -> List[str]: """ Get all sources in a zone. Args: zone (str): Name of the zone Returns: List[str]: List of source addresses """ try: return firewalld.get_sources(zone) except Exception as e: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to get sources: {str(e)}", ) @router.post( "/zones/{zone}/sources", response_model=SuccessResponse, responses={404: {"model": ErrorResponse}, 500: {"model": ErrorResponse}}, summary="Add source to zone", ) async def add_source( zone: str = Path(..., description="Zone name"), rule: FirewallSource = Body(..., description="Source configuration"), ) -> SuccessResponse: """ Add a source to a zone. Args: zone (str): Name of the zone rule (FirewallSource): Source configuration including source address Returns: SuccessResponse: Success confirmation message """ try: firewalld.add_source(zone, rule.address) return SuccessResponse(message=f"Source {rule.address} added to zone {zone}") except ValueError: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Zone '{zone}' not found" ) except Exception as e: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to add source: {str(e)}", ) @router.delete( "/zones/{zone}/sources/{source}", response_model=SuccessResponse, responses={404: {"model": ErrorResponse}, 500: {"model": ErrorResponse}}, summary="Remove source from zone", ) async def remove_source( zone: str = Path(..., description="Zone name"), source: str = Path(..., description="Source address"), ) -> SuccessResponse: """ Remove a source from a zone. Args: zone (str): Name of the zone source (str): Source address Returns: SuccessResponse: Success confirmation message """ try: firewalld.remove_source(zone, source) return SuccessResponse(message=f"Source {source} removed from zone {zone}") except ValueError: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Zone '{zone}' or source '{source}' not found", ) except Exception as e: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to remove source: {str(e)}", ) @router.post( "/reload", response_model=SuccessResponse, responses={500: {"model": ErrorResponse}}, summary="Reload firewall rules", ) async def reload_firewall() -> SuccessResponse: """ Reload firewall rules. Returns: SuccessResponse: Success confirmation message """ try: firewalld.reload() return SuccessResponse(message="Firewall rules reloaded successfully") except Exception as e: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to reload firewall rules: {str(e)}", )