[アップデート] IAM Policy AutopilotがTerraformのplanファイルをサポートするようになりました

[アップデート] IAM Policy AutopilotがTerraformのplanファイルをサポートするようになりました

Terraformの実行権限、実質Admin権限になっていませんか?そんな悩みを解決してくれるIAM Policy Autopilotが、Terraform planファイルに対応しました。本記事では、planから必要なIAMポリシーを自動生成する方法を実際に試してみます。
2026.08.19

はじめに

皆様こんにちは、あかいけです。
Terraform実行時のIAMロールの権限、実質 Admin or PowerUser 相当になっていませんか?
私はなっています。

Terraformはいろんなサービスのリソースを横断的に触るため、そのすべてに必要なアクションを一つひとつ洗い出して最小権限のロールを組むのは、正直かなり骨が折れる作業ですよね。
なので結局「とりあえずAdministratorAccess(あるいはPowerUserAccess)を付けて動かし、後で絞ろう!」と思ったまま数ヶ月放置…みたいな経験を私は何度もしています。(今もそうです)

そんな中、IAM Policy AutopilotがTerraformのplanファイルをサポートしたというアップデートがありました。
planから必要なIAMポリシーを生成してくれるとのことで、なんだか良さそうな気がします。

https://aws.amazon.com/about-aws/whats-new/2026/08/iam-policy-autopilot-now-supports-terraform-plan-files/

というわけで今回は、実際にTerraformのplanからIAMポリシーを生成するところまでを試してみます。
あわせて、MCPサーバ経由で使うパターンも紹介します。

IAM Policy Autopilotとは

IAM Policy Autopilotは、re:Invent 2025で公開されたオープンソースのIAMポリシー自動生成ツールです。

https://aws.amazon.com/jp/about-aws/whats-new/2025/11/iam-policy-autopilot-generate-iam-policies-code/
https://aws.amazon.com/jp/blogs/news/simplify-iam-policy-creation-with-iam-policy-autopilot-a-new-open-source-mcp-server-for-builders/

AWS SDKの呼び出しやTerraformのplanを決定論的(deterministic)に静的解析し、必要な権限にスコープダウンした「たたき台」のポリシーを生成してくれます。
そのためコードを実行したりCloudTrailのログを溜めたりする必要はなく、ローカルで解析が完結します。(追加料金もかかりません)

CLIには3つのコマンドがあり、これらを使うことでコードの内容に合わせたIAMポリシーを生成できます。

  • generate-policies
    • ソースコードやTerraformのplanからIAMポリシーを生成する
  • fix-access-denied
    • AccessDeniedエラーを解析し、必要なポリシーを生成・適用する
  • mcp-server
    • MCPサーバを起動する(Claude DesktopやKiroなどと連携する)

https://github.com/awslabs/iam-policy-autopilot

従来の制限

従来の解析対象は、AWS SDKを呼び出すアプリケーションのソースコードでした。
(対応言語はPythonのBoto3、Go/Java/JavaScript/TypeScriptの各SDKなど)

boto3のs3.put_object()を見て「s3:PutObjectが要るな」と導く、といった具合ですね。
つまり生成されるのは、アプリが実行時に必要とする権限でした。

今回追加された機能

今回のアップデートで、Terraformのplanファイルから生成できるようになりました。

planには「どのリソースを、どんな設定で作成/変更/削除するか」がすべて入っています。generate-policiesにplan(JSON)を渡すと、その内容をもとに必要なアクションへスコープしたポリシーを生成します。
しかも可能な限りワイルドカードではなく、具体的なリソースARNを参照してくれます。

つまり、terraform applyを通すための権限を、planから逆算してくれるわけですね。では、実際に試していきましょう。

試してみた(CLI編)

事前準備(uvのインストール)

IAM Policy Autopilotはいくつかの方法でインストールできますが、今回は追加インストール不要で実行できるuv(uvx)を使います。
uvが入っていない場合は、公式のインストーラで導入します。

# macOS / Linux
curl -LsSf https://astral.sh/uv/install.sh | sh

https://docs.astral.sh/uv/getting-started/installation/

uvxを使うと、パッケージを明示的にインストールしなくてもそのまま実行できます。

uvx iam-policy-autopilot

ただし上記コマンドだと私の環境では0.2.3になってしまったので、公式PyPIをインデックスに指定して最新版を取得しました。
(本ブログではこの後uvxには --default-index https://pypi.org/simple オプションをつけています)

uvx --default-index https://pypi.org/simple iam-policy-autopilot version

0.3.0が取得できればOKです。

iam-policy-autopilot 0.3.0

検証用のTerraform構成を用意する

今回はS3バケットとDynamoDBテーブルを作るだけの、シンプルな構成を用意しました。

main.tf
terraform {
  required_version = ">= 1.15"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 6.0"
    }
  }
}

provider "aws" {
  region = "ap-northeast-1"
}

resource "aws_s3_bucket" "app" {
  bucket = "autopilot-demo-bucket"
}

resource "aws_dynamodb_table" "app" {
  name         = "autopilot-demo-table"
  billing_mode = "PAY_PER_REQUEST"
  hash_key     = "id"

  attribute {
    name = "id"
    type = "S"
  }
}

planを生成してJSONに変換する

IAM Policy Autopilotが受け取るのは、JSON形式のplanファイルです。
terraform planでplanを保存し、terraform show -jsonでJSONに変換します。

# 初期化
terraform init

# planをファイルに保存
terraform plan -out=plan.tfplan

# 保存したplanをJSONに変換
terraform show -json plan.tfplan > plan.json

これでplan.jsonができました。

ポリシーを生成する

では生成したplan.jsonをIAM Policy Autopilotに渡します。
入力元の種類を指定する必要はなく、ツール側が自動で判定してくれるようです。
--prettyは出力JSONを整形するオプションです)

uvx --default-index https://pypi.org/simple iam-policy-autopilot generate-policies plan.json --pretty

実際に生成されたjsonは以下でした。

{
  "Policies": [
    {
      "Policy": {
        "Id": "IamPolicyAutopilot",
        "Version": "2012-10-17",
        "Statement": [
          {
            "Effect": "Allow",
            "Action": [
              "dynamodb:AssociateTableReplica",
              "dynamodb:BatchWriteItem",
              "dynamodb:CreateGlobalTableWitness",
              "dynamodb:CreateTable",
              "dynamodb:CreateTableReplica",
              "dynamodb:DeleteGlobalTableWitness",
              "dynamodb:DeleteItem",
              "dynamodb:DescribeContinuousBackups",
              "dynamodb:DescribeImport",
              "dynamodb:DescribeTable",
              "dynamodb:DescribeTimeToLive",
              "dynamodb:GetItem",
              "dynamodb:ImportTable",
              "dynamodb:ListTagsOfResource",
              "dynamodb:PutItem",
              "dynamodb:PutResourcePolicy",
              "dynamodb:Query",
              "dynamodb:ReadDataForReplication",
              "dynamodb:ReplicateSettings",
              "dynamodb:RestoreTableFromBackup",
              "dynamodb:Scan",
              "dynamodb:TagResource",
              "dynamodb:UntagResource",
              "dynamodb:UpdateContinuousBackups",
              "dynamodb:UpdateItem",
              "dynamodb:UpdateTable",
              "dynamodb:UpdateTimeToLive",
              "dynamodb:WriteDataForReplication"
            ],
            "Resource": [
              "arn:*:dynamodb:*:*:table/autopilot-demo-table",
              "arn:*:dynamodb:*:*:table/autopilot-demo-table/backup/*",
              "arn:*:dynamodb:*:*:table/autopilot-demo-table/import/*",
              "arn:*:dynamodb:*:*:table/autopilot-demo-table/index/*",
              "arn:*:dynamodb:*:*:table/autopilot-demo-table/stream/*"
            ]
          },
          {
            "Effect": "Allow",
            "Action": [
              "iam:PassRole"
            ],
            "Resource": [
              "*"
            ],
            "Condition": {
              "StringEquals": {
                "iam:PassedToService": [
                  "s3.amazonaws.com"
                ]
              }
            }
          },
          {
            "Effect": "Allow",
            "Action": [
              "kms:CreateGrant",
              "kms:DescribeKey"
            ],
            "Resource": [
              "arn:*:kms:*:*:key/*"
            ],
            "Condition": {
              "StringLike": {
                "kms:ViaService": [
                  "dynamodb.*.amazonaws.com"
                ]
              }
            }
          },
          {
            "Effect": "Allow",
            "Action": [
              "s3:CreateBucket",
              "s3:DeleteBucketPolicy",
              "s3:DeleteBucketWebsite",
              "s3:GetAccelerateConfiguration",
              "s3:GetBucketAcl",
              "s3:GetBucketCORS",
              "s3:GetBucketLogging",
              "s3:GetBucketObjectLockConfiguration",
              "s3:GetBucketPolicy",
              "s3:GetBucketRequestPayment",
              "s3:GetBucketTagging",
              "s3:GetBucketVersioning",
              "s3:GetBucketWebsite",
              "s3:GetEncryptionConfiguration",
              "s3:GetLifecycleConfiguration",
              "s3:GetReplicationConfiguration",
              "s3:ListBucket",
              "s3:PutAccelerateConfiguration",
              "s3:PutBucketAcl",
              "s3:PutBucketCORS",
              "s3:PutBucketLogging",
              "s3:PutBucketObjectLockConfiguration",
              "s3:PutBucketOwnershipControls",
              "s3:PutBucketPolicy",
              "s3:PutBucketRequestPayment",
              "s3:PutBucketTagging",
              "s3:PutBucketVersioning",
              "s3:PutBucketWebsite",
              "s3:PutEncryptionConfiguration",
              "s3:PutLifecycleConfiguration",
              "s3:PutReplicationConfiguration"
            ],
            "Resource": [
              "arn:*:s3:*:*:accesspoint/*",
              "arn:*:s3:::akaike-autopilot-demo-bucket"
            ]
          },
          {
            "Effect": "Allow",
            "Action": [
              "s3:DeleteObjectTagging",
              "s3:DeleteObjectVersionTagging",
              "s3:GetObjectTagging",
              "s3:GetObjectVersionTagging",
              "s3:PutObjectTagging",
              "s3:PutObjectVersionTagging"
            ],
            "Resource": [
              "arn:*:s3:*:*:accesspoint/*/object/*",
              "arn:*:s3:::akaike-autopilot-demo-bucket/*"
            ]
          },
          {
            "Effect": "Allow",
            "Action": [
              "s3-object-lambda:DeleteObjectTagging",
              "s3-object-lambda:GetObjectTagging",
              "s3-object-lambda:PutObjectTagging"
            ],
            "Resource": [
              "arn:*:s3:*:*:accesspoint/*/object/*",
              "arn:*:s3:::*/*"
            ]
          },
          {
            "Effect": "Allow",
            "Action": [
              "s3express:GetLifecycleConfiguration",
              "s3express:PutLifecycleConfiguration"
            ],
            "Resource": [
              "arn:*:s3:::*"
            ]
          }
        ]
      },
      "PolicyType": "Identity"
    }
  ],
  "Warnings": [
    {
      "WarningType": "WildcardResource",
      "Location": {
        "PolicyIndex": 0,
        "StatementIndex": 1
      },
      "Message": "Statement could not be scoped to specific resources and uses Resource \"*\". Review whether broad resource access is intended."
    }
  ]
}

Resourceがちゃんと具体的なリソース名(autopilot-demo-tableautopilot-demo-bucket)でスコープされている点がいいですね。
planに書いたバケット名・テーブル名がそのままARNに反映されているようですね。

また、末尾のWarningsですが、特定リソースにスコープできずResource: "*"になった箇所(今回はiam:PassRole)について、「本当に広い権限で意図通りか確認してね」と警告を出してくれています。
レビューすべき箇所を教えてくれるのは親切ですね。

region / accountを指定してARNをより具体的にする

先ほどの出力では、ARNのリージョンとアカウントIDがarn:*:dynamodb:*:*:...のようにワイルドカードになっていました。
--region--accountを指定すると、ここが具体的な値に置き換わります。

uvx --default-index https://pypi.org/simple iam-policy-autopilot generate-policies plan.json \
  --region ap-northeast-1 \
  --account XXXXXXXXXXXX \
  --pretty

こうすることでより最小権限に近づくので、よりセキュアな設定が望ましい場合はデプロイ先のリージョン・アカウントを指定するのがよさそうです。

出力例の抜粋
arn:aws:dynamodb:ap-northeast-1:XXXXXXXXXXXX:table/autopilot-demo-table

upload-policiesで生成したポリシーをそのままIAMに作成する

生成したポリシーは標準出力に出すだけでなく、--upload-policiesを付けるとその場でIAMポリシーとして作成できます。
内部的にはiam:CreatePolicyが呼ばれます。

# デフォルトの名前で作成
uvx --default-index https://pypi.org/simple iam-policy-autopilot generate-policies plan.json --upload-policies

# プレフィックスを指定して作成
uvx --default-index https://pypi.org/simple iam-policy-autopilot generate-policies plan.json --upload-policies TerraformDeployRole

作成されるポリシー名は、プレフィックス未指定ならIamPolicyAutopilotGeneratedPolicy_1、指定時は<プレフィックス>_1のように連番になります。
(既存と被らない番号を自動で選んでくれます)

スクリーンショット 2026-08-19 21.59.17

補足:aws login のプロファイルだとエラーになる

ここで私はエラーに遭遇しました。

Error: Failed to upload policies to AWS IAM
  Caused by: AWS IAM list policies error: dispatch failure
  Caused by: the credentials provider was not properly configured
  Caused by: ProfileFile provider could not be built: This behavior requires following cargo feature(s) enabled: credentials-login. In order to use an active login session, the `credentials-login` feature must be enabled.

これはIAM権限の不足ではなく、認証情報の取得方式の問題でした。
私のプロファイルはaws loginで認証していました。

まずIAM Policy AutopilotはRust製(AWS SDK for Rust)なのですが、このlogin_sessionを使ったプロファイルを読むにはSDK側でcredentials-loginフィーチャーを有効にしてビルドする必要があります。

https://github.com/awslabs/aws-sdk-rust/blob/main/sdk/aws-config/src/profile/credentials.rs

そしてIAM Policy AutopilotのCargo.tomlを見ると、aws-configはこのフィーチャーを指定がなかったので、エラーが出るっぽいです。(推測)

https://github.com/awslabs/iam-policy-autopilot/blob/5e3e168268cff3cd3154e78bb158da28651e7371/Cargo.toml#L83

解決策は、ツールが読める環境変数の静的クレデンシャルを渡してあげることです。
AWS CLIのexport-credentialsで、現在のセッションを環境変数にエクスポートできます。

# プロファイル名はお使いのものに置き換えてください
# この状態なら環境変数から認証情報を読めるので、エラーが解消します
eval "$(aws configure export-credentials --profile default --format env)"

試してみた(MCP編)

IAM Policy Autopilotは、MCP(Model Context Protocol)サーバとしても動作します。
今回はClaude Codeを例に紹介します。

Claude Codeへの登録

claude mcp addコマンドで登録するか、

claude mcp add iam-policy-autopilot \
  --env AWS_PROFILE=default \
  --env AWS_REGION=ap-northeast-1 \
  -- uvx --default-index https://pypi.org/simple iam-policy-autopilot mcp-server

プロジェクトレベルで設定する場合は、.mcp.jsonなどに書きましょう。

.mcp.json
{
  "mcpServers": {
    "iam-policy-autopilot": {
      "command": "uvx",
      "args": [
        "--default-index",
        "https://pypi.org/simple",
        "iam-policy-autopilot",
        "mcp-server"
      ],
      "env": {
        "AWS_PROFILE": "default",
        "AWS_REGION": "ap-northeast-1"
      }
    }
  }
}

MCPサーバが公開しているツール

実際にMCPサーバへ接続してtools/listを叩いてみたところ、以下の3つのツールが公開されていました。
今回のテーマであるTerraform planは、メインのgenerate_application_policiesツールで扱えます。

ツール名 役割
generate_application_policies ソースコードやTerraform planからIAMポリシーを生成する(メインのツール)
generate_policy_for_access_denied AccessDeniedエラーメッセージから必要なポリシーを生成する
fix_access_denied 生成したAccessDenied用ポリシーを実際にAWSアカウントへ適用する

使い方のイメージ

登録後は、Claude Codeに対して次のように依頼するだけです。

terraformをapplyするために必要な権限のIAMポリシーを作成して。

すると、Claude Codeがgenerate_application_policiesツールを呼び出し、生成されたポリシーJSONを提示してくれます。
CLIを直接叩くのと結果は同じですが、チャットの流れの中でそのままレビューや修正の相談まで進められるのが便利ですね。

なおCLIにあった--upload-policiesでそのままIAMポリシーを作成する機能は、対応してなさそうでした。
何回か試してみましたが、そもそもMCP版では対応してなさそうです。
(そもそも実装されてなさそう)

https://github.com/awslabs/iam-policy-autopilot/blob/main/iam-policy-autopilot-mcp-server/src/tools/generate_policy.rs

さいごに

以上、IAM Policy AutopilotのTerraform planファイル対応を試してみました。
これまでAIと壁打ちしながら探していた「Terraform実行ロールに必要な権限」を、planから自動でスコープして生成してくれるのは、なかなか実用的だと感じました。
ある程度AIのトークンも節約できそうですし、今後使っていこうともいます。

CI/CDのTerraform実行ロールをAdmin相当で作成して放置している方やIAM権限設計に悩んでいる方は、一度試してみてはいかがでしょうか。

この記事をシェアする

AWSのお困り事はクラスメソッドへ

関連記事