docs: актуализация (2026-05-30)
Deprecated (описывали состояние до рефакторинга): [DEPRECATED]-plan.md [DEPRECATED]-analysis-2026-05-30.md [DEPRECATED]-auth-architecture.md Новые/обновлённые: architecture.md — текущее устройство: стек, модули, auth-схема, env, маршруты plan.md — только pending задачи (блокеры KK, деплой, Redis, пагинация) questions.md — закрытые вопросы отмечены, открытые: KK creds + admin claim + /export IP
This commit is contained in:
@@ -2,6 +2,42 @@
|
|||||||
Микросервис управления доверенными адресами клиентов
|
Микросервис управления доверенными адресами клиентов
|
||||||
Self-service портал для указания клиентами доверенных IPv4-адресов и подсетей,
|
Self-service портал для указания клиентами доверенных IPv4-адресов и подсетей,
|
||||||
исключаемых из блокировки на стороне облачного провайдера во время DDoS-атак
|
исключаемых из блокировки на стороне облачного провайдера во время DDoS-атак
|
||||||
|
|
||||||
|
───────────────────────────────────────────────────────────────
|
||||||
|
КРАТКОЕ ОПИСАНИЕ (пояснение к реализации)
|
||||||
|
───────────────────────────────────────────────────────────────
|
||||||
|
Сервис даёт клиентам облачного провайдера личный кабинет, где они сами указывают
|
||||||
|
свои доверенные IPv4-адреса и подсети. Эти адреса провайдер не блокирует во время
|
||||||
|
DDoS-атак — так легитимный трафик клиента не попадает под ложные срабатывания
|
||||||
|
фильтрации.
|
||||||
|
|
||||||
|
Что реализуется:
|
||||||
|
|
||||||
|
Личный кабинет клиента. Клиент входит через привычную авторизацию (Keycloak),
|
||||||
|
видит свой список доверенных адресов и управляет им сам: добавляет, редактирует,
|
||||||
|
удаляет записи с комментариями. Если пользователь работает с несколькими
|
||||||
|
компаниями — переключается между ними.
|
||||||
|
|
||||||
|
Проверка вводимых данных. Форма принимает только корректные IPv4-адреса и подсети,
|
||||||
|
отклоняет «серые» и служебные диапазоны, не допускает дубликатов и пересечений
|
||||||
|
внутри одной компании.
|
||||||
|
|
||||||
|
Ограничение по количеству. На компанию по умолчанию 15 записей. Лимит
|
||||||
|
настраивается глобально, а для отдельной компании администратор может поднять или
|
||||||
|
опустить его индивидуально.
|
||||||
|
|
||||||
|
Режим администратора (сетевые инженеры провайдера). Единое окно, где видны записи
|
||||||
|
всех компаний, с фильтрами и доступом к истории изменений. Администратор управляет
|
||||||
|
лимитами и при необходимости любыми записями.
|
||||||
|
|
||||||
|
История изменений (аудит). Каждое создание, изменение и удаление фиксируется: кто,
|
||||||
|
когда, что именно изменил. Удаление — логическое, данные физически сохраняются.
|
||||||
|
|
||||||
|
Выдача для систем фильтрации. Отдельный адрес, по которому системы защиты
|
||||||
|
автоматически забирают итоговый сводный список всех доверенных адресов (одним
|
||||||
|
txt-файлом, по строке на запись). Адреса при этом схлопываются в компактный общий
|
||||||
|
перечень.
|
||||||
|
───────────────────────────────────────────────────────────────
|
||||||
1. Назначение и цели
|
1. Назначение и цели
|
||||||
1.1. Назначение
|
1.1. Назначение
|
||||||
Микросервис предоставляет клиентам облачного провайдера web-интерфейс для самостоятельного управления списком доверенных IPv4-адресов и подсетей. Записи из этого списка исключаются из автоматической блокировки сетевого взаимодействия системами фильтрации и митигации провайдера, что снижает количество ложноположительных срабатываний для легитимного трафика клиента.
|
Микросервис предоставляет клиентам облачного провайдера web-интерфейс для самостоятельного управления списком доверенных IPv4-адресов и подсетей. Записи из этого списка исключаются из автоматической блокировки сетевого взаимодействия системами фильтрации и митигации провайдера, что снижает количество ложноположительных срабатываний для легитимного трафика клиента.
|
||||||
|
|||||||
@@ -0,0 +1,107 @@
|
|||||||
|
# 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 — решить при деплое
|
||||||
@@ -0,0 +1,169 @@
|
|||||||
|
# IP WhiteList — Архитектура (актуально, 2026-05-30)
|
||||||
|
|
||||||
|
> Ветка `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
|
||||||
+67
-78
@@ -1,107 +1,96 @@
|
|||||||
# IP WhiteList — План разработки
|
# IP WhiteList — План (pending задачи, 2026-05-30)
|
||||||
|
|
||||||
> **Дата:** 2026-05-30
|
> **Ветка:** `sonnet`
|
||||||
> **Основание:** [WhiteIPlist.txt](../WhiteIPlist.txt) (ТЗ) + фактический код в [ipwhitelist-app](../../ipwhitelist-app)
|
> Всё что было в старых планах — реализовано. Здесь только то, чего ещё нет.
|
||||||
> **Статус:** каноничный — единственный актуальный план
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 1. Текущее состояние
|
## Блокер 1 — Keycloak OIDC (нужны данные от DevOps)
|
||||||
|
|
||||||
### Готово ✅
|
Код OIDC Authorization Code Flow написан и готов (`src/auth.js`).
|
||||||
|
Не активируется пока не получены:
|
||||||
|
|
||||||
| Слой | Файлы | Что есть |
|
| Что нужно | Env-переменная | Статус |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| БД | `sql/schema.sql` | `companies`, `whitelist_entries` (soft delete), `audit_log` + индексы |
|
| client_id в realm cloud | `KC_CLIENT_ID` | ❌ ждём DevOps |
|
||||||
| Валидатор | `src/validators.js` | Все 14 запрещённых диапазонов Приложения А, /22–/32, нормализация host-битов, проверка пересечений |
|
| client_secret | `KC_CLIENT_SECRET` | ❌ ждём DevOps |
|
||||||
| CRUD | `src/queries.js` | `createEntry`, `updateEntry`, `deleteEntry` (soft), `listEntries`, `getExportCIDRs`, `getAudit`, `getLimit` |
|
| redirect_uri зарегистрирован в KK | (в самом KK) | ❌ ждём DevOps |
|
||||||
| UI | `views/index.ejs` | Список, форма добавления, кнопка удаления, стиль Nubes |
|
| SESSION_SECRET для продакшена | `SESSION_SECRET` | ❌ нужно сгенерировать |
|
||||||
| Роуты | `server.js` | `GET /`, `POST /add`, `POST /delete/:id`, `GET /export`, `GET /healthz` |
|
|
||||||
|
|
||||||
### Написано в queries.js, но не подключено к роутам
|
Без этого сервис работает в mock-режиме (`/dev-login` как тестовый backdoor).
|
||||||
|
|
||||||
- `updateEntry` — нет `POST /update/:id`, нет UI редактирования
|
|
||||||
- `getAudit` — нет страницы аудита
|
|
||||||
- `listEntries(companyId, includeDeleted=true)` — флаг есть, не используется
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 2. Что требует ТЗ, но отсутствует
|
## Блокер 2 — Admin-роль из Keycloak
|
||||||
|
|
||||||
| # | Требование | Готовность |
|
Сейчас admin = `clientId === WZ01112`.
|
||||||
|---|---|---|
|
Как реально приходит `isAdmin` из KK — не ясно (см. [questions.md](questions.md)).
|
||||||
| 1 | Редактирование записи из UI | ❌ queries есть, роута нет |
|
После получения ответа — 1–2 строки в `userFromPayload()` в `src/auth.js`.
|
||||||
| 2 | Админ-панель (все компании, лимиты, аудит, фильтры) | ❌ |
|
|
||||||
| 3 | OIDC Keycloak вместо base64-заглушки | ❌ |
|
|
||||||
| 4 | Суммаризация CIDR в `/export` | ❌ отдаёт сырой список |
|
|
||||||
| 5 | Клиентская валидация (JS в форме) | ❌ |
|
|
||||||
| 6 | Переключатель компаний (multi-company) | ❌ |
|
|
||||||
| 7 | Просмотр soft-deleted записей админом | ❌ |
|
|
||||||
| 8 | Изменение `custom_limit` для компании | ❌ |
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 3. Порядок реализации
|
## Задача 1 — Деплой ветки sonnet
|
||||||
|
|
||||||
### Этап 1 — Пользовательский сценарий (client-flow)
|
- [ ] Передеплоить на `white.nodejsk8s.dev.nubes.ru` (сейчас там master)
|
||||||
|
- [ ] Добавить env-секреты в k8s: `SESSION_SECRET`, `CSRF_SECRET`
|
||||||
- [x] Валидатор IPv4/CIDR — полный
|
- [ ] Проверить smoke-тест: `/healthz`, `/login`, `/export`
|
||||||
- [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. Что НЕ делать
|
## Задача 2 — Сессии при multi-pod
|
||||||
|
|
||||||
- ❌ Не переписывать на Python/FastAPI
|
Сейчас: MemoryStore (in-process, не масштабируется).
|
||||||
- ❌ Не менять схему БД
|
При нескольких репликах k8s сессии будут теряться.
|
||||||
- ❌ Не переписывать `validators.js` (он полный)
|
|
||||||
- ❌ Не переписывать `queries.js`
|
- [ ] `npm install connect-redis ioredis`
|
||||||
- ❌ Не делать SPA
|
- [ ] Подключить Redis в `server.js` (3 строки конфига)
|
||||||
|
- [ ] Добавить `REDIS_URL` в env
|
||||||
|
|
||||||
|
**Блокер:** нужен Redis в k8s (или принять ограничение на 1 реплику пока).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 5. Зависимости для установки
|
## Задача 3 — IP-ограничение /export
|
||||||
|
|
||||||
```
|
ТЗ: endpoint доступен без авторизации, ограничен по IP на старте.
|
||||||
npm install cidr-tools openid-client
|
Текущее решение: открытый endpoint с rate-limit (20 req/мин).
|
||||||
npm install --save-dev jest supertest
|
|
||||||
```
|
|
||||||
|
|
||||||
Всё остальное — в рамках Express + EJS + pg.
|
- [ ] Уточнить: ограничение в приложении или ingress? (см. [questions.md](questions.md))
|
||||||
|
- [ ] Если в приложении: whitelist IP через `EXPORT_ALLOWED_IPS` env var
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 6. Риски
|
## Задача 4 — Пагинация
|
||||||
|
|
||||||
- **Admin-роль:** неясно как «отдельный чек-бокс» из ТЗ попадает в токен — требует уточнения с командой Keycloak
|
При `custom_limit` > 50 таблица станет неудобной.
|
||||||
- **Multi-company claims:** ТЗ говорит о нескольких компаниях, но в claims только `clientID` — нужен реальный формат
|
Сейчас все записи выводятся без пагинации.
|
||||||
- **IP-ограничение `/export`:** делать в приложении или на уровне ingress — решить при деплое
|
|
||||||
|
- [ ] `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` с пресетами + кастомные поля |
|
||||||
|
|||||||
+41
-51
@@ -1,80 +1,70 @@
|
|||||||
# Вопросы для уточнения — IP WhiteList
|
# Вопросы к DevOps / команде Keycloak — IP WhiteList
|
||||||
|
|
||||||
> Дата: 2026-05-30
|
> Обновлено: 2026-05-30
|
||||||
> Кому: команда Keycloak / Nubes
|
> Часть вопросов из первоначального списка закрыта реализацией.
|
||||||
> Статус: ждём ответов
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 1. Admin-роль: «отдельный чек-бокс»
|
## ❌ ОТКРЫТЫЕ (блокируют деплой)
|
||||||
|
|
||||||
**ТЗ:** Администратор = `clientId = WZ01112` + отдельный чек-бокс.
|
### 1. Данные клиента Keycloak
|
||||||
|
|
||||||
**Вопросы:**
|
Для активации OIDC-режима (код готов, ждёт переменных):
|
||||||
- В каком claim приходит этот чек-бокс? (realm_role, client_role, group, attribute?)
|
|
||||||
- Как точно называется claim? Примеры возможных значений:
|
|
||||||
- `"roles": ["whitelist-admin"]`
|
|
||||||
- `"resource_access.whitelist.roles": ["admin"]`
|
|
||||||
- `"is_admin": true`
|
|
||||||
- Нужна ли поддержка нескольких админов (не только WZ01112)?
|
|
||||||
|
|
||||||
**Как обойти:** захардкодить `clientId === 'WZ01112'` как признак админа, добавить `TODO` с ссылкой на этот файл.
|
```
|
||||||
|
KC_CLIENT_ID= ? # наш client_id в realm cloud
|
||||||
|
KC_CLIENT_SECRET= ? # наш client_secret
|
||||||
|
```
|
||||||
|
|
||||||
|
Redirect URI для регистрации в Keycloak:
|
||||||
|
```
|
||||||
|
https://white.nodejsk8s.dev.nubes.ru/callback
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 2. Multi-company: формат claims
|
### 2. Admin-роль: как приходит из Keycloak
|
||||||
|
|
||||||
**ТЗ:** Пользователь может принадлежать нескольким компаниям, в UI — переключатель.
|
**ТЗ:** Администратор = `clientId = WZ01112` + «отдельный чек-бокс».
|
||||||
|
|
||||||
**Вопросы:**
|
Сейчас: `isAdmin = (ClientID === process.env.ADMIN_CLIENT_ID)` — только WZ01112.
|
||||||
- `clientID` в токене — это строка или массив строк?
|
|
||||||
- Если массив — как называется claim? (`clientIDs`, `groups`, что-то ещё?)
|
|
||||||
- Есть ли claim с названием компании (для отображения в переключателе)?
|
|
||||||
- Пример реального payload токена (без секретов) для пользователя с 2+ компаниями.
|
|
||||||
|
|
||||||
**Как обойти:** всегда считать `clientId` строкой (один клиент), переключатель не делать, добавить `TODO`.
|
Вопросы:
|
||||||
|
- Есть ли отдельный claim в JWT для признака admin?
|
||||||
|
- Если да — как называется? Примеры: `realm_access.roles`, `resource_access.whitelist.roles`, `is_admin`, `groups`…
|
||||||
|
- Нужна поддержка нескольких adminов или только WZ01112?
|
||||||
|
|
||||||
|
**Если нет отдельного claim** — оставляем текущее решение, закрываем вопрос.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 3. IP-ограничение /export
|
### 3. /export — IP-ограничение
|
||||||
|
|
||||||
**ТЗ:** Endpoint экспорта «на старте может работать без авторизации (по сетевому ограничению)».
|
ТЗ: «на старте может работать без авторизации (по сетевому ограничению)».
|
||||||
|
|
||||||
**Вопросы:**
|
Сейчас: открытый endpoint, rate-limit 20 req/мин.
|
||||||
- Ограничение делаем в приложении или на уровне ingress (nginx/traefik)?
|
|
||||||
- Если в приложении — где взять список разрешённых IP? (env var, файл, БД?)
|
|
||||||
- Если ingress — кто настраивает?
|
|
||||||
|
|
||||||
**Как обойти:** оставить `/export` открытым, добавить `TODO`.
|
- Ограничение делаем в **приложении** или в **ingress/nginx**?
|
||||||
|
- Если в приложении — список разрешённых IP (env var `EXPORT_ALLOWED_IPS`?).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 4. Email пользователя
|
## ✅ ЗАКРЫТЫЕ
|
||||||
|
|
||||||
**ТЗ:** `email` — идентификация пользователя для аудита.
|
| # | Вопрос | Решение |
|
||||||
|
|---|---|---|
|
||||||
**Вопросы:**
|
| DEV_MODE | Нужен ли dev-режим на платформе? | `/dev-login` — backdoor с `DEV_MODE=true` (без пароля) или `DEV_SECRET=xyz` (с ключом). Работает в обоих режимах. |
|
||||||
- Как называется claim с email? (`email`, `preferred_username`, что-то ещё?)
|
| 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) |
|
||||||
**Как обойти:** брать `payload.email` с fallback на `payload.sub`, добавить `TODO`.
|
| Session | Хранение пользователя | express-session (httpOnly, sameSite lax, 8ч) |
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. DEV_MODE
|
|
||||||
|
|
||||||
**Вопросы:**
|
|
||||||
- Нужен ли dev-режим на платформе Nubes (не локально)?
|
|
||||||
- Или всегда только реальный Keycloak?
|
|
||||||
|
|
||||||
**Как обойти:** оставить `DEV_MODE=true` с проверкой что в production падает при включении.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Условные обозначения в коде
|
## Условные обозначения в коде
|
||||||
|
|
||||||
|
```js
|
||||||
|
// TODO(KK): уточнить формат claim — см. docs/questions.md#2
|
||||||
|
// FIXME(KK): временно, заменить после ответа команды
|
||||||
```
|
```
|
||||||
// TODO(Keycloak): уточнить формат claim — см. docs/questions.md#1
|
|
||||||
// FIXME(Keycloak): временно, заменить после ответа команды
|
|
||||||
// HACK(DEV_MODE): заглушка, убрать перед продакшеном
|
|
||||||
```
|
|
||||||
|
|||||||
Reference in New Issue
Block a user