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
+37 -1
View File
@@ -162,6 +162,12 @@ class ProxmoxProvider(Provider):
def policy_marker(self, rule: dict[str, Any]) -> str:
return f"NexaFabric policy={rule.get('policy_id')}"
def policy_id_marker(self, policy_id: str) -> str:
return f"NexaFabric policy={policy_id}"
def rule_comment_matches_marker(self, comment: str, marker: str) -> bool:
return comment == marker or comment.startswith(f"{marker} ")
def network_firewall_enabled_value(self, value: str) -> str:
parts = [part for part in value.split(",") if part]
found = False
@@ -190,7 +196,7 @@ class ProxmoxProvider(Provider):
for existing_rule in sorted(existing_rules, key=lambda item: int(item.get("pos", 0)), reverse=True):
comment = str(existing_rule.get("comment") or "")
pos = existing_rule.get("pos")
if marker in comment and pos is not None:
if self.rule_comment_matches_marker(comment, marker) and pos is not None:
delete_response = await client.delete(f"{rules_url}/{pos}", headers=headers)
delete_response.raise_for_status()
deletions.append({"pos": pos, "comment": comment})
@@ -350,3 +356,33 @@ class ProxmoxProvider(Provider):
"audit_only": audit_only_rules,
"rules": applied_rules,
}
async def delete_policy_rules(
self,
connection: ProviderConnection,
targets: list[dict[str, Any]],
policy_id: str,
) -> dict[str, Any]:
if connection.read_only:
return {"applied": False, "reason": "Cluster is read-only", "rules_deleted": 0, "targets": targets}
headers = {"Authorization": self.auth_header(connection.token)}
marker = self.policy_id_marker(policy_id)
deleted_rules = []
errors = []
async with httpx.AsyncClient(verify=connection.verify_tls, timeout=20) as client:
for target in targets:
rules_url = self.firewall_rules_url(connection, target)
try:
deleted = await self.delete_existing_policy_rules(client, headers, rules_url, marker)
except Exception as exc:
errors.append({"target": target, "error": str(exc)})
continue
deleted_rules.extend({"target": target, **item} for item in deleted)
return {
"applied": not errors,
"rules_deleted": len(deleted_rules),
"deleted_rules": deleted_rules,
"errors": errors,
}