- 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)
160 lines
8.9 KiB
Markdown
160 lines
8.9 KiB
Markdown
# Решение: Защита от ресурсного исчерпания (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 |
|