Перейти к содержанию

Безопасность

Безопасность

Безопасность распределённой системы — это не периметр, а сеть. В монолите доверие заканчивается на входной двери: прошёл аутентификацию — дальше внутри все свои. В микросервисах «внутри» нет. Десятки сервисов ходят друг к другу по сети, которая по умолчанию враждебна.

Главная идея: identity ездит с запросом до самого низа, и каждый сервис проверяет её сам.


AuthN vs AuthZ — не путай

Два разных вопроса, которые часто валят в кучу.

  • AuthN (authentication) — кто ты. Доказательство личности.
  • AuthZ (authorization) — что тебе можно. Проверка прав на конкретное действие.

AuthN делаешь один раз на входе. AuthZ — на каждом защищённом действии, в каждом сервисе. Сервис заказов не повторяет логин, но обязан сам решить, можно ли этому пользователю отменить чужой заказ.


OAuth2 и OpenID Connect

Главная путаница индустрии: OAuth2 — про authorization, не про authentication.

  • OAuth2 (RFC 6749, IETF, 2012) — authorization framework для делегированного доступа. Third-party приложение получает limited access к ресурсу от имени владельца через access token, без передачи пароля.
  • OpenID Connect (OIDC Core 1.0, OpenID Foundation, 2014) — identity-слой поверх OAuth2. Добавляет ID Token: доказательство, что пользователь аутентифицирован, и кто он.

Грабли: - Брать access token и по нему «логинить» пользователя. Access token — для resource server, он про «что можно трогать», а не «кто ты». Для identity бери ID Token из OIDC. - Считать OAuth2 единым протоколом. Это extensible framework с набором grant types. Для server-side и SPA сегодня берёшь Authorization Code + PKCE, не implicit (он deprecated).

Роли в контуре раздаются так. Есть Authorization Server (Identity Provider — Keycloak, Auth0, свой) — он один на весь контур, выдаёт токены. Сервисы — это resource servers, они токены только проверяют, сами не выдают.


JWT vs opaque tokens

Два способа представить access token. Выбор влияет на всю схему валидации.

  • JWT — самодостаточный токен. Внутри подписанный JSON с claims (sub, exp, scope, роли). Проверяешь подпись публичным ключом — и веришь содержимому, без сетевого вызова.
  • Opaque token — непрозрачная строка-идентификатор. Сам по себе ничего не значит. Чтобы узнать, валиден ли и чей он, дёргаешь introspection endpoint (RFC 7662) у Authorization Server.

Чем отличаются на практике:

Свойство JWT Opaque
Валидация локально, проверка подписи сетевой вызов на introspection
Latency ~ноль +RTT до auth server на запрос
Отзыв (revocation) трудно: живёт до exp мгновенно: auth server решает
Утечка видно содержимое (не secret, но PII утекает) строка бесполезна без introspection
Связь с auth server нужна только для ключей нужна на каждую проверку

Главная боль JWT — его трудно отозвать. Токен подписан и валиден до exp. Уволил сотрудника — а его токен ещё 15 минут живой. Поэтому:

  • Делай access token коротким (5–15 минут), refresh token — долгим и отзываемым.
  • На по-настоящему критичных действиях проверяй ещё и через introspection, не только подпись.
  • Держи механизм отзыва: blacklist по jti, или версия токена в claims против счётчика в БД.

Гибрид встречается часто: opaque token наружу для клиента (легко отозвать), JWT внутри контура между сервисами (дёшево валидировать). Gateway меняет одно на другое на входе — паттерн Token Exchange / Phantom Token.


RBAC vs ABAC

Две модели authorization. Грубо говоря, по ролям против по атрибутам.

  • RBAC (Role-Based Access Control) — права привязаны к ролям, роли — к пользователю. «admin может удалять заказы». Просто, предсказуемо, легко аудировать. Плохо тянет контекст: «можно, но только свои заказы и только в рабочие часы».
  • ABAC (Attribute-Based Access Control) — решение из атрибутов субъекта, ресурса и среды. «user может редактировать документ, если он его owner и документ в статусе draft». Гибко, но политики быстро превращаются в нечитаемый клубок.

Когда что: - RBAC — старт по умолчанию. Покрывает большинство случаев, дёшев в поддержке. - ABAC — когда правила реально зависят от данных (ownership, tenant, время, гео). - На практике часто смесь: роль грубо режет доступ, атрибуты доуточняют на уровне ресурса.

Отдельная тема — где живёт сама политика. Тренд — выносить authorization в отдельный policy engine (OPA/Rego, Cedar): сервис спрашивает «можно ли?», движок отвечает. Логика прав не размазана по коду каждого сервиса.


Где валидировать токен

Самый частый грабли всей секции: токен проверяют только на периметре, а внутри контур открыт.

Два уровня, и нужны оба:

  • На gateway (edge) — отсечь явный мусор как можно раньше. Нет токена или подпись битая — заворачивай на входе, не пускай нагрузку внутрь. Здесь же снимаешь identity и кладёшь в заголовок дальше.
  • В каждом сервисе — повторная проверка. Сервис не верит, что «раз дошло до меня, значит gateway всё проверил». Он валидирует токен (или подписанный gateway header) сам и делает свою AuthZ.

Почему нельзя только на edge: - Любой, кто попал внутрь сети (скомпрометированный сервис, под в том же кластере, SSRF), ходит к твоим сервисам напрямую, минуя gateway. - AuthZ всё равно контекстная. Только сервис заказов знает, чей это заказ. Gateway этого не знает.

                 ┌──────────────────────────────────────────────┐
   client ──TLS──▶│  Gateway / edge                              │
   (opaque/JWT)   │  - проверка подписи / introspection          │
                 │  - rate-limit, WAF, schema validation        │
                 │  - кладёт identity в заголовок / mint JWT     │
                 └───────────────────┬──────────────────────────┘
                                     │ mTLS, JWT внутри
              ┌──────────────────────┼──────────────────────┐
              ▼                      ▼                      ▼
        ┌──────────┐          ┌──────────┐          ┌──────────┐
        │ orders   │          │ payments │          │ users    │
        │ AuthZ тут│          │ AuthZ тут│          │ AuthZ тут│
        └──────────┘          └──────────┘          └──────────┘
   каждый сам валидирует identity и решает права — не доверяет соседу

Note

Не пробрасывай сырой клиентский токен бесконечно вглубь. Чем глубже, тем шире blast radius при утечке. Часто gateway обменивает внешний токен на внутренний, узкий по scope и короткий по времени (Token Exchange), и пробрасывает уже его.


mTLS внутри контура

Токен говорит, кто пользователь. mTLS говорит, какой сервис с каким сервисом разговаривает.

mTLS (mutual TLS) — это режим TLS, где обе стороны предъявляют и проверяют X.509-сертификаты, а не только клиент проверяет сервер.

  • Обычный TLS: клиент убедился, что сервер — тот самый. Сервер про клиента ничего не знает.
  • mTLS: сервер тоже требует сертификат клиента. Взаимная аутентификация service-to-service.

Зачем в микросервисах: - Шифрование трафика внутри кластера — сеть не доверенная даже за firewall. - Сервис A криптографически уверен, что к нему пришёл именно сервис B, а не кто-то в той же подсети. - Identity сервиса в самом сертификате (SPIFFE/SVID) — основа для service-level AuthZ.

mTLS — не отдельный протокол, а режим стандартного TLS (client-cert auth там с TLS 1.0, RFC 2246, 1999). И это authentication, не authorization: сертификат доказывает, кто звонит, но не что ему можно.

Грабли: - Ротация сертификатов. Коротко живущие серты на сотнях подов руками не выдашь — нужна автоматика. - Поэтому на практике mTLS почти всегда отдают service mesh (Istio, Linkerd): sidecar терминирует TLS, выдаёт и крутит серты сам. Про mesh и sidecar — подробнее в Macro-Architecture.


Zero Trust

Идея простая: сеть не доверенная по умолчанию. Нет «защищённого периметра», внутри которого все свои.

  • Старая модель: толстый firewall снаружи, мягкая середина. Пробил периметр — гуляй свободно.
  • Zero Trust: каждый запрос аутентифицируется и авторизуется, независимо от того, откуда пришёл — снаружи или из соседнего пода.

Что это значит для архитектуры: - Identity на каждом hop: и пользователь (токен), и сервис (mTLS-сертификат). - «За VPN/firewall, значит свой» — больше не аргумент. Внутренний вызов проверяется как внешний. - Least privilege: каждый сервис ходит только туда, куда ему явно разрешено (network policy + AuthZ).

Zero Trust — это не продукт, который покупаешь. Это принцип, который собирается из кусков этой секции: mTLS плюс per-request AuthZ плюс короткие scoped-токены плюс network policies.


Секреты

Пароли БД, ключи подписи JWT, API-ключи, TLS-серты. В распределённой системе их много, и они не должны лежать в коде или в .env-файлах в репозитории.

Грабли (повторяются в каждом втором проекте): - Секрет в git. Закоммитил — считай утёк навсегда, история помнит. Ротируй ключ, а не надейся на git rm. - Секрет в plain-env в образе или манифесте. kubectl describe, дамп образа, логи — и он наружу.

Куда складывать: - HashiCorp Vault — централизованное хранилище с динамическими секретами (генерит креды БД на лету, с TTL), аудитом, шифрованием. Дорого в эксплуатации, но мощно. - K8s Secrets — встроено в кластер, но по умолчанию это base64, не шифрование. Включай encryption at rest для etcd и RBAC на доступ, иначе это «секрет» только на словах. - SOPS — шифруешь секреты прямо в git, ключом из KMS/age. GitOps-friendly: зашифрованный файл лежит в репо, расшифровывается только при деплое.

Что в любом случае: - Ротация. Секрет с бесконечным сроком жизни — это утечка, которая ещё не случилась. - Least privilege на доступ: сервис читает только свои секреты, не все подряд. - Не логируй секреты. Очевидно, но именно так они чаще всего и утекают — в трейс или лог.


Политики на gateway

Gateway — естественная точка для cross-cutting защиты, которую глупо дублировать в каждом сервисе. Детали роутинга и протоколов — в Communication & API Style, здесь только про security-функции.

  • Rate-limiting — режешь частоту запросов на клиента/токен/IP. Защита от abuse и от того, что один крикливый клиент положит всех. Это не backpressure — это жёсткая внешняя политика «больше N в окно».
  • WAF (Web Application Firewall) — фильтр типовых атак на L7: SQL injection, XSS, известные сигнатуры. Первый грубый щит, не замена валидации в сервисе.
  • Schema validation на входе и выходе — gateway проверяет, что запрос соответствует контракту (тип, обязательные поля, формат). Кривой и враждебный ввод не доходит до сервисов.

Note

Gateway фильтрует и режет, но не заменяет AuthZ внутри. Он не знает бизнес-контекст — чей это заказ, в каком статусе документ. Edge защищает от мусора и шума, сервис защищает бизнес-правила.


Если коротко: - Нет токена или битая подпись на входе → заворачивай на gateway, не пускай нагрузку внутрь. - Дошло до сервиса → всё равно валидируй сам и делай свою AuthZ, не доверяй соседу. - Нужна личность пользователя → ID Token из OIDC, не access token. - Нужно мгновенно отзывать → opaque token + introspection или короткий JWT + refresh. - Сервис-сервис в кластере → mTLS, на проде через service mesh. - Секрет → в Vault/SOPS/зашифрованный K8s Secret, никогда в git или plain-env. - Хочешь Zero Trust → собери из кусков: mTLS + per-request AuthZ + scoped-токены + network policy.