# Agents ## Short install (рекомендуется) В UI `/agents` → **Добавить агента**: 1. Создаётся агент со статусом **Invited** (сразу виден в таблице) + install-ссылка. 2. Скопируйте one-liner (колонка Install или Sheet): **Linux:** ```bash curl -fsSL https:///agent-install/ | bash ``` **MikroTik:** ``` /tool fetch url="https:///agent-install/" dst-path=evofw-install.rsc; /import file-name=evofw-install.rsc ``` 3. После enroll статус станет **Pending** — одобрите агента (Approve). 4. **Approved** — агент синхронизирует политику. **Повторный запуск той же install-ссылки** на хосте, где агент уже стоит: обновляет sync-скрипт / timer (или MikroTik scheduler) **и пересоздаёт nft rules** (в т.ч. `deny_port_hits`), **без** повторного enroll — `CLIENT_ID`/`token` сохраняются. Сбрасывается `last_hash`, затем сразу force sync. Полный переустановки с новым токеном: `EVOFW_INSTALL_FORCE=1` (Linux). API (auth): `POST /api/v1/install-links` `{ "name": "web-01", "platform": "linux" | "mikrotik" }`. ## Linux (legacy one-liner) ```bash curl -fsSL https:///v1/agent/install.sh | \ EVOFW_CP_URL=https:// \ EVOFW_SEED= \ EVOFW_CLIENT_NAME="web-01" \ bash ``` Создаёт нового агента со статусом Pending (без Invited). Файлы: `/etc/evofw/agent.conf`, `/usr/local/sbin/evofw-firewall.sh`, timer `evofw-firewall.timer` (default 1min). Install сам ставит зависимости через apt/dnf/yum/apk: `curl`, `jq` (или `python3`), `nftables`/`iptables`(+`ipset`). Планировщик: **systemd timer** если есть `/run/systemd/system`, иначе ставит `cron`/`cronie` и пишет crontab. Значения в `agent.conf` всегда в single quotes (имена с пробелами безопасны). Sync при статусе pending завершается с exit 0 (`pending approval`), чтобы systemd timer не был failed. Если `/etc/evofw/agent.conf` уже есть — install переходит в **update**: скачивает свежий `sync-script` + `uninstall.sh`, перезаписывает unit/timer, оставляет токен. `EVOFW_INSTALL_FORCE=1` — полный re-enroll (новый токен; для уже Approved install-link обычно не сработает). **Uninstall (Linux):** ```bash curl -fsSL https:///v1/agent/uninstall.sh | bash # или локально после install: sudo /usr/local/sbin/evofw-uninstall.sh ``` Backend auto-detect: nft → ipset → iptables. Whitelist: nft chain policy drop + allow set. Blacklist: policy accept + deny set. ## Per-IP blocked stats (Linux) Linux agent reports optional `ip_hits` in `POST /v1/agent/apply-report`: - **nft:** tries set `deny_v4` with `flags interval; counter;`. If the kernel rejects counters on interval sets, falls back to plain interval (aggregate Traffic ↓ still works; per-IP empty). - Upgrade path: on install-link re-run, `last_hash` is cleared once so sets/chain can be recreated (chain deleted before set replace) — **обновляет и port-hit правила**. - **ipset:** prefers `hash:net … counters` on create; existing sets without counters are left as-is. - Payload: only entries with `packets > 0`, **top 200** by packets. - Control plane: `agent_ip_block_stats`, accumulates **deltas** of absolute kernel counters (как Traffic ↓). После flush set/chain (policy apply) CP сбрасывает per-IP baseline (`last_reported`), иначе вторая эпоха счётчиков теряется (Traffic растёт, Blocked IPs — нет). - `GET /api/v1/agents/:id/blocked-ips`, reset via `POST …/stats/reset`. - UI: agent detail → **Blocked IPs**. Sum of Blocked IPs ≈ Traffic ↓ для deny (при default accept); default-drop / allow в Traffic считаются отдельно. IPv6 skipped. ## Destination ports (Linux nft) На **nft** агент ведёт dynamic set `deny_port_hits` (`ipv4_addr . inet_proto . inet_service`, timeout 1h, counter): - Deny rule для tcp/udp: `update @deny_port_hits { ip saddr . meta l4proto . th dport }` + drop; прочие протоколы — plain drop. - В `apply-report`: optional `port_hits` top **500** `{ ip, port, protocol, packets }`. - CP: `agent_port_block_stats` (absolute deltas). Set **не** flush’ится на каждый policy apply (элементы живут по timeout) — baseline не сбрасывается при Traffic flush. - `GET /api/v1/agents/:id/blocked-ports` — aggregate top 50 портов; в `blocked-ips` у каждого IP — `ports` top 5. - UI: **Top ports** + колонка Ports в Blocked IPs (только `platform=linux`). - **ipset/iptables:** `port_hits: []`. MikroTik — без port hits. - Чтобы подтянуть правила на уже установленном агенте: **re-run install one-liner** (см. выше). IPv6 skipped. ## Host firewall snapshot + Port ACL (Linux) ### Observed (Host firewall) Каждый sync агент собирает best-effort снимок и шлёт в `apply-report.host_firewall`: - `nft list ruleset`, `iptables-save`, optional `ufw` / `firewall-cmd`, `ss -lntu` - Каждое правило: `ownership: evofw | foreign` (метка по имени table/chain/comment `evofw`) - CP: `agent_host_firewall_snapshots`; `GET /api/v1/agents/:id/host-firewall` - UI agent detail → tab **Host firewall** (Rules / Listeners) Foreign правила **только отображаются** — с CP не редактируются. ### Desired (Port ACL) Per-agent таблица `agent_port_rules`: `open|close`, `tcp|udp|both`, port range, `src_kind: all|cidr|list`. - 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: после L3 allow — close drop, затем open accept (`comment "evofw-port-"`) - UI: tab **Port ACL** (DataGrid + Sheet create/edit + Import) - ipset / MikroTik: без L4 apply; секции скрыты для non-linux Мутация Port ACL бампит `policy_generation` → agent re-apply. ## MikroTik (RouterOS 7.21+) В UI `/agents` → **Добавить агента** → platform **MikroTik**. Скопируйте one-liner: ``` /tool fetch url="https:///agent-install/" dst-path=evofw-install.rsc; /import file-name=evofw-install.rsc ``` Или короткий slug: `https:///`. Install RSC: 1. Enroll (с `install_link_id` → агент Invited → Pending). 2. Создаёт filter-правила `evofw-*` **в начале** цепочек `input`/`forward` (`place-before`) и address-list `EVOFW_DENY` / `EVOFW_ALLOW` / dynamic **`EVOFW_HITS`**. 3. Scheduler `evofw-sync` каждую минуту: `GET /v1/agent/policy` (JSON) → rebuild deny/allow + report (+ `ip_hits` из HITS). Не использует `/import` огромного `.rsc`. Лог: `/log print where message~"evofw"`. Ручной sync: `/system script run evofw-sync`. Traffic ↓/↑ в UI — сумма `packets` с `evofw-deny-drop-*` / `evofw-allow-*` / `evofw-default-drop-*` (накопительно, пока правила не пересозданы re-install). ### Per-IP / Blocked IPs (MikroTik) Цепочка deny: **hit → drop** (семантика drop/allow/default как раньше): 1. `evofw-deny-hit-input/forward` — `add-src-to-address-list` → `EVOFW_HITS`, `address-list-timeout=1h` (passthrough). 2. `evofw-deny-drop-input/forward` — `drop` по `EVOFW_DENY`. В `EVOFW_HITS` попадают реальные src **/32**. Policy rebuild **не** чистит HITS (только DENY/ALLOW). Sync шлёт top-200 в `ip_hits`; CP mode **presence**: `last_seen` обновляется, пока IP в HITS; `packets` (Hits) увеличивается только при первом появлении или **повторном входе** после исчезновения из списка (~>2.5 мин без report), а не на каждый sync. UI: agent detail → **Blocked IPs** (колонка Hits). **Default action** задаётся на **агенте** (`default_action: accept | drop`): - **accept** — пакет вне deny/allow пропускается - **drop** — пакет вне deny/allow отбрасывается (forward) Цепочка: deny-hit → deny-drop → allow-accept → default. Наборы несут только правила deny/allow, без exclusive mode. ## Force sync ```bash sudo rm -f /var/lib/evofw/last_hash sudo /usr/local/sbin/evofw-firewall.sh ```