Модель облачной идентичности
Облачная авторизация — не один ACL, а несколько политик, вычисляемых вместе: запрос разрешён, только если какая-то явно разрешает и ни одна не запрещает. Разберись с принципалом, типами политик и порядком deny-by-default — и загадки доступа исчезают.
Ваш сервис читает бакет, к которому ему никогда не выдавали доступ. Вы смотрите в политику роли — про этот бакет ни слова. Смотрите в политику бакета — она разрешает доступ совсем другому аккаунту. И всё же aws s3 ls работает. Никто не ошибся в очевидном файле, потому что облачная авторизация никогда не решается одним файлом. Решение — это слияние нескольких документов: identity-политики вызывающего, политики самого ресурса, общеаккаунтного ограничителя, может быть, permission boundary, — вычисляемых по фиксированным правилам. Бакет читался, потому что его resource-политика выдала доступ вашей роли, а у роли не было deny, чтобы это перебить. Пока вы не сможете прокрутить это слияние в голове, каждый сюрприз с облачным доступом выглядит как магия.
К концу урока вы сможете назвать, кто такой принципал, перечислить типы политик, которые крепятся к идентичностям и к ресурсам, и провести запрос на авторизацию через точное deny-by-default вычисление, которое запускают облачные платформы.
Принципал: кто спрашивает
Каждое решение об авторизации начинается с принципала — сущности, делающей запрос. В облачной IAM-модели принципал почти никогда не человек, набирающий пароль. Это идентичность, которая уже прошла аутентификацию и теперь несёт набор короткоживущих учёток: IAM-роль, принятая EC2-инстансом; Kubernetes service account, обменянный на облачный токен через workload identity; CI-задача, федерировавшаяся по OIDC-токену; execution-роль Lambda. Логин уже состоялся; к моменту вычисления политики платформа знает, какой принципал спрашивает. IAM — это слой авторизации, а не аутентификации: он отвечает на вопрос «можно ли этому уже-известному принципалу выполнить это действие над этим ресурсом», и именно с этого различия начинается бо́льшая часть путаницы.
Это важно по зрелой причине: принципалы обычно машины, и они долгоживущие по замыслу, но короткоживущие по учётке. У роли нет пароля; принципал принимает её и получает учётки, истекающие через час. Поэтому интересная поверхность атаки — не «угадать пароль», а «кому разрешено стать этим принципалом», и это управляется отдельной политикой, к которой мы перейдём.
Два вопроса политики: identity против resource
Облачная платформа задаёт один и тот же запрос с двух сторон, и обе должны согласиться.
- Identity-политика крепится к принципалу (роли, пользователю, группе). Она говорит, что может этот принципал: «роль
report-writerможетs3:PutObjectвreports-bucket». Читайте как предложение, начинающееся с актора. - Resource-политика крепится к самому ресурсу (S3-бакету, SQS-очереди, KMS-ключу). Она говорит, кто может действовать над этим ресурсом: «этот бакет разрешает
arn:aws:iam::111:role/report-writerчитать». Читайте как предложение, начинающееся с объекта.
Один и тот же доступ можно выразить с любой из сторон, и именно комбинация делает межаккаунтный доступ работающим без правки ролей в чужом аккаунте. Если у бакета аккаунта A есть resource-политика, называющая роль аккаунта B, а у роли B есть identity-политика, разрешающая действие, запрос проходит — в одиночку ни одна сторона не смогла бы выдать его через границу аккаунтов. Это же — самый частый источник «почему оно это читает?»: грант живёт на ресурсе, а не на идентичности, поэтому чтение только политики роли никогда этого не объяснит.
| Тип политики | Крепится к | Отвечает на | Типичное применение |
|---|---|---|---|
| Identity-based | Роль / пользователь / группа | Что может этот принципал? | Выдать приложению его права |
| Resource-based | Бакет / очередь / ключ | Кто может действовать над ресурсом? | Межаккаунтный доступ |
| Trust-политика | Роль (дверь AssumeRole) | Кто может стать принципалом? | Федерация, цепочки ролей |
| Permission boundary / SCP | Идентичность / весь аккаунт | Каков потолок, независимо от грантов? | Организационные ограничители |
Trust-политика: кто может стать этим принципалом
У роли есть дверь, и trust-политика — это замок на ней. Это resource-политика, живущая на роли и отвечающая на вопрос, которого identity-политика вообще не касается: кому в принципе разрешено принять эту роль? Identity-политика говорит, что роль может, когда вы уже внутри; trust-политика говорит, можете ли вы войти.
Это разделение — весь движок облачной федерации. Задача GitHub Actions, предъявляющая OIDC-токен, может стать deploy-ролью только потому, что trust-политика этой роли говорит: «принципалы, федерированные от этого OIDC-провайдера, с этим claim sub, могут меня принять». Человек из вашего SSO-каталога становится admin-ролью, потому что trust-политика доверяет провайдеру идентичности. Ошибитесь в trust-политике — доверьте * или OIDC sub с небрежным wildcard вроде repo:org/* — и вы отдали роль всему интернету, как бы туго ни была закручена её identity-политика. Trust-политика — это та часть, о существовании которой джуны забывают, а сеньоры ревьюят первой, потому что это и есть настоящая граница аутентификации для машинного принципала.
▸Почему это работает
Почему две отдельные политики на одной роли, а не один объединённый список прав? Потому что «кому можно пользоваться этой возможностью» и «что эта возможность может делать» меняются независимо и принадлежат разным людям. Платформенная команда курирует trust-политику (какие OIDC-провайдеры, какие аккаунты, какие SSO-группы могут принять роль); прикладная команда курирует identity-политику (какие API-вызовы нужны приложению). Слияние заставило бы каждое изменение прав заново открывать границу федерации, а каждое изменение федерации — заново аудитить права приложения. Отделить дверь от комнаты — это least privilege, применённый к самой структуре.
Как решение вычисляется на самом деле
Вот правило, которое растворяет загадки. Облачная авторизация — deny by default, и вычисление — это не «найти подходящий allow», а фиксированный приоритет:
- Старт — неявный deny. Ни одно утверждение не подходит → запрос отклонён. Ничего не разрешено, пока что-то не выдаст грант.
- Явный
Denyвсегда побеждает. Если любая применимая политика — identity, resource, boundary или организационный ограничитель — содержит подходящийDeny, запрос отклонён, точка. Никакой allow его не перебьёт. - Явный
Allowснимает неявный deny — но только в пределах потолка. Permission boundary или организационный ограничитель (SCP) могут урезать достижимое, даже когда identity-политика говоритAllow; эффективное право — это пересечение выданного и того, что разрешает потолок.
Так что реальное вычисление таково: deny, пока (какая-то политика разрешает) И (ни одна не запрещает) И (грант внутри каждого применимого потолка). Этот порядок объясняет, почему чрезмерно широкий Allow * гораздо менее опасен, чем боятся, когда его ограничивает boundary, — и почему один залётный Deny способен сломать деплой, у которого «очевидно есть права».
Чтение реального гранта: межаккаунтный доступ в одном слиянии
Прокрутим стартовую загадку через модель. Ваша роль в аккаунте B может s3:ListBucket на бакете в аккаунте A. Проследим:
- Trust-политика роли B разрешает вашему EC2-инстансу / CI-задаче принять её. Аутентификация машинного принципала: готово.
- Identity-политика роли B разрешает
s3:ListBucketнаarn:...:bucket-in-A. Сторона гранта со стороны B: есть. - Resource-политика бакета A разрешает
arn:aws:iam::B:role/yoursделатьs3:ListBucket. Сторона гранта со стороны A: есть. - Нигде нет
Deny, и действие лежит внутри permission boundary роли B и организационных ограничителей A.
Обе стороны согласились, ни один deny не вмешался, потолок разрешил → разрешено. Причина, по которой чтение одной роли не сработало, в том, что половина гранта жила на ресурсе в другом аккаунте. Зрелая привычка — для любого неожиданного доступа спросить: «покажите мне каждую политику в слиянии — обе идентичности, оба ресурса, boundary, организационный ограничитель, — а не только ту, которую я ожидал».
GitHub Actions-воркфлоу должен деплоить в ваш облачный аккаунт. Вы хотите, чтобы CI принимал deploy-роль. Какой подход — корректная модель идентичности?
Identity-политика на роли говорит `Allow s3:*`, но permission boundary на той же роли разрешает только `s3:GetObject`. Принципал вызывает `s3:DeleteObject`. Что произойдёт?
Бакет в аккаунте A читается ролью в аккаунте B, но identity-политики A никогда не упоминают B. Где живёт грант и почему чтение роли B его не объяснило?
Упорядочьте шаги вычисления решения об облачной авторизации, от старта до вердикта:
- 1 Старт с неявного deny (по умолчанию ничего не разрешено)
- 2 Проверить каждую применимую политику на явный Deny — если хоть один подходит, отклонить безусловно
- 3 Искать явный Allow в identity- или resource-политике
- 4 Подтвердить, что грант лежит внутри потолка permission boundary / организационного ограничителя
- 5 Разрешить только если allowed, не denied и внутри потолка
- 01Различите identity-политику принципала, resource-политику и trust-политику. На какой вопрос отвечает каждая и почему они разделены?
- 02Пройдите через то, как облачная платформа вычисляет запрос на авторизацию, включая роль permission boundary.
Облачный IAM никогда не решает доступ по одному файлу. Принципал — это уже-аутентифицированная идентичность, почти всегда машина с короткоживущими принятыми учётками, а не человек с паролем. Сливаются три вопроса политики: identity-политика говорит, что может принципал, resource-политика говорит, кто может действовать над ресурсом (механизм межаккаунтных грантов), а trust-политика на роли говорит, кому вообще можно стать этим принципалом — реальная граница аутентификации и то, что сеньоры ревьюят первым, потому что wildcard там раздаёт роль всем. Вычисление — deny-by-default с фиксированным приоритетом: любой явный Deny побеждает, явный Allow снимает неявный deny, но только внутри потолка permission boundary или организационного ограничителя, поэтому эффективное право — пересечение гранта и потолка. В следующий раз, когда доступ вас удивит, не читайте одну политику — прокрутите всё слияние: обе идентичности, оба ресурса, trust-дверь и каждый потолок.
Практика
Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.
Что-то непонятно?
Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.