Terraform で for_each をリソース間で連鎖・連携させる

公式ドキュメントのChaining for_each Between Resourcesの項目をやってみた
2022.06.29

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

ちゃだいん(@chazuke4649)です。

Terraformで複数リソースを少ないコード量で作成したい場合の選択肢の1つとしてfor_eachの活用があります。
今回はそのfor_eachを別のリソースにもさらに連鎖・連携させてより効率的にリソースを定義することができます。

Chaining for_each Between Resources - The for_each Meta-Argument | Terraform by HashiCorp

for_eachを使用したリソースは、他の場所で式に使用されるとオブジェクトのマップとして表示されるので、2つのオブジェクトのセットの間に一対一の関係がある状況では、あるリソースを他のリソースのfor_eachとして直接使用することができる。

上記を参考にやってみました。

やってみた

最も基本的なパターンは上記ドキュメントのサンプルがわかりやすいですが、今回は AWS SSO の許可セット(あるいは、アクセス権限セット、Permission sets)を作成してみました。

やりたいこと

  • 4つのプロダクト(banana,melon,grape,orange)がある
  • 開発者の権限を参照権限と、担当するプロダクトのEC2へのSession Managerでログインできる権限を付与したい

※こちらの詳細は以下で紹介しているものとほぼ同等です。

[AWS SSO版] Session Manager で特定の EC2 のみアクセスできるよう制限する | DevelopersIO

作成したいリソース

やりたいことを実現する手段として、以下のような構成にします。

  • 許可セット x 4 (banana,melon,grape,orange) を作成したい
  • それぞれの許可セットに対し、2つのアクセスポリシーを付与したい
    • マネージドポリシーの ReadOnlyAccess
    • インラインポリシーの AllowSessionManager
    • インラインポリシーの記述の中に環境変数として、各プロダクト名を入れたい

コードサンプル

作成したいリソースを for_eachの連鎖を使用して以下のように定義できます。

sso.tf

## AWS SSO インスタンス(コンソールでAWS SSOを有効化後、データソースとして引っ張ってくる)
data "aws_ssoadmin_instances" "main" {}

## プロダクト名
locals {
  products = {
    "banana" = "BananaDeveloper"
    "melon"  = "MelonDeveloper"
    "grape"  = "GrapeDeveloper"
    "orange" = "OrangeDeveloper"
  }
}

## 許可セット
resource "aws_ssoadmin_permission_set" "products" {
  for_each = local.products

  name             = each.value
  description      = "Test product developer"
  instance_arn     = tolist(data.aws_ssoadmin_instances.main.arns)[0]
  session_duration = "PT4H"
}

## 参照権限のマネージドポリシー
resource "aws_ssoadmin_managed_policy_attachment" "readonly_products" {
  for_each = aws_ssoadmin_permission_set.products

  instance_arn       = tolist(data.aws_ssoadmin_instances.main.arns)[0]
  permission_set_arn = each.value.arn
  managed_policy_arn = "arn:aws:iam::aws:policy/ReadOnlyAccess"
}

## インラインポリシー
resource "aws_ssoadmin_permission_set_inline_policy" "products" {
  for_each = aws_ssoadmin_permission_set.products

  instance_arn       = tolist(data.aws_ssoadmin_instances.main.arns)[0]
  permission_set_arn = each.value.arn
  inline_policy = templatefile(
    "./policies/AllowSessionManager.json",
    {
      projectName = each.key
    }
  )
}

AllowSessionManager.json

{
  "Version": "2012-10-17",
  "Statement": [
      {
          "Effect": "Allow",
          "Action": [
              "ssm:StartSession"
          ],
          "Resource": [
              "arn:aws:ec2:*:*:instance/*"
          ],
          "Condition": {
              "StringEquals": {
                  "ssm:ResourceTag/Project": [
                      "${projectName}"
                  ]
              }
          }
      },
      {
          "Effect": "Allow",
          "Action": [
              "ssm:TerminateSession",
              "ssm:ResumeSession"
          ],
          "Resource": [
            "arn:aws:ssm:*:*:session/$${aws:PrincipalTag/ssousername}-*"
          ]
      }
  ]
}

解説

そもそもAWS SSOの書き方については、以下を参照ください。

aws_ssoadmin_permission_set | Data Sources | hashicorp/aws | Terraform Registry TerraformがAWS SSO(Single Sign-On)に対応してました | DevelopersIO

また、AllowSessionManager.jsonの解説は以下を参照ください。(本ブログでは割愛します)

[AWS SSO版] Session Manager で特定の EC2 のみアクセスできるよう制限する | DevelopersIO

許可セット部分

まずは、基本的なfor_eachの使い方として、ローカル変数のproductsを繰り返し処理の対象とし、その数だけ許可セットを作成しています。

## プロダクト名
locals {
  products = {
    "banana" = "BananaDeveloper"
    "melon"  = "MelonDeveloper"
    "grape"  = "GrapeDeveloper"
    "orange" = "OrangeDeveloper"
  }
}

## 許可セット
resource "aws_ssoadmin_permission_set" "products" {
  for_each = local.products

  name             = each.value
  description      = "Test product developer"
  instance_arn     = tolist(data.aws_ssoadmin_instances.main.arns)[0]
  session_duration = "PT4H"
}

ポリシー部分

ここが本題です。

今度は for_each で複数作成した許可セットを繰り返し処理の対象とし、その数だけマネージドポリシーやインラインポリシーを作成しています。
各許可セットのARNを下記のように指定することができます。
インラインポリシー側では、各許可セットのキー部分(例"banana"など)を設定することができます。

## 参照権限のマネージドポリシー
resource "aws_ssoadmin_managed_policy_attachment" "readonly_products" {
  for_each = aws_ssoadmin_permission_set.products

  instance_arn       = tolist(data.aws_ssoadmin_instances.main.arns)[0]
  permission_set_arn = each.value.arn
  managed_policy_arn = "arn:aws:iam::aws:policy/ReadOnlyAccess"
}

## インラインポリシー
resource "aws_ssoadmin_permission_set_inline_policy" "products" {
  for_each = aws_ssoadmin_permission_set.products

  instance_arn       = tolist(data.aws_ssoadmin_instances.main.arns)[0]
  permission_set_arn = each.value.arn
  inline_policy = templatefile(
    "./policies/AllowSessionManager.json",
    {
      projectName = each.key
    }
  )
}

上記がfor_eachの連鎖(chaining)となります。

終わりに

Terraform のfor_eachを連鎖させる方法を紹介しました。お役に立てば幸いです。

それではこの辺で。ちゃだいん(@chazuke4649)でした。