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 がロール本体に書く必須項目。
# 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 |
|---|---|
| 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 で権限ポリシーを定義
# 権限ポリシーの作成例(独立した 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
}
こちらの利点:
- HCL の補間 (
${...}) が普通に使える conditionブロックを HCL で書ける(JSON のネスト地獄回避)- 動的に statement を生成(for_each や count)できる
- レビュー時の差分が読みやすい
ロールにポリシーを 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 は作らないでください。理由:
- 長期キーは漏れる: GitHub に push、PC 紛失、退職時の処理漏れ
- ローテーションが面倒: 90 日ごとに更新、それを忘れない仕組み作りがコスト
- 個人特定が弱い: アクセスキーだけでは「誰が使ったか」が完全には追えない
代わりに:
| 誰が/何が | 使う仕組み |
|---|---|
| 人間(社員) | IAM Identity Center(旧 SSO)→ ロール |
| EC2 / ECS / Lambda | サービスロール |
| GitHub Actions | OIDC → ロール |
| 外部 SaaS(Datadog 等) | クロスアカウントロール |