AWS Step Functionsと認証情報レポートを利用し、一定期間未利用の IAM ユーザーを自動削除してみた

AWS Step Functionsと認証情報レポートを利用し、一定期間未利用の IAM ユーザーを自動削除してみた

2025.10.20

はじめに

セキュリティ対策として、長期間使用されていないIAMユーザーの認証情報を定期的に無効化することは重要です。

AWS Configルール(iam-user-unused-credentials-check)とAWS Systems Managerオートメーションを利用することで、一定期間認証情報(アクセスキー・コンソールパスワード)が未利用のIAMユーザーの認証情報を自動で無効化できます。

https://dev.classmethod.jp/articles/automation-revoke-unused-iam-user-credentials/

自動無効化の仕組みを導入後、さらに一定期間利用されていないIAMユーザーを自動削除したいという要件もあるかと思います。

本記事では、AWS Step Functionsを使用して一定期間未利用のIAMユーザーを自動削除する仕組みを構築します。

削除対象となるのは、アクセスキーとコンソールアクセスが全て無効化されているIAMユーザーです。このようなユーザーが一定期間(半年や1年)未利用の場合、自動的に削除します。また、アクセスキーやコンソールアクセスを一度も利用したことがないIAMユーザーも削除対象に含まれます。この場合、IAMユーザーが作成されて一定期間経過した際に自動削除します。

処理の流れ(概要)

AWS Step Functionsを定期的に起動し、以下の処理を行います。

  1. 認証情報レポートの生成・取得: IAMユーザーの利用状況を取得
  2. ユーザー分類: アクティブユーザーと完全に非アクティブなユーザーに分類
  3. 非アクティブ期間の計算: 各ユーザーの最終アクティビティからの経過日数を算出
  4. 削除実行: 条件を満たすユーザーをSSMオートメーションで削除
  5. 結果レポート: 処理結果をまとめて出力

システム構成は以下の通りです。

cm-hirai-screenshot 2025-08-21 13.57.10

IAMユーザー削除方法について

AWS Step FunctionsでIAMユーザーを削除する際、直接IAM APIのDeleteUserを実行せず、AWS Systems Manager(SSM)ドキュメント「AWSConfigRemediation-DeleteIAMUser」を利用したSSMオートメーションを実行します。

詳細については、以下の記事をご参照ください。

https://dev.classmethod.jp/articles/aws-stepfunctions-iam-user-delete/

SSMオートメーション用IAMロールの作成

SSMオートメーションを実行するためのIAMロールを作成します。

必要な権限は、SSMドキュメントの公式ドキュメントに記載されています。

https://docs.aws.amazon.com/ja_jp/systems-manager-automation-runbooks/latest/userguide/automation-aws-delete-iam-user.html

以下のCloudFormationテンプレートを使用してスタックを作成してください。

			
			AWSTemplateFormatVersion: '2010-09-09'
Description: 'AWS Config Remediation IAM Role for AWSConfigRemediation-DeleteIAMUser'

Parameters:
  AWSConfigRemediationDeleteIAMUserRoleName:
    Type: String
    Default: AWSConfigRemediation-DeleteIAMUserRole
    Description: Name for the IAM role used by AWSConfigRemediation-DeleteIAMUser automation

  AWSConfigRemediationDeleteIAMUserPolicyName:
    Type: String
    Default: AWSConfigRemediation-DeleteIAMUserPolicy
    Description: Name for the policy attached to the remediation role

Resources:
  AWSConfigRemediationDeleteIAMUserRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Ref AWSConfigRemediationDeleteIAMUserRoleName
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - ssm.amazonaws.com
                - states.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: !Ref AWSConfigRemediationDeleteIAMUserPolicyName
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - iam:DeactivateMFADevice
                  - iam:DeleteAccessKey
                  - iam:DeleteLoginProfile
                  - iam:DeleteServiceSpecificCredential
                  - iam:DeleteSigningCertificate
                  - iam:DeleteSSHPublicKey
                  - iam:DeleteVirtualMFADevice
                  - iam:DeleteUser
                  - iam:DeleteUserPolicy
                  - iam:DetachUserPolicy
                  - iam:GetUser
                  - iam:ListAttachedUserPolicies
                  - iam:ListAccessKeys
                  - iam:ListGroupsForUser
                  - iam:ListMFADevices
                  - iam:ListServiceSpecificCredentials
                  - iam:ListSigningCertificates
                  - iam:ListSSHPublicKeys
                  - iam:ListUserPolicies
                  - iam:ListUsers
                  - iam:RemoveUserFromGroup
                Resource: '*'
              - Effect: Allow
                Action:
                  - ssm:GetAutomationExecution
                  - ssm:StartAutomationExecution
                Resource: '*'

		

このテンプレートにより、AWSConfigRemediation-DeleteIAMUserRoleという名前のIAMロールが作成されます。

ステートマシンの作成

ステートマシン定義

以下の設定でステートマシンを作成します。アカウントIDの箇所は各自変更してください。

  • ステートマシン名: iam-inactive-user-cleanup
  • IAMロール: AWSConfigRemediation-DeleteIAMUserRole

cm-hirai-screenshot 2025-08-21 13.33.08

			
			{
  "Comment": "Identify and manage inactive IAM users",
  "QueryLanguage": "JSONata",
  "StartAt": "SetupParameters",
  "States": {
    "SetupParameters": {
      "Type": "Pass",
      "Comment": "設定パラメータを初期化",
      "Assign": {
        "ssmRoleArn": "arn:aws:iam::アカウントID:role/AWSConfigRemediation-DeleteIAMUserRole",
        "inactiveDaysThreshold": 365
      },
      "Next": "RequestCredentialReport"
    },
    "ValidateInactiveUsers": {
      "Choices": [
        {
          "Condition": "{% $count($completelyInactiveUsers) = 0 %}",
          "Next": "ReturnEmptyResult"
        }
      ],
      "Comment": "非アクティブユーザーが存在するかチェック",
      "Default": "HandleInactiveUsers",
      "Type": "Choice"
    },
    "ParseCSVHeaders": {
      "Assign": {
        "dataLines": "{% $filter($reportLines, function($v, $i) { $i > 0 }) %}",
        "headerColumns": "{% $split($reportLines[0], ',') %}",
        "totalUserCount": "{% $count($filter($reportLines, function($v, $i) { $i > 0 })) %}"
      },
      "Comment": "CSVの最初の行からヘッダー列を抽出し、データ行を分離して全ユーザー数をカウント",
      "Next": "TransformUserData",
      "Type": "Pass"
    },
    "RequestCredentialReport": {
      "Arguments": {},
      "Comment": "IAM認証情報レポートの生成をリクエスト",
      "Next": "RetrieveCredentialReport",
      "Resource": "arn:aws:states:::aws-sdk:iam:generateCredentialReport",
      "Type": "Task"
    },
    "CompileFinalReport": {
      "Comment": "処理結果の最終レポートを生成して終了",
      "Output": {
        "actionResults": "{% $actionResults %}",
        "deletionDetails": {
          "deletedUsers": "{% $actionResults[action='delete'] ? $actionResults[action='delete'] : [] %}",
          "failedUsers": "{% $actionResults[action='delete_failed'] ? $actionResults[action='delete_failed'] : [] %}",
          "noActionUsers": "{% $actionResults[action='none'] ? $actionResults[action='none'] : [] %}"
        },
        "summary": {
          "activeUsers": "{% $activeUserCount %}",
          "completelyInactiveUsers": "{% $inactiveUserCount %}",
          "totalUsers": "{% $totalUserCount %}",
          "usersDeleted": "{% $count($actionResults[action='delete']) %}",
          "usersDeletionFailed": "{% $count($actionResults[action='delete_failed']) %}",
          "usersRequiringNoAction": "{% $count($actionResults[action='none']) %}"
        }
      },
      "Type": "Succeed"
    },
    "RetrieveCredentialReport": {
      "Arguments": {},
      "Assign": {
        "reportContent": "{% $states.result.Content %}"
      },
      "Comment": "生成された認証情報レポートを取得",
      "Next": "SplitReportLines",
      "Resource": "arn:aws:states:::aws-sdk:iam:getCredentialReport",
      "Type": "Task",
      "Retry": [
        {
          "ErrorEquals": [
            "States.ALL"
          ],
          "BackoffRate": 2,
          "IntervalSeconds": 10,
          "MaxAttempts": 3
        }
      ]
    },
    "ClassifyUsers": {
      "Assign": {
        "activeUserCount": "{% $count($filter($userRecords, function($user) { $user.password_enabled = 'true' or $user.access_key_1_active = 'true' or $user.access_key_2_active = 'true' })) %}",
        "completelyInactiveUsers": "{% [$filter($userRecords, function($user) { $user.password_enabled = 'false' and $user.access_key_1_active = 'false' and $user.access_key_2_active = 'false' })] %}",
        "inactiveUserCount": "{% $count($filter($userRecords, function($user) { $user.password_enabled = 'false' and $user.access_key_1_active = 'false' and $user.access_key_2_active = 'false' })) %}"
      },
      "Comment": "ユーザーを完全に非アクティブなユーザー(パスワードとアクセスキーが全て無効)とアクティブユーザーに分類",
      "Next": "ValidateInactiveUsers",
      "Type": "Pass"
    },
    "SplitReportLines": {
      "Assign": {
        "reportLines": "{% $split($reportContent, '\\n') %}"
      },
      "Comment": "レポートをCSV形式から行ごとに分割",
      "Next": "ParseCSVHeaders",
      "Type": "Pass"
    },
    "TransformUserData": {
      "Assign": {
        "userRecords": "{% $map($dataLines, function($line) { $split($line, ',') ~> function($values) { { 'user': $values[0], 'arn': $values[1], 'user_creation_time': $values[2], 'password_enabled': $values[3], 'password_last_used': $values[4], 'password_last_changed': $values[5], 'password_next_rotation': $values[6], 'mfa_active': $values[7], 'access_key_1_active': $values[8], 'access_key_1_last_rotated': $values[9], 'access_key_1_last_used_date': $values[10], 'access_key_1_last_used_region': $values[11], 'access_key_1_last_used_service': $values[12], 'access_key_2_active': $values[13], 'access_key_2_last_rotated': $values[14], 'access_key_2_last_used_date': $values[15], 'access_key_2_last_used_region': $values[16], 'access_key_2_last_used_service': $values[17], 'cert_1_active': $values[18], 'cert_1_last_rotated': $values[19], 'cert_2_active': $values[20], 'cert_2_last_rotated': $values[21] } } }) %}"
      },
      "Comment": "各データ行をJSONオブジェクトに変換。各行の値をヘッダー列に対応するキーを持つプロパティとして設定",
      "Next": "ClassifyUsers",
      "Type": "Pass"
    },
    "HandleInactiveUsers": {
      "Assign": {
        "actionResults": "{% $states.result %}"
      },
      "Comment": "各非アクティブユーザーに対して最終アクティビティ日時の計算と適切なアクションの決定を実行",
      "ItemProcessor": {
        "ProcessorConfig": {
          "Mode": "INLINE"
        },
        "StartAt": "DetermineLastActivity",
        "States": {
          "ComputeInactiveDays": {
            "Assign": {
              "inactiveDays": "{% $floor( ($toMillis($now()) - $lastActivityTimestamp) / 86400000 ) %}"
            },
            "Comment": "最終アクティビティから現在までの経過日数を計算",
            "Next": "DecideUserAction",
            "Type": "Pass"
          },
          "DetermineLastActivity": {
            "Assign": {
              "lastActivityTimestamp": "{% $max( $map( $filter([ $states.input.user_creation_time, $states.input.password_last_used, $states.input.password_last_changed, $states.input.access_key_1_last_used_date, $states.input.access_key_2_last_used_date, $states.input.access_key_1_last_rotated, $states.input.access_key_2_last_rotated],function($v){ $v and $v!='N/A' and $v!='no_information'}), function($d){ $toMillis($d)})) %}"
            },
            "Comment": "ユーザーの最終アクティビティ日時を特定",
            "Next": "ComputeInactiveDays",
            "Type": "Pass"
          },
          "MonitorSSMExecution": {
            "Arguments": {
              "AutomationExecutionId": "{% $automationId %}"
            },
            "Assign": {
              "currentStatus": "{% $states.result.AutomationExecution.AutomationExecutionStatus %}"
            },
            "Comment": "SSM Automationの実行状況を確認",
            "Next": "AssessSSMResult",
            "Resource": "arn:aws:states:::aws-sdk:ssm:getAutomationExecution",
            "Type": "Task"
          },
          "UserDeletionComplete": {
            "Comment": "IAMユーザーの削除が正常に完了",
            "Output": {
              "action": "delete",
              "automationId": "{% $automationId %}",
              "inactiveDays": "{% $inactiveDays %}",
              "status": "deletion_completed",
              "username": "{% $userName %}"
            },
            "Type": "Succeed"
          },
          "ExecuteUserDeletion": {
            "Arguments": {
              "DocumentName": "AWSConfigRemediation-DeleteIAMUser",
              "Parameters": {
                "AutomationAssumeRole": [
                  "{% $ssmRoleArn %}"
                ],
                "IAMUserId": [
                  "{% $userId %}"
                ]
              }
            },
            "Assign": {
              "automationId": "{% $states.result.AutomationExecutionId %}"
            },
            "Comment": "SSM Automationを使用してIAMユーザーを削除",
            "Next": "WaitForDeletion",
            "Resource": "arn:aws:states:::aws-sdk:ssm:startAutomationExecution",
            "Type": "Task"
          },
          "DecideUserAction": {
            "Choices": [
              {
                "Condition": "{% $inactiveDays >= $inactiveDaysThreshold  %}",
                "Next": "RetrieveUserId"
              }
            ],
            "Comment": "非アクティブ期間に基づいて適切なアクションを決定",
            "Default": "SkipUserAction",
            "Type": "Choice"
          },
          "AssessSSMResult": {
            "Choices": [
              {
                "Condition": "{% $currentStatus = 'Success' %}",
                "Next": "UserDeletionComplete"
              },
              {
                "Condition": "{% $currentStatus = 'Failed' %}",
                "Next": "UserDeletionFailed"
              },
              {
                "Condition": "{% $currentStatus in ['InProgress', 'Waiting'] %}",
                "Next": "WaitForDeletion"
              }
            ],
            "Comment": "SSM Automationの状況に基づいて次のアクションを決定",
            "Default": "UserDeletionFailed",
            "Type": "Choice"
          },
          "RetrieveUserId": {
            "Arguments": {
              "UserName": "{% $states.input.user %}"
            },
            "Assign": {
              "userId": "{% $states.result.User.UserId %}",
              "userName": "{% $states.input.user %}"
            },
            "Comment": "ユーザー名からユーザーIDを取得",
            "Next": "ExecuteUserDeletion",
            "Resource": "arn:aws:states:::aws-sdk:iam:getUser",
            "Type": "Task"
          },
          "SkipUserAction": {
            "Comment": "アクションが不要なユーザーとしてマーク",
            "End": true,
            "Output": {
              "action": "none",
              "inactiveDays": "{% $inactiveDays %}",
              "username": "{% $states.input.user %}"
            },
            "Type": "Pass"
          },
          "UserDeletionFailed": {
            "Cause": "{% \"SSM Automation failed for user: \" & $userName & \" (AutomationId: \" & $automationId & \")\" %}",
            "Comment": "SSM Automationによる削除が失敗した場合",
            "Error": "SSMAutomationDeletionFailed",
            "Type": "Fail"
          },
          "WaitForDeletion": {
            "Comment": "SSM Automationの完了を待機",
            "Next": "MonitorSSMExecution",
            "Seconds": 15,
            "Type": "Wait"
          }
        }
      },
      "Items": "{% $completelyInactiveUsers %}",
      "Next": "CompileFinalReport",
      "Type": "Map"
    },
    "ReturnEmptyResult": {
      "Comment": "非アクティブユーザーが存在しない場合の終了処理",
      "Output": {
        "summary": {
          "activeUsers": "{% $activeUserCount %}",
          "completelyInactiveUsers": 0,
          "message": "No completely inactive users found",
          "totalUsers": "{% $totalUserCount %}"
        }
      },
      "Type": "Succeed"
    }
  }
}

		

Step Functions実行ロールの権限設定

マネジメントコンソールでステートマシンを作成する際、実行ロールに以下のポリシーを手動で追加する必要があります。

cm-hirai-screenshot 2025-08-21 13.33.41
以下のポリシーを追加してください。アカウントIDは各自変更してください。

			
			{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "IAMUserOperations",
            "Effect": "Allow",
            "Action": [
                "iam:GenerateCredentialReport",
                "iam:GetCredentialReport",
                "iam:GetUser"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": "ssm:StartAutomationExecution",
            "Resource": "arn:aws:ssm:*::automation-definition/AWSConfigRemediation-DeleteIAMUser:*"
        },
        {
            "Effect": "Allow",
            "Action": "ssm:GetAutomationExecution",
            "Resource": "arn:aws:ssm:*:アカウントID:automation-execution/*"
        },
        {
            "Condition": {
                "StringEquals": {
                    "iam:PassedToService": "ssm.amazonaws.com"
                }
            },
            "Effect": "Allow",
            "Action": [
                "iam:PassRole"
            ],
            "Resource": [
                "arn:aws:iam::アカウントID:role/AWSConfigRemediation-DeleteIAMUserRole"
            ]
        }
    ]
}

		

認証情報レポートの取得について
ユーザー数が多い環境では、GetCredentialReportでタイムアウトが発生する可能性があります。そのため、本実装では10秒のリトライ間隔、最大3回のリトライ設定を行っています。

削除対象となるIAMユーザーの条件

本記事の自動削除処理では、以下の条件をすべて満たすIAMユーザーが対象となります。

1. 認証情報が完全に無効化されている

  • コンソールパスワード: 無効または未設定(password_enabled = false
  • アクセスキー1: 無効または未発行(access_key_1_active = false
  • アクセスキー2: 無効または未発行(access_key_2_active = false

2. 一定期間未使用である

最終アクティビティから指定日数(デフォルト: 365日)が経過していること。最終アクティビティは以下のうち最新の日時で判定します。

  • ユーザー作成日時(user_creation_time
  • パスワード最終使用日時(password_last_used
  • パスワード最終変更日時(password_last_changed
  • アクセスキー1最終使用日時(access_key_1_last_used_date
  • アクセスキー2最終使用日時(access_key_2_last_used_date
  • アクセスキー1最終ローテーション日時(access_key_1_last_rotated
  • アクセスキー2最終ローテーション日時(access_key_2_last_rotated

認証情報レポートの各キー名は、以下のドキュメントにまとめられています。

https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/id_credentials_getting-report.html

注意事項

  • アクセスキーやコンソールアクセスを一度も利用したことがないIAMユーザーも削除対象に含まれます(ユーザー作成日時から判定)
  • 認証情報が1つでも有効な場合(パスワードまたはアクセスキーのいずれか)、削除対象から除外されます

処理の流れ(詳細)

1. 認証情報レポートの生成・取得

関連ステート: SetupParametersRequestCredentialReportRetrieveCredentialReportSplitReportLinesParseCSVHeaders

  • パラメータ初期化: SSMロールARNと非アクティブ日数閾値(今回は、365日)を設定
  • レポート生成要求: GenerateCredentialReport APIでIAM認証情報レポートの生成を要求
  • レポート取得: GetCredentialReport APIで生成されたレポート(CSV形式)を取得
  • CSV解析: レポートを行ごとに分割し、ヘッダー行とデータ行を分離

2. ユーザー分類

関連ステート: TransformUserDataClassifyUsersValidateInactiveUsers

  • データ変換: CSV行を構造化されたJSONオブジェクトに変換(各フィールドをキー・バリューペアに)
  • アクティブユーザー判定: パスワードまたはアクセスキー(1つ目・2つ目)のいずれかが有効なユーザーをカウント
  • 完全非アクティブユーザー抽出: パスワードとアクセスキーが全て無効化されているユーザーを特定
  • 存在チェック: 完全非アクティブユーザーが存在しない場合は処理を終了

3. 非アクティブ期間の計算(Map処理)

関連ステート: HandleInactiveUsers (Map) 内の DetermineLastActivityComputeInactiveDays

各完全非アクティブユーザーに対して並列処理を実行:

  • 最終アクティビティ特定: 以下の日時から有効な値(N/A、no_information以外)の最新日時を選択
    • ユーザー作成日時(user_creation_time
    • パスワード最終使用日時(password_last_used
    • パスワード最終変更日時(password_last_changed
    • アクセスキー1最終使用日時(access_key_1_last_used_date
    • アクセスキー2最終使用日時(access_key_2_last_used_date
    • アクセスキー1最終ローテーション日時(access_key_1_last_rotated
    • アクセスキー2最終ローテーション日時(access_key_2_last_rotated
  • 経過日数算出: 最終アクティビティから実行時刻までの日数を計算(floor関数で切り捨て)

4. 削除実行

関連ステート: DecideUserActionRetrieveUserIdExecuteUserDeletionWaitForDeletionMonitorSSMExecutionAssessSSMResultUserDeletionComplete / UserDeletionFailed / SkipUserAction

  • 削除判定: 非アクティブ日数が閾値以上の場合のみ削除処理を実行
  • ユーザーID取得: GetUser APIでユーザー名からユーザーIDを取得
  • SSMオートメーション実行: AWSConfigRemediation-DeleteIAMUserドキュメントを使用してSSMオートメーションを開始
  • 進捗監視: 15秒間隔でSSMオートメーションの実行状況を監視
  • 結果判定: Success、Failed、InProgress/Waitingの状態に応じて処理を分岐

5. 結果レポート

関連ステート: CompileFinalReport / ReturnEmptyResult

  • 個別結果集約: 各ユーザーの処理結果(削除完了、削除失敗、アクション不要)を収集
  • 統計情報生成: 以下の情報を含む最終レポートを作成
    • 総ユーザー数、アクティブユーザー数、完全非アクティブユーザー数
    • 削除されたユーザー数、削除失敗ユーザー数、アクション不要ユーザー数
    • 詳細な処理結果リスト

Amazon EventBridge Schedulerの設定

Cron式0 0 * * ? *で毎日0時にステートマシンを実行するスケジューラーを作成します。
cm-hirai-screenshot 2025-08-21 13.52.54

設定内容は以下の通りです。

  • ターゲット: AWS Step Functions StartExecution API
  • スケジュール完了後のアクション: NONE
  • その他: デフォルト設定

cm-hirai-screenshot 2025-08-21 13.54.06

cm-hirai-screenshot 2025-08-21 13.54.20

実行結果の確認

削除対象がいなかった場合

CompileFinalReportステートの出力例です。

actionResultsには、アクセスキーやコンソールアクセスが全て無効化されているユーザーが表示されます。これらのユーザーのうち、無効化から指定日数が経過したユーザーが削除対象となります。
cm-hirai-screenshot 2025-08-22 9.51.56

			
			{
  "actionResults": [
    {
      "action": "none",
      "inactiveDays": 182,
      "username": "test1"
    },
    {
      "action": "none",
      "inactiveDays": 0,
      "username": "test11"
    }
  ],
  "deletionDetails": {
    "deletedUsers": [],
    "failedUsers": [],
    "noActionUsers": [
      {
        "action": "none",
        "inactiveDays": 182,
        "username": "test1"
      },
      {
        "action": "none",
        "inactiveDays": 0,
        "username": "test11"
      }
    ]
  },
  "summary": {
    "activeUsers": 12,
    "completelyInactiveUsers": 2,
    "totalUsers": 14,
    "usersDeleted": 0,
    "usersDeletionFailed": 0,
    "usersRequiringNoAction": 2
  }
}

		

削除対象がいて削除した場合

inactiveDaysThresholdを180に変更し、実際に削除が実行された場合の出力例です。

cm-hirai-screenshot 2025-08-22 10.37.02

			
			{
  "actionResults": [
    {
      "action": "delete",
      "automationId": "3dda95aa-8f36-4fb9-9d51-c106c2df65a8",
      "inactiveDays": 182,
      "status": "deletion_completed",
      "username": "test1"
    },
    {
      "action": "none",
      "inactiveDays": 0,
      "username": "test11"
    },
  ],
  "deletionDetails": {
    "deletedUsers": {
      "action": "delete",
      "automationId": "3dda95aa-8f36-4fb9-9d51-c106c2df65a8",
      "inactiveDays": 182,
      "status": "deletion_completed",
      "username": "test1"
    },
    "failedUsers": [],
    "noActionUsers": {
      "action": "none",
      "inactiveDays": 0,
      "username": "test11"
    }
  },
  "summary": {
    "activeUsers": 12,
    "completelyInactiveUsers": 2,
    "totalUsers": 14,
    "usersDeleted": 1,
    "usersDeletionFailed": 0,
    "usersRequiringNoAction": 1
  }
}

		

非アクティブ日数の計算ロジック

IAM認証情報レポート

参考として、test1とtest11ユーザーの認証情報レポートは以下の通りです。

			
			user,arn,user_creation_time,password_enabled,password_last_used,password_last_changed,password_next_rotation,mfa_active,access_key_1_active,access_key_1_last_rotated,access_key_1_last_used_date,access_key_1_last_used_region,access_key_1_last_used_service,access_key_2_active,access_key_2_last_rotated,access_key_2_last_used_date,access_key_2_last_used_region,access_key_2_last_used_service,cert_1_active,cert_1_last_rotated,cert_2_active,cert_2_last_rotated
test1,arn:aws:iam::アカウントID:user/test1,2024-08-08T00:48:14Z,false,2025-01-21T02:56:21Z,N/A,N/A,true,false,2025-02-19T08:32:35Z,N/A,N/A,N/A,false,2025-02-20T23:10:09Z,2025-02-20T23:10:00Z,ap-northeast-1,s3,false,N/A,false,N/A
test11,arn:aws:iam::アカウントID:user/test11,2025-08-21T04:44:24Z,false,N/A,N/A,N/A,false,false,N/A,N/A,N/A,N/A,false,N/A,N/A,N/A,N/A,false,N/A,false,N/A

		

test1ユーザーの計算例

test1ユーザーのinactiveDays: 182の計算根拠について説明します。

認証情報レポートの分析

test1ユーザーの各アクティビティ日時から最終アクティビティを特定します。

			
			test1,arn:aws:iam::アカウントID:user/test1,2024-08-08T00:48:14Z,false,2025-01-21T02:56:21Z,N/A,N/A,true,false,2025-02-19T08:32:35Z,N/A,N/A,N/A,false,2025-02-20T23:10:09Z,2025-02-20T23:10:00Z,ap-northeast-1,s3,false,N/A,false,N/A

		

各アクティビティ日時は以下の通りです。

  • user_creation_time: 2024-08-08T00:48:14Z
  • password_last_used: 2025-01-21T02:56:21Z
  • password_last_changed: N/A
  • access_key_1_last_rotated: 2025-02-19T08:32:35Z
  • access_key_1_last_used_date: N/A
  • access_key_2_last_rotated: 2025-02-20T23:10:09Z
  • access_key_2_last_used_date: 2025-02-20T23:10:00Z最新

最終アクティビティの特定

Step FunctionsのDetermineLastActivityステップで、有効な日時(N/Aやno_information以外)の中から最新のものを選択します。

			
			$max($map($filter([
  $states.input.user_creation_time,
  $states.input.password_last_used,
  $states.input.password_last_changed,
  $states.input.access_key_1_last_used_date,
  $states.input.access_key_2_last_used_date,
  $states.input.access_key_1_last_rotated,
  $states.input.access_key_2_last_rotated
], function($v){ $v and $v!='N/A' and $v!='no_information'}), function($d){ $toMillis($d)}))

		

最終アクティビティ: 2025-02-20T23:10:00Z (access_key_2_last_used_date)

非アクティブ日数の計算

実行日を2025年8月22日と仮定した場合の計算例です。

			
			// 現在時刻を2025年8月22日と仮定
const lastActivity = new Date('2025-02-20T23:10:00Z');
const now = new Date('2025-08-22T00:00:00Z');
const diffMs = now.getTime() - lastActivity.getTime();
const inactiveDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
// 結果: 182日

		

計算式は以下の通りです。

			
			2025-08-22 - 2025-02-20 = 182日

		

つまり、test1ユーザーの最後のS3アクセス(access_key_2_last_used_date)から182日経過しているため、inactiveDays: 182となります。

test11ユーザーの計算例

test11ユーザーのinactiveDays: 0について説明します。

認証情報レポートの分析

test11ユーザーは以下の状況です。

			
			test11,arn:aws:iam::アカウントID:user/test11,2025-08-21T04:44:24Z,false,N/A,N/A,N/A,false,false,N/A,N/A,N/A,N/A,false,N/A,N/A,N/A,N/A,false,N/A,false,N/A

		

test11ユーザーは以下の状況です:

  • user_creation_time: 2025-08-21T04:44:24Z唯一の有効な日時
  • password_enabled: false (パスワード未設定)
  • password_last_used: N/A (パスワード使用履歴なし)
  • password_last_changed: N/A (パスワード変更履歴なし)
  • mfa_active: false (MFA未設定)
  • access_key_1_active: false (アクセスキー1未作成)
  • access_key_2_active: false (アクセスキー2未作成)
  • その他すべての使用履歴: N/A

Step Functionsでの処理

DetermineLastActivityステップの処理結果は以下の通りです。

			
			$max($map($filter([
  $states.input.user_creation_time,        // 2025-08-21T04:44:24Z ← これのみ有効
  $states.input.password_last_used,        // N/A
  $states.input.password_last_changed,     // N/A
  $states.input.access_key_1_last_used_date, // N/A
  $states.input.access_key_2_last_used_date, // N/A
  $states.input.access_key_1_last_rotated,   // N/A
  $states.input.access_key_2_last_rotated    // N/A
], function($v){ $v and $v!='N/A' and $v!='no_information'}), function($d){ $toMillis($d)}))

		

結果: lastActivityTimestamp = 2025-08-21T04:44:24Z(ユーザー作成日時)

ComputeInactiveDaysステップ:

			
			$floor(($toMillis($now()) - $lastActivityTimestamp) / 86400000)

		

計算例(実行日が2025-08-22の場合)

実行時刻を2025年8月22日と仮定した場合、作成から24時間未満のため、経過日数は0日となります。

			
			const userCreation = new Date('2025-08-21T04:44:24Z');
const now = new Date('2025-08-22T00:00:00Z'); // 実行時刻
const diffMs = now.getTime() - userCreation.getTime();
const inactiveDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
// 結果: 0日 (作成から24時間未満)

		

非アクティブ日数が0となる理由

非アクティブ日数が0となる理由は以下の通りです。

  1. アクティビティが皆無: test11は作成後、一度も使用されていない
  2. 最終アクティビティ = 作成日時: 有効な日時がユーザー作成日時のみ
  3. 作成から短時間: 2025-08-21に作成され、翌日実行のため経過日数が1日未満
  4. floor関数: 小数点以下切り捨てにより0日となる

test11は「完全に未使用の新規ユーザー」であり、作成から24時間未満のためinactiveDays: 0となっています。

さいごに

本記事では、AWS Step Functionsと認証情報レポートを活用して、一定期間未利用のIAMユーザーを自動削除する仕組みを紹介しました。

この仕組みにより、アクセスキーとコンソールアクセスが全て無効化され、一定期間(デフォルト365日)未使用のIAMユーザーを自動的に削除できます。セキュリティリスクの低減と運用負荷の軽減を同時に実現できます。

ただし、IAMユーザーの自動削除は不可逆的な操作です。本番環境への導入前に、必ずテスト環境で動作確認を行ってくださ

この記事をシェアする

FacebookHatena blogX

関連記事