Terraformで Amazon Verified Permissionsを管理してみる（awscc）

# 最初に
本記事は Terraform Provider `aws` でなく `awscc` を利用している記事です。

https://registry.terraform.io/providers/hashicorp/awscc/latest


Verified PermissionsはIaCと相性良いと思っているので、以下のIssueを日々ウォッチしながら待っていました。

https://github.com/hashicorp/terraform-provider-aws/issues/32158

そんな中、上記のissueで `awscc` のプロバイダーに実装されたとコメントがあったので、そちらを使ってみた記事です。

`awscc` は HashiCorp オフィシャル管理であるものの、v1.0に到達していないので、利用は自己責任でお願いします。

※例えば執筆時点でも以下のIssueのバグなどがあります
https://github.com/hashicorp/terraform-provider-awscc/issues/1284

# 参考にした記事
CDK管理でも問題無さそうなら、こちらの記事を参考にCDK管理した方が良いかもです。

https://tech.isid.co.jp/entry/verified_permissions_and_cdk


# VSCodeの設定
IaC管理するとしたら、開発端末でCedarやSchemaを書くことになりますが、何の補助もなしで書くのはtypoとかもありそうで、心許ないなと思っていました。
ただ、その点は以下のPlugin入れれば解決します。設定などは上記のCDKの記事を参考にしました。

https://marketplace.visualstudio.com/items?itemName=cedar-policy.vscode-cedar


# 実装
では実際の内容に入ります。

## ディレクトリ構成
まずディレクトリ構成は以下のように整理しました。

```
├── cedar
│   ├── P001.cedar
│   ├── P002.cedar
│   ├── P003.cedar
│   └── xxx.cedar
├── schema
│   └── cedarschema.json
├── main.tf
```


## スキーマ定義: `schema/cedarschema.json` 
まずはサンプル用に以下のようなスキーマを定義してみました。




```json
{
    "MyApp": {
        "entityTypes": {
            "User": {
                "memberOfTypes": [
                    "Group"
                ]
            },
            "Api": {
                "memberOfTypes": [],
                "shape": {
                    "attributes": {
                        "path": {
                            "required": true,
                            "type": "String"
                        }
                    },
                    "type": "Record"
                }
            },
            "Group": {
                "shape": {
                    "attributes": {},
                    "type": "Record"
                },
                "memberOfTypes": []
            }
        },
        "actions": {
            "HEAD": {
                "appliesTo": {
                    "resourceTypes": [
                        "Api"
                    ],
                    "context": {
                        "type": "Record",
                        "attributes": {}
                    },
                    "principalTypes": [
                        "Group",
                        "User"
                    ]
                }
            },
            "GET": {
                "appliesTo": {
                    "context": {
                        "type": "Record",
                        "attributes": {}
                    },
                    "resourceTypes": [
                        "Api"
                    ],
                    "principalTypes": [
                        "Group",
                        "User"
                    ]
                }
            },
            "PUT": {
                "appliesTo": {
                    "resourceTypes": [
                        "Api"
                    ],
                    "context": {
                        "type": "Record",
                        "attributes": {}
                    },
                    "principalTypes": [
                        "Group",
                        "User"
                    ]
                }
            },
            "OPTIONS": {
                "appliesTo": {
                    "principalTypes": [
                        "Group",
                        "User"
                    ],
                    "resourceTypes": [
                        "Api"
                    ],
                    "context": {
                        "type": "Record",
                        "attributes": {}
                    }
                }
            },
            "POST": {
                "appliesTo": {
                    "context": {
                        "attributes": {},
                        "type": "Record"
                    },
                    "resourceTypes": [
                        "Api"
                    ],
                    "principalTypes": [
                        "Group",
                        "User"
                    ]
                }
            }
        }
    }
}
```

## ポリシー定義: `cedar/xxx.cedar` 
以下の二つのポリシー定義してみました。

```P001.cedar
permit (principal, action, resource)
when { principal in [MyApp::Group::"admin"] };
```

```P002.cedar
permit (
  principal,
  action in
    [MyApp::Action::"GET", MyApp::Action::"OPTIONS", MyApp::Action::"HEAD"],
  resource
);
```

## Terraformの定義
`main.tf` にまとめて書いてあります。バージョン等は適宜変更してほしいですが、実際に動かしたものを置いておきます。


ここまで見てきてわかる通り、Terraformとしては別ファイルで開発した定義をAWSに設定するだけの動きとなります。

```main.tf
terraform {
  required_version = ">= v1.5.2"
  required_providers {
    awscc = {
      version = "= 0.66.0"
      source  = "hashicorp/awscc"
    }
  }
  backend "s3" {
  }
}

variable "region" {
  type = string
}
variable "profile" {
  type = string
}

provider "awscc" {
  profile = var.profile
  region  = var.region
}

locals {
  cedar_dir   = "${path.module}/cedar"
  cedar_files = fileset(local.cedar_dir, "**/*.cedar")
}

resource "awscc_verifiedpermissions_policy_store" "this" {
  validation_settings = {
    mode = "STRICT"
  }

  schema = {
    cedar_json = file("${path.module}/schema/cedarschema.json")
  }

}

resource "awscc_verifiedpermissions_policy" "this" {
  for_each = local.cedar_files

  policy_store_id = awscc_verifiedpermissions_policy_store.this.id
  definition = {
    static = {
      statement   = file("${local.cedar_dir}/${each.value}")
      description = "Policy File: ${each.value}"
    }
  }
}
```

# terraform apply後確認
問題無く作成できています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116068/09ffa103-1906-15f2-186d-b79f81c9ec6e.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116068/fd4c1674-f5e0-ce12-51c2-de6c6983539a.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116068/8431a4e8-8495-1e8e-a760-eeb1fd0c33ac.png)