# Разделение доступа пользователей (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) → доступ только к своим очередям ```