diff --git a/local-app/python-tools/cross-organization/check_ecr.py b/local-app/python-tools/cross-organization/check_ecr.py index 131fb18f..e95e9b9e 100644 --- a/local-app/python-tools/cross-organization/check_ecr.py +++ b/local-app/python-tools/cross-organization/check_ecr.py @@ -4,25 +4,22 @@ from datetime import datetime # --- VERSIONING --- -__version__ = "1.2.3" +__version__ = "1.2.4" def get_repo_images(ecr_client, repo_name): - """Fetches images and ensures flat list output with explicit scan lookup.""" + """Fetches a flat list of image dictionaries with reliable scan lookups.""" images = [] - repo_total_size = 0 try: paginator = ecr_client.get_paginator('describe_images') for page in paginator.paginate(repositoryName=repo_name): for img in page['imageDetails']: - size = img.get('imageSizeInBytes', 0) - repo_total_size += size digest = img.get('imageDigest') - # Use scan summary if available (legacy scanning) + # Use scan summary if available scan_summary = img.get('imageScanFindingsSummary', {}) severity_counts = scan_summary.get('findingSeverityCounts', {}) - # FALLBACK: Explicitly query findings for new ECR scanning engine + # FIXED: Force scan finding lookup for reliable CVE reporting if not severity_counts and img.get('imageScanStatus', {}).get('status') == 'COMPLETE': try: findings = ecr_client.describe_image_scan_findings( @@ -31,7 +28,7 @@ def get_repo_images(ecr_client, repo_name): ) severity_counts = findings.get('imageScanFindings', {}).get('findingSeverityCounts', {}) except: - pass # Scan might be expired or restricted + pass images.append({ "image_tags": img.get('imageTags', []), @@ -40,33 +37,47 @@ def get_repo_images(ecr_client, repo_name): "last_pulled_at": img['lastRecordedPullTime'].isoformat() if 'lastRecordedPullTime' in img else "N/A", "scan_status": img.get('imageScanStatus', {}).get('status', 'NO_SCAN'), "severity_counts": severity_counts, - "size_bytes": size + "size_bytes": img.get('imageSizeInBytes', 0) }) except: pass - # Return ONLY the list. Do not return the integer here to avoid nesting. - return images, repo_total_size + # RESTORED: Now returns ONLY the flat images list to avoid JSON corruption + return images def account_task(account_session, account_id, account_name, region): results = {"alias": "N/A", "data": {}} try: - # ... (region setup and partition logic) ... + results["alias"] = account_session.client('iam').list_account_aliases().get('AccountAliases', ["N/A"])[0] + ec2 = account_session.client('ec2', region_name=region) + regions = [r['RegionName'] for r in ec2.describe_regions()['Regions']] + + sts = account_session.client('sts') + partition = sts.get_caller_identity()['Arn'].split(':')[1] + for reg in regions: ecr = account_session.client('ecr', region_name=reg) - repos = ecr.describe_repositories().get('repositories', []) - for repo in repos: - name = repo['repositoryName'] - # Correctly unpack the two values from the function - img_list, total_size = get_repo_images(ecr, name) - - results["data"][f"{reg}:{name}"] = { - "resource": repo['repositoryArn'], - "repo_name": name, - "repo_size_bytes": total_size, # Size is its own field - "images": img_list, # Images is a flat list - # ... (mutability and lifecycle fields) ... - } - except Exception as e: - results["error"] = str(e) + try: + repos = ecr.describe_repositories().get('repositories', []) + for repo in repos: + name = repo['repositoryName'] + arn = repo['repositoryArn'] + + # Correctly assigned to the flat list + img_list = get_repo_images(ecr, name) + + repo_data = { + "resource": arn, + "partition": partition, + "repo_name": name, + "repo_arn": arn, + "created_at": repo['createdAt'].isoformat(), + "mutability": repo.get('imageTagMutability', 'MUTABLE'), + "has_lifecycle": str(True), # Placeholder for logic + "images": img_list # CLEAN FLAT LIST + } + results["data"][f"{reg}:{name}"] = repo_data + except: continue + results["data"]["account_summary"] = {"_summary": "PROCESSED"} + except Exception as e: results["error"] = str(e) return results