Skip to content

Commit

Permalink
use arn, not trail name
Browse files Browse the repository at this point in the history
  • Loading branch information
badra001 committed Jan 2, 2026
1 parent dc354d0 commit cd10d19
Show file tree
Hide file tree
Showing 2 changed files with 30 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@
from collections import Counter

# --- VERSIONING ---
__version__ = "1.1.3"
__version__ = "1.1.4"

def find_latest_file(pattern):
files = glob.glob(pattern)
return max(files, key=os.path.getctime) if files else None

def main():
parser = argparse.ArgumentParser(description="AWS CloudTrail Audit Assessor")
parser = argparse.ArgumentParser(description="AWS CloudTrail Audit Assessor - ARN Edition")
parser.add_argument("--input", help="JSON file (default: latest audit_results.check_cloudtrail.*.json)")
args = parser.parse_args()

Expand All @@ -31,11 +31,14 @@ def main():

org_id = data[0].get("org_id", "Unknown") if data else "Unknown"

print("-" * 180)
print(f"CLOUDTRAIL ASSESSMENT REPORT | Org ID: {org_id} | Input: {os.path.basename(input_file)}")
print("-" * 180)
print(f"{'Account ID':<15} | {'OU Path':<30} | {'Global Summary':<25} | {'Active/Stopped':<15} | {'Security Issues'}")
print("-" * 180)
# EXPANDED COLUMN WIDTHS
# Account(15) | OU(30) | Global(25) | Active(15) | Resource(100)
report_width = 230
print("-" * report_width)
print(f"CLOUDTRAIL ARN ASSESSMENT REPORT | Org ID: {org_id} | Input: {os.path.basename(input_file)}")
print("-" * report_width)
print(f"{'Account ID':<15} | {'OU Path':<30} | {'Global Summary':<25} | {'Active/Stopped':<15} | {'Resource (ARN)'}")
print("-" * report_width)

stats = {
"s3_bytes": 0, "s3_objects": 0, "s3_buckets": set(),
Expand All @@ -53,54 +56,43 @@ def main():
summary = checks.get("account_summary", {}).get("_summary", "UNKNOWN")

acc_active, acc_stopped = 0, 0
issues = []

# Since one account can have multiple trails, we iterate them
for key, val in checks.items():
if key == "account_summary": continue

# Identify trails by the presence of a colon in the key (Region:TrailName)
if ":" not in key: continue

trail_name = val.get("resource") # Extraction from new field
trail_arn = val.get("resource") # Now contains the full ARN

if val.get("is_logging") == "True":
acc_active += 1; stats["logging_active"] += 1
else:
acc_stopped += 1; stats["logging_stopped"] += 1

# Metric Aggregation
bucket = val.get("s3_bucket")
if bucket and bucket != "N/A":
stats["s3_buckets"].add(bucket)
stats["s3_bytes"] += val.get("bucket_size_bytes", 0)
stats["s3_objects"] += val.get("object_count", 0)

if "cw_logs_size_bytes" in val:
unique_lg_key = f"{acc_id}:{val.get('home_region')}:{trail_name}"
stats["cw_group_arns"].add(unique_lg_key)
stats["cw_group_arns"].add(f"{acc_id}:{trail_arn}")
stats["cw_bytes"] += val["cw_logs_size_bytes"]
retention = val.get("cw_logs_retention_days", "Never Expire")
retention_distribution[retention] += 1

sns = val.get("sns_topic")
if sns and sns != "N/A": stats["sns_topics"].add(sns)
if val.get("kms_key_id") == "SSE-S3": stats["sse_s3_count"] += 1
else: stats["kms_cmk_count"] += 1
retention_distribution[val.get("cw_logs_retention_days", "Never Expire")] += 1

print(f"{acc_id:<15} | {ou_path[:30]:<30} | {summary:<25} | {f'{acc_active} ON / {acc_stopped} OFF':<15} | COMPLIANT")
# Print row for each trail found
print(f"{acc_id:<15} | {ou_path[:30]:<30} | {summary:<25} | {val.get('is_logging'):<15} | {trail_arn:<100}")

# FOOTPRINT SUMMARY
s3_gb, cw_gb = stats["s3_bytes"] / (1024**3), stats["cw_bytes"] / (1024**3)

print("-" * 180)
print(f"ORGANIZATION CLOUDTRAIL FOOTPRINT SUMMARY | Org ID: {org_id}")
print(f" Logging Status: {stats['logging_active']} Active Trails | {stats['logging_stopped']} Stopped Trails")
print(f" S3 Storage: {s3_gb:.2f} GB | {stats['s3_objects']:,} objects | {len(stats['s3_buckets'])} unique buckets")
print(f" CloudWatch Logs: {cw_gb:.2f} GB | {len(stats['cw_group_arns'])} unique log groups")
print(f" Log Group Retention Distribution:")
sorted_ret = sorted(retention_distribution.keys(), key=lambda x: (0, x) if isinstance(x, int) else (1, str(x)))
for period in sorted_ret:
label = f"{period} days" if isinstance(period, int) else str(period)
print(f" - {label:<15}: {retention_distribution[period]} group(s)")
print("-" * 180)
print("-" * report_width)
print(f"ORGANIZATION FOOTPRINT SUMMARY | Org ID: {org_id}")
print(f" Logging Status: {stats['logging_active']} Active | {stats['logging_stopped']} Stopped")
print(f" S3 Storage: {s3_gb:.2f} GB | {stats['s3_objects']:,} objects")
print(f" CloudWatch Logs: {cw_gb:.2f} GB | {len(stats['cw_group_arns'])} unique groups")
print("-" * report_width)

if __name__ == "__main__":
main()
11 changes: 6 additions & 5 deletions local-app/python-tools/cross-organization/check_cloudtrail.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from datetime import datetime, timedelta

# --- VERSIONING ---
__version__ = "1.1.3"
__version__ = "1.1.4"

def get_s3_metrics(session, bucket_name, region):
cw = session.client('cloudwatch', region_name=region)
Expand Down Expand Up @@ -51,7 +51,6 @@ def account_task(account_session, account_id, account_name, region):
for reg in enabled_regions:
reg_start = time.perf_counter()
ct = account_session.client('cloudtrail', region_name=reg)
# includeShadowTrails ensures we see trails from other home regions
trails = ct.describe_trails(includeShadowTrails=True).get('trailList', [])

for trail in trails:
Expand All @@ -66,7 +65,9 @@ def account_task(account_session, account_id, account_name, region):

t_name = trail['Name']
t_data = {
"resource": t_name, # Separated Resource Name
"resource": t_arn, # Use ARN as the resource for CSV mapping
"trail_name": t_name, # Preserved name in JSON
"trail_arn": t_arn, # New explicit ARN field for JSON
"home_region": trail.get('HomeRegion', reg),
"is_logging": str(status.get('IsLogging', False)),
"is_org_trail": str(is_org),
Expand All @@ -84,8 +85,8 @@ def account_task(account_session, account_id, account_name, region):
if t_data['s3_bucket'] != 'N/A':
t_data.update(get_s3_metrics(account_session, t_data['s3_bucket'], reg))

# New Key Format: Region:Resource
results["data"][f"{reg}:{t_name}"] = t_data
# Key remains Region:ARN to handle multi-trail scenarios
results["data"][f"{reg}:{t_arn}"] = t_data

summary = "OK"
if org_trail_count > 0 and local_trail_count > 0:
Expand Down

0 comments on commit cd10d19

Please sign in to comment.