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
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.
163 lines
13 KiB
Markdown
163 lines
13 KiB
Markdown
# Agents
|
||
|
||
## Short install (рекомендуется)
|
||
|
||
В UI `/agents` → **Добавить агента**:
|
||
|
||
1. Создаётся агент со статусом **Invited** (сразу виден в таблице) + install-ссылка.
|
||
2. Скопируйте one-liner (колонка Install или Sheet):
|
||
|
||
**Linux:**
|
||
```bash
|
||
curl -fsSL https://<cp>/agent-install/<id> | bash
|
||
```
|
||
|
||
**MikroTik:**
|
||
```
|
||
/tool fetch url="https://<cp>/agent-install/<id>" 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" }`.
|
||
|
||
## Одноразовое подключение и совместимость
|
||
|
||
Новый агент подключается только по созданной в UI install-ссылке. Скрипт содержит ограниченный credential конкретной ссылки; `POST /v1/agent/enroll` принимает его в `X-EvoFW-Enrollment-Credential`. Успешная регистрация потребляет credential атомарно; точный повтор того же запроса возвращает того же агента. Истёкшая, отозванная или использованная с другими данными ссылка не создаёт нового агента. Для чистой переустановки создайте новую ссылку.
|
||
|
||
Глобальный `EVOFW_SEED` / `X-EvoFW-Seed` больше не подходит для регистрации. `enroll_seed` хранится только на контроллере как HMAC master; UI/API возвращает маску, а не значение. Сохранение `********` оставляет ключ прежним. Явная смена master делает ещё не использованные старые install credentials недействительными; действующие токены агентов сохраняются.
|
||
|
||
Ротация токена поддерживается версиями `linux/10`, `rsc/10` и `install.sh/2`; более старый агент сначала обновляют. Администратор запрашивает ротацию для конкретного Approved агента; клиент сохраняет новый токен до обмена, получает подтверждение и атомарно обновляет конфигурацию. Старый токен после обмена принимается только для точного повтора завершённого обмена в течение 24 часов, а не для чтения политики. Отзыв агента запрещает и такие повторы.
|
||
|
||
Файлы: `/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 обычно не сработает).
|
||
|
||
### Linux self-update (sync-script)
|
||
|
||
После того как на хосте стоит скрипт с `maybe_self_update`, агент **сам** подтягивает новую версию при каждом timer (~1 мин), **до** `GET /v1/agent/policy` (pending тоже обновляются):
|
||
|
||
1. sha256 локального `/usr/local/sbin/evofw-firewall.sh`
|
||
2. `GET /v1/agent/sync-script` с `If-None-Match: "<sha256>"` → **304** = без изменений
|
||
3. **200**: проверка shebang + sha256 тела → `install -m 755`, сброс `last_hash`, `exec EVOFW_SKIP_SELF_UPDATE=1` новой копии
|
||
4. Ошибка download/verify — лог и продолжение **текущим** скриптом
|
||
|
||
`GET /v1/agent/policy` содержит `script_sha256` (не входит в `policy.hash`).
|
||
|
||
**Bootstrap:** агенты без `maybe_self_update` не умеют самообновляться. После деплоя API — **один** re-run install-ссылки (или ручная замена `sync-script`). Дальше curl не нужен.
|
||
|
||
MikroTik scheduler **не** обновляется этим путём — только повторный import install `.rsc`.
|
||
|
||
**Uninstall (Linux):**
|
||
```bash
|
||
curl -fsSL https://<cp>/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.
|
||
- Агенты с self-update подтянут nft-правила сами; без него — **один** 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: close drop → open accept (`comment "evofw-port-<id>"`) → **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
|
||
|
||
Мутация Port ACL бампит `policy_generation` → agent re-apply. Нужен self-update скрипта или re-run install-ссылки.
|
||
|
||
## MikroTik (RouterOS 7.21+)
|
||
|
||
В UI `/agents` → **Добавить агента** → platform **MikroTik**. Скопируйте one-liner:
|
||
|
||
```
|
||
/tool fetch url="https://<cp>/agent-install/<id>" dst-path=evofw-install.rsc; /import file-name=evofw-install.rsc
|
||
```
|
||
|
||
Или короткий slug: `https://<cp>/<slug>`.
|
||
|
||
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
|
||
```
|