IAMポリシー設計入門 評価ロジック 最小権限 特定法 詰まりポイント Terraform

目次

1. はじめに — IAMで詰まる3つの場面

AWSを使い始めて数ヶ月が経ち、Lambda関数やEC2インスタンスをある程度操作できるようになってきた。
しかし、IAMに差し掛かると突然うまくいかなくなる。そんな経験はないだろうか。

「S3にアクセスできるようAllow権限を書いたのに、なぜかPermission Deniedが返ってくる。」
「最小権限が大事と言われるが、どの権限が本当に必要なのかを調べる方法が分からない。」
「DenyポリシーなんてどこにもないはずなのにAPIが弾かれる。」

これらはAWS入門者が必ずといっていいほど経験する、IAM特有の3つの詰まりポイントだ。
本記事は、これら3つの壁を正面から乗り越えるための実践ガイドだ。

本記事は 8つの章構成 で、「基礎理解 → 評価ロジック → 特定手順 → 詰まり回避 → 演習」の流れで進む。
IAMに不慣れであれば§2から順番に読むことを勧める。
評価ロジックだけ確認したい場合は§3、最小権限の特定手順を知りたい場合は§4から読み始めてもよい。


1-1. IAMで詰まる3つの場面

場面①: Allowを書いたのに動かない

IAMポリシーを書き始めた最初の関門がこれだ。
例えば、Lambda関数からS3バケットにオブジェクトを書き込みたいとする。
Lambdaの実行ロールに次のようなポリシーをアタッチしたとしよう。

{
  "Version": "2012-10-17",
  "Statement": [
 {
"Effect": "Allow",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::my-bucket/*"
 }
  ]
}

一見、問題なさそうだ。しかし実際に動かしてみると、AccessDeniedが返ってくる。なぜか。

理由は複数考えられる。

  • バケット側のバケットポリシーで明示的なDenyが設定されている
  • このAWSアカウントに適用されたSCP(Service Control Policy)がs3:PutObjectを制限している
  • リソースポリシー側で特定のPrincipalのみにアクセスを限定している

「Allowを書いた」という事実だけでは、APIが成功するかどうかは決まらない。
IAMの評価ロジックは 複数のポリシーが連動して最終的なAllow/Denyを決定する 仕組みになっている。
この構造を理解していなければ、AccessDeniedをデバッグするすべがない。

デバッグには IAM Policy Simulator(マネジメントコンソール → IAM → Policy Simulator)が有効だ。
どのポリシーが評価に影響しているかを視覚的に追えるため、「どこで弾かれているか」の特定に役立つ。
ただし、Policy Simulatorで「Allow」と表示されても、実際の環境ではSCPやVPCエンドポイントポリシーが
介在する場合があり、最終的には§3の評価ロジックの理解が不可欠だ。

場面②: 最小権限の特定がわからない

「最小権限の原則(Principle of Least Privilege)を守ること」という言葉は、
AWSを使い始めれば必ず目にする。
しかし具体的な実践方法を解説している記事は意外と少ない。

  • Lambda関数にS3のどの操作権限が必要なのか
  • EC2インスタンスをSystems Managerで管理するために最低限必要なポリシーは何か
  • TerraformでAWSリソースをプロビジョニングするIAMユーザーに付与すべき権限の範囲はどこまでか

これらの問いに答えるためには、実際のAPIコールを観測して逆算する方法か、
AWS公式ドキュメントを地道に調べる方法しかないように思えてしまう。

しかし実は、AWS Access Analyzer × CloudTrail × Access Advisor という3つのツールを組み合わせることで、
実ログから最小権限を効率的に特定できる。本記事の§4がこの手順の山場となる。

初心者が陥りがちな回避策として Action: "*"Resource: "*" を乱用するケースがある。
短期的には動くが、権限の爆発的な拡大につながり、セキュリティインシデントの温床になる。
本記事では「動けば良い」から脱却し、実ログに基づいて最小権限を特定する具体的な手順を提供する。

場面③: Denyしていないのに弾かれる — 暗黙Denyの罠

「DenyポリシーなんてどこにもないはずなのにAPIが弾かれる。」
これが初心者が最も混乱する場面の一つだ。

IAMの設計思想の根本に 暗黙Deny(Implicit Deny) がある。
IAMは「明示的にAllowされたこと以外はすべてDeny」という、安全側に倒したデフォルト動作を持つ。
つまり、Allowを書かなければ自動的にDenyされる。

ここに落とし穴がある。
Allowは書いた。しかし許可しているResourceのARNパターンが実際のリソースと微妙に一致していない場合がある。

S3を例にとると、バケット自体に対する操作とバケット内オブジェクトに対する操作では、
必要なリソースARNが異なる。

{
  "Version": "2012-10-17",
  "Statement": [
 {
"Effect": "Allow",
"Action": ["s3:ListBucket"],
"Resource": "arn:aws:s3:::my-bucket"
 },
 {
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:PutObject"],
"Resource": "arn:aws:s3:::my-bucket/*"
 }
  ]
}

s3:ListBucketはバケット自体(arn:aws:s3:::my-bucket)に対する操作だが、
s3:GetObjects3:PutObjectはオブジェクト(arn:aws:s3:::my-bucket/*)に対する操作だ。
これを混同して両方をarn:aws:s3:::my-bucket/*に書いてしまうと、
s3:ListBucketだけが暗黙Denyになる。Denyポリシーを書いていなくても、だ。

こうした「書いたつもりだが効いていないAllow」が暗黙Denyとして表れる場面は多い。
IAMを扱う上で、暗黙Denyが常に存在することを意識しておく必要がある。
この点については§3の評価ロジック解説で改めて詳しく扱う。


1-2. 本記事のゴールと読了後到達状態

本記事を読み終えたとき、次の3つの能力が身についていることを目指す。

ゴール①: IAMの評価ロジックを自分の言葉で説明できる

最終的なAllow/Denyが決まるまでに、どのポリシータイプがどの順序で評価されるかを
図を見ながら説明できる状態を目指す。
「暗黙Denyがデフォルト」「明示Denyが最強」「SCPがガードレール」という3つの鉄則を押さえ、
「なぜ弾かれているのか」を論理的に追えるようになることが第一歩だ。

ゴール②: Access Analyzer × CloudTrail で最小権限を特定できる

既存のIAMロールやユーザーが実際に使っている権限を、
CloudTrailのアクセスログとAccess Analyzerのpolicy generation機能から逆算し、
必要最小限のポリシーを自力で生成できる状態を目指す。
「最小権限が大事とは知っているが、具体的な手順が分からない」という状態を卒業する。

ゴール③: よくある詰まりポイントを事前に回避できる

Resource: *Action: *の乱用、SCPとPermission Boundaryの混同、
Conditionキーの書き間違いなど、初心者が陥りやすい5つのアンチパターンを事前に把握する。
「知っていれば防げた」詰まりを、図解と演習で事前に回避できる状態を目指す。

これら3つのゴールは独立しているのではなく、相互に連携する。
評価ロジック(ゴール①)を理解してこそ、Access Analyzerの出力の意味が分かる(ゴール②)。
詰まりポイントのパターン(ゴール③)を知ることで、ポリシー設計の精度が上がる。

読了後の到達状態:

実際のプロジェクトで「このLambdaロールに必要な権限を洗い出す」という作業を、
Access Analyzer + CloudTrailを使って自力で完遂できる。
また、ポリシーを書いてデプロイした後にAccessDeniedが出た場合に、
評価ロジックに沿ってどのポリシーが原因かを絞り込める。


1-3. 対象読者像と前提知識

この記事が向いている読者

本記事は次のような読者を想定している。

  • AWSアカウントを開設して3ヶ月〜1年程度
  • Lambda / EC2 / S3 のいずれかの基本操作は経験がある
  • AWS CLIまたはCloudShellを使ったことがある
  • IAMポリシーを書いたことはあるが、動作の仕組みをしっかり理解していない
  • 「最小権限が大事」とは知っているが、具体的な実践方法が分からない

言い換えると、「IAMが難しい」と感じている入門者のための記事 だ。
IAMのすべてを網羅することは目的としていない。
初心者が現場で詰まる場面に的を絞って、実践的な突破口を提供することを優先する。

この記事で扱わないこと

以下は本記事の対象外とする。

  • AWS Organizations全体設計やSCPの大規模管理
  • SAML / OIDC連携の詳細実装(フェデレーション認証)
  • Terraform以外のIaCツール(CDK / CloudFormation)でのIAM設計
  • クロスアカウントロールの複雑なシナリオ

本記事でIAMの基礎評価ロジックと最小権限特定法を身につけた後に、
これらの発展的なトピックへ進むことを勧める。

前提知識チェックリスト

知識項目必要レベル
AWSアカウントの作成・マネジメントコンソール操作経験あり
S3 / Lambda / EC2 のいずれかの基本操作経験あり
AWS CLI または CloudShell の利用触れたことがある程度でOK
JSON の読み書き基本構文(波括弧・配列・文字列)が理解できれば十分
IAMポリシーの記述経験不問(§2から丁寧に解説する)

IAM Identity Centerを使った組織全体のSSO(シングルサインオン)設定については、
次の記事で詳しく解説している。
本記事を読んだ後のステップアップ先として参照してほしい。

IAM Identity Center完全ガイド — Permission Sets + ABAC本番設計


本記事のゴール・読了時間・前提知識まとめ

  • ゴール①: IAMの評価ロジック(暗黙Deny → 明示Deny → SCP → Boundary → Identity/Resource Policy → Session Policy の順序)を図で理解し、AccessDeniedの原因を論理的に追えるようになる
  • ゴール②: AWS Access Analyzer × CloudTrail × Access Advisorの3点セットを使って、実ログから最小権限ポリシーを逆算・生成できるようになる
  • ゴール③: Resource: * / Action: * の乱用・暗黙Deny誤解・Permission Boundary混同など、初心者5大アンチパターンを図解と演習で事前に把握して回避できるようになる
  • 読了時間: 約30〜40分(演習を含む。§4の手順をAWSコンソールで試しながら読む場合は60〜90分)
  • 前提知識: S3 / Lambda / EC2 のいずれかの基本操作経験 + JSON基本構文の理解。IAMポリシーの記述経験は不問

2. IAMポリシーの基礎 — JSON構造を5要素で押さえる

IAMポリシーは JSONドキュメント です。「誰が」「何を」「どこに対して」「どんな条件で」「許可/拒否するか」を5つのキーで表現します。この5要素を手なじみにするだけで、ポリシーを読む速度が大幅に上がり、エラーの原因特定も早くなります。

2-1. ポリシーJSON の5要素

キー必須説明
Effect必須Allow または Deny の2択。明示的 Deny は全ての Allow より優先される
Action必須許可/拒否する API アクション。s3:GetObject のように サービス:アクション名 で指定
Resource必須 (多くの場合)対象リソースの ARN。* はワイルドカード (全リソース)
Condition任意条件キー・演算子・値の組み合わせで適用条件を絞り込む
Principalリソースベースポリシーで必須ポリシーを適用する主体 (IAMユーザー / ロール / AWSサービス等)

実際のポリシーは Statement 配列の中にこれらのキーを持つオブジェクトを並べる構造です。

Minimal Allow — S3バケット内のオブジェクト読み取り許可

{
  "Version": "2012-10-17",
  "Statement": [
 {
"Effect": "Allow",
"Action": [
  "s3:GetObject",
  "s3:ListBucket"
],
"Resource": [
  "arn:aws:s3:::my-bucket",
  "arn:aws:s3:::my-bucket/*"
]
 }
  ]
}

ポイントは Resourceバケット ARNオブジェクト ARN (/* 付き) の両方を指定している点です。s3:ListBucket はバケット自体に作用し、s3:GetObject はオブジェクト (my-bucket/*) に作用するため、それぞれ別の ARN が必要になります。「アクションとリソースの ARN が対応しているか」はポリシーエラーの最初の確認ポイントです。

Minimal Deny — 特定リージョン外へのアクセス禁止

{
  "Version": "2012-10-17",
  "Statement": [
 {
"Effect": "Deny",
"Action": "*",
"Resource": "*",
"Condition": {
  "StringNotEquals": {
 "aws:RequestedRegion": [
"ap-northeast-1",
"us-east-1"
 ]
  }
}
 }
  ]
}

Condition キーの典型例です。aws:RequestedRegion はグローバル条件キーで、リクエストが向かうリージョンを評価します。StringNotEquals は「値が一致しない場合に条件が真」になるため、このポリシーは「東京リージョンと北部バージニアリージョン以外へのすべての操作を拒否する」という意味になります。SCP や Permission Boundary と組み合わせることでリージョン制限のガードレールとして活用できます。

Effect の意味を正確に理解する

Effect: Deny明示的拒否 (Explicit Deny) と呼ばれ、IAM評価ロジックの中で最優先で処理されます。Allow を何枚書いても、同じアクション・リソースに対する Deny が1枚でも存在すれば、その操作は必ず拒否されます。詳しい評価ロジックは §3 で解説しますが、「Deny は Allow より常に強い」 という原則をここで押さえておきましょう。

Action ワイルドカードの注意点

Action: "*" は全サービスの全アクションを対象にします。Deny ポリシー (特に SCP) では意図的に使うケースがありますが、Allow ポリシーで Action: "*" を書くと最小権限の原則に完全に反します。許可するアクションは必ず列挙するようにしてください。

Action: "s3:*" のようにサービス単位のワイルドカードも同様に危険です。「S3の全操作を許可」はバケット削除や ACL 変更まで含むため、意図しない権限付与になりがちです。

Resource の ARN 形式

ARN (Amazon Resource Name) は次の形式で構成されます。

arn:partition:service:region:account-id:resource-type/resource-id

主要な ARN の例:

リソースARN 例備考
S3 バケットarn:aws:s3:::my-bucketリージョン・アカウントID省略可
IAM ロールarn:aws:iam::123456789012:role/MyRoleリージョン省略 (グローバルサービス)
Lambda 関数arn:aws:lambda:ap-northeast-1:123456789012:function:my-funcリージョン・アカウントID必須
SQS キューarn:aws:sqs:ap-northeast-1:123456789012:my-queueリージョン・アカウントID必須

Resource: "*" は必要最小限のリソースにのみ使い、可能な限り ARN を特定することが最小権限の第一歩です。


2-2. ポリシー種別5分類の早見表

IAMには 5種類のポリシー があります。どのポリシーを使うかを誤ると「許可を書いたのに動かない」という典型的な詰まりが発生します。

種別誰が持つか何に対してどこで使うか特徴
Identity-based policyIAMユーザー / グループ / ロールAWSリソース最もよく使う基本形主体に直接アタッチ。AWS管理 / カスタマー管理 / インラインの3形態がある
Resource-based policyAWSリソース (S3 / SQS / KMS等)アクセス要求者クロスアカウントアクセス時に頻用Principal 必須。S3バケットポリシーが代表例
SCP (Service Control Policy)AWS Organizations の OU / アカウント組織内の全 IAM エンティティ組織全体のガードレール設置最大許可範囲を制限。Override 不可
Permission BoundaryIAMユーザー / ロールIdentity-based policy の効果範囲権限委譲の安全柵ポリシーと Boundary の交差部分のみ有効になる
Session policyIAMロールのセッションセッションの操作範囲AssumeRole 時に一時的な範囲絞り込み明示的に渡さない限り無効

Identity-based policy が「主役」で、他は「制限レイヤー」 というイメージで覚えると整理しやすいです。

  • SCP: アカウント/OU 単位の最大許可範囲を決める天井。SCP で許可されていないアクションは、Identity-based policy で Allow を書いても通りません。
  • Permission Boundary: ユーザー/ロール単位の最大許可範囲を決める天井。「管理者が開発者に権限委譲するときに、委譲先が自分より強い権限を作れないようにする」用途に使います。
  • Session policy: AssumeRole の PolicyArnsPolicy パラメータで渡す一時的な絞り込みです。Lambda から別ロールを引き受ける場合や、コンソールログイン時の権限を一時的に制限する場合などに活用します。

Identity-based policy の3形態

形態説明再利用推奨用途
AWS管理ポリシーAWSが事前定義・管理複数エンティティに付与可一般的な用途のクイックスタート
カスタマー管理ポリシー自分で定義、独立ARNを持つ複数エンティティに付与可組織横断で共通する権限
インラインポリシーユーザー/ロールに直接埋め込みそのエンティティ専用そのロール固有の最小権限

実務では「AWS管理ポリシーを基礎にして、足りない権限をカスタマー管理ポリシーで補い、ロール固有の権限はインラインで書く」という使い分けが一般的です。ただし、インラインポリシーはロール削除時に同時に消えるため、ポリシーの棚卸しが困難になりやすいという側面もあります。


2-3. AWSコンソールでのポリシー作成手順

ポリシーを初めて作成するなら、AWSマネジメントコンソールの IAM > ポリシー > ポリシーの作成 から始めるのがおすすめです。コンソールには ビジュアルエディタJSONエディタ の2つのモードがあります。

ビジュアルエディタ (GUI操作)

  1. サービスを選択 — ドロップダウンから対象サービス (例: S3) を選ぶ
  2. アクションを選択 — チェックボックスで許可するアクション (例: GetObject, ListBucket) にチェック
  3. リソースを指定 — 「特定」を選んで ARN を入力、または「すべて」で * を指定
  4. 条件を追加 (任意) — 条件キー / 演算子 / 値を指定して適用条件を絞り込む
  5. 次へポリシー名を入力ポリシーの作成 をクリック

ビジュアルエディタの利点は、アクション名をうろ覚えでも検索で絞り込める点と、リソース ARN をフォームで組み立てられる点です。初めて使うサービスのポリシーを書くときに特に有効です。また、アクションを選択すると右側に「リソースレベルのアクセス制御に対応しているか」が表示されるため、ARN で絞れるかどうかを事前に確認できます。

JSONエディタ (テキスト直接編集)

ビジュアルエディタで作成したポリシーは「JSONエディタ」タブに切り替えることで JSON を確認・編集できます。また最初から JSON を直接入力することも可能です。

JSONエディタが向いている場面:
Condition キーを細かく制御したい場合 (ビジュアルエディタでは設定できない条件キーがある)
– 既存ポリシーの JSON をコピー&ペーストして改変する場合
– IaC (Terraform / CloudFormation) で書いた JSON をそのまま貼り付けて確認する場合

コンソールの JSON バリデーション機能はキー名の typo や構文エラーを即座に指摘してくれます。手書きで JSON を書く際は積極的に活用してください。

ポリシー作成のコツ: まずビジュアル、次にJSONで仕上げ

  • 新規作成はビジュアルエディタでアクションとリソースの組み合わせを確認してから、JSONに切り替えてConditionを追加するのがスムーズ
  • 完成したJSONはコピーしてTerraformやCloudFormationに貼り付けると、IaC化の手間が省ける
  • ポリシーシミュレーター (IAM > ポリシーシミュレーター) でアタッチ前に動作確認するとデバッグが楽になる

2-4. Terraform でのIAMポリシー管理

実務では IaC (Infrastructure as Code) でポリシーを管理するのが標準的です。Terraform を使った基本パターンを押さえておきましょう。

aws_iam_policy + aws_iam_role_policy_attachment の組み合わせ

カスタマー管理ポリシーを作成し、ロールにアタッチする最も一般的なパターンです。

# カスタマー管理ポリシーの定義
resource "aws_iam_policy" "s3_read_only" {
  name  = "S3ReadOnlyPolicy"
  description = "S3バケットの読み取り専用アクセス"

  policy = jsonencode({
 Version = "2012-10-17"
 Statement = [
{
  Effect = "Allow"
  Action = [
 "s3:GetObject",
 "s3:ListBucket",
 "s3:GetBucketLocation"
  ]
  Resource = [
 "arn:aws:s3:::${var.bucket_name}",
 "arn:aws:s3:::${var.bucket_name}/*"
  ]
}
 ]
  })
}

# ポリシーをロールにアタッチ
resource "aws_iam_role_policy_attachment" "s3_read_attach" {
  role = aws_iam_role.lambda_exec.name
  policy_arn = aws_iam_policy.s3_read_only.arn
}

# ポリシーをアタッチするロール
resource "aws_iam_role" "lambda_exec" {
  name = "lambda-exec-role"

  assume_role_policy = jsonencode({
 Version = "2012-10-17"
 Statement = [
{
  Effect = "Allow"
  Principal = { Service = "lambda.amazonaws.com" }
  Action = "sts:AssumeRole"
}
 ]
  })
}

jsonencode() を使うと Terraform の HCL 構文で JSON を記述でき、変数 (var.bucket_name) の埋め込みが自然な形で書けます。ハードコードを避けて variabledata ソースから ARN を組み立てる習慣をつけると、環境ごとの差異 (dev/stg/prod) を吸収しやすくなります。

aws_iam_role_policy (インラインポリシー) との使い分け

# インラインポリシー: このロール専用の権限を直接定義
resource "aws_iam_role_policy" "inline_cloudwatch" {
  name = "CloudWatchLogsPolicy"
  role = aws_iam_role.lambda_exec.id

  policy = jsonencode({
 Version = "2012-10-17"
 Statement = [
{
  Effect = "Allow"
  Action = [
 "logs:CreateLogGroup",
 "logs:CreateLogStream",
 "logs:PutLogEvents"
  ]
  Resource = "arn:aws:logs:*:*:*"
}
 ]
  })
}
比較軸aws_iam_policy (カスタマー管理)aws_iam_role_policy (インライン)
再利用複数ロール・ユーザーにアタッチ可そのロール専用
ARN独立した ARN を持つ持たない (ロールに紐付く)
管理ポリシー単位でバージョン管理可ロールと一体で管理
ロール削除時ポリシー自体は残るロールと共に削除される
推奨用途組織横断で使い回す共通権限ロール固有の最小権限

実務上は「共通権限は aws_iam_policy、そのロール固有の最小権限は aws_iam_role_policy」で使い分けると、ポリシーの散逸を防ぎつつ責務を明確にできます。


3. 評価ロジック — 暗黙Deny → 明示Deny優先 → 4種ポリシーの順序

IAMの評価ロジックは「なぜ動かないか」を自力でデバッグするための最重要知識です。「Allowを書いたのに弾かれる」「Denyしていないのに拒否される」という現象は、評価ロジックを理解すれば必ず原因を特定できます。このセクションでは3つの鉄則と5段階の評価フローを、実例JSONとともに体系的に解説します。


3-1. 評価ロジック3つの鉄則

IAMの評価ロジックはシンプルな3つの鉄則で成り立っています。この3つを頭に入れるだけで、IAMトラブルシューティングの9割は対処できます。

鉄則1: 明示的Deny(Explicit Deny)はすべてのAllowより強い

ポリシーに "Effect": "Deny" が存在した瞬間、同じアクション・リソースへの "Effect": "Allow" は完全に無効化されます。どのポリシータイプ(SCP・Permission Boundary・Identity-based・Resource-based・Session)に書かれたAllowであっても例外はありません。

{
  "Version": "2012-10-17",
  "Statement": [
 {
"Sid": "AllowS3Read",
"Effect": "Allow",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::my-bucket/*"
 },
 {
"Sid": "DenySecretFolder",
"Effect": "Deny",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::my-bucket/secret/*"
 }
  ]
}

上記ポリシーでは my-bucket/secret/ 以下のオブジェクトへのアクセスは Deny となります。2つ目のStatementが1つ目のStatementを「上書き」するのではなく、「明示的Denyが存在する」という事実が最終結果を決定します。2つのStatementの順序を入れ替えても結果は変わりません。

明示的Denyが強力な理由は、セキュリティの安全側設計にあります。複数のポリシーが競合した場合、「最も制限の強い結果」を採用することで、意図しないアクセス許可が発生しにくくなっています。

鉄則2: 何も許可されていない = 暗黙的Deny(Implicit Deny)

AWSは ホワイトリスト型 の認可モデルを採用しています。明示的に許可(Allow)されていないアクションはすべてデフォルトで拒否されます。これを 暗黙的Deny と呼びます。

多くの入門者が陥る落とし穴は「Denyを書いていないから通るはず」という誤解です。以下のような状況が典型例です:

  • EC2インスタンスのIAMロールにS3のAllowポリシーがない → S3操作はすべてDeny
  • 新しいIAMユーザーに何もポリシーをアタッチしていない → AWSコンソールのすべての操作がDeny
  • Lambda関数の実行ロールにCloudWatch Logsへの書き込み権限がない → ログ出力がDeny

「何も書いていない = Deny」 がデフォルト状態です。「Allowを書いた範囲だけが許可される」と覚えておきましょう。

暗黙的DenyとResource-based Policyの関係には注意が必要です。S3バケットポリシーなどのResource-based Policyが存在する場合、Identity-based Policyに記述がなくてもResource-based PolicyのAllowだけで通過できることがあります(同一アカウント内の場合)。詳細は3-3の実例3で解説します。

鉄則3: SCPはガードレール — AllowをDenyで上書きできる

AWS Organizations の Service Control Policy(SCP) は、組織全体・OUレベル・アカウントレベルで適用できる「上位の制約」です。

SCPの動作は他のポリシーと根本的に異なります:

  • SCPは「できることの上限」を設定するガードレール
  • SCPで許可されていないアクションは、Identity-based PolicyでAllowしても Deny になる
  • 管理アカウント(Management Account)はSCPの影響を受けない
  • ルートユーザーはSCPの影響を受ける(管理アカウントのルートを除く)
{
  "Version": "2012-10-17",
  "Statement": [
 {
"Sid": "DenyNonApprovedRegions",
"Effect": "Deny",
"Action": "*",
"Resource": "*",
"Condition": {
  "StringNotEquals": {
 "aws:RequestedRegion": [
"ap-northeast-1",
"us-east-1"
 ]
  }
}
 }
  ]
}

上記はよく使われるSCPの例です。東京リージョン(ap-northeast-1)と米国東部(us-east-1)以外へのすべてのAPIコールをDenyします。このSCPが適用されたアカウントのIAMユーザーは、AWSAdministratorAccess 管理ポリシーがアタッチされていても、制限されたリージョンには操作できません。

「Identity-based PolicyでS3を全許可したのにアクセスできない」という状況で、SCPでS3が制限されているケースは実務でも頻繁に起きます。


3-2. 評価順序フロー — 5種ポリシーの判定ステップ

実際のIAMリクエスト評価は、以下の順序で段階的に判定されます。各ステップで明示的Denyが発見された場合は即座にDenyが確定し、残りのステップは評価しません。

fig01: IAMポリシー評価フロー

評価のエントリーポイントは「リクエストが発行された瞬間」です。IAMは5つのポリシーレイヤーを順番にチェックし、最終的なAllow/Denyを決定します。

ステップ1: SCP(Service Control Policy)の評価

最初にチェックされるのがSCPです。AWS Organizationsを使用している場合のみ適用されます。

SCPの状態評価結果
アクションを明示的にDeny即座に Deny 確定(後のステップは評価しない)
アクションを明示的にAllow次のステップへ
アクションの記述なし(FullAWSAccessなし)Deny(SCPがホワイトリスト方式の場合)
AWS Organizationsを使用していないこのステップをスキップ

AWS Organizations のデフォルト設定では、ルートに FullAWSAccess SCPが適用されておりすべてのアクションを許可しています。組織のSCPカスタマイズ状況は、IAMトラブルシューティングの際に必ず確認してください。

ステップ2: Permissions Boundary(権限境界)の評価

次にチェックされるのが Permissions Boundary です。IAMユーザーまたはIAMロールに設定するポリシーで、そのプリンシパルが持てる最大権限の「上限」を定義します。

Permissions Boundaryの状態評価結果
アクションを許可次のステップへ
アクションを許可しない(記述なし)Deny 確定
Permissions Boundaryが設定されていないこのステップをスキップ

Permissions Boundaryは「このロールはどんなに権限を付与されても、S3とDynamoDBしか使えない」という制約を設定したい場合に使います。開発者向けのロールや、IAMロール委任(他者がIAMロールを作成できる権限を付与する場合)で特に有効です。

ステップ3: Identity-based Policy の評価

IAMユーザー・IAMロールに直接アタッチされたポリシーです。インラインポリシーと管理ポリシー(AWS管理・カスタマー管理)の両方が対象です。

Identity-based Policyの状態評価結果
アクションを明示的にDenyDeny 確定
アクションをAllow次のステップへ(Resource-based Policyと並行評価)
アクションの記述なし評価継続(Resource-based PolicyがあればAllow可能性あり)

ステップ4: Resource-based Policy の評価

S3バケットポリシー・KMSキーポリシー・Lambda関数のリソースポリシーなど、AWSリソース側に設定するポリシーです。すべてのAWSサービスがResource-based Policyをサポートしているわけではありません(EC2インスタンスなどはサポート外)。

Resource-based Policyの状態評価結果
アクションを明示的にDenyDeny 確定
アクションをAllowAllow 確定(Identity-based PolicyがなくてもOK)
Resource-based Policyが存在しないIdentity-based PolicyのAllow有無に依存

Resource-based Policy と Identity-based Policy の関係は OR評価 です。どちらか一方がAllowであれば通過できます(ただし明示的Denyが存在せず、SCPとPermissions Boundaryが通過した場合)。

クロスアカウントアクセスの場合はルールが変わります。アカウントAのリソースにアカウントBのIAMユーザーがアクセスする際は、Resource-based PolicyでアカウントBのプリンシパルを許可しつつ、さらにアカウントB側のIdentity-based Policyでも許可が必要です(AND評価になる)。

ステップ5: Session Policy の評価

AssumeRole・GetFederationToken・AssumeRoleWithWebIdentityなどでロールを引き受ける際に、一時的に有効なポリシーを追加設定できます。これが Session Policy です。

Session Policyの状態評価結果
アクションをAllow(かつIdentity-based PolicyもAllow)Allow 確定
アクションの記述なし、またはDenyDeny
Session Policyが設定されていないこのステップをスキップ

Session Policyは元のIdentity-based Policyを 絞り込む ことはできますが、拡張はできません。IAMロール AdminRole を引き受ける際にSession PolicyでS3のみ許可すれば、その一時認証情報ではS3以外の操作ができなくなります。


ポリシータイプ優先順位フローチャート(mermaid01)

flowchart TD
 A[リクエスト発行] --> B{Organizations使用?}
 B -->|Yes| C{SCP: アクションを許可?}
 B -->|No| E
 C -->|明示的Deny or 記述なし| Z1[❌ Deny]
 C -->|Allow| E{Permissions Boundary\n設定あり?}
 E -->|Yes| F{Permissions Boundary:\nアクションを許可?}
 E -->|No| G
 F -->|許可しない| Z2[❌ Deny]
 F -->|許可| G{Identity-based Policy:\n明示的Deny?}
 G -->|あり| Z3[❌ Deny]
 G -->|なし| H{Identity-based Policy:\nAllow?}
 H -->|Allow| I{Session Policy\n設定あり?}
 H -->|記述なし| J{Resource-based Policy:\nAllow?}
 J -->|Allow| I
 J -->|なし or Deny| Z4[❌ Deny]
 I -->|Yes| K{Session Policy:\nAllow?}
 I -->|No| Z5[✅ Allow]
 K -->|Allow| Z5
 K -->|Deny or 記述なし| Z6[❌ Deny]

フローチャートの3つの重要な読み方:

  1. 上から下へ = 制約が強い順 — 上のレイヤー(SCP・Permissions Boundary)がDenyしたら後続はすべて無意味
  2. Identity-based と Resource-based は並列チェック — どちらかがAllowなら通過(明示的Denyなし・同一アカウントの場合)
  3. Session Policyは「絞り込みフィルター」 — 元の権限の上限を超えることはできない

3-3. 評価ロジックの実例

理論を理解したら実例で確認します。以下の3例で「なぜその結果になるか」を評価ステップごとに追います。

実例1: SCP でS3制限 + Identity-based でS3 Allow → 結果はDeny

シナリオ: AWSアカウントにSCPが適用されており、IAMユーザーにS3のAllowポリシーがアタッチされています。開発者はS3操作ができると思っているが、実際はすべてのS3操作がDenyになっている状況です。

SCPの内容(OUレベルで適用):

{
  "Version": "2012-10-17",
  "Statement": [
 {
"Sid": "DenyS3ForThisOU",
"Effect": "Deny",
"Action": "s3:*",
"Resource": "*"
 }
  ]
}

IAMユーザーのIdentity-based Policy:

{
  "Version": "2012-10-17",
  "Statement": [
 {
"Sid": "AllowS3Operations",
"Effect": "Allow",
"Action": [
  "s3:GetObject",
  "s3:PutObject",
  "s3:ListBucket"
],
"Resource": [
  "arn:aws:s3:::my-bucket",
  "arn:aws:s3:::my-bucket/*"
]
 }
  ]
}

評価ステップの追跡(s3:GetObject リクエスト)

ステップ評価対象判定理由
Step 1SCP❌ Deny確定s3:* を明示的Deny → 即座に終了
Step 2Permissions BoundaryスキップStep 1でDeny確定済み
Step 3Identity-based PolicyスキップStep 1でDeny確定済み
Step 4Resource-based PolicyスキップStep 1でDeny確定済み
最終結果❌ Deny

教訓: Identity-based PolicyでいくらAllowを書いても、上位のSCPがDenyしていれば無効です。AWSコンソールやCLIで「Access Denied」が出た際は、まずSCPを確認する習慣をつけましょう。SCPは aws organizations list-policies-for-target コマンドで確認できます。


実例2: Permissions Boundary でS3のみ許可 → S3以外はDeny

シナリオ: 開発者向けIAMロールにPermissions BoundaryとしてS3限定ポリシーが設定されています。このロールにはEC2とS3両方のAllowポリシーがアタッチされていますが、EC2操作はDenyになるはずです。

Permissions Boundary(IAMロールに設定):

{
  "Version": "2012-10-17",
  "Statement": [
 {
"Sid": "AllowS3Only",
"Effect": "Allow",
"Action": "s3:*",
"Resource": "*"
 }
  ]
}

IAMロールのIdentity-based Policy(AmazonS3FullAccess + AmazonEC2FullAccess をアタッチ相当):

{
  "Version": "2012-10-17",
  "Statement": [
 {
"Effect": "Allow",
"Action": [
  "s3:*",
  "ec2:*"
],
"Resource": "*"
 }
  ]
}

S3アクセス(s3:PutObject)の評価

ステップ評価対象判定理由
Step 1SCP通過デフォルトFullAWSAccess
Step 2Permissions Boundary通過s3:* をAllow
Step 3Identity-based PolicyAllows3:* をAllow
Step 4Resource-based PolicyスキップIdentity-based でAllow確定
最終結果✅ Allow

EC2アクセス(ec2:DescribeInstances)の評価

ステップ評価対象判定理由
Step 1SCP通過デフォルトFullAWSAccess
Step 2Permissions Boundary❌ Deny確定ec2:* の記述なし → 暗黙Deny
Step 3Identity-based PolicyスキップStep 2でDeny確定済み
最終結果❌ Deny

教訓: Permissions Boundaryは「このロールが持てる最大権限の上限」を定義します。Identity-based PolicyでEC2をAllowしていても、Permissions BoundaryにEC2の許可がない場合はDenyになります。「管理者が開発者に一定の権限委任を認めつつ、特定サービス以外は操作させない」制御として特に有効です。


実例3: Resource-based Policy のみでAllow — Identity-based Policy不要のケース

シナリオ: IAMユーザーにはS3の許可ポリシーがありませんが、S3バケット側のバケットポリシーでそのユーザーを明示的にAllowしています(同一アカウント内)。

バケットポリシー(Resource-based Policy):

{
  "Version": "2012-10-17",
  "Statement": [
 {
"Sid": "AllowSpecificUser",
"Effect": "Allow",
"Principal": {
  "AWS": "arn:aws:iam::123456789012:user/data-analyst"
},
"Action": [
  "s3:GetObject",
  "s3:ListBucket"
],
"Resource": [
  "arn:aws:s3:::analytics-bucket",
  "arn:aws:s3:::analytics-bucket/*"
]
 }
  ]
}

IAMユーザーのIdentity-based Policy:S3に関する記述なし(空または他サービスのみ)

評価ステップ(s3:GetObject リクエスト)

ステップ評価対象判定理由
Step 1SCP通過デフォルトFullAWSAccess
Step 2Permissions Boundaryスキップ設定なし
Step 3Identity-based Policy記述なし暗黙Denyだが評価継続(次ステップへ)
Step 4Resource-based Policy✅ Allow確定バケットポリシーで明示的Allow
最終結果✅ Allow

教訓: 同一アカウント内では、Resource-based PolicyのAllowだけでIdentity-based Policyなしにアクセスできるケースがあります。これはS3バケットポリシーやKMSキーポリシーでよく使われるパターンです。

ただしクロスアカウントの場合は異なります。アカウントAのバケットにアカウントBのユーザーがアクセスする際は、バケットポリシー(アカウントA側)Identity-based Policy(アカウントB側)両方にAllow が必要です(AND評価)。


評価ロジック3つの鉄則

  • 明示的Denyが最強: どのポリシータイプに書かれたAllowよりも、明示的Denyは常に優先される。1か所でもDenyがあれば即座にアクセス拒否。順序や位置は関係ない。
  • 暗黙Denyがデフォルト: AWSはホワイトリスト型。「何も書いていない = Deny」が大前提。「Denyを書いていないから通るはず」は誤解。許可したいアクションを明示的にAllowで列挙する必要がある。
  • SCPはガードレール: AWS OrganizationsのSCPは最上位レイヤー。SCPが許可しないアクションは、Identity-based PolicyでAllowしても無効。アカウントをOUに配置する際は必ずSCPを確認する。

評価順序(上が強い): SCP → Permissions Boundary → Identity-based → Resource-based → Session Policy

デバッグの第一歩: 「Access Denied」が出たらSCPとPermissions Boundaryから確認する。Identity-based Policyだけ見ても解決しないケースが多い。

クロスアカウントの例外: 別アカウントからのアクセスはResource-based Policy + Identity-based Policyの両方が必要(AND評価)。同一アカウントはOR評価。


4. 必要権限の特定法 — Access Analyzer × CloudTrail × Access Advisor

「最小権限の原則」は AWS セキュリティの基本中の基本だが、「では実際にどう権限を絞るか」という具体手順は意外と語られていない。本章ではその答えとして、AWS が公式に提供する 3 つのツール — IAM Access Advisor / AWS CloudTrail / IAM Access Analyzer policy generation — を組み合わせた実務フローを解説する。実際のログという事実から出発して最小権限ポリシーを逆算するアプローチは、「とりあえず * を付けたら動いた」状態からの確実な脱却方法だ。


4-1. 3ツールの役割分担

権限特定に使う 3 ツールの担当領域と使いどころは明確に異なる。サービスレベル → API アクションレベル → 自動ポリシー生成という段階的な絞り込みを理解することで、どのツールをいつ使うかが明確になる。

ツール粒度確認できること主な用途
IAM Access Advisorサービスレベル過去 400 日間に使用したサービス一覧と最終アクセス日未使用サービスを削除候補に絞り込む
AWS CloudTrailAPI アクションレベル実際に呼び出された API + 対象リソース ARN使用済み API を正確に特定する
IAM Access Analyzer policy generationAPI アクションレベル (自動)CloudTrail ログから自動生成された最小権限ポリシードラフトポリシーを自動生成して精査する

IAM Access Advisor — サービス単位の棚卸し

IAM コンソールの [ロール → アクセスアドバイザー] タブから確認できる。対象の IAM エンティティ (ユーザー / グループ / ロール / ポリシー) が過去 400 日間にアクセスしたサービス一覧と最終アクセス日時が一覧表示される。

主な利用シーン:

  • EC2 / S3 / RDS / Lambda などのサービスレベルで「本当に使っているか」を確認するファーストステップ。
  • 1 年以上アクセスがないサービスは削除候補として検討できる。

注意点:

  • 粒度はサービスレベルまで (例: s3:* のうちどの API を使ったかは分からない)。
  • ロールを引き受ける (AssumeRole) 側のエンティティではなく、ロール自身のポリシー使用状況を見る。
  • 一部のコントロールプレーン系サービスは Access Advisor に対応していない場合がある。

AWS CloudTrail — API アクション単位の証拠収集

CloudTrail は AWS へのすべての API コールを記録する監査ログサービスだ。S3 バケットへのイベント保存と、CloudTrail Lake または Amazon Athena を組み合わせることで、「いつ・どのロールが・どの API を・どのリソースに対して呼んだか」を特定できる。

主な利用シーン:

  • 特定のロールが過去 N 日間に呼び出した API アクションと対象リソース ARN を正確に列挙する。
  • sts:AssumeRole イベントを追跡してロールチェーンを把握する。
  • ポリシー変更直後にエラーイベントが発生していないか確認する。

Athena クエリ例 (CloudTrail ログが S3 に保存済みで Athena テーブルを作成済みの場合):

SELECT DISTINCT
 eventSource,
 eventName,
 COUNT(*) AS call_count
FROM
 cloudtrail_logs
WHERE
 userIdentity.sessionContext.sessionIssuer.arn
  = 'arn:aws:iam::123456789012:role/your-role'
 AND year = '2025'
 AND errorCode IS NULL
GROUP BY
 eventSource,
 eventName
ORDER BY
 eventSource,
 eventName;

errorCode IS NULL でフィルタすることで「成功した API 呼び出しのみ」を対象にできる。eventName に並んだ API アクション名が最小権限ポリシーの材料になる。

IAM Access Analyzer policy generation — CloudTrail ログからの自動ポリシー生成

Access Analyzer の機能の一つで、指定した IAM ロールの CloudTrail ログ (最大 90 日間) を解析し、実際に使用した API アクションだけで構成された最小権限ポリシーを自動生成する。

主な利点:

  • CloudTrail の手動クエリを省略してポリシードラフトを自動生成できる。
  • 90 日以内に使用した全 API アクションを網羅するため、手動リストアップの漏れを防げる。
  • サービス別・リソース別にアクションが整理されたポリシー JSON が出力される。

制約事項:

  • 分析対象は 最大 90 日間 の CloudTrail ログのみ。四半期に 1 回しか実行しないバッチジョブなどは補足されない。
  • S3 データイベント・Lambda データイベントなど「データプレーンイベント」は CloudTrail で別途有効化が必要。
  • 生成ポリシーはあくまでドラフト。"Resource": "*" になっている箇所は手動で ARN に絞り込む必要がある。

4-2. 実践フロー — 5ステップで最小権限ポリシーを作る

3 ツールを組み合わせた実践フローを 5 ステップで整理する。各ステップで何を確認し何を次に渡すかを意識することで、手戻りなく最小権限ポリシーを完成させられる。

fig02: 最小権限特定フロー

Step 1: Access Advisor でサービス棚卸し

目的: 現在のロールに付与されているポリシーのうち、完全に未使用のサービスを特定して削除候補にする。

IAM コンソール → [ロール] → 対象ロールを選択 → [アクセスアドバイザー] タブ

確認ポイント:

  • 「最終アクセス」列が「—」(未使用) または 365 日以上前のサービスは削除候補。
  • ec2:* のように広範な権限が付与されているにもかかわらず EC2 への記録が 1 年以上ない場合は、その Statement ごと削除を検討する。

アウトプット: 完全に未使用のサービス一覧 (→ 該当サービスのすべての Action を持つ Statement を削除)

Step 2: CloudTrail で使用済み API アクションを特定

目的: Step 1 で削除しきれなかった「使用しているサービス」の中で、実際に使われている API アクションを特定する。

コンソール簡易版:

CloudTrail コンソール → [イベント履歴]
フィルタ: [ユーザー名] に対象ロール名を入力
期間: 過去 90 日 (最大)
→ [フィルタの適用]

「イベント名」列に表示される API アクション名を記録していく。件数が多い場合は「CSV をダウンロード」で一括取得できる。

アウトプット: サービス別の使用済み API アクション一覧 (例: s3:GetObject, s3:PutObject, ec2:DescribeInstances など)

Step 3: Access Analyzer policy generation でドラフト生成

目的: Step 2 の手動クエリを自動化し、ポリシーのドラフトを生成する。

操作の詳細は §4-3 で説明する。ここではフロー上の位置づけを押さえる。

Access Analyzer は CloudTrail ログ (最大 90 日) を解析し、使用済み API アクションを自動的に抽出して JSON ポリシーを生成する。Step 2 の手動確認と Access Analyzer の自動生成を並用することで、互いの死角を補完できる。

  • 手動確認: 90 日超のログも確認可能
  • 自動生成: 90 日以内のアクションを漏れなく網羅

アウトプット: JSON 形式の最小権限ポリシー (ドラフト)

Step 4: 生成ポリシーのレビューと絞り込み

目的: 自動生成ポリシーの "Resource": "*" を具体的な ARN に絞り込み、不要なアクションを取り除く。

自動生成ポリシーに含まれる典型的な改善ポイント:

  1. "Resource": "*" の箇所を ARN 指定に変更する
  2. S3 例: "arn:aws:s3:::my-bucket/*"
  3. DynamoDB 例: "arn:aws:dynamodb:ap-northeast-1:123456789012:table/MyTable"
  4. Lambda ログ例: "arn:aws:logs:ap-northeast-1:123456789012:log-group:/aws/lambda/my-function:*"

  5. 90 日分析の死角を補完する (バッチ処理など低頻度 API は手動で追加)

  6. アクションの過剰包含を確認する (ec2:Describe* ワイルドカードが残っていれば個別 Describe に絞る)

レビューチェックリスト:

□ Resource が * のアクションを全てリストアップし ARN 指定に変更
□ Write 系アクション (Put / Create / Delete) は特に ARN を絞り込む
□ 分析期間外の API (四半期バッチ等) を手動追加済みか
□ iam:PassRole が含まれる場合、渡せるロールを条件で限定しているか
□ sts:AssumeRole が含まれる場合、信頼ポリシーと整合しているか

Step 5: iam:SimulatePrincipalPolicy で検証

目的: 絞り込んだポリシーで想定操作が許可されること、および意図しない操作が拒否されることを検証する。

許可確認 — S3 の特定バケットへの GetObject が許可されるか:

aws iam simulate-principal-policy \
  --policy-source-arn "arn:aws:iam::123456789012:role/your-role" \
  --action-names "s3:GetObject" \
  --resource-arns "arn:aws:s3:::my-bucket/my-key.json"

拒否確認 — S3 への DeleteBucket が拒否されるか:

aws iam simulate-principal-policy \
  --policy-source-arn "arn:aws:iam::123456789012:role/your-role" \
  --action-names "s3:DeleteBucket" \
  --resource-arns "arn:aws:s3:::my-bucket"

レスポンスの EvalDecision フィールドが "allowed" / "explicitDeny" / "implicitDeny" のいずれかで返る。意図と一致しない場合はポリシーを再レビューし、Step 4 に戻って修正する。


4-3. ハンズオン手順 — AWSコンソール

Access Analyzer policy generation を AWS コンソールで実際に操作する手順を説明する。

Access Analyzer の有効化

Access Analyzer はリージョンごとに有効化が必要だ。マルチリージョン構成の場合は使用するリージョン全てで有効化する。

IAM コンソール → 左メニュー [Access Analyzer] → [アナライザーを作成]

設定項目:

項目推奨値
アナライザー名access-analyzer-my-account (任意)
タイプ現在のアカウント
アーカイブルール必要に応じて後で追加 (初回は空で可)

「アナライザーを作成」ボタンをクリックするとステータスが「アクティブ」になる。

policy generation の起動

IAM コンソール → 左メニュー [ポリシーの生成] (Access Analyzer セクション内)
→ [ポリシーを生成する]

設定項目:

項目内容
IAM エンティティ分析対象のロール ARN または名前
CloudTrail 証跡 ARNCloudTrail トレイル ARN (S3 に出力している証跡)
開始日時分析期間の開始日時 (最大 90 日前まで)
終了日時分析期間の終了日時

「ポリシーの生成」ボタンをクリックするとジョブが開始される。完了まで数分〜最大 30 分程度かかる。

前提条件:

  • 分析対象ロールの API コールが CloudTrail に記録されている (デフォルトトレイルが有効な場合は満たされていることが多い)。
  • CloudTrail ログの S3 バケットに Access Analyzer がアクセスできること (コンソール経由なら自動設定される)。
  • 操作ユーザーに access-analyzer:GenerateServiceLastAccessedDetailscloudtrail:LookupEvents 権限があること。

生成ポリシーの確認・修正・適用

生成ジョブが完了するとステータスが「完了」になる。

[完了] のジョブを選択 → [生成されたポリシーを表示]

表示される JSON の例:

{
  "Version": "2012-10-17",
  "Statement": [
 {
"Effect": "Allow",
"Action": [
  "s3:GetObject",
  "s3:ListBucket",
  "s3:PutObject"
],
"Resource": [
  "arn:aws:s3:::my-bucket",
  "arn:aws:s3:::my-bucket/*"
]
 },
 {
"Effect": "Allow",
"Action": [
  "dynamodb:GetItem",
  "dynamodb:PutItem",
  "dynamodb:Query"
],
"Resource": "arn:aws:dynamodb:ap-northeast-1:123456789012:table/MyTable"
 },
 {
"Effect": "Allow",
"Action": [
  "logs:CreateLogGroup",
  "logs:CreateLogStream",
  "logs:PutLogEvents"
],
"Resource": "*"
 }
  ]
}

修正ポイント:

  • logs:*"Resource": "*" は対象ロググループ ARN に絞り込む。
  • 実際の ARN が判明している全箇所を具体化する。

修正後は「このポリシーを使用」ボタンで新規ポリシーとして保存し、ロールにアタッチする。

CloudTrail コンソールでの手動確認 (補助手順)

policy generation を使わず手動で確認する場合:

CloudTrail コンソール → [イベント履歴]
→ フィルタ: [ユーザー名] = 対象ロール名
→ 期間: 過去 90 日
→ [フィルタの適用]

「イベント名」列の API アクション名を記録する。件数が多い場合は「CSV ダウンロード」機能を使うと効率的だ。

Athena クエリを使う場合の前提:

  1. CloudTrail ログが S3 に保存されていること
  2. Athena コンソールまたは AWS Glue でテーブルを作成済みであること
  3. cloudtrail_logs テーブルに対して SQL でクエリを実行

Athena による集計は初期セットアップに手間がかかるが、大量ログの処理やクロスアカウント分析に適している。


4-4. Terraform での実装例

ハンズオン手順で特定した最小権限ポリシーを Terraform で定義・管理する方法を示す。

aws_iam_policy でポリシーを定義

resource "aws_iam_policy" "my_service_policy" {
  name  = "my-service-least-privilege-policy"
  description = "Least privilege policy generated from Access Analyzer analysis"

  policy = jsonencode({
 Version = "2012-10-17"
 Statement = [
{
  Sid = "S3ReadWrite"
  Effect = "Allow"
  Action = [
 "s3:GetObject",
 "s3:ListBucket",
 "s3:PutObject",
 "s3:DeleteObject",
  ]
  Resource = [
 "arn:aws:s3:::${var.bucket_name}",
 "arn:aws:s3:::${var.bucket_name}/*",
  ]
},
{
  Sid = "DynamoDBTableAccess"
  Effect = "Allow"
  Action = [
 "dynamodb:GetItem",
 "dynamodb:PutItem",
 "dynamodb:UpdateItem",
 "dynamodb:DeleteItem",
 "dynamodb:Query",
 "dynamodb:Scan",
  ]
  Resource = [
 "arn:aws:dynamodb:${var.aws_region}:${data.aws_caller_identity.current.account_id}:table/${var.table_name}",
 "arn:aws:dynamodb:${var.aws_region}:${data.aws_caller_identity.current.account_id}:table/${var.table_name}/index/*",
  ]
},
{
  Sid = "CloudWatchLogsWrite"
  Effect = "Allow"
  Action = [
 "logs:CreateLogGroup",
 "logs:CreateLogStream",
 "logs:PutLogEvents",
  ]
  Resource = "arn:aws:logs:${var.aws_region}:${data.aws_caller_identity.current.account_id}:log-group:/aws/lambda/${var.function_name}:*"
},
 ]
  })
}

jsonencode を使うと JSON を文字列で書かずに Terraform の型として定義でき、変数補間が使えるのが利点だ。

aws_iam_role + aws_iam_role_policy_attachment でロール管理

data "aws_caller_identity" "current" {}

data "aws_iam_policy_document" "assume_role" {
  statement {
 actions = ["sts:AssumeRole"]

 principals {
type  = "Service"
identifiers = ["lambda.amazonaws.com"]
 }
  }
}

resource "aws_iam_role" "my_lambda_role" {
  name= "my-lambda-execution-role"
  assume_role_policy = data.aws_iam_policy_document.assume_role.json

  tags = {
 Environment = var.environment
 ManagedBy= "terraform"
  }
}

resource "aws_iam_role_policy_attachment" "service_policy_attach" {
  role = aws_iam_role.my_lambda_role.name
  policy_arn = aws_iam_policy.my_service_policy.arn
}

実践例 — data source を使った型安全なポリシー定義

jsonencode よりも aws_iam_policy_document data source を使う方法を推奨する。Terraform Language Server によって引数の型チェックが効くため、JSON 構文エラーや誤ったキー名を早期に検出できる。

variable "bucket_name" {
  type  = string
  description = "Target S3 bucket name"
}

variable "table_name" {
  type  = string
  description = "Target DynamoDB table name"
}

variable "function_name" {
  type  = string
  description = "Lambda function name"
}

variable "aws_region" {
  type = string
  default = "ap-northeast-1"
}

variable "environment" {
  type = string
  default = "production"
}

data "aws_iam_policy_document" "least_privilege" {
  statement {
 sid = "S3Operations"
 effect = "Allow"

 actions = [
"s3:GetObject",
"s3:PutObject",
"s3:ListBucket",
 ]

 resources = [
"arn:aws:s3:::${var.bucket_name}",
"arn:aws:s3:::${var.bucket_name}/*",
 ]
  }

  statement {
 sid = "DynamoDBOperations"
 effect = "Allow"

 actions = [
"dynamodb:GetItem",
"dynamodb:PutItem",
"dynamodb:Query",
 ]

 resources = [
"arn:aws:dynamodb:${var.aws_region}:${data.aws_caller_identity.current.account_id}:table/${var.table_name}",
 ]
  }

  statement {
 sid = "CloudWatchLogsWrite"
 effect = "Allow"

 actions = [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents",
 ]

 resources = [
"arn:aws:logs:${var.aws_region}:${data.aws_caller_identity.current.account_id}:log-group:/aws/lambda/${var.function_name}:*",
 ]
  }
}

resource "aws_iam_policy" "lambda_least_privilege" {
  name= "${var.function_name}-least-privilege"
  policy = data.aws_iam_policy_document.least_privilege.json
}

resource "aws_iam_role" "lambda_execution" {
  name= "${var.function_name}-execution-role"
  assume_role_policy = data.aws_iam_policy_document.assume_role.json

  tags = {
 Function = var.function_name
 Environment = var.environment
 ManagedBy= "terraform"
  }
}

resource "aws_iam_role_policy_attachment" "lambda_policy_attach" {
  role = aws_iam_role.lambda_execution.name
  policy_arn = aws_iam_policy.lambda_least_privilege.arn
}

Terraform 実装のベストプラクティス:

  • data.aws_iam_policy_document を使うと IDE の型チェックが効き、actions / resources のスペルミスを早期検出できる。
  • var.aws_regiondata.aws_caller_identity.current.account_id を使い、ハードコードされたアカウント ID / リージョンを排除する。
  • sid (Statement ID) を付けると terraform plan の diff が分かりやすくなる。
  • タグで ManagedBy = "terraform" を明記し、手動操作との混在を防ぐ。

Access Analyzer Policy Generation — 公式ドキュメント


4-5. mermaid02: Access Analyzer policy generation のシーケンス図

Access Analyzer policy generation が CloudTrail ログを解析してポリシーを生成するまでのフローを、システム間のシーケンスとして可視化する。

sequenceDiagram
 autonumber
 actor Engineer as エンジニア
 participant Console as AWSコンソール
 participant AccessAnalyzer as IAM Access Analyzer
 participant S3 as S3 (CloudTrailログ保存先)
 participant IAM as IAM (ポリシー適用)

 Engineer->>Console: policy generation 開始<br>(ロールARN + CloudTrail ARN + 期間指定)
 Console->>AccessAnalyzer: GenerateServiceLastAccessedDetails リクエスト
 AccessAnalyzer->>S3: CloudTrail ログ読み込み (最大90日分)
 S3-->>AccessAnalyzer: イベントログ一覧
 note over AccessAnalyzer: 対象ロールのAPI呼び出しイベントを抽出<br>サービス/アクション/リソースARN 別に集約
 AccessAnalyzer-->>Console: 生成済みポリシー JSON (Status: Complete)
 Console-->>Engineer: ポリシー内容を表示

 Engineer->>Console: Resource:* を ARN に絞り込み・修正
 Console->>IAM: 新規ポリシー作成 (CreatePolicy)
 IAM-->>Console: ポリシー ARN 発行
 Console->>IAM: ロールにアタッチ (AttachRolePolicy)
 IAM-->>Console: 適用完了
 Console-->>Engineer: 最小権限ポリシー適用完了

このシーケンスから以下の重要な点が読み取れる:

  • S3 への直接アクセス: Access Analyzer は CloudTrail サービス API を経由せず、CloudTrail が出力した S3 バケット上のログを直接読み込む。そのため S3 バケットポリシーで Access Analyzer にアクセス許可を与える必要がある (コンソール経由の場合は自動設定)。
  • 90 日制限の意味: S3 上の過去 90 日分のログファイルを走査する仕組みのため、それ以前の API 呼び出しは補足されない。
  • 手動レビューが必須: "Resource": "*" を ARN に絞り込む工程は自動化できない。生成ポリシーはあくまでスタートラインだ。

5. 詰まりポイント図解 — 初心者5大パターン

fig03: IAMポリシー詰まりポイント5パターン

AWSを使い始めて数ヶ月のうちに、誰もが一度はIAMポリシーで行き詰まる経験をする。「Allowを書いたのに動かない」「なぜかS3の全バケットに触れてしまう」——これらはいくつかの典型的なパターンに集約される。

本章では、初心者が最も陥りやすい5大詰まりパターンを取り上げる。各パターンは「なぜ詰まるか」と「どう解くか」の2段構成で解説し、手を動かしながら回避策を身につけられるよう設計した。

5つのパターンは相互に関連している場合が多い。たとえば「Resource: *」を使いながら「Action: *」も設定してしまうケースは頻繁に発生する。各パターンを個別に理解したうえで、組み合わせを意識することが重要だ。


パターン1: Resource: “*” を使いすぎる

IAMポリシーを最初に書くとき、多くの人がResource: "*"を使う。動作確認を手早く済ませたい気持ちはわかるが、これを本番環境にそのまま持ち込むと深刻な問題を引き起こす。

なぜ詰まるか

Resource: "*"は文字通り「すべてのAWSリソース」を対象にする。たとえば以下のポリシーは、アカウント内のすべてのS3バケットに対して読み書き削除が可能になる。

{
  "Version": "2012-10-17",
  "Statement": [
 {
"Effect": "Allow",
"Action": [
  "s3:GetObject",
  "s3:PutObject",
  "s3:DeleteObject"
],
"Resource": "*"
 }
  ]
}

このポリシーを持つIAMユーザーやロールは、開発用バケットだけでなく、本番データが入ったバケット、ログバケット、他チームのバケットにも自由にアクセスできてしまう。「最小権限の原則」に完全に違反した状態だ。

さらに問題なのは、この設定がセキュリティ監査で即座に検出されることだ。AWS Security HubやAWS Configのマネージドルール「iam-no-inline-policy-check」「iam-policy-no-statements-with-admin-access」などが警告を発する。これが積み重なると、コンプライアンス審査で大量の指摘が発生する。

Resource: "*"が必要なアクションも一部存在する。たとえばリソースレベルの権限をサポートしていないAPIアクション(s3:ListAllMyBucketsiam:ListUsersなど)は、ARN指定ができないためやむを得ず*を使う。しかしS3の個別オブジェクト操作でこれを使うのは過剰権限だ。また、ポリシーがシンプルに見えてもResource: "*"であればサービスの全リソースを包含しているため、後から権限の棚卸しをしようとしたときに「どのリソースに本当にアクセスが必要だったか」が追跡できなくなる問題も生じる。

どう解くか

ARN(Amazon Resource Name)形式でリソースを限定する。ARNの基本形式は以下の通りだ。

arn:partition:service:region:account-id:resource

S3の場合、バケット自体とその配下のオブジェクトを別々のARNで表現する必要がある。

{
  "Version": "2012-10-17",
  "Statement": [
 {
"Effect": "Allow",
"Action": [
  "s3:GetObject",
  "s3:PutObject",
  "s3:DeleteObject"
],
"Resource": [
  "arn:aws:s3:::my-app-production-bucket",
  "arn:aws:s3:::my-app-production-bucket/*"
]
 },
 {
"Effect": "Allow",
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::my-app-production-bucket"
 }
  ]
}

arn:aws:s3:::my-app-production-bucketはバケット自体を、arn:aws:s3:::my-app-production-bucket/*はバケット内のすべてのオブジェクトを意味する。s3:ListBucket(バケットの中身を一覧する操作)はバケット自体のARNが必要で、オブジェクトパスの/*は不要な点に注意しよう。

ワイルドカード*を使う場合も、スコープを絞ることが可能だ。たとえばarn:aws:s3:::my-app-*と書けば「my-app-で始まるすべてのバケット」に限定できる。本番・開発・ステージングのバケットをまとめて指定したいケースで有効だ。アプリケーションの命名規則を事前に統一しておくと、このようなARNパターンが活用しやすくなる。

IAM Access Analyzerの「未使用のアクセス検出」機能を定期的に実行すると、実際には使われていない広すぎる権限を検出できる。Resource: "*"を設定していても「実際にアクセスしたリソースはこれだけ」というレポートが得られるため、権限の絞り込みに役立つ。

パターン1 対処法

  • Resource: "*" をそのまま本番に持ち込まない。動作確認後は必ずARN指定に切り替える
  • S3の操作はバケットARN (arn:aws:s3:::bucket-name) とオブジェクトARN (arn:aws:s3:::bucket-name/*) を使い分ける
  • リソースレベル権限未対応のアクション (s3:ListAllMyBuckets 等) は * が必要だが、その他は原則ARN指定
  • IAM Access Analyzerの「未使用のアクセス検出」で Resource: "*" の実際の使用状況を定期確認する

パターン2: Action: “*” を使いすぎる

Action: "*"は「すべてのAWSアクション」を許可する設定だ。これは事実上のAdministratorアクセスであり、IAMポリシーの中でも最も危険な設定の一つとして知られている。

なぜ詰まるか

Action: "*"を使うと、指定したリソースに対してあらゆる操作が可能になる。意図したのはS3への読み書きだけだったとしても、s3:DeleteBucket(バケット削除)、s3:PutBucketPolicy(バケットポリシー変更)なども含まれてしまう。

実際に問題が起きやすいのは、緊急対応時に「とりあえず全権限を渡して後で絞る」という運用をしたまま放置するケースだ。「後で」は往々にして「永遠に」になる。

{
  "Version": "2012-10-17",
  "Statement": [
 {
"Effect": "Allow",
"Action": "*",
"Resource": "arn:aws:s3:::my-app-production-bucket/*"
 }
  ]
}

一見「バケットを限定しているから安全」に見えるが、Action: "*"にはEC2やLambdaなど他サービスのAPIアクションも含まれる。S3バケットのARNに対してEC2のAPIを呼んでも意味はないが、将来AWSがS3に新しいAPIを追加した際に、その権限が自動的に付与されてしまう点がリスクだ。新機能が追加されるたびに、意図せず権限が拡大し続ける。

セキュリティ的な観点からは、攻撃者がこのロールを奪取した場合の被害が最大化する。不必要な権限を与えるほど、侵害時の影響範囲(Blast Radius)が広がる。AWS Security Hubはこの状態を重大度「High」または「Critical」として検出し、報告書の中で最も修正優先度が高い項目として扱われることが多い。

CI/CDパイプラインのIAMロールがAction: "*"になっているケースも散見される。デプロイには広い権限が必要な場合もあるが、Action: "*"は必要以上に広い。CDロールであればec2:*ecs:*など、デプロイ対象サービスのアクションに絞り込めることが多い。

どう解くか

必要なActionを明示的に列挙する。最初から完璧なリストを作る必要はなく、アプリケーションが使うAPIを整理するところから始める。

{
  "Version": "2012-10-17",
  "Statement": [
 {
"Effect": "Allow",
"Action": [
  "s3:GetObject",
  "s3:PutObject",
  "s3:ListBucket",
  "s3:DeleteObject"
],
"Resource": [
  "arn:aws:s3:::my-app-production-bucket",
  "arn:aws:s3:::my-app-production-bucket/*"
]
 }
  ]
}

実際のアプリケーションが使っているAPIを特定するには、§4で解説したAWS CloudTrail + Access Analyzerを活用する。CloudTrailのログを分析して実際に呼び出されたAPIのみをリストアップし、それを元にActionを絞り込む方法が最も確実だ。

AWSマネジメントコンソールでIAMポリシーを作成する際は「ビジュアルエディタ」を使うと、サービスごとにActionをチェックボックスで選べるので、誤ってAction: "*"を設定してしまうリスクを減らせる。また、AWSマネージドポリシー(例: AmazonS3ReadOnlyAccess)をベースに使用し、不足している権限だけを追加でカスタムポリシーに書く方法も有効だ。マネージドポリシーはAWSが最小権限の観点でメンテナンスしているため、ゼロから書くより安全なベースラインになる。

パターン2 対処法

  • Action: "*" はAdministrator相当のリスクがある。緊急時も含めて原則禁止
  • まずAWSマネージドポリシー (例: AmazonS3ReadOnlyAccess) を使い、必要に応じてカスタムポリシーで補完する
  • CloudTrailで実際に呼び出されているAPIをリストアップし、それをActionに列挙する
  • IAM Access Analyzerの「ポリシー生成」機能でCloudTrailログからポリシーを自動生成できる (§4参照)

パターン3: 暗黙的Denyを見落とす

「Allowを書いたのに動かない」という問い合わせで最も多いのが、このパターンだ。IAMの評価ロジックでは、明示的にAllowされていないアクションはすべてDenyされる(暗黙的Deny)

なぜ詰まるか

IAMの評価ロジックを簡潔に言うと「書いていないことはDeny」だ。たとえば以下のポリシーでは、S3へのPutObjectはAllowされているが、GetObjectはDenyされる(暗黙的に)。

{
  "Version": "2012-10-17",
  "Statement": [
 {
"Effect": "Allow",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::my-bucket/*"
 }
  ]
}

このユーザーがS3バケットからファイルを読み取ろうとすると「Access Denied」が返ってくる。「なぜか?PutObjectは許可したじゃないか」と思うかもしれないが、GetObjectは許可していないのだから当然の動作だ。

さらに混乱しやすいのが、複数のポリシーが合算されるという仕組みだ。あるIAMユーザーにポリシーAとポリシーBが両方アタッチされている場合、両方のAllow文が合算されて有効になる。一方で、SCP(Service Control Policy)やPermission Boundaryが存在する場合、それらが「上限」として機能し、IdentityベースポリシーでAllowしていても弾かれることがある。

特にOrganizationsを使った環境では、SCPによる制限が見えにくく「ポリシーを見ても問題ない」のに動かない、という状況が発生しやすい。管理アカウントから設定されたSCPは、メンバーアカウントのIAMコンソールからは直接確認できないためだ。

もう一つの見落としポイントはリソースベースポリシーとの相互作用だ。S3バケットポリシーでExplicit Denyが設定されていると、IAMポリシーでAllowしていても弾かれる。明示的Deny(Explicit Deny)は最強であり、どのポリシーのAllowも覆す。バケットポリシーに"Effect": "Deny"が書かれていないか確認することも重要だ。

どう解くか

iam:SimulatePrincipalPolicy APIを使って、特定のアクションが許可されるかどうかをシミュレーションする。

aws iam simulate-principal-policy \
  --policy-source-arn arn:aws:iam::123456789012:user/myuser \
  --action-names s3:GetObject \
  --resource-arns arn:aws:s3:::my-bucket/test.txt

このコマンドの出力にはEvalDecisionフィールドが含まれ、allowedexplicitDenyimplicitDenyかが明示される。implicitDenyの場合は「どのポリシーにも該当するAllowがない」ことを意味し、ポリシーへのActionの追記が必要だ。explicitDenyの場合は、どこかにDenyが書かれているのでそれを探す必要がある。

AWSマネジメントコンソールの「IAM Policy Simulator」も同じ機能をGUIで提供している。対象のユーザーやロールを選んでシミュレーションできるので、視覚的に確認したい場合に便利だ。

CloudTrailでerrorCode: "AccessDenied"を検索すると、実際に弾かれたアクションの詳細が確認できる。errorMessageには「because no identity-based policy allows the s3:GetObject action」のように、拒否の理由が記載されることがある。これを手がかりに、どのポリシーを修正すればよいかを特定できる。

SCPの制限が疑われる場合は、マスターアカウントあるいは管理アカウントへのアクセス権があればaws organizations list-policiesでアカウントに適用されているSCPを確認できる。一般の開発者がSCPを直接確認できない場合は、クラウド基盤チームへの問い合わせが必要になる。

パターン3 対処法

  • IAMは「書いていないことはDeny」が原則。Allowを明示しないとアクセスできない
  • iam:SimulatePrincipalPolicy またはIAM Policy Simulatorで動作確認してから本番適用する
  • 「ポリシーに問題ないのに動かない」場合はSCPやPermission Boundaryの存在を疑う
  • リソースベースポリシー (S3バケットポリシー等) のExplicit DenyはIAMポリシーのAllowより優先される

パターン4: Condition の記法ミス

Conditionブロックは強力な機能だが、記法が複雑なため誤りが多い。特に演算子の選択ミスやConditionキーの使い方の間違いが頻発する。

なぜ詰まるか

Conditionで最も使われる演算子はStringEqualsStringLikeだ。この2つの違いを混同すると、意図しない動作を引き起こす。

  • StringEquals: 完全一致。ワイルドカードは機能しない
  • StringLike: パターンマッチ。*(任意の文字列)と?(任意の1文字)が使える

以下のポリシーは「ap-northeast-1リージョンのみ操作を許可する」つもりで書かれているが、機能しない

{
  "Version": "2012-10-17",
  "Statement": [
 {
"Effect": "Allow",
"Action": "ec2:*",
"Resource": "*",
"Condition": {
  "StringEquals": {
 "aws:RequestedRegion": "ap-northeast-*"
  }
}
 }
  ]
}

StringEqualsでワイルドカード*を使っても、それはリテラルの*として扱われる。つまり「リージョン名が文字通りap-northeast-*と一致するか?」と評価されるため、実際のリージョン名(ap-northeast-1など)には一致せず、結果としてすべてDenyされる。

もう一つよくあるミスが、Conditionの論理構造の誤解だ。同じConditionキーに複数の値を指定した場合の動作が直感と異なる。

"Condition": {
  "StringEquals": {
 "aws:RequestedRegion": ["ap-northeast-1", "us-east-1"]
  }
}

これは「ap-northeast-1 または us-east-1」を意味する(同一キーの複数値はOR)。ところが異なるキーを並べると「かつ」の意味になる(AND)。

"Condition": {
  "StringEquals": {
 "aws:RequestedRegion": "ap-northeast-1"
  },
  "ArnLike": {
 "aws:PrincipalArn": "arn:aws:iam::123456789012:role/MyRole"
  }
}

これは「リージョンがap-northeast-1 かつ プリンシパルがMyRole」を意味する。「同一キーの複数値はOR、異なるキーはAND」というルールは、複雑な条件を組み立てるときに必ず意識する必要がある。

GlobalConditionContextキー(aws:RequestedRegionaws:SourceIpなど)と、サービス固有のConditionキー(s3:prefixs3:RequestObjectTagKeysなど)を混同するケースも多い。サービス固有のキーはそのサービスのAPIリクエストにのみ適用され、他のサービスのリクエストでは評価されない。

どう解くか

Conditionを書く際は、まずConditionキーの正式名称をIAM Actions Reference(AWS公式ドキュメント)で確認する。リージョン制限にはaws:RequestedRegionを、送信元IPにはaws:SourceIpを使う。

リージョンをワイルドカードでまとめるにはStringLikeを使う。

{
  "Version": "2012-10-17",
  "Statement": [
 {
"Effect": "Allow",
"Action": "ec2:*",
"Resource": "*",
"Condition": {
  "StringLike": {
 "aws:RequestedRegion": "ap-northeast-*"
  }
}
 }
  ]
}

複数リージョンを明示的に列挙する場合は、StringEqualsに配列を使う。

"Condition": {
  "StringEquals": {
 "aws:RequestedRegion": [
"ap-northeast-1",
"ap-northeast-3",
"us-east-1"
 ]
  }
}

IAM Policy Simulatorでは、Conditionに関する動作も確認できる。コンテキストキーとしてaws:RequestedRegionなどを手動で入力してシミュレーションすることで、Conditionが意図通りに機能するか検証できる。本番適用前にシミュレーションで確認する習慣をつけると、記法ミスによる誤設定を事前に防げる。

AWS公式ドキュメントの「IAM JSON ポリシーの要素: Condition」ページには、全演算子と全グローバルConditionキーの一覧が掲載されている。Conditionを書くときはここを参照先の基準とすることを推奨する。

パターン4 対処法

  • StringEquals はワイルドカード不可。パターンマッチには StringLike を使う
  • 同一Conditionキーの複数値は「OR」、異なるキーの並列は「AND」と覚える
  • Conditionキーの正式名称はAWS公式ドキュメント (IAM Actions Reference) で確認する
  • IAM Policy Simulatorでコンテキストキーを手動指定してConditionの動作を事前検証する

パターン5: Permission Boundary と Identity-based の混同

Permission BoundaryはIAMの中でも特に誤解されやすい概念だ。「Boundaryで権限を与えたのに動かない」という問い合わせは、Boundaryの役割を根本的に誤解していることが原因であることがほとんどだ。

なぜ詰まるか

Permission Boundaryは「IAMエンティティが持てる権限の上限(ガードレール)」であり、それ自体はアクセスを許可しない

多くの入門者がやってしまうミスは、「BoundaryにポリシーAをアタッチすれば、ポリシーAの権限が使える」という誤解だ。実際には以下の関係になる。

実際に許可される権限 = Permission Boundary ∩ Identity-based Policy

つまり、BoundaryとIdentityベースポリシーの両方でAllowされている権限のみが有効になる。片方にしかないAllowは無効だ。

たとえばBoundaryとしてAmazonS3ReadOnlyAccessを設定したIAMユーザーに、AmazonS3FullAccessをIdentityベースポリシーとしてアタッチした場合を考える。このユーザーが実際に実行できるのはS3の読み取り操作のみだ。AmazonS3FullAccessには書き込みや削除も含まれているが、Boundary(上限)がReadOnlyなのでそれ以上はできない。

逆のケースも成立する。BoundaryにS3フルアクセスを設定しても、Identityベースポリシーにs3:GetObjectしかなければ、GetObjectしか実行できない。Boundaryは「上限を設定する」だけであって、「権限を与える」ことはしない。この非対称な役割を理解することが重要だ。

Permission BoundaryはOrganizationsの管理者がSCPとともに使うことが多く、開発者が自分でIAMロールを作成できる環境でよく登場する。「このロールは最大でもS3しか触れないように制限する」といったガードレールをBoundaryで実装し、開発者はその範囲内でIAMポリシーを自由に設定できる、という設計だ。セキュリティチームが全体のガードレールを管理しながら、開発チームに一定の自由度を与えるための仕組みとして使われる。

Permission BoundaryはIAMユーザーとIAMロールにのみ設定できる。IAMグループには設定できない点にも注意が必要だ。

どう解くか

Permission Boundaryを正しく理解するには、「必要な権限をIdentityベースポリシーで付与し、Boundaryはその上限を定義する」という分離を意識することが重要だ。

Boundaryを設定してもアクセスできない場合、確認すべきは2点だ。

  1. Identityベースポリシーに該当のAction + Resourceが含まれているか
  2. Boundaryに該当のAction + Resourceが含まれているか

両方に含まれていれば実行できる。どちらか一方でも欠けていれば実行できない。

# Boundaryが設定されているロールの確認
aws iam get-role --role-name MyRole \
  --query 'Role.PermissionsBoundary'

# Boundaryに設定されているポリシードキュメントの確認
aws iam get-policy-version \
  --policy-arn arn:aws:iam::123456789012:policy/MyBoundaryPolicy \
  --version-id v1

IAM Policy Simulatorは、Boundaryが設定されたロールのシミュレーションにも対応している。ロールを選択してシミュレーションすると、BoundaryとIdentityベースポリシーの積の結果がEvalDecisionに反映される。

iam:SimulatePrincipalPolicyを使ってCLIでシミュレーションする場合も、Boundary設定済みのロールのARNを--policy-source-arnに指定すれば、Boundary込みの評価結果が得られる。

Boundaryを設定する際のよくある落とし穴として、BoundaryポリシーにLambdaが使うsts:AssumeRoleiam:PassRoleが含まれていない場合に、ロールの引き受けや他のロールへの委任ができなくなる点がある。アプリケーションがLambdaやECSのタスクロールを使う構成では、これらのActionがBoundaryに含まれているかも確認が必要だ。

パターン5 対処法

  • Permission Boundaryは「上限(ガードレール)」であり、それ自体はアクセスを許可しない
  • 実際に許可される権限 = Boundary ∩ Identity-based Policy (両方でAllowされていることが必要)
  • BoundaryとIdentityベースポリシーの両方に必要なAction + Resourceが含まれているか個別に確認する
  • IAM Policy SimulatorはBoundaryが設定されたロールも含めてシミュレーションできる
  • BoundaryポリシーにLambda/ECS等で必要な sts:AssumeRole / iam:PassRole が含まれているか確認する

6. 高度パターン — NotAction / NotResource / policy variables

この章では、Allow を列挙し続ける設計から一歩踏み出すための高度なポリシー記法を学ぶ。NotActionNotResource・policy variables の 3 つを活用すると、「許可するアクションを列挙する」アプローチの限界を超え、シンプルかつメンテナンスしやすいポリシーを書けるようになる。


6-1. NotAction の使いどころ

NotAction とは何か

通常の Action フィールドは「許可(または拒否)する API アクション名のリスト」を列挙する。例えば "Action": ["s3:GetObject", "s3:PutObject"] と書けば、その 2 つのアクションだけが対象になる。

これに対して NotAction「ここに列挙したアクション以外を対象にする」 という反転記法だ。

{
  "Version": "2012-10-17",
  "Statement": [
 {
"Sid": "DenyAllExceptS3ReadWrite",
"Effect": "Deny",
"NotAction": [
  "s3:GetObject",
  "s3:PutObject",
  "s3:DeleteObject",
  "s3:ListBucket"
],
"Resource": "*"
 }
  ]
}

このポリシーは「S3 の読み書き削除・一覧以外の 全アクション を Deny する」という意味になる。許可したい S3 アクションは Identity Policy や Resource Policy で別途 Allow する必要がある点に注意してほしい。

管理アカウント保護への応用

AWS Organizations の管理アカウント(Payer Account)では、CloudTrail・Config・AWS Organizations 管理アクション以外を極力実行させたくない場合がある。このようなシナリオで NotAction は特に有効だ。

{
  "Version": "2012-10-17",
  "Statement": [
 {
"Sid": "ProtectManagementAccount",
"Effect": "Deny",
"NotAction": [
  "iam:CreateVirtualMFADevice",
  "iam:EnableMFADevice",
  "iam:GetUser",
  "iam:ListMFADevices",
  "iam:ListVirtualMFADevices",
  "iam:ResyncMFADevice",
  "sts:GetSessionToken",
  "cloudtrail:*",
  "config:*",
  "organizations:*"
],
"Resource": "*",
"Condition": {
  "StringEquals": {
 "aws:PrincipalType": "IAMUser"
  }
}
 }
  ]
}

このパターンは SCP(Service Control Policy)でよく使われる。「許可リストを列挙するより禁止リストを列挙した方が短くなる」ケースで威力を発揮する。

NotAction 使用時の注意点

Effect: Allow と組み合わせると「その他全部を Allow する」になり、意図せず権限過剰になる危険がある。基本的には Effect: Deny と組み合わせて使うのが安全だ。

Effect: Allow + NotAction を使う正当なユースケースは非常に限られている。IAM ユーザーが自分のパスワードと MFA のみ管理できるポリシーを書く場合などが典型例だ。

{
  "Version": "2012-10-17",
  "Statement": [
 {
"Sid": "AllowSelfServiceCredentials",
"Effect": "Allow",
"Action": [
  "iam:ChangePassword",
  "iam:GetAccountPasswordPolicy",
  "iam:GetUser",
  "iam:ListMFADevices",
  "iam:CreateVirtualMFADevice",
  "iam:EnableMFADevice",
  "iam:DeactivateMFADevice",
  "iam:ResyncMFADevice"
],
"Resource": "arn:aws:iam::*:user/${aws:username}"
 }
  ]
}

NotAction はアクション名の プレフィックスワイルドカードs3:* 形式)も有効だ。"NotAction": ["s3:*"] と書けば「S3 以外の全アクション」が対象になる。迷った場合は Effect: Deny + NotAction の組み合わせを選ぼう。


6-2. NotResource の使いどころ

NotResource とは何か

Resource フィールドが「対象とするリソースの ARN リスト」を指定するのに対し、NotResource「ここに列挙したリソース以外を対象にする」 という反転記法だ。

{
  "Version": "2012-10-17",
  "Statement": [
 {
"Sid": "DenyAccessOutsidePublicBucket",
"Effect": "Deny",
"Action": "s3:*",
"NotResource": [
  "arn:aws:s3:::company-public-assets",
  "arn:aws:s3:::company-public-assets/*"
]
 }
  ]
}

このポリシーは「company-public-assets バケット以外の S3 リソースへのアクセスを全て Deny する」という意味になる。

典型的な使いどころ

NotResource が特に役立つのは、「特定のリソースグループだけにアクセスを制限したいが、そのリソース ARN パターンが複雑すぎてワイルドカードで表現しにくい」場合だ。

例えば、開発者に本番環境のバケットを触らせたくない場合を考えよう。本番バケットの命名規則が prod-* であれば次のように書ける。

{
  "Version": "2012-10-17",
  "Statement": [
 {
"Sid": "DenyProductionS3Access",
"Effect": "Deny",
"Action": [
  "s3:GetObject",
  "s3:PutObject",
  "s3:DeleteObject"
],
"NotResource": [
  "arn:aws:s3:::dev-*",
  "arn:aws:s3:::dev-*/*",
  "arn:aws:s3:::staging-*",
  "arn:aws:s3:::staging-*/*"
]
 }
  ]
}

このポリシーは「dev- または staging- で始まるバケット以外への S3 操作を Deny する」、つまり命名規則 prod-* の本番バケットへのアクセスを遮断する。本番バケットが増えるたびにポリシーを変更する必要がなく、命名規則を守る運用を前提にした柔軟な設計だ。

NotResource 使用時の注意点

NotResourceEffect: Allow の組み合わせは「そのリソース以外への全アクセスを許可する」という広範な権限になるため、ほぼ使うべきではないEffect: Deny + NotResource の組み合わせでのみ安全に使用できる。

S3 バケットを指定する際は「バケット ARN 本体」と「バケット内オブジェクトの ARN(/* 付き)」の両方を列挙し忘れると、バケット自体へのアクセスや ListBucket 操作が制限されないケースがある。必ずペアで書くことを習慣づけてほしい。


6-3. policy variables の活用

policy variables とは何か

IAM ポリシーの ResourceCondition フィールドには、実行時に動的に解決される プレースホルダー変数 を埋め込める。これを「policy variables(ポリシー変数)」と呼ぶ。

代表的な変数:

変数解決される値
${aws:username}IAM ユーザー名
${aws:userid}IAM ユーザー ID(AIDAXXXXXXXXXX 形式)
${aws:principaltype}プリンシパルの種別(User / AssumedRole / FederatedUser)
${aws:PrincipalTag/tag-key}プリンシパルに付与されたタグの値
${s3:prefix}S3 ListBucket リクエストのプレフィックス
${iam:ResourceTag/tag-key}リソースに付与されたタグの値

変数が解決できない状況(フェデレーテッドユーザーで aws:username を参照するなど)では、変数部分がリテラル文字列として扱われるため意図せずアクセス拒否が発生する。policy variables を使う場合は必ず "Version": "2012-10-17" を宣言すること(2008-10-17 は変数展開に対応していない)。

実例: IAMユーザー自身のフォルダーのみアクセス許可

S3 バケット内に「ユーザー名と同名のフォルダー」を作成し、各ユーザーが自分のフォルダーにしかアクセスできないポリシーは、policy variables を使うと 1 つのポリシーで全ユーザーに適用できる。

{
  "Version": "2012-10-17",
  "Statement": [
 {
"Sid": "AllowListingOwnFolder",
"Effect": "Allow",
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::company-user-storage",
"Condition": {
  "StringLike": {
 "s3:prefix": [
"",
"home/",
"home/${aws:username}/",
"home/${aws:username}/*"
 ]
  }
}
 },
 {
"Sid": "AllowReadWriteOwnFolder",
"Effect": "Allow",
"Action": [
  "s3:GetObject",
  "s3:PutObject",
  "s3:DeleteObject"
],
"Resource": "arn:aws:s3:::company-user-storage/home/${aws:username}/*"
 }
  ]
}

このポリシーを全 IAM ユーザーに適用すると、Alice は home/alice/ 以下のみ、Bob は home/bob/ 以下のみにアクセスできるようになる。ユーザーを追加するたびにポリシーを変更する必要がなく、管理コストが大幅に下がる。

${aws:username} は IAM ユーザーにのみ有効だ。IAM ロール(AssumedRole)の場合は ${aws:userid}${aws:PrincipalArn} を使う必要がある。

PrincipalTag 変数による ABAC(属性ベースアクセス制御)

${aws:PrincipalTag/Department} のように、IAM ユーザーやロールに付与したタグの値を変数として使える。これにより、タグベースのアクセス制御(ABAC)を実装できる。

{
  "Version": "2012-10-17",
  "Statement": [
 {
"Sid": "AccessOwnDepartmentEC2",
"Effect": "Allow",
"Action": [
  "ec2:StartInstances",
  "ec2:StopInstances",
  "ec2:DescribeInstances"
],
"Resource": "*",
"Condition": {
  "StringEquals": {
 "ec2:ResourceTag/Department": "${aws:PrincipalTag/Department}"
  }
}
 }
  ]
}

IAM ユーザーのタグ Department=engineering と EC2 インスタンスのタグ Department=engineering が一致する場合のみ操作を許可する。組織の拡大に伴ってリソースやユーザーが増えても、ポリシー自体を変更せずにアクセス制御を維持できる。これが ABAC(Attribute-Based Access Control)の強力な点だ。

ABAC を本格導入する際は IAM Identity Center(旧 AWS SSO)と組み合わせるとさらに効果的だ。フェデレーション属性(部署・職種・チーム)をそのまま IAM ポリシー変数に反映できるため、人事異動時のアクセス権更新が自動化される。


7. アンチパターン演習 — 壊れたJSONを直そう

この章では、現場でよく見かける「壊れたポリシー JSON」を題材に、何が問題でどう修正すべきかを演習形式で学ぶ。能動的に考えることで、§2〜§6 の知識が定着する。

演習の進め方

  1. 「壊れた JSON(Before)」を見て、何が問題か自分で考える
  2. 「何が問題か」セクションを読んで答え合わせ
  3. 「修正後 JSON(After)」で正解を確認する

問題 1: Resource: “*” の過剰権限

壊れた JSON(Before)

{
  "Version": "2012-10-17",
  "Statement": [
 {
"Sid": "LambdaExecution",
"Effect": "Allow",
"Action": [
  "logs:CreateLogGroup",
  "logs:CreateLogStream",
  "logs:PutLogEvents",
  "s3:GetObject"
],
"Resource": "*"
 }
  ]
}

このポリシーは Lambda 関数の実行ロールに付与されており、CloudWatch Logs への書き込みと S3 からのオブジェクト取得を意図している。

何が問題か

Resource: "*" を使うことで、以下の問題が発生している。

  1. S3 の過剰権限: s3:GetObjectResource: "*" を指定すると、AWS アカウント内の 全バケット・全オブジェクト からの読み取りが許可される。Lambda 関数がアクセスすべき S3 バケットは特定のバケットのみのはずだ。
  2. セキュリティリスク: この Lambda 関数が不正利用された場合、機密データを含む全バケットへのアクセスが可能になる。
  3. 最小権限原則の逸脱: AWS では「タスクに必要な最小限のアクセス権限のみを付与する」ことを推奨している。

修正後 JSON(After)

{
  "Version": "2012-10-17",
  "Statement": [
 {
"Sid": "CloudWatchLogsWrite",
"Effect": "Allow",
"Action": [
  "logs:CreateLogGroup",
  "logs:CreateLogStream",
  "logs:PutLogEvents"
],
"Resource": "arn:aws:logs:ap-northeast-1:123456789012:log-group:/aws/lambda/my-function:*"
 },
 {
"Sid": "S3ReadSpecificBucket",
"Effect": "Allow",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::my-data-bucket/*"
 }
  ]
}

修正のポイント:

  • CloudWatch Logs の権限は この Lambda 関数のロググループ ARN に限定する
  • S3 の権限は アクセスすべきバケットの ARN に限定する
  • Statement を分割することで、各権限の意図を明確化する

問題 2: 暗黙 Deny で動かないポリシー

壊れた JSON(Before)

{
  "Version": "2012-10-17",
  "Statement": [
 {
"Sid": "DenyDeleteS3Objects",
"Effect": "Deny",
"Action": "s3:DeleteObject",
"Resource": "arn:aws:s3:::production-data/*"
 }
  ]
}

このポリシーは「本番バケットからのオブジェクト削除を禁止する」ことを意図して IAM ユーザーに付与されている。しかしこのポリシーだけでは、ユーザーは S3 に対して何もできない。

何が問題か

Allow がなければ暗黙の Deny」がデフォルトだ。このポリシーには Deny しか含まれていない。そのため:

  • s3:DeleteObject → 明示 Deny によりブロック(意図通り)
  • s3:GetObject → Allow がないため 暗黙 Deny でブロック(意図せずブロック)
  • s3:ListBucket → Allow がないため 暗黙 Deny でブロック(意図せずブロック)
  • その他の全 S3 操作 → 同様にブロック

「Deny を書けば、それ以外は許可される」という誤解が典型的な罠だ。

修正後 JSON(After)

{
  "Version": "2012-10-17",
  "Statement": [
 {
"Sid": "AllowS3ReadAndList",
"Effect": "Allow",
"Action": [
  "s3:GetObject",
  "s3:GetObjectVersion",
  "s3:ListBucket",
  "s3:ListBucketVersions",
  "s3:GetBucketLocation"
],
"Resource": [
  "arn:aws:s3:::production-data",
  "arn:aws:s3:::production-data/*"
]
 },
 {
"Sid": "DenyDeleteS3Objects",
"Effect": "Deny",
"Action": [
  "s3:DeleteObject",
  "s3:DeleteObjectVersion"
],
"Resource": "arn:aws:s3:::production-data/*"
 }
  ]
}

修正のポイント:

  • 許可したいアクション(読み取り・一覧)を 明示的に Allow する Statement を追加する
  • 禁止したいアクション(削除)は 明示 Deny で上書きする
  • ListBucket はバケット ARN に、GetObject / DeleteObject はオブジェクト ARN(/* 付き)に適用する

問題 3: Condition の記法ミス(JSON キー重複)

壊れた JSON(Before)

{
  "Version": "2012-10-17",
  "Statement": [
 {
"Sid": "AllowOnlyFromOfficeIP",
"Effect": "Allow",
"Action": "s3:*",
"Resource": "*",
"Condition": {
  "IpAddress": {
 "aws:SourceIp": "203.0.113.0/24",
 "aws:SourceIp": "198.51.100.0/24"
  }
}
 }
  ]
}

このポリシーは「オフィスの IP アドレス 2 つからのみ S3 へのアクセスを許可する」ことを意図している。

何が問題か

2 つの問題がある。

  1. JSON キーの重複: "aws:SourceIp" キーが同じオブジェクト内に 2 回出現している。これは 無効な JSON だ。後から書いたキー(198.51.100.0/24)だけが有効になり、最初の IP(203.0.113.0/24)は無視される
  2. 単一 IP のみ有効: 結果として、198.51.100.0/24 からのアクセスのみ許可され、もう一方のオフィス IP からアクセスできない障害が発生する。

修正後 JSON(After)

{
  "Version": "2012-10-17",
  "Statement": [
 {
"Sid": "AllowOnlyFromOfficeIP",
"Effect": "Allow",
"Action": "s3:*",
"Resource": "*",
"Condition": {
  "IpAddress": {
 "aws:SourceIp": [
"203.0.113.0/24",
"198.51.100.0/24"
 ]
  }
}
 }
  ]
}

修正のポイント:

  • 同一キーに複数の値を指定するには 配列形式 [...] を使う
  • Condition 内で同じキーを持つ値を複数指定する場合は常に配列にまとめる
  • Condition 演算子では、配列内の値は OR 条件(いずれか一致)として評価される

問題 4: バケット ARN の不完全な指定

壊れた JSON(Before)

{
  "Version": "2012-10-17",
  "Statement": [
 {
"Sid": "S3FullAccess",
"Effect": "Allow",
"Action": "s3:*",
"Resource": "arn:aws:s3:::my-bucket"
 }
  ]
}

何が問題か

S3 の ARN には 2 種類ある。

  • バケット自体: arn:aws:s3:::my-buckets3:ListBucket 等のバケットレベル操作に適用)
  • バケット内のオブジェクト: arn:aws:s3:::my-bucket/*s3:GetObject 等のオブジェクトレベル操作に適用)

arn:aws:s3:::my-bucket だけを指定した場合、s3:GetObjects3:PutObject などのオブジェクト操作は ARN が一致しないためアクセス拒否される。「バケットへのフルアクセスを与えたはずなのにファイルが取得できない」という事象は、このパターンが原因であることがほとんどだ。

修正後 JSON(After)

{
  "Version": "2012-10-17",
  "Statement": [
 {
"Sid": "S3FullAccess",
"Effect": "Allow",
"Action": "s3:*",
"Resource": [
  "arn:aws:s3:::my-bucket",
  "arn:aws:s3:::my-bucket/*"
]
 }
  ]
}

修正のポイント:

  • S3 の権限を付与する際は バケット ARNオブジェクト ARN(/* 付き) を必ずセットで指定する
  • ListBucket はバケット ARN のみに適用され、GetObject / PutObject / DeleteObject はオブジェクト ARN に適用される

問題 5: VPC Condition の誤用

壊れた JSON(Before)

{
  "Version": "2012-10-17",
  "Statement": [
 {
"Sid": "AllowS3FromVPC",
"Effect": "Allow",
"Action": [
  "s3:GetObject",
  "s3:PutObject"
],
"Resource": "arn:aws:s3:::app-data/*",
"Condition": {
  "StringEquals": {
 "aws:SourceVpc": "vpc-0123456789abcdef0"
  }
}
 }
  ]
}

このポリシーは「VPC 内のリソース(EC2、Lambda 等)から S3 バケットへのアクセスを許可する」ことを意図している。

何が問題か

aws:SourceVpc 条件キーは、リクエストが VPC エンドポイント を経由した場合にのみ有効になる。VPC エンドポイントを設定していない環境では、aws:SourceVpc が設定されないため Condition が成立せず、アクセスが拒否される

Lambda 関数が VPC 内にあっても VPC エンドポイントを経由しない場合は aws:SourceVpc は設定されない点に注意してほしい。「VPC 内の Lambda なのに S3 に書けない」という事象がこのパターンで起きる。

修正後 JSON(After)

VPC エンドポイントを設定している場合:

{
  "Version": "2012-10-17",
  "Statement": [
 {
"Sid": "AllowS3FromVPCEndpoint",
"Effect": "Allow",
"Action": [
  "s3:GetObject",
  "s3:PutObject"
],
"Resource": "arn:aws:s3:::app-data/*",
"Condition": {
  "StringEquals": {
 "aws:sourceVpce": "vpce-0123456789abcdef0"
  }
}
 }
  ]
}

VPC エンドポイントを設定していない場合は、Condition ブロック自体を削除してアクセスを許可する。

修正のポイント:

  • aws:SourceVpc は VPC エンドポイント経由のリクエストにのみ設定される。インターネット経由では設定されない
  • VPC エンドポイント ID による制御には aws:sourceVpce を使う
  • Condition を加える前に「この条件キーはいつ設定されるのか」を AWS ドキュメントで必ず確認する

演習を終えて

5 問を通じて「壊れた JSON を見た瞬間に問題を診断する」力が身に付いたはずだ。実務では以下のフローで確認する習慣を持つと効果的だ。

  1. Resource: "*" を見たら「本当に全リソースに適用すべきか?」と自問する
  2. Deny だけのポリシーを見たら「Allow がどこにあるか?」を確認する
  3. Condition の同一キーを見たら「配列になっているか?」をチェックする
  4. S3 の ARN を見たら「バケットとオブジェクトの両方が指定されているか?」を確認する
  5. VPC 系の Condition を見たら「VPC エンドポイントが設定されているか?」を確認する

演習の解答・解説を確認する


8. まとめ + 次のステップ

8-1. 本記事の学習ポイント再掲

本記事では IAM ポリシー設計の基礎から実践まで、4 本柱で学習を進めてきた。

評価ロジックの理解

IAM の評価は「暗黙 Deny → 明示 Deny 優先 → 4 種ポリシーの順序」で行われる。「Allow を書いたのに動かない」「Deny してないのに弾かれる」という迷子状態から脱却するには、この評価順序を体に染み込ませることが近道だ。評価ロジックの 3 つの鉄則——明示的 Deny が最強・暗黙 Deny がデフォルト・SCP はガードレール——を常に意識して設計してほしい。

必要権限の特定法(山場)

「最小権限が大事と分かっているが、何を許可すれば良いか分からない」という悩みには、以下の 3 ツールセットを組み合わせる。

ツール粒度主な用途
IAM Access Advisorサービスレベル未使用サービスを削除候補に絞り込む
AWS CloudTrailAPI アクションレベル使用済み API を正確に特定する
IAM Access Analyzer(ポリシー生成)API アクションレベル(自動)CloudTrail ログから最小権限ポリシーを自動生成

3 ツールは排他ではなく組み合わせて使う。Access Advisor で不要サービスを特定し、CloudTrail でアクション・リソースを逆算し、Access Analyzer で生成してレビューするという順序が実務での標準フローだ。

詰まりポイントの事前回避

初心者が陥りがちな 5 大パターンを事前に把握することで、試行錯誤の時間を大幅に削減できる。

パターン症状対処法
Resource: * の過剰権限広範なアクセス権限が付与されるリソース ARN を明示する
Action: * の全権限付与全 API 操作が許可される必要なアクションのみ列挙する
暗黙 Deny の誤解Allow がないのにアクセス可能だと思い込むAllow を明示する
Condition 記法ミス複数値が OR 評価されず片方しか効かない配列形式で記述する
Permission Boundary の混同Boundary 外のアクションが意図せずブロックされるBoundary と Identity Policy の積集合を意識する

アンチパターン演習

§7 の 5 問を通じて能動的に「壊れた JSON を直す」経験を積んだ。問題を見た瞬間に「これは Condition の配列化が必要」「暗黙 Deny の誤解パターン」と診断できるようになることが目標だ。


8-2. IAMポリシー設計の次のステップ

IAM ポリシーの基礎を理解した次は、以下のトピックに進むことで、より堅牢な組織全体のアクセス管理が実現できる。

IAM Identity Center + Permission Sets + ABAC

AWS SSO(IAM Identity Center)を使うと、複数の AWS アカウントへのアクセスを一元管理できる。Permission Sets でポリシーを束ね、ABAC(属性ベースアクセス制御)でタグによる動的なアクセス制御を実現する。個人ごとに IAM ユーザーを管理する手間がなくなり、人事異動時の権限変更も一元化される。§6-3 で学んだ ${aws:PrincipalTag/...} 変数を本番環境で活用するための土台だ。

Service Control Policies(SCP)

AWS Organizations の SCP を使うと、組織全体にガードレールを設けられる。「本番アカウントでは特定リージョン以外を使用禁止」「管理者以外による IAM 設定変更を禁止」などの制約を組織レベルで強制できる。SCP は Identity Policy と積集合で評価されるため、誤って広範な権限が付与されても SCP でブロックできる安全網として機能する。

AWS Config + IAM Access Analyzer による継続監査

一度ポリシーを設計した後も、権限のクリープ(じわじわと広がる権限)は起こる。AWS Config Rules と IAM Access Analyzer を組み合わせた継続的な監査の仕組みを整えることで、セキュリティ品質を維持できる。Access Analyzer は外部エンティティからアクセス可能なリソースを自動検出し、意図しない公開リソースを発見するのに特に有効だ。


8-3. 関連記事リンク

IAM Identity Center と Permission Sets・ABAC については、以下の記事で詳しく解説している。組織全体のアクセス管理を一元化したい場合は合わせて参照してほしい。

IAM Identity Center + Permission Sets + ABAC で組織全体のアクセス管理を一元化する


8-4. IAMポリシーを書く前に確認するチェックリスト

これまでの章で学んだ内容を、ポリシーを書く前に確認できるチェックリストとしてまとめる。

リソース指定

  • Resource: "*" を使っていないか。S3 はバケット ARN とオブジェクト ARN をセットで指定したか
  • 特定の IAM ロール・関数名・テーブル名が ARN に含まれているか
  • ワイルドカード(*)を使う場合、スコープが意図した範囲に限定されているか

アクション指定

  • Action: "*"s3:* など、使わないアクションが含まれていないか
  • IAM Access Advisor で「最後に使用したサービス」から不要なアクションを除外できないか
  • 書き込み系アクション(Put / Create / Delete / Update)は特に意図を確認したか

Condition の記法

  • 同一キーに複数の値を指定する場合、配列形式で記述しているか
  • StringEqualsStringLike の使い分けは正しいか(ワイルドカードは StringLike
  • 条件キーがそのサービス・シナリオで実際に設定されるか(VPC エンドポイント等)を確認したか

評価ロジックの確認

  • SCPや Permission Boundary が存在する環境か把握しているか
  • クロスアカウントアクセスの場合、両アカウント側のポリシーを確認したか
  • iam:SimulatePrincipalPolicy で本番適用前に動作確認したか

8-5. IAMポリシー設計パターン全体図

fig04: IAMポリシー設計パターン全体図


次のステップ

  • IAM Identity Center + Permission Sets + ABACで組織全体のアクセス管理を一元化する
  • Service Control Policies (SCP) で組織全体にガードレールを設ける
  • AWS Config + IAM Access Analyzerで継続的なポリシー監査を自動化する


IAM入門 Vol2: 複数アカウント時代のIAM設計 — Organizations × Identity Center × SCP → 読む


IAM入門 Vol3: 権限棚卸し自動化と継続運用 — Access Analyzer × Lambda × Terraform → 読む