diff --git a/local-app/python-tools/cross-organization/assess_check_cloudtrail.py b/local-app/python-tools/cross-organization/assess_check_cloudtrail.py index eb3824ee..ddb489e9 100755 --- a/local-app/python-tools/cross-organization/assess_check_cloudtrail.py +++ b/local-app/python-tools/cross-organization/assess_check_cloudtrail.py @@ -7,7 +7,8 @@ import glob from collections import Counter -__version__ = "1.0.9" +# --- VERSIONING --- +__version__ = "1.1.0" def find_latest_file(pattern): files = glob.glob(pattern) @@ -19,13 +20,9 @@ def main(): parser.add_argument("--central-bucket-regex", default=".*", help="Regex for central bucket") args = parser.parse_args() - input_file = args.input + input_file = args.input or find_latest_file("audit_results.check_cloudtrail.*.json") if not input_file: - input_file = find_latest_file("audit_results.check_cloudtrail.*.json") - if not input_file: - print("Error: No input file provided and no recent check_cloudtrail JSON found.") - sys.exit(1) - print(f"Auto-discovered latest input: {input_file}") + print("Error: No input file found."); sys.exit(1) try: with open(input_file, 'r') as f: @@ -35,17 +32,16 @@ def main(): org_id = data[0].get("org_id", "Unknown") if data else "Unknown" - print("-" * 160) - print(f"CLOUDTRAIL ASSESSMENT REPORT | Organization: {org_id}") - print("-" * 160) - print(f"{'Account ID':<15} | {'OU Path':<25} | {'Global Summary':<25} | {'Active/Stopped':<15} | {'Security Issues'}") - print("-" * 160) + 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) stats = { "s3_bytes": 0, "s3_objects": 0, "s3_buckets": set(), "cw_bytes": 0, "cw_group_arns": set(), "sns_topics": set(), "kms_cmk_count": 0, "sse_s3_count": 0, - "local_trails_total": 0, "local_trails_by_reg": {}, "logging_active": 0, "logging_stopped": 0 } @@ -73,6 +69,7 @@ def main(): 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 not re.match(args.central_bucket_regex, bucket): issues.append(f"Non-Central:{bucket}") @@ -83,12 +80,30 @@ def main(): retention = val.get("cw_logs_retention_days", "Never Expire") retention_distribution[retention] += 1 - print(f"{acc_id:<15} | {ou_path[:25]:<25} | {summary:<25} | {f'{acc_active} ON / {acc_stopped} OFF':<15} | {', '.join(issues) if issues else 'COMPLIANT'}") + 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["kms_cmk_count"] += 1 + else: stats["sse_s3_count"] += 1 - print("-" * 160) - print(f"FOOTPRINT SUMMARY | Org ID: {org_id}") - print(f" S3 Storage: {stats['s3_bytes'] / (1024**3):.2f} GB | CW Storage: {stats['cw_bytes'] / (1024**3):.2f} GB") - print("-" * 160) + print(f"{acc_id:<15} | {ou_path[:30]:<30} | {summary:<25} | {f'{acc_active} ON / {acc_stopped} OFF':<15} | {', '.join(issues) if issues else 'COMPLIANT'}") + + # RESTORED SUMMARY SECTION + s3_gb = stats["s3_bytes"] / (1024**3) + cw_gb = 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(f" Encryption: {stats['kms_cmk_count']} KMS CMK | {stats['sse_s3_count']} SSE-S3") + print(f" Notifications: {len(stats['sns_topics'])} unique SNS Topics") + print("-" * 180) if __name__ == "__main__": main() diff --git a/local-app/python-tools/cross-organization/assess_check_config.py b/local-app/python-tools/cross-organization/assess_check_config.py index 68b9ee34..5c1a9a1c 100755 --- a/local-app/python-tools/cross-organization/assess_check_config.py +++ b/local-app/python-tools/cross-organization/assess_check_config.py @@ -8,30 +8,22 @@ import glob # --- VERSIONING --- -__version__ = "1.0.5" +__version__ = "1.0.6" def find_latest_file(pattern): """Searches for the most recent file matching the pattern.""" files = glob.glob(pattern) - if not files: - return None - return max(files, key=os.path.getctime) + return max(files, key=os.path.getctime) if files else None def main(): parser = argparse.ArgumentParser(description=f"AWS Config Audit Assessor v{__version__}") - # Made --input optional to support auto-discovery parser.add_argument("--input", help="JSON audit file (default: latest audit_results.check_config.*.json)") - # Default regex set to '-org-' - parser.add_argument("--central-bucket-regex", default="-org-", help="Regex for corporate S3 standards") + parser.add_argument("--central-bucket-regex", default=".*-org-.*", help="Regex for corporate S3 standards") args = parser.parse_args() - input_file = args.input + input_file = args.input or find_latest_file("audit_results.check_config.*.json") if not input_file: - input_file = find_latest_file("audit_results.check_config.*.json") - if not input_file: - print("Error: No input file provided and no recent check_config JSON found.") - sys.exit(1) - print(f"Auto-discovered latest input: {input_file}") + print("Error: No input file found."); sys.exit(1) try: with open(input_file, 'r') as f: @@ -41,14 +33,16 @@ def main(): org_id = data[0].get("org_id", "Unknown") if data else "Unknown" - print("-" * 125) + print("-" * 140) print(f"AWS CONFIG ASSESSMENT | Org ID: {org_id} | Pattern: {args.central_bucket_regex}") - print("-" * 125) - print(f"{'Account ID':<15} | {'OU Path':<25} | {'Global Status':<12} | {'S3 Compliance'}") - print("-" * 125) + print("-" * 140) + print(f"{'Account ID':<15} | {'OU Path':<30} | {'Global Status':<12} | {'S3 Compliance'}") + print("-" * 140) - total_stats = {"objects": 0, "size_bytes": 0, "non_central_buckets": 0, "accounts": len(data)} - unique_non_central = set() + stats = { + "objects": 0, "size_bytes": 0, "non_central_buckets": set(), + "total_recorders": 0, "accounts": len(data) + } for account in data: acc_id = account.get("account_id") @@ -59,25 +53,32 @@ def main(): s3_issues = [] for reg, reg_data in checks.items(): if reg == "account_summary": continue + + if reg_data.get("recorder_status") == "ON": + stats["total_recorders"] += 1 + bucket = reg_data.get("s3_bucket", "N/A") - total_stats["objects"] += reg_data.get("object_count", 0) - total_stats["size_bytes"] += reg_data.get("bucket_size_bytes", 0) + stats["objects"] += reg_data.get("object_count", 0) + stats["size_bytes"] += reg_data.get("bucket_size_bytes", 0) - if bucket != "N/A": - if not re.match(args.central_bucket_regex, bucket): - s3_issues.append(f"{reg}:{bucket}") - unique_non_central.add(bucket) + if bucket != "N/A" and not re.match(args.central_bucket_regex, bucket): + s3_issues.append(bucket) + stats["non_central_buckets"].add(bucket) s3_status = "NON_COMPLIANT" if s3_issues else "COMPLIANT" - print(f"{acc_id:<15} | {ou_path[:25]:<25} | {summary:<12} | {s3_status}") + print(f"{acc_id:<15} | {ou_path[:30]:<30} | {summary:<12} | {s3_status}") - size_gb = total_stats["size_bytes"] / (1024**3) - print("-" * 125) - print(f"SUMMARY (CONFIG) | Org ID: {org_id}") - print(f" Total S3 Storage: {size_gb:.2f} GB") - print(f" Non-Central Buckets: {len(unique_non_central)}") - print("-" * 125) + # RESTORED SUMMARY SECTION + size_gb = stats["size_bytes"] / (1024**3) + print("-" * 140) + print(f"ORGANIZATION FOOTPRINT SUMMARY (CONFIG) | Org ID: {org_id}") + print(f" Active Recorders: {stats['total_recorders']}") + print(f" Total S3 Objects: {stats['objects']:,}") + print(f" Total S3 Storage: {size_gb:.2f} GB") + print(f" Non-Central Buckets: {len(stats['non_central_buckets'])}") + for b in sorted(stats["non_central_buckets"]): + print(f" - {b}") + print("-" * 140) if __name__ == "__main__": main() -