fix(daemon): update PID and timestamps in .codegraph/daemon.pid for synchronization
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"pid": 59836,
|
||||
"pid": 40060,
|
||||
"version": "0.9.9",
|
||||
"socketPath": "\\\\.\\pipe\\codegraph-97b92efdcc5351da",
|
||||
"startedAt": 1783332305171
|
||||
"startedAt": 1783395081742
|
||||
}
|
||||
|
||||
@@ -0,0 +1,330 @@
|
||||
---
|
||||
name: Firewall HTTP blocklist feature
|
||||
overview: "Реализовать подсистему «Firewall Blocklist»: произвольный Linux-сервер через bash-скрипт по cron получает по HTTP список CIDR выбранного BGP community из последней ревизии tenant и блокирует их через ipset/nftables/iptables (автоопределение). Авторизация клиента — Bearer-токен, который клиент генерирует сам; первичная регистрация (enroll) авторизуется через `EVOBGP_BUNDLE_SEED_HEX`; operator подтверждает доверие клиенту в Web UI EvoBGP. На клиенте — только bash-скрипты, без бинарников."
|
||||
todos:
|
||||
- id: migrate
|
||||
content: Создать миграцию 000027_firewall_client (postgres + sqlite, 4 файла) по DEP-03
|
||||
status: pending
|
||||
- id: store
|
||||
content: "Расширить store.Backend: типы FirewallClient*, методы CRUD/Lookup/Touch + реализации в memory.go и postgres_firewall_client.go"
|
||||
status: pending
|
||||
- id: resolver
|
||||
content: Создать firewall_resolver.go (по образцу api_key_resolver.go) и интегрировать в resolveAuth (auth.go)
|
||||
status: pending
|
||||
- id: handlers
|
||||
content: Написать routes_firewall.go (enroll через seed, blocklist, apply-report, CRUD operator-эндпоинты) + регистрация в registerV1
|
||||
status: pending
|
||||
- id: openapi
|
||||
content: Обновить docs/openapi.yaml схемами и операциями тега Firewall; npx @redocly/cli lint (TEST-05)
|
||||
status: pending
|
||||
- id: webui
|
||||
content: "Web UI: queries/firewall.ts + route /firewall + sidebar link + shadcn-компоненты; typecheck+lint+build exit 0 (WEB-19)"
|
||||
status: pending
|
||||
- id: bash
|
||||
content: Написать scripts/firewall/{install.sh,evobgp-firewall.sh,uninstall.sh} с автоопределением backend (nft→ipset→iptables)
|
||||
status: pending
|
||||
- id: docs
|
||||
content: "Документация: docs/firewall.md (RU) + правки access.md/api.md/architecture.md"
|
||||
status: pending
|
||||
- id: tests
|
||||
content: "Тесты: табличные store-тесты + handlers-тесты (enroll/approve/blocklist/apply-report); go vet + go test -race + golangci-lint"
|
||||
status: pending
|
||||
isProject: false
|
||||
---
|
||||
|
||||
# Firewall Blocklist — план реализации
|
||||
|
||||
## Контекст проекта (ключевые факты из анализа)
|
||||
|
||||
- Авторизация: `internal/httpapi/auth.go` — Bearer → SHA-256 → in-process индекс (`apiKeyResolver`). Роли `viewer/editor/operator/node` через `roleLevel` + `requireAtLeast`/`requireNode`. Чтобы добавить новую роль, нужно синхронно править: `roleLevel`, `store.ValidAPIKeyRole`, CHECK-констрейнт `api_key_role_chk` новой миграцией (DEP-03 — пары postgres+sqlite).
|
||||
- Подпись бандлов: `EVOBGP_BUNDLE_SEED_HEX` (32 байта hex) → `ed25519.NewKeyFromSeed` в `internal/httpapi/server.go:60-71`. Seed уже доступен в `Server` через `opts.BundleSeedHex`.
|
||||
- Префиксы в ревизиях: `store.Backend.ListRevisionPrefixes(tenantID, revID, cursor, limit)` возвращает `[]PrefixRow{Prefix, CommunityID, Source}`. `ListRevisions(tenantID, moduleID, cursor, limit)` даёт последнюю ревизию. `prefix_snapshot_row.community_id` — FK на `bgp_community`.
|
||||
- Токены API: `internal/authkey/token.go` — `GenerateToken()` (`evobgp_<32 b64url>`), `HashToken()` (SHA-256 → 32 байта), `Prefix()`. В БД — `api_key.token_hash BYTEA` + UNIQUE-индекс.
|
||||
- Миграции: 26 пар, последняя `000026_runtime_log_cleanup_audit`. Новая — `000027_firewall_client` для postgres+sqlite, синхронно.
|
||||
- Web UI: TanStack Router file-based (`apps/web/src/routes/_auth/*.tsx`), TanStack Query (`apps/web/src/queries/*.ts`), shadcn/ui + ReUI.
|
||||
|
||||
## Поток данных
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph client [Linux-клиент только bash]
|
||||
Installer[install.sh curl-bash]
|
||||
Conf["/etc/evobgp/firewall.conf
|
||||
client_id, token, base_url,
|
||||
community_id"]
|
||||
Sync[evobgp-firewall.sh по cron/systemd]
|
||||
Kernel[(ipset/nftables/iptables)]
|
||||
end
|
||||
subgraph cp [Control Plane EvoBGP]
|
||||
API[evobgp-api/all]
|
||||
PG[(PostgreSQL)]
|
||||
UI[Web UI /firewall]
|
||||
end
|
||||
Installer -->|1 POST /v1/firewall/enroll
|
||||
X-EvoBGP-Seed, client_token| API
|
||||
API --> PG
|
||||
Installer --> Conf
|
||||
Operator -->|2 Approve в UI| UI
|
||||
UI -->|PATCH /firewall/clients/{id}/approve| API
|
||||
Sync -->|3 GET /v1/firewall/blocklist
|
||||
Bearer client_token| API
|
||||
API --> PG
|
||||
API -->|CIDR list по community_id| Sync
|
||||
Sync --> Kernel
|
||||
Sync -->|report last_apply| API
|
||||
```
|
||||
|
||||
## Шаг 1 — Миграция БД `000027_firewall_client`
|
||||
|
||||
Новые файлы (DEP-03 — пары для postgres+sqlite):
|
||||
|
||||
- `migrations/postgres/000027_firewall_client.up.sql`
|
||||
- `migrations/postgres/000027_firewall_client.down.sql`
|
||||
- `migrations/sqlite/000027_firewall_client.up.sql`
|
||||
- `migrations/sqlite/000027_firewall_client.down.sql`
|
||||
|
||||
Схема `firewall_client` (Postgres):
|
||||
|
||||
```sql
|
||||
CREATE TABLE firewall_client (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tenant_id UUID NOT NULL REFERENCES tenant (id) ON DELETE CASCADE,
|
||||
name TEXT NOT NULL,
|
||||
hostname TEXT,
|
||||
community_id UUID REFERENCES bgp_community (id) ON DELETE SET NULL,
|
||||
token_prefix TEXT NOT NULL,
|
||||
token_hash BYTEA NOT NULL,
|
||||
status TEXT NOT NULL DEFAULT 'pending',
|
||||
enroll_seed_used BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
last_seen_at TIMESTAMPTZ,
|
||||
last_seen_ip TEXT,
|
||||
last_apply_at TIMESTAMPTZ,
|
||||
last_apply_status TEXT,
|
||||
last_apply_error TEXT,
|
||||
last_apply_prefix_count INTEGER DEFAULT 0,
|
||||
last_apply_ip_count INTEGER DEFAULT 0,
|
||||
client_version TEXT,
|
||||
settings_json JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
approved_at TIMESTAMPTZ,
|
||||
approved_by_api_key_id UUID,
|
||||
revoked_at TIMESTAMPTZ,
|
||||
CONSTRAINT firewall_client_status_chk CHECK (status IN ('pending','approved','revoked')),
|
||||
CONSTRAINT firewall_client_name_chk CHECK (length(trim(name)) > 0),
|
||||
CONSTRAINT firewall_client_token_hash_len_chk CHECK (octet_length(token_hash) = 32)
|
||||
);
|
||||
CREATE UNIQUE INDEX idx_firewall_client_token_hash ON firewall_client (token_hash);
|
||||
CREATE INDEX idx_firewall_client_tenant_status ON firewall_client (tenant_id, status);
|
||||
CREATE INDEX idx_firewall_client_last_seen ON firewall_client (last_seen_at DESC) WHERE status = 'approved';
|
||||
```
|
||||
|
||||
SQLite — то же с поправкой на типы (`TEXT`/`BLOB`, `length(token_hash) = 32`).
|
||||
|
||||
Миграция НЕ трогает `api_key` — `firewall_client` — отдельная таблица (как у speakers своя `agent_secret`, но здесь переиспользуется только формат токена `authkey`).
|
||||
|
||||
## Шаг 2 — Роль `firewall`
|
||||
|
||||
Изолированная роль (level 0, как `node`):
|
||||
|
||||
- `internal/httpapi/auth.go`: добавить функцию `requireFirewall(w, a)` по аналогии с `requireNode` (`auth.go:137-143`). В `roleLevel` `firewall` явно не добавлять (остаётся `default → 0`), чтобы `requireAtLeast` её отбрасывал.
|
||||
- `internal/store/backend.go:305-313` — добавить `firewall` в `ValidAPIKeyRole` НЕ нужно (токены firewall-клиентов живут в отдельной таблице, не в `api_key`). Но если хотим единую модель — рассмотреть расширение `api_key`. Решение: **отдельная таблица**, чтобы не дублировать резолвер. Новый resolver `firewallTokenResolver` по аналогии с `apiKeyResolver`, но с тремя статусами.
|
||||
- `internal/httpapi/auth.go` `resolveAuth` — расширить: если `apiKeyResolver.Lookup` не нашёл, попробовать `firewallResolver.Lookup`. Возвращать `Auth{Role: "firewall", APIKeyID: <firewall_client_id>, TenantID: ...}`.
|
||||
|
||||
## Шаг 3 — Store слой
|
||||
|
||||
В `internal/store/backend.go`:
|
||||
|
||||
- Структуры `FirewallClient`, `FirewallClientPatch`, `FirewallClientWithSecret` (для issue/rotate).
|
||||
- Методы в `Backend`:
|
||||
- `ListFirewallClients(tenantID string) ([]*FirewallClient, error)`
|
||||
- `GetFirewallClient(tenantID, id string) (*FirewallClient, error)`
|
||||
- `CreateFirewallClient(tenantID string, in *FirewallClientCreate) (*FirewallClient, error)`
|
||||
- `UpdateFirewallClient(tenantID, id string, patch *FirewallClientPatch) (*FirewallClient, error)`
|
||||
- `ApproveFirewallClient(tenantID, id, approverAPIKeyID string) (*FirewallClient, error)`
|
||||
- `RevokeFirewallClient(tenantID, id string) error`
|
||||
- `DeleteFirewallClient(tenantID, id string) error`
|
||||
- `LookupFirewallClientByTokenHash(hash []byte) (*FirewallClient, error)`
|
||||
- `TouchFirewallClientLastSeen(id, clientIP, clientVersion string) error`
|
||||
- `TouchFirewallClientLastApply(id, status, errMsg string, prefixCount, ipCount int) error`
|
||||
- `ListActiveFirewallClientHashes() ([]FirewallClientAuthRow, error)`
|
||||
|
||||
Реализации:
|
||||
|
||||
- `internal/repository/postgres_firewall_client.go` (по образцу `postgres_api_key.go`)
|
||||
- `internal/store/memory.go` (in-memory для тестов/dev)
|
||||
|
||||
## Шаг 4 — HTTP API
|
||||
|
||||
Новый файл `internal/httpapi/routes_firewall.go` + регистрация в `registerV1` (`routes.go:80`):
|
||||
|
||||
```go
|
||||
m.HandleFunc("POST /firewall/enroll", s.handleFirewallEnroll) // X-EvoBGP-Seed авторизация
|
||||
m.HandleFunc("GET /firewall/clients", s.handleListFirewallClients) // operator+
|
||||
m.HandleFunc("GET /firewall/clients/{id}", s.handleGetFirewallClient)
|
||||
m.HandleFunc("PATCH /firewall/clients/{id}", s.handlePatchFirewallClient)
|
||||
m.HandleFunc("POST /firewall/clients/{id}/approve", s.handleApproveFirewallClient)
|
||||
m.HandleFunc("POST /firewall/clients/{id}/revoke", s.handleRevokeFirewallClient)
|
||||
m.HandleFunc("DELETE /firewall/clients/{id}", s.handleDeleteFirewallClient)
|
||||
m.HandleFunc("GET /firewall/blocklist", s.handleFirewallBlocklist) // role=firewall
|
||||
m.HandleFunc("POST /firewall/apply-report", s.handleFirewallApplyReport) // role=firewall
|
||||
```
|
||||
|
||||
### 4.1 `POST /v1/firewall/enroll`
|
||||
|
||||
- Авторизация по заголовку `X-EvoBGP-Seed: <hex>`. Сервер сверяет `strings.EqualFold(headerSeed, opts.BundleSeedHex)`. Если BundleSeedHex пустой или не совпадает → 403.
|
||||
- Body: `{name, hostname, community_id, client_token, client_version}`. `client_token` — генерируется клиентом в формате `evobgp_fw_<32 b64url>` (валидация через новую функцию `authkey.ValidateToken`).
|
||||
- Tenant для нового клиента — берётся из demo-tenant (`s.store.DemoIDs()`) или первого tenant в системе (как делает `dev`-токен в `auth.go:103-109`). Альтернатива: tenant берётся из tied api_key, которым operator заранее создал «invite» — **не реализуем в первой версии**, упростим до demo/первого tenant.
|
||||
- Создаёт `firewall_client` со статусом `pending`, `token_hash = authkey.HashToken(client_token)`.
|
||||
- Ответ `201`: `{client_id, status: "pending", message: "ожидает подтверждения оператором"}`.
|
||||
|
||||
### 4.2 `GET /v1/firewall/blocklist` (роль `firewall`)
|
||||
|
||||
- `requireFirewall(w, a)`. `a.APIKeyID` — это `firewall_client.id`.
|
||||
- Загрузить клиент → если `status != "approved"` → `403` с `Retry-After: 60` и `{status, message}`.
|
||||
- Обновить `TouchFirewallClientLastSeen(id, RemoteAddr, User-Agent)`.
|
||||
- Найти последнюю ревизию: `s.store.ListRevisions(tenantID, "", "", 1)`.
|
||||
- Если `community_id` клиента задан — вытащить все префиксы ревизии с этим `CommunityID` через `ListRevisionPrefixes` (пагинация до `limit=500`, далее курсор), фильтр на стороне Go (или новый метод `ListRevisionPrefixesByCommunity` — опционально для перфоманса PERF-01).
|
||||
- Формат ответа (JSON):
|
||||
|
||||
```json
|
||||
{
|
||||
"client_id": "<uuid>",
|
||||
"community_id": "<uuid>",
|
||||
"revision_id": "<uuid>",
|
||||
"generated_at": "2026-07-07T16:00:00Z",
|
||||
"prefixes": ["1.2.3.0/24", "5.6.7.8/32", "2001:db8::/32"],
|
||||
"total": 1234,
|
||||
"hash": "sha256:..."
|
||||
}
|
||||
```
|
||||
|
||||
Альтернативный формат `Accept: text/plain` — построчно `# <community>\n1.2.3.0/24\n...` (для удобства bash).
|
||||
|
||||
### 4.3 `POST /v1/firewall/apply-report` (роль `firewall`)
|
||||
|
||||
- Body: `{status: "ok|error", error, prefix_count, ip_count, version, kernel_method: "ipset|nftables|iptables"}`.
|
||||
- Сервер обновляет `last_apply_*` поля клиента.
|
||||
|
||||
### 4.4 Operator-эндпоинты (`/firewall/clients/*`)
|
||||
|
||||
- `requireAtLeast(w, a, "operator")` для всех (по аналогии с `routes_api_keys.go`).
|
||||
- `POST /approve` — ставит `status='approved'`, `approved_at=now()`, `approved_by_api_key_id=a.APIKeyID`. После — `firewallResolver.Reload`.
|
||||
- `POST /revoke` — `status='revoked'`, `revoked_at=now()`. Reload.
|
||||
- PATCH — смена `community_id`, `name`, `hostname`.
|
||||
- DELETE — каскадное удаление (или soft-delete через revoked).
|
||||
|
||||
## Шаг 5 — Firewall Token Resolver
|
||||
|
||||
`internal/httpapi/firewall_resolver.go` (по образцу `api_key_resolver.go`):
|
||||
|
||||
- `type firewallTokenResolver struct { mu sync.RWMutex; byHash map[string]firewallAuthRow }`
|
||||
- `Lookup(raw string) (firewallAuthRow, bool)` — SHA-256(raw) → hex → map.
|
||||
- `Reload(s store.Backend) error` — `store.ListActiveFirewallClientHashes()` (только `status='approved'`).
|
||||
- После approve/revoke/PATCH в handlers — `s.firewallResolver.Reload(s.store)`.
|
||||
- В `auth.go` `resolveAuth` — fallback на `firewallResolver.Lookup` если `apiKeyResolver` промахнулся.
|
||||
|
||||
## Шаг 6 — OpenAPI (DOC-02: сначала контракт)
|
||||
|
||||
Обновить `docs/openapi.yaml`:
|
||||
|
||||
- Схемы: `FirewallClient`, `FirewallClientCreate`, `FirewallEnrollRequest`, `FirewallEnrollResponse`, `FirewallBlocklist`, `FirewallApplyReport`.
|
||||
- Параметр `X-EvoBGP-Seed` (header) для `/firewall/enroll`.
|
||||
- 7 операций под тегом `Firewall`. lint: `npx @redocly/cli lint docs/openapi.yaml` (TEST-05).
|
||||
|
||||
## Шаг 7 — Web UI (`apps/web/`)
|
||||
|
||||
- `apps/web/src/queries/firewall.ts` — key factory + queryOptions (`firewallKeys.all/clients/client(id)`), mutations через `apiJSON`/`apiFetch`.
|
||||
- `apps/web/src/routes/_auth/firewall.tsx` — страница:
|
||||
- Список клиентов (таблица через ReUI data-grid): name, hostname, community, status (badge: pending/approved/revoked), last_seen_at, last_apply_at, last_apply_status.
|
||||
- Кнопки: Approve (для pending), Revoke (для approved), Edit (смена community), Delete, View token (только после создания).
|
||||
- Раздел «Запросы на подтверждение» (pending) сверху с уведомлением.
|
||||
- Помощь по установке (один клик копирует curl-bash).
|
||||
- Добавить пункт в `apps/web/src/components/app-sidebar.tsx` (или где nav): «Firewall клиенты».
|
||||
- Использовать shadcn/ui компоненты (MCP `plugin-shadcn-shadcn`): `DataTable`, `Badge`, `Dialog`, `Select` (для community). Скилл `.agents/skills/shadcn-react/SKILL.md` — обязательно.
|
||||
- Перед завершением (WEB-19): `pnpm --filter @evobgp/web run typecheck && lint && build` — все exit 0.
|
||||
|
||||
## Шаг 8 — Bash-скрипты клиента
|
||||
|
||||
Каталог `scripts/firewall/`:
|
||||
|
||||
### 8.1 `install.sh` (curl-bash one-liner)
|
||||
|
||||
```bash
|
||||
curl -fsSL https://cp.example.com/firewall/install.sh | \
|
||||
EVOBGP_CP_URL=https://cp.example.com \
|
||||
EVOBGP_SEED=<seed_hex> \
|
||||
EVOBGP_COMMUNITY_ID=<uuid> \
|
||||
EVOBGP_CLIENT_NAME="edge-router-01" \
|
||||
bash
|
||||
```
|
||||
|
||||
Что делает `install.sh`:
|
||||
|
||||
1. Генерирует `CLIENT_TOKEN="evobgp_fw_$(head -c 32 /dev/urandom | basenc --base64url)"`.
|
||||
2. `POST ${EVOBGP_CP_URL}/v1/firewall/enroll` с заголовком `X-EvoBGP-Seed: ${EVOBGP_SEED}` и body `{name, hostname, community_id, client_token}`.
|
||||
3. Получает `client_id`, сохраняет в `/etc/evobgp/firewall.conf` (`CLIENT_ID`, `CLIENT_TOKEN`, `EVOBGP_CP_URL`, `COMMUNITY_ID`).
|
||||
4. Скачивает `evobgp-firewall.sh` в `/usr/local/sbin/evobgp-firewall.sh`, делает `chmod +x`.
|
||||
5. Автоопределение backend: проверяет наличие `nft` → `ipset` → `iptables`.
|
||||
6. Ставит systemd timer `evobgp-firewall.timer` (каждые 5 мин) и сервис, активирует. Fallback — crontab-строка `*/5 * * * *`.
|
||||
|
||||
### 8.2 `evobgp-firewall.sh` (main sync)
|
||||
|
||||
Алгоритм (atomic swap):
|
||||
|
||||
1. Читать `/etc/evobgp/firewall.conf`.
|
||||
2. `GET ${CP_URL}/v1/firewall/blocklist` с `Authorization: Bearer ${CLIENT_TOKEN}`.
|
||||
- Если 403 + status `pending` — exit 0, залогировать «ожидает подтверждения».
|
||||
- Если 401 — exit с ошибкой (токен отозван?).
|
||||
3. Распарсить JSON (через `jq` или минимальный grep, т.к. не везде есть jq; fallback: запросить `Accept: text/plain`).
|
||||
4. Подготовить новый набор через backend:
|
||||
- **nftables**: создать `evobgp_blocklist_v4`/`evobgp_blocklist_v6` sets в новой таблице `inet evobgp_blocklist`, atomic `flush` + add, цепочка `input` с drop.
|
||||
- **ipset**: `ipset create evobgp_blocklist_v4_new hash:net family inet`, заполнить, `ipset swap` + destroy old. Аналогично для v6. iptables-правило `-m set --match-set evobgp_blocklist_v4 src -j DROP` (idempotently через `-C`).
|
||||
- **iptables**: вызвать `iptables-restore` с transition-чек-примитивами (медленно, но fallback).
|
||||
5. `POST ${CP_URL}/v1/firewall/apply-report` с метрикой.
|
||||
6. Лог в `/var/log/evobgp-firewall.log` + `logger -t evobgp-firewall`.
|
||||
|
||||
Идемпотентность: финальное состояние = ровно набор IP из ответа (atomic swap гарантирует consistency).
|
||||
|
||||
### 8.3 `uninstall.sh`
|
||||
|
||||
- Снять systemd unit, убрать crontab, flush/destroy ipset sets / nft table / iptables rules.
|
||||
|
||||
## Шаг 9 — Конфиг и env
|
||||
|
||||
В `internal/httpapi/server.go` `Options` уже есть `BundleSeedHex` — переиспользуем. Новых env не нужно. Документировать `EVOBGP_BUNDLE_SEED_HEX` как обязательно-стабильный для firewall-flow в `docs/access.md`.
|
||||
|
||||
## Шаг 10 — Документация (DOC-01, RU)
|
||||
|
||||
- `docs/firewall.md` — раздел: назначение, схема авторизации, install one-liner, операторский flow (approve в UI), troubleshooting.
|
||||
- Обновить `docs/access.md` — новая роль `firewall` + матрица прав в конце.
|
||||
- Обновить `docs/api.md` — группа `/v1/firewall/*`.
|
||||
- Обновить `docs/architecture.md` — упомянуть firewall-клиентов как отдельный class edge-clients.
|
||||
|
||||
## Шаг 11 — Тесты (TEST-01/02)
|
||||
|
||||
- `internal/store/...` — табличные тесты CRUD `firewall_client` для memory + postgres.
|
||||
- `internal/httpapi/routes_firewall_test.go` — enroll через seed, approve, blocklist (pending → 403, approved → 200), apply-report.
|
||||
- `scripts/firewall/test_install.sh` — smoke-тест install.sh в Docker (опционально).
|
||||
|
||||
## Порядок выполнения (после подтверждения плана)
|
||||
|
||||
1. Миграция `000027` (postgres + sqlite, 4 файла).
|
||||
2. Store: структуры + интерфейс + memory + postgres реализации.
|
||||
3. `firewall_resolver.go` + интеграция в `resolveAuth`.
|
||||
4. Handlers `routes_firewall.go` + регистрация.
|
||||
5. OpenAPI-обновление + redocly lint.
|
||||
6. Web UI: queries + route + sidebar link + shadcn-компоненты.
|
||||
7. Bash-скрипты `scripts/firewall/`.
|
||||
8. Документация `docs/firewall.md` + правки access.md/api.md/architecture.md.
|
||||
9. Тесты + локальная проверка: `go vet`, `go test -race`, `golangci-lint`, `pnpm typecheck/lint/build`, `redocly lint`.
|
||||
|
||||
## Ключевые файлы, которые будут затронуты
|
||||
|
||||
- Создать: `migrations/{postgres,sqlite}/000027_firewall_client.{up,down}.sql` (4 файла), `internal/repository/postgres_firewall_client.go`, `internal/httpapi/{routes_firewall.go,firewall_resolver.go,routes_firewall_test.go}`, `internal/store/firewall_types.go`, `apps/web/src/queries/firewall.ts`, `apps/web/src/routes/_auth/firewall.tsx`, `scripts/firewall/{install.sh,evobgp-firewall.sh,uninstall.sh}`, `docs/firewall.md`.
|
||||
- Изменить: `internal/store/backend.go` (новые методы интерфейса), `internal/store/memory.go` (in-memory реализации), `internal/httpapi/{server.go,routes.go,auth.go}` (wiring + resolver), `docs/openapi.yaml`, `docs/{access,api,architecture}.md`, `apps/web/src/components/app-sidebar.tsx`.
|
||||
|
||||
## Риски и компромиссы
|
||||
|
||||
- **Tenant для нового firewall-клиента при enroll**: в v1 берём demo/первый tenant системы. Если нужна мультитенантная привязка — нужна отдельная сущность «firewall invite» (operator создаёт, клиент redeem по invite-коду). Это можно вынести во вторую итерацию.
|
||||
- **Масштаб ipset**: hash:net при ~10⁵ CIDR работает; для ~10⁶ нужно переключаться на `bitmap`/`hash:net,net` или nftables с interval-sets. В скрипте оставить комментарий с порогом.
|
||||
- **Безопасность seed**: `X-EvoBGP-Seed` летит по HTTPS. Если CP без TLS — небезопасно. Зафиксировать в docs как requirement.
|
||||
Reference in New Issue
Block a user