★★ 中級

05. IAM

AWS で 「誰が/何が/何にアクセスできるか」 を決めるのが IAM。Terraform で IAM を書けるようになると、最小権限の設計が再現可能になり、セキュリティが大きく前進します。

登場人物

用語役割
Role「特定の権限の塊」を一時的に引き受ける器EC2 用ロール、Lambda 用ロール、GitHub Actions 用ロール
Policy「何ができるか」を JSON で書いたルールS3 の特定バケットを Get/Put 可
Trust Policy「誰が/どのサービスがロールを assume できるか」EC2 サービスがこのロールを使える
Instance ProfileEC2 にロールを貼り付けるためのラッパー必須(直接 Role を EC2 には付けられない)
User人間用のアカウント+アクセスキー原則使わない(IAM Identity Center / SSO へ)

2026 年現在、「IAM User は新規に作らない」 が業界の標準。人間は SSO で、機械(EC2/Lambda/CI)はロールで、というのが鉄則です。

aws_iam_role と Trust Policy

ロールは 「Trust Policy」と「権限ポリシー」 のセットで成り立ちます。Trust Policy = assume_role_policy がロール本体に書く必須項目。

# IAM ロールの作成例(assume_role_policy = Trust Policy が必須)
# Trust Policy = 「このロールを誰が assume(引き受け)できるか」を定義
resource "aws_iam_role" "ec2_web" {
  name = "ec2-web"

  # jsonencode で HCL の map を JSON 文字列に変換
  # Principal.Service = "ec2.amazonaws.com" → EC2 サービスがこのロールを使える
  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Effect = "Allow"
      Principal = {
        Service = "ec2.amazonaws.com"
      }
      Action = "sts:AssumeRole"
    }]
  })
}

Trust Policy の Principal バリエーション

用途Principal
EC2Service = "ec2.amazonaws.com"
LambdaService = "lambda.amazonaws.com"
ECS タスクService = "ecs-tasks.amazonaws.com"
別アカウントのロールAWS = "arn:aws:iam::222222222222:role/source-role"
GitHub Actions OIDCFederated = aws_iam_openid_connect_provider.github.arn

aws_iam_policy で権限ポリシーを定義

# 権限ポリシーの作成例(独立した IAM Policy として)
# Statement は配列で複数の許可を並べる。Effect / Action / Resource の組み合わせがコア
resource "aws_iam_policy" "s3_data_rw" {
  name        = "s3-data-rw"
  description = "Allow R/W on data bucket"

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      # 1 つ目: バケット内オブジェクトの読み書き削除
      {
        Effect = "Allow"
        Action = [
          "s3:GetObject",
          "s3:PutObject",
          "s3:DeleteObject",
        ]
        Resource = "${aws_s3_bucket.data.arn}/*"   # オブジェクトレベル指定
      },
      # 2 つ目: バケット自体の一覧(ListBucket はバケット ARN そのものを Resource に)
      {
        Effect = "Allow"
        Action = "s3:ListBucket"
        Resource = aws_s3_bucket.data.arn
      },
    ]
  })
}

aws_iam_policy_document(HCL でポリシー JSON)

jsonencode({...}) 方式は読みやすいですが、条件式や複雑なポリシーになると HCL の良さが薄れます。data ソースで HCL ネイティブに書く ほうが整備されたコードになります。

# aws_iam_policy_document = ポリシー JSON を HCL ネイティブに組み立てる data ソース
# 上の jsonencode() 方式と内容は同じだが、HCL 補間 / condition / ループが書きやすい
data "aws_iam_policy_document" "s3_data_rw" {
  # statement ブロックを並べると、Statement[] に対応する JSON が生成される
  statement {
    sid    = "ObjectRW"   # この statement の識別名(任意)
    effect = "Allow"

    actions = [
      "s3:GetObject",
      "s3:PutObject",
      "s3:DeleteObject",
    ]
    resources = ["${aws_s3_bucket.data.arn}/*"]
  }

  # condition で「特定のプレフィックスのみ ListBucket 許可」
  statement {
    sid       = "BucketList"
    effect    = "Allow"
    actions   = ["s3:ListBucket"]
    resources = [aws_s3_bucket.data.arn]

    condition {
      test     = "StringLike"
      variable = "s3:prefix"
      values   = ["uploads/*"]   # uploads/ 配下だけリスト可
    }
  }
}

# data の .json 属性で生成された JSON 文字列を取り出し、aws_iam_policy に渡す
resource "aws_iam_policy" "s3_data_rw" {
  name   = "s3-data-rw"
  policy = data.aws_iam_policy_document.s3_data_rw.json
}

こちらの利点:

ロールにポリシーを attach する

# ロールとポリシーを「アタッチ」して紐付ける
# 自分で作ったポリシーを attach
resource "aws_iam_role_policy_attachment" "s3" {
  role       = aws_iam_role.ec2_web.name
  policy_arn = aws_iam_policy.s3_data_rw.arn
}

# AWS が公式提供する「管理ポリシー」(ARN 直書き)も同じ書式で attach できる
# 例: SSM Session Manager 用の管理ポリシー
resource "aws_iam_role_policy_attachment" "ssm" {
  role       = aws_iam_role.ec2_web.name
  policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
}

1 ロールに対して複数 attachment を作れるので、機能ごとに分けると整理しやすい。

インラインポリシー(aws_iam_role_policy)

# インラインポリシー = ロールに直接埋め込むポリシー(独立した aws_iam_policy を作らない)
# 「このロールでしか使わない」一時的な許可を書く用途
resource "aws_iam_role_policy" "inline" {
  name = "inline-extras"
  role = aws_iam_role.ec2_web.id

  policy = jsonencode({
    # ...
  })
}

インラインは「このロールでしか使わない」ポリシーに向きますが、複数ロールで使い回したい時は aws_iam_policy + attachment に分けるべき。

EC2 用の instance_profile

EC2 にロールを貼り付ける時だけ必要な「ラッパー」。Lambda や ECS では不要。

# EC2 用 Instance Profile = IAM ロールを EC2 にアタッチする「ラッパー」
# EC2 は IAM ロールを直接受け取れず、必ず Instance Profile 経由で渡す必要がある
resource "aws_iam_instance_profile" "ec2_web" {
  name = "ec2-web"
  role = aws_iam_role.ec2_web.name
}

# EC2 に Instance Profile を割り当て
resource "aws_instance" "web" {
  iam_instance_profile = aws_iam_instance_profile.ec2_web.name
  # ...
}

aws_iam_user は使う?

原則、新しい IAM User は作らないでください。理由:

代わりに:

誰が/何が使う仕組み
人間(社員)IAM Identity Center(旧 SSO)→ ロール
EC2 / ECS / Lambdaサービスロール
GitHub ActionsOIDC → ロール
外部 SaaS(Datadog 等)クロスアカウントロール