☁️

CloudTrailを使わずにIAMアクションの棚卸し補助を行うCLIツールを作成した話

に公開

IAMポリシーの最適化や棚卸しを行うにあたって、アクションの使用状況を把握することは重要ですが、実際の運用では判断が難しい場面が多くあります。

この記事では、CloudTrailやAccess Analyzerのリソースを利用せずに、IAMの「最終アクセス情報」APIと独自のアクションカタログを用いて、未使用とみなせるアクションを抽出するために実装したCLIツール iam-action-catalog ついて紹介します。

https://github.com/tanagumo/iam-action-catalog


1. 作成の背景

アクションの棚卸しにおける課題

  • どのアクションが使用されていないか判断しづらい
  • CloudTrailやAccess Analyzerの利用にはコストや管理・運用負荷が伴います

実際に発生した問題

あるIAMロールについて、IAMサービスへのアクセス履歴がなかったため該当ロールにアタッチしていたIAMポリシーに含まれていた iam:PassRole を削除してしまいました。
ところが、このロールの利用主体がデプロイの一環でECSタスク定義を作成しており、権限不足でタスク定義にIAMロールを指定する事ができなくなりデプロイが失敗するようになりました。

調べてみたら、PassRole は最終アクセスの追跡対象外のアクションであり、使用の有無を誤って判断していたことが分かりました(恥ずかしい)。結構あるあるのようなのですが、そもそも今まで棚卸しを行なってきていなかったので追跡対象外のアクションがある事を把握していませんでした。


2. 最初に作成したもの:カタログ

AWSのIAMアクション一覧ドキュメント(Actions, Resources, and Condition Keys)には、各アクションの追跡可否に関する情報は記載されておらず、最終アクセスの追跡対象となるアクション一覧は(IAM action last accessed information services and actions)に記載があります。

とりあえずまずは上記のドキュメント群をパースして、アクションごとに最終アクセス可能かどうかの情報ももたせたアクションのカタログを作成する事にしました。

出力例は以下の通りです。

[
  {
    "access_level": "Read",
    "condition_keys": [],
    "dependent_actions": [],
    "description": "Grants permission to retrieve metadata for a list of traces specified by ID",
    "last_accessed_trackable": false,
    "name": "BatchGetTraceSummaryById",
    "permission_only": true,
    "ref": "https://docs.aws.amazon.com/xray/latest/devguide/security_iam_id-based-policy-examples.html#security_iam_id-based-policy-examples-console",
    "resource_types": []
  },
  {
    "access_level": "Write",
    "condition_keys": [
      {
        "ref": "https://docs.aws.amazon.com/service-authorization/latest/reference/list_awsx-ray.html#awsx-ray-aws_ResourceTag___TagKey_",
        "value": "aws:ResourceTag/${TagKey}"
      }
    ],
    "dependent_actions": [],
    "description": "Grants permission to delete a group resource",
    "last_accessed_trackable": true,
    "name": "DeleteGroup",
    "permission_only": false,
    "ref": "https://docs.aws.amazon.com/xray/latest/api/API_DeleteGroup.html",
    "resource_types": [
      {
        "condition_keys": [],
        "name": "group",
        "ref": "https://docs.aws.amazon.com/service-authorization/latest/reference/list_awsx-ray.html#awsx-ray-group",
        "required": true
      }
    ]
  },...
]

last_accessed_trackable が最終アクセスが追跡されるアクションかどうかの判定用フラグです。


3. 機能拡張:未使用アクションの抽出

IAMには「最終アクセス情報」APIが存在します

IAMには GenerateServiceLastAccessedDetails, GetServiceLastAccessedDetails というAPIがあり、指定のArnからのサービス、アクションの最終アクセス情報を取得することができます。サービスによってはサービスレベルの最終アクセスしか取得できないものもあります。
また、このAPIが対応しているのは「追跡可能なアクション」のみで、全てのアクションの利用が最終アクセス日時に反映される訳ではないようです。

とにかく

  1. アクションが追跡可能
  2. GetServiceLastAccessedDetails の結果の最終アクセス日時が一定期間以上過去、ないしはアクセス履歴がない

の両方の条件を満たすアクションは 利用されていないアクション と判断できます。

しかしながら

  • 手動で逐一APIを叩いたり管理コンソールで最終アクセス情報を確認するのは面倒
  • カタログを利用する事で上記の条件チェックをプログラムで行う事が出来る

という事で、対象のIAMロール・グループ・ユーザー及び日数を指定する事で最終アクセス情報に加えて利用されていないと見なせるかどうかのフラグも含んだ情報を返却する機能を追加しました。

出力例は以下の通りです。

$ iam-action-catalog --catalog-path ./catalog.json \
    list-last-accessed-details --arn role/service-role/some-role \
    --days-from-last-accessed 146 --mask-arn --pretty --exclude-aws-managed
[
    {
        "arn": "arn:aws:iam::xxxxxxxxxxxx:role/service-role/some-role",
        "items": [
            {
                "name": "arn:aws:iam::xxxxxxxxxxxx:policy/some-role_policy",
                "kind": "attached",
                "last_accessed_details": [
                    {
                        "action_name": "BatchCheckLayerAvailability",
                        "service_namespace": "ecr",
                        "service_name": "Amazon Elastic Container Registry",
                        "granularity": "action_level",
                        "service_level_last_authenticated": "2024-12-26T07:40:55+00:00",
                        "service_level_last_authenticated_entity": "arn:aws:iam::xxxxxxxxxxxx:role/service-role/some-role",
                        "service_level_last_authenticated_region": "ap-northeast-1",
                        "action_level_last_accessed": "2024-12-26T07:40:54+00:00",
                        "action_level_last_authenticated_entity": "arn:aws:iam::xxxxxxxxxxxx:role/service-role/some-role",
                        "action_level_last_authenticated_region": "ap-northeast-1",
                        "considered_unused": true,
                        "considered_unused_reason": "This action is tracked by Access Analyzer and has not been accessed in the past 146 days.",
                        "considered_not_unused_reason": null,
                        "considered_not_unused_reason_detail": null
                    },
                    {
                        "action_name": "PutLogEvents",
                        "service_namespace": "logs",
                        "service_name": "Amazon CloudWatch Logs",
                        "granularity": "service_level",
                        "service_level_last_authenticated": "2024-12-26T07:40:55+00:00",
                        "service_level_last_authenticated_entity": "arn:aws:iam::xxxxxxxxxxxx:role/service-role/some-role",
                        "service_level_last_authenticated_region": "ap-northeast-1",
                        "action_level_last_accessed": null,
                        "action_level_last_authenticated_entity": null,
                        "action_level_last_authenticated_region": null,
                        "considered_unused": false,
                        "considered_unused_reason": null,
                        "considered_not_unused_reason": "non_trackable_action",
                        "considered_not_unused_reason_detail": "Not considered unused because the action is not trackable by Access Analyzer."
                    },
                    {
                        "action_name": "GetParameters",
                        "service_namespace": "ssm",
                        "service_name": "AWS Systems Manager",
                        "granularity": "action_level",
                        "service_level_last_authenticated": "2024-12-26T07:36:41+00:00",
                        "service_level_last_authenticated_entity": "arn:aws:iam::xxxxxxxxxxxx:role/service-role/some-role",
                        "service_level_last_authenticated_region": "ap-northeast-1",
                        "action_level_last_accessed": "2024-12-26T07:36:41+00:00",
                        "action_level_last_authenticated_entity": "arn:aws:iam::xxxxxxxxxxxx:role/service-role/some-role",
                        "action_level_last_authenticated_region": "ap-northeast-1",
                        "considered_unused": true,
                        "considered_unused_reason": "This action is tracked by Access Analyzer and has not been accessed in the past 146 days.",
                        "considered_not_unused_reason": null,
                        "considered_not_unused_reason_detail": null
                    }
                ]
            }
        ]
    }
]

considered_unusedtrue のものがアクションが使われていないと見なせる事を意味しており、上述の通りこの項目が true になるのは

  1. アクションが追跡可能
  2. GetServiceLastAccessedDetails の結果のサービス単位、アクション単位での最終アクセス日時が一定期間以上過去ないしはアクセス履歴がない

の両方の条件を満たす場合のみとなります。

一部のサービスはアクションレベルの最終アクセス情報が取得できないものもありますが、その場合はサービスに対する最終アクセス日時が実行時点を起点として指定の日数分よりも過去日の場合のみ、そのサービスのアクションで追跡可能なものは全て利用していないアクションと見なすようにしています。

従って、サービスレベルでしか最終アクセス情報が取得できないケースにおいては、あるアクションの実行によりサービスレベルの最終アクセス日時が更新されてしまい、実際には利用されていないアクションだったとしても未使用という判定は下せなくなる、ということになりますが、実際には未使用ではないものを未使用と判定してしまうというのは避けなければならないので、安全側に振った設計にしています。

そういう意味において、このツールはCloudTrailやAccess Analyzerを代替するようなものではなく、それらのサービスを利用しなくても使用していないと判断できるアクションを特定するための補助ツールという位置付けです。ですが、それらを利用せずとも利用していないと判断できるアクションはそれなりにあったりするので棚卸しの初手で利用するには一定の価値のあるツールなのではと思っています。実際に自分は最近業務でこのツールを利用して不要なアクションの特定を進めており近いうちに未使用アクションの棚卸しを行う予定です。


4. CLI構成と使用例

カタログの作成

iam-action-catalog --catalog-path ./catalog.json catalog build

カタログの表示

iam-action-catalog --catalog-path ./catalog.json catalog show --pretty

アクションの抽出(例)

$ iam-action-catalog --catalog-path ./catalog.json \
    list-last-accessed-details --arn role/service-role/some-role \
    --days-from-last-accessed 90 --mask-arn --pretty --exclude-aws-managed

短縮形式のARN(例: role/MyRole)を指定する場合は、環境変数 AWS_ACCOUNT_ID の設定が必要です。細かいオプションはREADMEに記載してあります。


5. その他のちょっとした工夫

  • 出力結果のアカウントIDをマスクする --mask-arn オプションを用意しています
  • considered_not_unused_reason はEnumで分類されており、判定理由の機械可読性とフィルタリング性を確保するようにしました
  • コマンド実行履歴にアカウントIDを残したくないというケースを一応考慮して、role/FooRole のような短縮形式のARNをサポートしています。
  • AWS管理のポリシーからはアクションを撤去できないので、出力結果からAWS管理ポリシーを除外するために --exclude-aws-managed オプションを用意しています

業務での利用や自動化への組み込みに役立てていただければ幸いです。

Discussion