v0.1.16: JWT auth via nubes API, auto-provisioning, UI login

- app/auth/jwt.go: ParseJWTClaims, TenantIDFromSub (sless-compatible SHA256), PingNubesAPI
- app/admin/admin.go: POST /ui/api/auth endpoint, jwtMiddleware for /ui/api/*
- app/tenant/tenant_store.go: NubesSub/Email fields, GetBySub, CreateFromJWT
- app/ui/index.html: login page, email in navbar, JWT session in localStorage
- deployments/k8s/deployment.yaml: v0.1.16, NUBES_ENDPOINT env
- doc/decisions/resource-limits-plan.md: 20 vulnerabilities audit
- Fix: /ui/api/auth moved to subrouter (gorilla/mux PathPrefix conflict)
This commit is contained in:
Naeel
2026-04-10 18:35:19 +03:00
parent 6fb160f8ae
commit b9d434bcc5
9 changed files with 854 additions and 25 deletions
+159
View File
@@ -0,0 +1,159 @@
# Решение: Защита от ресурсного исчерпания (Resource Exhaustion Protection)
**Дата:** 2026-04-10
**Статус:** ПЛАН (на согласовании)
**Автор анализа:** GitHub Copilot (Claude Opus 4.6)
---
## Контекст
Проведён полный аудит shared-sqs на уязвимости типа DoS / resource exhaustion.
Найдено **20 уязвимостей** (2 Critical, 8 High, 8 Medium, 2 Low).
Главная угроза: **один тенант может положить сервис для всех** — через создание огромных очередей, бесконечный long polling, отсутствие валидации размеров.
Кросс-тенантный доступ к данным **невозможен** — изоляция через составной ключ работает корректно.
---
## Найденные уязвимости
### 🔴 CRITICAL
| # | Уязвимость | Где | Как эксплуатировать |
|---|-----------|-----|-------------------|
| 1 | **Публичный Admin API без auth** | `/ui/api/*` | Любой может создавать тенантов, очереди, слать сообщения, удалять данные без токена |
| 2 | **Batch message size bypass** | `send_message_batch.go` | `SendMessageBatchV1` не проверяет размер тела каждого сообщения; 10×256MB = 2.5GB в одном запросе |
### 🟠 HIGH
| # | Уязвимость | Где | AWS лимит |
|---|-----------|-----|-----------|
| 3 | QueueName без валидации | `create_queue.go` | Макс 80 chars, `[a-zA-Z0-9_-]` |
| 4 | WaitTimeSeconds без потолка | `receive_message.go` | 0–20 сек |
| 5 | ReceiveMessageWaitTimeSeconds без потолка | `queue_attributes.go` | 0–20 сек |
| 6 | DelaySeconds без потолка | `queue_attributes.go` | 0–900 сек |
| 7 | VisibilityTimeout без потолка | `queue_attributes.go` | 0–43200 сек |
| 8 | MaxNumberOfMessages без потолка | `receive_message.go` | 1–10 |
| 9 | Message attributes без лимита | `requests.go` | Макс 10, общий размер ≤256KB |
| 10 | Data race в GetQueueUrlV1 | `get_queue_url.go` | Нет RLock перед чтением |
### 🟡 MEDIUM
| # | Уязвимость | Где | Последствие |
|---|-----------|-----|------------|
| 11 | Нет лимита сообщений в очереди | все send handlers | OOM при миллионах сообщений |
| 12 | Нет лимита на создание тенантов | `admin.go` | OOM при 100K тенантов |
| 13 | FIFO group lock без таймаута | `receive_message.go` | Group заблокирован навсегда |
| 14 | Duplicates map без очистки | `models.go` | Утечка памяти |
| 15 | BatchEntryId без валидации длины | `send_message_batch.go` | Раздутые ключи |
| 16 | DeduplicationID без валидации | `send_message_batch.go` | 128 chars макс по AWS |
| 17 | GroupID без валидации | `send_message.go` | 128 chars макс по AWS |
| 18 | Redis serialization без лимита | `redis.go` | Redis OOM при гигантских очередях |
### 🟢 LOW
| # | Уязвимость | Где |
|---|-----------|-----|
| 19 | `{account}` в URL не валидируется | `router.go` |
| 20 | ReceiptHandle не валидируется по формату | `delete_message.go` |
---
## План реализации
### Фаза 1 — КРИТИЧЕСКОЕ (блокирует production)
**Цель:** устранить уязвимости, позволяющие анонимную атаку.
| Задача | Файлы | Сложность | Что делать |
|--------|-------|-----------|-----------|
| 1.1 Защитить публичный API | `admin.go`, `router.go` | Низкая | Добавить опцию: либо bearer auth на `/ui/api/*`, либо убрать write-эндпоинты из public routes, оставив только read |
| 1.2 Валидация размера в batch | `send_message_batch.go` | Низкая | Добавить проверку `len(entry.MessageBody) > queue.MaximumMessageSize` в цикле по entries |
| 1.3 RLock в GetQueueUrl | `get_queue_url.go` | Низкая | Обернуть чтение SyncQueues в `RLock()/RUnlock()` |
### Фаза 2 — AWS-совместимые лимиты (валидация параметров)
**Цель:** привести параметры к стандартам AWS SQS. Создать единый пакет `validation`.
| Задача | Файлы | Что делать |
|--------|-------|-----------|
| 2.1 Валидация QueueName | `create_queue.go` | Макс 80 chars, regex `^[a-zA-Z0-9_-]+(\.fifo)?$` |
| 2.2 WaitTimeSeconds cap | `receive_message.go` | Clamp к 0–20 |
| 2.3 ReceiveMessageWaitTimeSeconds cap | `queue_attributes.go` | Clamp к 0–20 |
| 2.4 DelaySeconds cap | `queue_attributes.go` | Clamp к 0–900 |
| 2.5 VisibilityTimeout cap | `queue_attributes.go` | Clamp к 0–43200 |
| 2.6 MaxNumberOfMessages cap | `receive_message.go` | Clamp к 1–10 |
| 2.7 Message attributes limit | `requests.go`, `send_message.go`, `send_message_batch.go` | Макс 10 атрибутов, общий размер ≤256KB |
| 2.8 DeduplicationID/GroupID length | `send_message.go`, `send_message_batch.go` | Макс 128 chars каждый |
### Фаза 3 — Per-tenant resource limits
**Цель:** один тенант не может выжрать все ресурсы.
| Задача | Файлы | Что делать |
|--------|-------|-----------|
| 3.1 Макс сообщений в очереди | `send_message.go`, `send_message_batch.go` | Лимит per queue (напр. 120,000 — как AWS standard) |
| 3.2 Макс суммарный размер per tenant | `tenant_helpers.go` | Счётчик bytes per tenant; отказ при превышении |
| 3.3 Rate limiting per tenant | Новый middleware | Token bucket или sliding window; напр. 300 req/sec per tenant |
| 3.4 Макс тенантов в системе | `admin.go`, `tenant_store.go` | Глобальный лимит (конфигурируемый) |
| 3.5 HTTP request body size limit | `router.go` или middleware | `http.MaxBytesReader` — напр. 1MB на запрос |
### Фаза 4 — Стабильность и очистка
**Цель:** утечки памяти и deadlock-сценарии.
| Задача | Файлы | Что делать |
|--------|-------|-----------|
| 4.1 FIFO group lock timeout | `models.go`, `receive_message.go` | Таймаут = VisibilityTimeout очереди. Горутина чистит expired locks |
| 4.2 Duplicates map cleanup | `models.go` | Горутина-ticker каждые 30 сек, удаляет записи старше 5 мин |
| 4.3 Redis size guard | `redis.go` | Не сохранять в Redis если `len(data) > 50MB`; логировать warning |
| 4.4 `{account}` валидация | `router.go` или handlers | Проверять что `{account}` == tenant.ID из context |
---
## Порядок действий (рекомендация)
```
Фаза 1 (Critical) → тесты → деплой
↓
Фаза 2 (AWS limits) → тесты → деплой
↓
Фаза 3 (Per-tenant) → тесты → деплой
↓
Фаза 4 (Stability) → тесты → деплой
```
Каждая фаза — отдельный коммит/PR с тестами.
---
## Что НЕ делаем (и почему)
| Отброшено | Причина |
|----------|---------|
| WAF / Nginx rate limit | Overkill для текущего масштаба; лучше in-app |
| Подпись проверки (HMAC) | Сервис эмулирует SQS — подпись не проверяется by design (как LocalStack) |
| Шифрование сообщений at rest | Redis на localhost, не критично на этом этапе |
| Горизонтальное масштабирование | Другая задача; лимиты работают и в single-pod |
---
## AWS SQS лимиты (справка)
| Параметр | AWS лимит |
|----------|----------|
| Queue name length | 80 chars |
| Queue name chars | `[a-zA-Z0-9_-]` (+ `.fifo` суффикс) |
| Message body | 256 KB |
| Message attributes | 10, общий размер ≤ 256 KB |
| MaxNumberOfMessages | 1–10 |
| WaitTimeSeconds | 0–20 |
| DelaySeconds | 0–900 |
| VisibilityTimeout | 0–43200 (12 часов) |
| MessageRetentionPeriod | 60–1,209,600 (14 дней) |
| Messages per queue | ~120,000 in-flight |
| DeduplicationID | 128 chars |
| GroupID | 128 chars |
| Batch size | 10 entries |