TerraformでIAMポリシーをコード管理する方法|aws_iam_policy_documentで最小権限を宣言的に書く

宮崎智広 この記事の監修:宮崎智広(Linux実務・教育歴20年以上・受講者3,100名超)
HOMELinux技術 リナックスマスター.JP(Linuxマスター.JP)Terraform > TerraformでIAMポリシーをコード管理する方法|aws_iam_policy_documentで最小権限を宣言的に書く
「AWSのIAMポリシーをコンソールでポチポチ設定してきたが、誰がいつ何を変更したのかわからなくなった」
「最小権限の原則は理解しているが、全員がコンソールを触れる状況では権限の肥大化を止められない」

これはIAM管理が手作業に頼り続けることで生まれる問題です。TerraformでIAMを管理するようにすると、ポリシーの変更はすべてHCLファイルのコードとして記録され、Gitで差分管理・コードレビューできるようになります。

この記事では、Terraformのaws_iam_policy_documentデータソースを中心に、最小権限のIAMポリシーを宣言的に書く方法を解説します。S3読み取り専用ポリシーの基本から、Conditionブロックによる細かい権限制御、IAMロールへのアタッチまで一気通貫で習得できます。

実行環境:Terraform 1.8.x(RHEL 9.4 / Ubuntu 24.04 LTS で動作確認済み)

この記事のポイント

・aws_iam_policy_documentはIAMポリシーJSONをHCLで書くためのdata sourceで、コードレビューが可能になる
・statementブロックにactions/resources/effectを明示し、最小権限を宣言的に設計できる
・Conditionブロックを使うとIPアドレス制限やMFA必須などの細かい権限制御ができる
・tfstateにIAMアクセスキーが入るリスクに注意し、IAMロール設計を優先する


「このままじゃマズい」と感じていませんか?
参考書を開く気力もない、同年代に取り残される不安——
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
図解60P/登録10秒/解除も3秒 / 詳細はこちら

TerraformでIAMポリシーをコード管理するメリット

IAMポリシーをコンソールで管理し続けると、現場では次の3つの問題が積み重なります。

1つ目は変更履歴の消失です。コンソール操作はCloudTrailには残りますが、「変更前のポリシー全文」と「変更後のポリシー全文」の差分を人が追うのは現実的ではありません。2つ目はレビューの欠如です。誰かが権限を広げてもプルリクエストのような承認フローが存在しないため、気づいたときには必要以上に広い権限が付いています。3つ目はドリフト(コードと実態の乖離)です。コンソールで直接変更されるとTerraformのstateと食い違い、次のapply時に予期しない差分が生まれます。

TerraformでIAMを管理すると、これらの問題をまとめて解消できます。

terraform planで変更差分を事前に確認できる
・GithubのPull Requestでポリシー変更をコードレビューできる
terraform applyで冪等に適用され、実態とコードが一致し続ける

たとえばIAMポリシーのActionsにS3の書き込み権限を追加した場合、terraform planを実行すると次のような差分が表示されます。

# terraform plan を実行してIAMポリシーの変更差分を確認する例 $ terraform plan # aws_iam_policy.s3_read_only will be updated in-place ~ resource "aws_iam_policy" "s3_read_only" { ~ policy = jsonencode( ~ { ~ Statement = [ ~ { ~ Action = [ "s3:GetObject", "s3:ListBucket", + "s3:PutObject", # 追加される権限 ] } ] } ) } Plan: 0 to add, 1 to change, 0 to destroy.

コンソールのクリック操作では差分が目視できませんが、Terraformなら追加・削除・変更が行単位で明確になります。このためレビュアーが「なぜこの権限が必要か」を確認できるのです。

aws_iam_policy_documentとは

aws_iam_policy_documentは、Terraform の AWS プロバイダが提供する「data source(データソース)」の一種です。IAMポリシードキュメント(JSON形式)をHCLで記述し、aws_iam_policyリソースやassume_role_policy属性に渡すためのブリッジ役を果たします。

IAMポリシーをJSON文字列として直接Terraformに埋め込む方法もありますが、aws_iam_policy_documentを使う方が次の点で優れています。

・HCLの変数(var.)を使って動的にARNや条件値を差し込める
・statementブロックで権限の意図を明示できるためレビューしやすい
terraform planで変更差分が行単位で確認できる(JSON文字列だと全体が差分に見えてしまう)

aws_iam_policy_document自体はAWSにリソースを作成しません。定義した内容を.json属性として出力する計算用の要素です。実際のIAMポリシーを作るのは次のステップのaws_iam_policyリソースの役割です。

基本的な記述パターン

1. S3読み取り専用ポリシーの例

まず最もシンプルなS3読み取り専用ポリシーをaws_iam_policy_documentで書いてみます。

# S3バケット読み取り専用ポリシーのdata source定義 data "aws_iam_policy_document" "s3_read_only" { statement { effect = "Allow" actions = [ "s3:GetObject", "s3:ListBucket", ] resources = [ "arn:aws:s3:::my-app-bucket", "arn:aws:s3:::my-app-bucket/*", ] } }

effect = "Allow"はデフォルトで省略可能ですが、複数のstatementが混在するポリシーでは必ず明示することを推奨します。レビュー時に意図が一目でわかるためです。

resourcesは2行必要な点に注意してください。1行目がバケット自体(ListBucketに必要)、2行目がバケット内のオブジェクト(GetObjectに必要)です。どちらかを忘れると権限エラーになります。

2. 複数のstatementブロックで権限を組み合わせる

実務では複数のAWSサービスへのアクセスをひとつのポリシーにまとめることが多いです。aws_iam_policy_documentではstatementブロックを並べることで対応できます。

data "aws_iam_policy_document" "app_policy" { # S3読み取り statement { effect = "Allow" actions = [ "s3:GetObject", "s3:ListBucket", ] resources = [ "arn:aws:s3:::my-app-bucket", "arn:aws:s3:::my-app-bucket/*", ] } # SSMパラメータストアからの設定値取得 statement { effect = "Allow" actions = [ "ssm:GetParameter", "ssm:GetParametersByPath", ] resources = [ "arn:aws:ssm:ap-northeast-1:*:parameter/myapp/*", ] } # IAM操作は明示的Denyで封鎖 statement { effect = "Deny" actions = ["iam:*"] resources = ["*"] } }

末尾にeffect = "Deny"でIAM操作を封鎖しています。AWSのポリシー評価では「明示的Deny」が「明示的Allow」よりも優先されます。他のポリシーでうっかりIAM権限が付与されても、このDenyが上書き拒否するための安全装置です。
現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、TerraformのIAMポリシー設計から安全なインフラコード管理まで、ハンズオン形式で体系的に学べるセミナーを開催しています。
>> Terraform実践セミナーの詳細はこちら

Conditionブロックで最小権限を細かく設計する

最小権限の原則を徹底するには、「何を許可するか(actions/resources)」だけでなく「どの条件のときだけ許可するか(condition)」まで指定することが重要です。

1. IPアドレス制限を付けた例

オフィスのIPアドレスからのアクセスのみ許可したい場合は、conditionブロックを使います。

data "aws_iam_policy_document" "s3_ip_restricted" { statement { effect = "Allow" actions = ["s3:GetObject"] resources = ["arn:aws:s3:::my-secure-bucket/*"] condition { test = "IpAddress" variable = "aws:SourceIp" values = ["203.0.113.0/24"] } } }

conditionブロックの3つのフィールドの意味は次のとおりです。

test: 条件演算子。IpAddressはIPアドレス比較を意味する
variable: 条件キー。aws:SourceIpはリクエスト元IPアドレスを参照する
values: 許可する値のリスト。CIDR表記で複数指定できる

2. MFA必須条件を付けた例

コンソール操作にMFAを必須にしたい場合は、「MFAがないリクエストを全拒否」する方向で書きます。

data "aws_iam_policy_document" "require_mfa" { statement { effect = "Deny" actions = ["*"] resources = ["*"] condition { test = "BoolIfExists" variable = "aws:MultiFactorAuthPresent" values = ["false"] } } }

「MFAが使われていないセッションで全操作をDeny」という設計です。BoolIfExists演算子を使うことで、MFAトークンが存在しないセッション(アクセスキーによるプログラムアクセス)も対象にできます。この条件はSCPやユーザーポリシーと組み合わせて使うことが多いです。

aws_iam_policyリソースとのセット定義

data sourceで定義したポリシードキュメントは、aws_iam_policyリソースに渡してAWS上にポリシーとして作成します。

# ポリシードキュメントのdata source data "aws_iam_policy_document" "s3_read_only" { statement { effect = "Allow" actions = ["s3:GetObject", "s3:ListBucket"] resources = [ aws_s3_bucket.app.arn, "${aws_s3_bucket.app.arn}/*", ] } } # IAMポリシーリソースの作成 resource "aws_iam_policy" "s3_read_only" { name = "${var.env}-s3-read-only" description = "S3バケット読み取り専用ポリシー(${var.env}環境)" policy = data.aws_iam_policy_document.s3_read_only.json }

data.aws_iam_policy_document.s3_read_only.jsonという属性でJSON文字列を参照します。resourcesaws_s3_bucket.app.arnのようなリソース参照を使っているため、バケット名をハードコードする必要がなく、バケット名が変わっても自動追従します。

IAMロールへのアタッチまで(aws_iam_role_policy_attachment)

作成したIAMポリシーをIAMロールにアタッチするには、aws_iam_roleaws_iam_role_policy_attachmentaws_iam_instance_profileをセットで定義します。EC2インスタンスにIAMロールを割り当てる完全な例を示します。

# 信頼ポリシー(EC2からのAssumeRoleを許可) data "aws_iam_policy_document" "assume_ec2" { statement { effect = "Allow" principals { type = "Service" identifiers = ["ec2.amazonaws.com"] } actions = ["sts:AssumeRole"] } } # IAMロールの作成 resource "aws_iam_role" "app_role" { name = "${var.env}-app-role" assume_role_policy = data.aws_iam_policy_document.assume_ec2.json tags = { Env = var.env } } # IAMポリシーのアタッチ resource "aws_iam_role_policy_attachment" "app_s3" { role = aws_iam_role.app_role.name policy_arn = aws_iam_policy.s3_read_only.arn } # インスタンスプロファイル(EC2へ割り当てるためのラッパー) resource "aws_iam_instance_profile" "app" { name = "${var.env}-app-profile" role = aws_iam_role.app_role.name }

assume_role_policyには、このロールを引き受けられるサービスまたはアカウントを定義します。EC2の場合はec2.amazonaws.comを指定します。Lambda関数ならlambda.amazonaws.com、ECSタスクならecs-tasks.amazonaws.comに変えるだけです。

複数のポリシーをアタッチしたい場合は、aws_iam_role_policy_attachmentリソースをアタッチしたいポリシーの数だけ定義します。

よくあるトラブルと対策

1. tfstateにIAMアクセスキーが記録されるリスク

TerraformでIAMユーザーのアクセスキー(aws_iam_access_keyリソース)を作成すると、生成されたシークレットキーがtfstateファイルにプレーンテキストで保存されます。S3バックエンドを使っている場合でも、バケットの権限が広いと漏洩リスクがあります。

推奨設計は次のとおりです。

・EC2やLambdaからAWSリソースにアクセスする場合はIAMロール+インスタンスプロファイルを使う(アクセスキー不要)
・アクセスキーが必要なシステム間連携は、TerraformではなくコンソールまたはAWS CLIで発行してSecrets Managerに保管する
・tfstateはS3バケットに保管し、バケットポリシーで最小アクセス権限を設定するとともにバージョニングを有効にする

tfstateの安全な管理についてはTerraformのtfstate管理とS3バックエンド設定で詳しく解説しています。

2. terraform applyがAccessDeniedで失敗する

CI/CDパイプラインでterraform applyを実行する場合、Terraform実行ロール(CI/CDが使うロール)にIAMリソースを操作する権限が不足していると発生します。必要な最小権限は次のとおりです。

# Terraform実行ロールに必要なIAM権限(最小) "iam:CreatePolicy", "iam:GetPolicy", "iam:GetPolicyVersion", "iam:ListPolicyVersions", "iam:DeletePolicy", "iam:CreatePolicyVersion", "iam:DeletePolicyVersion", "iam:CreateRole", "iam:GetRole", "iam:DeleteRole", "iam:AttachRolePolicy", "iam:DetachRolePolicy", "iam:ListAttachedRolePolicies", "iam:PassRole", "iam:CreateInstanceProfile", "iam:DeleteInstanceProfile", "iam:GetInstanceProfile", "iam:AddRoleToInstanceProfile", "iam:RemoveRoleFromInstanceProfile"

iam:PassRoleはEC2やLambdaにIAMロールを割り当てる操作(aws_iam_instance_profile作成時など)に必要です。ハマりやすいので忘れずに追加してください。

3. ポリシードキュメントのサイズ制限に注意

IAMマネージドポリシーのドキュメントには6,144文字のサイズ制限があります(インラインポリシーは2,048文字)。statementが増えすぎてサイズ制限に引っかかる場合は、複数のaws_iam_policyに分割して、同一ロールに複数ポリシーをアタッチします。

# ポリシーを機能単位に分割してアタッチ resource "aws_iam_role_policy_attachment" "s3" { role = aws_iam_role.app_role.name policy_arn = aws_iam_policy.s3_read_only.arn } resource "aws_iam_role_policy_attachment" "ssm" { role = aws_iam_role.app_role.name policy_arn = aws_iam_policy.ssm_readonly.arn } resource "aws_iam_role_policy_attachment" "cloudwatch" { role = aws_iam_role.app_role.name policy_arn = aws_iam_policy.cloudwatch_write.arn }

1つのロールにアタッチできるポリシーは最大10個です。それを超える場合はロールを機能ごとに分割する設計を検討してください。

本記事のまとめ

TerraformでIAMポリシーを管理するための主要リソースと役割を一覧で整理します。
やること HCLリソース / データソース
IAMポリシードキュメントの定義 data "aws_iam_policy_document" "名前" {}
IAMポリシーのAWS作成 resource "aws_iam_policy" "名前" {}
IAMロールの作成 resource "aws_iam_role" "名前" {}
ポリシーをロールにアタッチ resource "aws_iam_role_policy_attachment" "名前" {}
EC2へのロール割り当て resource "aws_iam_instance_profile" "名前" {}
IPアドレス・MFAなどの条件制御 condition {}ブロック(statementの中)
Terraformのhcl変数(var.)とdata sourceを組み合わせることで、環境ごとのIAMポリシーを1つのコードベースで管理できます。コンソールクリック作業からの脱却は、チームのセキュリティ水準を大きく引き上げます。

次のステップとして、変数設計(sensitive変数の扱い方)や、prevent_destroyで重要IAMリソースを誤削除から守る方法も合わせて確認してください。

TerraformのHCL変数設計|variable・locals・output・data sourceで構成を整理する方法
Terraformのlifecycleブロックで本番リソースを誤削除から守る方法
Terraformのtfstate管理とS3バックエンド設定|チーム運用で壊さないための基礎
現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、Terraformのaws_iam_policy_document設計からCI/CDパイプライン統合まで、実務で使えるIaCスキルをハンズオン形式で習得できるセミナーを開催しています。
>> Terraform実践セミナーの詳細はこちら

無料メルマガで学習を続ける

Linuxの実践スキルをメールで毎週お届け。
登録は1分、解除もいつでも可。

登録無料・いつでも解除できます

暗記不要・1時間後にはサーバーが動く

3,100名以上が実践した「型」を無料で公開中

プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。

登録10秒/合わなければ解除3秒 / 詳細はこちら

Linux無料マニュアル(図解60P) 名前とメールで30秒登録
宮崎 智広

この記事を書いた人

宮崎 智広(みやざき ともひろ)

株式会社イーネットマーキュリー代表。現役のLinuxサーバー管理者として20年以上の実務経験を持ち、これまでに累計3,100名以上のエンジニアを指導してきたLinux教育のプロフェッショナル。「現場で本当に使える技術」を体系的に伝えることをモットーに、実践型のLinuxセミナーの開催や無料マニュアルの配布を通じてLinux人材の育成に取り組んでいる。

趣味は、キャンプにカメラ、トラウト釣り。好きな食べ物は、ラーメンにお酒。休肝日が作れない、酒量を減らせないのが悩み。最近、ドラマ「フライトエンジェル」を観て涙腺が崩壊しました。