この記事で分かること
simulate_principal_policyでResourceArnsを省略すると、正しいポリシーでもimplicitDenyが返るケースがあるContextEntriesにaws:RequestedRegion等を渡さないと、DenyRegion 型 SCP に引っかかって誤判定になる- Condition 付き SCP はそもそもシミュレーターで評価できないという公式の制約がある
- これらを事前に見抜くための確認方法・ベストプラクティス
背景
AWS CLI v2.32.0 で追加された aws login コマンド。既存の AWS マネジメントコンソールのサインイン認証情報を使い、ブラウザ経由の OAuth2 認証フローで一時的な認証情報を取得して CLI/SDK で利用可能にする機能です。
Organizations 配下の全アカウントについて「どの IAM ユーザー・ロールが aws login を実行できるのか」を一括調査する必要があり、simulate_principal_policy API を使って自動化しました。
必要な権限は signin:AuthorizeOAuth2Access と signin:CreateOAuth2Token の2つ。AWS マネージドポリシー SignInLocalDevelopmentAccess で定義されているアクションです。このポリシーの Resource は arn:aws:signin:*:*:oauth2/public-client/* とワイルドカードで定義されており、localhost(同一デバイス)と remote(aws login --remote)の両方のリソースタイプを許可しています。
参考: SignInLocalDevelopmentAccess - AWS Managed Policy Reference
ところが、いざスクリプトを回してみると AdministratorAccess(Action: "*")がアタッチされたロールですら implicitDeny という結果に。
ここから3つの落とし穴にハマり、1つずつ潰していくことになりました。
落とし穴1: ResourceArns を指定しないと常に implicitDeny
最初に書いたコードはこんな感じでした(簡略化しています)。
response = iam.simulate_principal_policy(
PolicySourceArn=role_arn,
ActionNames=["signin:AuthorizeOAuth2Access"],
)
# → EvalDecision: "implicitDeny" 🤔
AdministratorAccess(Action: "*", Resource: "*")がアタッチされているロールなのに implicitDeny。ポリシー的には許可されているはずなのに...
原因: リソースタイプが必須のアクションだった
simulate_principal_policy の ResourceArns パラメータを省略すると、デフォルトで *(全リソース)として扱われます。
Each API in the ActionNames parameter is evaluated for each resource in this list. If this parameter is not provided, then the value defaults to
*(all resources).
一見問題なさそうですが、signin:AuthorizeOAuth2Access はリソースタイプが必須のアクションです。* ではリソースが特定できず、シミュレーターは許可を出せません。
正しいリソース ARN を指定する必要がありました。
response = iam.simulate_principal_policy(
PolicySourceArn=role_arn,
ActionNames=["signin:AuthorizeOAuth2Access"],
ResourceArns=[
f"arn:aws:signin:us-east-1:{account_id}:oauth2/public-client/localhost",
],
)
なお、aws login --remote の権限も調査する場合は、リソース ARN を oauth2/public-client/remote に変えて別途シミュレーションする必要があります。
リソースタイプが必須かどうかの確認方法
Service Authorization Reference の各サービスページで確認できます。Actions テーブルの「Resource types」列を見てください。
Required resources are indicated in the table with an asterisk (*). If you specify a resource-level permission ARN in a statement using this action, then it must be of this type.
signin:AuthorizeOAuth2Access の場合、AWS Sign-In のページ を見ると、Resource types 列に以下が * マーク付きで記載されています。
oauth2-public-client-localhost *oauth2-public-client-remote *
この * マークが付いているアクションでは、simulate_principal_policy の ResourceArns に具体的な ARN を渡さないと正しい結果が得られません。
なお、これをプログラム的に判定する API は現時点では提供されていないため、Service Authorization Reference を手動で確認する必要があります。
落とし穴2: ContextEntries でリージョンを指定しないと DenyRegion SCP に引っかかる
ResourceArns を修正して再実行。しかしまだ全員 implicitDeny。
デバッグのために s3:GetObject のような明らかに許可されるべきアクションをシミュレーションしてみると、それすら implicitDeny。レスポンスの OrganizationsDecisionDetail に AllowedByOrganizations: False の表示。SCP が原因でした。
原因: DenyRegion 型 SCP と aws:RequestedRegion
調査環境には以下のような DenyRegion 型の SCP が適用されていました。
{ "Sid": "DenyRegion", "Effect": "Deny", "NotAction": ["chatbot:*", "iam:PassRole", ...], "Resource": "*", "Condition": { "StringNotEquals": { "aws:RequestedRegion": ["ap-northeast-1", "ap-northeast-3", "us-east-1"] } } }
この SCP は「許可リストにないリージョンへのリクエストを Deny する」ものです。実際のリクエストでは aws:RequestedRegion は常にコンテキストに含まれますが、シミュレーターではデフォルトで何も渡されません。
If you don't provide a value for a context key, the simulation still runs. But the results might not be reliable because the policy simulator cannot include that context key in the evaluation.
― Testing IAM policies with the IAM policy simulator(AWS CLI and AWS API セクション)
リージョン情報がないため、SCP の Condition がまともに評価できず、結果として全アクションが Deny 扱いになっていたわけです。
修正: ContextEntries にリージョンを追加
response = iam.simulate_principal_policy(
PolicySourceArn=role_arn,
ActionNames=["signin:AuthorizeOAuth2Access"],
ResourceArns=[
f"arn:aws:signin:us-east-1:{account_id}:oauth2/public-client/localhost",
],
ContextEntries=[
{
"ContextKeyName": "aws:RequestedRegion",
"ContextKeyValues": ["us-east-1"],
"ContextKeyType": "string",
},
],
)
ContextEntries は常に必要?
常に必要なわけではありません。 ポリシーの Condition 要素にそのコンテキストキーが含まれている場合のみ影響します。
ただし、どのコンテキストキーが必要かを事前に知るのは難しいですよね。そこで使えるのが GetContextKeysForPrincipalPolicy API です。
Use GetContextKeysForPrincipalPolicy to understand what key names and values you must supply when you call SimulatePrincipalPolicy.
この API は、対象プリンシパルにアタッチされた IAM ポリシーが参照しているコンテキストキーの一覧を返してくれます。simulate_principal_policy を呼ぶ前にこれを実行して、返ってきたキーに適切な値を設定するのがベストプラクティスです。
aws iam get-context-keys-for-principal-policy \ --policy-source-arn "arn:aws:iam::123456789012:role/MyRole"
レスポンスに aws:RequestedRegion が含まれていれば、ContextEntries に渡す必要があるとわかります。
ただし注意点があります。 この API が返すのは IAM エンティティにアタッチされたポリシーのコンテキストキーのみです。SCP は Organizations 管轄のポリシーであり、GetContextKeysForPrincipalPolicy の対象外です。SCP 内で使われているコンテキストキー(今回の aws:RequestedRegion のような)は、この API では検出できない場合があります。
Gets a list of all of the context keys referenced in all the IAM policies that are attached to the specified IAM entity.
SCP のコンテキストキーを確認するには、Organizations API(ListPoliciesForTarget → DescribePolicy)で SCP の内容を取得し、Condition で使われているキーを別途確認する必要があります。
落とし穴3: Condition 付き SCP はシミュレーターで評価できない
落とし穴1・2を修正して再実行。今度はようやく正しそうな結果が返ってきました。
……が、調査を進める中でもう1つ気になる挙動に気づきました。メンバーアカウントからシミュレーターを実行すると、管理アカウントから実行した場合と結果が異なるケースがあったのです。
これは公式ドキュメントにはっきり書かれている制約でした。
You can't test service control policies (SCPs) with any conditions.
Condition 要素を含む SCP は、シミュレーターで正しく評価できません。
「あれ、落とし穴2で ContextEntries にリージョンを渡したら結果が変わったのでは?」と思いますよね。実際に ContextEntries の追加で結果は改善しました。しかし、これはあくまで部分的な改善です。Condition 付き SCP のシミュレーションは公式にサポートされていないため、ContextEntries を正しく渡しても結果の正確性は保証されません。落とし穴2の修正は「明らかに間違った結果を減らす」効果はありますが、「正しい結果を保証する」ものではないという点に注意してください。
さらに、公式ドキュメントには他にも以下の制約が列挙されています。
- リソースベースポリシーは IAM ロールのシミュレーションには含められない(IAM ユーザーの場合は可能)
- クロスアカウントアクセスのシミュレーションはできない
- RCP(リソースコントロールポリシー)は評価対象外
After you test the policies in the IAM policy simulator, make sure to confirm the results with your live AWS environment.
公式も「シミュレーション後は必ず実環境で確認しろ」と言っています。シミュレーターは万能ではなく、あくまで参考ツールという位置づけです。
今回の対応
Condition 付き SCP の影響を正しく評価するために、以下の代替アプローチを取りました。
- 対象ロールにアタッチされたポリシードキュメントを直接取得
- 適用される全 SCP の内容を取得
- ポリシーの Allow/Deny をロジックで自前判定
シミュレーターだけに頼らず、ポリシードキュメントの中身を直接確認することで、より正確な結果を得ることができました。
正しく使ったら結果が変わった
3つの落とし穴を潰した結果、当初は「全員 Deny」だったものが、修正後は実行可能なユーザーが相当数いることが判明しました。
最初の結果をそのまま報告していたら「aws login のリスクはゼロ」という誤った結論になっていたところです。
Policy Simulator を使うときのチェックリスト
今回の経験を踏まえて、simulate_principal_policy を使う際のチェックリストをまとめます。
- ⬜ Service Authorization Reference でリソースタイプを確認したか? —
*マーク付きのアクションにはResourceArnsに具体的な ARN を渡す - ⬜
GetContextKeysForPrincipalPolicyで必要なコンテキストキーを確認したか? — 返ってきたキーに適切な値をContextEntriesで渡す - ⬜ Condition 付きの SCP が適用されていないか? — 適用されている場合、シミュレーターの結果は不正確になる可能性がある
- ⬜ シミュレーション結果を実環境で検証したか? — 公式が推奨する最後のステップ
まとめ
IAM Policy Simulator は権限調査の強力なツールですが、パラメータの渡し方次第で全く異なる結果が返ります。
今回ハマった3つの落とし穴を振り返ると:
- ResourceArns の省略 → リソースタイプ必須のアクションでは常に
implicitDenyになる - ContextEntries の未指定 → Condition を含むポリシー/SCP が正しく評価されない
- Condition 付き SCP の制約 → そもそもシミュレーターでは評価できない公式の制約
特に1と2は、エラーではなく implicitDeny が静かに返ってくるのが厄介です。「ポリシーが許可していないんだな」と素直に受け取ってしまいがちで、パラメータ不足を疑うのが難しい。
シミュレーターの結果は「参考」であって「正解」ではない。 公式ドキュメントの「実環境で確認しろ」というアドバイスを身をもって実感した調査でした。
参考
- Testing IAM policies with the IAM policy simulator - IAM User Guide
- SimulatePrincipalPolicy - IAM API Reference
- GetContextKeysForPrincipalPolicy - IAM API Reference
- Actions, resources, and condition keys for AWS Sign-In
- Actions, resources, and condition keys for AWS services
- SignInLocalDevelopmentAccess - AWS Managed Policy Reference
- Login for AWS local development using console credentials - AWS CLI User Guide
- Simplified developer access to AWS with 'aws login' - AWS Security Blog
- Policy evaluation logic - IAM User Guide