From 0413b44051909fb711142a12efaf349dd49c3720 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CNaeel=E2=80=9D?= Date: Sun, 31 May 2026 09:42:46 +0300 Subject: [PATCH] fix: P0/P1 fixes (auth+limit+schema) + project audit docs --- docs/ТЗ-реализация.md | 242 ++++++++++++++++++++++++++++++++++++++++ docs/ТЗ.md | 97 ++++++++++++++++ public/nubes-logo.svg | 2 + src/api/index.js | 4 +- src/api/routes/admin.js | 10 +- src/auth.js | 22 +++- src/queries.js | 7 +- ui/routes/admin.js | 3 +- ui/routes/auth.js | 8 +- 9 files changed, 385 insertions(+), 10 deletions(-) create mode 100644 docs/ТЗ-реализация.md create mode 100644 docs/ТЗ.md create mode 100644 public/nubes-logo.svg diff --git a/docs/ТЗ-реализация.md b/docs/ТЗ-реализация.md new file mode 100644 index 0000000..b5c6491 --- /dev/null +++ b/docs/ТЗ-реализация.md @@ -0,0 +1,242 @@ +Техническое задание +Микросервис управления доверенными адресами клиентов +Self-service портал для указания клиентами доверенных IPv4-адресов и подсетей, +исключаемых из блокировки на стороне облачного провайдера во время DDoS-атак + +# 1. Назначение и цели + +## 1.1. Назначение + +Микросервис предоставляет клиентам облачного провайдера web-интерфейс для самостоятельного управления списком доверенных IPv4-адресов и подсетей. Записи из этого списка исключаются из автоматической блокировки сетевого взаимодействия системами фильтрации и митигации провайдера, что снижает количество ложноположительных срабатываний для легитимного трафика клиента. + +> **Реализация:** `server.js` — точка входа, подключает все роуты. `ui/index.js` — UI-слой. + +## 1.2. Цели + +- Дать клиентам возможность самостоятельно поддерживать актуальный список доверенных IPv4-адресов, которые будут исключаться из фильтрации во время DDoS-атак. + + > **Реализация:** `ui/routes/entries.js` — UI CRUD. `src/api/routes/entries.js` — REST API. + +- Предоставить сетевым инженерам единую точку просмотра и управления списками доверенных клиентских белых IPv4-адресов. + + > **Реализация:** `ui/routes/admin.js` — /admin и /audit UI. `src/api/routes/admin.js` — API /companies, /audit. + +- Обеспечить машиночитаемую выдачу агрегированного (суммаризированного) списка для систем фильтрации трафика. + + > **Реализация:** `src/routes/export.js` — GET /export. `src/validators.js` строки 125–181 — функция `aggregateCIDRs`. + +# 2. Объем работ + +- Web-страница / закладка в личном кабинете для управления whitelist-записями. + + > **Реализация:** `views/index.ejs` — главная страница. `ui/routes/entries.js` — роуты GET /, POST /add, POST /edit/:id, POST /delete/:id. + +- Авторизация через существующий экземпляр Keycloak (OIDC). + + > **Реализация:** `src/auth.js` строки 220–260 — верификация JWT RS256 токена Keycloak. Строка 245: чтение claim `ClientID`. Строка 251: определение роли `isAdmin`. + +- Валидация формы на стороне клиента и сервера. + + > **Реализация:** `src/validators.js` строки 26–79 — серверная валидация `validate()`. `views/index.ejs` — атрибут `pattern` в поле ввода (клиентская). + +- Внешний endpoint выдачи агрегированного списка. Выдача txt-файлом с переносом строки. Одна строка – один объект. + + > **Реализация:** `src/routes/export.js` строки 27–64 — GET /export, Content-Type: text/plain, aggregateCIDRs → join('\n'). + +- Хранение записей, журнал аудита. + + > **Реализация:** `sql/schema.sql` — таблицы `whitelist_entries`, `audit_log`. `src/queries.js` строки 211–230 — функция `logAudit`. + +- Административное управление лимитами по компаниям. + + > **Реализация:** `src/api/routes/admin.js` строки 27–35 — PATCH /companies/:id/limit. `src/queries.js` — функция `setLimit`. `views/admin.ejs` — UI формы лимитов. + +# 3. Роли и права доступа + +Роли определяются на основании claims в OIDC-токене Keycloak. Соответствие claim → роль настраивается на этапе развёртывания. + +> **Реализация:** `src/auth.js` строка 31: `ADMIN_CLIENT_ID = process.env.ADMIN_CLIENT_ID || 'WZ01112'`. Строка 251: `isAdmin: clientId === ADMIN_CLIENT_ID`. + +| Роль | Идентификация | Видимость записей | Права на изменение | +| --- | --- | --- | --- | +| Клиент (client) | clientId | Только записи компаний, к которым принадлежит пользователь. | Создание, редактирование и удаление записей своих компаний (в пределах лимита). | +| Администратор (admin) | clientId = WZ01112 (Нубес) и отдельный чек-бокс | Записи всех компаний. | Создание, редактирование, удаление всех записей. Изменение лимита для отдельных компаний. | + +> **Реализация ролей в API:** `src/api/routes/entries.js` — функция `resolveCompany()`: если `req.user.isAdmin && req.query.company` → берёт чужую компанию, иначе `getOrCreateCompany(clientId)`. `src/auth.js` строки 260–265 — middleware `requireAdmin`. + +## 3.1. Принадлежность к компании + +Принадлежность пользователя к компании определяется из claim токена. Поддерживается сценарий, когда пользователь принадлежит нескольким компаниям: в этом случае в интерфейсе предусматривается переключатель активной компании, а все операции выполняются в контексте выбранной компании. + +> **Реализация:** `src/auth.js` строка 245: `clientId = payload.ClientID`. `src/queries.js` строки 6–15: `getOrCreateCompany(clientId, companyName)` — атомарный upsert. +> +> ⚠️ **Сценарий нескольких компаний не реализован** — в реальном токене Nubes `ClientID` = одна строка, массива нет. Ждём уточнения от devops. + +Ожидаемые claims (имена согласуются с командой Keycloak): +- `ClientID` — идентификатор компании → `src/auth.js` строка 245 +- `email` — идентификация пользователя для аудита → `src/auth.js` строка 248 + +# 4. Функциональные требования + +## 4.1. Просмотр списка записей + +- Клиент видит таблицу записей активной компании; Администратор – записи всех компаний с фильтром по компании. + + > **Реализация:** `ui/routes/entries.js` — GET /: для admin грузит `getAllCompanies()` + фильтр по `?company=`. `views/index.ejs` — dropdown компаний для admin, таблица записей. + +- Для каждой записи отображаются: значение (адрес/подсеть), комментарий (если есть), автор(email), дата создания, дата последнего изменения. + + > **Реализация:** `views/index.ejs` строки 280–295 — колонки таблицы: `value_cidr`, `comment`, `created_by`, `created_at`, `updated_at`. Даты в timezone `Europe/Moscow`. + +- Soft-deleted записи по умолчанию скрыты; для администратора предусмотрен фильтр для их отображения. + + > **Реализация:** `src/queries.js` строки 25–31 — `listEntries(companyId, includeDeleted)`. SQL: `AND deleted_at IS NULL` когда `includeDeleted=false`. + > + > ⚠️ **Фильтр "показать удалённые" в UI не реализован** — `includeDeleted` всегда `false`. + +- Отображается текущее использование лимита: «использовано X из N». + + > **Реализация:** `src/api/routes/entries.js` строка 62: `res.json({ entries, limit, used: entries.length })`. `views/index.ejs` — блок статистики `<%= used %> / <%= limit %>`. + +## 4.2. Создание записи + +- Форма содержит поля: значение (IPv4-адрес или подсеть CIDR) и необязательный комментарий (до 255 символов). + + > **Реализация:** `views/index.ejs` — форма POST /add с полями `value` и `comment`. Атрибут `maxlength="18"` на поле адреса. + +- Значение проходит валидацию (см. раздел 5) на клиенте и обязательно повторно на сервере. + + > **Реализация:** `src/validators.js` строки 26–79 — `validate(input)`. Вызывается в `src/queries.js` строка 33: `const { cidr, wasNormalized } = validate(rawValue)`. + +- Перед сохранением проверяется: соблюдение лимита компании, отсутствие пересечений и дубликатов внутри компании, отсутствие принадлежности к запрещённым диапазонам. + + > **Реализация:** `src/queries.js` строки 32–70 — `createEntry()`: блокировка строки компании (FOR UPDATE), проверка лимита (строки 43–46), проверка дубликатов и пересечений (строки 49–57), проверка запрещённых диапазонов в `validate()` (строки 73–75 validators.js). + +- При успешном сохранении создаётся запись аудита. + + > **Реализация:** `src/queries.js` строка 66: `logAudit(userEmail, companyId, 'CREATE', null, cidr, ...)`. + +## 4.3. Редактирование записи + +- Редактирование значения и комментария доступно компании в рамках своих прав. + + > **Реализация:** `ui/routes/entries.js` — POST /edit/:id. `src/api/routes/entries.js` — PATCH /entries/:id. Modal в `views/index.ejs` — кнопка `.btn-edit`, event delegation в `