Skip to content

Commit

Permalink
add check-iam and readme for the plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
badra001 committed Jan 22, 2026
1 parent 006fdca commit 95c7e77
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 325 deletions.
55 changes: 55 additions & 0 deletions local-app/python-tools/cross-organization/README.plugin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Organization Crawler Module Specification

This specification defines the contract between the **Master Crawler** (the orchestration engine) and individual **Check Modules**.

## **Overview**

The Crawler is a multi-threaded engine that assumes a role into target accounts and dynamically imports "Check Modules" to gather data. Each module must be a standalone Python file.

## **I. Function Signature Requirements**

Each module MUST implement an `account_task` function with the following signature:
`def account_task(account_session, account_id, account_name, region):`

* **`account_session`**: A `boto3.Session` object pre-authenticated into the target account.
* **`account_id`**: The 12-digit AWS Account ID string.
* **`account_name`**: The name of the account as defined in AWS Organizations.
* **`region`**: The default region for the session (though modules may iterate through other regions internally).

## **II. Return Data Structure**

The function must return a dictionary with the following top-level keys:

1. **`alias`**: The IAM Account Alias (string).
2. **`data`**: A dictionary containing the actual findings.
* **Resource Keys**: Every entry in `data` should be keyed by a unique identifier (e.g., ARN or Region:ResourceName).
* **The `resource` Key**: Every inner dictionary MUST contain a `resource` key (usually the ARN). The Crawler uses this for CSV mapping.


3. **`account_summary`**: A nested dictionary inside `data` with a `_summary` key used for high-level reporting.
4. **`error`**: (Optional) If the task fails, include the exception string here.

## **III. Data Standards**

* **Date Formats**: All datetime objects MUST be converted to strings using `.isoformat()` to ensure JSON serializability.
* **Booleans**: Convert booleans to strings (`"True"`/`"False"`) if they are going to be displayed in CLI tables or CSVs.
* **Global vs. Regional**:
* **Regional Modules**: Should iterate through `ec2.describe_regions()` and gather data for every enabled region.
* **Global Modules**: (like IAM or Route53) should run exactly once per account to avoid duplicate data.



## **IV. File Output Handling**

Modules do not handle their own file I/O.

1. The **Crawler** collects the returned dictionaries from all threads.
2. The **Crawler** saves the aggregated list as a JSON file named `audit_results.<module_name>.<timestamp>.json`.
3. The **Crawler** automatically flattens the `data` block into a CSV file for any keys that exist in the first returned object.

## **V. Development Checklist**

* [ ] Includes `__version__ = "x.x.x"` at the top.
* [ ] Uses `get_paginator` for any AWS calls that may return more than 100 items.
* [ ] Implements a `try/except` block inside `account_task` to prevent one failing account from crashing the entire thread.
* [ ] Returns "NEVER" or "N/A" for null fields rather than omitting them.
120 changes: 0 additions & 120 deletions local-app/python-tools/cross-organization/analyze-tag-data.py

This file was deleted.

74 changes: 74 additions & 0 deletions local-app/python-tools/cross-organization/check-iam.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import boto3
from datetime import datetime, timezone

# --- VERSIONING ---
__version__ = "1.0.0"

def get_days_ago(dt):
"""Calculates days since a given datetime object."""
if not dt: return "NEVER"
now = datetime.now(timezone.utc)
return (now - dt).days

def account_task(account_session, account_id, account_name, region):
"""
IAM is a global service; this runs once per account.
Captures credential aging, MFA status, and key rotation metrics.
"""
results = {"alias": "N/A", "data": {}}
try:
iam = account_session.client('iam')
results["alias"] = iam.list_account_aliases().get('AccountAliases', ["N/A"])[0]

users = []
paginator = iam.get_paginator('list_users')

for page in paginator.paginate():
for user in page['Users']:
username = user['UserName']

# Basic Login Stats
last_login = user.get('PasswordLastUsed')
days_since_login = get_days_ago(last_login)

# MFA Status
mfa = iam.list_mfa_devices(UserName=username).get('MFADevices', [])
has_mfa = len(mfa) > 0

# Access Key Stats
keys = iam.list_access_keys(UserName=username).get('AccessKeyMetadata', [])
key_details = []
for k in keys:
k_id = k['AccessKeyId']
last_used_resp = iam.get_access_key_last_used(AccessKeyId=k_id)
last_used_date = last_used_resp.get('AccessKeyLastUsed', {}).get('LastUsedDate')

key_details.append({
"access_key_id": k_id,
"status": k['Status'],
"created_date": k['CreateDate'].isoformat(),
"days_since_rotated": get_days_ago(k['CreateDate']),
"last_used_date": last_used_date.isoformat() if last_used_date else "NEVER",
"days_since_used": get_days_ago(last_used_date)
})

user_payload = {
"resource": user['Arn'],
"username": username,
"user_id": user['UserId'],
"create_date": user['CreateDate'].isoformat(),
"mfa_enabled": str(has_mfa),
"last_login_date": last_login.isoformat() if last_login else "NEVER",
"days_since_login": days_since_login,
"access_keys": key_details
}

# Key into data by Username for the JSON output
results["data"][username] = user_payload

results["data"]["account_summary"] = {"_summary": f"USERS:{len(results['data'])}"}

except Exception as e:
results["error"] = str(e)

return results
Loading

0 comments on commit 95c7e77

Please sign in to comment.