fix(mcp): подготовить доступ и отзыв разрешений для приёмки

This commit is contained in:
Denozordec committed 2026-11-03 03:05:24 +07:00
commit ef7bb79d4e
311 files changed
+73119

No files matched your search

+259
View File
@@ -0,0 +1,259 @@
# Развёртывание Auth Portal + Traefik (один Docker Compose)
Один стек: **Traefik** (HTTPS, Let's Encrypt DNS-01 через Cloudflare) + **auth-portal** (API + SPA, SQLite).
Образ приложения: `git.shx.one/denozord/auth-portal` (тот же манифест публикуется как `authportal`)
Runtime: `node:22-alpine`, API + SPA + SQLite в одном процессе.
Пайплайн: [`.gitea/README.md`](../.gitea/README.md), [`docs/releasing.md`](releasing.md).
Публичный URL: `https://auth.shnt.top`
Compose: [`deploy/docker-compose.traefik.yml`](../deploy/docker-compose.traefik.yml)
Env-шаблон: [`deploy/env.traefik.example`](../deploy/env.traefik.example)
Документация Traefik: [Expose Docker](https://doc.traefik.io/traefik/expose/docker/basic/), [ACME DNS challenge](https://doc.traefik.io/traefik/https/acme/).
```
Internet → :80/:443 (Traefik) → auth-portal:8080
↑
Cloudflare DNS TXT (ACME)
```
## Предпосылки
1. Зона домена в Cloudflare (например `shnt.top`).
2. Docker Engine + Compose plugin на VPS.
3. Свободные порты **80** и **443** на хосте (этот стек сам поднимает Traefik).
4. `docker login git.shx.one`.
> Если на сервере уже крутится другой Traefik на 80/443 — либо остановите его, либо смените `TRAEFIK_HTTP_PORT` / `TRAEFIK_HTTPS_PORT` (и проброс снаружи). Два Traefik на одних портах не запустятся.
---
## 1. Cloudflare: токен и DNS
### API-токен
[API Tokens → Create Token](https://dash.cloudflare.com/profile/api-tokens) — шаблон **Edit zone DNS** или Custom:
| Permission | Access |
|------------|--------|
| Zone → DNS | Edit |
| Zone → Zone | Read (желательно) |
**Zone Resources:** Include → Specific zone → ваша зона.
Токен → `CF_DNS_API_TOKEN` в `.env` стека. Не Global API Key, не в Git.
### DNS-запись
| Type | Name | Content | Proxy |
|------|------|---------|-------|
| `A` / `AAAA` | `auth` | IP VPS | **DNS only** (серое облако) |
```bash
dig +short auth.shnt.top A
```
Traefik для сертификата создаёт TXT `_acme-challenge.auth…` через Cloudflare API (HTTP-01 на :80 не обязателен).
---
## 2. Файлы на сервере
```bash
mkdir -p /opt/auth-portal/data
chown -R 1000:1000 /opt/auth-portal/data # контейнер работает от uid 1000 (node)
cd /opt/auth-portal
# из репозитория или curl:
# deploy/docker-compose.traefik.yml → docker-compose.yml
# deploy/env.traefik.example → .env
curl -fsSL -o docker-compose.yml \
https://git.shx.one/denozord/auth-portal/raw/branch/main/deploy/docker-compose.traefik.yml
curl -fsSL -o .env \
https://git.shx.one/denozord/auth-portal/raw/branch/main/deploy/env.traefik.example
nano .env # заполнить секреты
```
### Обязательные переменные в `.env`
| Переменная | Назначение |
|------------|------------|
| `CF_DNS_API_TOKEN` | Cloudflare token для ACME DNS-01 (env контейнера **Traefik**) |
| `LETSENCRYPT_EMAIL` | Email для Let's Encrypt |
| `JWT_SECRET` | HS256; тот же секрет в VPS Tracker / CFDM (`AUTH_JWT_SECRET`) |
| `ADMIN_PASSWORD` | Пароль bootstrap-админа (только при пустой БД) |
| `AUTH_DOMAIN` | Хост в Traefik rule, по умолчанию `auth.shnt.top` |
| `ISSUER` | `https://auth.shnt.top` — совпадает с `AUTH_ISSUER` приложений **и** OIDC issuer |
| `RETURN_TO_ALLOWLIST` | Origins SSO / App Switcher (`.shnt.top`, `https://dns.shnt.top`, …) |
| `OIDC_ISSUER` | Опционально; если пусто — берётся `ISSUER`. Публичный URL IdP для Technitium |
| `OIDC_RSA_PRIVATE_KEY` | Опционально PKCS8 PEM; иначе RSA-ключ в SQLite (`./data`) |
Опционально: `AUTH_IMAGE_TAG`, `TRAEFIK_IMAGE_TAG`, `TRAEFIK_HTTP_PORT`, `TRAEFIK_HTTPS_PORT`.
В production cookie refresh с флагом `Secure` — нужен HTTPS.
### Проверка OIDC после старта
```bash
curl -fsS https://auth.shnt.top/.well-known/openid-configuration | head
curl -fsS https://auth.shnt.top/.well-known/jwks.json | head
```
Админка: `https://auth.shnt.top/admin/oidc` — создать client для Technitium.
Полная инструкция SSO: [integrate-technitium.md](integrate-technitium.md).
---
## 3. Запуск (один compose)
```bash
cd /opt/auth-portal
docker login git.shx.one
docker compose pull
docker compose up -d
docker compose ps
docker compose logs -f --tail=100
```
Сервисы:
| Service | Контейнер | Роль |
|---------|-----------|------|
| `traefik` | `auth-portal-traefik` | :80 → HTTPS, ACME, роутинг |
| `app` | `auth-portal` | приложение на внутренней сети `auth-portal`, порт 8080 |
Первая выдача сертификата обычно 30–90 с.
### Проверка
```bash
curl -fsS https://auth.shnt.top/health
echo | openssl s_client -connect auth.shnt.top:443 -servername auth.shnt.top 2>/dev/null \
| openssl x509 -noout -issuer -dates -subject
docker compose logs traefik 2>&1 | grep -iE 'acme|certificate|cloudflare|error'
```
### Обновление / остановка
```bash
cd /opt/auth-portal
docker compose pull
docker compose up -d
# остановка (тома и ./data сохраняются)
docker compose down
# НЕ делайте down -v без бэкапа — сотрёт ACME (auth_portal_traefik_letsencrypt)
```
> **Переход с root-образа (версии до не-root).** Контейнер теперь работает от
> uid 1000 (`node`). Если `./data` и `app.db` созданы старым контейнером под
> root, при старте будет `SqliteError: SQLITE_READONLY`. Один раз выполнить:
>
> ```bash
> cd /opt/auth-portal
> docker compose down
> sudo chown -R 1000:1000 ./data
> docker compose up -d
> ```
---
## Что внутри compose
- Сеть Docker **`auth-portal`** (внутренняя, создаётся стеком).
- Том **`auth_portal_traefik_letsencrypt`** → `/letsencrypt/acme.json`.
- Том хоста **`./data`** → SQLite портала.
- Labels на `app`: `Host(AUTH_DOMAIN)`, `entrypoints=websecure`, `certresolver=letsencrypt`, backend port `8080`.
- `CF_DNS_API_TOKEN` только у сервиса `traefik`.
Полный файл: [`deploy/docker-compose.traefik.yml`](../deploy/docker-compose.traefik.yml).
---
## Связка с приложениями
### JWT apps (CFDM / VPS / BGP / FW)
```env
AUTH_REQUIRED=true
AUTH_JWT_SECRET=<тот же JWT_SECRET>
AUTH_ISSUER=https://auth.shnt.top
AUTH_PORTAL_URL=https://auth.shnt.top
```
UI:
```env
VITE_AUTH_ENABLED=true
VITE_AUTH_PORTAL_URL=https://auth.shnt.top
```
См. [integrate-vps-tracker.md](integrate-vps-tracker.md), [integrate-cfdm.md](integrate-cfdm.md), [integrate-evobgp.md](integrate-evobgp.md), [integrate-evofirewall.md](integrate-evofirewall.md), [integrate-cdnmanager.md](integrate-cdnmanager.md), [integrate-mikrotikmanager.md](integrate-mikrotikmanager.md).
Logout SSO: `https://auth.shnt.top/logout`.
### Technitium DNS (OIDC)
Portal — IdP; Technitium — RP. После `docker compose up`:
1. В `.env` добавьте origin DNS в `RETURN_TO_ALLOWLIST` (например `https://dns.shnt.top`).
2. `curl https://auth.shnt.top/.well-known/openid-configuration` — должен отвечать JSON.
3. Admin → OIDC-клиенты → redirect `https://dns.shnt.top/sso/callback`.
4. В Technitium SSO: Metadata = `https://auth.shnt.top/.well-known/openid-configuration`.
Важно: Technitium резолвит IdP **своим** DNS — A/AAAA для `auth.shnt.top` должна быть видна с DNS-сервера.
Детали: [integrate-technitium.md](integrate-technitium.md).
---
## Бэкап
```bash
# SQLite: согласованная онлайн-копия (WAL учитывается, сервис не останавливается),
# проверка integrity_check и ротация (BACKUP_KEEP, по умолчанию 7) → /data/backups/
docker exec auth-portal node dist/server.js backup
# Либо встроенный планировщик: BACKUP_INTERVAL_HOURS=24 в .env (0 = выключен).
# Каталог: BACKUP_DIR (по умолчанию <каталог БД>/backups). Не копируйте app.db
# через `cp` на живой БД — без -wal/-shm копия может быть неполной.
# ACME
docker run --rm -v auth_portal_traefik_letsencrypt:/data -v "$PWD:/backup" alpine \
tar czf /backup/traefik-acme-$(date +%F).tgz -C /data .
```
**Восстановление:** `docker compose stop app`, затем в `./data` удалить
`app.db-wal`/`app.db-shm`, скопировать нужный `backups/app-*.db` как `app.db`,
`chown 1000:1000`, `docker compose up -d`. OIDC-ключ подписи хранится в этой же
БД, если не задан `OIDC_RSA_PRIVATE_KEY` — храните ключ отдельным секретом.
Откат образа: в `.env` `AUTH_IMAGE_TAG=vX.Y.Z` → `docker compose pull && docker compose up -d`.
---
## Troubleshooting
| Симптом | Что проверить |
|---------|----------------|
| `Bind for 0.0.0.0:80/443 failed` | Другой Traefik/nginx занимает порты |
| ACME / нет HTTPS | `CF_DNS_API_TOKEN`, права Zone:DNS:Edit, логи `docker compose logs traefik` |
| `invalid credentials` | Не Global Key; зона в scope токена; пробелы в `.env` |
| Gateway Timeout / 404 | `docker compose ps`; labels; сеть `auth-portal` |
| Login OK, SSO в app падает | `JWT_SECRET` / `ISSUER` |
| `return_to` rejected | `RETURN_TO_ALLOWLIST` |
| Cookie не держится | HTTPS; `NODE_ENV=production` |
| Technitium «Failed to reach SSO provider» | A/AAAA `auth.*` в самом Technitium; `curl` с хоста DNS к discovery URL |
| OIDC discovery 404 | образ без OIDC; `ISSUER`/`OIDC_ISSUER` = публичный HTTPS URL; Traefik Host |
```bash
docker compose logs traefik 2>&1 | grep -iE 'acme|certificate|cloudflare|error'
docker compose logs app --tail=50
```
---
## Альтернатива: Docker CLI (без compose)
Если нужен ручной запуск — создайте сеть и два контейнера с теми же env/labels, что в compose. Предпочтительный путь — **один `docker compose up -d`** выше.