😊
terraformでAWS管理ポリシーをマージする
背景
AWSでは管理ポリシーをユーザーやロールに割り当てることができます。しかし、Service Quota Limitがあり、10個までしか割り当てることができません。Quotaを引き上げることもできますが最大でも20個までとなります。無理にQuotaを引き上げるよりは、管理ポリシーの内容をマージしたカスタム管理ポリシーを作った方が早いことがあります。
ところで管理ポリシー自体も6,144文字というサイズ制限があるため、複数の管理ポリシーを単純結合しても溢れます。
方針
管理ポリシーのStatement
には稀によく
{
"Effect": "Allow",
"Resource": "*",
"Action": [
"何か"
]
}
というパターンが現れます。Action
が異なるだけなのでこれを1つのStatement
にマージします。
それでも文字数はカツカツで更なる削減策が必要です。Action
にはこれまた稀によくec2:*
のように全てのを認めるものが含まれているため、この場合、個々のec2:何か
は不要になります。
実装
それではterraform module化していきます。
merged_policy.tf
terraform {
required_version = ">= 0.13"
required_providers {
aws = {
source = "hashicorp/aws"
}
}
}
variable "policy_arns" {
description = "(Required) List of IAM managed policy arn."
type = list(string)
}
# 管理ポリシーを読み込みます。
data "aws_iam_policy" "policies" {
for_each = toset(var.policy_arns)
arn = each.value
}
locals {
# 管理ポリシーのJSONを読み込み、`Statement`を取り出します。仕様上、単一オブジェクト
# の場合とオブジェクト配列の場合があるため`[*]`で配列化しています。配列の配列になる
# ため`flatten()`で平坦化します。
all_statements = flatten([
for it in data.aws_iam_policy.policies : jsondecode(it.policy).Statement[*]
])
# 稀によくあるパターンか否かで場合分けをし、`...`を使ってグループ化します。
# `true`には稀によくあるパターン、`false`にはそれ以外がまとめられます。
# 稀によくあるパターンの条件としては、3項目で、`Action`が存在し、
# `Effect`が`"Allow"`であり、`Resource`が`"*"`で見ています。3項目としているので
# これ以外の`Condition`などは含まれていないことになります。
partitioned = {
for it in local.all_statements :
length(it) == 3 && can(it.Action) &&
try(it.Effect == "Allow" && one(it.Resource[*]) == "*", false) => it...
}
# 稀によくあるパターンについて、namespaceでグループ化します。
grouped = {
for it in flatten(local.partitioned.true[*].Action[*]) :
split(":", it)[0] => it...
}
# 稀によくあるパターンのnamespace毎について、グループ内に`namespace:*`のパターンが
# 存在するか確認します。存在する場合は`namespace:*`のみ、存在しない場合は
# グループそのままとします。
compaction = flatten([
for ns, actions in local.grouped :
contains(actions, "${ns}:*") ? ["${ns}:*"] : actions
])
# 稀によくあるパターンについて`Statement`を再構築しつつ、その他のパターンと
# マージします。
merge_statements = flatten([
{ Effect = "Allow", Resource = "*", Action = local.compaction },
try(local.partitioned.false, []),
])
}
output "json" {
description = "IAM policy document of marged policies."
value = jsonencode({ Version = "2012-10-17", Statement = local.merge_statements })
}
最後に
terraformの利点は、データソースとして既存のリソースを読み込み、それを使って処理を行い、新たなリソース作成に役立てることができる点にあると思います。CloudFormationではこうはいきません。
Discussion