API security: WAF/CDN edge -> API Gateway (AuthN/AuthZ/Schema/RateLimit) -> Services -> DB. Concept lesson with 5 scenarios: happy-path OAuth+rate-limit, schema validation rejecting mass assignment, WAF blocking SQLi, token-bucket rate limit, and OWASP API1 BOLA/IDOR.
API security — это про защиту endpoint'а, который атакующий может звать напрямую (curl, Postman, бот, скрипт). В отличие от веб-приложения, где можно опираться на UI как первый барьер, у API нет client-side проверок: всё валидируется на сервере, default deny на каждом endpoint, schema validation на каждое поле.
OWASP API Security Top 10 за 2023 год меняет акцент: #1 проблема — BOLA (Broken Object Level Authorization), то есть /orders/123 без проверки, что 123 принадлежит вызывающему. Это причина большинства реальных утечек: First American (885M документов), Optus (10M австралийцев), T-Mobile (37M клиентов). Не RCE, не SQLi — банальное «забыли проверить владельца объекта».
API security — это defence in depth: пять слоёв, каждый ловит свой класс атак. Если выбить один слой, остальные удержат.
PATCH /users {is_admin: true} отвергается, потому что is_admin не в схеме.scope=orders:read, но не проверил, что order 99 принадлежит именно Алисе. Лечится в SQL: WHERE id=? AND user_id=? или через политику ReBAC.«Auth check в gateway only» — антипаттерн. Сервис обязан перепроверять object ownership, потому что gateway не знает, чей конкретно order запрашивается.
Топология слоёная, сверху вниз:
user_id из propagation header.Все запросы клиентов идут только через цепочку cdn → waf → rate-limiter → gw → service. Прямых edges «browser → service» нет: это бы означало обход всей защиты. Audit log пишется и из gateway, и из WAF (для security-only событий).
Алиса делает GET /api/orders/42 с валидным Bearer-токеном. Запрос проходит CDN (cache miss), WAF (нет сигнатур атак), edge rate limit (12/1000 запросов от IP — ок). На gateway: AuthN декодирует JWT, дёргает JWKS у IdP (ключи кешируются на 24 часа), AuthZ проверяет, что у Алисы есть scope orders:read, per-user rate limit (8/100 запросов в минуту — ок). Gateway пересылает в orders-svc по mTLS, пропагируя user_id=alice. Сервис делает SQL с WHERE id=42 AND user_id=alice — order действительно её, отдаёт 200 с DTO (только id/total/status, без internal флагов).
Боту удалось украсть JWT-токен валидного пользователя. Он шлёт PATCH /users/123 {name: "x", is_admin: true, balance: 99999}. WAF ничего подозрительного не видит (никаких SQLi-паттернов), edge пропускает, AuthN проверяет токен — валидный. Но schema-валидатор (Zod, описание DTO) отвергает запрос: в схеме только name и email, лишние поля — 400 ошибка. Это API3: Broken Object Property Level Authorization, фикс — никогда не делать db.update(req.body), только db.update(parsed.data) после валидации.
Бот шлёт GET /api/users?id=1 OR 1=1 UNION SELECT password FROM users. WAF (OWASP CRS rule 942100) ловит сигнатуру UNION SELECT ещё на edge, возвращает 403, пишет в audit log с IP. Бот пробует 200 вариантов (hex-encode, комментарии, смена регистра) — ML-модель WAF + behavioral score флагает как бот, дропает. Это virtual patching: WAF блокирует уязвимость, пока команда фиксит код. Managed-правила (Cloudflare, AWS WAF) обновляются провайдером быстрее, чем релизный цикл приложения.
Mobile app начинает грузить /api/feed для Алисы со скоростью 50 req/s. Edge rate limit per-IP пропускает (под 1000/min). На gateway per-user token bucket: 100 токенов, refill 10/s. Бурст из 50 — ок, остаются 50. Скорость растёт до 200 req/s — токены кончились (расход выше refill rate), gateway возвращает 429 с Retry-After: 2s и X-RateLimit-Reset. Token bucket лучше fixed window (последний разрешает удвоенный burst на границе окна). Per-endpoint лимиты обязательны: /login — 5/min (брутфорс), /feed — 100/min.
API1, главная уязвимость 2023 года. Алиса с валидным токеном делает GET /api/orders/99, но order 99 принадлежит Бобу. Gateway: AuthN ок (токен валидный), AuthZ ок (scope=orders:read есть). Forward в orders-svc. Сервис делает SQL WHERE id=99 без AND user_id=? — возвращает чужие данные. Это и есть BOLA: на уровне функции (GET /orders/:id) права есть, на уровне объекта (этот конкретный 99) — нет проверки. Фикс: либо WHERE id=? AND user_id=caller в каждом запросе, либо policy engine (OPA, Cedar, OSO) с ReBAC-моделью.
| Решение | За | Против | Когда |
|---|---|---|---|
| WAF на edge | virtual patching, ML/behavioral, OWASP CRS из коробки | false positives на легитимные кейсы, цена, добавляет latency | всегда для публичного API |
| API Gateway как choke point | единая точка для AuthN/RL/audit, observability | single point of failure, vendor lock-in, $$ | от 3+ микросервисов |
| Schema validation per-endpoint | блокирует mass assignment + excessive data exposure | overhead на разработку, нужно поддерживать DTO | обязательно для всех write endpoints |
| mTLS service-to-service | защита от lateral movement, identity in transit | сложность ротации сертов, debugging | zero-trust networks |
| Object-level authz в сервисе | защита от BOLA, единственный надёжный способ | дублирует логику с AuthZ в gateway | всегда для пользовательских данных |
| Policy engine (OPA/Cedar) | централизованные правила, аудит политик | сложность setup, latency на запрос | enterprise, compliance-heavy |
| API key vs JWT | API key проще, не expire | JWT даёт scope/expiry/refresh | API key — для machine-to-machine + IP allowlist; JWT — для пользователей |
Главное правило: AuthN ≠ AuthZ ≠ Object Authz. Три разных проверки, каждая обязательна. Gateway покрывает первые две, сервис обязан делать третью.
/document/000000075 без проверки владельца — меняешь номер, видишь чужой документ. Классический BOLA на статическом сайте./admin/customer/:id.PATCH /users {public_key: ...}. API3. Привело к появлению attr_accessible в Rails по умолчанию.Все эти кейсы — не нулевой день, не сложная криптография. Это banal «забыли проверить владельца», «забыли отключить admin endpoint», «оставили legacy API».
X-User-ID: alice от клиента. Доверять можно только тем заголовкам, которые выставил твой gateway/sidecar после AuthN./login и /feed нужны разные лимиты. Login — 5/min (брутфорс), feed — 100/min, search — 30/min.Allow-Origin: * + Allow-Credentials: true — спецификация запрещает (браузер игнорирует), но некоторые серверы по ошибке принимают. Явно перечисляй origin'ы.db.users.update(req.body). Атакующий может выставить is_admin: true. Explicit DTO + allowlist полей.API Gateway + полный стек защиты — это инфраструктурный налог. Не нужен:
Главное: не ставь WAF и не получай PCI-сертификацию для блога на 100 запросов в день. Но как только API публичный и хранит чужие данные — все пять слоёв обязательны.