05. IAM
AWS で 「誰が/何が/何にアクセスできるか」 を決めるのが IAM。Terraform で IAM を書けるようになると、最小権限の設計が再現可能になり、セキュリティが大きく前進します。
この章の目次
登場人物
| 用語 | 役割 | 例 |
|---|---|---|
| Role | 「特定の権限の塊」を一時的に引き受ける器 | EC2 用ロール、Lambda 用ロール、GitHub Actions 用ロール |
| Policy | 「何ができるか」を JSON で書いたルール | S3 の特定バケットを Get/Put 可 |
| Trust Policy | 「誰が/どのサービスがロールを assume できるか」 | EC2 サービスがこのロールを使える |
| Instance Profile | EC2 にロールを貼り付けるためのラッパー | 必須(直接 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 |
|---|---|
| EC2 | Service = "ec2.amazonaws.com" |
| Lambda | Service = "lambda.amazonaws.com" |
| ECS タスク | Service = "ecs-tasks.amazonaws.com" |
| 別アカウントのロール | AWS = "arn:aws:iam::222222222222:role/source-role" |
| GitHub Actions OIDC | Federated = 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
}
こちらの利点:
- HCL の補間 (
${...}) が普通に使える conditionブロックを HCL で書ける(JSON のネスト地獄回避)- 動的に statement を生成(for_each や count)できる
- レビュー時の差分が読みやすい
ロールにポリシーを 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 は作らないでください。理由:
- 長期キーは漏れる: GitHub に push、PC 紛失、退職時の処理漏れ
- ローテーションが面倒: 90 日ごとに更新、それを忘れない仕組み作りがコスト
- 個人特定が弱い: アクセスキーだけでは「誰が使ったか」が完全には追えない
代わりに:
| 誰が/何が | 使う仕組み |
|---|---|
| 人間(社員) | IAM Identity Center(旧 SSO)→ ロール |
| EC2 / ECS / Lambda | サービスロール |
| GitHub Actions | OIDC → ロール |
| 外部 SaaS(Datadog 等) | クロスアカウントロール |