Защита данных: публичный доступ к S3, шифрование и класс утечек
Канонический облачный взлом — это публичный S3-бакет по ошибке конфигурации, а не криптография. Block Public Access — override на весь аккаунт; bucket-политики лучше ACL; SSE-KMS даёт аудит и отзыв; запрещай не-TLS и нешифрованные загрузки; presigned URL держи короткими.
Джуниор-инженеру нужно дать маркетинговому сайту прочитать пару картинок товаров, и он вставляет bucket-политику с "Principal": "*" и "Action": "s3:GetObject" на весь бакет — самое быстрое, что сработало. А в этом же бакете, в другом префиксе, лежат загруженные документы каждого клиента и ночной экспорт таблицы пользователей. Четыре месяца никто не замечал. Потом исследователь безопасности запускает рутинное сканирование открытых бакетов, находит ваш и присылает по почте выборку из 3,2 миллиона записей, прежде чем выйти в публичность. К пятнице ваше имя в новостях. Шифрование было в порядке — объекты даже были зашифрованы at rest. Ничего не расшифровывали, не брутфорсили, не крал изощрённый злоумышленник; дверь просто оставили открытой одним wildcard-principal, и перед ней не стояло ни одной защиты. Это самый частый облачный взлом данных, и почти ничего в нём не про криптографию.
После урока ты будешь знать один переключатель уровня аккаунта, закрывающий весь класс взломов, почему AES-256 не твоё слабое место и какое условие политики делает TLS обязательным, а не вежливой просьбой.
Взлом публичного бакета и как на самом деле решается доступ
Бакеты, ключи и storage-классы ты уже знаешь из урока про хранилище. Сеньорский вопрос уже и острее: для одного GetObject — кому разрешено и что может перекрыть ошибку? Взлом из Hook — канонический: миллионы объектов наружу, потому что одна политика сказала Principal: "*". Чтобы никогда такое не выкатить, надо знать порядок, в котором S3 решает доступ, и какой контроль — последнее слово.
Доступ к объекту могут дать несколько механизмов, и они комбинируются: IAM identity-политика (что может делать principal в твоём аккаунте), bucket-политика (resource-based политика на бакете — единственная, что может дать анонимный или кросс-аккаунтный доступ) и легаси-ACL (per-object/per-bucket гранты, появившиеся раньше политик). Анонимное чтение из всего интернета случается только через bucket-политику с Principal: "*" или public-read ACL — IAM-политика никогда не сделает объект публичным, ведь у анонимных вызывающих нет IAM-идентичности. ACL — это старая модель с раскиданными грантами; AWS теперь отключает их по умолчанию (Object Ownership в режиме bucket-owner-enforced), так что современный бакет должен выражать весь доступ через одну читаемую bucket-политику, а не десятки object-ACL.
Надо всем этим стоит override, который побеждает несмотря ни на что: S3 Block Public Access (BPA). BPA — это четыре настройки, которые отклоняют публичные гранты: можно игнорировать существующие публичные ACL, блокировать новые публичные ACL, игнорировать публичные bucket-политики и ограничивать кросс-аккаунтный публичный доступ. Он применяется и на уровне аккаунта, и на уровне бакета, и когда включён, перекрывает любую политику или ACL, которые иначе сделали бы бакет публичным. Включи BPA на уровне аккаунта — и даже будущий инженер, вставивший Principal: "*", не сможет открыть данные: публичный грант просто не учитывается. Это один переключатель на весь аккаунт, побеждающий весь класс взломов:
# Защита на весь аккаунт: один вызов, нейтрализующий каждый публичный грант,
# нынешний и будущий, во всех бакетах аккаунта.
aws s3control put-public-access-block \
--account-id 111122223333 \
--public-access-block-configuration \
BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true
# Те же четыре флага можно задать и на бакет — как defense in depth.
aws s3api put-public-access-block \
--bucket acme-customer-data \
--public-access-block-configuration \
BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=trueЧтобы дать ровно тот доступ, что ты задумал, сужай principal через conditions, а не открывай миру: aws:PrincipalOrgID ограничивает bucket-политику principal’ами внутри твоей AWS Organization, а aws:SourceArn / aws:SourceAccount привязывают кросс-сервисный доступ к тому единственному ресурсу, что должен вызывать. Failure mode самого фикса — слишком широкий кросс-аккаунтный доступ: ты даёшь другому аккаунту s3:GetObject для интеграции с партнёром, админ того аккаунта вешает wildcard на одну из своих ролей, и теперь over-privileged principal в чужом аккаунте читает твой бакет — слабым звеном была твоя политика, не их. Аудируй реальность, а не намерение, через IAM Access Analyzer, который непрерывно подсвечивает любой бакет, чья политика даёт доступ внешнему аккаунту или анонимному principal, — превращай его находки в сигнал с нулевой терпимостью.
▸Почему это работает
Почему Block Public Access перекрывает совершенно валидную bucket-политику, говорящую «разрешить всем»? Потому что BPA не часть обычного allow/deny-evaluation — это защита, наложенная поверх него, которая лишает запрос возможности быть обслуженным анонимно ещё до того, как политику вообще прочитают. AWS намеренно сделал его неперекрываемым локальной bucket-политикой, чтобы самая дорогая, самая частая ошибка — небрежный Principal: "*" — падала закрыто, а не открыто. С включённым BPA на уровне аккаунта политика может говорить что угодно; публичное чтение отклоняется. Это ровно то свойство, которого хочешь от защиты: она должна побеждать ту самую ошибку, ради предотвращения которой существует, иначе это не защита.
Шифрование at rest: SSE-S3, SSE-KMS и SSE-C
Каждый объект должен быть зашифрован at rest, и S3 теперь делает это по умолчанию — но выбор ключа — реальный tradeoff. SSE-S3 использует ключи, которыми владеет и управляет AWS: бесплатно, полностью прозрачно, AES-256, ты не делаешь ничего. SSE-KMS шифрует data-key каждого объекта под customer-managed KMS-ключом (CMK) — envelope-механика разобрана в уроке IAM/KMS; здесь важно, что́ CMK тебе даёт: CloudTrail-аудит каждого encrypt/decrypt, контроль через IAM/key-политику над тем, кто может расшифровать, и возможность мгновенно отозвать доступ к данным, отключив ключ — без перезаписи объектов, любое чтение просто начинает падать. SSE-C значит, что ты поставляешь и держишь ключ на каждом запросе; AWS шифрует им и тут же забывает, так что управление ключами целиком на тебе (и потеряешь данные, если потеряешь ключ).
Tradeoff SSE-KMS — стоимость и throttling. Каждый GetObject/PutObject к SSE-KMS-объекту делает KMS API-вызов, а у KMS есть квота на rate запросов на аккаунт (обычно 5 500–50 000 запросов/секунду по региону и операции). Высоконагруженный бакет может throttle’иться на KMS, а не на S3, и KMS-запросы тарифицируются за вызов (~$0.03 за 10 000). S3 Bucket Keys это чинят: S3 генерирует короткоживущий bucket-level ключ из твоего CMK и защищает им многие объекты, срезая прямые KMS-вызовы до ~99% — что рубит и стоимость, и риск throttling. Включай Bucket Key всегда, когда используешь SSE-KMS на масштабе. Задай default encryption на бакете, чтобы ничего не приземлялось нешифрованным:
{
"Rules": [
{
"ApplyServerSideEncryptionByDefault": {
"SSEAlgorithm": "aws:kms",
"KMSMasterKeyID": "arn:aws:kms:us-east-1:111122223333:key/abcd-1234"
},
"BucketKeyEnabled": true
}
]
}Регулируемый бакет хранит клиентские PII. Аудиторы требуют аудит-трейл каждого decrypt по доступу и возможность мгновенно отозвать доступ к данным во время инцидента без перезаписи объектов. Пропускная способность умеренная. Выбери шифрование.
Шифрование in transit и enforcement политикой
Шифрование at rest ничего не делает для байтов на проводе; ещё нужен TLS in transit, и сеньорский ход — принудить его, а не надеяться, что клиенты используют HTTPS. Bucket-политика может запретить любой запрос, где aws:SecureTransport равно false, так что plain-HTTP GetObject отклоняется напрочь. Та же политика может запретить загрузки, не несущие требуемый тобой заголовок шифрования, так что ничего не приземлится нешифрованным, даже если клиент забыл. Failure mode забыть про это: клиентская библиотека дефолтится в HTTP, креды и байты объекта идут по сети в открытом виде, и атакующий с сетевой позиции их читает — шифрование at rest данных и не видело. Принудь оба одной политикой:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyInsecureTransport",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::acme-customer-data",
"arn:aws:s3:::acme-customer-data/*"
],
"Condition": { "Bool": { "aws:SecureTransport": "false" } }
},
{
"Sid": "DenyUnEncryptedUploads",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::acme-customer-data/*",
"Condition": {
"StringNotEquals": { "s3:x-amz-server-side-encryption": "aws:kms" }
}
}
]
}Чтобы держать S3-трафик целиком вне публичного интернета, маршрутизируй его через gateway VPC endpoint для S3 и прикрепи endpoint-политику; условие bucket-политики на aws:SourceVpce тогда может требовать, чтобы чтения шли только через твой endpoint, так что даже утёкшие креды бесполезны снаружи VPC. Defense in depth: BPA закрывает публичный доступ, statement deny-insecure-transport закрывает провод, а endpoint закрывает путь.
Lifecycle, снижение экспозиции и правда о misconfiguration
Самые дешёвые в защите данные — те, что ты вообще не хранил: собирай минимум и удаляй по расписанию через lifecycle-expiration, чтобы будущему взлому было что утечь меньше. Для контролируемого шаринга presigned URL лучше, чем делать что-либо публичным, но их безопасность и есть их срок: URL несёт права подписавшего на одну операцию до истечения, и presigned URL со сроком 7 дней, попавший в лог, в заголовок referrer или в сообщение Slack, — это открытая на неделю дверь к тому объекту. Подписывай на минуты, сужай до одного ключа и одной операции и никогда не переиспользуй долгоживущую идентичность подписанта для публичных URL.
Против удаления и ransomware складываются три фичи: versioning хранит каждую прежнюю версию, так что перезапись или удаление восстановимы; MFA-delete требует аппаратный/виртуальный MFA-токен для перманентного удаления версии, так что одних украденных кредов мало, чтобы стереть историю; а Object Lock (WORM — write once, read many) делает объекты неизменяемыми на срок хранения, так что даже админ или атакующий не изменит и не удалит их до истечения. Сквозная мысль через всё это: подавляющее большинство S3-взломов — это misconfiguration (публичный грант, слишком широкая кросс-аккаунтная политика, утёкший долгоживущий URL), а не сломанная криптография. AES-256 — не твоё слабое место; слабое место — конфигурация доступа перед ним.
Block Public Access включён на уровне аккаунта. Инженер вешает bucket-политику с Principal "*", разрешающую s3:GetObject на весь бакет. Что станет с анонимными чтениями из интернета?
- 01Пройди, как S3 решает запрос к объекту, и объясни, почему Block Public Access побеждает класс взломов публичных бакетов.
- 02Сравни SSE-S3, SSE-KMS и SSE-C и объясни tradeoff SSE-KMS по стоимости/throttling и как Bucket Keys его чинят.
Самый частый облачный взлом данных — это не сломанная криптография, а S3-бакет, по ошибке сделанный публичным через пермиссивную bucket-политику (Principal: "*") или публичный ACL (Access Control List — постарший механизм per-object грантов), утекающий миллионы объектов. Теперь, когда видишь правку bucket-политики, первый вопрос — включён ли Block Public Access на уровне аккаунта: именно эта настройка — разница между «небрежным wildcard, который ничего не сделает» и «небрежным wildcard, из-за которого твоё имя попадёт в новости». Доступ к объекту комбинирует IAM identity-политику, bucket-политику (единственную, что может дать анонимный или кросс-аккаунтный доступ) и легаси-ACL (теперь отключены по умолчанию), но надо всеми ними стоит Block Public Access — четыре настройки, применяемые и на уровне аккаунта, и на уровне бакета, которые отклоняют любой публичный грант и не могут быть перекрыты локальной политикой, так что BPA на уровне аккаунта закрывает весь класс взломов даже против будущего небрежного wildcard. Сужай задуманный доступ через aws:PrincipalOrgID и aws:SourceArn, следи за слишком широкими кросс-аккаунтными грантами и аудируй непрерывно через IAM Access Analyzer. Шифрование — вторая линия: каждый объект зашифрован at rest по умолчанию, при выборе между SSE-S3 (бесплатно, под управлением AWS, без аудит-трейла), SSE-KMS (customer-ключ, добавляющий CloudTrail-аудит и мгновенный отзыв отключением ключа ценой KMS-вызовов на запрос и throttling, который S3 Bucket Keys режут на ~99%) и SSE-C (ключ держишь ты). Принуждай TLS, запрещая запросы, где aws:SecureTransport равно false, запрещай нешифрованные загрузки условием политики и маршрутизируй трафик через VPC endpoint, чтобы держать его вне публичного интернета. Снижай экспозицию, храня минимум и удаляя по lifecycle, держа presigned URL короткими и узкими до одной операции, и защищайся от удаления и ransomware через versioning, MFA-delete и Object Lock. Сквозная мысль: AES-256 — не твоё слабое место, слабое место — конфигурация доступа перед ним.
Практика
Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.
Что-то непонятно?
Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.