cc4c3fcecb
- Backend: Customer/Satellite Models, customer_id auf Server/Job/Audit - Satellite-API: heartbeat, poll (atomares Claiming), logs, result, scan-result, health-report - Auth via X-Api-Key (SHA-256 gehasht) - Job-Queue: pending/claimed/running/success/failed + Stale-Janitor - Batch-Trigger: ein Job pro Server, Satellite arbeitet sequenziell ab - Credentials bleiben lokal: nur symbolische credential_ref zentral - Neues Paket satellite/: Pull-Loop, WinRM/SSH/CAU/Scanner, PyInstaller-tauglich - Frontend: Kunden-Switcher, Satelliten-View, Polling statt WebSocket - Entfernt: WebSocket/Socket.io, Redis, zentrale Credentials, JobRunner - Docs: README/AGENTS/PROMPT auf neue Architektur aktualisiert
112 lines
3.6 KiB
Python
112 lines
3.6 KiB
Python
"""Satellite management routes (dashboard side).
|
|
|
|
The plaintext API key is returned exactly once on creation.
|
|
"""
|
|
|
|
from fastapi import APIRouter, Depends, Request
|
|
from sqlalchemy import select
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from app.api.deps import client_ip, get_current_user
|
|
from app.core.database import get_db
|
|
from app.core.exceptions import NotFoundError
|
|
from app.models.customer import Customer
|
|
from app.models.satellite import Satellite, generate_api_key, hash_api_key
|
|
from app.models.user import User
|
|
from app.schemas.satellite import SatelliteCreate, SatelliteCreated, SatelliteRead
|
|
from app.services.audit import AuditService
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
def _created_response(satellite: Satellite, api_key: str) -> SatelliteCreated:
|
|
data = SatelliteRead.model_validate(satellite).model_dump()
|
|
return SatelliteCreated(**data, api_key=api_key)
|
|
|
|
|
|
@router.get("", response_model=list[SatelliteRead])
|
|
async def list_satellites(
|
|
customer_id: int | None = None,
|
|
db: AsyncSession = Depends(get_db),
|
|
_user: User = Depends(get_current_user),
|
|
) -> list[Satellite]:
|
|
stmt = select(Satellite).order_by(Satellite.id)
|
|
if customer_id is not None:
|
|
stmt = stmt.where(Satellite.customer_id == customer_id)
|
|
result = await db.execute(stmt)
|
|
return list(result.scalars().all())
|
|
|
|
|
|
@router.post("", response_model=SatelliteCreated, status_code=201)
|
|
async def create_satellite(
|
|
payload: SatelliteCreate,
|
|
request: Request,
|
|
db: AsyncSession = Depends(get_db),
|
|
user: User = Depends(get_current_user),
|
|
) -> SatelliteCreated:
|
|
customer = await db.get(Customer, payload.customer_id)
|
|
if not customer:
|
|
raise NotFoundError("Kunde nicht gefunden")
|
|
|
|
api_key = generate_api_key()
|
|
satellite = Satellite(
|
|
customer_id=payload.customer_id,
|
|
name=payload.name,
|
|
api_key_hash=hash_api_key(api_key),
|
|
api_key_prefix=api_key[:11],
|
|
)
|
|
db.add(satellite)
|
|
await db.flush()
|
|
await AuditService(db).log(
|
|
username=user.username,
|
|
action="satellite.create",
|
|
target=f"{customer.name}/{satellite.name}",
|
|
customer_id=customer.id,
|
|
ip_address=client_ip(request),
|
|
)
|
|
return _created_response(satellite, api_key)
|
|
|
|
|
|
@router.delete("/{satellite_id}", status_code=204)
|
|
async def delete_satellite(
|
|
satellite_id: int,
|
|
request: Request,
|
|
db: AsyncSession = Depends(get_db),
|
|
user: User = Depends(get_current_user),
|
|
) -> None:
|
|
satellite = await db.get(Satellite, satellite_id)
|
|
if not satellite:
|
|
raise NotFoundError("Satellite nicht gefunden")
|
|
await AuditService(db).log(
|
|
username=user.username,
|
|
action="satellite.delete",
|
|
target=satellite.name,
|
|
customer_id=satellite.customer_id,
|
|
ip_address=client_ip(request),
|
|
)
|
|
await db.delete(satellite)
|
|
|
|
|
|
@router.post("/{satellite_id}/rotate-key", response_model=SatelliteCreated)
|
|
async def rotate_satellite_key(
|
|
satellite_id: int,
|
|
request: Request,
|
|
db: AsyncSession = Depends(get_db),
|
|
user: User = Depends(get_current_user),
|
|
) -> SatelliteCreated:
|
|
satellite = await db.get(Satellite, satellite_id)
|
|
if not satellite:
|
|
raise NotFoundError("Satellite nicht gefunden")
|
|
|
|
api_key = generate_api_key()
|
|
satellite.api_key_hash = hash_api_key(api_key)
|
|
satellite.api_key_prefix = api_key[:11]
|
|
await AuditService(db).log(
|
|
username=user.username,
|
|
action="satellite.rotate_key",
|
|
target=satellite.name,
|
|
customer_id=satellite.customer_id,
|
|
ip_address=client_ip(request),
|
|
)
|
|
return _created_response(satellite, api_key)
|