Skip to content

Commit

Permalink
add cve checking
Browse files Browse the repository at this point in the history
  • Loading branch information
badra001 committed Jan 9, 2026
1 parent 27c21f8 commit b0a9760
Show file tree
Hide file tree
Showing 2 changed files with 66 additions and 58 deletions.
102 changes: 49 additions & 53 deletions local-app/python-tools/cross-organization/assess_check_ecr.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
#!/usr/bin/env python

import json, argparse, sys, os, glob
from datetime import datetime, timezone
from collections import Counter, defaultdict

# --- VERSIONING ---
__version__ = "1.1.1"
__version__ = "1.3.1"

def find_latest_file(pattern):
files = glob.glob(pattern)
Expand All @@ -16,8 +15,7 @@ def get_days_ago(iso_str):
try:
dt = datetime.fromisoformat(iso_str)
if dt.tzinfo is None: dt = dt.replace(tzinfo=timezone.utc)
now = datetime.now(timezone.utc)
return (now - dt).days
return (datetime.now(timezone.utc) - dt).days
except: return None

def bucket_age(days, counters):
Expand All @@ -30,7 +28,7 @@ def bucket_age(days, counters):
else: counters['<30'] += 1

def main():
parser = argparse.ArgumentParser(description="AWS ECR Audit Assessor - Image Statistics Edition")
parser = argparse.ArgumentParser(description="ECR Full Spectrum Assessor - v1.3.1")
parser.add_argument("--input", help="JSON audit file")
args = parser.parse_args()

Expand All @@ -39,85 +37,83 @@ def main():

with open(input_file, 'r') as f: data = json.load(f)

report_width = 230
report_width = 240
print("-" * report_width)
print(f"ECR IMAGE STATISTICS ASSESSMENT | Total Accounts: {len(data)}")
print(f"ECR COMPREHENSIVE AUDIT | Accounts: {len(data)} | Input: {os.path.basename(input_file)}")
print("-" * report_width)
print(f"{'Idx':<5} | {'Account ID':<15} | {'Region':<12} | {'Repo Name':<45} | {'Img Count':<10} | {'Mutability':<12} | {'Lifecycle'}")
print(f"{'Idx':<4} | {'Account ID':<15} | {'Region':<12} | {'Repo Name':<40} | {'Size (GB)':<10} | {'Mutability':<11} | {'L/Cycle':<7} | {'CRITICAL':<8} | {'HIGH':<8}")
print("-" * report_width)

# Core Stats
stats = {
"repos": 0, "total_images": 0, "total_bytes": 0,
"total_repos": 0, "total_images": 0, "total_bytes": 0, "no_lc": 0,
"mut": {"IMMUTABLE": 0, "MUTABLE": 0},
"region_bytes": defaultdict(int),
"push_ages": Counter(), "pull_ages": Counter(),
"total_push_days": 0, "push_day_count": 0,
"mutable": 0, "immutable": 0, "no_lifecycle": 0
"total_push_days": 0, "push_count": 0,
"org_vulns": Counter(), "scanned_imgs": 0
}

for idx, account in enumerate(data, 1):
checks = account.get("data", {})
for key, val in checks.items():
if key == "account_summary" or ":" not in key: continue

region = key.split(":")[0]
stats["repos"] += 1
stats["total_repos"] += 1

repo_mut = val.get("mutability", "MUTABLE")
stats["mut"][repo_mut] += 1
if val.get("has_lifecycle") == "False": stats["no_lc"] += 1

# Mutability & Lifecycle
mut = val.get("mutability", "MUTABLE")
if mut == "IMMUTABLE": stats["immutable"] += 1
else: stats["mutable"] += 1
if val.get("has_lifecycle") == "False": stats["no_lifecycle"] += 1
repo_size = val.get("repo_size_bytes", 0)
stats["total_bytes"] += repo_size
stats["region_bytes"][region] += repo_size

# Process Images
repo_vulns = Counter()
images = val.get("images", [])
stats["total_images"] += len(images)

for img in images:
# Size calculation
img_size = img.get("size_bytes", 0)
stats["total_bytes"] += img_size
stats["region_bytes"][region] += img_size

# Age calculation
# Security
# FIX: Ensure img is a dictionary before calling .get()
# If img is a single-element list containing a dict, use img[0]
if isinstance(img, list) and len(img) > 0:
img = img[0]

counts = img.get("severity_counts", {})
if counts:
stats["scanned_imgs"] += 1
for sev, count in counts.items():
repo_vulns[sev] += count
stats["org_vulns"][sev] += count
# Aging
p_days = get_days_ago(img.get("pushed_at"))
if p_days is not None:
bucket_age(p_days, stats["push_ages"])
stats["total_push_days"] += p_days
stats["push_day_count"] += 1

l_days = get_days_ago(img.get("last_pulled_at"))
bucket_age(l_days, stats["pull_ages"])
stats["push_count"] += 1
bucket_age(get_days_ago(img.get("last_pulled_at")), stats["pull_ages"])

print(f"{idx:<5} | {account['account_id']:<15} | {region:<12} | {val['repo_name']:<45} | {len(images):<10} | {mut:<12} | {'YES' if val.get('has_lifecycle')=='True' else 'NO'}")
print(f"{idx:<4} | {account['account_id']:<15} | {region:<12} | {val['repo_name']:<40} | {repo_size/(1024**3):<10.2f} | {repo_mut:<11} | {'YES' if val.get('has_lifecycle')=='True' else 'NO':<7} | {repo_vulns['CRITICAL']:<8} | {repo_vulns['HIGH']:<8}")

# Aggregated Results
total_gb = stats["total_bytes"] / (1024**3)
# Footers
avg_img_mb = (stats["total_bytes"] / stats["total_images"]) / (1024**2) if stats["total_images"] > 0 else 0
avg_push_age = stats["total_push_days"] / stats["push_day_count"] if stats["push_day_count"] > 0 else 0
avg_push_age = stats["total_push_days"] / stats["push_count"] if stats["push_count"] > 0 else 0

print("-" * report_width)
print(f"ORGANIZATION IMAGE FOOTPRINT SUMMARY")
print(f" --- Image Storage Statistics ---")
print(f" Total Images Found: {stats['total_images']:,}")
print(f" Total Image Storage: {total_gb:.2f} GB")
print(f" Average Image Size: {avg_img_mb:.2f} MB")

print(f"\n --- Aging & Lifecycle ---")
print(f" Average Image Age: {avg_push_age:.1f} days")
print(f" Images Older > 1yr: {stats['push_ages']['365+']} pushed | {stats['pull_ages']['365+']} pulled")
print(f" Repos w/o Lifecycle: {stats['no_lifecycle']} (Critical Gap)")
print(f"ORGANIZATION ECR SUMMARY\n")
print(f" --- Config & Storage ---")
print(f" Repos: {stats['total_repos']} ({stats['mut']['IMMUTABLE']} Immutable / {stats['mut']['MUTABLE']} Mutable) | Missing Lifecycle: {stats['no_lc']}")
print(f" Total Data: {stats['total_bytes']/(1024**3):.2f} GB | Average Image Size: {avg_img_mb:.2f} MB\n")

print(f"\n --- Regional Image Storage Breakdown ---")
for reg, r_bytes in sorted(stats["region_bytes"].items(), key=lambda x: x[1], reverse=True):
print(f" - {reg:<15}: {r_bytes/(1024**3):>8.2f} GB")
print(f" --- Security & Vulnerabilities ---")
print(f" Scan Coverage: {stats['scanned_imgs']} of {stats['total_images']} images scanned")
print(f" Org Totals: " + " | ".join([f"{s}: {stats['org_vulns'][s]:,}" for s in ["CRITICAL", "HIGH", "MEDIUM", "LOW"]]))

print(f"\n Age Distribution (Days Since Action):")
print(f" Bucket | Pushed Count | Last Pull Count")
print(f" ----------- | ------------ | ---------------")
print(f"\n --- Age Distribution (Days since Push vs Pull) ---")
print(f" Average Image Age: {avg_push_age:.1f} days")
print(f" Bucket | Pushed Count | Pulled Count")
for b in ['<30', '30-59', '60-89', '90-179', '180-364', '365+']:
print(f" {b:<11} | {stats['push_ages'][b]:<12} | {stats['pull_ages'][b]}")
print(f" {b:<11} | {stats['push_ages'][b]:<12} | {stats['pull_ages'][b]}")
print("-" * report_width)

if __name__ == "__main__":
main()
if __name__ == "__main__": main()
22 changes: 17 additions & 5 deletions local-app/python-tools/cross-organization/check_ecr.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,36 @@
from datetime import datetime

# --- VERSIONING ---
__version__ = "1.0.0"
__version__ = "1.2.0"

def get_repo_images(ecr_client, repo_name):
"""Fetches details for all images in a repository."""
"""Fetches images and their scan results."""
images = []
repo_total_size = 0
try:
# describe_images provides the findingSeverityCounts summary directly
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

# Extract scan summary if available
scan_summary = img.get('imageScanFindingsSummary', {})
severity_counts = scan_summary.get('findingSeverityCounts', {})

images.append({
"image_tags": img.get('imageTags', []),
"image_digest": img.get('imageDigest'),
"pushed_at": img['imagePushedAt'].isoformat() if 'imagePushedAt' in img else "N/A",
"last_pulled_at": img['lastRecordedPullTime'].isoformat() if 'lastRecordedPullTime' in img else "N/A",
"status": img.get('imageStatus', 'ACTIVE'),
"size_bytes": img.get('imageSizeInBytes', 0)
"scan_status": img.get('imageScanStatus', {}).get('status', 'NO_SCAN'),
"severity_counts": severity_counts, # Restored: CVE Severity Counts
"size_bytes": size
})
except: pass
return images
return images, repo_total_size


def get_lifecycle_policy(ecr_client, repo_name):
"""Checks for lifecycle policy and counts rules."""
Expand Down

0 comments on commit b0a9760

Please sign in to comment.