Data security and KMS
Шифрование at-rest стоит ровно столько, скольких принципалов пускает политика ключа. Данные защищает envelope-шифрование KMS и deny-by-default политика ключа — а не галочка про AES.
Разбор инцидента окончен, и вердикт тот же, что ты видел трижды за этот год: бакет был зашифрован. «Server-side encryption with KMS» — стоит галочка, чекбокс комплаенса зелёный, аудитор подписал. И всё же 40 миллионов записей клиентов ушли наружу в открытом виде. Атакующий ни разу не коснулся шифротекста. Он перехватил слишком широкую IAM-роль, вызвал kms:Decrypt — и сервис KMS, делая ровно свою работу, выдал ему дата-ключ и расшифровал всё. Шифрование at-rest не отказало. Оно сработало идеально — для не того принципала. Контролем, который имел значение, был не шифр. Это была политика ключа — и её никто не прочитал.
К концу урока ты поймёшь, почему «зашифровано at-rest» само по себе — почти бессмысленная гарантия, как envelope-шифрование с KMS на деле переносит границу безопасности на решение о доступе и как написать политику ключа, в которой Decrypt становится сложной частью, а не бесплатной.
Что на самом деле даёт «шифрование at-rest»
Начнём с неудобной правды: server-side шифрование at-rest защищает ровно от одной угрозы — что кто-то физически унесёт диск или прочитает сырой носитель из-под работающей системы. И всё. Украденный диск, списанный SSD без затирания, снапшот, скопированный уставшим инженером не в тот аккаунт: против этого at-rest шифрование реально и ценно. Это нижняя планка.
Оно ничего не делает против угрозы, которая случается на деле. Когда приложение читает строку, слой хранения прозрачно расшифровывает её первым — в этом и весь смысл прозрачного шифрования. Поэтому любой принципал, который может заставить твой сервис прочитать данные или может вызвать decrypt-API напрямую, получает открытый текст. SQL-инъекция возвращает открытый текст. Утёкшая учётка БД возвращает открытый текст. Сверхпривилегированная IAM-роль с вызовом kms:Decrypt возвращает открытый текст. Шифрование невидимо для атаки, потому что оно невидимо для любого легитимного чтения, а атакующий едет на легитимном пути чтения. «Данные зашифрованы at-rest?» — неверный вопрос аудита. Вопрос — «кто может вызвать их расшифровку?», и ответ живёт в IAM и политике ключа, а не в конфиге хранилища.
Этот сдвиг рамки и есть весь урок. Шифрование at-rest превращает задачу конфиденциальности данных в задачу контроля доступа к ключу. Это хороший обмен — про контроль доступа можно рассуждать, его можно аудировать, логировать и отзывать, — но только если ты относишься к доступу к ключу как к настоящей границе безопасности, а не к мелочи, унаследованной из дефолта.
Envelope-шифрование: почему никто не шифрует данные прямо через KMS
Ты не можешь отправить файл на 4 ГБ в KMS на шифрование. Аппаратные сервисы ключей ограничивают прямой encrypt несколькими килобайтами (AWS KMS: 4 КБ), потому что мастер-ключи никогда не покидают HSM — каждый байт, который ты хочешь зашифровать, делал бы сетевой round-trip внутрь границы и обратно. Поэтому вся индустрия использует envelope-шифрование, и понимание его двухключевой структуры — то, что делает всё остальное осмысленным.
Есть два слоя ключей:
- Дата-ключ (DEK) — свежий симметричный ключ (например, AES-256), генерируемый на каждый объект или на логическую область. Он шифрует сами байты, локально, на полной скорости. Это ключ, делающий основную работу.
- Ключ шифрования ключей (KEK), он же мастер-ключ или CMK, — долгоживущий ключ, который никогда не покидает KMS/HSM. Его единственная задача — шифровать и расшифровывать дата-ключи.
Поток: ты просишь KMS сгенерировать дата-ключ. Он выдаёт его дважды — один раз в открытом виде (используй сейчас, в памяти) и один раз уже зашифрованным под KEK («обёрнутый» или «зашифрованный дата-ключ»). Ты шифруешь полезную нагрузку открытым DEK, затем немедленно стираешь открытый DEK из памяти и хранишь обёрнутый DEK прямо рядом с шифротекстом. Чтобы прочитать данные обратно, ты отправляешь обёрнутый DEK в KMS, KMS вызывает Decrypt с помощью своего KEK, возвращает открытый DEK, ты расшифровываешь нагрузку и снова стираешь DEK.
Почему эта структура оправдывает свою сложность: основные данные никогда не пересекают границу KMS, поэтому ты держишь AES на скорости канала; единственное, чего KMS вообще касается, — крошечный обёрнутый DEK. И она даёт два независимых рычага отзыва. Ты можешь ротировать KEK, не перешифровывая ни одного байта данных, — лишь маленькие обёрнутые DEK нужно переобернуть. А поскольку открытый DEK существует лишь временно в памяти приложения, долговременная безопасность всех твоих данных сводится к долговременной безопасности одной вещи: доступа к вызову Decrypt на KEK. Этот схлоп — петабайты конфиденциальности, держащиеся на одном решении о контроле доступа, — ровно поэтому политика ключа и есть настоящий артефакт.
▸Почему это работает
Зачем генерировать новый дата-ключ на каждый объект, а не переиспользовать один быстрый локальный ключ на всё? Радиус поражения и крипто-гигиена. Один DEK, шифрующий миллионы записей, — единая точка компрометации: утеки его раз (дамп кучи, выгруженная страница, лог расшифрованного материала) — и каждая запись, которой он касался, раскрыта, без возможности отозвать только одну. DEK на объект (или на тенанта) означает, что утёкший открытый DEK раскрывает только свою область, и ты можешь крипто-шреднуть одного тенанта, уничтожив его обёрнутый DEK, — шифротекст становится безвозвратно нечитаемым шумом, не трогая данные других тенантов. Это ещё и держит каждый отдельный ключ хорошо под лимитами объёма данных, которые ослабляют уникальность nonce у AES-GCM на масштабе.
Политика ключа — это и есть контроль безопасности
Вот часть, которую команды пропускают, и причина, по которой пробой из Hook повторяется: политика ключа и IAM-права вокруг kms:Decrypt и есть контроль шифрования at-rest. Шифр — это коммодити; решение о доступе — это продукт.
Здравая политика ключа — это deny-by-default и least-privilege над ключом, та же дисциплина, что ты применил бы к любому ресурсу, — просто применённая к одному ресурсу, который держит на замке всё остальное:
- Сузь
Decryptдо наименьшего набора принципалов, которым действительно нужен открытый текст. Сервису приёма, который пишет зашифрованные строки, часто нужен толькоGenerateDataKey(создавать новые DEK), аDecrypt— вообще нет: он не читает обратно. Аналитической задачеDecryptможет быть нужен, но только через конкретную роль. Большинства людей и большинства сервисов в политике ключа быть вообще не должно. - Ограничивай условиями, а не только принципалами. Используй encryption context (дополнительные аутентифицируемые данные), чтобы обёрнутый DEK тенанта A буквально не мог быть расшифрован под запросом, заявляющим тенанта B, — вызов
Decryptпадает, потому что AAD не совпадает. Привязывай grant к исходному VPC, к конкретной сессии роли, к конкретному ресурсу. ГолыйAllow Decryptна*— это та политика, что потеряла 40 миллионов записей. - Разделяй администраторов ключа и пользователей ключа. Тот, кто может вызывать
Decrypt, не должен также мочь переписать политику ключа, чтобы выдать себе больше, — а тот, кто может менять политику, не должен незаметно мочь читать данные. Это separation of duties применительно к ключу, и именно это не даёт одной скомпрометированной роли и читать данные, и стирать улики того, что она могла.
Зрелый рефлекс: когда видишь «зашифровано через KMS», твой следующий клик — политика ключа, и ты читаешь её как список контроля доступа, спрашивая: какие принципалы могут превратить шифротекст обратно в открытый текст, при каких условиях и кто может изменить этот ответ. Если политика — дефолт AWS (root-аккаунт + широкий admin), данные фактически защищены твоей общей IAM-гигиеной и ничем более конкретным — шифрование делает пиар, а не оборону.
| Угроза | Только at-rest шифрование | Что её реально останавливает |
|---|---|---|
| Украденный диск / незатёртый SSD | Останавливает — это та самая угроза, ради которой оно | Само at-rest шифрование |
| Снапшот скопирован не в тот аккаунт | Останавливает, только если целевой аккаунт не может использовать KEK | Политика ключа, ограниченная исходным аккаунтом |
| Сверхпривилегированная роль вызывает kms:Decrypt | Защиты нет — KMS расшифровывает для неё | Least-privilege политика ключа + условия |
| SQL-инъекция / утёкшая учётка БД | Защиты нет — путь чтения авто-расшифровывает | Authz на уровне приложения; шифрование — не тот инструмент |
| Нужно навсегда уничтожить данные одного тенанта | Тяжело — надо перезаписать каждую запись | Крипто-шред: уничтожить обёрнутый DEK этого тенанта |
Ротация, шреддинг и сбойные сценарии
Из двухключевого дизайна выпадают два операционных свойства, и оба регулярно понимают неверно.
Ротация. Автоматическая ротация KEK (например, ежегодная) не перешифровывает твои данные и не переобёртывает хранимые DEK — она добавляет новую версию KEK и использует её для новых операций обёртывания, держа старые версии доступными для распаковки существующих DEK. Поэтому ротация дешева и поэтому те, кто ждёт от неё «переzащиты старых данных», ошибаются: DEK, обёрнутый под версией KEK 2023 года, всё равно распаковываем вечно, пока ты активно не переобернёшь его. Истинное уничтожение ключа (удаление версии KEK) — единственное, что делает его обёрнутые DEK — а значит и нижележащий шифротекст — безвозвратно невосстановимыми. Это механизм крипто-шреддинга: чтобы сделать данные невосстановимыми по требованию (запрос на удаление по GDPR, списанный тенант), ты не скребёшь петабайты — ты уничтожаешь маленький обёрнутый DEK, единственный путь к этим данным, и шифротекст мгновенно становится необратимым шумом.
Сбойные сценарии, которые надо уважать:
- Потеря или уничтожение KEK, который ещё был нужен, — это невосстановимая потеря данных by design: нет восстановления, нет тикета в саппорт, нет бэкдора. То же свойство, что делает крипто-шред мощным, делает случайное удаление ключа катастрофой. AWS навязывает обязательный период ожидания 7–30 дней на удаление ключа именно потому, что операция необратима.
- Вызов
Decrypt— жёсткая рантайм-зависимость. Если KMS недостижим, или ты пересёк регион, где ключа нет, или попал в лимит частоты запросов на ключ во время всплеска трафика, твои чтения начинают падать — данные целы, но не расшифровываемы, пока KMS не ответит. Envelope-шифрование меняет гарантию конфиденциальности на зависимость доступности от сервиса ключей, и ты должен планировать троттлинг KMS и региональную доступность ключа так же, как планировал бы любую критическую зависимость. - Забыть стереть открытый DEK превращает всю схему в театр. Если открытый DEK задержался в логе, дампе кучи, кеше или артефакте краша, атакующий, прочитавший его, пропускает KMS целиком. Обёрнутый DEK безопасно хранить где угодно; открытый DEK радиоактивен и должен жить только временно в памяти.
Свяжи это обратно с детектированием и постурой: политика ключа — статический артефакт, который можно сканировать (постура), а вызовы Decrypt — события, которые можно мониторить (детектирование). Модель непрерывного мониторинга NIST SP 800-137 применяется напрямую — аномальный всплеск kms:Decrypt от роли, которая обычно только пишет, или Decrypt, чей encryption context не совпадает с ожидаемым тенантом, — один из самых сигнальных алертов, что ты можешь поставить, потому что по дизайну выше он сидит ровно на той единственной границе, что имеет значение.
Скан комплаенса рапортует «S3-бакет зашифрован через KMS — PASS». В бакете PII клиентов. Ты security-ревьюер — каков твой следующий шаг?
Почему envelope-шифрование использует дата-ключ на объект (DEK), обёрнутый мастер-ключом (KEK), а не шифрует данные напрямую мастер-ключом KMS?
Бакет хранения зашифрован at-rest через KMS. Атакующий крадёт валидную учётку БД и запрашивает данные обычным путём чтения. Что он получит?
Упорядочь путь записи envelope-шифрования, от запроса ключа до безопасного сохранения результата:
- 1 Вызвать KMS GenerateDataKey для KEK
- 2 Получить DEK дважды: открытый + обёрнутый под KEK
- 3 Зашифровать нагрузку локально открытым DEK
- 4 Стереть открытый DEK из памяти
- 5 Сохранить шифротекст рядом с обёрнутым DEK
- 01Объясни, почему «бакет зашифрован at-rest через KMS» почти ничего не говорит о том, защищены ли данные на деле, и на что смотреть вместо этого.
- 02Пройди envelope-шифрование по шагам и объясни, что оно даёт по сравнению с прямым шифрованием данных мастер-ключом KMS.
Шифрование at-rest защищает от одной узкой угрозы — что кто-то прочитает сырой носитель — и ни от чего больше, потому что любой легитимный путь чтения прозрачно расшифровывает данные до того, как их увидит приложение. Это значит, что утёкшая учётка, SQL-инъекция или слишком широкая IAM-роль с вызовом kms:Decrypt — все уходят с открытым текстом, пока чекбокс «зашифровано» остаётся зелёным. Envelope-шифрование — это то, как ты превращаешь это в нечто защитимое: дата-ключ на объект (DEK) шифрует основные данные локально на полной скорости, этот DEK обёрнут мастер-ключом (KEK), который никогда не покидает HSM, а обёрнутый DEK хранится рядом с шифротекстом. Тогда вся безопасность схемы схлопывается до одного решения о контроле доступа — кто может вызвать Decrypt на KEK, — поэтому политика ключа, ограниченная deny-by-default и least-privilege с условиями вроде encryption context и разделения админов ключа и пользователей ключа, и есть настоящий контроль, а не шифр. Двухключевой дизайн ещё и даёт дешёвую ротацию KEK (без перешифрования данных) и крипто-шреддинг (уничтожь один обёрнутый DEK, чтобы сделать ровно эти данные невосстановимыми) — ценой того, что Decrypt становится жёсткой зависимостью доступности, а случайное удаление ключа — необратимым. Так что в следующий раз, когда скан скажет «зашифровано через KMS — PASS», твой первый ход — открыть политику ключа и спросить: какие принципалы могут превратить этот шифротекст обратно в открытый текст, при каких условиях и кто может изменить этот ответ?
Практика
Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.
Что-то непонятно?
Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.
Примени это
Примени этот урок в реальном проекте.