Claude CodeでIAM最小権限をAccess Analyzerから作る手順
過剰権限のIAMロールを、CloudTrailの利用履歴から作ったポリシーに差し替える流れ。生成、Terraform化、validate-policyの指摘修正をClaude Codeに任せる設定と、生成結果の限界を示します。
広すぎる権限を持つIAMロールを絞るとき、いちばん手間がかかるのは「何が本当に必要か」を洗い出す作業です。IAM Access Analyzerは、CloudTrailのログからロールが実際に使った操作を拾い、ポリシーの下書きを作ります。Claude Codeの出番は、その下書きをTerraformに落とし、validate-policyの指摘を直し、差し替え前の差分を読み上げる後半です。
先に注意点を1つ。下書きは「指定期間に使われた操作」の写しで、正解ではありません。生成の前後で何が抜けるかを知っておくと、差し替えで本番が止まる事故を避けられます。
作業は6段階で、Claude Codeに渡すのは後半の4つ
流れを先に示します。1〜2を人間が行い、3以降をClaude Codeと進める分担を想定しています。
過剰権限ロールを絞る流れ
- 1
1. 前提を満たす
CloudTrailのトレイルが有効で、Access Analyzerが読めるサービスロールがあること。
- 2
2. ポリシーを生成する
対象ロールのARNと期間(最長90日)を指定して、生成ジョブを開始します。
- 3
3. 結果を取り出す
ジョブが成功したら、生成されたポリシーJSONをファイルに保存します。
- 4
4. Terraformに落とす
ロールに付いている広い権限を、生成物ベースのポリシーに置き換えるコードを書かせます。
- 5
5. validate-policyで検査する
指摘を種別ごとに仕分けして直します。
- 6
6. 差し替えて、広い方を外す
新しいポリシーを付け、動作を見てから広いポリシーを外します。
Claude Code側の権限は、読み取りと検証だけを許可する
AWSに触る作業をClaude Codeに任せる前に、何を勝手に実行させないかを決めます。権限ルールは「拒否、確認、許可」の順に評価され、最初に当たったものが結果になります。広い拒否ルールは、狭い許可ルールがあっても覆りません。
この性質から、書き込み系を拒否に、検査系を許可に置く設計が素直です。
{
"permissions": {
"allow": [
"Bash(aws accessanalyzer get-generated-policy *)",
"Bash(aws accessanalyzer list-policy-generations *)",
"Bash(aws accessanalyzer validate-policy *)",
"Bash(terraform fmt *)",
"Bash(terraform validate)",
"Bash(terraform plan *)"
],
"ask": [
"Bash(aws accessanalyzer start-policy-generation *)"
],
"deny": [
"Bash(terraform apply *)",
"Bash(aws iam put-role-policy *)",
"Bash(aws iam attach-role-policy *)",
"Bash(aws iam detach-role-policy *)",
"Bash(aws iam delete-role-policy *)"
]
}
}start-policy-generationを確認付きにしているのは、期間とロールを人間が目で見てから走らせたいためです。生成ジョブはCloudTrailのログが入ったS3バケットを読むので、リクエストごとに標準のS3料金がかかります。
この設定には穴もあります。Bashのルールはコマンド文字列に一致するもので、同じ操作の別の書き方までは止めません。たとえばgit pushの拒否ルールがgit -C . pushを止めないのと同じで、terraform -chdir=infra applyのような書き方は上の拒否ルールをすり抜けます。ルールはClaude Codeが守るもので、AWS側の権限とは別物です。分析作業に使うAWSプロファイルは、Access Analyzerの参照とCloudTrail参照だけを持つ読み取り用にしておく分け方があります。
terraform applyの実行は、人間が手元で行う運用にすると境界がはっきりします。同じ考え方で承認制のデプロイを組む例はaws-coreプラグインの記事にもあります。
生成の前に、下書きに入らないものを知る
Access Analyzerが作るのは、CloudTrailに残ったイベントから逆算した下書きです。ログに残らない操作と、ログがそもそも読めない構成は、最初から対象外になります。
| 条件 | 生成結果への影響 | 取りうる対処 |
|---|---|---|
| トレイルが無い | 生成結果への影響生成できない | 取りうる対処トレイルを有効にしてから期間を取り直す |
| データイベント(S3オブジェクト操作など) | 生成結果への影響操作単位の履歴が入らない | 取りうる対処該当サービスの操作を手で足す |
iam:PassRole | 生成結果への影響CloudTrailに記録されず入らない | 取りうる対処必要なら手で追加する |
| AWS Control Towerが作ったトレイル | 生成結果への影響生成に使えない | 取りうる対処別のトレイルを用意する |
| 拒否された操作 | 生成結果への影響生成物に混ざりうる | 取りうる対処失敗していた呼び出しを洗い直す |
| 期間内に呼ばれなかった操作 | 生成結果への影響入らない | 取りうる対処月次バッチなどが期間に収まる長さを選ぶ |
期間は最長90日なので、四半期に一度しか動かないジョブを持つロールは、90日を取っても含まれない可能性があります。
次のコールアウトも、差し替えの前に目を通してください。
また、生成物には取りこぼしの有無を示すisCompleteが付きます。falseは、特定した操作がサービスの取りうる全操作を含まない場合を指します。ドキュメントの例ではfalseでした。値を見て判断する癖を付けると、「生成されたから完成」という思い込みを避けられます。
生成ジョブを開始し、結果を取り出す
console操作は手順が文書化されていますが、Claude Codeに渡すならCLIが向きます。最初に、Access Analyzer用のサービスロールが必要です。権限ポリシーは、トレイル参照、利用履歴の参照、ログのS3バケット読み取りの3種類で、信頼ポリシーはaccess-analyzer.amazonaws.comが引き受けられる形にします。
ジョブの入力は、JSONファイルにまとめると読みやすくなります。
{
"trails": [
{
"cloudTrailArn": "arn:aws:cloudtrail:ap-northeast-1:111122223333:trail/org-trail",
"allRegions": true
}
],
"accessRole": "arn:aws:iam::111122223333:role/service-role/AccessAnalyzerMonitorServiceRole_example",
"startTime": "2026-08-01T00:00:00Z",
"endTime": "2026-10-26T00:00:00Z"
}ARNとアカウントIDは例示用の値です。endTimeを省くと現在時刻になります。実行は次のとおりで、ジョブIDが返ります。
aws accessanalyzer start-policy-generation \
--policy-generation-details \
principalArn=arn:aws:iam::111122223333:role/app-batch \
--cloud-trail-details file://trail.json
aws accessanalyzer list-policy-generationslist-policy-generationsで状態を見て、SUCCEEDEDになったら取り出します。状態には他にIN_PROGRESSがあります。
aws accessanalyzer get-generated-policy \
--job-id <ジョブID> \
--include-resource-placeholders \
--query 'generatedPolicyResult.generatedPolicies[0].policy' \
--output text > policies/app-batch.generated.jsonここで--queryと--output textを使うのは、policyが「JSONを文字列として持つ項目」だからです。ドキュメントの出力例でも、エスケープされた1行の文字列で返っています。取り出したファイルは、整形してから比べます。
CLIでも--include-resource-placeholdersでARNのプレースホルダーを取れる
上の例の--include-resource-placeholdersは、リソース単位の指定に対応するアクションについて、"Resource": "*"の代わりに"Resource": "arn:aws:s3:::${BucketName}"のようなプレースホルダーを入れるオプションです。付けない場合の下書きは"Resource": "*"になり、ドキュメントのCLI出力例もその形です。付けて取っても、${BucketName}に入る実際のリソースは自分で決めます。
コンソールでも同じようにプレースホルダー入りのテンプレートが出て、画面上で絞り込めます。生成済みのポリシーを画面で見られるのは7日間で、同時に1つだけです。
Terraformに落とす:広い権限を外す前に、生成物を管理下に置く
Terraformでロールのインラインポリシーを管理するなら、aws_iam_role_policyを使います。プロバイダのドキュメントには、2点の注意があります。
aws_iam_roleのinline_policy引数と併用すると、両方が同じロールのインラインポリシーを管理しようとして、差分が消えなくなるpolicyに渡す値は、jsonencode()かaws_iam_policy_documentを使う。空白や整形の違いによる揺れを避けられる
この2点は、Claude Codeへの指示にそのまま入れられます。次のような依頼文が出発点になります。
policies/app-batch.generated.json を元に、
infra/iam.tf の aws_iam_role_policy を書き直してください。
- policy は jsonencode() で組み、Sid は意味のある名前にする
- aws_iam_role の inline_policy 引数は使わない
- ${BucketName} などのプレースホルダーは、infra/ 内のリソース参照に置き換える。
置き換え先が無いものは、一覧にして報告する
- terraform fmt と validate を実行し、plan の差分を要約する
- apply はしない三つめの指示が肝になります。プレースホルダーを実リソースに置き換えるには、生成物にない情報が要ります。Terraform内でバケットやキューのリソースが定義されているなら、その参照に置き換えられます。--include-resource-placeholdersを付けずに取った下書きなら、"*"の文を絞る作業になります。定義されていないリソースのARNは、推測させず、一覧にして返させるようにします。
落とし穴
生成物をそのまま上書きしない
生成物のSidは意味のある名前になっていないことがあるので、後から読める名前に付け直します。また、iam:PassRoleは生成されないので、Lambdaやタスクを作るロールでは、PassRoleを足し忘れると、ロールを渡す段階で権限不足になりえます。Terraform化の差分レビューで、PassRoleが元の広いポリシーにあったかを必ず確認します。
validate-policyの指摘は、種別で仕分けてから直す
Terraformに載せたポリシーは、デプロイの前にAccess Analyzerのポリシーチェックにかけられます。
aws accessanalyzer validate-policy \
--policy-document file://policies/app-batch.generated.json \
--policy-type IDENTITY_POLICY--policy-typeにはIDENTITY_POLICYのほか、RESOURCE_POLICY、SERVICE_CONTROL_POLICY、RESOURCE_CONTROL_POLICYが指定できます。ロールに付ける権限ポリシーは、アイデンティティポリシーです。信頼ポリシーを検査するなら、RESOURCE_POLICYに--validate-policy-resource-type AWS::IAM::AssumeRolePolicyDocumentを付けます。
結果はfindingsの配列で、各要素にfindingType、issueCode、findingDetails、learnMoreLinkが入ります。findingTypeは4種類です。
指摘の4種類と、直し方の方針
ERROR
ポリシーの一部が機能しない状態。まず直す対象です。
SECURITY_WARNING
過度に広いアクセスを許す記述。Claude Codeに直させず、意図の確認を挟みます。
WARNING
セキュリティ以外で、ポリシー記述のベストプラクティスに沿わない一般的な指摘です。
SUGGESTION
アクセスに影響しない、書式の改善案。Sidが空のときなどが該当します。
分け方のポイントは、「直して意味が変わるかどうか」です。ERRORとSUGGESTIONは、記述の誤りや体裁の修正が中心なので、Claude Codeに任せやすい部分です。SECURITY_WARNINGは、広い権限そのものを指していることがあり、機械的に消すと必要な操作まで外れます。
依頼文には、この線引きを書いておきます。
validate-policy の結果を findingType で分けてください。
- ERROR / WARNING / SUGGESTION: 修正案を出し、適用後に再検査する
- SECURITY_WARNING: 修正せず、対象の文と issueCode、
learnMoreLink を表にして報告する
再検査で ERROR が 0 件になるまで繰り返してください。この指示は、検査コマンドの出力を読ませて直す反復という形をとっています。Claude Codeの権限ルールでvalidate-policyを許可済みにしておくと、確認なしで再検査まで進みます。
validate-policyが見るのはポリシー文書そのもので、そのロールが実際に必要とする操作を満たしているかは判定しません。指摘が0件でも、必要な操作が抜けていないとは言えない点を覚えておきます。抜けの確認は次の手順でします。
差し替えは「付ける、観察する、外す」の順にする
ドキュメントの流れも、新しいポリシーを作ってロールに付けたあと、広すぎるポリシーを外せる、という順です。
- 生成ポリシーを新しい名前で付ける(広い方はまだ残す)
- 期間を決めて、アクセス拒否のイベントが出ていないか見る
- 問題が無ければ、広いポリシーを外すTerraformの差分を出す
- 外した後も、同じ期間だけ拒否イベントを見る
1と3を別のコミットに分けておくと、問題が出たときに戻す範囲が狭くなります。Claude Codeには、コミットを分ける指示と、planの結果から「消える権限」を一覧にする指示を出せます。
差し替えの前に確かめる順番
- Q期間内に動いていなかったジョブやバッチがあるか?答えがあれば、そのジョブの操作を手で足すか、期間を取り直します答えがはい
あれば、CloudTrailを別の期間で生成し直し、2つの下書きを重ねて比べます。
- QPassRoleが必要なサービス(Lambda、ECSなど)をこのロールが作るか?答えが作るなら、PassRoleを手で追加し、対象ロールを限定します答えがはい
作らないなら、そのまま次へ進みます。
付けてから外す
広いポリシーはすぐ外さず、拒否イベントが無いことを見てから外します。
CLAUDE.mdに残す、繰り返し作業のための決まり
同じ作業を他のロールにも回すなら、手順をCLAUDE.mdに書いておくと、依頼文を毎回書かずに済みます。
## IAMの最小権限化
- ポリシーは infra/iam.tf で管理し、policy は jsonencode() で書く
- 生成物は policies/*.generated.json に置き、直接編集しない
- validate-policy の SECURITY_WARNING は自分で消さず、報告する
- terraform apply と aws iam の書き込み系コマンドは実行しない
- PassRole の有無は、差分の要約に必ず書くCLAUDE.mdの記述はClaude Codeの振る舞いを導くものですが、実行できるかどうかを決めるのは権限ルールです。両方に同じ内容を書いておき、重要な禁止は権限ルール側で強制するようにします。
自分のロールを絞る作業と、Claude側のIAMの違い
この記事で扱ったのは、自分のAWSアカウントにあるロールを、利用履歴で絞る作業です。Claude Platform on AWSを使うために付けるaws-external-anthropic系のアクションを整理した記事は、IAMアクション一覧と最小権限ポリシーにあります。BedrockでClaude Codeを動かすロールを組む手順は、Bedrock IAM設定とクロスリージョン推論プロファイルに分かれています。
まとめ
生成ポリシーは、権限を絞る作業の出発点としては強力ですが、完成品にはなりません。Claude Codeに任せやすいのは、Terraformへの変換、検査コマンドの反復、差分の要約です。任せにくいのは、iam:PassRoleや期間外のジョブのように、ログに出ない必要権限を判断する部分です。
取りかかる順番としては、書き込みを拒否する権限ルールを先に置き、生成物を管理下に置いてから、広いポリシーを最後に外す流れが、戻しやすい形です。
参照した一次ソース
この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。
- code.claude.com/docs/en/permissions
- docs.aws.amazon.com/IAM/latest/UserGuide/access-analyzer-policy-generation.html
- docs.aws.amazon.com/cli/latest/reference/accessanalyzer/start-policy-generatio…
- docs.aws.amazon.com/cli/latest/reference/accessanalyzer/get-generated-policy.h…
- docs.aws.amazon.com/cli/latest/reference/accessanalyzer/validate-policy.html
- registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_pol…