Terraformの便利ツールを使ってみよう(第一回)〜Pike編〜

# はじめに
- 今回から何回かに渡ってTerraformの便利なツールを使ってみてご紹介していこうと思います
- 第一回はTerraformの実行権限を最小化(最小権限の原則)するためのツールであるPikeをご紹介します
# 前提
- Terraformに限らずですが、IaCを実行するときに細々と権限を検討するのがめんどくさくなって、とりあえずで付与した"AdministratorAccess"をずっと使い続けている、、なんてことはないでしょうか？
- 気持ちはわかります
- 最小権限の原則なんて聞きますが、実際それをマネジメントコンソールユーザーに対してはしっかり適用できているけど、IaCユーザーに対しても適用できている人はかなりの少数派なのではないか？というのが筆者のイメージです
  - もし、「いや当社は特段何のツールも使わずにできているよ」、という方がいらっしゃいましたら、尊敬です。素晴らしい
- なぜ、IaCで最小権限の原則の適用が難しいのか？これは筆者のイメージは、IaCコードを作成する際にコードの全文を一気に作成、、なんてことはせず、少しずつ部分を作成/applyし、組み上げていくとすることが多く、都度権限付与なんてしていたらめんどくさい！というのがあると思っています
- さらには、AWSのきめ細やかな権限設定が可能な仕様が、それを細かく設定するのがめんどくさくさせている、、とも考えています
  - 「きめ細やかな権限設定」はメリットでありデメリット、、
  - ちょっと項目多すぎない？と常日頃感じています
- そうしたとき、Pikeという"Terraformでapplyするために必要な権限を作成してくれるツール"があるという話を聞き、試してみました
# Pikeとは
## 概要
- TerraformでApplyを実行するときに必要となる権限を出力、さらには作成してくれるツールです
## もうちょっと詳しく
- TerraformのプロバイダーとしてAWS/Azure/GCPをサポートしており、これらに対してTerraformコードの実行に必要最小限の権限を出力してくれます
- さらにAWSに対してはより開発が進んでいるようで、必要と思われる権限を有したIAMポリシー/ロールをデプロイできます
  - 2025.02.10時点ではAzure/GCPに対して権限を作成する、まではできないようで、開発が期待されている状態のようです
- 現在Mac/Windows/Dockerへのインストールがサポートされています
- ざっくり月一はリリースされているようで、定期的なメンテナンスがあることはかなり利用者にとっては嬉しい状況なのではないでしょうか
## Pikeのヘルプをチェック
```shell
% pike -h
NAME:
   pike - Generate IAM policy from your IAC code

USAGE:
   pike [global options] command [command options]

VERSION:
   v0.3.39

AUTHOR:
   James Woolfenden <james.woolfenden@gmail.com>

COMMANDS:
   apply, a    Create a policy and use it to instantiate the IAC
   compare, c  policy comparison of deployed versus IAC
   inspect, x  policy comparison of environment versus IAC
   invoke, i   Triggers a gitHub action specified with the workflow flag
   make, m     make the policy/role required for this IAC to deploy
   parse, p    Triggers a gitHub action specified with the workflow flag
   pull, l     Clones remote repo and scans it using pike
   readme, r   Looks in dir for a README.md and updates it with the Policy required to build the code
   remote, o   Create/Update the Policy and set credentials/secret for Github Action
   scan, s     scan a directory for IAM code
   version, v  Outputs the application version
   watch, w    Waits for policy update
   help, h     Shows a list of commands or help for one command

GLOBAL OPTIONS:
   --help, -h     show help
   --version, -v  print the version
```
- COMMANDS部分を日本語訳
```
コマンド:
  apply, a    ポリシーを作成し、それを使用してIACをインスタンス化する
  compare, c  デプロイされたものとIACのポリシー比較する
  inspect, x  環境とIACのポリシー比較する
  invoke, i   workflowフラグで指定されたGitHub Actionをトリガーする
  make, m     このIACをデプロイするために必要なポリシー/ロールを作成する
  parse, p    workflowフラグで指定されたGitHub Actionをトリガーする
  pull, l     リモートリポジトリをクローンし、pikeを使用してスキャンする
  readme, r   ディレクトリ内のREADME.mdを探し、コードを構築するために必要なポリシーで更新する
  remote, o   ポリシーの作成/更新とGitHub Action用の認証情報/シークレットを設定する
  scan, s     IAMコードのディレクトリをスキャンする
  version, v  アプリケーションのバージョンを出力する
  watch, w    ポリシーの更新を待機する
  help, h     コマンド一覧または特定のコマンドのヘルプを表示する
```
## 主要なCOMMANDSを紹介
### scan
- 概要
  - Terraformファイルを含むディレクトリをスキャンし、必要なIAMポリシーを生成
- 実行オプション
  - -d オプションでディレクトリを指定
  - -w オプションで.pikeフォルダにポリシーを出力
  - -i オプションでterraform initを実行
### make
- 概要
  - スキャンしたポリシーを直接AWSにデプロイ
  - デプロイされたロールのARNを返してくれる
  - 現時点ではAWSのみサポートしている
### apply
- 概要
  - makeコマンドの拡張機能
  - ポリシーとロールを適用し、そのロールを使用してインフラを作成してくれる
- 実行オプション
  - -d オプションでディレクトリを指定
  - -region オプションでリージョンを指定
### compare
- 概要
  - デプロイ済みのIAMポリシーとIACの要件を比較
  - 既存のポリシーとの差分を確認可能するために使用
### remote
- 概要
  - 一時的なAWS認証情報をワークフローに書き込む
  - makeとapplyのコア機能を使用
  - 現時点ではAWSのみサポートしている
# 導入手順
以下の他、Dockerによる導入手順が記載されていましたが、今回は省略しています
## MacOS
- 標準がこちらっぽいので、実際の使ってみた、はこちらを前提に記載しています
- 導入コマンド
```
% brew tap jameswoolfenden/homebrew-tap
% brew install jameswoolfenden/tap/pike
```
- 実行例

<details><summary>MacOSにおけるPikeコマンドのインストール実行例</summary>

```shell
% brew tap jameswoolfenden/homebrew-tap
==> Auto-updating Homebrew...
Adjust how often this is run with HOMEBREW_AUTO_UPDATE_SECS or disable with
HOMEBREW_NO_AUTO_UPDATE. Hide these hints with HOMEBREW_NO_ENV_HINTS (see `man brew`).
==> Downloading https://ghcr.io/v2/homebrew/portable-ruby/portable-ruby/blobs/sha256:d9faa506c014dedc0b034a68103ba75c9a58242f4d6c67b6ca0f649c39602bcf
############################################################### 100.0%
==> Pouring portable-ruby-3.3.7.arm64_big_sur.bottle.tar.gz
==> Auto-updated Homebrew!
Updated 4 taps (derailed/k9s, hashicorp/tap, homebrew/core and homebrew/cask).
==> New Formulae
(略)
You have 24 outdated formulae installed.

==> Tapping jameswoolfenden/tap
Cloning into '/opt/homebrew/Library/Taps/jameswoolfenden/homebrew-tap'...
remote: Enumerating objects: 741, done.
remote: Counting objects: 100% (282/282), done.
remote: Compressing objects: 100% (189/189), done.
remote: Total 741 (delta 177), reused 152 (delta 93), pack-reused 459 (from 1)
Receiving objects: 100% (741/741), 129.06 KiB | 595.00 KiB/s, done.
Resolving deltas: 100% (365/365), done.
Tapped 5 formulae (17 files, 161.6KB).
% 
% brew install jameswoolfenden/tap/pike
==> Fetching jameswoolfenden/tap/pike
==> Downloading https://github.com/JamesWoolfenden/pike/releases/downl
==> Downloading from https://objects.githubusercontent.com/github-prod
############################################################### 100.0%
==> Installing pike from jameswoolfenden/tap
🍺  /opt/homebrew/Cellar/pike/0.3.39: 6 files, 21.8MB, built in 2 seconds
==> Running `brew cleanup pike`...
Disable this behaviour by setting HOMEBREW_NO_INSTALL_CLEANUP.
Hide these hints with HOMEBREW_NO_ENV_HINTS (see `man brew`).
==> `brew cleanup` has not been run in the last 30 days, running now..
Disable this behaviour by setting HOMEBREW_NO_INSTALL_CLEANUP.
Hide these hints with HOMEBREW_NO_ENV_HINTS (see `man brew`).
Removing: /Users/xxxxxxx/Library/Caches/Homebrew/brotli_bottle_manifest--1.1.0-1... (11.1KB)
Removing: /Users/xxxxxxx/Library/Caches/Homebrew/portable-ruby-3.3.3.arm64_big_sur.bottle.tar.gz... (11MB)
Removing: /Users/xxxxxxx/Library/Logs/Homebrew/node@16... (64B)
Removing: /Users/xxxxxxx/Library/Logs/Homebrew/openssl@3... (64B)
Removing: /Users/xxxxxxx/Library/Logs/Homebrew/ca-certificates... (64B)
Removing: /Users/xxxxxxx/Library/Logs/Homebrew/unbound... (64B)
Removing: /Users/xxxxxxx/Library/Logs/Homebrew/gnupg... (64B)
% 
% pike -v
pike version v0.3.39
% 
```

</details>

## Windows
- Windowsの場合はscoopというツールの導入が前提とされているため、そこからの手順を記載しています
- 導入コマンド at PowerShell
```
# Scoop
# https://scoop.sh/
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
Invoke-RestMethod -Uri https://get.scoop.sh | Invoke-Expression

# Git(Pikeコマンドのインストール前提)
scoop install git

# Pike
scoop bucket add iac https://github.com/JamesWoolfenden/scoop.git
scoop install pike
```
- 実行例

<details><summary>WindowsにおけるPikeコマンドのインストール実行例</summary>

```powershell
PS > Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
PS > Invoke-RestMethod -Uri https://get.scoop.sh | Invoke-Expression
Initializing...
Downloading...
Extracting...
Creating shim...
Adding ~\scoop\shims to your path.
Scoop was installed successfully!
Type 'scoop help' for instructions.
PS > scoop help
Usage: scoop <command> [<args>]

Available commands are listed below.

Type 'scoop help <command>' to get more help for a specific command.

Command    Summary
-------    -------
alias      Manage scoop aliases
bucket     Manage Scoop buckets
cache      Show or clear the download cache
cat        Show content of specified manifest.
checkup    Check for potential problems
cleanup    Cleanup apps by removing old versions
config     Get or set configuration values
create     Create a custom app manifest
depends    List dependencies for an app, in the order they'll be installed
download   Download apps in the cache folder and verify hashes
export     Exports installed apps, buckets (and optionally configs) in JSON format
help       Show help for a command
hold       Hold an app to disable updates
home       Opens the app homepage
import     Imports apps, buckets and configs from a Scoopfile in JSON format
info       Display information about an app
install    Install apps
list       List installed apps
prefix     Returns the path to the specified app
reset      Reset an app to resolve conflicts
search     Search available apps
shim       Manipulate Scoop shims
status     Show status and check for new app versions
unhold     Unhold an app to enable updates
uninstall  Uninstall an app
update     Update apps, or Scoop itself
virustotal Look for app's hash or url on virustotal.com
which      Locate a shim/executable (similar to 'which' on Linux)


PS >
PS > scoop install git
Installing '7zip' (24.09) [64bit] from 'main' bucket
7z2409-x64.msi (1.9 MB) [=====================================================================================] 100%
Checking hash of 7z2409-x64.msi ... ok.
Extracting 7z2409-x64.msi ... done.
Linking ~\scoop\apps\7zip\current => ~\scoop\apps\7zip\24.09
Creating shim for '7z'.
Creating shim for '7zFM'.
Making C:\Users\xxxxx\scoop\shims\7zfm.exe a GUI binary.
Creating shim for '7zG'.
Making C:\Users\xxxxx\scoop\shims\7zg.exe a GUI binary.
Creating shortcut for 7-Zip (7zFM.exe)
Persisting Codecs
Persisting Formats
Running post_install script...done.
'7zip' (24.09) was installed successfully!
Notes
-----
Add 7-Zip as a context menu option by running: "C:\Users\xxxxx\scoop\apps\7zip\current\install-context.reg"
Installing 'git' (2.47.1.2) [64bit] from 'main' bucket
PortableGit-2.47.1.2-64-bit.7z.exe (60.0 MB) [================================================================] 100%
Checking hash of PortableGit-2.47.1.2-64-bit.7z.exe ... ok.
Extracting PortableGit-2.47.1.2-64-bit.7z.exe ... done.
Linking ~\scoop\apps\git\current => ~\scoop\apps\git\2.47.1.2
Creating shim for 'sh'.
Creating shim for 'bash'.
Creating shim for 'git'.
Creating shim for 'gitk'.
Making C:\Users\xxxxx\scoop\shims\gitk.exe a GUI binary.
Creating shim for 'git-gui'.
Making C:\Users\xxxxx\scoop\shims\git-gui.exe a GUI binary.
Creating shim for 'scalar'.
Creating shim for 'tig'.
Creating shim for 'git-bash'.
Making C:\Users\xxxxx\scoop\shims\git-bash.exe a GUI binary.
Creating shortcut for Git Bash (git-bash.exe)
Creating shortcut for Git GUI (git-gui.exe)
Running post_install script...done.
'git' (2.47.1.2) was installed successfully!
Notes
-----
Set Git Credential Manager Core by running: "git config --global credential.helper manager"

To add context menu entries, run 'C:\Users\xxxxx\scoop\apps\git\current\install-context.reg'

To create file-associations for .git* and .sh files, run
'C:\Users\xxxxx\scoop\apps\git\current\install-file-associations.reg'
PS > git -v
git version 2.47.1.windows.2
PS > 

PS > scoop bucket add iac https://github.com/JamesWoolfenden/scoop.git
Checking repo... OK
The iac bucket was added successfully.
PS > scoop install pike
Installing 'pike' (v0.3.40) [64bit] from 'iac' bucket
pike_0.3.40_windows_amd64.zip (7.6 MB) [======================================================================] 100%
Checking hash of pike_0.3.40_windows_amd64.zip ... ok.
Extracting pike_0.3.40_windows_amd64.zip ... done.
Linking ~\scoop\apps\pike\current => ~\scoop\apps\pike\v0.3.40
Creating shim for 'pike'.
'pike' (v0.3.40) was installed successfully!
PS > pike -v
pike version v0.3.40
PS >
```

</details>

# お試し手順
## お試し環境
- 今回試す環境は、以下の構成です

![VPC Origin環境](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1276335/4f08e09a-5704-3b9e-5462-15e8c7bb7fd3.png)

- 環境概要
  - 最近リリースされた、VPC Origin機能を用いています
  - CloudfrontからプライベートなALBをターゲットに設定ができるようになった機能です
  - VPC Origin機能については以下をご参照ください
    - [Amazon CloudFront VPC オリジンの紹介: アプリケーションのセキュリティ強化と運用の合理化](https://aws.amazon.com/jp/blogs/news/introducing-amazon-cloudfront-vpc-origins-enhanced-security-and-streamlined-operations-for-your-applications/)
    - [CloudFront 新機能！VPCオリジンでプライベートなALBをセキュアに公開してみた](https://dev.classmethod.jp/articles/cloudfront-vpc-origins-internal-alb/)
  - また、ALBのアクセスログをランダムな名前のS3バケットに出力するようにしています
- tfファイルは以下のサイトのものを元に改変しています
  - [TerraformでCloudFront VPCオリジンの設定をしてみた](https://qiita.com/atsaki/items/1973e1f9a38637fca285)
    <details><summary>VPC originを用いたTerraformコード</summary>

    ```hcl
    terraform {
    required_providers {
        aws = {
        source  = "hashicorp/aws"
        version = "5.84.0"
        }
        random = {
        source  = "hashicorp/random"
        version = "~> 3.0"
        }
    }
    backend "s3" {
        bucket = "<Backend用S3バケットName>"
        key    = "terraofrm-tool-pike/terraform.tfstate"
        region = "ap-northeast-1"
    }
    }

    provider "aws" {
    region = "ap-northeast-1"
    default_tags {
        tags = {
        Terraform = "true"
        }
    }
    }

    locals {
    availability_zones = [
        "ap-northeast-1a",
        "ap-northeast-1c",
        "ap-northeast-1d",
    ]
    }

    data "aws_ec2_managed_prefix_list" "cloudfront" {
    name = "com.amazonaws.global.cloudfront.origin-facing"
    }

    resource "aws_vpc" "this" {
    cidr_block = "10.0.0.0/16"
    }

    resource "aws_internet_gateway" "this" {
    vpc_id = aws_vpc.this.id
    }

    resource "aws_subnet" "this" {
    for_each = toset(local.availability_zones)

    vpc_id            = aws_vpc.this.id
    availability_zone = each.key
    cidr_block        = cidrsubnet(aws_vpc.this.cidr_block, 8, index(local.availability_zones, each.key))
    }

    resource "aws_security_group" "private_alb" {
    vpc_id = aws_vpc.this.id

    ingress {
        from_port       = 80
        to_port         = 80
        protocol        = "tcp"
        prefix_list_ids = [data.aws_ec2_managed_prefix_list.cloudfront.id]
    }
    }

    resource "aws_lb" "private_alb" {
    name               = "private-alb"
    internal           = true
    load_balancer_type = "application"
    security_groups    = [aws_security_group.private_alb.id]
    subnets            = [for subnet in aws_subnet.this : subnet.id]
    }

    resource "aws_lb_listener" "private_alb" {
    load_balancer_arn = aws_lb.private_alb.arn
    port              = 80
    protocol          = "HTTP"

    default_action {
        type = "fixed-response"

        fixed_response {
        content_type = "text/plain"
        message_body = "Hello"
        status_code  = "200"
        }
    }
    }

    resource "aws_cloudfront_vpc_origin" "private_alb" {
    vpc_origin_endpoint_config {
        name                   = "private-alb"
        arn                    = aws_lb.private_alb.arn
        http_port              = 80
        https_port             = 443
        origin_protocol_policy = "http-only"

        origin_ssl_protocols {
        quantity = 1
        items    = ["TLSv1.2"]
        }
    }
    }

    resource "aws_cloudfront_distribution" "private_alb" {
    origin {
        origin_id   = "private_alb"
        domain_name = aws_lb.private_alb.dns_name
        vpc_origin_config {
        vpc_origin_id = aws_cloudfront_vpc_origin.private_alb.id

        }
    }

    enabled = true

    default_cache_behavior {
        allowed_methods  = ["GET", "HEAD"]
        cached_methods   = ["GET", "HEAD"]
        target_origin_id = "private_alb"

        forwarded_values {
        query_string = false

        cookies {
            forward = "none"
        }
        }

        viewer_protocol_policy = "redirect-to-https"
    }

    restrictions {
        geo_restriction {
        restriction_type = "none"
        }
    }

    viewer_certificate {
        cloudfront_default_certificate = true
    }

    logging_config {
        include_cookies = false
        bucket          = aws_s3_bucket.cloudfront_logs.bucket_domain_name
        prefix          = "cloudfront-logs/"
    }
    }

    # S3
    ## ランダム文字列を生成するリソース
    resource "random_string" "bucket_suffix" {
    length  = 16
    special = false
    upper   = false
    }

    resource "aws_s3_bucket" "cloudfront_logs" {
    bucket        = "my-cloudfront-logs-bucket-${random_string.bucket_suffix.result}"
    force_destroy = true
    }

    resource "aws_s3_bucket_ownership_controls" "cloudfront_logs" {
    bucket = aws_s3_bucket.cloudfront_logs.id

    rule {
        object_ownership = "ObjectWriter"
    }
    }

    resource "aws_s3_bucket_acl" "cloudfront_logs" {
    depends_on = [aws_s3_bucket_ownership_controls.cloudfront_logs]

    bucket = aws_s3_bucket.cloudfront_logs.id
    acl    = "private"
    }

    resource "aws_s3_bucket_acl" "cloudfront_logs_canonical" {
    depends_on = [aws_s3_bucket_ownership_controls.cloudfront_logs]

    bucket = aws_s3_bucket.cloudfront_logs.id

    access_control_policy {
        owner {
        id = data.aws_canonical_user_id.current.id
        }

        grant {
        grantee {
            id   = data.aws_canonical_user_id.current.id
            type = "CanonicalUser"
        }
        permission = "FULL_CONTROL"
        }

        grant {
        grantee {
            id   = "c4c1ede66af53448b93c283ce9448c4ba468c9432aa01d700d3878632f77d2d0" # CloudFront Log Delivery のカノニカルユーザーID
            type = "CanonicalUser"
        }
        permission = "WRITE"
        }
    }
    }

    data "aws_canonical_user_id" "current" {}

    output "cloudfront_url" {
    description = "URL of the CloudFront distribution"
    value       = "http://${aws_cloudfront_distribution.private_alb.domain_name}"
    }
    ```
    </details>

## 主なお試しの流れ
1. 適当なIAMユーザーを作成(権限は空)
   権限が空の状態でapplyすると、そもそもバックエンドのS3にアクセスできないと言われてしまいますね
   ```
   % terraform apply  
    ╷
    │ Error: error loading state: Unable to access object "terraofrm-tool-pike/terraform.tfstate" in S3 bucket "terraform-backend-73m3j2gank8zpymc8yyn": operation error S3: HeadObject, https response error StatusCode: 403, RequestID: H0VC28K9GE57V3ZY, HostID: GiBfHxzni0WDuUIoE90sAtC30h5eP2AmDx1IMBRrEPgyomzky0yQu02NVGstXSiR4PL03LZEPQ8=, api error Forbidden: Forbidden
    │ 
    │ 
    ╵
    % 
   ```
2. scanコマンドを実行し、必要と思われる権限を付与、applyします
3. apply後にコレコレの権限が足りない、、と言われるので、それを追加する
4. 3をapplyが成功するまで繰り返します
## 注意事項
- 今回はあくまでPikeのscanコマンドでどの程度の最小権限ポリシーが作成されるかを見ています
- コマンド全てを使ってみた、、ではないのでご了承ください
# コマンド使ってみた
## お試し履歴
- お試し1
  - scan結果のポリシーを付与してapplyを実行
    - これで十分か自信はなかった(結果的に不足していましたが)のですが、少なくともDynamoDBっていらないよね、、とは思いました
  ```
    % pike scan .
    resource "aws_iam_policy" "terraform_pike" {
    name_prefix = "terraform_pike"
    path        = "/"
    description = "Pike Autogenerated policy from IAC"

    policy = jsonencode({
        "Version": "2012-10-17",
        "Statement": [
            {
                "Sid": "VisualEditor0",
                "Effect": "Allow",
                "Action": [
                    "cloudfront:CreateDistribution",
                    "cloudfront:CreateDistributionWithTags",
                    "cloudfront:CreateVpcOrigin",
                    "cloudfront:DeleteDistribution",
                    "cloudfront:DeleteVpcOrigin",
                    "cloudfront:GetDistribution",
                    "cloudfront:GetDistributionConfig",
                    "cloudfront:GetVpcOrigin",
                    "cloudfront:ListTagsForResource",
                    "cloudfront:UpdateDistribution",
                    "cloudfront:UpdateDistributionWithStagingConfig",
                    "cloudfront:UpdateVpcOrigin"
                ],
                "Resource": [
                    "*"
                ]
            },
            {
                "Sid": "VisualEditor1",
                "Effect": "Allow",
                "Action": [
                    "dynamodb:DeleteItem",
                    "dynamodb:DescribeTable",
                    "dynamodb:GetItem",
                    "dynamodb:PutItem"
                ],
                "Resource": [
                    "*"
                ]
            },
            {
                "Sid": "VisualEditor2",
                "Effect": "Allow",
                "Action": [
                    "ec2:AssociateSubnetCidrBlock",
                    "ec2:AttachInternetGateway",
                    "ec2:AuthorizeSecurityGroupIngress",
                    "ec2:CreateInternetGateway",
                    "ec2:CreateSecurityGroup",
                    "ec2:CreateSubnet",
                    "ec2:CreateVPC",
                    "ec2:DeleteInternetGateway",
                    "ec2:DeleteNetworkInterface",
                    "ec2:DeleteSecurityGroup",
                    "ec2:DeleteSubnet",
                    "ec2:DeleteVPC",
                    "ec2:DescribeAccountAttributes",
                    "ec2:DescribeInternetGateways",
                    "ec2:DescribeManagedPrefixLists",
                    "ec2:DescribeNetworkAcls",
                    "ec2:DescribeNetworkInterfaces",
                    "ec2:DescribeSecurityGroups",
                    "ec2:DescribeSubnets",
                    "ec2:DescribeVpcAttribute",
                    "ec2:DescribeVpcs",
                    "ec2:DetachInternetGateway",
                    "ec2:DetachNetworkInterface",
                    "ec2:DisassociateSubnetCidrBlock",
                    "ec2:GetManagedPrefixListEntries",
                    "ec2:ModifySubnetAttribute",
                    "ec2:ModifyVpcAttribute",
                    "ec2:ModifyVpcTenancy",
                    "ec2:RevokeSecurityGroupEgress",
                    "ec2:RevokeSecurityGroupIngress"
                ],
                "Resource": [
                    "*"
                ]
            },
            {
                "Sid": "VisualEditor3",
                "Effect": "Allow",
                "Action": [
                    "elasticloadbalancing:AttachLoadBalancerToSubnets",
                    "elasticloadbalancing:CreateListener",
                    "elasticloadbalancing:CreateLoadBalancer",
                    "elasticloadbalancing:CreateLoadBalancerListeners",
                    "elasticloadbalancing:DeleteListener",
                    "elasticloadbalancing:DeleteLoadBalancer",
                    "elasticloadbalancing:DescribeListeners",
                    "elasticloadbalancing:DescribeLoadBalancerAttributes",
                    "elasticloadbalancing:DescribeLoadBalancers",
                    "elasticloadbalancing:DescribeTags",
                    "elasticloadbalancing:ModifyListener",
                    "elasticloadbalancing:ModifyLoadBalancerAttributes",
                    "elasticloadbalancing:SetSecurityGroups"
                ],
                "Resource": [
                    "*"
                ]
            },
            {
                "Sid": "VisualEditor4",
                "Effect": "Allow",
                "Action": [
                    "s3:CreateBucket",
                    "s3:DeleteBucket",
                    "s3:DeleteObject",
                    "s3:GetAccelerateConfiguration",
                    "s3:GetBucketAcl",
                    "s3:GetBucketCORS",
                    "s3:GetBucketLogging",
                    "s3:GetBucketObjectLockConfiguration",
                    "s3:GetBucketPolicy",
                    "s3:GetBucketRequestPayment",
                    "s3:GetBucketTagging",
                    "s3:GetBucketVersioning",
                    "s3:GetBucketWebsite",
                    "s3:GetEncryptionConfiguration",
                    "s3:GetIntelligentTieringConfiguration",
                    "s3:GetLifecycleConfiguration",
                    "s3:GetObject",
                    "s3:GetObjectAcl",
                    "s3:GetReplicationConfiguration",
                    "s3:ListAllMyBuckets",
                    "s3:ListBucket",
                    "s3:PutBucketAcl",
                    "s3:PutIntelligentTieringConfiguration",
                    "s3:PutObject"
                ],
                "Resource": [
                    "*"
                ]
            }
        ]
    })
    }
    % 
  ```
- お試し1結果
  ```
  % terraform apply
    (略)
    aws_s3_bucket.cloudfront_logs: Destroying... [id=my-cloudfront-logs-bucket-2b7mc6d2ty0gjo2e]
    aws_vpc.this: Creating...
    aws_s3_bucket.cloudfront_logs: Destruction complete after 0s
    aws_s3_bucket.cloudfront_logs: Creating...
    ╷
    │ Error: creating EC2 VPC: operation error EC2: CreateVpc, https response error StatusCode: 403, RequestID: ab0f87d6-db89-4a5f-9786-e38559be57e0, api error UnauthorizedOperation: You are not authorized to perform this operation. User: arn:aws:iam::xxxxxxx:user/pike-user is not authorized to perform: ec2:CreateTags on resource: arn:aws:ec2:ap-northeast-1:xxxxxxx:vpc/* because no identity-based policy allows the ec2:CreateTags action. Encoded authorization failure message: IzFGgY7jnF4BcldQZ0vnYFirT3zbnWBZe3AOOHpROEMQdKzUuMK2zBItEsKCv9DDeG-Jm4UgNcsjufL6pXvSAd99ycKcUMSFaWzkufUcS2V3hMKbZVx806YcK27h-KMCVPmz10-zmtqAy8qF16LwCE0s502Kc33nGigk337D0IrAx1B_dPmtMwefvNOBEOxtHmINygZpQblPvm3Q0ZEWEOFoZPfkrczcDLVKFE3nOqPA6FIzuAdXIQh6ODmPkpgptct5dso9QhAGthiI84wqaUJFsaRTivqIdyu8VXK-C8qScx-TFU8mZpCJCE85INdMnAzzBTICW6cpN8nHqbstDgpe0m0uqQ_QwFDvQssAn9-if2ZNupXiLpF4XrQEuU9P9Jrbk4EDNznl8reBrkNuMHar6pfPV_bJzQSeDi3fGt2hV0gz-cN8xlKQakO36JRg8rJ97xv728FCHGU1zq03iMGoWex1tOf9EQfEvES-S6UeCvzkPal57PjMUKMy1pwJpN4tVvrMCw
    │ 
    │   with aws_vpc.this,
    │   on main.tf line 40, in resource "aws_vpc" "this":
    │   40: resource "aws_vpc" "this" {
    │ 
    ╵
    ╷
    │ Error: setting S3 Bucket (my-cloudfront-logs-bucket-2b7mc6d2ty0gjo2e) tags: setting resource tags (my-cloudfront-logs-bucket-2b7mc6d2ty0gjo2e): operation error S3: PutBucketTagging, https response error StatusCode: 403, RequestID: RKPKAPV81KH3A4HG, HostID: sgAiWfQTj0jdUYs7BJkpOw2TmBbVSLbO8127BquJXnOk3rZy6jhTrAZ/IYR648f/3foYqKtXDBCgcaTr3emFWaePSqrJ4ImC, api error AccessDenied: User: arn:aws:iam::xxxxxxx:user/pike-user is not authorized to perform: s3:PutBucketTagging on resource: "arn:aws:s3:::my-cloudfront-logs-bucket-2b7mc6d2ty0gjo2e" because no identity-based policy allows the s3:PutBucketTagging action
    │ 
    │   with aws_s3_bucket.cloudfront_logs,
    │   on main.tf line 159, in resource "aws_s3_bucket" "cloudfront_logs":
    │  159: resource "aws_s3_bucket" "cloudfront_logs" {
    │ 
    ╵
    % 
  ```
- お試し2
  - 以下権限を付与して再実行
    - ec2:CreateTags
    - s3:PutBucketTagging
- お試し2結果
  ```
    % terraform apply
    (略)
    aws_s3_bucket.cloudfront_logs: Destroying... [id=my-cloudfront-logs-bucket-2b7mc6d2ty0gjo2e]
    aws_vpc.this: Creating...
    aws_s3_bucket.cloudfront_logs: Destruction complete after 0s
    aws_s3_bucket.cloudfront_logs: Creating...
    aws_vpc.this: Creation complete after 1s [id=vpc-0446c70777575d991]
    aws_internet_gateway.this: Creating...
    aws_subnet.this["ap-northeast-1d"]: Creating...
    aws_security_group.private_alb: Creating...
    aws_subnet.this["ap-northeast-1c"]: Creating...
    aws_subnet.this["ap-northeast-1a"]: Creating...
    aws_internet_gateway.this: Creation complete after 1s [id=igw-08a69dc8b79e1eed0]
    aws_subnet.this["ap-northeast-1c"]: Creation complete after 1s [id=subnet-0c74ad680131b3de1]
    aws_s3_bucket.cloudfront_logs: Creation complete after 2s [id=my-cloudfront-logs-bucket-2b7mc6d2ty0gjo2e]
    aws_s3_bucket_ownership_controls.cloudfront_logs: Creating...
    aws_subnet.this["ap-northeast-1a"]: Creation complete after 2s [id=subnet-03d7214512c9747f2]
    aws_security_group.private_alb: Creation complete after 3s [id=sg-0ac3a09108266aaee]
    aws_subnet.this["ap-northeast-1d"]: Creation complete after 3s [id=subnet-0b55ac8fb8f512ced]
    aws_lb.private_alb: Creating...
    ╷
    │ Error: creating ELBv2 application Load Balancer (private-alb): operation error Elastic Load Balancing v2: CreateLoadBalancer, https response error StatusCode: 403, RequestID: c17cdd8d-e826-454f-b6a9-1af1be1a5a67, api error AccessDenied: User: arn:aws:iam::xxxxxxx:user/pike-user is not authorized to perform: elasticloadbalancing:AddTags on resource: arn:aws:elasticloadbalancing:ap-northeast-1:xxxxxxx:loadbalancer/app/private-alb/* because no identity-based policy allows the elasticloadbalancing:AddTags action
    │ 
    │   with aws_lb.private_alb,
    │   on main.tf line 67, in resource "aws_lb" "private_alb":
    │   67: resource "aws_lb" "private_alb" {
    │ 
    ╵
    ╷
    │ Error: creating S3 Bucket (my-cloudfront-logs-bucket-2b7mc6d2ty0gjo2e) Ownership Controls: operation error S3: PutBucketOwnershipControls, https response error StatusCode: 403, RequestID: W70TJ5QW10ARWE2A, HostID: LxtrBk49SXTRYKqTD4PzKwoDSNhcdlQftq5meyK3m15X/Yd8UynJg5fje58RwGJZkyGkEoaRhbc=, api error AccessDenied: User: arn:aws:iam::xxxxxxx:user/pike-user is not authorized to perform: s3:PutBucketOwnershipControls on resource: "arn:aws:s3:::my-cloudfront-logs-bucket-2b7mc6d2ty0gjo2e" because no identity-based policy allows the s3:PutBucketOwnershipControls action
    │ 
    │   with aws_s3_bucket_ownership_controls.cloudfront_logs,
    │   on main.tf line 164, in resource "aws_s3_bucket_ownership_controls" "cloudfront_logs":
    │  164: resource "aws_s3_bucket_ownership_controls" "cloudfront_logs" {
    │ 
    ╵
    % 
  ```
- お試し3
  - 以下権限を付与して再実行
    - elasticloadbalancing:AddTags
    - s3:PutBucketOwnershipControls
- お試し3結果
  ```
    % terraform apply
    (略)
    aws_s3_bucket_ownership_controls.cloudfront_logs: Creating...
    aws_lb.private_alb: Creating...
    aws_lb.private_alb: Still creating... [10s elapsed]
    aws_lb.private_alb: Still creating... [20s elapsed]
    aws_lb.private_alb: Still creating... [30s elapsed]
    aws_lb.private_alb: Still creating... [40s elapsed]
    aws_lb.private_alb: Still creating... [50s elapsed]
    aws_lb.private_alb: Still creating... [1m0s elapsed]
    aws_lb.private_alb: Still creating... [1m10s elapsed]
    aws_lb.private_alb: Still creating... [1m20s elapsed]
    aws_lb.private_alb: Still creating... [1m30s elapsed]
    aws_lb.private_alb: Still creating... [1m40s elapsed]
    aws_lb.private_alb: Still creating... [1m50s elapsed]
    aws_lb.private_alb: Still creating... [2m0s elapsed]
    aws_lb.private_alb: Still creating... [2m10s elapsed]
    aws_lb.private_alb: Still creating... [2m20s elapsed]
    aws_lb.private_alb: Still creating... [2m30s elapsed]
    aws_lb.private_alb: Still creating... [2m40s elapsed]
    aws_lb.private_alb: Still creating... [2m50s elapsed]
    aws_lb.private_alb: Still creating... [3m0s elapsed]
    aws_lb.private_alb: Creation complete after 3m2s [id=arn:aws:elasticloadbalancing:ap-northeast-1:xxxxxxx:loadbalancer/app/private-alb/9b05e6e5e9318d4b]
    aws_lb_listener.private_alb: Creating...
    aws_cloudfront_vpc_origin.private_alb: Creating...
    ╷
    │ Error: modifying ELBv2 Listener (arn:aws:elasticloadbalancing:ap-northeast-1:xxxxxxx:listener/app/private-alb/9b05e6e5e9318d4b/00dcecacfa73d17c) attributes: operation error Elastic Load Balancing v2: ModifyListenerAttributes, https response error StatusCode: 403, RequestID: 7e779449-47d8-484f-b7b8-fe75074bb58a, api error AccessDenied: User: arn:aws:iam::xxxxxxx:user/pike-user is not authorized to perform: elasticloadbalancing:ModifyListenerAttributes on resource: arn:aws:elasticloadbalancing:ap-northeast-1:xxxxxxx:listener/app/private-alb/9b05e6e5e9318d4b/00dcecacfa73d17c because no identity-based policy allows the elasticloadbalancing:ModifyListenerAttributes action
    │ 
    │   with aws_lb_listener.private_alb,
    │   on main.tf line 75, in resource "aws_lb_listener" "private_alb":
    │   75: resource "aws_lb_listener" "private_alb" {
    │ 
    ╵
    ╷
    │ Error: creating CloudFront VPC Origin
    │ 
    │   with aws_cloudfront_vpc_origin.private_alb,
    │   on main.tf line 91, in resource "aws_cloudfront_vpc_origin" "private_alb":
    │   91: resource "aws_cloudfront_vpc_origin" "private_alb" {
    │ 
    │ operation error CloudFront: CreateVpcOrigin, https response error StatusCode: 403, RequestID: 084d810f-5d08-4b4d-a750-850d781162b6,
    │ AccessDenied: User: arn:aws:iam::xxxxxxx:user/pike-user is not authorized to perform: cloudfront:TagResource on resource:
    │ arn:aws:cloudfront::xxxxxxx:vpcorigin/* because no identity-based policy allows the cloudfront:TagResource action
    ╵
    ╷
    │ Error: waiting for S3 Bucket Ownership Controls (my-cloudfront-logs-bucket-2b7mc6d2ty0gjo2e) create: operation error S3: GetBucketOwnershipControls, https response error StatusCode: 403, RequestID: HH78Q50EWF4TDFJ5, HostID: qAa7rSmG6bW9g4MZLgbaLYDKjfpdbRn3NAGI9mMb99vrt31MxCny8+nHRmttKDhRgXTw+oMfotE=, api error AccessDenied: User: arn:aws:iam::xxxxxxx:user/pike-user is not authorized to perform: s3:GetBucketOwnershipControls on resource: "arn:aws:s3:::my-cloudfront-logs-bucket-2b7mc6d2ty0gjo2e" because no identity-based policy allows the s3:GetBucketOwnershipControls action
    │ 
    │   with aws_s3_bucket_ownership_controls.cloudfront_logs,
    │   on main.tf line 164, in resource "aws_s3_bucket_ownership_controls" "cloudfront_logs":
    │  164: resource "aws_s3_bucket_ownership_controls" "cloudfront_logs" {
    │ 
    ╵
    % 
  ```
- お試し4
  - 以下権限を付与して再実行
    - elasticloadbalancing:ModifyListenerAttributes
    - cloudfront:TagResource
    - s3:GetBucketOwnershipControls
- お試し4結果
  ```
    % terraform apply
    (略)
    Plan: 5 to add, 0 to change, 1 to destroy.

    Changes to Outputs:
    + cloudfront_url = (known after apply)
    ╷
    │ Error: reading ELBv2 Listener (arn:aws:elasticloadbalancing:ap-northeast-1:xxxxxxx:listener/app/private-alb/9b05e6e5e9318d4b/00dcecacfa73d17c) attributes: operation error Elastic Load Balancing v2: DescribeListenerAttributes, https response error StatusCode: 403, RequestID: 9cc34d93-7b76-43ab-b33b-46ee23907bf5, api error AccessDenied: User: arn:aws:iam::xxxxxxx:user/pike-user is not authorized to perform: elasticloadbalancing:DescribeListenerAttributes because no identity-based policy allows the elasticloadbalancing:DescribeListenerAttributes action
    │ 
    │   with aws_lb_listener.private_alb,
    │   on main.tf line 75, in resource "aws_lb_listener" "private_alb":
    │   75: resource "aws_lb_listener" "private_alb" {
    │ 
    ╵
    % 
  ```
- お試し5
  - 以下権限を付与して再実行
  ```
  elasticloadbalancing:DescribeListenerAttributes
  ```
- お試し5結果
  - この回でようやく成功！！
  ```
    % terraform apply
    (略)
    aws_cloudfront_distribution.private_alb: Creation complete after 4m49s [id=E2MPHC3MIQK7SJ]

    Apply complete! Resources: 6 added, 0 changed, 2 destroyed.

    Outputs:

    cloudfront_url = "http://d2giur4kk0v5i9.cloudfront.net"
    %
  ```
## 所感
- 結果、Pikeコマンドによる生成と手動追加をしたことにより、これで十分という権限は以下IAMポリシーとなりました
  ```
  {
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "cloudfront:CreateDistribution",
                "cloudfront:CreateDistributionWithTags",
                "cloudfront:CreateVpcOrigin",
                "cloudfront:DeleteDistribution",
                "cloudfront:DeleteVpcOrigin",
                "cloudfront:GetDistribution",
                "cloudfront:GetDistributionConfig",
                "cloudfront:GetVpcOrigin",
                "cloudfront:ListTagsForResource",
                "cloudfront:UpdateDistribution",
                "cloudfront:UpdateDistributionWithStagingConfig",
                "cloudfront:UpdateVpcOrigin",
                "cloudfront:TagResource"
            ],
            "Resource": [
                "*"
            ]
        },
        {
            "Sid": "VisualEditor1",
            "Effect": "Allow",
            "Action": [
                "dynamodb:DeleteItem",
                "dynamodb:DescribeTable",
                "dynamodb:GetItem",
                "dynamodb:PutItem"
            ],
            "Resource": [
                "*"
            ]
        },
        {
            "Sid": "VisualEditor2",
            "Effect": "Allow",
            "Action": [
                "ec2:AssociateSubnetCidrBlock",
                "ec2:AttachInternetGateway",
                "ec2:AuthorizeSecurityGroupIngress",
                "ec2:CreateInternetGateway",
                "ec2:CreateSecurityGroup",
                "ec2:CreateSubnet",
                "ec2:CreateVPC",
                "ec2:CreateTags",
                "ec2:DeleteInternetGateway",
                "ec2:DeleteNetworkInterface",
                "ec2:DeleteSecurityGroup",
                "ec2:DeleteSubnet",
                "ec2:DeleteVPC",
                "ec2:DescribeAccountAttributes",
                "ec2:DescribeInternetGateways",
                "ec2:DescribeManagedPrefixLists",
                "ec2:DescribeNetworkAcls",
                "ec2:DescribeNetworkInterfaces",
                "ec2:DescribeSecurityGroups",
                "ec2:DescribeSubnets",
                "ec2:DescribeVpcAttribute",
                "ec2:DescribeVpcs",
                "ec2:DetachInternetGateway",
                "ec2:DetachNetworkInterface",
                "ec2:DisassociateSubnetCidrBlock",
                "ec2:GetManagedPrefixListEntries",
                "ec2:ModifySubnetAttribute",
                "ec2:ModifyVpcAttribute",
                "ec2:ModifyVpcTenancy",
                "ec2:RevokeSecurityGroupEgress",
                "ec2:RevokeSecurityGroupIngress"
            ],
            "Resource": [
                "*"
            ]
        },
        {
            "Sid": "VisualEditor3",
            "Effect": "Allow",
            "Action": [
                "elasticloadbalancing:AttachLoadBalancerToSubnets",
                "elasticloadbalancing:CreateListener",
                "elasticloadbalancing:CreateLoadBalancer",
                "elasticloadbalancing:CreateLoadBalancerListeners",
                "elasticloadbalancing:DeleteListener",
                "elasticloadbalancing:DeleteLoadBalancer",
                "elasticloadbalancing:DescribeListeners",
                "elasticloadbalancing:DescribeLoadBalancerAttributes",
                "elasticloadbalancing:DescribeLoadBalancers",
                "elasticloadbalancing:DescribeTags",
                "elasticloadbalancing:ModifyListener",
                "elasticloadbalancing:ModifyLoadBalancerAttributes",
                "elasticloadbalancing:SetSecurityGroups",
                "elasticloadbalancing:AddTags",
                "elasticloadbalancing:ModifyListenerAttributes",
                "elasticloadbalancing:DescribeListenerAttributes"
            ],
            "Resource": [
                "*"
            ]
        },
        {
            "Sid": "VisualEditor4",
            "Effect": "Allow",
            "Action": [
                "s3:CreateBucket",
                "s3:DeleteBucket",
                "s3:DeleteObject",
                "s3:GetAccelerateConfiguration",
                "s3:GetBucketAcl",
                "s3:GetBucketCORS",
                "s3:GetBucketLogging",
                "s3:GetBucketObjectLockConfiguration",
                "s3:GetBucketPolicy",
                "s3:GetBucketRequestPayment",
                "s3:GetBucketTagging",
                "s3:GetBucketVersioning",
                "s3:GetBucketWebsite",
                "s3:GetEncryptionConfiguration",
                "s3:GetIntelligentTieringConfiguration",
                "s3:GetLifecycleConfiguration",
                "s3:GetObject",
                "s3:GetObjectAcl",
                "s3:GetReplicationConfiguration",
                "s3:ListAllMyBuckets",
                "s3:ListBucket",
                "s3:PutBucketAcl",
                "s3:PutIntelligentTieringConfiguration",
                "s3:PutObject",
                "s3:PutBucketTagging",
                "s3:PutBucketOwnershipControls",
                "s3:GetBucketOwnershipControls"
            ],
            "Resource": [
                "*"
            ]
        }
    ]
  }
  ```
- しかし、追加で確認しましたがDynamoDBの権限はやはり不要だったり、tag系は苦手としているなど、本当に最小権限をTerraformのコードを見て自動生成しているのという多少疑問は残る結果となりました
  - DynamoDBの権限が存在している理由の可能性として、TerraformのバックエンドをS3にしていることから、ロック機能を使用することを想定しているのかもしれません
  - また、scanコマンドは待機時間もなく即結果が返ってきたので、これはコードが200行ちょっとと小規模であるからなのか？、、と思っていました
  - 今にして思うと、全ての内容をscanしているわけではなく、ある程度目星をつけて、こういった構成だったらきっとこういう権限が必要だよね、、で生成しているような気もします
- Pikeコマンドは有益ではあるものの、あくまで参考として使用するくらいに留めておくのが良さそうですね
- ただ、VPC Originという近々リリースされた機能に対応していたのは正直驚きました
  - これも頻度高くPikeがリリースされているからできることなのかなと思います
# 参考
## Claude 3.5 Sonnetでも最小権限ポリシーの発行を試してみた
### プロンプト
### 発行されたIAMポリシー
注：”terraform-backend-73m3j2gank8zpymc8yyn”はTerraformのbackend用として用意したS3バケットです
```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "TerraformStateManagement",
            "Effect": "Allow",
            "Action": [
                "s3:GetObject",
                "s3:PutObject",
                "s3:ListBucket"
            ],
            "Resource": [
                "arn:aws:s3:::terraform-backend-73m3j2gank8zpymc8yyn",
                "arn:aws:s3:::terraform-backend-73m3j2gank8zpymc8yyn/*"
            ]
        },
        {
            "Sid": "VPCManagement",
            "Effect": "Allow",
            "Action": [
                "ec2:CreateVpc",
                "ec2:DeleteVpc",
                "ec2:ModifyVpcAttribute",
                "ec2:CreateTags",
                "ec2:DeleteTags",
                "ec2:CreateInternetGateway",
                "ec2:DeleteInternetGateway",
                "ec2:AttachInternetGateway",
                "ec2:DetachInternetGateway",
                "ec2:CreateSubnet",
                "ec2:DeleteSubnet",
                "ec2:ModifySubnetAttribute",
                "ec2:DescribeVpcs",
                "ec2:DescribeInternetGateways",
                "ec2:DescribeSubnets",
                "ec2:DescribeAvailabilityZones",
                "ec2:DescribeManagedPrefixLists"
            ],
            "Resource": "*"
        },
        {
            "Sid": "SecurityGroupManagement",
            "Effect": "Allow",
            "Action": [
                "ec2:CreateSecurityGroup",
                "ec2:DeleteSecurityGroup",
                "ec2:AuthorizeSecurityGroupIngress",
                "ec2:RevokeSecurityGroupIngress",
                "ec2:DescribeSecurityGroups"
            ],
            "Resource": "*"
        },
        {
            "Sid": "LoadBalancerManagement",
            "Effect": "Allow",
            "Action": [
                "elasticloadbalancing:CreateLoadBalancer",
                "elasticloadbalancing:DeleteLoadBalancer",
                "elasticloadbalancing:ModifyLoadBalancerAttributes",
                "elasticloadbalancing:CreateListener",
                "elasticloadbalancing:DeleteListener",
                "elasticloadbalancing:ModifyListener",
                "elasticloadbalancing:DescribeLoadBalancers",
                "elasticloadbalancing:DescribeListeners",
                "elasticloadbalancing:DescribeLoadBalancerAttributes"
            ],
            "Resource": "*"
        },
        {
            "Sid": "CloudFrontManagement",
            "Effect": "Allow",
            "Action": [
                "cloudfront:CreateDistribution",
                "cloudfront:DeleteDistribution",
                "cloudfront:GetDistribution",
                "cloudfront:UpdateDistribution",
                "cloudfront:TagResource",
                "cloudfront:UntagResource",
                "cloudfront:CreateOriginAccessControl",
                "cloudfront:DeleteOriginAccessControl",
                "cloudfront:GetOriginAccessControl",
                "cloudfront:CreateVpcOriginAssociation",
                "cloudfront:DeleteVpcOriginAssociation",
                "cloudfront:GetVpcOriginAssociation"
            ],
            "Resource": "*"
        },
        {
            "Sid": "S3Management",
            "Effect": "Allow",
            "Action": [
                "s3:CreateBucket",
                "s3:DeleteBucket",
                "s3:PutBucketAcl",
                "s3:PutBucketOwnershipControls",
                "s3:GetBucketAcl",
                "s3:GetBucketOwnershipControls",
                "s3:GetBucketLocation"
            ],
            "Resource": [
                "arn:aws:s3:::my-cloudfront-logs-bucket-*"
            ]
        },
        {
            "Sid": "IAMReadOnly",
            "Effect": "Allow",
            "Action": [
                "iam:GetUser"
            ],
            "Resource": "*"
        }
    ]
}
```
### 結果
- Claude 3.5 Sonnetでも一発では必要ポリシー発行はできませんでした、、
```
% terraform apply  
data.aws_canonical_user_id.current: Reading...
data.aws_ec2_managed_prefix_list.cloudfront: Reading...
(略)
╷
│ Error: reading EC2 Managed Prefix List (pl-58a04531) Entries: operation error EC2: GetManagedPrefixListEntries, https response error StatusCode: 403, RequestID: 071dcaf0-c5bd-44b1-8023-e337d4b30953, api error UnauthorizedOperation: You are not authorized to perform this operation. User: arn:aws:iam::xxxxxxx:user/pike-user is not authorized to perform: ec2:GetManagedPrefixListEntries on resource: arn:aws:ec2:ap-northeast-1:aws:prefix-list/pl-58a04531 because no identity-based policy allows the ec2:GetManagedPrefixListEntries action. Encoded authorization failure message: ZejH3YupWbKXhnOaDBNYiWZ1LD4DWimmF9Dm8I-qLSarQq4U54YRRlCE6Xi6fLmKUUuiEzhm2cgcX-ktCJ-KTk9Di5xUyaSJjmyI7tCOrUgp4JAunwjGzhNETp_hpvjc5hGCXvWKH5nAUg27R6mi47EZvFNSThTjGm3V5Mh6Al24XIwF5tZk2t5GYst1jn6bB_GzvNDGZeTCLDqwyNplK5CiyRR5phTrt3T7hXqUt65nig5i75t5ATImEB4Ck-OERVabPOZAD5aT_kiNXmRSbfFei7pM54ZwkSYduCDr5VLoCaeQIOh5ByzisaztOEMwzminFpS4CZfuFVeEdK_Z9PxxEpsRoH9a14INDk8Ct3Wf-Nu9_dHaMAGrDeBd3zcDjIZq9tkxrpNBuK1o3z7bzMYBlFdFTssFuc0ftviVLoncSNjnc1yHPQkioyys4tHWpj6rPExm-mhOADvEJcwrj8POH6EAcgF-1meikN3awA9nYl8YGEe2faxra1bmZAcdRA-xWLzaNz4fngCBN3i4l-AYRTxMkwprGsJnqvDMMStLRCI2fDEFUwMojt8
│ 
│   with data.aws_ec2_managed_prefix_list.cloudfront,
│   on main.tf line 36, in data "aws_ec2_managed_prefix_list" "cloudfront":
│   36: data "aws_ec2_managed_prefix_list" "cloudfront" {
│ 
╵
╷
│ Error: listing S3 Buckets: operation error S3: ListBuckets, https response error StatusCode: 403, RequestID: WCJHZM1DKP1MJWV0, HostID: 5FZHP32VERALfR4kAJV+xG2S+UWbsoytA8SPmbMUnTQHv6uPCQ28nO023/hQCMd4Tu4UJ5IeeJU=, api error AccessDenied: User: arn:aws:iam::xxxxxxx:user/pike-user is not authorized to perform: s3:ListAllMyBuckets because no identity-based policy allows the s3:ListAllMyBuckets action
│ 
│   with data.aws_canonical_user_id.current,
│   on main.tf line 207, in data "aws_canonical_user_id" "current":
│  207: data "aws_canonical_user_id" "current" {}
│ 
╵
shinjo.atuhisa@MacBookAir web_env_v2 % ╷
│ Error: reading EC2 Managed Prefix List (pl-58a04531) Entries: operation error EC2: GetManagedPrefixListEntries, https response error StatusCode: 403, RequestID: 071dcaf0-c5bd-44b1-8023-e337d4b30953, api error UnauthorizedOperation: You are not authorized to perform this operation. User: arn:aws:iam::xxxxxxx:user/pike-user is not authorized to perform: ec2:GetManagedPrefixListEntries on resource: arn:aws:ec2:ap-northeast-1:aws:prefix-list/pl-58a04531 because no identity-based policy allows the ec2:GetManagedPrefixListEntries action. Encoded authorization failure message: ZejH3YupWbKXhnOaDBNYiWZ1LD4DWimmF9Dm8I-qLSarQq4U54YRRlCE6Xi6fLmKUUuiEzhm2cgcX-ktCJ-KTk9Di5xUyaSJjmyI7tCOrUgp4JAunwjGzhNETp_hpvjc5hGCXvWKH5nAUg27R6mi47EZvFNSThTjGm3V5Mh6Al24XIwF5tZk2t5GYst1jn6bB_GzvNDGZeTCLDqwyNplK5CiyRR5phTrt3T7hXqUt65nig5i75t5ATImEB4Ck-OERVabPOZAD5aT_kiNXmRSbfFei7pM54ZwkSYduCDr5VLoCaeQIOh5ByzisaztOEMwzminFpS4CZfuFVeEdK_Z9PxxEpsRoH9a14INDk8Ct3Wf-Nu9_dHaMAGrDeBd3zcDjIZq9tkxrpNBuK1o3z7bzMYBlFdFTssFuc0ftviVLoncSNjnc1yHPQkioyys4tHWpj6rPExm-mhOADvEJcwrj8POH6EAcgF-1meikN3awA9nYl8YGEe2faxra1bmZAcdRA-xWLzaNz4fngCBN3i4l-AYRTxMkwprGsJnqvDMMStLRCI2fDEFUwMojt8
│ 
│   with data.aws_ec2_managed_prefix_list.cloudfront,
│   on main.tf line 36, in data "aws_ec2_managed_prefix_list" "cloudfront":
│   36: data "aws_ec2_managed_prefix_list" "cloudfront" {
│ 
╵
╷
│ Error: listing S3 Buckets: operation error S3: ListBuckets, https response error StatusCode: 403, RequestID: WCJHZM1DKP1MJWV0, HostID: 5FZHP32VERALfR4kAJV+xG2S+UWbsoytA8SPmbMUnTQHv6uPCQ28nO023/hQCMd4Tu4UJ5IeeJU=, api error AccessDenied: User: arn:aws:iam::xxxxxxx:user/pike-user is not authorized to perform: s3:ListAllMyBuckets because no identity-based policy allows the s3:ListAllMyBuckets action
│ 
│   with data.aws_canonical_user_id.current,
│   on main.tf line 207, in data "aws_canonical_user_id" "current":
│  207: data "aws_canonical_user_id" "current" {}
│ 
╵
% 
```
## 参考ページ
- [Pike GitHub](https://github.com/JamesWoolfenden/pike)
- [Pikeを使ってTerraform実行ユーザー用の最小限のIAMポリシーを生成する](https://dev.classmethod.jp/articles/pike-terraform-generate-iam-policy/)
- [Windows でのパッケージ管理は Scoop が便利](https://qiita.com/coitate/items/2cff1f7335e231f2d4d5)