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