Skip to content

Commit

Permalink
update assessor
Browse files Browse the repository at this point in the history
  • Loading branch information
badra001 committed Jan 2, 2026
1 parent a67cc6b commit 0a5fa80
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 48 deletions.
116 changes: 75 additions & 41 deletions local-app/python-tools/cross-organization/assess_check_cloudtrail.py
Original file line number Diff line number Diff line change
@@ -1,65 +1,99 @@
#!/usr/bin/env python

#!/usr/bin/env python3
import json
import argparse
import re
import sys

# --- VERSIONING ---
__version__ = "1.0.3"
__version__ = "1.0.5"

def main():
parser = argparse.ArgumentParser(description="AWS Config Audit Assessor")
parser.add_argument("--input", required=True, help="JSON audit file from check_config")
parser.add_argument("--central-bucket-regex", required=True, help="Regex for corporate S3 standards")
parser = argparse.ArgumentParser(description="AWS CloudTrail Audit Assessor")
parser.add_argument("--input", required=True, help="JSON file from check_cloudtrail")
args = parser.parse_args()

try:
with open(args.input, 'r') as f:
data = json.load(f)
except Exception as e:
print(f"Error: {e}"); sys.exit(1)
except:
print("Error reading input file"); sys.exit(1)

print("-" * 125)
print(f"{'Account ID':<15} | {'Global Status':<12} | {'S3 Compliance'}")
print("-" * 125)
print("-" * 160)
print(f"{'Account ID':<15} | {'Global Summary':<25} | {'Active/Stopped':<15} | {'Security/Cost Issues'}")
print("-" * 160)

stats = {"objects": 0, "bytes": 0, "total_accounts": len(data)}
unique_buckets = set()
non_central_buckets = set()
stats = {
"s3_bytes": 0, "s3_objects": 0, "s3_buckets": set(),
"cw_bytes": 0, "cw_groups": 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
}

for account in data:
acc_id = account.get("account_id")
checks = account.get("data", {})
summary = checks.get("account_summary", {}).get("_summary", "UNKNOWN")

s3_issues = []
for reg, reg_data in checks.items():
if reg == "account_summary": continue
acc_active = 0
acc_stopped = 0
issues = []

if "DUPLICATIVE" in summary: issues.append("DUPLICATE_TRAILS")
elif "NO_TRAILS" in summary: issues.append("CRITICAL_NO_LOGGING")

for key, val in checks.items():
if not key.startswith("trail:"): continue

bucket = reg_data.get("s3_bucket", "N/A")
if bucket != "N/A":
unique_buckets.add(bucket)
stats["objects"] += reg_data.get("object_count", 0)
stats["bytes"] += reg_data.get("bucket_size_bytes", 0)

if not re.match(args.central_bucket_regex, bucket):
s3_issues.append(bucket)
non_central_buckets.add(bucket)

s3_status = f"NON_COMPLIANT ({len(s3_issues)} regions)" if s3_issues else "COMPLIANT"
print(f"{acc_id:<15} | {summary:<12} | {s3_status}")

# Summary Section
size_gb = stats["bytes"] / (1024**3)
print("-" * 125)
print(f"ORGANIZATION STORAGE SUMMARY (CONFIG):")
print(f" Total S3 Storage: {size_gb:.2f} GB")
print(f" Total S3 Objects: {stats['objects']:,}")
print(f" Total S3 Buckets: {len(unique_buckets)}")
print(f" Non-Central Buckets: {len(non_central_buckets)}")
print("-" * 125)
# Status tracking
if val.get("is_logging") == "True":
acc_active += 1; stats["logging_active"] += 1
else:
acc_stopped += 1; stats["logging_stopped"] += 1

# Type tracking
if val.get("is_org_trail") == "False":
stats["local_trails_total"] += 1
reg = val.get("home_region", "unknown")
stats["local_trails_by_reg"][reg] = stats["local_trails_by_reg"].get(reg, 0) + 1

# Storage rollup
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_groups"].add(val.get("trail_name"))
stats["cw_bytes"] += val["cw_logs_size_bytes"]

# Safe SNS topic access
sns = val.get("sns_topic")
if sns and sns != "N/A":
stats["sns_topics"].add(sns)

# Encryption breakdown
if val.get("kms_key_id") == "SSE-S3":
stats["sse_s3_count"] += 1
else:
stats["kms_cmk_count"] += 1

print(f"{acc_id:<15} | {summary:<25} | {f'{acc_active} ON / {acc_stopped} OFF':<15} | {', '.join(issues) if issues else 'COMPLIANT'}")

# Summary Footer
s3_gb = stats["s3_bytes"] / (1024**3)
cw_gb = stats["cw_bytes"] / (1024**3)

print("-" * 160)
print(f"ORGANIZATION CLOUDTRAIL FOOTPRINT:")
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'])} buckets")
print(f" CloudWatch Logs: {cw_gb:.2f} GB | {len(stats['cw_groups'])} log groups")
print(f" Notifications: {len(stats['sns_topics'])} SNS Topics")
print(f" Encryption: {stats['kms_cmk_count']} KMS CMK | {stats['sse_s3_count']} SSE-S3")
print(f" Local Trails: {stats['local_trails_total']} total")
for r, c in sorted(stats["local_trails_by_reg"].items()):
print(f" - {r:<15}: {c}")
print("-" * 160)

if __name__ == "__main__":
main()
15 changes: 8 additions & 7 deletions local-app/python-tools/cross-organization/check_cloudtrail.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,19 @@
import time
from datetime import datetime, timedelta

__version__ = "1.1.0"
__version__ = "1.1.1"

def get_s3_metrics(session, bucket_name, region):
cw = session.client('cloudwatch', region_name=region)
metrics = {"bucket_size_bytes": 0, "object_count": 0}
end = datetime.utcnow()
start = end - timedelta(days=2)
try:
# Check both Standard and All Storage types for full visibility
for metric, key in [('BucketSizeBytes', 'bucket_size_bytes'), ('NumberOfObjects', 'object_count')]:
r = cw.get_metric_statistics(
Namespace='AWS/S3', MetricName=metric,
Dimensions=[{'Name': 'BucketName', 'Value': bucket_name}, {'Name': 'StorageType', 'Value': 'StandardStorage' if key == 'bucket_size_bytes' else 'AllStorageTypes'}],
Dimensions=[{'Name': 'BucketName', 'Value': bucket_name},
{'Name': 'StorageType', 'Value': 'StandardStorage' if key == 'bucket_size_bytes' else 'AllStorageTypes'}],
StartTime=start, EndTime=end, Period=86400, Statistics=['Average']
)
if r['Datapoints']: metrics[key] = int(r['Datapoints'][-1]['Average'])
Expand Down Expand Up @@ -50,8 +50,7 @@ def account_task(account_session, account_id, account_name, region):

for reg in enabled_regions:
ct = account_session.client('cloudtrail', region_name=reg)
# describe_trails with IncludeShadowTrails=True is powerful but we loop to catch
# trails that might be single-region in non-standard regions.
# IncludeShadowTrails catches regional replications of Org/Multi-region trails
trails = ct.describe_trails(includeShadowTrails=True).get('trailList', [])

for trail in trails:
Expand All @@ -65,6 +64,7 @@ def account_task(account_session, account_id, account_name, region):
else: local_trail_count += 1

t_name = trail['Name']
# Use .get() for optional fields like SnsTopicARN and KmsKeyId
t_data = {
"trail_name": t_name,
"home_region": trail.get('HomeRegion', reg),
Expand All @@ -73,8 +73,9 @@ def account_task(account_session, account_id, account_name, region):
"is_multi_region": str(trail.get('IsMultiRegionTrail', False)),
"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": 0 # Placeholder for loop timing if needed
"check_elapsed_sec": 0
}

if 'CloudWatchLogsLogGroupArn' in trail:
Expand All @@ -85,7 +86,7 @@ def account_task(account_session, account_id, account_name, region):

results["data"][f"trail:{t_name}:{t_data['home_region']}"] = t_data

# Summary Logic
# Logic summary
summary = "OK"
if org_trail_count > 0 and local_trail_count > 0:
summary = f"DUPLICATIVE/{local_trail_count}_LOCAL_WITH_ORG"
Expand Down

0 comments on commit 0a5fa80

Please sign in to comment.