feat: redesign portal UX and stabilize web session runtime

This commit is contained in:
2026-04-13 08:35:07 +00:00
commit fc46d90194
29 changed files with 3915 additions and 0 deletions

154
README.md Normal file
View 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 и централизованный логинг.