Files
SQS-service/doc/decisions/resource-limits-plan.md
T
Naeel b9d434bcc5 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)
2026-04-10 18:35:19 +03:00

160 lines
8.9 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.
# Решение: Защита от ресурсного исчерпания (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` | 020 сек |
| 5 | ReceiveMessageWaitTimeSeconds без потолка | `queue_attributes.go` | 020 сек |
| 6 | DelaySeconds без потолка | `queue_attributes.go` | 0900 сек |
| 7 | VisibilityTimeout без потолка | `queue_attributes.go` | 043200 сек |
| 8 | MaxNumberOfMessages без потолка | `receive_message.go` | 110 |
| 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 к 020 |
| 2.3 ReceiveMessageWaitTimeSeconds cap | `queue_attributes.go` | Clamp к 020 |
| 2.4 DelaySeconds cap | `queue_attributes.go` | Clamp к 0900 |
| 2.5 VisibilityTimeout cap | `queue_attributes.go` | Clamp к 043200 |
| 2.6 MaxNumberOfMessages cap | `receive_message.go` | Clamp к 110 |
| 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 | 110 |
| WaitTimeSeconds | 020 |
| DelaySeconds | 0900 |
| VisibilityTimeout | 043200 (12 часов) |
| MessageRetentionPeriod | 601,209,600 (14 дней) |
| Messages per queue | ~120,000 in-flight |
| DeduplicationID | 128 chars |
| GroupID | 128 chars |
| Batch size | 10 entries |