feat: add automatic policy rule cleanup when disabling or switching to audit mode with provider-level rule deletion

Add cluster_provider_targets helper to build target list from all cluster workloads, implement cleanup_policy_provider_rules to delete policy rules across all clusters with per-cluster result tracking, add delete_policy_rules method to ProxmoxProvider that removes rules matching policy ID marker with error collection, extend policy_id_marker and rule_comment_matches_marker helpers for
This commit is contained in:
2026-07-09 21:31:20 +02:00
parent 0d07349de0
commit d651a11472
3 changed files with 118 additions and 7 deletions
+61 -6
View File
@@ -853,6 +853,45 @@ def resolve_firewall_preview(db: Session, cluster: Cluster, preview: FirewallPre
)
def cluster_provider_targets(db: Session, cluster: Cluster) -> list[dict]:
targets = []
workloads = db.scalars(select(Workload).where(Workload.cluster_id == cluster.id).order_by(Workload.name)).all()
for workload in workloads:
resolved = workload_provider_target(db, cluster, f"workload:{workload.id}")
if resolved:
target, _ = resolved
targets.append(target)
return targets
async def cleanup_policy_provider_rules(db: Session, policy_id: str) -> dict[str, object]:
results = []
total_deleted = 0
for cluster in db.scalars(select(Cluster).order_by(Cluster.name)).all():
targets = cluster_provider_targets(db, cluster)
if not targets:
results.append({"cluster_id": cluster.id, "cluster_name": cluster.name, "rules_deleted": 0, "reason": "No targets"})
continue
provider = get_provider(cluster.provider)
delete_rules = getattr(provider, "delete_policy_rules", None)
if not delete_rules:
results.append({"cluster_id": cluster.id, "cluster_name": cluster.name, "rules_deleted": 0, "reason": "Provider cannot delete policy rules"})
continue
result = await delete_rules(
ProviderConnection(
api_url=cluster.api_url,
token=cluster.token_ref or "",
verify_tls=cluster.verify_tls,
read_only=cluster.mode == "read_only",
),
targets,
policy_id,
)
total_deleted += int(result.get("rules_deleted") or 0)
results.append({"cluster_id": cluster.id, "cluster_name": cluster.name, **result})
return {"policy_id": policy_id, "rules_deleted": total_deleted, "clusters": results}
@api_router.get("/setup/status", response_model=SetupStatus)
def setup_status(db: Session = Depends(get_db)) -> SetupStatus:
setting = setup_setting(db)
@@ -1820,31 +1859,47 @@ def create_policy(payload: PolicyCreate, user: CurrentUser, db: Session = Depend
@api_router.patch("/policies/{policy_id}", response_model=PolicyRead)
def update_policy(policy_id: str, payload: PolicyCreate, user: CurrentUser, db: Session = Depends(get_db)) -> Policy:
async def update_policy(policy_id: str, payload: PolicyCreate, user: CurrentUser, db: Session = Depends(get_db)) -> Policy:
policy = db.get(Policy, policy_id)
if not policy:
raise HTTPException(status_code=404, detail="Policy not found")
old_values = {"name": policy.name, "enabled": policy.enabled, "definition": policy.definition, "version": policy.version}
old_mode = policy.enforcement_mode
old_enabled = policy.enabled
new_definition = normalized_policy_definition(payload.definition)
new_mode = str(new_definition.get("enforcement_mode") or "enforced")
policy.project_id = payload.project_id
policy.name = payload.name
policy.enabled = payload.enabled
policy.definition = normalized_policy_definition(payload.definition)
policy.definition = new_definition
policy.version += 1
commit_or_400(db)
db.refresh(policy)
write_audit(db, action="policy.updated", object_type="policy", object_id=policy.id, user_id=user.id, old_values=old_values, new_values=payload.model_dump())
cleanup_result = None
if old_enabled and old_mode == "enforced" and (not payload.enabled or new_mode == "audit"):
cleanup_result = await cleanup_policy_provider_rules(db, policy.id)
write_audit(
db,
action="policy.updated",
object_type="policy",
object_id=policy.id,
user_id=user.id,
old_values=old_values,
new_values={**payload.model_dump(), "provider_cleanup": cleanup_result},
)
return policy
@api_router.delete("/policies/{policy_id}")
def delete_policy(policy_id: str, user: CurrentUser, db: Session = Depends(get_db)) -> dict[str, str]:
async def delete_policy(policy_id: str, user: CurrentUser, db: Session = Depends(get_db)) -> dict[str, object]:
policy = db.get(Policy, policy_id)
if not policy:
raise HTTPException(status_code=404, detail="Policy not found")
cleanup_result = await cleanup_policy_provider_rules(db, policy.id)
db.delete(policy)
commit_or_400(db)
write_audit(db, action="policy.deleted", object_type="policy", object_id=policy_id, user_id=user.id)
return {"status": "deleted", "id": policy_id}
write_audit(db, action="policy.deleted", object_type="policy", object_id=policy_id, user_id=user.id, new_values={"provider_cleanup": cleanup_result})
return {"status": "deleted", "id": policy_id, "provider_cleanup": cleanup_result}
@api_router.post("/policies/{policy_id}/compile", response_model=PolicyRead)