feat: add initial setup wizard, workload insights, and policy audit mode
Add setup wizard with status tracking via SystemSetting model, implement /setup/status and /setup/complete endpoints to create initial admin user and optional cluster configuration, add workload insights endpoint with traffic analysis and policy matching including audit mode detection, implement enforcement_mode property on Policy model with audit/enforced states, add Modal component for dialogs, create SetupWizard page with multi
This commit is contained in:
@@ -25,6 +25,7 @@ from app.models.domain import (
|
||||
SecurityGroup,
|
||||
SecurityRule,
|
||||
ServiceCatalogItem,
|
||||
SystemSetting,
|
||||
Subnet,
|
||||
Tenant,
|
||||
User,
|
||||
@@ -54,12 +55,15 @@ from app.schemas.domain import (
|
||||
ServiceCatalogRead,
|
||||
SecurityGroupCreate,
|
||||
SecurityGroupRead,
|
||||
SetupCompleteRequest,
|
||||
SetupStatus,
|
||||
SubnetCreate,
|
||||
SubnetRead,
|
||||
TenantCreate,
|
||||
TenantRead,
|
||||
UserCreate,
|
||||
UserRead,
|
||||
WorkloadInsight,
|
||||
WorkloadRead,
|
||||
)
|
||||
from app.services.audit import write_audit
|
||||
@@ -79,6 +83,69 @@ def commit_or_400(db: Session) -> None:
|
||||
raise HTTPException(status_code=409, detail="Resource conflicts with an existing record") from exc
|
||||
|
||||
|
||||
def setup_setting(db: Session) -> SystemSetting:
|
||||
setting = db.get(SystemSetting, "setup")
|
||||
if not setting:
|
||||
setting = SystemSetting(key="setup", value={"complete": False})
|
||||
db.add(setting)
|
||||
db.commit()
|
||||
db.refresh(setting)
|
||||
return setting
|
||||
|
||||
|
||||
@api_router.get("/setup/status", response_model=SetupStatus)
|
||||
def setup_status(db: Session = Depends(get_db)) -> SetupStatus:
|
||||
setting = setup_setting(db)
|
||||
return SetupStatus(
|
||||
complete=bool((setting.value or {}).get("complete")),
|
||||
has_users=bool(db.scalar(select(func.count()).select_from(User))),
|
||||
has_clusters=bool(db.scalar(select(func.count()).select_from(Cluster))),
|
||||
)
|
||||
|
||||
|
||||
@api_router.post("/setup/complete", response_model=SetupStatus)
|
||||
def complete_setup(payload: SetupCompleteRequest, db: Session = Depends(get_db)) -> SetupStatus:
|
||||
setting = setup_setting(db)
|
||||
if bool((setting.value or {}).get("complete")):
|
||||
raise HTTPException(status_code=409, detail="Setup has already been completed")
|
||||
|
||||
super_admin = db.scalar(select(Role).where(Role.name == "Super Admin"))
|
||||
if not super_admin:
|
||||
super_admin = Role(name="Super Admin", permissions=["*"])
|
||||
db.add(super_admin)
|
||||
db.flush()
|
||||
|
||||
email = payload.admin_email.strip().lower()
|
||||
admin = db.scalar(select(User).where(User.email == email))
|
||||
if not admin:
|
||||
admin = User(email=email, display_name=payload.admin_name, password_hash=hash_password(payload.admin_password))
|
||||
db.add(admin)
|
||||
admin.display_name = payload.admin_name
|
||||
admin.password_hash = hash_password(payload.admin_password)
|
||||
admin.is_active = True
|
||||
if super_admin not in admin.roles:
|
||||
admin.roles.append(super_admin)
|
||||
|
||||
if payload.cluster_name and payload.cluster_api_url and payload.cluster_api_token:
|
||||
existing_cluster = db.scalar(select(Cluster).where(Cluster.name == payload.cluster_name))
|
||||
if not existing_cluster:
|
||||
db.add(
|
||||
Cluster(
|
||||
name=payload.cluster_name,
|
||||
api_url=payload.cluster_api_url,
|
||||
token_ref=payload.cluster_api_token,
|
||||
provider=payload.cluster_provider,
|
||||
mode=payload.cluster_mode,
|
||||
verify_tls=payload.verify_tls,
|
||||
)
|
||||
)
|
||||
|
||||
setting.value = {"complete": True, "completed_at": datetime.utcnow().isoformat()}
|
||||
db.add(AuditLog(user_id=admin.id, action="setup.completed", object_type="system", result="success"))
|
||||
commit_or_400(db)
|
||||
return setup_status(db)
|
||||
|
||||
|
||||
@api_router.get("/dashboard")
|
||||
def dashboard(_: CurrentUser, db: Session = Depends(get_db)) -> dict:
|
||||
return {
|
||||
@@ -260,6 +327,49 @@ def workloads(_: CurrentUser, db: Session = Depends(get_db)) -> list[Workload]:
|
||||
return db.scalars(select(Workload).order_by(Workload.name)).all()
|
||||
|
||||
|
||||
@api_router.get("/vms/{workload_id}/insights", response_model=WorkloadInsight)
|
||||
def workload_insights(workload_id: str, _: CurrentUser, db: Session = Depends(get_db)) -> WorkloadInsight:
|
||||
workload = db.get(Workload, workload_id)
|
||||
if not workload:
|
||||
raise HTTPException(status_code=404, detail="Workload not found")
|
||||
policies = db.scalars(
|
||||
select(Policy).where((Policy.project_id == workload.project_id) | (Policy.project_id.is_(None))).order_by(Policy.name)
|
||||
).all()
|
||||
traffic = [
|
||||
{
|
||||
"timestamp": datetime.utcnow().isoformat(),
|
||||
"source": workload.name,
|
||||
"destination": "finance-db-1" if "web" in workload.tags else "core-services-1",
|
||||
"protocol": "tcp",
|
||||
"port": 5432 if "web" in workload.tags else 22,
|
||||
"bytes": 1489200,
|
||||
"decision": "allowed",
|
||||
},
|
||||
{
|
||||
"timestamp": datetime.utcnow().isoformat(),
|
||||
"source": "unknown-external",
|
||||
"destination": workload.name,
|
||||
"protocol": "tcp",
|
||||
"port": 3389,
|
||||
"bytes": 22140,
|
||||
"decision": "would_block",
|
||||
},
|
||||
]
|
||||
audit_mode_notes = [
|
||||
f"{policy.name} is in audit mode; matching traffic is logged without enforcement."
|
||||
for policy in policies
|
||||
if policy.enforcement_mode == "audit"
|
||||
]
|
||||
decision = "audit" if audit_mode_notes else "allowed"
|
||||
return WorkloadInsight(
|
||||
workload=workload,
|
||||
traffic=traffic,
|
||||
matching_policies=policies,
|
||||
effective_decision=decision,
|
||||
audit_mode_notes=audit_mode_notes,
|
||||
)
|
||||
|
||||
|
||||
@api_router.get("/networks", response_model=list[NetworkRead])
|
||||
def networks(_: CurrentUser, db: Session = Depends(get_db)) -> list[Network]:
|
||||
return db.scalars(select(Network).order_by(Network.name)).all()
|
||||
|
||||
Reference in New Issue
Block a user