docs: перенос всей документации в ipwhitelist-app

This commit is contained in:
“Naeel”
2026-05-30 19:11:36 +03:00
parent 53d3901239
commit 21eea523d5
33 changed files with 0 additions and 67937 deletions
-156
View File
@@ -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
-112
View File
@@ -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`
-95
View File
@@ -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
-131
View File
@@ -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 достаточно для ТЗ
-55
View File
@@ -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)
Всё остальное будет реализовано в рамках существующего стека.
-552
View File
@@ -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, лимитов, ролей и аудита. План должен защищать именно эти части, а не раздувать стек.
-188
View File
@@ -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`. Без пересборки.
-103
View File
@@ -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)
- Тонкая настройка прав
-107
View File
@@ -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 — решить при деплое
-169
View File
@@ -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
-112
View File
@@ -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` |
-96
View File
@@ -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` с пресетами + кастомные поля |
-70
View File
@@ -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): временно, заменить после ответа команды
```