220 lines
7.7 KiB
Markdown
220 lines
7.7 KiB
Markdown
# 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`).
|
||
|
||
## 5.1 Развертывание На Другом Сервере (Gitea)
|
||
|
||
Пример для чистого Ubuntu-сервера.
|
||
|
||
1. Установить Docker + Compose plugin:
|
||
|
||
```bash
|
||
sudo apt-get update
|
||
sudo apt-get install -y ca-certificates curl gnupg
|
||
sudo install -m 0755 -d /etc/apt/keyrings
|
||
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
|
||
sudo chmod a+r /etc/apt/keyrings/docker.gpg
|
||
echo \
|
||
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
|
||
$(. /etc/os-release && echo $VERSION_CODENAME) stable" | \
|
||
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
|
||
sudo apt-get update
|
||
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin git
|
||
sudo usermod -aG docker $USER
|
||
newgrp docker
|
||
```
|
||
|
||
2. Клонировать проект из Gitea:
|
||
|
||
```bash
|
||
git clone http://gitea.ruslan.xyz/ruslan/Stend_mont.git
|
||
cd Stend_mont
|
||
```
|
||
|
||
3. Подготовить конфиг:
|
||
|
||
```bash
|
||
cp .env.example .env
|
||
mkdir -p traefik/letsencrypt
|
||
touch traefik/letsencrypt/acme.json
|
||
chmod 600 traefik/letsencrypt/acme.json
|
||
```
|
||
|
||
4. Отредактировать `.env`:
|
||
|
||
- `PUBLIC_HOST` -> домен нового стенда (например `stend.example.com`)
|
||
- `LETSENCRYPT_EMAIL` -> рабочий email
|
||
- `ADMIN_USERNAME`, `ADMIN_PASSWORD` -> учетка администратора
|
||
- `DATABASE_URL` при необходимости оставить как есть (внутренний docker postgres)
|
||
|
||
5. Собрать и запустить:
|
||
|
||
```bash
|
||
docker compose --profile build-only build kiosk-image rdp-proxy-image universal-runtime-image
|
||
docker compose up -d --build
|
||
```
|
||
|
||
6. Проверка:
|
||
|
||
```bash
|
||
docker compose ps
|
||
docker compose logs -f api traefik
|
||
```
|
||
|
||
Если домен уже смотрит на этот сервер и 80/443 открыты, админка будет доступна по:
|
||
|
||
```text
|
||
https://<PUBLIC_HOST>/admin
|
||
```
|
||
|
||
## 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 и централизованный логинг.
|