「最小権限の原則は理解しているが、全員がコンソールを触れる状況では権限の肥大化を止められない」
これは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ロール設計を優先する
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
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.
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が上書き拒否するための安全装置です。
>> 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"] } } }
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文字列を参照します。resourcesにaws_s3_bucket.app.arnのようなリソース参照を使っているため、バケット名をハードコードする必要がなく、バケット名が変わっても自動追従します。IAMロールへのアタッチまで(aws_iam_role_policy_attachment)
作成したIAMポリシーをIAMロールにアタッチするには、aws_iam_role・aws_iam_role_policy_attachment・aws_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 }
本記事のまとめ
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の中) |
var.)とdata sourceを組み合わせることで、環境ごとのIAMポリシーを1つのコードベースで管理できます。コンソールクリック作業からの脱却は、チームのセキュリティ水準を大きく引き上げます。次のステップとして、変数設計(sensitive変数の扱い方)や、prevent_destroyで重要IAMリソースを誤削除から守る方法も合わせて確認してください。
・TerraformのHCL変数設計|variable・locals・output・data sourceで構成を整理する方法
・Terraformのlifecycleブロックで本番リソースを誤削除から守る方法
・Terraformのtfstate管理とS3バックエンド設定|チーム運用で壊さないための基礎
>> Terraform実践セミナーの詳細はこちら
3,100名以上が実践した「型」を無料で公開中
プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。
登録10秒/合わなければ解除3秒 / 詳細はこちら

無料メルマガで学習を続ける
Linuxの実践スキルをメールで毎週お届け。
登録は1分、解除もいつでも可。
登録無料・いつでも解除できます