feat: redesign portal UX and stabilize web session runtime
This commit is contained in:
154
README.md
Normal file
154
README.md
Normal file
@@ -0,0 +1,154 @@
|
||||
# Portal Stand Access (MVP)
|
||||
|
||||
MVP-портал доступа к стендам в Proxmox через единый вход `https://stend.4mont.ru`.
|
||||
|
||||
## 1. Архитектура
|
||||
|
||||
- `traefik`: входная точка TLS, маршрутизация на API и динамические сессионные контейнеры.
|
||||
- `api` (FastAPI): auth, ACL, админ API, создание runtime-сессий, cleanup.
|
||||
- `db` (PostgreSQL): пользователи, сервисы, ACL, сессии, аудит.
|
||||
- `portal-kiosk` image: Chromium kiosk + noVNC (для WEB target).
|
||||
- `portal-rdp-proxy` image: xfreerdp + websockify/noVNC proxy до удаленного RDP host:port.
|
||||
- `portal-universal-runtime` image: универсальный runtime (WEB/RDP), используется общим warm pool `UNIVERSAL_POOL_SIZE`.
|
||||
- Опциональный prewarm pool:
|
||||
- глобальный fallback `PREWARM_POOL_SIZE`;
|
||||
- приоритетный `warm_pool_size` на каждом сервисе в админке.
|
||||
|
||||
Поток:
|
||||
1. Пользователь логинится на `/`.
|
||||
2. Dashboard показывает разрешённые сервисы.
|
||||
3. Клик по `/go/<slug>` -> ACL check + проверка `expires_at` + создание записи `sessions` + старт отдельного контейнера.
|
||||
4. Пользователь редиректится на `/s/<session_id>/`.
|
||||
5. Traefik отправляет `/s/<session_id>/...` в конкретный runtime контейнер по dynamic labels.
|
||||
6. Runtime страница шлёт heartbeat в `/api/sessions/<session_id>/touch`.
|
||||
7. Фоновый cleanup завершает сессии при idle > 30 минут.
|
||||
|
||||
При prewarm:
|
||||
- создаются warm-контейнеры с маршрутом `/svc/<slug>/`;
|
||||
- клик по плитке использует prewarmed runtime без задержки cold start;
|
||||
- сессии в БД создаются, но контейнеры остаются пулом (без стопа на каждую сессию).
|
||||
- в `/admin` есть:
|
||||
- отдельные разделы Users / WEB / RDP (список + форма выбранной записи);
|
||||
- `pool size` на сервис;
|
||||
- кнопка `Prewarm now`;
|
||||
- health `running/desired` по каждому пулу.
|
||||
- авто-генерация `slug` из названия (поддержка кириллицы -> латиница).
|
||||
|
||||
## 2. Схема БД
|
||||
|
||||
Основные таблицы:
|
||||
- `users`
|
||||
- `services`
|
||||
- `user_service_access`
|
||||
- `sessions`
|
||||
|
||||
Дополнительно:
|
||||
- `audit_logs`
|
||||
|
||||
SQL-схема: `scripts/schema.sql`.
|
||||
|
||||
## 3. Безопасность
|
||||
|
||||
- Пароли: `argon2` (`passlib[argon2]`).
|
||||
- Cookie auth: `HttpOnly`, `Secure`, `SameSite=Strict`.
|
||||
- CSRF:
|
||||
- формы (`/login`) через hidden token + cookie;
|
||||
- admin JSON API через `X-CSRF-Token`.
|
||||
- Проверки при каждом запросе:
|
||||
- пользователь `active=true`;
|
||||
- `expires_at > now()`;
|
||||
- ACL на `/go/<slug>`.
|
||||
- Аудит: события входа и создания сессий в `audit_logs`.
|
||||
|
||||
## 4. Файлы
|
||||
|
||||
- `docker-compose.yml`
|
||||
- `traefik/traefik.yml`
|
||||
- `traefik/dynamic/security.yml`
|
||||
- `app/main.py`
|
||||
- `kiosk/Dockerfile`, `kiosk/entrypoint.sh`
|
||||
- `rdp-proxy/Dockerfile`, `rdp-proxy/entrypoint.sh`
|
||||
|
||||
## 5. Запуск
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
mkdir -p traefik/letsencrypt
|
||||
touch traefik/letsencrypt/acme.json
|
||||
chmod 600 traefik/letsencrypt/acme.json
|
||||
```
|
||||
|
||||
Собрать runtime образы:
|
||||
|
||||
```bash
|
||||
docker compose --profile build-only build kiosk-image rdp-proxy-image universal-runtime-image
|
||||
```
|
||||
|
||||
Поднять систему:
|
||||
|
||||
```bash
|
||||
docker compose up -d --build
|
||||
```
|
||||
|
||||
Проверка:
|
||||
|
||||
```bash
|
||||
docker compose ps
|
||||
docker compose logs -f api traefik
|
||||
```
|
||||
|
||||
Дефолтный админ берётся из `.env` (`ADMIN_USERNAME`, `ADMIN_PASSWORD`).
|
||||
|
||||
## 6. Примеры admin API
|
||||
|
||||
1) Создать сервис WEB:
|
||||
|
||||
```bash
|
||||
curl -k -X POST "https://stend.4mont.ru/api/admin/services" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "X-CSRF-Token: <csrf_from_cookie>" \
|
||||
-H "Cookie: portal_auth=<cookie>; csrf_token=<csrf>" \
|
||||
-d '{"name":"CRM","slug":"crm","type":"WEB","target":"http://192.168.1.10:3000","active":true}'
|
||||
```
|
||||
|
||||
2) Создать сервис RDP:
|
||||
|
||||
```bash
|
||||
curl -k -X POST "https://stend.4mont.ru/api/admin/services" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "X-CSRF-Token: <csrf_from_cookie>" \
|
||||
-H "Cookie: portal_auth=<cookie>; csrf_token=<csrf>" \
|
||||
-d '{"name":"Windows Desktop","slug":"win-rdp1","type":"RDP","target":"192.168.1.60:3389","active":true}'
|
||||
```
|
||||
|
||||
Для RDP `target` также поддерживает креды и параметры:
|
||||
|
||||
```text
|
||||
rdp://user:password@192.168.1.60:3389?domain=AD&sec=nla
|
||||
```
|
||||
|
||||
3) Создать пользователя:
|
||||
|
||||
```bash
|
||||
curl -k -X POST "https://stend.4mont.ru/api/admin/users" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "X-CSRF-Token: <csrf_from_cookie>" \
|
||||
-H "Cookie: portal_auth=<cookie>; csrf_token=<csrf>" \
|
||||
-d '{"username":"user1","password":"Passw0rd!","expires_at":"2026-12-31T23:59:59+00:00","active":true}'
|
||||
```
|
||||
|
||||
4) Назначить ACL:
|
||||
|
||||
```bash
|
||||
curl -k -X PUT "https://stend.4mont.ru/api/admin/users/2/acl" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "X-CSRF-Token: <csrf_from_cookie>" \
|
||||
-H "Cookie: portal_auth=<cookie>; csrf_token=<csrf>" \
|
||||
-d '{"service_ids":[1,2]}'
|
||||
```
|
||||
|
||||
## 7. Ограничения MVP
|
||||
|
||||
- Нет отдельной UI-админки (есть admin API).
|
||||
- TTL неактивности основан на heartbeat runtime-страницы.
|
||||
- Для production стоит добавить Alembic-миграции, rate limiting и централизованный логинг.
|
||||
Reference in New Issue
Block a user