★★ 中級

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 がロール本体に書く必須項目。

resource "aws_iam_role" "ec2_web" {
  name = "ec2-web"

  # Trust Policy: 「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 で権限ポリシーを定義

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 = [
      {
        Effect = "Allow"
        Action = [
          "s3:GetObject",
          "s3:PutObject",
          "s3:DeleteObject",
        ]
        Resource = "${aws_s3_bucket.data.arn}/*"
      },
      {
        Effect = "Allow"
        Action = "s3:ListBucket"
        Resource = aws_s3_bucket.data.arn
      },
    ]
  })
}

aws_iam_policy_document(HCL でポリシー JSON)

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

data "aws_iam_policy_document" "s3_data_rw" {
  statement {
    sid    = "ObjectRW"
    effect = "Allow"

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

  statement {
    sid       = "BucketList"
    effect    = "Allow"
    actions   = ["s3:ListBucket"]
    resources = [aws_s3_bucket.data.arn]

    condition {
      test     = "StringLike"
      variable = "s3:prefix"
      values   = ["uploads/*"]
    }
  }
}

resource "aws_iam_policy" "s3_data_rw" {
  name   = "s3-data-rw"
  policy = data.aws_iam_policy_document.s3_data_rw.json
}

こちらの利点:

ロールにポリシーを 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 管理ポリシーをそのまま付ける(SSM など)
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)

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 では不要。

resource "aws_iam_instance_profile" "ec2_web" {
  name = "ec2-web"
  role = aws_iam_role.ec2_web.name
}

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 等)クロスアカウントロール