IAM Access AnalyzerのカスタムポリシーチェックをCI/CDで活用する際の設計と工夫

IAM Access AnalyzerのカスタムポリシーチェックをCI/CDで活用する際の設計と工夫

2025.12.25

こんにちは。サービス開発室の武田です。

IAM Access Analyzerのカスタムポリシーチェック機能を使ったことはあります?DevelopersIOでも紹介されている機能のひとつです。

これらのAPIを実際にCI/CDパイプラインに組み込んで運用してみたので、そのときに工夫した点も含めて紹介します。

この記事で書くこと

  • CheckAccessNotGrantedとCheckNoNewAccessを両方使う理由
  • AWS公式のGitHub Actionsではなくboto3で自前実装した理由
  • 実運用で発生する誤検知への対処方法(抑制機能)
  • CheckNoNewAccessで何が追加されたかを可視化する工夫
  • GitHub Actionsでの自動化とPRコメントへのレポート出力

この記事で書かないこと

  • APIの基本的な使い方(既存記事を参照してください)

カスタムポリシーチェックAPIの概要

まずIAM Access Analyzerが提供するAPIを整理しておきます。IAMポリシーを検証するためのAPIは4つあります。

API 用途 対象 料金
CheckAccessNotGranted 指定したアクションが許可されていないことを確認 アイデンティティ/リソース $0.002/回
CheckNoNewAccess 既存ポリシーと比較して新しい権限が追加されていないことを確認 アイデンティティ/リソース $0.002/回
CheckNoPublicAccess リソースポリシーがパブリックアクセスを許可していないことを確認 リソースのみ $0.002/回
ValidatePolicy ポリシーの文法とベストプラクティスを検証 アイデンティティ/リソース 無料

今回はIAMポリシー(アイデンティティポリシー)のCI/CDチェックに適したCheckAccessNotGrantedとCheckNoNewAccessを中心に扱います。CheckNoPublicAccessはS3バケットポリシーなどのリソースポリシー向けなので対象外とし、ValidatePolicyは補助的に使用します。

なぜCheckAccessNotGrantedとCheckNoNewAccessを両方使うのか

CheckAccessNotGrantedとCheckNoNewAccessは、片方だけでは足りません。

CheckAccessNotGrantedだけの場合

CheckAccessNotGrantedは、禁止リストに含まれないアクションの追加は検出できません。致命的な権限の追加を見逃してしまう可能性があります。

例:禁止リストに「s3:GetObject」があるとして...
- s3:GetObject の追加 → ✅ 検出できる
- ec2:RunInstances の追加 → ❌ 検出できない(リストにないため)

CheckNoNewAccessだけの場合

CheckNoNewAccessは、既存ポリシーにすでに含まれている問題を検出できません。既存ポリシーとの「差分」しか見ないため、もともと問題があるポリシーはスルーされてしまいます。

両方使って補完する

観点 CheckAccessNotGranted CheckNoNewAccess
禁止アクションの使用を検出 ❌(禁止リストの概念なし)
既存ポリシーの問題を検出 ❌(差分しか見ない)
リスト外のアクション追加を検出

というわけで、両方使うことでお互いの弱点を補えます。

なぜboto3で自前実装したか

AWSはカスタムポリシーチェックをCI/CDに組み込むための公式ツールを提供しています。

このActionはCloudFormationテンプレートやTerraformプランからIAMポリシーを抽出し、IAM Access Analyzerでチェックしてくれます。便利そうですね。

ユースケースに合わなかった

ただ私たちのプロジェクトでは、スタンドアローンのJSONポリシーファイルをリポジトリで管理し、Lambdaから動的にIAMロールへアタッチする構成を取っていました。

repository/
├── policies/
│   ├── AdminPolicy.json            # スタンドアローンのポリシーファイル
│   ├── DeveloperPolicy.json
│   └── DenyListPolicy.json         # Denyのみのポリシー
└── lambda/
    └── create_role.py              # ポリシーを読み込んでロールにアタッチ

AWS公式のActionだと次の点で合いませんでした。

観点 AWS公式Action 私たちの要件
入力形式 CloudFormation/Terraform スタンドアローンJSON
禁止アクションリスト 静的に指定 別のポリシーファイルから動的に読み込みたい
抑制機能 リソース名単位 アクション+リソースパターンで細かく抑制したい
レポート形式 固定 PRコメントにカスタム形式で出力したい

そのためboto3で自前実装する方針としました。以降では実装時の工夫した点をまとめていきます。

実運用での工夫1:許容ケースの抑制

困ったこと

CheckAccessNotGrantedの禁止リストは アクション単位 でしか指定できません。そのため、特定のリソースに限定して許可したいケースでも、アクション自体が禁止リストに含まれていればFAILになります。

たとえばssm:GetParameterを禁止アクションに含めているとしましょう。でもAWSサービスが提供するパブリックパラメーター(/aws/service/配下)へのアクセスは許可したいケースがありますよね。

{
  "Effect": "Allow",
  "Action": "ssm:GetParameter",
  "Resource": "arn:aws:ssm:*:*:parameter/aws/service/*"
}

これは顧客データへのアクセスではないので、許容したいところです。

解決策:抑制設定ファイルを作る

そこでアクションとリソースパターンの組み合わせで「許容するケース」を定義できるようにしました。

{
  "suppressed_actions": [
    {
      "action": "ssm:GetParameter",
      "resource_pattern": "arn:aws:ssm:*:*:parameter/aws/service/*",
      "reason": "AWSサービス提供のパブリックパラメータのみ参照可能"
    }
  ]
}

チェックスクリプト側でFAILしたアクションがこの設定にマッチするかを確認します。マッチすれば「抑制済み」として警告から除外するしくみです。

def is_action_suppressed(action: str, resource: str, suppression_rules: list) -> bool:
    """アクションが抑制対象かどうかを判定"""
    for rule in suppression_rules:
        # アクションがマッチするか(ワイルドカード対応)
        if not fnmatch.fnmatch(action, rule.action):
            continue
        # リソースがパターンにマッチするか
        if fnmatch.fnmatch(resource, rule.resource_pattern):
            return True
    return False

レポート出力では「抑制された件数」も表示して、透明性を確保しています。

**Summary**: ✅ 22 passed, ⚠️ 1 failed, 🔇 3 suppressed

実運用での工夫2:追加されたアクションの可視化

困ったこと

CheckNoNewAccess APIは結果として「PASS」または「FAIL」と、FAILの場合は「どのStatementが原因か」を返します。でも具体的にどのアクションが追加されたかは教えてくれません。

{
  "result": "FAIL",
  "message": "The modified permissions grant new access compared to your existing policy.",
  "reasons": [
    {
      "description": "...",
      "statementIndex": 0
    }
  ]
}

PRレビューで「FAILしたけど、何が追加されたの?」と毎回確認するのは手間です。

解決策:自前で差分を計算

既存ポリシーと新ポリシーからAllowステートメントのアクションを抽出し、差分を取ります。

def get_added_actions(existing_policy: str, new_policy: str) -> list[str]:
    """既存ポリシーと新ポリシーを比較し、追加されたアクションを返す"""
    existing_actions = extract_actions_from_policy(existing_policy)
    new_actions = extract_actions_from_policy(new_policy)
    added = new_actions - existing_actions
    return sorted(added)

レポートでは次のように表示します。

- `path/to/policy.json`
  - The modified permissions grant new access compared to your existing policy.
  - **追加されたアクション**: `ec2:DescribeInstances`, `ec2:DescribeSecurityGroups`

これによりレビュアーは一目で「何が追加されたか」を把握できます。

GitHub Actionsでの自動化

PRでIAMポリシーファイルが変更されたときにチェックを実行し、結果をPRコメントとして投稿します。

name: IAM Policy Check

on:
  pull_request:
    paths:
      - 'path/to/policies/**/*.json'

permissions:
  id-token: write
  contents: read
  pull-requests: write

jobs:
  check-iam-policies:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0  # CheckNoNewAccessでmainとの比較に必要

      - name: Fetch base branch
        run: git fetch origin ${{ github.base_ref }}

      - uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/github-actions-role
          aws-region: ap-northeast-1

      - uses: actions/setup-python@v5
        with:
          python-version: '3.13'

      - name: Install dependencies
        run: pip install boto3

      - name: Run IAM Policy Check
        continue-on-error: true  # FAILしてもCIを止めない(情報提供目的)
        env:
          BASE_REF: ${{ github.base_ref }}
        run: python scripts/check_iam_policies.py > results.md

      - name: Comment on PR
        uses: actions/github-script@v7
        with:
          script: |
            const fs = require('fs');
            const results = fs.readFileSync('results.md', 'utf8');

            // 既存コメントを更新、なければ新規作成
            const { data: comments } = await github.rest.issues.listComments({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
            });

            const botComment = comments.find(comment =>
              comment.user.type === 'Bot' &&
              comment.body.includes('IAM Access Analyzer Check Results')
            );

            if (botComment) {
              await github.rest.issues.updateComment({
                comment_id: botComment.id,
                owner: context.repo.owner,
                repo: context.repo.repo,
                body: results
              });
            } else {
              await github.rest.issues.createComment({
                issue_number: context.issue.number,
                owner: context.repo.owner,
                repo: context.repo.repo,
                body: results
              });
            }

ポイント

  • fetch-depth: 0
    • CheckNoNewAccessでmainブランチのポリシーと比較するために必要
  • continue-on-error: true
    • FAILしてもCIを停止しない(情報提供目的のため)
  • 既存コメントの更新
    • 同じPRで複数回チェックを実行した場合、コメントが増え続けないように更新する

レポート出力例

PRコメントとして次のようなレポートを出力します。

## 🔐 IAM Access Analyzer Check Results

### 1. CheckAccessNotGranted(禁止アクションのチェック)

**Summary**: ✅ 22 passed, ⚠️ 1 failed, 🔇 1 suppressed

<details><summary>⚠️ Failed Checks</summary>

- `policies/AdminPolicy.json`
  - The policy grants access to the restricted actions.
  - Statement[0] の問題アクション: `s3:GetObject`
  - ℹ️ DenyListPolicyでDenyされている場合は実効的に問題ありません

</details>

### 2. CheckNoNewAccess(権限拡大のチェック)

**Summary**: ✅ 20 passed, ⚠️ 1 failed, ⏭️ 2 skipped

<details><summary>⚠️ Failed Checks (New access detected)</summary>

- `policies/AdminPolicy.json`
  - The modified permissions grant new access compared to your existing policy.
  - **追加されたアクション**: `ec2:DescribeInstances`, `ec2:DescribeSecurityGroups`

</details>

### 3. ValidatePolicy(文法・ベストプラクティスチェック)

**Summary**: ✅ 23 passed, ⚠️ 0 warnings

---

> **Note**: このチェックはIAM Access Analyzerを使用しています。
> FAILの場合でもCIは停止しませんが、権限の変更内容を確認してください。

料金について

API 単価 想定利用量 月額
CheckAccessNotGranted $0.002/回 23ポリシー × 20PR $0.92
CheckNoNewAccess $0.002/回 23ポリシー × 20PR $0.92
ValidatePolicy 無料 - $0

チェックするポリシーファイルの数とPR回数で変動はしますが、おおむね月額$2程度という試算となりました。

まとめ

IAM Access AnalyzerのカスタムポリシーチェックをCI/CDに組み込む際のTipsを紹介しました。

  • AWS公式Actionsはユースケースに合わなかった
    • スタンドアローンJSON + カスタム要件には自前実装が必要
  • CheckAccessNotGrantedとCheckNoNewAccessは両方使う
    • それぞれの弱点を補完
  • 抑制機能を実装する
    • ノイズを減らし、本当に対処が必要なものにフォーカス
  • 追加アクションを可視化する
    • レビュアーの負担を軽減
  • PRコメントとして記録
    • レビューの証跡として活用

カスタムポリシーチェックはIAMポリシーのガバナンスを自動化する強力なツールです。公式ツールが合わない場合でもboto3で比較的簡単に実装できますので、ぜひ試してみてください。

参考リンク

この記事をシェアする

FacebookHatena blogX

関連記事