Перейти к содержимому

Bucket access policy: как защитить Vk cloud object storage без единой опоры на прокси

Bucket Access Policy: когда авторизацию возвращают в хранилище, а прокси выключают

В объектном хранилище публичного облака VK Cloud для каждого бакета доступна Bucket Access Policy - политика доступа в формате JSON, размещённая непосредственно на бакете. Она определяет, каким субъектам разрешены конкретные операции с определёнными объектами, префиксами или всем содержимым контейнера.

Такая политика проверяется самим S3-совместимым хранилищем при каждом запросе. Настроить её можно через личный кабинет или S3 API. Однако на практике многие команды включают её только после инцидента, миграции или проверки со стороны информационной безопасности.

Особенно актуальна политика для систем, где перед Object Storage установлен nginx, API-шлюз или собственный прокси. Пока все запросы проходят через этот промежуточный слой, он может проверять токены, сопоставлять пользователя с таблицей разрешений, ограничивать частоту обращений и вести единый журнал. Но такая защита действует только на контролируемом маршруте.

Почему прокси не является полноценной границей безопасности

Распространённая архитектура выглядит так:

1. приложение отправляет запрос на прокси;
2. прокси проверяет токен и права пользователя;
3. прокси подписывает запрос S3-ключом;
4. Object Storage принимает запрос уже от общего технического аккаунта.

В бакете при этом может находиться единственная пара ключей с широкими полномочиями. Она прописана в конфигурации прокси, а правила разграничения доступа хранятся в nginx или коде приложения.

Проблема возникает, когда эти ключи копируются в другие места. Они могут понадобиться CI/CD, инженеру поддержки, разработчику для локального тестирования или подрядчику для временной работы. Если endpoint хранилища доступен напрямую, достаточно изменить параметр `--endpoint-url` в клиенте S3 - и запрос обойдёт прокси вместе со всеми его ограничениями.

В результате появляются типичные сценарии:

- аналитик получает доступ не только к `reports/`, но и к `inbox/` с исходными пользовательскими файлами;
- подрядчику отключают учётную запись в прокси, но его старая копия S3-ключа продолжает работать;
- CI/CD из-за ошибки в переменной пути удаляет чужой префикс;
- разработчик случайно публикует секрет в репозитории, а обнаружить это удаётся уже после использования ключа.

Bucket Access Policy переносит критически важное ограничение на уровень самого хранилища. Даже прямой запрос тогда будет проверен по правилам бакета, а не только по логике прокси.

Основные термины

Бакет - контейнер для объектов с собственным именем и настройками доступа.

Объект - файл, сохранённый в бакете.

Префикс - логическая папка внутри бакета. Например, `inbox/` может использоваться для входящих файлов, а `reports/` - для готовых отчётов.

S3-ключ - идентификатор и секрет, которыми подписываются запросы.

Endpoint - адрес, принимающий запросы к объектному хранилищу.

Какие проверки участвуют в принятии решения

На доступ к объекту обычно влияют несколько уровней контроля:

1. полномочия, назначенные ключу или пользователю;
2. Bucket Access Policy;
3. ACL объекта или бакета, если механизм используется;
4. дополнительные ограничения на уровне прокси или приложения.

Для практической диагностики важно понимать порядок. Разрешение само по себе не гарантирует доступ, если действует явный запрет. В общем случае логика выглядит так:

- сначала проверяется, имеет ли субъект право выполнять действие;
- затем учитываются разрешения политики;
- после этого применяются ограничения и запреты;
- любой явный `Deny` перекрывает соответствующий `Allow`.

Именно поэтому два похожих запрета могут вести себя по-разному. Один запрос может быть отклонён ещё на уровне прав ключа, другой - политикой бакета. В журнале аудита это будут разные причины отказа.

ACL также нельзя считать полной заменой Bucket Access Policy. ACL удобны для отдельных сценариев совместимости, но они хуже подходят для сложной логики с субъектами, префиксами, адресами и условиями. Для централизованного разграничения доступа предпочтительнее политика бакета.

Почему прямой перенос политики из AWS может не сработать

Политики, найденные в руководствах AWS, часто переносят в другое S3-совместимое хранилище без адаптации. Формально JSON может быть корректным, но это не означает, что все его поля поддерживаются конкретной платформой.

Наиболее частые причины отказа:

- используется ресурс в формате, не совпадающем с моделью ARN конкретного хранилища;
- указан неподдерживаемый тип `Principal`;
- применён оператор условия, которого нет в реализации;
- поле принято системой, но не участвует в проверке;
- действие записано в форме, отличающейся от поддерживаемого набора;
- политика установлена на бакет, но проверяется запрос к объекту другого пути.

Особенно опасна "тихая" ошибка: политика сохраняется без явного сообщения, однако отдельное условие фактически игнорируется. Поэтому проверять нужно не только факт загрузки JSON, но и реальное поведение на наборе запросов.

Четыре итерации создания политики

Итерация 1. Правильный ресурс

Сначала нужно точно определить область действия. Для доступа к самому бакету и к объектам внутри него часто требуются разные ресурсы. Если разрешение предназначено для префикса, его следует явно включить в шаблон ключа объекта.

Например, правило для `reports/` не должно автоматически распространяться на `inbox/`. Ошибка в одной строке ресурса способна либо полностью закрыть доступ, либо открыть весь бакет.

Итерация 2. Корректный Principal

Далее задаётся субъект: конкретный пользователь, роль, аккаунт или технический идентификатор. Нельзя без проверки переносить конструкции `Principal` из документации AWS. В разных реализациях поддерживаемые формы могут отличаться.

Безопаснее начинать с минимально конкретного субъекта и сразу проверять его реальным запросом. Если правило должно работать только для одного ключа, не следует заменять его универсальным значением.

Итерация 3. Ограничение по адресу

Условие по IP может сузить действие политики до корпоративной сети, подсети или диапазона адресов. Это полезный дополнительный барьер, но не замена идентификации.

Нужно учитывать NAT, VPN, балансировщики и прокси. Хранилище может видеть не адрес пользователя, а внешний адрес шлюза. Поэтому условие следует проверять с того маршрута, который реально использует клиент.

Итерация 4. Явный запрет

Явный `Deny` используют как страховку: например, чтобы запретить удаление объектов или доступ к чувствительному префиксу независимо от общего разрешения.

Однако чрезмерное количество запретов усложняет сопровождение. Ошибка в шаблоне ресурса или условии может перекрыть легитимный доступ. Каждое правило нужно тестировать отдельно, а затем проверять совместно с остальными.

Матрица тестирования

Минимальный набор проверок должен включать не менее 16 сценариев. В матрице фиксируются:

- политика;
- принципал;
- действие;
- ресурс;
- источник запроса;
- ожидаемый HTTP-код;
- фактический код;
- причина отказа в аудите.

Проверять стоит как минимум следующие комбинации:

- разрешённый ключ и разрешённый префикс;
- разрешённый ключ и чужой префикс;
- неизвестный ключ;
- чтение объекта;
- загрузка объекта;
- удаление объекта;
- запрос из разрешённой сети;
- запрос с другого адреса;
- существующий объект;
- отсутствующий объект;
- обращение к бакету вместо объекта;
- прямой запрос к endpoint;
- запрос через прокси;
- действие с явным запретом;
- запрос с неправильным форматом ресурса;
- запрос после отзыва или замены ключа.

Ожидаемые результаты могут различаться: `200` или `204` для успешного действия, `403` для отказа в доступе, `404` для отсутствующего ресурса. Нельзя автоматически считать любой `403` доказательством работы именно Bucket Access Policy - запрос мог быть отклонён раньше.

Скрипт проверки и аудит

Автоматизированный прогон лучше ручного тестирования через консоль. Скрипт должен по очереди выполнять операции для разных ключей и префиксов, сохранять статус ответа и сопоставлять его с журналом Cloud Audit.

Полезно считать долю запросов, отклонённых непосредственно хранилищем:

`число отказов на endpoint / общее число проверенных запросов × 100%`.

Эта метрика не показывает общую защищённость сама по себе, но помогает доказать, что ограничения действительно работают на уровне хранения, а не только в прокси.

Аудит также позволяет определить источник отказа, время операции, используемый ключ, адрес клиента и тип действия. Для расследований это значительно ценнее скриншота из консоли.

Что важно учесть при миграции

При переносе политики между AWS S3, Ceph, MinIO и VK Object Storage нельзя копировать JSON без проверки. Нужно отдельно сопоставить:

- формат идентификаторов ресурсов;
- поддерживаемые значения `Principal`;
- набор операций;
- условия и операторы сравнений;
- работу с ACL;
- правила для бакета и объектов;
- обработку виртуального и path-style адреса;
- формат журналов;
- поведение при отсутствии объекта;
- порядок обработки явных запретов.

Восемь мест, где чаще всего теряется ограничение:

1. прокси продолжает использовать общий ключ с полными правами;
2. старый ключ остаётся в CI;
3. политика разрешает весь бакет вместо нужного префикса;
4. ресурс описан только для бакета, но не для объектов;
5. `Principal` слишком широкий;
6. условие по IP не учитывает NAT;
7. явный запрет установлен на неправильный путь;
8. после миграции не проведена негативная проверка.

Роль прокси после переноса контроля

Bucket Access Policy не отменяет необходимость прокси. Он закрывает другой класс рисков: прямой доступ, утечку ключа и обход прикладных правил.

Прокси по-прежнему может отвечать за:

- пользовательскую аутентификацию;
- выдачу временных ссылок;
- ограничение скорости;
- антивирусную проверку;
- преобразование форматов;
- бизнес-логику;
- единый пользовательский аудит;
- сокрытие технических имён объектов.

При этом прокси не должен оставаться единственной точкой принятия решения о доступе к данным. Хранилище должно иметь собственную минимально необходимую политику, а ключи - ограниченные полномочия и короткий срок жизни.

Регуляторные и организационные аспекты

Для систем, обрабатывающих персональные данные, важна не только настройка доступа, но и возможность доказать её работоспособность. В рамках требований 152-ФЗ и мер защиты, применяемых по подходам ФСТЭК, обычно требуется контролировать доступ, вести журналирование, управлять учётными данными и регулярно проверять настройки.

Политика бакета помогает показать, что ограничение задано на уровне инфраструктуры. Но одного JSON недостаточно: нужны результаты тестов, записи аудита, история изменений и подтверждение того, что ключи не обладают лишними полномочиями.

Практический порядок внедрения

1. Составить перечень субъектов и операций.
2. Разделить данные по бакетам и префиксам.
3. Отозвать неиспользуемые ключи.
4. Создать отдельные ключи для CI, приложений и сотрудников.
5. Выдать каждому ключу минимальные права.
6. Настроить Bucket Access Policy.
7. Добавить запреты на критичные операции.
8. Проверить доступ напрямую и через прокси.
9. Зафиксировать результаты в тестовой матрице.
10. Настроить регулярный просмотр аудита.
11. Повторить тестирование после миграций и изменений конфигурации.

Главная идея проста: прокси может быть удобным интерфейсом и дополнительным уровнем защиты, но границу доступа к объектам нельзя оставлять только в его конфигурации. Если endpoint доступен напрямую, правила должны находиться и в самом хранилище. Bucket Access Policy превращает это требование из договорённости в проверяемый технический механизм.

Прокрутить вверх