Claude CodeでSCPの拒否とpermissions boundaryを切り分ける
IAMポリシーで許可したのにAWSが拒否するとき、SCPかpermissions boundaryかをエラー文とCLIの出力で切り分けます。Claude Codeに読み取りだけ許可して調べさせる設定例つきです。
AWSの拒否は、エラー文の文末で誰が止めたかが分かる
IAMポリシーで許可したはずの操作がAccessDeniedになるとき、止めているのはIAMの外にあるSCPか、ロールに付いたpermissions boundaryであることが多くあります。どちらなのかは、エラー文の文末を読むだけでかなり絞れます。
AWSのトラブルシュート用ページには、拒否の理由ごとのメッセージ例が載っています。文末の言い回しが違います。
| エラー文の文末 | 止めている層 | 次に見る場所 |
|---|---|---|
because no identity-based policy allows the ... action | 止めている層identity policyにAllowが無い | 次に見る場所ロールに付いたポリシー |
because no service control policy allows the ... action | 止めている層SCPにAllowが無い | 次に見る場所アカウントからrootまでの各階層のSCP |
with an explicit deny in a service control policy | 止めている層SCPのDeny | 次に見る場所メッセージ末尾にARNがあればそのSCP |
because no permissions boundary allows the ... action | 止めている層boundaryにAllowが無い | 次に見る場所ロールに設定したboundary |
with an explicit deny in a permissions boundary | 止めている層boundaryのDeny | 次に見る場所メッセージ末尾にARNがあればそのポリシー |
Denyの場合、ポリシーARNがメッセージに付くことがあり、付かないこともあります。ARNが付いていればそのポリシーのDeny文を探し、付いていなければ対象の層にある全ポリシーのDeny文を探します。
エラー文が手元に残っていない、もしくは別のツールが文言を丸めている場合は、CLIで3層を順に確かめます。この記事の後半は、その手順をClaude Codeに任せる方法です。
有効な権限はidentity policy・boundary・SCPの共通部分になる
3層は足し算ではなく、重なった部分だけが有効になります。
- identity policyとpermissions boundaryは、両者の共通部分が有効権限になる
- 組織のメンバーアカウントでは、identity policy・SCP・RCPの共通部分になる(リソースベースポリシーが無い場合)
- boundaryとSCPが両方ある場合、boundary・SCP・identity policyのすべてが許可して初めて通る
- どの層の明示的なDenyも、ほかの層のAllowを上書きする
「権限は付けたのに拒否される」は、identity policyが十分でも、boundaryかSCPのどちらかが狭いときに起きます。
SCPには、boundaryにない癖が3つあります。
SCPで先に疑う3点
Allowは全階層に必要
アカウントでアクションが許可されるには、rootから対象アカウントまでの経路上のすべての階層に明示的な
Allowが要ります。どこか1階層でも許可が欠けると通りません。Denyは1階層で効く
どの階層に付いたSCPでも、
Denyがあれば拒否されます。別の階層に同じサービスを許可するSCPがあっても覆りません。管理アカウントとサービスリンクロールは対象外
SCPが制限するのはメンバーアカウントだけで、管理アカウントのユーザーやロールには効きません。サービスリンクロールの権限にも効きません。
組織の作成時には、すべてを許可するAWS管理SCPのFullAWSAccessが、root・OU・アカウントのそれぞれに付けられます。これを外してDenyだけのSCPをrootに置くと、メンバーアカウントはどのサービスにも届かなくなります。
boundaryの側にも癖があります。boundaryが効くのは主にidentity policyの許可範囲です。同じアカウント内でIAMユーザーのARNに許可を与えるリソースベースポリシーは、boundaryの暗黙のDenyに制限されません。一方、IAMロールのARNに許可を与えるリソースベースポリシーは、boundaryの暗黙のDenyに制限されます。リソースベースポリシーのあるS3やKMSで挙動が割れて見えるときは、この違いを疑います。
Claude Codeには読み取り系のCLIだけを許可する
切り分けに必要なAWS CLIは、いずれも参照だけです。Claude Codeには参照コマンドだけを許可し、ポリシーの付け替えや削除はdenyに置いておきます。
{
"permissions": {
"allow": [
"Bash(aws sts get-caller-identity *)",
"Bash(aws iam get-role *)",
"Bash(aws organizations list-parents *)",
"Bash(aws organizations list-policies-for-target *)",
"Bash(aws organizations describe-policy *)"
],
"deny": [
"Bash(aws organizations attach-policy *)",
"Bash(aws organizations detach-policy *)",
"Bash(aws organizations update-policy *)",
"Bash(aws iam put-role-permissions-boundary *)",
"Bash(aws iam delete-role-permissions-boundary *)"
]
}
}ルールの評価順はdeny、ask、allowで、最初に一致したものが結果を決めます。許可と拒否が両方に一致するコマンドは拒否されるので、Bash(aws *)のような広いdenyを置くと上のallowは働きません。
もう1点あります。Bashのdenyルールは、Claudeが書いたコマンド文字列に一致を見る仕組みで、プログラムそのものを囲う境界ではありません。sh -cで包んだ形などは止まらないことがあるため、本番アカウントの認証情報を持つ環境では、AWS側の権限でも書き込みを絞る前提にします。AWS側でも書き込みを絞った読み取り専用のロールを別に用意します。Terraform系の変更を検証付きで通す設計はClaude CodeでOpenTofuを使う権限設計にあります。
SCPとboundaryを切り分ける4つの手順
エラー文が文末まで残っているなら、その層だけ調べれば済みます。残っていないときは次の順に進めます。
4コマンドで拒否元を絞る
- 1
誰として動いているかを確かめる
aws sts get-caller-identityのArnから、ロール名とアカウントIDを取ります。 - 2
ロールにboundaryが付いているかを見る
aws iam get-roleの出力にPermissionsBoundaryがあれば、そのPermissionsBoundaryArnが境界のポリシーです。 - 3
アカウントからrootまでの親をたどる
aws organizations list-parentsで親のOUかrootを取り、rootに着くまで繰り返します。 - 4
各階層のSCPを列挙して中身を読む
階層ごとに
list-policies-for-targetでSCPを列挙し、describe-policyでContentを読みます。
実際のコマンドは次のとおりです。
aws sts get-caller-identity
aws iam get-role --role-name DevRole
aws organizations list-parents --child-id 444444444444
aws organizations list-policies-for-target \
--filter SERVICE_CONTROL_POLICY \
--target-id 444444444444
aws organizations describe-policy --policy-id p-examplepolicyid222DevRoleと各IDは例です。list-policies-for-targetの--filterは必須で、SCPを調べるならSERVICE_CONTROL_POLICYを指定します。--target-idにはroot、OU、アカウントのいずれかのIDを渡せます。
出力は次のような形になります(コマンドリファレンスの例に沿った形です)。
{
"Policies": [
{
"Id": "p-examplepolicyid222",
"Arn": "arn:aws:organizations::o-exampleorgid:policy/service_control_policy/p-examplepolicyid222",
"Name": "AllowAllEC2Actions",
"Type": "SERVICE_CONTROL_POLICY",
"AwsManaged": false
}
]
}見るのはNameとIdです。怪しい名前のポリシーにdescribe-policyを当てると、Contentにポリシー本文のJSON文字列が入っています。その中のDeny文とAllow文のアクションを、拒否されたアクションと突き合わせます。
list-policies-for-targetは継承分を返さない
list-policies-for-targetの出力には、OU階層の上位から継承しているポリシーが含まれません。アカウントを指定して「FullAWSAccessだけ」と返ってきても、上位のOUやrootにDenyがあるかもしれません。
だから手順3のlist-parentsが要ります。アカウント、親のOU、さらに上のOU、rootと、経路上のすべての--target-idで列挙し直します。各階層で見るのは2点です。Deny文がないか、サービスを限定するAllowが上位で許可されたアクションを狭めていないか。
Contentのどこを突き合わせるか
describe-policyのContentは、次のようなJSONの文字列です(読み方を示す例で、実在のポリシーではありません)。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Deny",
"Action": "ec2:RunInstances",
"Resource": "*"
}
]
}見る順序は、EffectがDenyの文を先に探し、Actionが拒否されたアクションに一致するかを確かめ、次にConditionの有無を確かめることです。ConditionがあるSCPは、リージョンやタグが条件に合うときだけ拒否します。別のリージョンで同じ操作が通るなら、条件付きのDenyを疑います。
Claude Codeに渡す依頼と、返ってきた出力の読み方
調べる作業は、アクションとロールを指定して頼むと進みます。
AWSで次のエラーが出ています。SCPとpermissions boundaryのどちらが原因か
切り分けてください。変更系のコマンドは実行しないでください。
エラー: User: arn:aws:sts::123456789012:assumed-role/DevRole/... is not
authorized to perform: ec2:RunInstances ... (文末まで貼る)
手順: get-caller-identity → get-role のboundary →
list-parents で経路をたどり、各階層の SCP を describe-policy で読む。
結果は階層ごとに「Allow/Deny の有無」を表にして。返ってきた表は、次の3点で点検します。
- 経路上のすべての階層を調べているか。アカウント直下だけで終わっていないか
- boundaryが付いていない場合に、「boundaryなし」と書かれているか。
PermissionsBoundaryが出力に無いロールは、boundaryを持たない - 拒否の理由が暗黙のDenyなのか明示的なDenyなのか、エラー文の文末と整合しているか
暗黙の拒否なら、必要なAllowを足す場所を決める話になります。SCPなら、足す先は組織の管理者の判断です。ここでClaude Codeにattach-policyやupdate-policyを実行させる前提にはしません。上の設定でdenyに置いているのはそのためです。
Claude Code自身の拒否とAWSの拒否を取り違えない
AWSのCLIがAccessDeniedを返したのなら、リクエストはAWSに届いています。Claude Code側の権限ルールは、コマンドが実行される前に評価されます。denyに一致したコマンドは実行されないので、AWSからのエラー文は返ってきません。
つまり、エラー文にexplicit deny in a service control policyなどの文言があれば、それはAWSの応答です。逆に、コマンドそのものが実行されず権限プロンプトや拒否通知が出るなら、Claude Code側の設定です。後者はClaude Codeの/permissionsコマンドで、どのスコープのどのルールが効いているかを確かめられます。
読み取りだけを許可する運用で、aws organizationsの呼び出しがClaude Codeの確認を毎回求めるなら、allowルールの書式を見直します。ルールはBash(aws organizations list-policies-for-target *)のように、サブコマンド名まで含めて書きます。
まとめ
拒否の層は、まずエラー文の文末で当たりを付けます。service control policyならSCP、permissions boundaryならboundaryです。文末が残っていなければ、get-roleでboundaryを見て、list-parentsでrootまで階層をたどり、各階層のSCPをdescribe-policyで読みます。
効く権限は共通部分なので、足す場所は拒否している層の側です。identity policyをいくら広げても、SCPやboundaryの外には出られません。調査はClaude Codeに任せられますが、ポリシーの変更だけは権限を渡さず、人が決める作業として残しておく運用が安全です。
参照した一次ソース
この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。
- docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_evaluation-logic.h…
- docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_boundaries.html
- docs.aws.amazon.com/IAM/latest/UserGuide/troubleshoot_access-denied.html
- docs.aws.amazon.com/organizations/latest/userguide/orgs_manage_policies_scps.h…
- docs.aws.amazon.com/organizations/latest/userguide/orgs_manage_policies_scps_e…
- docs.aws.amazon.com/cli/latest/reference/organizations/list-policies-for-targe…
- code.claude.com/docs/en/permissions