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`