ADR-0018: Master key для шифрования credentials и политика ротации DEK

Status: accepted Date: 2026-04-17 Deciders: команда проекта

Контекст

ADR-0010 ввёл envelope encryption для секретных полей SupplierCredential: master key + per-customer DEK. Сам выбор бэкенда master key и регламент ротации DEK были оставлены как “deployment decision (TBD)“.

Эта недоопределённость блокирует prod-готовность:

  • Без выбранного бэкенда невозможно задать процедуру bootstrap’а окружения.
  • Без регламента ротации DEK мы не имеем compliance-приемлемой системы хранения чувствительных данных клиентов (ИНН, токены доступа к API поставщиков).
  • Break-glass и аудит расшифровки не привязаны к конкретному источнику ключа.

Threat model (явно):

  • T1: компрометация одной реплики backend’а — атакующий не должен прочитать секреты других клиентов.
  • T2: компрометация снапшота PostgreSQL — атакующий не должен расшифровать секреты без master key.
  • T3: компрометация одного DEK — должна затронуть только одного customer’а и быть восстановимой через ротацию.
  • T4: insider abuse (разработчик / SRE) — расшифровка должна быть аудируемой.

Не в scope: защита от полной компрометации платформы (root + KMS access одновременно).

Решение

1. Абстракция MasterKeyProvider

Master key используется только через интерфейс:

MasterKeyProvider
├── Encrypt(plaintext) → ciphertext            // оборачивает DEK в master-key envelope
├── Decrypt(ciphertext) → plaintext
└── ActiveKeyId() → string                     // для metadata записи "каким master key завёрнут DEK"

Каждая зашифрованная DEK-запись хранит master_key_id — позволяет вращать master key без single-shot миграции.

2. Реализации провайдера (по убыванию приоритета для prod)

ПровайдерГде допустимЗамечание
HashiCorp Vault Transitprod, stagingдефолт для prod; даёт audit, ротацию ключа без выгрузки материала наружу
AWS KMS / Yandex Cloud KMSprod (cloud-native deployments)альтернатива Vault для cloud-only установок
Local file (sealed by passphrase)dev, CIpassphrase из MASTER_KEY_PASSPHRASE env, файл монтируется в контейнер только в dev
Plaintext env (MASTER_KEY_RAW)только локальная разработка одного разработчикаflag TRACIUM_ALLOW_INSECURE_MASTER_KEY=1 обязателен; в prod backend паникует на старте

Bootstrap процедура per env описана в 40-operations/deployment.md.

3. Ротация master key

  • Внешний trigger (Vault rotation policy / KMS rotate-key).
  • При смене ActiveKeyId — все новые DEK заворачиваются новым master key.
  • Существующие DEK перезаворачиваются фоновым джобом rewrap-deks (idempotent, batch по customer_id).
  • Активной может быть >1 master key (старый — для расшифровки legacy DEK, новый — для записи).
  • Полное удаление старого master key — только после rewrap-deks complete + 7 дней grace.

4. Per-customer DEK

  • Один DEK на customer_id (для customer-scope credentials) и один DEK на system (для system credentials).
  • DEK хранится в таблице credential_dek (customer_id, dek_ciphertext, master_key_id, created_at, retired_at).
  • Кэш расшифрованных DEK в памяти процесса — TTL 5 минут, max 1000 entries, явная инвалидация при ротации.

5. Регламент ротации DEK

ТриггерSLA на ротацию
Плановая (compliance)каждые 180 дней на customer_id
Реактивная: подозрение компрометации credentialв течение 1 часа
Реактивная: компрометация master keyв течение 24 часов на все DEK
Удаление аккаунта customer’анемедленно, DEK помечается retired_at и через 30 дней удаляется (после анонимизации credentials)

Ротация:

  1. Сгенерировать новый DEK.
  2. Расшифровать все credentials customer’а старым DEK, зашифровать новым в одной транзакции.
  3. Старый DEK помечается retired_at, удаляется через 30 дней.
  4. Событие CredentialDekRotated{customer_id, reason} в event_store.

Owner регламента — security lead (или CTO в его отсутствие).

6. Audit и break-glass

  • Каждая операция Decrypt логируется в credential_decrypt_audit (event_id, actor, credential_id, customer_id, master_key_id, reason, ts).
  • Поле reason обязательно: pricing_pipeline | manual_break_glass | rewrap_job | validation.
  • Break-glass (расшифровка вне обычного pipeline) требует:
    • двух approver’ов в admin UI (4-eyes);
    • указания reason text;
    • открытого incident ticket (ID указывается);
    • автоматической нотификации в security channel.
  • Все break-glass операции попадают в отдельный отчёт раз в неделю.

Последствия

Плюсы

  • Bootstrap окружения детерминирован — MasterKeyProvider единая точка контракта.
  • Ротация master key и DEK не требует downtime.
  • Compromise blast radius ограничен: один DEK = один customer.
  • Audit полный — расшифровка не выполняется неатрибутируемо.

Минусы

  • Vault или KMS становится hard dependency для prod (но именно это даёт security gain).
  • Сложность реализации rewrap job’а: должен быть idempotent + транзакционным per customer.
  • Дополнительная latency на pricing pipeline при cache miss DEK (mitigate: prefetch при первом запросе клиента).

Нейтральные последствия

  • Возможен переход с Vault Transit на KMS (или наоборот) без миграции данных credentials — достаточно сменить backend MasterKeyProvider и однократно прогнать rewrap-deks под новым master key.

Рассмотренные альтернативы

Hardcoded в env только

Не масштабируется. Не даёт audit, ротации, защиты от insider’а. Допустим только для dev.

Per-credential ключ (без promotion DEK на customer)

Слишком много объектов ключей, дороже ротация при компрометации одного credential.

Внешнее хранение секретов целиком (Vault как KV)

Vault KV становится критическим runtime-компонентом для каждого pricing-запроса. Transit-режим достаточен — он шифрует/расшифровывает, не хранит сами credentials.

Ссылки