Files
Anthropic-Cybersecurity-Skills/skills/securing-aws-iam-permissions/scripts/agent.py
T
mukul975 27c6414ca5 Add folder anatomy (scripts/agent.py + references/api-reference.md) for 648 cybersecurity skills
Complete skill folder anatomy across all cybersecurity skills:
- scripts/agent.py: 80-150 line Python agents using real libraries (impacket,
  boto3, azure-mgmt-*, kubernetes, pefile, yara, scapy, shodan, stix2, etc.)
- references/api-reference.md: real API documentation with method signatures
- LICENSE: MIT license for all skill folders
2026-03-10 21:02:12 +01:00

179 lines
7.5 KiB
Python

#!/usr/bin/env python3
"""Agent for auditing and hardening AWS IAM permissions using least-privilege principles."""
import boto3
import json
import csv
import sys
import argparse
from datetime import datetime, timedelta, timezone
from base64 import b64decode
def get_credential_report():
"""Generate and parse the IAM credential report."""
iam = boto3.client("iam")
iam.generate_credential_report()
import time
for _ in range(10):
try:
response = iam.get_credential_report()
content = b64decode(response["Content"]).decode("utf-8")
lines = content.strip().split("\n")
reader = csv.DictReader(lines)
report = list(reader)
print(f"[*] Credential report: {len(report)} users")
return report
except iam.exceptions.CredentialReportNotReadyException:
time.sleep(2)
return []
def find_stale_access_keys(max_age_days=90):
"""Find access keys older than the specified threshold."""
iam = boto3.client("iam")
cutoff = datetime.now(timezone.utc) - timedelta(days=max_age_days)
stale_keys = []
users = iam.list_users()["Users"]
for user in users:
keys = iam.list_access_keys(UserName=user["UserName"])["AccessKeyMetadata"]
for key in keys:
if key["CreateDate"] < cutoff and key["Status"] == "Active":
last_used = iam.get_access_key_last_used(AccessKeyId=key["AccessKeyId"])
last_used_date = last_used["AccessKeyLastUsed"].get("LastUsedDate", "Never")
stale_keys.append({
"user": user["UserName"], "key_id": key["AccessKeyId"],
"created": key["CreateDate"].isoformat(), "last_used": str(last_used_date),
})
print(f" [!] Stale key: {user['UserName']} - {key['AccessKeyId']} "
f"(created: {key['CreateDate'].strftime('%Y-%m-%d')})")
print(f"[*] Found {len(stale_keys)} stale access keys (>{max_age_days} days)")
return stale_keys
def find_overprivileged_roles():
"""Identify roles with AdministratorAccess or wildcard policies."""
iam = boto3.client("iam")
findings = []
roles = iam.list_roles()["Roles"]
for role in roles:
if role["Path"].startswith("/aws-service-role/"):
continue
attached = iam.list_attached_role_policies(RoleName=role["RoleName"])["AttachedPolicies"]
for policy in attached:
if policy["PolicyName"] in ("AdministratorAccess", "PowerUserAccess"):
findings.append({
"role": role["RoleName"], "policy": policy["PolicyName"],
"severity": "CRITICAL" if policy["PolicyName"] == "AdministratorAccess" else "HIGH",
})
print(f" [!] {role['RoleName']} has {policy['PolicyName']}")
inline_policies = iam.list_role_policies(RoleName=role["RoleName"])["PolicyNames"]
for pol_name in inline_policies:
pol_doc = iam.get_role_policy(RoleName=role["RoleName"], PolicyName=pol_name)["PolicyDocument"]
for stmt in pol_doc.get("Statement", []):
actions = stmt.get("Action", [])
resources = stmt.get("Resource", [])
if isinstance(actions, str):
actions = [actions]
if isinstance(resources, str):
resources = [resources]
if "*" in actions and "*" in resources and stmt.get("Effect") == "Allow":
findings.append({
"role": role["RoleName"], "policy": f"inline:{pol_name}",
"severity": "CRITICAL",
})
print(f" [!] {role['RoleName']} inline policy '{pol_name}' has *:* Allow")
print(f"[*] Found {len(findings)} overprivileged roles")
return findings
def check_mfa_status():
"""Check which IAM users have MFA enabled."""
iam = boto3.client("iam")
users_without_mfa = []
users = iam.list_users()["Users"]
for user in users:
mfa_devices = iam.list_mfa_devices(UserName=user["UserName"])["MFADevices"]
if not mfa_devices:
login_profile = None
try:
iam.get_login_profile(UserName=user["UserName"])
login_profile = True
except iam.exceptions.NoSuchEntityException:
login_profile = False
if login_profile:
users_without_mfa.append(user["UserName"])
print(f" [!] {user['UserName']} has console access but NO MFA")
print(f"[*] {len(users_without_mfa)} console users without MFA")
return users_without_mfa
def check_access_analyzer(region="us-east-1"):
"""Check IAM Access Analyzer findings for external access."""
aa = boto3.client("accessanalyzer", region_name=region)
analyzers = aa.list_analyzers(Type="ACCOUNT")["analyzers"]
if not analyzers:
print(" [-] No Access Analyzer configured. Creating one...")
aa.create_analyzer(analyzerName="account-analyzer", type="ACCOUNT")
return []
analyzer_arn = analyzers[0]["arn"]
findings = aa.list_findings(analyzerArn=analyzer_arn,
filter={"status": {"eq": ["ACTIVE"]}})["findings"]
print(f"[*] Access Analyzer active findings: {len(findings)}")
for f in findings[:10]:
print(f" [!] {f['resourceType']}: {f['resource']} - {f.get('condition', {})}")
return findings
def generate_audit_report(stale_keys, overpriv_roles, no_mfa, aa_findings, output_path):
"""Generate a consolidated IAM audit report."""
report = {
"audit_date": datetime.now().isoformat(),
"summary": {
"stale_access_keys": len(stale_keys),
"overprivileged_roles": len(overpriv_roles),
"users_without_mfa": len(no_mfa),
"access_analyzer_findings": len(aa_findings),
},
"stale_access_keys": stale_keys,
"overprivileged_roles": overpriv_roles,
"users_without_mfa": no_mfa,
}
with open(output_path, "w") as f:
json.dump(report, f, indent=2, default=str)
print(f"\n[*] Audit report saved to {output_path}")
total = len(stale_keys) + len(overpriv_roles) + len(no_mfa) + len(aa_findings)
print(f"[*] Total findings: {total}")
def main():
parser = argparse.ArgumentParser(description="AWS IAM Security Audit Agent")
parser.add_argument("action", choices=["full-audit", "stale-keys", "overpriv-roles", "mfa-check",
"access-analyzer", "credential-report"])
parser.add_argument("--max-key-age", type=int, default=90, help="Max access key age in days")
parser.add_argument("--region", default="us-east-1")
parser.add_argument("-o", "--output", default="iam_audit_report.json")
args = parser.parse_args()
if args.action == "credential-report":
get_credential_report()
elif args.action == "stale-keys":
find_stale_access_keys(args.max_key_age)
elif args.action == "overpriv-roles":
find_overprivileged_roles()
elif args.action == "mfa-check":
check_mfa_status()
elif args.action == "access-analyzer":
check_access_analyzer(args.region)
elif args.action == "full-audit":
print("[*] Running full IAM security audit...")
stale = find_stale_access_keys(args.max_key_age)
overpriv = find_overprivileged_roles()
no_mfa = check_mfa_status()
aa = check_access_analyzer(args.region)
generate_audit_report(stale, overpriv, no_mfa, aa, args.output)
if __name__ == "__main__":
main()