iamポリシーのjsonをHCLで書き直すパズル集

# はじめに。aws_iam_policy_documentは便利だが、、、

terraformでIAMのポリシーを書く時に、いくつか選択肢がありますが、そのうちの一つで [aws_iam_policy_document](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) を使うという手段があります。

 
[Terraformでテンプレートを使ってポリシーを定義する \- Qiita](https://qiita.com/ringo/items/9e05df9da984fdc059f3#aws_iam_policy_document-resource)

> HCLのシンタックスハイライトを使ってポリシーを書ける
plan 実行時にある程度の構文チェックができる
書くものが減りすこしスッキリする（テンプレートの方式だと、 json ファイル、 data ソース、 aws_iam_policy リソースを用意しないといけない）


ということで基本的にすごく便利なのですが、**JSONで書かれた各種サンプルをHCLに変換して書き直さないといけない（コピペできない）というデメリット**があります。

というわけで、いくつか例を挙げながら、JSONのポリシーをaws_iam_policy_documentで書き換えてみます。



# 初級編

お題はこちら。
[ビルドプロジェクトに関する情報の取得をユーザーに許可する
](https://docs.aws.amazon.com/ja_jp/codebuild/latest/userguide/auth-and-access-control-iam-identity-based-access-control.html#customer-managed-policies-example-batch-get-projects)

```json

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "codebuild:BatchGetProjects",
      "Resource": "arn:aws:codebuild:us-east-2:123456789012:project/my*"      
    }
  ]
}
```

一番シンプルなパターンです。

正解はこう。

```hcl
data "aws_iam_policy_document" "sample" {
  version = "2012-10-17"
  statement {
    sid = ""
    actions = [
      "codebuild:BatchGetProjects",
    ]
    effect = "Allow"
    resources = [
      "arn:aws:codebuild:us-east-2:123456789012:project/my*"
    ]
  }
}
// 確認用
output "sample" {
  value = data.aws_iam_policy_document.sample.json
}
```

JSONと1:1の構造になっているのでわかりやすいですね。`terraform plan` を実行すると、内容が同じで有ることが確認できます。

```diff
  + sample = jsonencode(
        {
          + Statement = [
              + {
                  + Action   = "codebuild:BatchGetProjects"
                  + Effect   = "Allow"
                  + Resource = "arn:aws:codebuild:us-east-2:123456789012:project/my*"
                  + Sid      = ""
                },
            ]
          + Version   = "2012-10-17"
        }
    )
```


# 初級編②

次はこちら。
[AWS CodeBuild のアイデンティティベースのポリシーの使用 \- AWS CodeBuild](https://docs.aws.amazon.com/ja_jp/codebuild/latest/userguide/auth-and-access-control-iam-identity-based-access-control.html#customer-managed-policies-example-create-project)

Statementが複数です。

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "codebuild:CreateProject",
      "Resource": "arn:aws:codebuild:us-east-2:123456789012:project/*"      
    },
    {
      "Effect": "Allow",
      "Action": "iam:PassRole",
      "Resource": "arn:aws:iam:123456789012:role/CodeBuildServiceRole"
    }
  ]
}
```

正解はこう。

```terraform
data "aws_iam_policy_document" "sample" {
  version = "2012-10-17"
  statement {
    sid = ""
    actions = [
      "codebuild:codebuild:CreateProject",
    ]
    effect = "Allow"
    resources = [
      "arn:aws:codebuild:us-east-2:123456789012:project/*"
    ]
  }

  statement {
    sid = ""
    actions = [
      "iam:PassRole",
    ]
    effect = "Allow"
    resources = [
      "arn:aws:iam:123456789012:role/CodeBuildServiceRole"
    ]
  }
}
```
シンプルにstatementを複数書いてあげればOKです。

# 中級編
お題はこちら。

[数値条件演算子](https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/reference_policies_elements_condition_operators.html#Conditions_Numeric)

```json
{
  "Version": "2012-10-17",
  "Statement": {
    "Effect": "Allow",
    "Action": "s3:ListBucket",
    "Resource": "arn:aws:s3:::example_bucket",
    "Condition": {"NumericLessThanEquals": {"s3:max-keys": "10"}}
  }
}
```

`condition`という複雑そうなものがありますが、正解はこう。

```terraform

data "aws_iam_policy_document" "sample" {
  version = "2012-10-17"

  statement {
    sid = ""
    actions = [
      "s3:ListBucket",
    ]
    effect = "Allow"
    resources = [
      "arn:aws:s3:::example_bucket"
    ]

    condition {
      test = "NumericLessThanEquals"
      values = ["10"]
      variable = "s3:max-keys"
    }
  }
}
```

書き方がjsonと比べて直感的ではないですが、`aws_iam_policy_document` にも同じように `condition`が書けます。


# 上級編

お題はこちら。

[VPC ネットワークインターフェイスを作成するために必要な AWS のサービスへのアクセスを CodeBuild に許可する
](https://docs.aws.amazon.com/ja_jp/codebuild/latest/userguide/auth-and-access-control-iam-identity-based-access-control.html#customer-managed-policies-example-create-vpc-network-interface)

今までの組み合わせ + `Condition` が複雑なパターンです。一番伝えたかったのはこれ。

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow", 
            "Action": [
                "ec2:CreateNetworkInterface",
                "ec2:DescribeDhcpOptions",
                "ec2:DescribeNetworkInterfaces",
                "ec2:DeleteNetworkInterface",
                "ec2:DescribeSubnets",
                "ec2:DescribeSecurityGroups",
                "ec2:DescribeVpcs"
            ], 
            "Resource": "*" 
        },
        {
            "Effect": "Allow",
            "Action": [
                "ec2:CreateNetworkInterfacePermission"
            ],
            "Resource": "arn:aws:ec2:region:account-id:network-interface/*",
            "Condition": {
                "StringEquals": {
                    "ec2:Subnet": [
                        "arn:aws:ec2:region:account-id:subnet/subnet-id-1",
                        "arn:aws:ec2:region:account-id:subnet/subnet-id-2"
                    ],
                    "ec2:AuthorizedService": "codebuild.amazonaws.com"
                }
            }
        }
    ]
}
```

正解はこう

```terraform
data "aws_iam_policy_document" "sample" {
  version = "2012-10-17"

  statement {
    sid = ""
    actions = [
      "ec2:CreateNetworkInterface",
      "ec2:DescribeDhcpOptions",
      "ec2:DescribeNetworkInterfaces",
      "ec2:DeleteNetworkInterface",
      "ec2:DescribeSubnets",
      "ec2:DescribeSecurityGroups",
      "ec2:DescribeVpcs"
    ]
    effect = "Allow"
    resources = [
      "*"
    ]
  }

  statement {
    sid = ""
    actions = [
      "ec2:CreateNetworkInterfacePermission"
    ]
    effect = "Allow"
    resources = [
      "arn:aws:ec2:region:account-id:network-interface/*"
    ]
    condition {
      test = "StringEquals"
      values = [
        "arn:aws:ec2:region:account-id:subnet/subnet-id-1",
        "arn:aws:ec2:region:account-id:subnet/subnet-id-2"
      ]
      variable = "ec2:Subnet"
    }

    condition {
      test = "StringEquals"
      values = [
        "codebuild.amazonaws.com"
      ]
      variable = "ec2:AuthorizedService"
    }
  }
}
```

conditionを複数書くのが、元のjsonと構造が変わっているのが難しいですね。
`terraform plan`を実行すると同様で有ることが確認できます。

```diff

 + sample = jsonencode(
        {
          + Statement = [
              + {
                  + Action   = [
                      + "ec2:DescribeVpcs",
                      + "ec2:DescribeSubnets",
                      + "ec2:DescribeSecurityGroups",
                      + "ec2:DescribeNetworkInterfaces",
                      + "ec2:DescribeDhcpOptions",
                      + "ec2:DeleteNetworkInterface",
                      + "ec2:CreateNetworkInterface",
                    ]
                  + Effect   = "Allow"
                  + Resource = "*"
                  + Sid      = ""
                },
              + {
                  + Action    = "ec2:CreateNetworkInterfacePermission"
                  + Condition = {
                      + StringEquals = {
                          + ec2:AuthorizedService = "codebuild.amazonaws.com"
                          + ec2:Subnet            = [
                              + "arn:aws:ec2:region:account-id:subnet/subnet-id-2",
                              + "arn:aws:ec2:region:account-id:subnet/subnet-id-1",
                            ]
                        }
                    }
                  + Effect    = "Allow"
                  + Resource  = "arn:aws:ec2:region:account-id:network-interface/*"
                  + Sid       = ""
                },
            ]
          + Version   = "2012-10-17"
        }
    )
```


# おわりに

いくつか具体例を上げてみましたが参考になりましたでしょうか。

このパターンの方がもっと難しい！という題材を見つけた方は教えていただけるとうれしいです。