Разбираем AccessDenied в AWS без гаданий: IAM SimulatePrincipalPolicy, CloudTrail и ранжирование причин. Научитесь находить точную причину за минуты.
Каждый, кто работал с AWS, сталкивался с ошибкой AccessDenied. Вы копируете текст ошибки, вставляете его в чат с ИИ и получаете список из пяти возможных причин. Все они выглядят правдоподобно, но ни одна не проверена. Это не ответ, а приглашение к долгому клику по консоли IAM. Я решил эту проблему, создав инструмент Rosetta, который не гадает, а проверяет. В этой статье я расскажу, как он устроен, и покажу, как вы можете использовать его в своих проектах.
Текст ошибки AccessDenied при вызове GetObject может означать множество разных проблем: отсутствие Allow в политике, явный Deny, ограничение SCP, permissions boundary, политика ресурса или отсутствие KMS-ключа. Шесть разных причин — одно сообщение. Ни одна модель ИИ не сможет определить точную причину, потому что в самом тексте просто нет нужной информации. Единственный способ — запросить аккаунт, который выдал ошибку.
AWS предоставляет API iam:SimulatePrincipalPolicy, который оценивает политики точно так же, как это делает IAM во время запроса. Он возвращает решение allow/deny, а также точное выражение, вызвавшее решение, и отдельные флаги для SCP и boundary. Вот как это выглядит в коде:
def simulate(session, principal_arn, action, resource):
if not (principal_arn and action):
return []
iam = session.client('iam')
resp = iam.simulate_principal_policy(
PolicySourceArn=principal_arn,
ActionNames=[action],
ResourceArns=[resource] if resource and resource.startswith('arn:aws') else ['*'],
)
out = []
for r in resp.get('EvaluationResults', []):
detail = {
'action': r.get('EvalActionName'),
'decision': r.get('EvalDecision'),
'allowed_by_organizations': r.get('OrganizationsDecisionDetail', {}).get('AllowedByOrganizations'),
'allowed_by_boundary': r.get('PermissionsBoundaryDecisionDetail', {}).get('AllowedByPermissionsBoundary'),
'matched_statements': [m.get('SourcePolicyId') for m in r.get('MatchedStatements', [])],
}
out.append(Evidence(kind='simulate', source='iam:SimulatePrincipalPolicy', detail=json.dumps(detail), authoritative=True))
return outОбратите внимание на поля allowed_by_organizations и allowed_by_boundary. Они позволяют различить, кто именно заблокировал доступ: SCP, permissions boundary или обычная политика. Это три разных исправления, и API даёт их напрямую.
Rosetta — это конвейер из четырёх этапов: парсинг ошибки, обогащение через simulate и CloudTrail, опциональное рассуждение через Bedrock и ранжирование причин. Каждый этап — чистая функция, что упрощает тестирование. Главное решение — гибридный подход: текстовый режим не требует никаких прав доступа и работает с любой ошибкой, а обогащённый режим использует read-only роль для проверки.
Парсер — это простые регулярные выражения, которые извлекают сервис, операцию, principal и ресурс из текста ошибки. Например:
_CODE = re.compile(r"An error occurred \(([A-Za-z0-9.]+)\)")
_OP = re.compile(r"when calling the (\w+) operation")
_PRINCIPAL = re.compile(r"(?:User|Role):\s*(arn:aws[^\s]+)")
_ACTION = re.compile(r"perform:\s*([a-zA-Z0-9]+:[A-Za-z0-9*]+)")
_RESOURCE = re.compile(r"on resource:\s*(arn:aws[^\s]+|\*|[^\s]+)")Если парсер ошибётся в извлечении ARN principal, весь последующий анализ будет неверным, поэтому эти регексы — ключевой элемент.
Для обогащённого режима нужна роль, которую Rosetta может принять. Она должна иметь только права на чтение: simulate, чтение политик, CloudTrail. Пример политики:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "RosettaSimulate",
"Effect": "Allow",
"Action": [
"iam:SimulatePrincipalPolicy",
"iam:GetPolicy",
"iam:GetPolicyVersion",
"iam:ListAttachedRolePolicies",
"iam:ListAttachedUserPolicies"
],
"Resource": "*"
},
{
"Sid": "RosettaResourcePolicies",
"Effect": "Allow",
"Action": [
"s3:GetBucketPolicy",
"kms:GetKeyPolicy"
],
"Resource": "*"
},
{
"Sid": "RosettaCloudTrail",
"Effect": "Allow",
"Action": ["cloudtrail:LookupEvents"],
"Resource": "*"
}
]
}Роль ничего не может изменить в аккаунте. Вы передаёте её ARN, Rosetta принимает её через STS, выполняет чтение и завершает сессию.
Чтобы не тестировать на реальной инфраструктуре, я создал CloudFormation стек с заведомо сломанным IAM. Пользователь имеет Allow на S3, но явный Deny на один bucket. Это позволяет увидеть, как Rosetta находит причину. Пример шаблона:
VictimUser:
Type: AWS::IAM::User
Properties:
UserName: rosetta-victim
Policies:
- PolicyName: rosetta-victim-policy
PolicyDocument:
Version: "2012-10-17"
Statement:
- Sid: AllowS3Read
Effect: Allow
Action: s3:GetObject
Resource: "*"
- Sid: DenySecretBucket
Effect: Deny
Action: s3:GetObject
Resource: arn:aws:s3:::rosetta-demo-secret/*Разверните стек, запустите Rosetta, получите результат, затем удалите стек — и никакого мусора.
Хотите показать реальный запрос, который был отклонён? CloudTrail не позволяет искать по requestID. Вместо этого ищите по EventName и фильтруйте по requestID внутри события. Вот код:
def cloudtrail_match(session, operation, principal_arn, request_id):
ct = session.client('cloudtrail')
resp = ct.lookup_events(
LookupAttributes=[{"AttributeKey": "EventName", "AttributeValue": operation}],
MaxResults=20,
)
for ev in resp.get('Events', []):
record = json.loads(ev.get('CloudTrailEvent', '{}'))
if request_id and record.get('requestID') != request_id:
continue
# нашли точный вызов
...Учтите, что CloudTrail имеет задержку — свежая ошибка может не иметь события. Rosetta честно сообщает об этом, а не выдумывает.
Когда я запустил Rosetta на своём сломанном стеке, она уверенно сказала: «Никакая политика не разрешает это действие (неявный deny)». Но это было не так. Оказалось, что мой код ранжирования неправильно обрабатывал случай, когда simulate возвращает explicitDeny. Я исправил это, добавив приоритет для authoritative evidence. Это напоминание, что даже хорошие инструменты нужно тестировать на реальных сценариях.
Не тратьте время на гадание. Используйте iam:SimulatePrincipalPolicy для проверки. Создайте read-only роль, настройте Rosetta или напишите свой скрипт. Это сэкономит часы отладки и сделает вашу работу с AWS надёжнее.
Хочешь закрепить знания на практике?
Решай задачи на Algolit — интерактивная платформа для обучения
Начать бесплатно →