Избыточные права доступа: где ломаются IAM-политики S3
Права, выданные под задачу «на время», почти никогда не отзывают. Сервисному аккаунту добавляют s3:GetObject на весь бакет, чтобы разово выгрузить отчёт: отчёт уехал, задача закрыта — а правило осталось в политике. Через несколько релизов таких «временных» записей десятки, и уже никто не помнит, какая из них чему служит.
Обычный мониторинг этого не поймает: он смотрит на коды ответов, время отклика и доступность, а избыточный доступ не даёт ни ошибок, ни всплесков. Запросы валидные, ответы успешные, графики ровные — тревог нет, хотя данные давно читает тот, кому они не предназначены. Публичный доступ к объектам опасен именно этим: хранилище работает штатно, а отказ уже случился.
Ломается всё на трёх типовых местах. Подстановочный знак в Resource или Action — политика выглядит узкой по названию, но покрывает весь бакет. Отсутствие Condition по источнику запроса: правило не привязано ни к сети, ни к конкретной роли. И ACL на уровне отдельного объекта, который расширяет доступ вопреки политике бакета, если нигде не стоит явный запрет.
Лечится это не разовой чисткой, а режимом: принцип наименьших привилегий плюс регулярный аудит доступа к хранилищу — сверка выданных прав с теми, что реально используются по логам обращений. Разбираем, как это выстроить так, чтобы «временное» перестало становиться постоянным.
Что перестаёт ломаться после строгих политик доступа
Явный запрет в политике корзины перебивает любое разрешение, поэтому контур сначала закрывается целиком, а доступ выдаётся точечно. Политика на корзине отвечает за то, что можно делать с объектами: по префиксу, из каких сетей, только по защищённому каналу. IAM-правила отвечают за то, кому это доступно. Пока уровни смешаны, права висят на аккаунте, а не на роли.
| Класс отказа | Как возникал | Что стало после политик |
|---|---|---|
| Доступ живёт после увольнения | права на личном аккаунте | роль исключена из группы — доступ закрыт |
| Данные уходят из закрытого контура | флаг публичного доступа на объекте | публичный доступ запрещён на уровне корзины |
| Чтение чужих префиксов | одна широкая политика на всех | доступ выдан по префиксу и роли |
Выдача доступа становится операцией на минуты: роль с нужным набором прав описана как код в Terraform, её остаётся назначить сотруднику. Отзыв прав при увольнении — одно исключение из группы, закрываются и долгоживущие ключи, и временные токены. Смена роли сотрудника внутри команды перестаёт требовать новой заявки: права пересобираются при переводе.
Доля данных в закрытом контуре растёт не за счёт ручных проверок, а потому что публичный доступ запрещён на самой корзине и обойти его случайным флагом нельзя. Уходят совсем: чтение чужих префиксов, доступ после увольнения, объект в открытом доступе из-за ошибки в скрипте.
Способы разграничения доступа: сравнение вариантов
Разграничение доступа в объектном хранилище решается на четырёх уровнях — от IAM-политик провайдера до собственной проверки перед выдачей ссылки. Выбор зависит от того, кто запрашивает объект: внутренний сервис, внешний партнёр или браузер конечного пользователя.
Ни один уровень не закрывает задачу целиком. Правила провайдера не знают вашей бизнес-логики, а собственная проверка не спасает от обхода через прямой адрес. Поэтому уровни комбинируют: снизу жёсткая основа, сверху точечные исключения.
| Вариант | Когда подходит | Ограничения |
|---|---|---|
| Правила провайдера (IAM) | Сервисные учётные записи внутри платформы, доступ на чтение или запись целиком | Нет контекста приложения, права раздаются широкими группами |
| Правила уровня бакета | Публичный или партнёрский обмен, один набор условий на весь контейнер | Гранулярность до отдельного объекта ограничена, изоляция достигается дроблением бакетов |
| Правила уровня объекта и временные ссылки на файлы | Выдача файла конкретному пользователю на короткий срок, без раскрытия ключей | Ссылку нельзя отозвать до истечения срока: при утечке она работает у любого |
| Собственный слой авторизации перед выдачей ссылки | Роли, владельцы, арендаторы, аудит каждого обращения | Дополнительный сервис в критическом пути — нужен запас по отказоустойчивости |
Как выбрать уровень
Если правило описывается одним условием, держите его на стороне провайдера. Если решение зависит от данных в вашей базе — выносите проверку в собственный слой, а временную ссылку делайте короткоживущей.
Порядок внедрения: от инвентаризации до передачи
Начинаем с фактов, а не с шаблона: инвентаризация показывает, кто реально обращается к хранилищу — сервисы, аналитика, внешние подрядчики. Без этой карты схема ролей строится наугад, а переключение политик превращается в поиск отказавшего доступа по логам.
- Инвентаризация прав доступа. Выгружаем действующие bucket policy, IAM-роли, публичные ACL и активные ссылки на объекты, сверяем с логами обращений. На выходе — карта «кто, к какому бакету, какие операции» и список лишних разрешений.
- Схема ролей. Роли проектируем под функции — чтение аналитики, запись из сервиса, обмен с подрядчиком по отдельному префиксу, — а не под конкретных людей. У каждой роли минимальный набор действий и условие применения.
- Настройка политик. Ограничение по префиксам, условие на источник запроса, обязательное шифрование, запрет публичного доступа. Явный запрет перекрывает случайно выданные разрешения.
- Проверка на копии данных. Тестовый контур на обезличенной копии: прогоняем чтение, запись, удаление, генерацию ссылок. Отдельно — негативные проверки: доступ закрыт там, где должен быть закрыт.
- Перевод без простоя. Старые и новые политики работают параллельно, переключаем по сервисам с окном наблюдения по логам отказов. Каждый шаг откатывается возвратом предыдущей версии конфигурации.
- Регламент. Фиксируем, кто выдаёт доступ, на какой срок, как проходит ревью и что делать при увольнении или смене подрядчика. Правила хранятся в репозитории, изменения — через проверку.
- Передача. Документация, схемы ролей, конфигурации в репозитории и мониторинг отказов в доступе — чтобы команда дальше работала самостоятельно.
Что остаётся у вас на руках: конфигурации и регламенты
Мы передаём не архив с настройками, а рабочий контур доступа, который ваша команда сможет читать и менять без нас. Конфигурации IAM-политик и bucket policies лежат в репозитории рядом с инфраструктурным кодом: изменение проходит проверку и остаётся в истории — видно, кто и когда расширил права.
- Матрица доступа — таблица «субъект → ресурс → действие → условие»: по ней видно, почему конкретный сервис или сотрудник имеет право на нужный префикс бакета и где граница его полномочий.
- Схема ролей — какие роли существуют, чем отличаются, какие выдаются людям через федерацию, а какие — сервисам; отдельно помечены роли с правом менять сами политики.
- Рабочие конфигурации — версионированные файлы политик с осмысленными именами и комментариями вместо выгрузки из консоли.
- Регламент выдачи прав — порядок: заявка, согласование владельцем данных, срок действия, автоматический отзыв; для постоянных ролей — периодический пересмотр.
- Документация по доступу — имена бакетов, точки входа, порядок ротации учётных данных и типовые ошибки при обращении.
- Описание интеграций — какие системы обращаются к хранилищу, с какими правами и что произойдёт с ними при смене политики.
- Процедура проверки — набор обращений, который должен проходить, и набор, который должен получать отказ.
На выходе остаётся управляемая система, а не разовая настройка: доступ новому подрядчику выдаётся по регламенту, а при разборе инцидента есть журнал изменений и понятная точка отката.
Кейс: как закрыли публичный доступ к объектному хранилищу
Что было до изменений
Аудит начали с инвентаризации: прошли по всем бакетам во всех аккаунтах и сверили bucket policy, ACL и настройки блокировки публичного доступа. Результат — 17% бакетов отдавали объекты анонимно: часть открыли под быстрый запуск и не закрыли, часть — когда подрядчикам выдавали ссылки «на время». Отдельная беда — наследование: правка политики одного бакета незаметно открывала соседние, и в S3-совместимом хранилище MinIO это ловилось только ручной сверкой.
Процессы добавляли своё. Права выдавались через тикеты: 2-3 дня от заявки до рабочего доступа, а отзыв при увольнении затягивался до недели — ключи всё это время оставались активными. Поддержка получала несколько десятков обращений в месяц только по доступам, и половина — «дайте то же, что у коллеги».
Что изменили и что получили
Публичность закрыли на двух уровнях: на аккаунте включили блокировку публичных политик, а для сервисов с реальной внешней аудиторией оставили точечные условия — явный список источников и короткий срок жизни ссылок. Права перевели на роли с атрибутами: доступ определяется тегами пользователя и бакета, а не ручным перечислением. Новая команда получает нужный набор автоматически, при смене роли старые разрешения снимаются вместе с тегом.
Выдачу сделали самообслуживанием через каталог ролей. Метрики после: доля анонимно доступных бакетов — ноль, полный аудит прав занимает один прогон скрипта вместо недели ручной сверки, обращения в поддержку по доступам сократились в пять раз.
Чем IAM-политики S3 отличаются от правил бакета
У объектного хранилища, например Amazon S3, два независимых уровня доступа. Политика IAM крепится к субъекту — пользователю, роли, сервисной учётной записи — и отвечает, что этот субъект вправе делать: какие действия и над какими ресурсами. Правило бакета крепится к самому бакету, содержит указание на субъект и описывает обратную сторону — кто снаружи обращается к данным, включая другие аккаунты.
При пересечении модели складываются по логике: разрешено, если разрешено хотя бы где-то, запрещено — если запрещено хоть где-то. Разрешения объединяются, а у явного запрета высший приоритет: один Deny в IAM-политике или в правиле бакета перекрывает любое число Allow, в том числе выданных через группу. Это и есть приоритет запрета — барьер, который не обходится добавлением нового разрешения.
Отсюда практическое разделение. Внутри одного аккаунта хватает ролей IAM: команды и внутренние сервисы получают доступ через роль, а не через статичные ключи. Правило бакета нужно там, где обращение идёт извне — партнёрский аккаунт, внешний сервис, раздача статики. В смешанной схеме ограничения уровня организации работают верхней границей, которую не расширит ни одна из моделей.
Настраиваем от запретов: сначала закрываем публичный доступ и выгрузку без шифрования канала, затем добавляем разрешения по префиксам и ролям. Разбор отказа идёт в том же порядке — IAM, правило бакета, ограничения организации; чаще всего доступ блокирует не отсутствующий Allow, а явный Deny в одном из слоёв.
Технические сомнения по разграничению доступа
Сомнения по разграничению доступа обычно касаются не самой политики, а того, как она уживётся с уже работающими интеграциями и что произойдёт при ошибке в правилах. Ниже — пять технических вопросов, которые задают чаще всего перед стартом.
Не сломает ли новая IAM-политика действующие интеграции?
Сначала снимаем текущие обращения и собираем карту: кто к каким префиксам ходит. Затем выдаём явные разрешения и включаем запрет по умолчанию — сервис, не попавший в карту, отваливается на проверке, а не в проде.
Можно ли выдать доступ к отдельным объектам, а не ко всему хранилищу?
Можно: политика описывает и префиксы, и конкретные ключи. Для точечной выдачи разрешаем один объект, для выборок — префикс с условием по имени. Подрядчик получает ровно те файлы, которые нужны.
Как давать доступ внешним подрядчикам, чтобы не раздавать общий ключ?
Внешним выдаём отдельного пользователя или роль с ограничением по сроку и диапазону адресов. Доступ отзывается одной правкой, а история обращений остаётся в логах.
Как убедиться, что правила работают именно так, как задумано?
После применения прогоняем сценарии: служебные ключи, ключи подрядчика, анонимный запрос, запрос к соседнему префиксу. Ожидаемый ответ — разрешено или отказ — фиксируем, чтобы регресс ловился при следующем изменении.
Что будет с данными, если правило отзовёт нужные права?
Данные не пострадают: меняется только ответ на запрос. Откат — возврат предыдущей версии политики, это минуты, без повторной выгрузки и заливки объектов.
Обсудим задачу: разграничение доступа к хранилищу
Оценка работ по доступу к хранилищу строится не на общем описании, а на фактическом состоянии политик. Пока не видно, кто и через какую роль читает данные сегодня, любые сроки будут гаданием — поэтому просим прислать несколько артефактов, и на их основе собираем план:
- выгрузку действующих правил: bucket-политики, IAM-политики ролей и пользователей, а также ACL, если они ещё применяются;
- карту хранилищ: бакеты, их назначение, какие наборы данных и насколько чувствительные в них лежат;
- перечень сервисов и приложений с указанием ролей, под которыми они обращаются к хранилищу, включая внешние интеграции;
- требования по разграничению: кому и к какому префиксу нужен доступ, где важны ограничения по сети, времени или источнику запроса;
- настройки логирования обращений: куда пишутся события доступа и как долго хранятся;
- ограничения на вашей стороне: окна обслуживания, регламент согласования правок, кто подтверждает изменения в проде.
В ответ возвращаемся с планом внедрения: схема наследования доступов, разделение по ролям под реальные задачи сервисов, готовые черновики политик рядом с конфигурацией сервиса и порядок выката с точками отката.
Первый шаг занимает немного и сразу даёт результат:
- Инвентаризация: сводим все правила в одну таблицу «кто — что — откуда».
- Фиксация избыточных доступов и мест, где права шире необходимого.
- Настройка логирования и тестового стенда, где правки проверяются без риска для прода.
- Передача плана внедрения и согласование порядка выката.
Опишите задачу и приложите то, что есть под рукой, — вернёмся с оценкой и планом.







