docs: перенос всей документации в ipwhitelist-app
This commit is contained in:
@@ -1,156 +0,0 @@
|
||||
Техническое задание
|
||||
Микросервис управления доверенными адресами клиентов
|
||||
Self-service портал для указания клиентами доверенных IPv4-адресов и подсетей,
|
||||
исключаемых из блокировки на стороне облачного провайдера во время DDoS-атак
|
||||
|
||||
───────────────────────────────────────────────────────────────
|
||||
КРАТКОЕ ОПИСАНИЕ (пояснение к реализации)
|
||||
───────────────────────────────────────────────────────────────
|
||||
Сервис даёт клиентам облачного провайдера личный кабинет, где они сами указывают
|
||||
свои доверенные IPv4-адреса и подсети. Эти адреса провайдер не блокирует во время
|
||||
DDoS-атак — так легитимный трафик клиента не попадает под ложные срабатывания
|
||||
фильтрации.
|
||||
|
||||
Что реализуется:
|
||||
|
||||
Личный кабинет клиента. Клиент входит через привычную авторизацию (Keycloak),
|
||||
видит свой список доверенных адресов и управляет им сам: добавляет, редактирует,
|
||||
удаляет записи с комментариями. Если пользователь работает с несколькими
|
||||
компаниями — переключается между ними.
|
||||
|
||||
Проверка вводимых данных. Форма принимает только корректные IPv4-адреса и подсети,
|
||||
отклоняет «серые» и служебные диапазоны, не допускает дубликатов и пересечений
|
||||
внутри одной компании.
|
||||
|
||||
Ограничение по количеству. На компанию по умолчанию 15 записей. Лимит
|
||||
настраивается глобально, а для отдельной компании администратор может поднять или
|
||||
опустить его индивидуально.
|
||||
|
||||
Режим администратора (сетевые инженеры провайдера). Единое окно, где видны записи
|
||||
всех компаний, с фильтрами и доступом к истории изменений. Администратор управляет
|
||||
лимитами и при необходимости любыми записями.
|
||||
|
||||
История изменений (аудит). Каждое создание, изменение и удаление фиксируется: кто,
|
||||
когда, что именно изменил. Удаление — логическое, данные физически сохраняются.
|
||||
|
||||
Выдача для систем фильтрации. Отдельный адрес, по которому системы защиты
|
||||
автоматически забирают итоговый сводный список всех доверенных адресов (одним
|
||||
txt-файлом, по строке на запись). Адреса при этом схлопываются в компактный общий
|
||||
перечень.
|
||||
───────────────────────────────────────────────────────────────
|
||||
1. Назначение и цели
|
||||
1.1. Назначение
|
||||
Микросервис предоставляет клиентам облачного провайдера web-интерфейс для самостоятельного управления списком доверенных IPv4-адресов и подсетей. Записи из этого списка исключаются из автоматической блокировки сетевого взаимодействия системами фильтрации и митигации провайдера, что снижает количество ложноположительных срабатываний для легитимного трафика клиента.
|
||||
1.2. Цели
|
||||
Дать клиентам возможность самостоятельно поддерживать актуальный список доверенных IPv4-адресов, которые будут исключаться из фильтрации во время DDoS-атак.
|
||||
Предоставить сетевым инженерам единую точку просмотра и управления списками доверенных клиентских белых IPv4-адресов.
|
||||
Обеспечить машиночитаемую выдачу агрегированного (суммаризированного) списка для систем фильтрации трафика.
|
||||
2. Объем работ
|
||||
Web-страница / закладка в личном кабинете для управления whitelist-записями.
|
||||
Авторизация через существующий экземпляр Keycloak (OIDC).
|
||||
Валидация формы на стороне клиента и сервера.
|
||||
Внешний endpoint выдачи агрегированного списка. Выдача txt-файлом с переносом строки. Одна строка – один объект.
|
||||
Хранение записей, журнал аудита.
|
||||
Административное управление лимитами по компаниям.
|
||||
3. Роли и права доступа
|
||||
Роли определяются на основании claims в OIDC-токене Keycloak. Соответствие claim → роль настраивается на этапе развёртывания.
|
||||
Роль
|
||||
Идентификация
|
||||
Видимость записей
|
||||
Права на изменение
|
||||
Клиент (client)
|
||||
clientId
|
||||
Только записи компаний, к которым принадлежит пользователь.
|
||||
Создание, редактирование и удаление записей своих компаний (в пределах лимита).
|
||||
Администратор (admin)
|
||||
clientId = WZ01112 (Нубес) и отдельный чек-бокс
|
||||
Записи всех компаний.
|
||||
Создание, редактирование, удаление всех записей. Изменение лимита для отдельных компаний.
|
||||
3.1. Принадлежность к компании
|
||||
Принадлежность пользователя к компании определяется из claim токена. Поддерживается сценарий, когда пользователь принадлежит нескольким компаниям: в этом случае в интерфейсе предусматривается переключатель активной компании, а все операции выполняются в контексте выбранной компании.
|
||||
Ожидаемые claims (имена согласуются с командой Keycloak):
|
||||
clientID — идентификатор компании
|
||||
email — идентификация пользователя для аудита
|
||||
4. Функциональные требования
|
||||
4.1. Просмотр списка записей
|
||||
Клиент видит таблицу записей активной компании; Администратор – записи всех компаний с фильтром по компании.
|
||||
Для каждой записи отображаются: значение (адрес/подсеть), комментарий (если есть), автор(email), дата создания, дата последнего изменения.
|
||||
Soft-deleted записи по умолчанию скрыты; для администратора предусмотрен фильтр для их отображения.
|
||||
Отображается текущее использование лимита: «использовано X из N».
|
||||
4.2. Создание записи
|
||||
Форма содержит поля: значение (IPv4-адрес или подсеть CIDR) и необязательный комментарий (до 255 символов).
|
||||
Значение проходит валидацию (см. раздел 5) на клиенте и обязательно повторно на сервере.
|
||||
Перед сохранением проверяется: соблюдение лимита компании, отсутствие пересечений и дубликатов внутри компании, отсутствие принадлежности к запрещённым диапазонам.
|
||||
При успешном сохранении создаётся запись аудита.
|
||||
4.3. Редактирование записи
|
||||
Редактирование значения и комментария доступно компании в рамках своих прав.
|
||||
При изменении значения повторно выполняется полный набор проверок валидации и пересечений.
|
||||
Изменение фиксируется в журнале аудита с сохранением прежнего и нового значения.
|
||||
4.4. Удаление записи (soft delete)
|
||||
Удаление выполняется как логическое (soft delete): запись помечается удалённой (deleted_at, deleted_by), но физически сохраняется.
|
||||
Удалённая запись освобождает место в лимите компании и исключается из внешней агрегированной выдачи.
|
||||
Действие фиксируется в журнале аудита.
|
||||
4.5. Лимит записей на компанию
|
||||
Действует глобальный лимит по умолчанию: 15 активных записей на компанию.
|
||||
Значение глобального лимита по умолчанию задаётся конфигурацией сервиса и может быть изменено без пересборки.
|
||||
Для отдельной компании администратор может задать индивидуальный лимит, переопределяющий глобальный (как в большую, так и в меньшую сторону).
|
||||
При попытке превысить лимит создание блокируется с понятным сообщением; в подсчёт идут только активные записи.
|
||||
Снижение лимита ниже текущего числа записей не удаляет существующие записи, но блокирует создание новых до приведения в соответствие.
|
||||
4.6. Журнал аудита
|
||||
Все изменяющие операции фиксируются неизменяемыми записями аудита.
|
||||
Каждая запись аудита содержит: кто (пользователь), когда (timestamp), компания, тип действия, прежнее и новое состояние.
|
||||
Журнал доступен для просмотра только администратору.
|
||||
4.7. Внешняя выдача агрегированного списка
|
||||
Подсети суммаризируются (агрегируются в минимальный набор CIDR) по всем компаниям совместно. Пересечения между разными компаниями допустимы.
|
||||
Предоставляется отдельный HTTP GET endpoint, отдающий полный суммаризированный список активных записей всех компаний файлом в формате txt.
|
||||
Авторизация: на старте endpoint может работать без авторизации (по сетевому ограничению / разрешенный список потребителей по ip).
|
||||
5. Требования к валидации
|
||||
Валидация выполняется на клиенте и обязательно дублируется на сервере. Серверная валидация является авторитетной.
|
||||
Правило
|
||||
Описание
|
||||
Формат IPv4
|
||||
Допускается одиночный адрес (например 203.0.113.10) или подсеть в нотации CIDR (например 203.0.113.0/24). Допускается использование масок /32 - /22. Маска /21 и больше не допускается.
|
||||
Только IPv4
|
||||
IPv6-значения или доменные имена отклоняются.
|
||||
Корректность подсети
|
||||
Введенный адрес с маской подсети должен нормализоваться к адресу подсети, все host-биты должны быть обнулены.
|
||||
Пользователь должен быть уведомлен, что ввел адрес из хостовой части, а не адрес подсети и произошла нормализация.
|
||||
Запрет серых адресов
|
||||
Адреса и подсети из частных диапазонов (Приложение А) запрещены к добавлению.
|
||||
Отсутствие дубликатов
|
||||
В пределах одной компании запрещены полностью совпадающие записи.
|
||||
Отсутствие пересечений
|
||||
В пределах одной компании запрещено добавление записи, пересекающейся с уже существующей (включая вложенность подсетей). Между разными компаниями пересечения допускаются.
|
||||
Длина комментария
|
||||
Не более 255 символов; поле необязательное.
|
||||
Приложение А – Список запрещенных к созданию подсетей.
|
||||
Назначение
|
||||
Префикс
|
||||
Private (RFC1918)
|
||||
10.0.0.0/8
|
||||
Private (RFC1918)
|
||||
172.16.0.0/12
|
||||
Private (RFC1918)
|
||||
192.168.0.0/16
|
||||
CGNAT (RFC6598)
|
||||
100.64.0.0/10
|
||||
Loopback
|
||||
127.0.0.0/8
|
||||
Link-local (APIPA)
|
||||
169.254.0.0/16
|
||||
IANA special block
|
||||
192.0.0.0/24
|
||||
TEST-NET-1 (docs)
|
||||
192.0.2.0/24
|
||||
TEST-NET-2 (docs)
|
||||
198.51.100.0/24
|
||||
TEST-NET-3 (docs)
|
||||
203.0.113.0/24
|
||||
Benchmarking
|
||||
198.18.0.0/15
|
||||
Multicast
|
||||
224.0.0.0/4
|
||||
Reserved (Class E)
|
||||
240.0.0.0/4
|
||||
Limited broadcast
|
||||
255.255.255.255/32
|
||||
@@ -1,112 +0,0 @@
|
||||
# Анализ проекта IP WhiteList — 30.05.2026
|
||||
|
||||
## Что сделано
|
||||
|
||||
| Компонент | Файл | Строк | Статус |
|
||||
|---|---|---|---|
|
||||
| Express-сервер, роуты | `server.js` | 101 | ✅ |
|
||||
| Валидатор IPv4/CIDR | `src/validators.js` | 99 | ✅ |
|
||||
| CRUD + audit_log | `src/queries.js` | 149 | ✅ |
|
||||
| Пул PG | `src/db.js` | 24 | ✅ |
|
||||
| UI (EJS, стиль Nubes) | `views/index.ejs` | 248 | ✅ |
|
||||
| Схема БД | `sql/schema.sql` | 39 | ✅ |
|
||||
| Favicon | `public/favicon.png` | — | ✅ |
|
||||
| Дизайн-система (документ) | `docs/nubes-design-system.md` | — | ✅ |
|
||||
| Тестирование | `tests/test-results.md` | 16/16 ✅ | ✅ |
|
||||
|
||||
### Валидатор — что проверяет:
|
||||
- RFC1918 (10/8, 172.16/12, 192.168/16) — запрет
|
||||
- TEST-NET (192.0.2/24, 198.51.100/24, 203.0.113/24) — запрет
|
||||
- Loopback, link-local — запрет
|
||||
- IPv6 — запрет
|
||||
- Маска: допускается только /22–/32
|
||||
- Нормализация host-битов (10.0.0.1/24 → 10.0.0.0/24)
|
||||
- Пересечения с уже существующими записями — запрет
|
||||
- Лимит записей на компанию (по умолчанию 15)
|
||||
|
||||
### Экспорт:
|
||||
- `GET /export` → plain text, один CIDR на строку, без заголовков
|
||||
|
||||
---
|
||||
|
||||
## Что не готово (критично для продакшена)
|
||||
|
||||
### 1. JWT-авторизация
|
||||
**Сейчас:** `DEV_MODE=true` — авторизация заглушена, `clientId` захардкожен.
|
||||
**Нужно:** Распаковка JWT из `Authorization: Bearer <token>` → извлечение `clientId` и `email`.
|
||||
Схема описана в `docs/auth-architecture.md`.
|
||||
**Риск:** без этого нельзя открывать сервис для реальных пользователей — любой запрос видит и меняет все данные.
|
||||
|
||||
### 2. Изоляция данных по компании
|
||||
**Сейчас:** Все роуты используют один захардкоженный `clientId`. БД правильная (таблица `companies` есть), но `WHERE company_id = $1` не работает по-настоящему без JWT.
|
||||
**Нужно:** После JWT — всё заработает автоматически, код менять не придётся.
|
||||
|
||||
### 3. CSRF-защита на POST-формах
|
||||
**Сейчас:** Формы без CSRF-токена.
|
||||
**Нужно:** `csurf` middleware или `SameSite=Strict` на session cookie.
|
||||
Или, если фронт перейдёт на fetch/JSON API — Authorization header автоматически решает проблему.
|
||||
|
||||
---
|
||||
|
||||
## Замечания и предложения
|
||||
|
||||
### Архитектура
|
||||
|
||||
- `server.js` содержит все роуты в одном файле (101 строка). Пока норм, но при добавлении админки/API станет неудобно. Рекомендую разбить на `routes/user.js`, `routes/admin.js`, `routes/api.js` перед тем как добавлять функциональность.
|
||||
|
||||
- `queries.js` возвращает сырые строки, не объекты с явным типом. Если сервис будет расти — стоит обернуть в Result-паттерн `{ ok, data, error }`.
|
||||
|
||||
### UI
|
||||
|
||||
- Нет пагинации. При лимите 15 записей на компанию — не критично. Если лимит поднимут до 100+ — нужна.
|
||||
- Нет поиска/фильтрации в таблице.
|
||||
- Алерты исчезают только при перезагрузке страницы (flash-сообщения). Если добавить JS — можно auto-dismiss через 5 секунд.
|
||||
|
||||
### Экспорт
|
||||
|
||||
- Сейчас `GET /export` отдаёт все CIDR без суммаризации. Если у компании 10 записей типа `1.2.3.0/28` и `1.2.3.16/28` — они будут двумя строками. Суммаризация в `/28` + `/28` → `/27` сократила бы файл и упростила настройку оборудования. Это отдельная задача, потребует библиотеку CIDR-merge.
|
||||
|
||||
### Тесты
|
||||
|
||||
- Тесты ручные (curl в md-файле). Для CI/CD нужен автоматический прогон: `jest` или `supertest`. Команда для инициализации: `npm install --save-dev jest supertest`.
|
||||
|
||||
### БД
|
||||
|
||||
- `audit_log` пишется, но нигде не отображается пользователю. Для полноты — стоит добавить вкладку "История" или endpoint `GET /audit`.
|
||||
- Нет индекса на `whitelist_entries(company_id)` — при росте данных будет полный скан. Добавить в `schema.sql`:
|
||||
```sql
|
||||
CREATE INDEX idx_whitelist_company ON whitelist_entries(company_id);
|
||||
CREATE INDEX idx_audit_company ON audit_log(company_id);
|
||||
```
|
||||
|
||||
### Безопасность
|
||||
|
||||
- `.env` в `.gitignore` ✅ — правильно.
|
||||
- `secrets.txt` в корне репо — нужно убедиться что он тоже в `.gitignore`.
|
||||
- Параметризованные запросы в `queries.js` ✅ — SQL-инъекции закрыты.
|
||||
- `express-validator` не подключён — валидация только на уровне `validators.js`. Нормально, т.к. все входные данные проходят через него.
|
||||
|
||||
---
|
||||
|
||||
## Приоритеты следующих задач
|
||||
|
||||
1. **JWT** — без этого продакшен не открыть
|
||||
2. **CSRF** — параллельно с JWT
|
||||
3. **Индексы БД** — 2 строки в schema.sql, риск нулевой
|
||||
4. **Разбивка роутов** — перед добавлением админки
|
||||
5. **Автотесты** — перед CI/CD
|
||||
6. **Суммаризация CIDR** — nice to have
|
||||
7. **Пагинация + фильтр** — после поднятия лимита
|
||||
8. **Audit UI** — опционально
|
||||
|
||||
---
|
||||
|
||||
## Текущий стек
|
||||
|
||||
- Node.js + Express.js
|
||||
- PostgreSQL (pg pool)
|
||||
- EJS templates
|
||||
- Платформа: Nubes NodeJS instance "white"
|
||||
- URL: `https://white.nodejsk8s.dev.nubes.ru`
|
||||
- Repo (код): `gitea.services.ngcloud.ru/Nail/ipwhitelist-app.git`
|
||||
- Repo (docs): `gitea.services.ngcloud.ru/Nail/IPWhiteList.git`
|
||||
@@ -1,95 +0,0 @@
|
||||
# Архитектура авторизации облачного портала
|
||||
|
||||
> ✅ ПРОВЕРЕНО: API успешно вызван через curl 2026-05-29.
|
||||
|
||||
---
|
||||
|
||||
## Схема аутентификации
|
||||
|
||||
```
|
||||
Пользователь (браузер)
|
||||
│
|
||||
▼
|
||||
Keycloak (keycloak.nubes.ru, realm=cloud)
|
||||
│ Authorization Code Flow
|
||||
│ client_id=deck.ngcloud.ru
|
||||
│ scope=openid email
|
||||
▼
|
||||
auth-api.ngcloud.ru (СОБСТВЕННЫЙ сервис)
|
||||
│ Создаёт свой JWT (issuer="auth-api")
|
||||
│ Подпись: RS256 (RSA)
|
||||
▼
|
||||
deck.ngcloud.ru (портал)
|
||||
│ Микро-фронтенды (SPA): dashboard, contracts, services, ...
|
||||
│ JWT хранится в localStorage: authApiTokens.access_token
|
||||
▼
|
||||
IPWhiteList (наш сервис) ← будет встроен как микро-фронтенд в портал
|
||||
```
|
||||
|
||||
## Важное
|
||||
|
||||
- JWT подписывает **не Keycloak**, а **auth-api**
|
||||
- Issuer: `"auth-api"`, алгоритм: `RS256`
|
||||
- Для валидации нужен публичный ключ auth-api (JWKS или статический)
|
||||
|
||||
---
|
||||
|
||||
## Структура JWT (access_token)
|
||||
|
||||
_Из localStorage → authApiTokens → access_token_
|
||||
|
||||
| Поле | Тип | Значение (пример) | Назначение |
|
||||
|---|---|---|---|
|
||||
| `iss` | string | `"auth-api"` | Кто выпустил токен |
|
||||
| `sub` | string | `"0199e325-..."` | UUID пользователя |
|
||||
| `iat` | number | `1780073927` | Выпущен (Unix time) |
|
||||
| `exp` | number | `1780117127` | Истекает (~12 часов) |
|
||||
| `jti` | string | `"4d8d7240-..."` | Уникальный ID токена |
|
||||
| `ClientID` | string | `"WZ01325"` | ID **ТЕКУЩЕЙ** компании пользователя |
|
||||
| `company_id` | string (UUID) | `"3e64aac6-..."` | UUID компании |
|
||||
| `company_name` | string | `"Тест"` | Название компании |
|
||||
| `email` | string | `"tazet@narod.ru"` | Email (для аудита) |
|
||||
| `login` | string | `"tazet@narod.ru"` | Логин |
|
||||
| `firstname` | string | `"Наиль"` | Имя |
|
||||
| `lastname` | string | `"Тазетдинов"` | Фамилия |
|
||||
| `token_type` | string | `"access"` | Тип токена |
|
||||
|
||||
## Что НЕ в JWT (отдельный authData в localStorage)
|
||||
|
||||
_Из localStorage → authData → v_
|
||||
|
||||
| Поле | Значение | Где используется |
|
||||
|---|---|---|
|
||||
| `userInfo.isAdmin` | `false` (boolean) | ⚠️ Признак администратора |
|
||||
| `profiles[]` | Массив `{company_id, company_name, is_active_profile}` | Список всех компаний пользователя |
|
||||
| `roles[]` | Массив `{role_id, role_name}` | Роли пользователя |
|
||||
| `permissions.can_write` | `false` | Есть ли права на запись |
|
||||
|
||||
---
|
||||
|
||||
## Открытые вопросы (нужно уточнить с командой портала)
|
||||
|
||||
1. **Валидация JWT.** ✅ Выяснено: API gateway (`lk-api-gateway.ngcloud.ru`) сам валидирует JWT через auth-api. Нам достаточно передавать `Authorization: Bearer <JWT>`.
|
||||
|
||||
2. **isAdmin.** Флаг админа есть только в authData localStorage, но не в JWT. Как наш сервис узнает что пользователь — админ?
|
||||
- Нужно уточнить: можно ли добавить `isAdmin` в JWT или получать через API gateway
|
||||
|
||||
3. **Список компаний.** В JWT — только одна компания. В authData.profiles — массив. Для переключателя компаний — откуда брать список?
|
||||
|
||||
4. **Монтирование в API gateway.** Наш сервис будет за `lk-api-gateway.ngcloud.ru` как отдельный route (например `/api/v1/whitelist/...`). Нужно уточнить процедуру добавления нового route.
|
||||
|
||||
---
|
||||
|
||||
## Как вызывать API (проверено curl'ом)
|
||||
|
||||
```bash
|
||||
curl -H "Authorization: Bearer <JWT>" \
|
||||
-H "Origin: https://deck.ngcloud.ru" \
|
||||
-H "Cookie: __ddg1_=...; __ddg8_=...; __ddg9_=...; __ddg10_=..." \
|
||||
"https://lk-api-gateway.ngcloud.ru/api/v1/..."
|
||||
```
|
||||
|
||||
- **API Gateway:** `lk-api-gateway.ngcloud.ru` (не deck-api.ngcloud.ru!)
|
||||
- **DDOS-Guard cookies ОБЯЗАТЕЛЬНЫ** (без них 403)
|
||||
- **JWT issuer:** `auth-api`
|
||||
- **Алгоритм:** RS256
|
||||
@@ -1,131 +0,0 @@
|
||||
# План разработки IP WhiteList — актуальный (30.05.2026)
|
||||
|
||||
> Основан на ТЗ (`WhiteIPlist.txt`) и реальном состоянии кода.
|
||||
> Все предыдущие планы (plan.md, plan-v2.md, plan-working.md, plan-gemini.md, plan-gpt54.md) устарели.
|
||||
|
||||
---
|
||||
|
||||
## Что уже работает (не трогать)
|
||||
|
||||
| Компонент | Файл | Состояние |
|
||||
|---|---|---|
|
||||
| Валидатор IPv4/CIDR | `src/validators.js` | ✅ Полный — все 14 диапазонов Приложения А |
|
||||
| CRUD функции | `src/queries.js` | ✅ `createEntry`, `updateEntry`, `deleteEntry`, `listEntries`, `getExportCIDRs`, `getAudit` |
|
||||
| Soft delete | `src/queries.js` + `sql/schema.sql` | ✅ `deleted_at`, `deleted_by` |
|
||||
| Схема БД | `sql/schema.sql` | ✅ Все таблицы и индексы |
|
||||
| Аудит-лог запись | `src/queries.js` | ✅ Пишется при CREATE/UPDATE/DELETE |
|
||||
| UI главная страница | `views/index.ejs` | ✅ Стиль Nubes |
|
||||
| Экспорт txt | `server.js GET /export` | ✅ Работает (без суммаризации) |
|
||||
| Лимит + custom_limit | `src/queries.js` | ✅ |
|
||||
|
||||
---
|
||||
|
||||
## Что есть в queries.js, но не подключено к роутам
|
||||
|
||||
- `updateEntry` — написана, роута `POST /update/:id` нет, UI-кнопки нет
|
||||
- `getAudit` — написана, роута и страницы нет
|
||||
- `listEntries(companyId, includeDeleted=true)` — параметр есть, но нигде не вызывается с `true`
|
||||
|
||||
---
|
||||
|
||||
## Что отсутствует полностью
|
||||
|
||||
### A. Роуты (server.js)
|
||||
- `POST /update/:id` — редактирование записи
|
||||
- `GET /admin` — страница администратора
|
||||
- `POST /admin/limit/:companyId` — изменение лимита компании
|
||||
- `GET /admin/audit` — журнал аудита (только для admin)
|
||||
|
||||
### B. UI (views/)
|
||||
- Кнопка и inline-форма редактирования в таблице
|
||||
- Страница admin (`views/admin.ejs`):
|
||||
- Список всех компаний
|
||||
- Записи с фильтром по компании
|
||||
- Переключатель "показать удалённые"
|
||||
- Журнал аудита
|
||||
- Изменение лимита компании
|
||||
|
||||
### C. Авторизация
|
||||
- Текущий код: наивный base64 decode JWT — не проверяет подпись, не Keycloak
|
||||
- Нужно: `openid-client` (OIDC), интеграция с Keycloak, проверка `clientID` и `email` из claims
|
||||
- Admin-роль: `clientId === 'WZ01112'` — определяется из claim
|
||||
|
||||
### D. Суммаризация CIDR в экспорте
|
||||
- ТЗ требует агрегацию в минимальный набор CIDR
|
||||
- Нужна библиотека: `npm install cidr-tools` или `ip-range-check`
|
||||
- Или самописный merge (sorted ranges → contiguous merge)
|
||||
|
||||
### E. Клиентская валидация
|
||||
- ТЗ: валидация на клиенте обязательна, серверная — авторитетная
|
||||
- Сейчас: только серверная
|
||||
- Нужно: JS в index.ejs — проверить формат CIDR, маску /22–/32, не RFC1918
|
||||
|
||||
### F. Переключатель компаний
|
||||
- ТЗ: пользователь может быть в нескольких компаниях (несколько `clientId` в токене)
|
||||
- Нужно: UI-dropdown, переключение активного `clientId`, хранение выбора в сессии
|
||||
|
||||
---
|
||||
|
||||
## Обнаруженные баги
|
||||
|
||||
### overlaps() в validators.js — логическая ошибка
|
||||
|
||||
```js
|
||||
// Текущий код (неверно):
|
||||
return a.start <= b.end && b.start <= a.start ||
|
||||
b.start <= a.end && a.start <= b.start;
|
||||
|
||||
// Правильно:
|
||||
return a.start <= b.end && b.start <= a.end;
|
||||
```
|
||||
|
||||
Стандартное условие пересечения отрезков. Текущий код даёт false positive для некоторых смежных диапазонов.
|
||||
**Приоритет: высокий** — влияет на корректность проверки пересечений.
|
||||
|
||||
---
|
||||
|
||||
## Порядок реализации
|
||||
|
||||
### Этап 1 — Критические фиксы (не ломают работу)
|
||||
1. Исправить `overlaps()` в `validators.js`
|
||||
2. Добавить `POST /update/:id` в `server.js`
|
||||
3. Добавить кнопку редактирования + inline-форму в `views/index.ejs`
|
||||
|
||||
### Этап 2 — Полнота ТЗ
|
||||
4. Клиентская валидация (JS в index.ejs)
|
||||
5. Суммаризация CIDR в `GET /export` (cidr-tools или ручной merge)
|
||||
6. Определение admin-роли (`clientId === 'WZ01112'`)
|
||||
7. Страница `/admin` + роут `GET /admin`
|
||||
8. Роут `POST /admin/limit/:companyId`
|
||||
9. Журнал аудита для admin (`GET /admin/audit`)
|
||||
|
||||
### Этап 3 — Авторизация (блокирующая для продакшена)
|
||||
10. `npm install openid-client` — OIDC Keycloak
|
||||
11. Заменить auth middleware на реальную проверку токена
|
||||
12. Убрать `DEV_MODE` (или оставить только для локальной разработки)
|
||||
|
||||
### Этап 4 — Опционально
|
||||
13. Переключатель компаний (multi-company claim)
|
||||
14. Автотесты: `npm install --save-dev jest supertest`
|
||||
15. Пагинация (только если лимит вырастет > 50)
|
||||
16. Audit UI для клиента (не только admin)
|
||||
|
||||
---
|
||||
|
||||
## Зависимости
|
||||
|
||||
```
|
||||
openid-client — OIDC / Keycloak
|
||||
cidr-tools — суммаризация CIDR в экспорте
|
||||
```
|
||||
|
||||
Всё остальное — в рамках существующего стека (Express, EJS, pg).
|
||||
|
||||
---
|
||||
|
||||
## Что НЕ нужно делать
|
||||
|
||||
- Переписывать validators.js (только исправить overlaps)
|
||||
- Переписывать queries.js — всё уже написано
|
||||
- Менять схему БД — она полная
|
||||
- Пересобирать стек — Express + EJS достаточно для ТЗ
|
||||
@@ -1,55 +0,0 @@
|
||||
# Актуальный план разработки — IP WhiteList Microservice
|
||||
|
||||
> **Автор:** GitHub Copilot (Gemini 3.1 Pro Preview)
|
||||
> **Дата:** 2026-05-30
|
||||
> **Основание:** ТЗ (WhiteIPlist.txt) + Реальный код (Node.js/Express)
|
||||
|
||||
## 1. Анализ предыдущего плана и моё мнение
|
||||
|
||||
Мой предыдущий план (`plan-gemini.md`) оказался **полностью оторванным от реальности**. Я предполагал писать всё с нуля на Python/FastAPI+SQLModel. На деле же ядро полностью готово и написано на **Node.js, Express, EJS и чистом SQL (pg pool)**.
|
||||
Писать с нуля на питоне — это плодить техдолг и выкидывать рабочий код. Более того, серверная валидация, структура БД, CRUD и UI уже в целом соответствуют ТЗ, но сильно не хватает связующих звеньев.
|
||||
|
||||
Поэтому мой главный тезис: **хватит придумывать архитектуру, нужно закрывать дыры по функциональным требованиям ТЗ в текущем Node.js проекте.**
|
||||
|
||||
## 2. Разрыв между ТЗ (WhiteIPlist.txt) и кодом (Node.js)
|
||||
|
||||
Что готово и работает:
|
||||
- **База данных:** Полная структура (`companies`, `whitelist_entries`, `audit_log`), реализован soft delete (`deleted_at`).
|
||||
- **Слой данных (`queries.js`):** Работает базовый CRUD, сохраняются логи аудита, обрабатываются ограничения (глобальные и кастомные).
|
||||
- **Валидация (`validators.js`):** Реализованы проверки на IPv4, маски /22-/32, зашиты все запрещённые диапазоны (RFC1918, CGNAT, Loopback и т.д.).
|
||||
- **UI:** Причесанный EJS-шаблон добавления/просмотра/удаления.
|
||||
|
||||
Чего не хватает по ТЗ (Фокус дальнейшей разработки):
|
||||
1. **Авторизация (Keycloak OIDC):** Сейчас сделан временный парсинг JWT через Base64 без валидации ключей (DEV_MODE).
|
||||
2. **Multi-company и Роли (Админ/Клиент):** Не реализованы переключатель компаний и админские страницы (видимость всех записей, изменение лимитов, просмотр аудита).
|
||||
3. **Редактирование записей:** Метод `updateEntry` написан в БД-слое, но UI и роут отсутствуют. Формально ТЗ не закрыто.
|
||||
4. **Агрегация в Экспорте:** Маршрут `GET /export` просто выплёвывает адреса в столбик, тогда как ТЗ жёстко требует суммаризировать подсети в минимальный набор CIDR.
|
||||
5. **Клиентская валидация:** ТЗ явно требует валидировать формат и маски на фронтенде перед отправкой.
|
||||
|
||||
## 3. Детальный план по шагам (Node.js)
|
||||
|
||||
### Этап 1: Исправление багов в текущем MVP
|
||||
- [ ] **Баг с overlaps:** В `validators.js` функция `overlaps()` содержит логическую ошибку в `start`/`end` (может пропускать пересекающиеся подсети). Переписать условие.
|
||||
- [ ] **UI Редактирования:** Добавить роут `POST /update/:id` в `server.js` и добавить кнопку/форму "Изменить" в `index.ejs`, подключив существующую `updateEntry(...)`.
|
||||
|
||||
### Этап 2: Строгое соответствие ТЗ (Валидация и Экспорт)
|
||||
- [ ] **Суммаризация CIDR:** Так как в Node.js нет `ipaddress.collapse_addresses`, необходимо использовать библиотеку вроде `cidr-tools` (функция `merge()` отлично справится) для `GET /export`.
|
||||
- [ ] **Клиентская валидация:** Добавить минимальный JavaScript в `index.ejs` для проверки валидности вводимого IP-адреса и маски до ухода POST-запроса, чтобы экономить серверные ресурсы (Требование ТЗ).
|
||||
|
||||
### Этап 3: Подключение OIDC Keycloak
|
||||
- [ ] Установка библиотеки `openid-client` (или `passport-openidconnect`).
|
||||
- [ ] Настройка middleware: получение сертификатов из Keycloak, валидация подписи JWT-токена.
|
||||
- [ ] Извлечение claim `clientID` (и логика переключения между компаниями, если `clientID` является массивом). Определение роли (кто админ, `WZ01112`). Обязательный отказ от dev-заглушки в PROD.
|
||||
|
||||
### Этап 4: Админ-панель (пользователь WZ01112)
|
||||
- [ ] Роут `GET /admin` и шаблон `admin.ejs`. Таблица со всеми компаниями и их лимитами, фильтрацией.
|
||||
- [ ] Функционал установки `custom_limit` для компании.
|
||||
- [ ] Страница/вкладка `GET /admin/audit` для просмотра `audit_log`.
|
||||
- [ ] Переключатель отображения Soft-deleted записей.
|
||||
|
||||
## 4. Зависимости
|
||||
В `package.json` придется добавить лишь две новые production-зависимости, не усложняя проект:
|
||||
- `cidr-tools` (для агрегации при экспорте)
|
||||
- `openid-client` / `jsonwebtoken` (для безопасной работы с Keycloak)
|
||||
|
||||
Всё остальное будет реализовано в рамках существующего стека.
|
||||
@@ -1,552 +0,0 @@
|
||||
# Актуальный план реализации — IP WhiteList
|
||||
|
||||
> Автор: GitHub Copilot (GPT-5.4)
|
||||
> Дата: 2026-05-30
|
||||
> Основание: ТЗ из WhiteIPlist.txt + фактический код в ipwhitelist-app
|
||||
> Статус: заменяет предыдущую версию плана
|
||||
|
||||
## 1. Вывод по текущему состоянию
|
||||
|
||||
Предыдущий план был неполным, потому что строился не от ТЗ, а от видимого MVP. После сверки с WhiteIPlist.txt картина такая:
|
||||
|
||||
1. Основа сервиса уже написана лучше, чем казалось: схема БД, soft delete, аудит, custom_limit, updateEntry и полный список запрещённых диапазонов уже есть в коде.
|
||||
2. Основной разрыв находится не в модели данных, а между слоями: часть функций реализована в src/queries.js, но не выведена в server.js и views.
|
||||
3. Главные недостающие вещи для соответствия ТЗ: нормальная OIDC-авторизация, admin-сценарии, редактирование из UI, клиентская валидация, агрегирующий экспорт.
|
||||
4. Переписывать стек или БД не нужно. Нужно довести существующую реализацию до полноты требований.
|
||||
|
||||
## 2. Что требует ТЗ
|
||||
|
||||
Сервис по ТЗ обязан поддерживать:
|
||||
|
||||
1. Self-service страницу управления whitelist-записями.
|
||||
2. Авторизацию через Keycloak OIDC.
|
||||
3. Роли client и admin.
|
||||
4. Возможность работы пользователя с одной или несколькими компаниями.
|
||||
5. Создание, редактирование и soft delete записей.
|
||||
6. Серверную и клиентскую валидацию IPv4/CIDR.
|
||||
7. Глобальный лимит и индивидуальные лимиты по компаниям.
|
||||
8. Журнал аудита.
|
||||
9. Внешний endpoint выдачи агрегированного списка активных CIDR.
|
||||
10. Admin-функции: просмотр всех компаний, фильтрация, просмотр удалённых записей, изменение лимитов.
|
||||
|
||||
## 3. Что уже реализовано сейчас
|
||||
|
||||
### 3.1 База данных
|
||||
|
||||
В sql/schema.sql уже есть всё базовое, что нужно для ТЗ:
|
||||
|
||||
1. Таблица companies с client_id и custom_limit.
|
||||
2. Таблица whitelist_entries с updated_by, updated_at, deleted_by, deleted_at.
|
||||
3. Таблица audit_log.
|
||||
4. Индекс активных записей и индекс по audit_log(company_id).
|
||||
|
||||
Вывод: схему БД переписывать не нужно.
|
||||
|
||||
### 3.2 Серверная логика
|
||||
|
||||
В src/queries.js уже реализованы:
|
||||
|
||||
1. getOrCreateCompany
|
||||
2. getLimit
|
||||
3. listEntries с includeDeleted
|
||||
4. createEntry
|
||||
5. updateEntry
|
||||
6. deleteEntry как soft delete
|
||||
7. getExportCIDRs
|
||||
8. getAudit
|
||||
|
||||
Вывод: слой работы с БД уже покрывает значительную часть ТЗ.
|
||||
|
||||
### 3.3 Валидация
|
||||
|
||||
В src/validators.js уже есть:
|
||||
|
||||
1. Только IPv4.
|
||||
2. Маски только /22–/32.
|
||||
3. Нормализация host-битов.
|
||||
4. Запрет всех диапазонов из Приложения А ТЗ, включая CGNAT, Benchmarking, Multicast, Reserved и Limited broadcast.
|
||||
5. Проверка пересечений.
|
||||
|
||||
Вывод: серверная валидация по составу требований почти полная.
|
||||
|
||||
### 3.4 UI и маршруты
|
||||
|
||||
В server.js и views/index.ejs уже есть:
|
||||
|
||||
1. Главная страница со списком записей.
|
||||
2. Форма добавления.
|
||||
3. Soft delete через POST /delete/:id.
|
||||
4. Экспорт через GET /export.
|
||||
5. Вывод текущего лимита и числа использованных записей.
|
||||
6. UI, близкий к стилю Nubes.
|
||||
|
||||
Вывод: клиентский сценарий создания и удаления уже работает как MVP.
|
||||
|
||||
## 4. Что не соответствует ТЗ или не доведено до конца
|
||||
|
||||
### 4.1 Авторизация
|
||||
|
||||
Сейчас в server.js не OIDC, а упрощённый decode токена через base64 без проверки подписи. Это подходит только как временная заглушка, но не соответствует ТЗ.
|
||||
|
||||
Нужно:
|
||||
|
||||
1. Реальная проверка JWT через Keycloak/JWKS или openid-client.
|
||||
2. Нормальный маппинг claims в user-модель.
|
||||
3. Явное определение роли admin.
|
||||
4. Поддержка сценария с несколькими компаниями.
|
||||
|
||||
Это главный блокер продакшна.
|
||||
|
||||
### 4.2 Редактирование записи
|
||||
|
||||
updateEntry уже написана, но:
|
||||
|
||||
1. Нет роута POST /update/:id.
|
||||
2. Нет формы редактирования в index.ejs.
|
||||
3. Нет пользовательского сценария изменения записи.
|
||||
|
||||
То есть требование ТЗ формально не закрыто, хотя код на уровне queries уже есть.
|
||||
|
||||
### 4.3 Admin-функции
|
||||
|
||||
По ТЗ администратор должен:
|
||||
|
||||
1. Видеть записи всех компаний.
|
||||
2. Фильтровать по компании.
|
||||
3. Видеть soft-deleted записи.
|
||||
4. Смотреть аудит.
|
||||
5. Менять лимиты компаний.
|
||||
|
||||
Сейчас ничего из этого не выведено в server.js и views.
|
||||
|
||||
### 4.4 Экспорт
|
||||
|
||||
GET /export уже есть, но он отдаёт просто список value_cidr без агрегации. ТЗ требует суммаризацию в минимальный набор CIDR по всем активным записям.
|
||||
|
||||
Это функциональный разрыв, а не косметика.
|
||||
|
||||
### 4.5 Клиентская валидация
|
||||
|
||||
ТЗ требует валидацию на клиенте и сервере. Сейчас есть только серверная.
|
||||
|
||||
Нужно добавить в форму как минимум:
|
||||
|
||||
1. Проверку формата IPv4/CIDR.
|
||||
2. Проверку диапазона маски.
|
||||
3. Ограничение длины комментария.
|
||||
4. Сообщение о возможной нормализации.
|
||||
|
||||
### 4.6 Multi-company сценарий
|
||||
|
||||
ТЗ явно говорит, что пользователь может принадлежать нескольким компаниям. Сейчас всё построено вокруг одного clientId в req.user.
|
||||
|
||||
Нужно:
|
||||
|
||||
1. Понять реальный формат claims.
|
||||
2. Ввести activeCompany в контекст пользователя.
|
||||
3. Добавить переключатель активной компании в UI.
|
||||
|
||||
## 5. Что не надо перепридумывать
|
||||
|
||||
1. Не надо переписывать проект на другой язык или другой фреймворк.
|
||||
2. Не надо менять схему БД ради самой схемы.
|
||||
3. Не надо переписывать validators.js целиком.
|
||||
4. Не надо переписывать queries.js целиком.
|
||||
5. Не надо делать SPA.
|
||||
|
||||
Правильный путь: минимально нарастить уже существующую Node.js/Express/EJS реализацию.
|
||||
|
||||
## 6. Риски и спорные места
|
||||
|
||||
### 6.1 overlaps в validators.js
|
||||
|
||||
Условие в overlaps написано нестандартно и плохо читается. Прямого доказанного бага по одной только формуле сейчас нет, но это место требует отдельного теста на:
|
||||
|
||||
1. полное совпадение,
|
||||
2. вложенность,
|
||||
3. непересекающиеся диапазоны,
|
||||
4. соседние диапазоны,
|
||||
5. одиночный IP против подсети.
|
||||
|
||||
Решение: сначала добавить точечные тесты, и только потом менять формулу, если тест покажет дефект.
|
||||
|
||||
### 6.2 Admin-роль
|
||||
|
||||
ТЗ говорит: admin это clientId = WZ01112 и отдельный чек-бокс. Сейчас неясно, как этот чек-бокс попадает в токен. Без этого нельзя финально закрыть auth-модель.
|
||||
|
||||
### 6.3 Multi-company claims
|
||||
|
||||
ТЗ требует сценарий нескольких компаний, но в списке claims указан только clientID. Здесь нужна конкретика от команды Keycloak.
|
||||
|
||||
## 7. Новый план реализации
|
||||
|
||||
### Этап 1. Довести до полноты пользовательский сценарий
|
||||
|
||||
Цель: закрыть основной client-flow без смены архитектуры.
|
||||
|
||||
1. Добавить роут POST /update/:id в server.js.
|
||||
2. Добавить UI редактирования в views/index.ejs.
|
||||
3. Показать updated_at и updated_by, если запись менялась.
|
||||
4. Добавить клиентскую валидацию формы добавления и редактирования.
|
||||
5. Добавить точечные тесты на overlaps и нормализацию.
|
||||
|
||||
Результат этапа: client сможет не только добавлять и удалять, но и редактировать записи, как требует ТЗ.
|
||||
|
||||
### Этап 2. Закрыть admin-функциональность
|
||||
|
||||
Цель: реализовать недостающую управленческую часть ТЗ.
|
||||
|
||||
1. Ввести определение роли admin в auth-слое.
|
||||
2. Добавить GET /admin.
|
||||
3. Добавить фильтр по компании.
|
||||
4. Добавить показ удалённых записей.
|
||||
5. Добавить GET /admin/audit.
|
||||
6. Добавить POST /admin/limit/:companyId.
|
||||
7. Добавить отдельный admin view.
|
||||
|
||||
Результат этапа: появляется реальная административная панель, а не только клиентский экран.
|
||||
|
||||
### Этап 3. Привести авторизацию к ТЗ
|
||||
|
||||
Цель: убрать временную заглушку и сделать реальную интеграцию с Keycloak.
|
||||
|
||||
1. Заменить наивный decode токена на верификацию подписи.
|
||||
2. Добавить конфигурацию issuer, audience, jwks/oidc.
|
||||
3. Нормализовать claims в req.user.
|
||||
4. Поддержать admin-claim.
|
||||
5. Поддержать multi-company claims.
|
||||
6. Оставить DEV_MODE только для локальной разработки.
|
||||
|
||||
Результат этапа: сервис можно выводить из чисто dev-сценария.
|
||||
|
||||
### Этап 4. Довести экспорт до требований ТЗ
|
||||
|
||||
Цель: сделать экспорт пригодным для систем фильтрации.
|
||||
|
||||
1. Добавить суммаризацию активных записей в минимальный набор CIDR.
|
||||
2. Суммаризировать по всем компаниям совместно.
|
||||
3. Исключать soft-deleted записи.
|
||||
4. Оставить выдачу text/plain, одна строка на объект.
|
||||
5. Отдельно решить, где ограничивается доступ к export endpoint: ingress, app или оба уровня.
|
||||
|
||||
Результат этапа: экспорт соответствует ТЗ, а не является просто дампом таблицы.
|
||||
|
||||
### Этап 5. Завершение и проверка полноты
|
||||
|
||||
1. Сверить все пункты ТЗ с реализованным поведением.
|
||||
2. Обновить тестовый сценарий.
|
||||
3. Проверить UX ошибок и предупреждений.
|
||||
4. Проверить поведение при снижении custom_limit ниже текущего числа активных записей.
|
||||
5. Проверить сценарии admin/client отдельно.
|
||||
|
||||
## 8. Практический приоритет
|
||||
|
||||
Если делать не всё сразу, а по реальной важности, порядок такой:
|
||||
|
||||
1. Редактирование записи из UI.
|
||||
2. Клиентская валидация.
|
||||
3. Admin-панель и лимиты.
|
||||
4. Реальный OIDC.
|
||||
5. Multi-company.
|
||||
6. Суммаризация export.
|
||||
|
||||
Почему именно так:
|
||||
|
||||
1. Редактирование уже почти готово и закрывает явный пробел ТЗ.
|
||||
2. Admin-функции сейчас отсутствуют полностью.
|
||||
3. OIDC блокирует продакшн, но не мешает локально добить функциональность.
|
||||
4. Экспорт уже работает как черновой endpoint, но должен быть доведён до суммаризации до релиза.
|
||||
|
||||
## 9. Итог
|
||||
|
||||
Правильный план для этого проекта не “переписать всё правильно”, а “довести уже написанное до требований ТЗ”.
|
||||
|
||||
Текущее состояние проекта:
|
||||
|
||||
1. Data-layer в основном готов.
|
||||
2. Server-layer частично готов.
|
||||
3. UI-layer закрывает только часть client-сценария.
|
||||
4. Auth-layer пока временный.
|
||||
5. Admin-layer почти отсутствует.
|
||||
6. Export-layer не завершён по требованиям агрегации.
|
||||
|
||||
Главный вывод: проект ближе к рабочему состоянию, чем казалось по старым планам, но прошлый план был методологически неверен, потому что не опирался на ТЗ и не различал “не написано” и “написано, но не подключено”.
|
||||
|
||||
Эта часть критична. Её нужно делать одной из первых и сразу покрывать тестами.
|
||||
|
||||
### Поддерживаемый ввод
|
||||
|
||||
1. Одиночный IPv4 адрес, который трактуется как /32.
|
||||
2. IPv4 подсеть в CIDR нотации.
|
||||
|
||||
### Запрещённый ввод
|
||||
|
||||
1. IPv6.
|
||||
2. Доменное имя.
|
||||
3. Маска шире допустимой.
|
||||
4. Любые private или special ranges из приложения А.
|
||||
|
||||
### Правила маски
|
||||
|
||||
Допустимы только /22 ... /32.
|
||||
|
||||
### Нормализация
|
||||
|
||||
Если пользователь ввёл адрес с host-битами, сервис должен:
|
||||
|
||||
1. Нормализовать значение до адреса сети.
|
||||
2. Сохранить нормализованное значение.
|
||||
3. Вернуть пользователю явное сообщение, что адрес был нормализован.
|
||||
|
||||
### Проверки в пределах компании
|
||||
|
||||
1. Запрет полного дубликата активной записи.
|
||||
2. Запрет любого пересечения активной записи с существующими активными записями той же компании.
|
||||
3. Между разными компаниями пересечения допускаются.
|
||||
|
||||
### Список запрещённых диапазонов
|
||||
|
||||
Нужно захардкодить как конфигурацию приложения и покрыть тестами:
|
||||
|
||||
1. 10.0.0.0/8
|
||||
2. 172.16.0.0/12
|
||||
3. 192.168.0.0/16
|
||||
4. 100.64.0.0/10
|
||||
5. 127.0.0.0/8
|
||||
6. 169.254.0.0/16
|
||||
7. 192.0.0.0/24
|
||||
8. 192.0.2.0/24
|
||||
9. 198.51.100.0/24
|
||||
10. 203.0.113.0/24
|
||||
11. 198.18.0.0/15
|
||||
12. 224.0.0.0/4
|
||||
13. 240.0.0.0/4
|
||||
14. 255.255.255.255/32
|
||||
|
||||
## 10. Лимиты и конкурентность
|
||||
|
||||
Это важное место, которого обычно недооценивают.
|
||||
|
||||
### Правила лимитов
|
||||
|
||||
1. Есть глобальный DEFAULT_LIMIT, по умолчанию 15.
|
||||
2. Для компании может быть custom_limit.
|
||||
3. При снижении лимита ниже текущего количества записей существующие записи не удаляются.
|
||||
4. Пока число активных записей больше лимита, новые записи создавать нельзя.
|
||||
|
||||
### Риск гонок
|
||||
|
||||
Если два запроса одновременно создают записи в одной компании, возможны:
|
||||
|
||||
1. Пробитие лимита.
|
||||
2. Пропуск пересечения.
|
||||
3. Пропуск дубликата.
|
||||
|
||||
### Что делать
|
||||
|
||||
Операцию создания и обновления записи нужно делать в транзакции с сериализацией логики на уровне компании. Практически это можно решить так:
|
||||
|
||||
1. Брать advisory lock по company_id перед проверками и записью.
|
||||
2. Либо делать SELECT ... FOR UPDATE по строке компании, если этого достаточно для вашей схемы доступа.
|
||||
|
||||
Для первой версии я бы выбрал advisory lock по company_id. Это проще и надёжнее для бизнес-ограничений, которые нельзя полностью выразить обычным unique index.
|
||||
|
||||
## 11. Экспорт агрегированного списка
|
||||
|
||||
### Требования
|
||||
|
||||
1. В экспорт попадают только активные записи.
|
||||
2. Данные берутся по всем компаниям.
|
||||
3. Пересечения между компаниями допустимы на уровне хранения, но в export должны агрегироваться в минимальный набор CIDR.
|
||||
4. Формат ответа: text/plain.
|
||||
5. Одна строка = один CIDR.
|
||||
|
||||
### Что нужно зафиксировать реализационно
|
||||
|
||||
1. Результат должен быть отсортирован для стабильности.
|
||||
2. В ответе должен быть завершающий перевод строки.
|
||||
3. Content-Type должен быть text/plain; charset=utf-8.
|
||||
4. Желательно отдавать Content-Disposition с понятным именем файла.
|
||||
|
||||
### Защита endpoint
|
||||
|
||||
Если endpoint на старте работает без auth, то доступ надо ограничить минимум одним из способов:
|
||||
|
||||
1. Проверка client IP в приложении.
|
||||
2. Ограничение на reverse proxy.
|
||||
3. Оба сразу.
|
||||
|
||||
## 12. UI-потоки
|
||||
|
||||
### Экран клиента
|
||||
|
||||
Должны быть:
|
||||
|
||||
1. Селектор активной компании, если компаний несколько.
|
||||
2. Таблица записей.
|
||||
3. Индикатор использовано X из N.
|
||||
4. Форма создания записи.
|
||||
5. Возможность редактирования.
|
||||
6. Возможность soft delete.
|
||||
|
||||
### Экран администратора
|
||||
|
||||
Должны быть:
|
||||
|
||||
1. Таблица по всем компаниям.
|
||||
2. Фильтр по компании.
|
||||
3. Фильтр показа удалённых записей.
|
||||
4. Просмотр журнала аудита.
|
||||
5. Управление лимитами компании.
|
||||
|
||||
### UX-детали, которые обязательно сделать
|
||||
|
||||
1. Понятные сообщения об ошибках валидации.
|
||||
2. Явное сообщение о нормализации адреса.
|
||||
3. Явное сообщение о превышении лимита.
|
||||
4. Явное сообщение о пересечении с существующей записью.
|
||||
|
||||
## 13. Пошаговый план реализации
|
||||
|
||||
### Этап 1. Каркас проекта
|
||||
|
||||
1. Создать структуру каталогов.
|
||||
2. Подготовить requirements.txt.
|
||||
3. Подготовить .env.example.
|
||||
4. Подключить FastAPI, Jinja2, static.
|
||||
5. Подготовить docker-compose.yml с PostgreSQL.
|
||||
|
||||
Результат этапа: приложение стартует, открывается базовая страница, есть подключение к БД.
|
||||
|
||||
### Этап 2. Схема БД и миграции
|
||||
|
||||
1. Настроить Alembic.
|
||||
2. Создать initial migration.
|
||||
3. Поднять таблицы companies, whitelist_entries, audit_log.
|
||||
4. Добавить нужные индексы.
|
||||
|
||||
Результат этапа: схема БД фиксирована и воспроизводима.
|
||||
|
||||
### Этап 3. Валидатор CIDR
|
||||
|
||||
1. Реализовать разбор IPv4 и CIDR.
|
||||
2. Реализовать проверку маски.
|
||||
3. Реализовать нормализацию.
|
||||
4. Реализовать проверку запрещённых диапазонов.
|
||||
5. Написать тесты на валидатор.
|
||||
|
||||
Результат этапа: независимый, протестированный модуль бизнес-валидации.
|
||||
|
||||
### Этап 4. Сервисный слой для записей
|
||||
|
||||
1. Реализовать list.
|
||||
2. Реализовать create.
|
||||
3. Реализовать update.
|
||||
4. Реализовать soft delete.
|
||||
5. Реализовать проверки лимитов, дубликатов и пересечений.
|
||||
6. Добавить транзакционную защиту от гонок.
|
||||
|
||||
Результат этапа: бизнес-операции работают без UI.
|
||||
|
||||
### Этап 5. Аудит
|
||||
|
||||
1. Добавить запись CREATE.
|
||||
2. Добавить запись UPDATE со старым и новым состоянием.
|
||||
3. Добавить запись DELETE.
|
||||
4. Добавить интерфейс чтения для admin.
|
||||
|
||||
Результат этапа: все изменяющие действия фиксируются.
|
||||
|
||||
### Этап 6. Авторизация
|
||||
|
||||
1. Реализовать dev-заглушку.
|
||||
2. Реализовать чтение и валидацию JWT из Keycloak.
|
||||
3. Реализовать преобразование claims в current user.
|
||||
4. Реализовать проверки client/admin.
|
||||
5. Реализовать переключение компании.
|
||||
|
||||
Результат этапа: права и контекст пользователя работают сквозным образом.
|
||||
|
||||
### Этап 7. HTML-интерфейс
|
||||
|
||||
1. Реализовать страницу списка.
|
||||
2. Реализовать формы создания и редактирования.
|
||||
3. Реализовать soft delete из UI.
|
||||
4. Реализовать админские экраны.
|
||||
|
||||
Результат этапа: сервис пригоден для ручной эксплуатации.
|
||||
|
||||
### Этап 8. Экспорт
|
||||
|
||||
1. Реализовать сбор всех активных CIDR.
|
||||
2. Реализовать агрегацию.
|
||||
3. Реализовать endpoint export.
|
||||
4. Реализовать сетевое ограничение.
|
||||
|
||||
Результат этапа: внешняя система может забирать текстовый агрегированный whitelist.
|
||||
|
||||
### Этап 9. Финализация
|
||||
|
||||
1. Написать README.
|
||||
2. Подготовить Dockerfile.
|
||||
3. Подготовить пример systemd unit при необходимости.
|
||||
4. Прогнать ручной smoke-test.
|
||||
|
||||
Результат этапа: сервис можно разворачивать и передавать коллегам.
|
||||
|
||||
## 14. Тестовая стратегия
|
||||
|
||||
Минимально обязательные тесты:
|
||||
|
||||
1. Валидный одиночный IPv4 превращается в /32.
|
||||
2. Валидная подсеть принимается.
|
||||
3. Host-биты нормализуются.
|
||||
4. Маски шире допустимой границы отклоняются.
|
||||
5. IPv6 отклоняется.
|
||||
6. Все запрещённые диапазоны отклоняются.
|
||||
7. Дубликат в одной компании запрещён.
|
||||
8. Пересечение в одной компании запрещено.
|
||||
9. Тот же CIDR в другой компании разрешён.
|
||||
10. Soft delete освобождает лимит.
|
||||
11. Export не включает soft-deleted записи.
|
||||
12. Export агрегирует CIDR корректно.
|
||||
13. Client не видит чужие компании.
|
||||
14. Admin видит все компании.
|
||||
15. Аудит создаётся для create, update, delete.
|
||||
|
||||
## 15. Что можно отложить после первой версии
|
||||
|
||||
Это не нужно тащить в MVP:
|
||||
|
||||
1. Полноценный SPA.
|
||||
2. Сложная ORM.
|
||||
3. WebSocket.
|
||||
4. Фоновая очередь.
|
||||
5. Исторические версии записей кроме audit log.
|
||||
6. Автоматическое уведомление по email.
|
||||
|
||||
## 16. Главные риски проекта
|
||||
|
||||
1. Неясный формат claims из Keycloak.
|
||||
2. Гонки при одновременном создании записей.
|
||||
3. Ошибки в трактовке пересечений CIDR.
|
||||
4. Неправильная нормализация адресов без понятного сообщения пользователю.
|
||||
5. Слишком раннее усложнение фронтенда.
|
||||
|
||||
## 17. Что я бы делал первым
|
||||
|
||||
Если начинать реализацию прямо сейчас, порядок такой:
|
||||
|
||||
1. Каркас проекта.
|
||||
2. Схема БД.
|
||||
3. Валидатор и тесты.
|
||||
4. Сервис create/update/delete с транзакциями.
|
||||
5. Только потом UI и Keycloak.
|
||||
|
||||
Это самый безопасный путь: сначала фиксируется ядро бизнес-логики, потом уже внешний слой.
|
||||
|
||||
## 18. Итоговое решение
|
||||
|
||||
За основу реализации стоит брать простой Python/FastAPI сервис с PostgreSQL, синхронной серверной логикой, жёсткой серверной валидацией, транзакционной защитой от гонок и минималистичным HTML UI.
|
||||
|
||||
Главная мысль: сложность здесь не во фронтенде и не в фреймворке, а в корректной реализации правил CIDR, лимитов, ролей и аудита. План должен защищать именно эти части, а не раздувать стек.
|
||||
@@ -1,188 +0,0 @@
|
||||
# План разработки — IP WhiteList Microservice v2
|
||||
|
||||
> **Автор:** GitHub Copilot (Claude Sonnet 4.6)
|
||||
> **Дата:** 2026-05-29
|
||||
|
||||
---
|
||||
|
||||
## Стек
|
||||
|
||||
| Слой | Технология | Обоснование |
|
||||
|---|---|---|
|
||||
| Бэкенд | Python 3.11+ / FastAPI | Коллеги знают Python, авто-документация |
|
||||
| БД | PostgreSQL | Надёжно, поддерживает аудит и сложные запросы |
|
||||
| Работа с БД | psycopg2 + сырой SQL | Проще чем ORM, понятно всем, никакой магии |
|
||||
| Миграции | Alembic | Только для версионирования схемы |
|
||||
| Фронтенд | Jinja2 + обычные HTML-формы | Без JS-фреймворков, минимум зависимостей |
|
||||
| Авторизация | Keycloak OIDC (JWT) | Заглушка только в dev через `.env` флаг |
|
||||
| IP-логика | stdlib `ipaddress` + `netaddr` | Суммаризация CIDR через `netaddr` |
|
||||
|
||||
---
|
||||
|
||||
## Открытые вопросы (нужно прояснить до кодирования)
|
||||
|
||||
1. **Чекбокс администратора** — ТЗ: admin = `clientId == WZ01112` + «отдельный чек-бокс». Что это: отдельный claim в Keycloak-токене (`is_admin: true`)? Роль? Нужно уточнить у команды Keycloak.
|
||||
2. **Создание Company в БД** — когда появляется запись: при первом входе пользователя автоматически, или администратор заводит вручную?
|
||||
3. **Кто потребляет внешний endpoint** — endpoint без авторизации, доступ по IP. Список доверенных IP задаётся конфигом? Nginx ACL?
|
||||
|
||||
---
|
||||
|
||||
## Файловая структура
|
||||
|
||||
```
|
||||
IPWhiteList/
|
||||
├── app/
|
||||
│ ├── main.py # FastAPI app, роутеры, startup
|
||||
│ ├── config.py # Настройки из .env (DEFAULT_LIMIT, DEV_MODE, DB_DSN и др.)
|
||||
│ ├── db.py # psycopg2 connection pool
|
||||
│ ├── models/
|
||||
│ │ └── sql.py # DDL-схема (только для документации, не ORM)
|
||||
│ ├── validators.py # IPv4/CIDR: формат, маска, серые адреса, нормализация
|
||||
│ ├── crud/
|
||||
│ │ ├── entries.py # CRUD whitelist_entries
|
||||
│ │ ├── companies.py # Компании и лимиты
|
||||
│ │ └── audit.py # Запись в audit_log
|
||||
│ ├── auth/
|
||||
│ │ ├── oidc.py # Валидация JWT Keycloak
|
||||
│ │ ├── stub.py # Dev-заглушка (только при DEV_MODE=true)
|
||||
│ │ └── deps.py # FastAPI Depends: current_user
|
||||
│ ├── routers/
|
||||
│ │ ├── entries.py # CRUD UI-роуты + HTMX-фрагменты
|
||||
│ │ ├── admin.py # Аудит, лимиты (только admin)
|
||||
│ │ └── external.py # GET /api/v1/export — txt-файл
|
||||
│ └── cidr_utils.py # Суммаризация через netaddr
|
||||
├── templates/
|
||||
│ ├── base.html
|
||||
│ ├── index.html # Таблица записей + индикатор лимита
|
||||
│ ├── partials/
|
||||
│ │ ├── table.html # HTMX-фрагмент таблицы
|
||||
│ │ └── form.html # Форма создания/редактирования
|
||||
│ └── admin/
|
||||
│ ├── audit.html
|
||||
│ └── limits.html
|
||||
├── static/
|
||||
│ └── style.css
|
||||
├── migrations/
|
||||
│ ├── env.py
|
||||
│ └── versions/
|
||||
├── tests/
|
||||
│ ├── test_validators.py # Юниты для IPv4-валидации (критично!)
|
||||
│ └── test_crud.py
|
||||
├── docs/
|
||||
│ ├── plan.md # LEGACY
|
||||
│ ├── plan-v2.md # Этот файл
|
||||
│ └── WhiteIPlist.docx # Исходное ТЗ
|
||||
├── .env.example
|
||||
├── alembic.ini
|
||||
├── docker-compose.yml # PostgreSQL для dev
|
||||
├── requirements.txt
|
||||
└── README.md
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Схема БД
|
||||
|
||||
```sql
|
||||
-- Компании (создаются автоматически при первом входе или вручную админом — уточнить)
|
||||
CREATE TABLE companies (
|
||||
id SERIAL PRIMARY KEY,
|
||||
client_id VARCHAR(64) UNIQUE NOT NULL, -- из Keycloak claim
|
||||
name VARCHAR(255),
|
||||
custom_limit INTEGER DEFAULT NULL -- NULL = использовать глобальный DEFAULT_LIMIT
|
||||
);
|
||||
|
||||
-- Whitelist-записи
|
||||
CREATE TABLE whitelist_entries (
|
||||
id SERIAL PRIMARY KEY,
|
||||
company_id INTEGER NOT NULL REFERENCES companies(id),
|
||||
value CIDR NOT NULL, -- нормализованный CIDR
|
||||
comment VARCHAR(255),
|
||||
created_by VARCHAR(255) NOT NULL, -- email из токена
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_by VARCHAR(255),
|
||||
updated_at TIMESTAMPTZ,
|
||||
deleted_by VARCHAR(255),
|
||||
deleted_at TIMESTAMPTZ -- NULL = активная запись
|
||||
);
|
||||
|
||||
-- Аудит (только append, без UPDATE/DELETE)
|
||||
CREATE TABLE audit_log (
|
||||
id SERIAL PRIMARY KEY,
|
||||
user_email VARCHAR(255) NOT NULL,
|
||||
company_id INTEGER NOT NULL,
|
||||
action VARCHAR(32) NOT NULL, -- CREATE | UPDATE | DELETE
|
||||
old_value TEXT,
|
||||
new_value TEXT,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Индексы
|
||||
CREATE INDEX ON whitelist_entries(company_id) WHERE deleted_at IS NULL;
|
||||
CREATE INDEX ON audit_log(company_id);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Этапы
|
||||
|
||||
### Этап 1 — Каркас + конфиг
|
||||
- [ ] Структура папок
|
||||
- [ ] `requirements.txt`: fastapi, uvicorn, psycopg2-binary, alembic, jinja2, python-jose, netaddr
|
||||
- [ ] `.env.example` со всеми переменными: `DB_DSN`, `DEFAULT_LIMIT=15`, `DEV_MODE=false`, `KEYCLOAK_URL`, `KEYCLOAK_REALM`, `KEYCLOAK_CLIENT_ID`, `ALLOWED_EXPORT_IPS`
|
||||
- [ ] `config.py` — читает `.env`, все параметры типизированы
|
||||
- [ ] `db.py` — psycopg2 connection pool (SimpleConnectionPool)
|
||||
- [ ] `docker-compose.yml` с PostgreSQL
|
||||
|
||||
### Этап 2 — Миграции (схема БД)
|
||||
- [ ] Alembic init
|
||||
- [ ] Initial migration: `companies`, `whitelist_entries`, `audit_log` + индексы
|
||||
- [ ] Проверка `alembic upgrade head`
|
||||
|
||||
### Этап 3 — Валидатор IPv4 (с тестами)
|
||||
- [ ] `validators.py`: принимает строку → возвращает нормализованный CIDR или ошибку
|
||||
- [ ] Проверка формата: одиночный IP или CIDR
|
||||
- [ ] Проверка маски: /22 – /32 (шире /21 — `ValidationError`)
|
||||
- [ ] Нормализация host-битов: `192.168.1.5/24` → `192.168.1.0/24` + флаг `was_normalized=True`
|
||||
- [ ] Запрет серых диапазонов (все из Приложения А ТЗ)
|
||||
- [ ] `tests/test_validators.py` — покрыть все граничные случаи
|
||||
|
||||
### Этап 4 — CRUD-логика
|
||||
- [ ] `crud/companies.py`: get_or_create по client_id, get_limit (custom_limit ?? DEFAULT_LIMIT)
|
||||
- [ ] `crud/entries.py`: список активных, создание (лимит + дубликаты + пересечения), редактирование, soft-delete
|
||||
- [ ] `crud/audit.py`: append-only запись
|
||||
|
||||
### Этап 5 — Авторизация
|
||||
- [ ] `auth/oidc.py` — валидация JWT через JWKS Keycloak, извлечение `clientID`, `email`, определение роли
|
||||
- [ ] Логика роли admin: `clientID == WZ01112` + (claim `is_admin == true` — **уточнить**)
|
||||
- [ ] `auth/stub.py` — только при `DEV_MODE=true`: читает `X-Dev-User` из заголовка
|
||||
- [ ] `auth/deps.py` — `Depends(current_user)` для роутеров
|
||||
|
||||
### Этап 6 — Роутеры + UI
|
||||
- [ ] `routers/entries.py`: список, форма создания, форма редактирования, удаление, переключатель компании
|
||||
- [ ] `routers/admin.py`: журнал аудита, управление лимитами
|
||||
- [ ] Шаблоны Jinja2: base.html, index.html, form.html, admin/audit.html, admin/limits.html
|
||||
- [ ] Индикатор лимита «X из N» на странице
|
||||
- [ ] Уведомление о нормализации адреса пользователю
|
||||
|
||||
### Этап 7 — Внешний endpoint
|
||||
- [ ] `GET /api/v1/export` — только активные записи всех компаний
|
||||
- [ ] Суммаризация через `netaddr.cidr_merge()`
|
||||
- [ ] Ответ: `text/plain`, одна строка — один CIDR
|
||||
- [ ] IP-фильтр из `ALLOWED_EXPORT_IPS` (middleware или Depends)
|
||||
|
||||
### Этап 8 — Деплой
|
||||
- [ ] `Dockerfile` (python:3.11-slim, uvicorn)
|
||||
- [ ] Systemd unit как альтернатива
|
||||
- [ ] Nginx конфиг: reverse proxy + location для static
|
||||
- [ ] README: как поднять с нуля
|
||||
|
||||
---
|
||||
|
||||
## Ключевые принципы
|
||||
|
||||
- **Синхронный код везде** — никакого async/await. FastAPI поддерживает синхронные роутеры.
|
||||
- **Серверная валидация — авторитетная**. Клиентская — только UX.
|
||||
- **`DEV_MODE=true`** — единственный способ обойти Keycloak. В prod недоступен.
|
||||
- **Audit log — append only**. Никаких UPDATE/DELETE в `audit_log`.
|
||||
- **Лимит `DEFAULT_LIMIT`** — всегда из `config.py`, который читает `.env`. Без пересборки.
|
||||
@@ -1,103 +0,0 @@
|
||||
# Рабочий план — IP WhiteList
|
||||
|
||||
> **Автор:** GitHub Copilot (Claude Sonnet 4.6)
|
||||
> **Дата:** 2026-05-29
|
||||
> **Стек:** Node.js + Express + pg + EJS
|
||||
> **Деплой:** nubes_nodejs + nubes_postgres (через веб-кабинет)
|
||||
|
||||
---
|
||||
|
||||
## Что выяснили
|
||||
|
||||
| Факт | Детали |
|
||||
|---|---|
|
||||
| **API шлюз** | `lk-api-gateway.ngcloud.ru`, за DDOS-Guard |
|
||||
| **JWT** | `iss: auth-api`, claims: `ClientID`, `company_id`, `company_name`, `email` |
|
||||
| **Токен для dev** | `secrets.txt`, tech-токен, долгий |
|
||||
| **Валидация JWT** | Шлюз делает сам, нам не нужно |
|
||||
| **Деплой** | Пользователь создаёт инстансы через веб-кабинет |
|
||||
| **isAdmin** | ❓ В JWT нет, нужно уточнить как передавать |
|
||||
| **Мульти-компании** | ❓ В JWT одна компания, список — в authData (localStorage) |
|
||||
|
||||
---
|
||||
|
||||
## Файлы проекта (всё в корне)
|
||||
|
||||
```
|
||||
├── server.js # Express: старт, роуты, middleware
|
||||
├── db.js # pg Pool
|
||||
├── .env.example # DB_DSN, PORT, DEFAULT_LIMIT, DEV_MODE
|
||||
├── package.json
|
||||
├── views/ # EJS-шаблоны
|
||||
│ └── index.ejs # таблица + форма
|
||||
├── public/
|
||||
│ └── style.css
|
||||
├── sql/
|
||||
│ └── schema.sql # CREATE TABLE companies, whitelist_entries, audit_log
|
||||
└── .gitignore
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Схема БД
|
||||
|
||||
```sql
|
||||
companies (id, client_id UNIQUE, name, custom_limit)
|
||||
whitelist_entries (id, company_id FK, value_cidr, comment, created_by, created_at, updated_by, updated_at, deleted_by, deleted_at)
|
||||
audit_log (id, user_email, company_id, action, old_value, new_value, created_at)
|
||||
```
|
||||
|
||||
- `deleted_at IS NULL` = активная запись
|
||||
- `custom_limit IS NULL` = использовать DEFAULT_LIMIT (15)
|
||||
|
||||
---
|
||||
|
||||
## Порядок действий
|
||||
|
||||
### Шаг 1 — Каркас
|
||||
- package.json (express, pg, ejs, dotenv)
|
||||
- server.js (Express, EJS, static, health check `/healthz`)
|
||||
- db.js (pg Pool, `SELECT 1` при старте)
|
||||
- .env.example
|
||||
|
||||
### Шаг 2 — Схема БД
|
||||
- sql/schema.sql
|
||||
- Запустить на своём PG
|
||||
|
||||
### Шаг 3 — Валидатор IPv4
|
||||
- Функция validateCIDR(input) → { cidr, wasNormalized } | error
|
||||
- Правила: /32–/22, запрет серых, нормализация host-битов
|
||||
|
||||
### Шаг 4 — CRUD (сырой pg, без ORM)
|
||||
- Список записей компании
|
||||
- Создание (проверка лимита, дубликатов, пересечений)
|
||||
- Редактирование
|
||||
- Soft-delete
|
||||
|
||||
### Шаг 5 — Auth middleware
|
||||
- DEV_MODE=true: читать заголовок X-Dev-User
|
||||
- PROD: читать JWT из Authorization (шлюз уже проверил)
|
||||
|
||||
### Шаг 6 — UI
|
||||
- Таблица + форма создания/редактирования
|
||||
- Индикатор лимита «X из N»
|
||||
- Сообщения: нормализация, превышение, пересечение
|
||||
|
||||
### Шаг 7 — Аудит
|
||||
- Запись в audit_log при create/update/delete
|
||||
|
||||
### Шаг 8 — Экспорт
|
||||
- GET /api/v1/export — txt, все активные CIDR
|
||||
|
||||
### Шаг 9 — Деплой
|
||||
- Завести nubes_postgres и nubes_nodejs через кабинет
|
||||
- Подключить к API-шлюзу (уточнить процедуру)
|
||||
|
||||
---
|
||||
|
||||
## Что откладываем
|
||||
|
||||
- Keycloak OIDC (шлюз делает)
|
||||
- Полноценный админ-интерфейс
|
||||
- Переключатель компаний (multi-profile)
|
||||
- Тонкая настройка прав
|
||||
@@ -1,107 +0,0 @@
|
||||
# IP WhiteList — План разработки
|
||||
|
||||
> **Дата:** 2026-05-30
|
||||
> **Основание:** [WhiteIPlist.txt](../WhiteIPlist.txt) (ТЗ) + фактический код в [ipwhitelist-app](../../ipwhitelist-app)
|
||||
> **Статус:** каноничный — единственный актуальный план
|
||||
|
||||
---
|
||||
|
||||
## 1. Текущее состояние
|
||||
|
||||
### Готово ✅
|
||||
|
||||
| Слой | Файлы | Что есть |
|
||||
|---|---|---|
|
||||
| БД | `sql/schema.sql` | `companies`, `whitelist_entries` (soft delete), `audit_log` + индексы |
|
||||
| Валидатор | `src/validators.js` | Все 14 запрещённых диапазонов Приложения А, /22–/32, нормализация host-битов, проверка пересечений |
|
||||
| CRUD | `src/queries.js` | `createEntry`, `updateEntry`, `deleteEntry` (soft), `listEntries`, `getExportCIDRs`, `getAudit`, `getLimit` |
|
||||
| UI | `views/index.ejs` | Список, форма добавления, кнопка удаления, стиль Nubes |
|
||||
| Роуты | `server.js` | `GET /`, `POST /add`, `POST /delete/:id`, `GET /export`, `GET /healthz` |
|
||||
|
||||
### Написано в queries.js, но не подключено к роутам
|
||||
|
||||
- `updateEntry` — нет `POST /update/:id`, нет UI редактирования
|
||||
- `getAudit` — нет страницы аудита
|
||||
- `listEntries(companyId, includeDeleted=true)` — флаг есть, не используется
|
||||
|
||||
---
|
||||
|
||||
## 2. Что требует ТЗ, но отсутствует
|
||||
|
||||
| # | Требование | Готовность |
|
||||
|---|---|---|
|
||||
| 1 | Редактирование записи из UI | ❌ queries есть, роута нет |
|
||||
| 2 | Админ-панель (все компании, лимиты, аудит, фильтры) | ❌ |
|
||||
| 3 | OIDC Keycloak вместо base64-заглушки | ❌ |
|
||||
| 4 | Суммаризация CIDR в `/export` | ❌ отдаёт сырой список |
|
||||
| 5 | Клиентская валидация (JS в форме) | ❌ |
|
||||
| 6 | Переключатель компаний (multi-company) | ❌ |
|
||||
| 7 | Просмотр soft-deleted записей админом | ❌ |
|
||||
| 8 | Изменение `custom_limit` для компании | ❌ |
|
||||
|
||||
---
|
||||
|
||||
## 3. Порядок реализации
|
||||
|
||||
### Этап 1 — Пользовательский сценарий (client-flow)
|
||||
|
||||
- [x] Валидатор IPv4/CIDR — полный
|
||||
- [x] Создание / удаление / экспорт
|
||||
- [ ] **Добавить `POST /update/:id`** в `server.js`
|
||||
- [ ] **Добавить inline-форму редактирования** в `views/index.ejs`
|
||||
- [ ] **Клиентская валидация** (JS: формат, маска, длина комментария)
|
||||
|
||||
### Этап 2 — Административная панель
|
||||
|
||||
- [ ] Определение admin-роли (`clientId === 'WZ01112'` + чекбокс)
|
||||
- [ ] `GET /admin` — страница со всеми компаниями
|
||||
- [ ] `POST /admin/limit/:companyId` — изменение custom_limit
|
||||
- [ ] `GET /admin/audit` — журнал аудита
|
||||
- [ ] Фильтр по компании + показ удалённых записей
|
||||
|
||||
### Этап 3 — Авторизация Keycloak OIDC
|
||||
|
||||
- [ ] `npm install openid-client`
|
||||
- [ ] Замена base64-decode на проверку подписи JWT
|
||||
- [ ] Маппинг claims → `req.user` (clientId, email, role)
|
||||
- [ ] Оставить `DEV_MODE` только для локальной разработки
|
||||
|
||||
### Этап 4 — Экспорт и Multi-company
|
||||
|
||||
- [ ] `npm install cidr-tools` — суммаризация в `GET /export`
|
||||
- [ ] Переключатель активной компании (если несколько `clientId` в claims)
|
||||
|
||||
### Этап 5 — Завершение
|
||||
|
||||
- [ ] Автотесты (`jest` + `supertest`)
|
||||
- [ ] Пагинация (если лимит > 50)
|
||||
- [ ] Сверка всех пунктов ТЗ
|
||||
|
||||
---
|
||||
|
||||
## 4. Что НЕ делать
|
||||
|
||||
- ❌ Не переписывать на Python/FastAPI
|
||||
- ❌ Не менять схему БД
|
||||
- ❌ Не переписывать `validators.js` (он полный)
|
||||
- ❌ Не переписывать `queries.js`
|
||||
- ❌ Не делать SPA
|
||||
|
||||
---
|
||||
|
||||
## 5. Зависимости для установки
|
||||
|
||||
```
|
||||
npm install cidr-tools openid-client
|
||||
npm install --save-dev jest supertest
|
||||
```
|
||||
|
||||
Всё остальное — в рамках Express + EJS + pg.
|
||||
|
||||
---
|
||||
|
||||
## 6. Риски
|
||||
|
||||
- **Admin-роль:** неясно как «отдельный чек-бокс» из ТЗ попадает в токен — требует уточнения с командой Keycloak
|
||||
- **Multi-company claims:** ТЗ говорит о нескольких компаниях, но в claims только `clientID` — нужен реальный формат
|
||||
- **IP-ограничение `/export`:** делать в приложении или на уровне ingress — решить при деплое
|
||||
@@ -1,169 +0,0 @@
|
||||
# IP WhiteList — Архитектура (актуально, 2026-05-30 14:43)
|
||||
|
||||
> Ветка `sonnet`, репо: `gitea.services.ngcloud.ru/Nail/ipwhitelist-app.git`
|
||||
> Код: `/home/naeel/ipwhitelist-app`
|
||||
|
||||
---
|
||||
|
||||
## Стек
|
||||
|
||||
| Слой | Технология |
|
||||
|---|---|
|
||||
| Сервер | Node.js + Express 4 |
|
||||
| Шаблоны | EJS (server-side, без SPA) |
|
||||
| БД | PostgreSQL, клиент `pg` (pool) |
|
||||
| Безопасность | helmet, express-rate-limit, csrf-csrf, express-session |
|
||||
| JWT | jsonwebtoken (mock RS256 / OIDC validation) |
|
||||
| Авторизация | Keycloak (OIDC) или mock при локальной разработке |
|
||||
|
||||
---
|
||||
|
||||
## Структура модулей
|
||||
|
||||
```
|
||||
server.js — точка входа: init + подключение роутеров (131 строк)
|
||||
src/
|
||||
auth.js — аутентификация: OIDC или mock, session middleware
|
||||
config.js — MOCK_USERS, backUrl()
|
||||
db.js — pg pool (checkConnection)
|
||||
queries.js — все SQL-запросы к БД
|
||||
validators.js — validateCIDR, aggregateCIDRs
|
||||
middleware/
|
||||
csrf.js — initCsrf() → { doubleCsrfProtection, generateCsrfToken }
|
||||
rateLimit.js — mutationLimiter (30/мин), exportLimiter (20/мин)
|
||||
routes/
|
||||
auth.js — /login /callback /logout /dev-login
|
||||
entries.js — / /add /edit/:id /delete/:id
|
||||
admin.js — /audit /admin /admin/limit/:companyId
|
||||
export.js — /export
|
||||
views/
|
||||
login.ejs — форма входа (mock) или заглушка (OIDC)
|
||||
dev-login.ejs — тестовый вход (пресеты + произвольные поля)
|
||||
index.ejs — список записей пользователя / admin-обзор
|
||||
admin.ejs — admin-панель (все компании, лимиты)
|
||||
audit.ejs — лог операций
|
||||
sql/
|
||||
schema.sql — companies, whitelist_entries (soft delete), audit_log
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Схема авторизации
|
||||
|
||||
```
|
||||
GET /login
|
||||
┌─ OIDC-режим (KC_CLIENT_ID задан) ──────────────────────────────────────┐
|
||||
│ state → сессия │
|
||||
│ redirect → keycloak.nubes.ru/realms/cloud/.../auth │
|
||||
│ ↓ KK перенаправляет на /callback │
|
||||
│ GET /callback → проверить state → exchangeCode → токен KK │
|
||||
│ → userFromPayload → req.session.user → redirect / │
|
||||
└─────────────────────────────────────────────────────────────────────────┘
|
||||
┌─ Mock-режим (KC_CLIENT_ID не задан) ───────────────────────────────────┐
|
||||
│ render login.ejs (выбрать из MOCK_USERS) │
|
||||
│ POST /login → CSRF → req.session.user → redirect / │
|
||||
└─────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
GET /dev-login (DEV_MODE=true или DEV_SECRET задан)
|
||||
render dev-login.ejs (пресеты MOCK_USERS + произвольные поля)
|
||||
POST /dev-login → CSRF → req.session.user → redirect /
|
||||
⚠ Работает в ОБОИХ режимах — для тестирования с разными ролями
|
||||
|
||||
GET /logout
|
||||
session.destroy() + clearCookie('connect.sid')
|
||||
OIDC: redirect → keycloak logout endpoint
|
||||
Mock: redirect → /login
|
||||
|
||||
Все защищённые роуты: auth.middleware
|
||||
req.session.user → req.user (основной путь)
|
||||
Authorization: Bearer → verify JWT → req.user (API-клиенты)
|
||||
cookie 'jwt' (legacy) → verify → session → req.user (плавная миграция)
|
||||
нет ничего → GET: redirect /login, остальное: 401
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## JWT claims (auth-api, из реального токена портала)
|
||||
|
||||
> auth-api выпускает свой JWT — **не Keycloak напрямую**.
|
||||
> Issuer: `"auth-api"`, алгоритм: RS256.
|
||||
> Наш сервис принимает и валидирует этот токен.
|
||||
|
||||
| Поле | Тип | Пример | Что используем |
|
||||
|---|---|---|---|
|
||||
| `ClientID` | string | `"WZ01325"` | `req.user.clientId` |
|
||||
| `company_id` | UUID | `"3e64aac6-..."` | `req.user.companyId`, FK в БД |
|
||||
| `company_name` | string | `"Тест"` | `req.user.companyName` |
|
||||
| `email` | string | `"user@example.com"` | `req.user.email` (аудит) |
|
||||
| `login` | string | `"user@example.com"` | fallback для email |
|
||||
| `sub` | UUID | `"0199e325-..."` | fallback для companyId |
|
||||
| `iss` | string | `"auth-api"` | проверяется при валидации |
|
||||
| `exp` | number | ~12 часов | автоматически |
|
||||
|
||||
**Признак admin:** `clientId === ADMIN_CLIENT_ID` (env `ADMIN_CLIENT_ID`, default `WZ01112`).
|
||||
`isAdmin` не приходит в JWT — см. [questions.md](questions.md).
|
||||
|
||||
---
|
||||
|
||||
## Маршруты
|
||||
|
||||
| Метод | Путь | Доступ | Лимит |
|
||||
|---|---|---|---|
|
||||
| GET | `/healthz` | public | — |
|
||||
| GET | `/.well-known/jwks.json` | public | — |
|
||||
| GET | `/export` | public | 20/мин |
|
||||
| GET/POST | `/login` | public | — |
|
||||
| GET | `/callback` | public | — |
|
||||
| GET | `/logout` | public | — |
|
||||
| GET/POST | `/dev-login` | public (с guard) | — |
|
||||
| GET | `/` | auth | — |
|
||||
| POST | `/add` `/edit/:id` `/delete/:id` | auth | 30/мин |
|
||||
| GET | `/audit` `/admin` | auth + admin | — |
|
||||
| POST | `/admin/limit/:companyId` | auth + admin | 30/мин |
|
||||
|
||||
---
|
||||
|
||||
## Env-переменные
|
||||
|
||||
### Обязательные в продакшене
|
||||
|
||||
| Переменная | Описание |
|
||||
|---|---|
|
||||
| `DB_HOST` / `DB_PORT` / `DB_NAME` / `DB_USER` / `DB_PASS` | PostgreSQL |
|
||||
| `SESSION_SECRET` | Секрет сессии (≥ 32 символа) |
|
||||
| `CSRF_SECRET` | Секрет CSRF (≥ 32 символа) |
|
||||
| `KC_CLIENT_ID` | client_id в Keycloak realm cloud |
|
||||
| `KC_CLIENT_SECRET` | client_secret |
|
||||
| `APP_URL` | Внешний URL приложения (для redirect_uri) |
|
||||
|
||||
### Опциональные
|
||||
|
||||
| Переменная | Default | Описание |
|
||||
|---|---|---|
|
||||
| `KC_BASE_URL` | `https://keycloak.nubes.ru/realms/cloud` | Keycloak realm base URL |
|
||||
| `ADMIN_CLIENT_ID` | `WZ01112` | WZ-номер администратора |
|
||||
| `PORT` | `3000` | HTTP-порт |
|
||||
| `NODE_ENV` | — | `production` включает secure cookie, trust proxy |
|
||||
| `DEV_MODE` | `false` | `true` → /dev-login без пароля |
|
||||
| `DEV_SECRET` | — | Ключ для /dev-login в staging |
|
||||
|
||||
---
|
||||
|
||||
## БД (dev)
|
||||
|
||||
```
|
||||
Host: write.bde8229b-1381-4330-b24b-727ad73fcb44.dev.nubes.ru
|
||||
DB: ipwhitelist
|
||||
User: super
|
||||
```
|
||||
|
||||
Схема: `sql/schema.sql` — `companies`, `whitelist_entries` (soft delete), `audit_log` + индексы.
|
||||
|
||||
---
|
||||
|
||||
## Деплой (текущий)
|
||||
|
||||
- URL: `https://white.nodejsk8s.dev.nubes.ru`
|
||||
- k8s namespace: `whitelist`
|
||||
- Сессии: in-memory (MemoryStore) — **не масштабируется**, нужен Redis при multi-pod
|
||||
- Режим: ожидает KC_CLIENT_ID/KC_CLIENT_SECRET для перехода с mock на OIDC
|
||||
@@ -1,112 +0,0 @@
|
||||
# Дизайн-система платформы Nubes
|
||||
|
||||
> Извлечено из сохранённой страницы `h.h` (deck-test.ngcloud.ru)
|
||||
|
||||
---
|
||||
|
||||
## Сетка и контейнеры
|
||||
|
||||
| Элемент | Класс | Описание |
|
||||
|---|---|---|
|
||||
| Страница | `navigation:flex`, `services:grid` | Flex/grid на всём |
|
||||
| Карточка | `ui-kit:bg-card ui-kit:rounded-xl ui-kit:border ui-kit:shadow-sm` | Белая карточка, border-radius 12px |
|
||||
| Заголовок карточки | `ui-kit:bg-brand-grey-light ui-kit:px-3 ui-kit:py-3` | Серый фон `#f3f4f6`, padding 12px |
|
||||
| Тело карточки | `ui-kit:px-3 ui-kit:py-3` | padding 12px |
|
||||
|
||||
---
|
||||
|
||||
## Цвета (переменные)
|
||||
|
||||
| Переменная | Назначение | Примерный HEX |
|
||||
|---|---|---|
|
||||
| `--brand-primary` | Основной цвет | `#2563eb` (синий) |
|
||||
| `--brand-gray` | Цвет границ | `#d1d5db` |
|
||||
| `--brand-grey-light` | Фон заголовков | `#f3f4f6` |
|
||||
| `--brand-primary-dark` | Ховер ссылок | `#1d4ed8` |
|
||||
|
||||
---
|
||||
|
||||
## Формы
|
||||
|
||||
```
|
||||
form-table (класс services:):
|
||||
grid-template-columns: fit-content(200px) minmax(200px, 1fr) 0px
|
||||
gap: 8px 16px
|
||||
```
|
||||
|
||||
| Элемент | Класс | Стиль |
|
||||
|---|---|---|
|
||||
| Лейбл | `ui-kit:text-sm ui-kit:leading-none ui-kit:font-normal ui-kit:mb-1 ui-kit:ml-1` | 14px, sans-serif, отступ слева |
|
||||
| Инпут | `ui-kit:h-9 ui-kit:rounded-md ui-kit:border ui-kit:px-3 ui-kit:text-sm` | h=36px, border, padding |
|
||||
| Текстареа | `ui-kit:rounded-md ui-kit:border ui-kit:px-3 ui-kit:py-2 ui-kit:text-sm` | авто-height |
|
||||
|
||||
---
|
||||
|
||||
## Кнопки
|
||||
|
||||
| Тип | Стиль | Класс |
|
||||
|---|---|---|
|
||||
| Обычная | border, bg-white, hover:bg-accent | `ui-kit:border ui-kit:bg-background ui-kit:h-8 ui-kit:rounded-md` |
|
||||
| Удалить | bg-destructive, text-white | `ui-kit:bg-destructive ui-kit:text-white` |
|
||||
| Иконка | size-5 | `ui-kit:size-5 ui-kit:cursor-pointer` |
|
||||
|
||||
Все кнопки: `ui-kit:h-8 ui-kit:rounded-md ui-kit:gap-1.5 ui-kit:px-3`, 14px шрифт.
|
||||
|
||||
---
|
||||
|
||||
## Таблицы
|
||||
|
||||
| Элемент | Стиль |
|
||||
|---|---|
|
||||
| Обёртка | `ui-kit:rounded-md ui-kit:border ui-kit:overflow-hidden` |
|
||||
| Шапка (th) | `ui-kit:bg-brand-grey-light`, uppercase, `ui-kit:py-1 ui-kit:px-2`, border-right |
|
||||
| Ячейка (td) | `ui-kit:px-2 ui-kit:py-1`, border-right, border-bottom |
|
||||
| Строка (tr) | `ui-kit:border-b ui-kit:border-brand-gray-8`, hover: `ui-kit:bg-brand-grey-light/50` |
|
||||
|
||||
---
|
||||
|
||||
## Иконки
|
||||
|
||||
Используются: **Lucide** (`lucide-*`)
|
||||
|
||||
Часто используемые:
|
||||
- `lucide-square-pen` — редактировать
|
||||
- `lucide-rotate-cw` — перезапустить
|
||||
- `lucide-trash` — удалить
|
||||
- `lucide-pause` — остановить
|
||||
- `lucide-play` — запустить
|
||||
- `lucide-save` — сохранить
|
||||
- `lucide-x` — закрыть
|
||||
- `lucide-copy` — копировать
|
||||
- `lucide-check` — успех (зелёный)
|
||||
- `lucide-x` — ошибка (красный)
|
||||
|
||||
---
|
||||
|
||||
## Типографика
|
||||
|
||||
- Основной шрифт: system-ui (Segoe UI, Roboto, etc.)
|
||||
- Размер: `text-sm` = 14px, `text-base` = 16px
|
||||
- Межстрочный: `leading-none` (1), `leading-normal` (1.5)
|
||||
- Цвет текста: `#1a1a1a`
|
||||
- Muted: `text-muted-foreground` = серый `#6b7280`
|
||||
- Заголовки карточек: `font-semibold`, 16px
|
||||
|
||||
---
|
||||
|
||||
## Навигация (слева)
|
||||
|
||||
- Ширина: `var(--sidebar-width)` = 12rem (192px)
|
||||
- Свёрнуто: `var(--sidebar-width-icon)` = 4.5rem (72px)
|
||||
- Верхняя панель: h-20 (80px), border-t-4 border-t-blue-500
|
||||
- Лого: инлайн SVG, 150px ширина
|
||||
|
||||
---
|
||||
|
||||
## Состояния
|
||||
|
||||
| Статус | Цвет |
|
||||
|---|---|
|
||||
| Успех / running | `text-green-500` + иконка `lucide-check` |
|
||||
| Ошибка | `text-red-500` + иконка `lucide-x` |
|
||||
| Предупреждение | `text-amber-500` |
|
||||
@@ -1,96 +0,0 @@
|
||||
# IP WhiteList — План (pending задачи, 2026-05-30 14:43)
|
||||
|
||||
> **Ветка:** `sonnet` · **Обновлено:** 2026-05-30 14:43
|
||||
> Всё что было в старых планах — реализовано. Здесь только то, чего ещё нет.
|
||||
|
||||
---
|
||||
|
||||
## Блокер 1 — Keycloak OIDC (нужны данные от DevOps)
|
||||
|
||||
Код OIDC Authorization Code Flow написан и готов (`src/auth.js`).
|
||||
Не активируется пока не получены:
|
||||
|
||||
| Что нужно | Env-переменная | Статус |
|
||||
|---|---|---|
|
||||
| client_id в realm cloud | `KC_CLIENT_ID` | ❌ ждём DevOps |
|
||||
| client_secret | `KC_CLIENT_SECRET` | ❌ ждём DevOps |
|
||||
| redirect_uri зарегистрирован в KK | (в самом KK) | ❌ ждём DevOps |
|
||||
| SESSION_SECRET для продакшена | `SESSION_SECRET` | ❌ нужно сгенерировать |
|
||||
|
||||
Без этого сервис работает в mock-режиме (`/dev-login` как тестовый backdoor).
|
||||
|
||||
---
|
||||
|
||||
## Блокер 2 — Admin-роль из Keycloak
|
||||
|
||||
Сейчас admin = `clientId === WZ01112`.
|
||||
Как реально приходит `isAdmin` из KK — не ясно (см. [questions.md](questions.md)).
|
||||
После получения ответа — 1–2 строки в `userFromPayload()` в `src/auth.js`.
|
||||
|
||||
---
|
||||
|
||||
## Задача 1 — Деплой ветки sonnet
|
||||
|
||||
- [ ] Передеплоить на `white.nodejsk8s.dev.nubes.ru` (сейчас там master)
|
||||
- [ ] Добавить env-секреты в k8s: `SESSION_SECRET`, `CSRF_SECRET`
|
||||
- [ ] Проверить smoke-тест: `/healthz`, `/login`, `/export`
|
||||
|
||||
---
|
||||
|
||||
## Задача 2 — Сессии при multi-pod
|
||||
|
||||
Сейчас: MemoryStore (in-process, не масштабируется).
|
||||
При нескольких репликах k8s сессии будут теряться.
|
||||
|
||||
- [ ] `npm install connect-redis ioredis`
|
||||
- [ ] Подключить Redis в `server.js` (3 строки конфига)
|
||||
- [ ] Добавить `REDIS_URL` в env
|
||||
|
||||
**Блокер:** нужен Redis в k8s (или принять ограничение на 1 реплику пока).
|
||||
|
||||
---
|
||||
|
||||
## Задача 3 — IP-ограничение /export
|
||||
|
||||
ТЗ: endpoint доступен без авторизации, ограничен по IP на старте.
|
||||
Текущее решение: открытый endpoint с rate-limit (20 req/мин).
|
||||
|
||||
- [ ] Уточнить: ограничение в приложении или ingress? (см. [questions.md](questions.md))
|
||||
- [ ] Если в приложении: whitelist IP через `EXPORT_ALLOWED_IPS` env var
|
||||
|
||||
---
|
||||
|
||||
## Задача 4 — Пагинация
|
||||
|
||||
При `custom_limit` > 50 таблица станет неудобной.
|
||||
Сейчас все записи выводятся без пагинации.
|
||||
|
||||
- [ ] `GET /?page=N` + `LIMIT/OFFSET` в `queries.listEntries()`
|
||||
- [ ] Кнопки Назад/Вперёд в `views/index.ejs`
|
||||
|
||||
Низкий приоритет — текущий дефолтный лимит 15 записей на компанию.
|
||||
|
||||
---
|
||||
|
||||
## Задача 5 — Auto-dismiss alert
|
||||
|
||||
Flash-алерты (ошибка/успех) сейчас исчезают только при перезагрузке.
|
||||
|
||||
- [ ] 10 строк JS в `views/index.ejs`: `setTimeout(() => alert.remove(), 5000)`
|
||||
|
||||
---
|
||||
|
||||
## Не делать (закрыто)
|
||||
|
||||
| Что | Почему |
|
||||
|---|---|
|
||||
| ~~CSRF-защита~~ | ✅ csrf-csrf (double-submit cookie) |
|
||||
| ~~Рефакторинг server.js~~ | ✅ 131 строка, роутеры вынесены |
|
||||
| ~~Автотесты~~ | ✅ 50 unit-тестов (`node tests/run-tests.js`) |
|
||||
| ~~aggregateCIDRs в /export~~ | ✅ в `src/validators.js` |
|
||||
| ~~Admin-панель~~ | ✅ `src/routes/admin.js`, `views/admin.ejs` |
|
||||
| ~~Редактирование записи~~ | ✅ `POST /edit/:id` |
|
||||
| ~~Аудит~~ | ✅ `GET /audit` |
|
||||
| ~~OIDC Authorization Code Flow~~ | ✅ `src/auth.js` + `src/routes/auth.js` |
|
||||
| ~~Session middleware~~ | ✅ `express-session` в `server.js` |
|
||||
| ~~Dev-login backdoor~~ | ✅ `/dev-login` с пресетами + кастомные поля |
|
||||
@@ -1,70 +0,0 @@
|
||||
# Вопросы к DevOps / команде Keycloak — IP WhiteList
|
||||
|
||||
> Обновлено: 2026-05-30 14:43
|
||||
> Часть вопросов из первоначального списка закрыта реализацией.
|
||||
|
||||
---
|
||||
|
||||
## ❌ ОТКРЫТЫЕ (блокируют деплой)
|
||||
|
||||
### 1. Данные клиента Keycloak
|
||||
|
||||
Для активации OIDC-режима (код готов, ждёт переменных):
|
||||
|
||||
```
|
||||
KC_CLIENT_ID= ? # наш client_id в realm cloud
|
||||
KC_CLIENT_SECRET= ? # наш client_secret
|
||||
```
|
||||
|
||||
Redirect URI для регистрации в Keycloak:
|
||||
```
|
||||
https://white.nodejsk8s.dev.nubes.ru/callback
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. Admin-роль: как приходит из Keycloak
|
||||
|
||||
**ТЗ:** Администратор = `clientId = WZ01112` + «отдельный чек-бокс».
|
||||
|
||||
Сейчас: `isAdmin = (ClientID === process.env.ADMIN_CLIENT_ID)` — только WZ01112.
|
||||
|
||||
Вопросы:
|
||||
- Есть ли отдельный claim в JWT для признака admin?
|
||||
- Если да — как называется? Примеры: `realm_access.roles`, `resource_access.whitelist.roles`, `is_admin`, `groups`…
|
||||
- Нужна поддержка нескольких adminов или только WZ01112?
|
||||
|
||||
**Если нет отдельного claim** — оставляем текущее решение, закрываем вопрос.
|
||||
|
||||
---
|
||||
|
||||
### 3. /export — IP-ограничение
|
||||
|
||||
ТЗ: «на старте может работать без авторизации (по сетевому ограничению)».
|
||||
|
||||
Сейчас: открытый endpoint, rate-limit 20 req/мин.
|
||||
|
||||
- Ограничение делаем в **приложении** или в **ingress/nginx**?
|
||||
- Если в приложении — список разрешённых IP (env var `EXPORT_ALLOWED_IPS`?).
|
||||
|
||||
---
|
||||
|
||||
## ✅ ЗАКРЫТЫЕ
|
||||
|
||||
| # | Вопрос | Решение |
|
||||
|---|---|---|
|
||||
| DEV_MODE | Нужен ли dev-режим на платформе? | `/dev-login` — backdoor с `DEV_MODE=true` (без пароля) или `DEV_SECRET=xyz` (с ключом). Работает в обоих режимах. |
|
||||
| Email claim | Как называется claim с email? | Берём `payload.email \|\| payload.login \|\| 'unknown'` |
|
||||
| JWT структура | Формат токена auth-api | Изучен из реального токена (см. [DEPRECATED]-auth-architecture.md). Поля: `ClientID`, `company_id`, `company_name`, `email`, `login`. |
|
||||
| CSRF | Защита POST-форм | csrf-csrf (double-submit cookie pattern) |
|
||||
| Session | Хранение пользователя | express-session (httpOnly, sameSite lax, 8ч) |
|
||||
|
||||
---
|
||||
|
||||
## Условные обозначения в коде
|
||||
|
||||
```js
|
||||
// TODO(KK): уточнить формат claim — см. docs/questions.md#2
|
||||
// FIXME(KK): временно, заменить после ответа команды
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user