From 0ce776037c124730054c7057ae2b0fbac30de9eb Mon Sep 17 00:00:00 2001 From: badra001 Date: Fri, 2 Jan 2026 15:15:26 -0500 Subject: [PATCH] home region/shadow region fix --- .../cross-organization/check_cloudtrail.py | 51 +++++++++---------- 1 file changed, 24 insertions(+), 27 deletions(-) diff --git a/local-app/python-tools/cross-organization/check_cloudtrail.py b/local-app/python-tools/cross-organization/check_cloudtrail.py index 4fb8dc71..93b554b3 100644 --- a/local-app/python-tools/cross-organization/check_cloudtrail.py +++ b/local-app/python-tools/cross-organization/check_cloudtrail.py @@ -3,9 +3,7 @@ from datetime import datetime, timedelta # --- VERSIONING --- -__version__ = "1.1.6" - -# ... (get_s3_metrics and get_log_group_details remain same) ... +__version__ = "1.1.7" def account_task(account_session, account_id, account_name, region): results = {"alias": "N/A", "data": {}} @@ -17,35 +15,38 @@ def account_task(account_session, account_id, account_name, region): enabled_regions = [r['RegionName'] for r in ec2.describe_regions()['Regions']] total_reg_count = len(enabled_regions) - found_trail_arns = set() + # Track ARNs globally across the account scan to prevent duplicates + seen_arns = set() for reg in enabled_regions: - reg_start = time.perf_counter() - ct = account_session.client('cloudtrail', region_name=reg) + # Always ensure the region key exists in the JSON so it's not "empty" + results["data"][reg] = {} - # We must use includeShadowTrails=True to discover trails managed elsewhere - trails = ct.describe_trails(includeShadowTrails=True).get('trailList', []) + ct = account_session.client('cloudtrail', region_name=reg) + try: + # Include shadow trails to see multi-region/org trails from any region + trails = ct.describe_trails(includeShadowTrails=True).get('trailList', []) + except: + continue # Skip regions where CloudTrail might be restricted for trail in trails: t_arn = trail['TrailARN'] - home_reg = trail.get('HomeRegion') - is_multi = trail.get('IsMultiRegionTrail', False) - # Logic: Only process the trail in its Home Region to prevent duplication. - if reg != home_reg: + # DUPLICATION PROTECTION: + # Only process the trail once. If it's multi-region, we'll + # likely see it in the first region we scan. + if t_arn in seen_arns: continue - - if t_arn in found_trail_arns: continue - found_trail_arns.add(t_arn) + seen_arns.add(t_arn) + home_reg = trail.get('HomeRegion') + is_multi = trail.get('IsMultiRegionTrail', False) status = ct.get_trail_status(Name=t_arn) is_org = trail.get('IsOrganizationTrail', False) if is_org: org_trail_count += 1 else: local_trail_count += 1 - # Calculate Shadow Regions: - # If multi-region, it covers all enabled regions minus the home region. shadow_count = (total_reg_count - 1) if is_multi else 0 t_data = { @@ -53,28 +54,24 @@ def account_task(account_session, account_id, account_name, region): "trail_name": trail['Name'], "trail_arn": t_arn, "home_region": home_reg, - "shadow_region_count": shadow_count, # New field + "shadow_region_count": shadow_count, "is_logging": str(status.get('IsLogging', False)), "is_org_trail": str(is_org), "is_multi_region": str(is_multi), "s3_bucket": trail.get('S3BucketName', 'N/A'), "log_file_validation": str(trail.get('LogFileValidationEnabled', False)), "sns_topic": trail.get('SnsTopicARN', 'N/A'), - "kms_key_id": trail.get('KmsKeyId', 'SSE-S3'), - "check_elapsed_sec": round(time.perf_counter() - reg_start, 3) + "kms_key_id": trail.get('KmsKeyId', 'SSE-S3') } - - if 'CloudWatchLogsLogGroupArn' in trail: - t_data.update(get_log_group_details(account_session, trail['CloudWatchLogsLogGroupArn'], reg)) - - if t_data['s3_bucket'] != 'N/A': - t_data.update(get_s3_metrics(account_session, t_data['s3_bucket'], reg)) + # Place the trail data into the region it was discovered in results["data"][f"{reg}:{t_arn}"] = t_data + # Final account summary ensures the file is never "empty" results["data"]["account_summary"] = { "_summary": f"ORG:{org_trail_count}|LOCAL:{local_trail_count}", - "enabled_regions": total_reg_count + "enabled_regions": total_reg_count, + "total_unique_trails": len(seen_arns) } except Exception as e: