From 6dc5fb5f3b8a264a53476d398e5b6dd9475e311f Mon Sep 17 00:00:00 2001 From: Denozordec Date: Sun, 16 Aug 2026 18:17:27 +0700 Subject: [PATCH] fix(api, web, docs): refine port ACL logic and documentation updates - Updated the `evofw-firewall.sh` script to clarify the handling of incoming traffic for port ACLs, ensuring accurate rule application for Docker NAT and local addresses. - Enhanced the UI description for port ACLs to specify that only incoming traffic is affected, improving user understanding of the firewall behavior. - Revised documentation to reflect the updated logic for port ACLs, emphasizing the distinction between incoming and outgoing traffic and the implications for service accessibility. These changes improve the clarity and functionality of port ACL management, enhancing user experience and system reliability. --- apps/api/src/agent-scripts/evofw-firewall.sh | 19 ++++++++++++++----- .../src/components/agents/agent-port-acl.tsx | 6 +++--- docs/agents.md | 2 +- docs/architecture.md | 2 +- 4 files changed, 19 insertions(+), 10 deletions(-) diff --git a/apps/api/src/agent-scripts/evofw-firewall.sh b/apps/api/src/agent-scripts/evofw-firewall.sh index 1ddb62a..de088bf 100644 --- a/apps/api/src/agent-scripts/evofw-firewall.sh +++ b/apps/api/src/agent-scripts/evofw-firewall.sh @@ -443,8 +443,8 @@ apply_nft() { ((${#batch[@]})) && nft_add_chunk "$table" "$name" allow_v4 "${batch[@]}" # input + forward: deny → Port ACL → allow → default. - # prerouting (mangle, before Docker NAT): established → deny → Port ACL only - # (no default drop — other traffic continues to host/Docker). + # prerouting (mangle, before Docker NAT): established → deny → Port ACL inbound only + # (fib daddr type local; no default drop — other traffic continues to host/Docker). if [[ "$DEFAULT_ACTION" == "drop" ]]; then nft add chain "$table" "$name" input '{ type filter hook input priority 0; policy drop; }' nft add chain "$table" "$name" forward '{ type filter hook forward priority 0; policy drop; }' @@ -562,18 +562,26 @@ def emit_set(p): if chunk: print(f"nft add element inet evofw {setname} '{{ {', '.join(chunk)} }}'") +def chain_match(chain): + if chain == "prerouting": + return "fib daddr type local " + if chain == "forward": + return "ct status dnat " + return "" + def emit_rule(p, chain): verdict = "drop" if p["action"] == "close" else "accept" comment = f"evofw-port-{p['rid']}" proto, dport = p["proto"], p["dport"] + prefix = chain_match(chain) if p["is_all"]: print( - f'nft add rule inet evofw {chain} {proto} dport {dport} counter {verdict} comment "{comment}"' + f'nft add rule inet evofw {chain} {prefix}{proto} dport {dport} counter {verdict} comment "{comment}"' ) return setname = f"port_src_{p['rid']}_{p['proto']}" print( - f'nft add rule inet evofw {chain} ip saddr @{setname} {proto} dport {dport} counter {verdict} comment "{comment}"' + f'nft add rule inet evofw {chain} {prefix}ip saddr @{setname} {proto} dport {dport} counter {verdict} comment "{comment}"' ) parsed = [p for p in (parse_rule(r) for r in rules) if p] @@ -598,8 +606,9 @@ for chain in CHAINS: continue seen.add(key) comment = f"evofw-port-implicit-{p['proto']}-{p['dport']}" + prefix = chain_match(chain) print( - f'nft add rule inet evofw {chain} {p["proto"]} dport {p["dport"]} counter drop comment "{comment}"' + f'nft add rule inet evofw {chain} {prefix}{p["proto"]} dport {p["dport"]} counter drop comment "{comment}"' ) PY local cmd diff --git a/apps/web/src/components/agents/agent-port-acl.tsx b/apps/web/src/components/agents/agent-port-acl.tsx index 4ecc437..52e450c 100644 --- a/apps/web/src/components/agents/agent-port-acl.tsx +++ b/apps/web/src/components/agents/agent-port-acl.tsx @@ -689,9 +689,9 @@ export function AgentPortAcl({ agentId }: AgentPortAclProps) {
Port ACL - Open по списку — порт только с этих IP (хост и Docker). - Системные порты можно переопределить. Apply через nft - (upgrade install-ссылкой). + Open по списку — входящие на порт только с этих IP (хост и + Docker publish). Исходящий клиентский 80/443 не режется. + Apply через nft (upgrade install-ссылкой).
diff --git a/docs/agents.md b/docs/agents.md index 52e3fa0..c491ba2 100644 --- a/docs/agents.md +++ b/docs/agents.md @@ -115,7 +115,7 @@ Per-agent таблица `agent_port_rules`: `open|close`, `tcp|udp|both`, port - API: CRUD `/api/v1/agents/:id/port-rules`, import `/port-rules/import` (from list или policy set sources) - Policy `apply_version: 3` → `port_rules[]` с expanded `src_cidrs` -- nft apply: close drop → open accept (`comment "evofw-port-"`) → **implicit drop** для каждого `(proto, dport)` с хотя бы одним `open` (`comment "evofw-port-implicit-…"`). Одно правило покрывает **хост и Docker**: hooks **prerouting** (priority mangle, до Docker NAT — публичный dport) + **input** + **forward**. Порт с open = whitelist: src из правила accept, остальные внешние drop. `lo` и `ct established,related` выше по цепочке. prerouting **без** terminal default drop (прочий трафик идёт дальше). +- nft apply: close drop → open accept (`comment "evofw-port-"`) → **implicit drop** для каждого `(proto, dport)` с хотя бы одним `open` (`comment "evofw-port-implicit-…"`). **Только входящие к сервисам** (не клиентский egress): **prerouting** `fib daddr type local` (публичный порт до Docker NAT) + **input** + **forward** `ct status dnat` (Docker `-p`). Порт с open = whitelist входящих: src из правила accept, остальные внешние drop. Исходящий curl/контейнер на 80/443 не матчится. `lo` и `ct established,related` выше по цепочке. prerouting **без** terminal default drop. - UI: tab **Port ACL** (DataGrid + Sheet create/edit + Import). Owner: **EvoFW** (desired) и **system** (listeners + foreign allow с хоста). Переопределённые system-строки скрыты в All. Системный порт можно переопределить → desired `open` (список/CIDR); ufw/iptables/Docker-цепочки не меняются. - ipset / MikroTik: без L4 apply; секции скрыты для non-linux diff --git a/docs/architecture.md b/docs/architecture.md index 0e09f88..6a96801 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -28,7 +28,7 @@ - Правило в наборе: `action: deny | allow` + ровно один источник — IP-список (`list_id`), CIDR или DNS-имя (`hostname` → A/AAAA, кэш в `policy_rule_resolved`) - Evaluate: правила всех назначенных enabled-наборов (sort + priority) + `ip_overrides` - Цепочка ядра **всегда**: deny → allow → `default_action` (`accept` | `drop` на агенте) -- На Linux nft: deny → **Port ACL** (`close` / `open` / implicit drop для портов с open) → allow → `default_action` на hooks **prerouting** (priority mangle, до Docker DNAT), **input** и **forward**. `open` по списку = публичный порт только с разрешённых src, хост и Docker `-p`. +- На Linux nft: deny → **Port ACL** (`close` / `open` / implicit drop для портов с open) → allow → `default_action` на hooks **prerouting** (priority mangle, `fib daddr type local`, до Docker DNAT), **input** и **forward** (`ct status dnat`). `open` по списку = **входящие** на публичный порт только с разрешённых src (хост и Docker `-p`); исходящий клиентский 80/443 не режется. - Exact overlap: `allow \ deny` (`conflicts_dropped`); deny wins - Overrides, смена наборов, `default_action`, Port ACL и refresh DNS/lists бампят `policy_generation`