feat: add policy enforcement mode normalization and audit mode protection for firewall apply operations

Add normalized_policy_definition helper to validate and default enforcement_mode to "enforced" or "audit" when creating/updating policies, extend firewall_apply to block live apply when policy is in audit mode with explanatory message, add policy_mode field to all firewall apply response paths, update FirewallPreview UI to show enforcement mode in policy dropdown with version number, display audit mode warning
This commit is contained in:
2026-07-09 15:57:23 +02:00
parent 571d1513e7
commit 5302a8bc82
2 changed files with 31 additions and 6 deletions
+8 -4
View File
@@ -29,6 +29,8 @@ export function FirewallPreview() {
}),
});
const selectedPolicyId = policyId || policies.data?.[0]?.id || "";
const selectedPolicy = (policies.data ?? []).find((policy) => policy.id === selectedPolicyId);
const auditMode = selectedPolicy?.enforcement_mode === "audit";
const busyMessage = preview.isPending
? "Generating firewall preview..."
: apply.isPending
@@ -46,7 +48,7 @@ export function FirewallPreview() {
<div className="grid gap-3">
<Field label="Policy">
<select className={selectClass} value={selectedPolicyId} onChange={(event) => setPolicyId(event.target.value)}>
{(policies.data ?? []).map((policy) => <option key={policy.id} value={policy.id}>{policy.name}</option>)}
{(policies.data ?? []).map((policy) => <option key={policy.id} value={policy.id}>{policy.name} · {policy.enforcement_mode} · v{policy.version}</option>)}
</select>
</Field>
<Field label="Cluster">
@@ -59,7 +61,9 @@ export function FirewallPreview() {
Dry run
</label>
<div className="rounded-md border border-border bg-canvas p-3 text-xs text-slate-500 dark:text-slate-400">
{dryRun
{auditMode
? "This policy is in audit mode. Preview and dry apply are allowed, but live apply will not write Proxmox firewall rules."
: dryRun
? "Simulation only. NexaFabric will generate the same provider rules, but nothing is written to Proxmox."
: "Live apply. NexaFabric will send the generated rules to the selected write-enabled cluster."}
</div>
@@ -67,9 +71,9 @@ export function FirewallPreview() {
<Play size={18} />
Generate Preview
</button>
<button className={buttonClass} disabled={!selectedPolicyId || apply.isPending} onClick={() => apply.mutate()}>
<button className={buttonClass} disabled={!selectedPolicyId || apply.isPending || (auditMode && !dryRun)} onClick={() => apply.mutate()}>
<ShieldCheck size={18} />
{dryRun ? "Run Dry Apply" : "Apply Confirmed"}
{dryRun ? "Run Dry Apply" : auditMode ? "Audit Mode Only" : "Apply Confirmed"}
</button>
</div>
</section>