Skip to main content

Overview

The iam_audit.py module provides a programmatic interface for auditing IAM users and access keys across AWS Organizations. All functions are designed to work with boto3 clients and temporary credentials obtained through sts:AssumeRole.

Core Functions

get_accounts()

Retrieves all active accounts from an AWS Organization.
org_client
boto3.client
required
Boto3 Organizations client authenticated with the management account
accounts
list[dict]
List of account dictionaries containing:
id
str
AWS account ID (12 digits)
name
str
Account name as configured in AWS Organizations
Function Signature:
def get_accounts(org_client) -> list[dict]
Example Usage:
import boto3

session = boto3.Session(profile_name='management-account')
org_client = session.client('organizations')

accounts = get_accounts(org_client)
for account in accounts:
    print(f"Account: {account['name']} ({account['id']})")
Implementation Details:
  • Uses pagination to handle organizations with many accounts
  • Filters to only return accounts with Status == 'ACTIVE'
  • Located at: iam_audit.py:8-19

assume_role()

Asumes a role in a target AWS account and returns temporary credentials.
session
boto3.Session
required
Boto3 session with credentials for the source account
account_id
str
required
Target AWS account ID where the role will be assumed
role_name
str
required
Name of the IAM role to assume (e.g., ‘AWSControlTowerExecution’, ‘IAMAuditRole’)
session_name
str
required
Session name for CloudTrail auditing (e.g., ‘SecurityAudit’, ‘CloudTrailAudit’)
credentials
dict
AWS temporary credentials dictionary containing:
AccessKeyId
str
Temporary access key ID
SecretAccessKey
str
Temporary secret access key
SessionToken
str
Temporary session token
Function Signature:
def assume_role(session, account_id, role_name, session_name) -> dict
Example Usage:
session = boto3.Session(profile_name='management-account')
credentials = assume_role(
    session=session,
    account_id='123456789012',
    role_name='IAMAuditRole',
    session_name='SecurityAudit'
)

iam_client = boto3.client(
    'iam',
    aws_access_key_id=credentials['AccessKeyId'],
    aws_secret_access_key=credentials['SecretAccessKey'],
    aws_session_token=credentials['SessionToken']
)
Implementation Details:
  • Uses STS assume_role API
  • Constructs ARN: arn:aws:iam::{account_id}:role/{role_name}
  • Credentials are temporary and expire automatically
  • Located at: iam_audit.py:21-28

get_iam_users_with_keys()

Lists all IAM users with access keys in a target account, including security metadata.
iam_client
boto3.client
required
Boto3 IAM client authenticated with the target account (via assumed role)
account_id
str
required
AWS account ID being audited
account_name
str
required
Human-readable account name for reporting
findings
list[dict]
List of finding dictionaries, one per access key found:
account_id
str
AWS account ID
account_name
str
Account name
username
str
IAM user name
password_status
str
‘Configurada’ or ‘No configurada’ (console access enabled/disabled)
password_last_used
str
Timestamp of last console login, or ‘Nunca’
access_key_id
str
Access key ID (e.g., AKIAIOSFODNN7EXAMPLE)
status
str
‘Active’ or ‘Inactive’
created_date
str
ISO timestamp when the access key was created
last_used_date
str
ISO timestamp of last usage, or ‘Nunca utilizada’
service_name
str
Last AWS service used with this key, or ‘N/A’
mfa_status
str
‘Virtual’, ‘Hardware’, or ‘None’
Function Signature:
def get_iam_users_with_keys(iam_client, account_id, account_name) -> list[dict]
Example Usage:
findings = get_iam_users_with_keys(
    iam_client=iam_client,
    account_id='123456789012',
    account_name='Production'
)

for finding in findings:
    print(f"User: {finding['username']}")
    print(f"  Key: {finding['access_key_id']}")
    print(f"  Status: {finding['status']}")
    print(f"  MFA: {finding['mfa_status']}")
Implementation Details:
  • Uses pagination for accounts with many IAM users
  • Queries multiple IAM APIs per user:
    • list_access_keys - Get all access keys
    • list_mfa_devices - Check MFA status
    • get_login_profile - Check console access
    • get_access_key_last_used - Get usage metadata
  • Handles NoSuchEntityException for users without console access
  • Returns one finding per access key (users can have multiple keys)
  • Located at: iam_audit.py:30-83
This function calls the IAM API multiple times per user. In accounts with hundreds of users, expect longer execution times due to API rate limits.

get_cloudtrail_events()

Retrieves IAM-related CloudTrail events across multiple accounts for remediation tracking.
session
boto3.Session
required
Boto3 session with credentials for the management account
accounts
list[dict]
required
List of account dictionaries (output from get_accounts())
role_name
str
required
Name of the IAM role to assume in each account
start_date
datetime
required
Start of the time range for CloudTrail lookup
end_date
datetime
required
End of the time range for CloudTrail lookup
events
list[dict]
List of CloudTrail event dictionaries:
eventName
str
IAM event name (e.g., ‘DeleteAccessKey’, ‘CreateUser’)
eventTime
str
ISO timestamp when the event occurred
username
str
IAM user who triggered the event, or ‘N/A’
account_id
str
AWS account ID where the event occurred
account_name
str
Account name
resources
list[str]
List of affected resource names
Function Signature:
def get_cloudtrail_events(session, accounts, role_name, start_date, end_date) -> list[dict]
Example Usage:
from datetime import datetime, timedelta

start_date = datetime.now() - timedelta(days=30)
end_date = datetime.now()

events = get_cloudtrail_events(
    session=session,
    accounts=accounts,
    role_name='IAMAuditRole',
    start_date=start_date,
    end_date=end_date
)

for event in events:
    print(f"{event['eventTime']} - {event['eventName']}")
    print(f"  User: {event['username']}")
    print(f"  Account: {event['account_name']}")
Monitored Events: The function tracks these IAM events:
  • DeleteUser
  • DeleteAccessKey
  • DeleteLoginProfile
  • CreateAccessKey
  • CreateUser
Implementation Details:
  • Uses CloudTrail lookup_events API with pagination
  • Region locked to us-east-1 (see Limitations)
  • Assumes role in each account to query CloudTrail
  • Errors in individual accounts are caught and logged, but don’t stop execution
  • Located at: iam_audit.py:85-120
CloudTrail events are queried from us-east-1 only. IAM is a global service, but CloudTrail events are regional. All IAM API calls generate events in us-east-1 by default.

main()

Orchestrates the complete audit workflow across all accounts. Function Signature:
def main() -> tuple[list[dict], list[dict]]
return
tuple
findings
list[dict]
All IAM findings from get_iam_users_with_keys() across all accounts
cloudtrail_events
list[dict]
All CloudTrail events from get_cloudtrail_events() across all accounts
Workflow:
  1. Parses CLI arguments (--profile, --role)
  2. Initializes boto3 session with specified profile
  3. Retrieves all active accounts from AWS Organizations
  4. Fetches CloudTrail events for all accounts
  5. Iterates through each account:
    • Assumes the audit role
    • Queries IAM users and access keys
    • Aggregates findings
  6. Returns consolidated findings and events
Example Usage:
if __name__ == "__main__":
    findings, cloudtrail_events = main()
    
    # Process findings
    for finding in findings:
        if finding['mfa_status'] == 'None':
            print(f"⚠️  User without MFA: {finding['username']}")
Implementation Details:
  • Uses hardcoded date range: start_date = datetime(2026, 2, 18)
  • Continues on errors (failed accounts are logged but don’t stop the audit)
  • Located at: iam_audit.py:128-167
When run as a script, main() also generates two CSV reports:
  • iam_audit_report_YYYYMMDD_HHMMSS.csv - IAM findings
  • cloudtrail_events_YYYYMMDD_HHMMSS.csv - CloudTrail events

CLI Arguments

When running iam_audit.py as a script:
--profile
str
required
AWS CLI profile name for the management account
--role
str
required
IAM role name to assume in each child account (e.g., ‘AWSControlTowerExecution’)
Example:
python iam_audit.py --profile mgmt-profile --role IAMAuditRole

CSV Output Format

IAM Audit Report

Filename: iam_audit_report_YYYYMMDD_HHMMSS.csv Fields:
  • account_id - AWS account ID
  • account_name - Account name
  • username - IAM user name
  • password_status - Console access status
  • password_last_used - Last console login
  • access_key_id - Access key ID
  • status - Active/Inactive
  • created_date - Key creation date
  • last_used_date - Last key usage
  • service_name - Last service used
  • mfa_status - MFA status (Virtual/Hardware/None)

CloudTrail Events Report

Filename: cloudtrail_events_YYYYMMDD_HHMMSS.csv Fields:
  • eventTime - Event timestamp
  • eventName - IAM event name
  • username - User who triggered the event
  • account_id - Account ID
  • account_name - Account name
  • resources - Comma-separated list of affected resources

Error Handling

All functions implement error handling:
  • Account access errors: Logged to console, execution continues with remaining accounts
  • Missing login profile: Caught via NoSuchEntityException, sets password_status = 'No configurada'
  • CloudTrail errors: Logged per-account, doesn’t stop the audit
try:
    credentials = assume_role(session, account['id'], role_name, 'SecurityAudit')
    # ... audit code ...
except Exception as e:
    print(f"Error en cuenta {account['name']}: {e}")
    # Continues with next account

Build docs developers (and LLMs) love