Skip to content

Track Last Used AWS IAM Actions for a particular role Using Boto3

Monitoring IAM activity is essential for maintaining a secure and compliant AWS environment. Whether you’re auditing permissions or cleaning up unused roles, knowing when a specific AWS action was last used can help you make informed decisions. In this guide, we’ll show you how to use AWS IAM Access Advisor with Boto3 to track the last time an IAM principal (user, role, or group) accessed a particular service or action.

Why Use IAM Access Advisor?

AWS IAM Access Advisor is a built-in tool that provides granular, time-stamped insights into the usage of services and actions by your IAM entities. This information is a game-changer for IAM optimization and security enhancement.

Use CaseBenefit
Identify Unused PermissionsEasily spot permissions that haven’t been used, leading to policy right-sizing.
Audit Role ActivityGet a clear timeline of when and what services a principal is interacting with.
Optimize IAM PoliciesRefine overly permissive policies to enforce least privilege.
Enhance Security PostureReduce your security surface area by eliminating unnecessary access.

📌 Step 1: Generate the Last Accessed Report

To begin, use the generate_service_last_accessed_details() API call. This starts an asynchronous job that builds a report for a specific IAM principal.

import boto3

iam = boto3.client('iam')

def start_access_report(iam_principal_arn):
    response = iam.generate_service_last_accessed_details(
        Arn=iam_principal_arn,
        Granularity='ACTION_LEVEL'  # Enables action-level tracking
    )
    return response['JobId']

# Example
iam_role_arn = 'arn:aws:iam::123456789012:role/MyExampleRole'
job_id = start_access_report(iam_role_arn)
print(f"Started job with ID: {job_id}")

Step 2: Retrieve and Parse the Report

Once the job is complete, use get_service_last_accessed_details() to fetch the report and extract the last accessed timestamp for the specific action

import time
from botocore.exceptions import ClientError

def get_last_used_action_time(iam_principal_arn, service_namespace, action_name):
    try:
        job_id = start_access_report(iam_principal_arn)
        print("Generating report...")

        while True:
            response = iam.get_service_last_accessed_details(JobId=job_id)
            status = response['JobStatus']

            if status == 'COMPLETED':
                break
            elif status == 'IN_PROGRESS':
                time.sleep(5)
            else:
                print(f"Report failed: {status}")
                return None

        for service in response.get('ServicesLastAccessed', []):
            if service['ServiceNamespace'] == service_namespace:
                for action in service.get('TrackedActionsLastAccessed', []):
                    if action['ActionName'] == action_name:
                        return action.get('LastAccessedTime') or "Never accessed within the tracking period."

        return f"Action '{action_name}' not found for service '{service_namespace}'."

    except ClientError as e:
        print(f"AWS error: {e}")
        return None
    except Exception as e:
        print(f"Unexpected error: {e}")
        return None

# Example usage
iam_principal_arn = 'arn:aws:iam::123456789012:role/MyExampleRole'
service_namespace = 's3'
action_name = 'ListBuckets'

last_access_time = get_last_used_action_time(iam_principal_arn, service_namespace, action_name)
print(f"\nLast time '{action_name}' was used: {last_access_time}")

Complete Code with both combined

Here is the complete code with both generating the report and accessing the report combined

import boto3
import time
import csv
from botocore.exceptions import ClientError

# Initialize IAM client
iam = boto3.client('iam')

# IAM principal details
iam_principal_arn = 'arn:aws:iam::123456789012:role/MyExampleRole'  # Replace with your IAM principal ARN
service_namespace = 's3'        # e.g., 's3', 'ec2', 'lambda'
action_name = 'ListBuckets'     # e.g., 'ListBuckets', 'DescribeInstances'

# Start report generation
def start_access_report(iam_principal_arn):
    response = iam.generate_service_last_accessed_details(
        Arn=iam_principal_arn,
        Granularity='ACTION_LEVEL'
    )
    return response['JobId']

# Retrieve and parse report
def get_last_used_action_time(iam_principal_arn, service_namespace, action_name):
    try:
        job_id = start_access_report(iam_principal_arn)
        print(f"Started report job: {job_id}")

        while True:
            response = iam.get_service_last_accessed_details(JobId=job_id)
            status = response['JobStatus']

            if status == 'COMPLETED':
                break
            elif status == 'IN_PROGRESS':
                time.sleep(5)
            else:
                print(f"Report failed with status: {status}")
                return "Report failed"

        for service in response.get('ServicesLastAccessed', []):
            if service['ServiceNamespace'] == service_namespace:
                for action in service.get('TrackedActionsLastAccessed', []):
                    if action['ActionName'] == action_name:
                        return action.get('LastAccessedTime') or "Never accessed within the tracking period."

        return f"Action '{action_name}' not found for service '{service_namespace}'."

    except ClientError as e:
        return f"AWS client error: {e}"
    except Exception as e:
        return f"Unexpected error: {e}"

# Get last accessed time
last_access_time = get_last_used_action_time(iam_principal_arn, service_namespace, action_name)

# Write to CSV
with open('iam_action_last_used.csv', mode='w', newline='') as file:
    writer = csv.writer(file)
    writer.writerow(['IAMPrincipalARN', 'ServiceNamespace', 'ActionName', 'LastAccessedTime'])
    writer.writerow([iam_principal_arn, service_namespace, action_name, last_access_time])

print(f"✅ Report written to 'iam_action_last_used.csv'")
Tags:

Leave a Reply

Your email address will not be published. Required fields are marked *