Skip to content

Commit

Permalink
fix CW reporting
Browse files Browse the repository at this point in the history
  • Loading branch information
badra001 committed Jan 2, 2026
1 parent cdab2e2 commit 68975d3
Showing 1 changed file with 43 additions and 21 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.5"
__version__ = "1.1.6"

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 - Storage & Retention Edition")
parser.add_argument("--input", help="JSON file (default: latest audit_results.check_cloudtrail.*.json)")
args = parser.parse_args()

Expand All @@ -34,63 +34,85 @@ def main():

report_width = 240
print("-" * report_width)
print(f"CLOUDTRAIL ARN ASSESSMENT REPORT | Org ID: {org_id} | Input: {os.path.basename(input_file)}")
print(f"CLOUDTRAIL COMPREHENSIVE ASSESSMENT | Org ID: {org_id} | Input: {os.path.basename(input_file)}")
print("-" * report_width)
# Added Idx Column to Header
print(f"{'Idx':<5} | {'Account ID':<15} | {'OU Path':<35} | {'Global Summary':<25} | {'Active/Stopped':<15} | {'Resource (ARN)'}")
print("-" * report_width)

# UPDATED STATS DICTIONARY
stats = {
"s3_bytes": 0, "s3_objects": 0, "s3_buckets": set(),
"cw_bytes": 0, "cw_group_arns": set(),
"logging_active": 0, "logging_stopped": 0,
"total_shadow_regions": 0, "total_home_regions": 0
"s3_bytes": 0,
"s3_objects": 0,
"s3_buckets": set(),
"cw_bytes": 0,
"cw_log_groups": set(), # Track unique log groups
"logging_active": 0,
"logging_stopped": 0,
"total_shadow_regions": 0,
"total_home_regions": 0
}

retention_distribution = Counter()

# Added enumerate to track Index
for idx, account in enumerate(data, 1):
acc_id = account.get("account_id")
ou_path = account.get("ou_path", "Root")
checks = account.get("data", {})
summary = checks.get("account_summary", {}).get("_summary", "UNKNOWN")

for key, val in checks.items():
if key == "account_summary": continue
if ":" not in key: continue
if key == "account_summary" or ":" not in key: continue

trail_arn = val.get("resource")
stats["total_home_regions"] += 1
stats["total_shadow_regions"] += int(val.get("shadow_region_count", 0))

# Status tracking
if val.get("is_logging") == "True":
stats["logging_active"] += 1
else:
stats["logging_stopped"] += 1

# S3 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:
stats["cw_group_arns"].add(f"{acc_id}:{trail_arn}")
stats["cw_bytes"] += val["cw_logs_size_bytes"]
retention_distribution[val.get("cw_logs_retention_days", "Never Expire")] += 1
# RESTORED: CloudWatch Aggregation
cw_arn = val.get("cw_logs_arn", "N/A")
if cw_arn != "N/A":
# Key log group by account:region:arn to ensure uniqueness
unique_lg_id = f"{acc_id}:{val.get('home_region')}:{cw_arn}"
stats["cw_log_groups"].add(unique_lg_id)
stats["cw_bytes"] += val.get("cw_logs_size_bytes", 0)

# Distribution of retention policies
retention = val.get("cw_logs_retention_days", "Never Expire")
retention_distribution[retention] += 1

# Print row with Index
print(f"{idx:<5} | {acc_id:<15} | {ou_path[:35]:<35} | {summary:<25} | {val.get('is_logging'):<15} | {trail_arn:<100}")

s3_gb, cw_gb = stats["s3_bytes"] / (1024**3), stats["cw_bytes"] / (1024**3)
# CONVERT BYTES TO GB
s3_gb = stats["s3_bytes"] / (1024**3)
cw_gb = stats["cw_bytes"] / (1024**3)

# FOOTER SUMMARY
print("-" * report_width)
print(f"ORGANIZATION CLOUDTRAIL FOOTPRINT SUMMARY | Org ID: {org_id}")
print(f" Total Accounts Found: {account_count}") # Added Account Count
print(f" Primary (Home) Trails: {stats['total_home_regions']}")
print(f" Shadow Regions Covered: {stats['total_shadow_regions']}")
print(f" S3 Storage Total: {s3_gb:.2f} GB")
print(f" Total Accounts Found: {account_count}")
print(f" Logging Status: {stats['logging_active']} Active Trails | {stats['logging_stopped']} Stopped Trails")
print(f" S3 Storage Total: {s3_gb:.2f} GB ({stats['s3_objects']:,} objects across {len(stats['s3_buckets'])} buckets)")
print(f" CloudWatch Storage Total: {cw_gb:.2f} GB (across {len(stats['cw_log_groups'])} log groups)")

print(f"\n CloudWatch Retention Distribution:")
# Sort: Integers first, then strings like "Never Expire"
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]} trail(s)")

print("-" * report_width)

if __name__ == "__main__":
Expand Down

0 comments on commit 68975d3

Please sign in to comment.