Files
EvoFirewall/docs/policy-explain.md
T
Denozordec 9fa6269174
quality / changes (push) Successful in 7s
quality / commitlint (push) Skipped
quality / openapi (push) Successful in 30s
quality / web (push) Successful in 1m3s
quality / api (push) Successful in 1m15s
quality / linux-agent-smoke (push) Failing after 27s
quality / docker-check (push) Failing after 2m28s
CD / quality (push) Failing after 5m52s
CD / publish (push) Skipped
feat: harden firewall policies telemetry and operations
Implement reliable agent apply, telemetry v2, analytics retention, guarded rollouts, enrollment, labels and webhooks. Improve ReUI frontend data safety and bounded list loading, and add Docker smoke, recovery and resource checks. Document local verification and pending native acceptance.
2026-10-26 13:10:58 +07:00

5.5 KiB

Explain and temporary overrides

Explain endpoint

GET /api/v1/agents/:id/explain requires fw:agents:read (write/admin include read).

Query fields:

  • ip: strict IPv4/IPv6 address, canonicalized by shared network parsing.
  • port and protocol=tcp|udp: supplied together, otherwise omitted.
  • chain=input|forward, default input.
  • dnat=true|false, default false; forward Port ACL applies only when DNAT is explicitly selected.
  • connection_state=new|established|related, default new.

Shared exports: ExplainQuery, ExplainMatch, ExplainResponse, explainQuerySchema, explainResponseSchema.

The response contains desired (revision/hash, nullable decision, stage, one logical source witness), applied (last acknowledged revision/hash, comparison and diagnostics state), at most100 matches, matches_total, truncated, limits, and last observed external-firewall metadata. The external snapshot is always marked complete:false. An EvoFirewall accept does not establish that an external firewall or the destination service accepts the packet.

Desired Linux nft order mirrors the emitted rules: established/related acceptance, L3 deny membership, explicit matching Port ACL closes, matching opens, implicit close for unmatched sources of a configured open, L3 allow, default action. Input assumes non-loopback ingress. Ordinary non-DNAT forwarding skips Port ACL. With configured Port ACL and missing port/protocol, the result can be transport_unknown with a null decision. A known ipset/iptables backend has no owned conntrack bypass: its INPUT chain evaluates deny, allow, then optional default drop; accept-default falls through to external rules. Its FORWARD chain is external because the compiler installs no owned jump there. A regression asserts the real shell compiler's INPUT-only contract. Source witnesses reuse the compiler's list/hostname/active-override expansion; L3 attribution describes configured logical sources rather than a per-policy kernel packet counter.

A reported ipset/iptables backend with any Port ACL makes the entire candidate unsupported: the agent refuses that candidate rather than partially applying its L3 rules. Known IPv6 policy/ACL capabilities gate IPv6 explanation. RouterOS without a verified policy-ipv6 capability remains IPv4-only; RouterOS rejects all Port ACL candidates. For supported RouterOS families, input owns deny only and otherwise falls through to external rules; forward owns deny, allow, and optional default drop. Accept-default fallthrough remains external. Structured UNSUPPORTED_CAPABILITY errors map to diagnostics unsupported before generic failed status.

Override creation and expiry

POST /api/v1/agents/:id/overrides retains existing CIDR/action/comment input and adds:

  • Optional reason, trimmed,1..1024 characters.
  • Either optional ttl_seconds60..2592000 or expires_at (ISO timestamp), mutually exclusive. Explicit expiry must be future and within30 days. Omit both or set expires_at:null for a permanent override.
  • Author identity comes from authenticated user context. Client-supplied author fields are ignored.

Read/create DTO adds reason, created_by_user_id, created_by_name, expires_at, expired, and expiration_enforcement:"next_policy_sync". Legacy author/reason remain null when unknown. An allow override retains existing deny-wins semantics; it cannot bypass an applicable deny set.

Expiry changes desired policy when the next controller policy evaluation occurs. It is not a device-local timer and cannot remove an override from an offline agent. Expiration is recorded transactionally, bumps the agent generation once, and is excluded before hashing/compilation. Re-evaluation does not repeatedly bump generation and a backward clock does not reactivate an already expired row. An expired network can receive a new override without a unique-key conflict.

Limits:1000 active overrides per agent; exceeded creation or clone returns409. Clone validates its source before replacing target assignments/default/overrides and preserves absolute expiry/reason/provenance for active rows. Existing legacy over-capacity active configurations are preserved for operator reconciliation, while new additions and over-capacity clones are refused.

Expired display history is bounded to100 rows per agent and7 days, pruned during expiry/create lifecycle after generation handling. An inactive agent can temporarily retain up to its previous active bound until that lifecycle runs again. Local audit history is the durable source for creation author, reason and scheduled expiry after display rows are pruned. No global/shared policy set is mutated by override expiry.

Migration015 adds override metadata and an indexed expiry lookup. Migration016 is reserved for operational outbox work; further backend feature migrations start017.

Validation checkpoint

Nine service regressions cover backend-specific rule order, IPv4/IPv6/DNAT/stateful behavior, unsupported candidates, source witnesses, desired/applied separation, once-only expiry, archive/cardinality limits and clone isolation. A route regression verifies RBAC, canonical CIDR, server-owned author, TTL conflicts, duplicate409, expiry on policy sync and replacement after expiry. API TypeScript and shared/DB builds pass.

Separately, analytics follow-ups preserve explicit unknown v2 backfill deltas, count interval losses once across proportional buckets/retries, keep traffic-only coverage complete, preserve actual unsupported reasons, and retain exact hourly totals when exact minute intervals fit inside an hour.