Files
SQS-service/doc/architecture/tenant-isolation.md
T

97 lines
4.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Разделение доступа пользователей (Tenant Isolation)
Дата: 2026-04-13
## Аутентификация (два пути)
### 1. AWS SigV4 (для SQS-клиентов)
Стандартная AWS-подпись. В заголовке `Authorization` передаётся `AccessKey` формата `SSAK-{hex}`, по нему находится тенант в `TenantStore`.
- `AccessKey`: `SSAK-{12 hex bytes}`
- `SecretKey`: `{64 hex chars}` (32 random bytes)
- Заголовок: `Authorization: AWS4-HMAC-SHA256 Credential={AccessKeyId}/{date}/{region}/sqs/aws4_request, SignedHeaders=..., Signature=...`
- Presigned URLs тоже поддерживаются (через `X-Amz-Credential` query parameter)
### 2. JWT (для UI-консоли)
Токен от облачной платформы Nubes. При первом входе тенант автоматически создаётся из JWT-клейма `sub`. Возвращаются `access_key` + `secret_key`.
Клеймы JWT:
- `sub` — UUID пользователя (используется для идентификации)
- `email` — email пользователя
- `exp` — время истечения
- `iss` — издатель
Генерация TenantID из JWT: `SHA256(sub) → первые 8 байт → hex → "sless-{16hex}"`.
Валидация подписи JWT не делается (trusted perimeter). Проверяется структура, `exp`, и через `PingNubesAPI()` — принимает ли облачный API этот токен.
## Изоляция очередей
Каждая очередь в памяти хранится с ключом `"{accessKey}:{queueName}"`:
```
Очередь тенанта A: SSAK-aaa:my-queue
Очередь тенанта B: SSAK-bbb:my-queue
```
Все операции (SendMessage, ReceiveMessage, ListQueues и т.д.) строят ключ через `tenantQueueKey(tenant.AccessKey, queueName)` — тенант берётся из контекста запроса. Тенант A физически не может обратиться к ключу `SSAK-bbb:*`, потому что его AccessKey другой.
Файлы:
- `app/gosqs/tenant_helpers.go` — `tenantQueueKey()`, `countTenantQueues()`
- `app/tenant/tenant_store.go` — `TenantStore` с тремя индексами (`byID`, `byAccessKey`, `bySub`)
## TenantStore — три индекса
```
byID map[string]*Tenant — для admin API: GetByID(tenantID)
byAccessKey map[string]*Tenant — для SQS auth: GetByAccessKey(accessKey)
bySub map[string]*Tenant — для JWT auth: GetBySub(sub)
```
## Защита от IDOR в UI
В `jwtMiddleware` (admin.go) проверяется что `tenantID` из URL совпадает с `tenantID` из JWT-токена. Если не совпадает — 403 Forbidden.
## Управление тенантами (admin API)
| Метод | Путь | Назначение | Доступ |
|---|---|---|---|
| POST | `/admin/tenants` | Создать тенанта | Bearer token (admin) |
| GET | `/admin/tenants` | Список тенантов | Bearer или JWT (UI видит только себя) |
| GET | `/admin/tenants/{id}` | Детали тенанта | Bearer или JWT |
| DELETE | `/admin/tenants/{id}` | Удалить тенанта + все очереди | Bearer token (admin) |
| GET | `/admin/tenants/{id}/queues` | Очереди тенанта | Bearer или JWT |
`SecretKey` показывается только при создании. Потом получить нельзя.
## Роутинг и защита эндпоинтов
```
/health → PUBLIC
/metrics → PUBLIC
/admin/** → Bearer token auth
/ui/api/auth → PUBLIC (вход через JWT)
/ui/api/** → JWT auth
/ (SQS API) → AWS SigV4 auth
```
## Схема потока запроса
```
HTTP запрос
↓
AuthMiddleware.extractAccessKeyID()
↓
TenantStore.GetByAccessKey(accessKey)
↓
verifySigV4 (проверка подписи)
↓
context.WithValue(TenantContextKey, tenant)
↓
actionHandler (SendMessage, ReceiveMessage, ...)
↓
tenantQueueKey(tenant.AccessKey, queueName) → доступ только к своим очередям
```