diff --git a/local-app/python-tools/cross-organization/check_config.py b/local-app/python-tools/cross-organization/check_config.py index ac973e70..0b052216 100644 --- a/local-app/python-tools/cross-organization/check_config.py +++ b/local-app/python-tools/cross-organization/check_config.py @@ -1,9 +1,38 @@ -def account_task(account_session, account_id, account_name, region): - """ - Task: Deep Audit of AWS Config across all regions. - Returns: Data structured by region for JSON/CSV processing. - """ - results = {"alias": "None", "status": "SUCCESS", "data": {}} +import re +from datetime import datetime, timedelta + +def get_s3_metrics(session, bucket_name, region): + """Fetches NumberOfObjects and BucketSizeBytes from CloudWatch.""" + cw = session.client('cloudwatch', region_name=region) + metrics = {"bucket_size_bytes": 0, "object_count": 0} + + end = datetime.utcnow() + start = end - timedelta(days=2) + + try: + for metric_name in ['BucketSizeBytes', 'NumberOfObjects']: + response = cw.get_metric_statistics( + Namespace='AWS/S3', + MetricName=metric_name, + Dimensions=[ + {'Name': 'BucketName', 'Value': bucket_name}, + {'Name': 'StorageType', 'Value': 'StandardStorage'} + ], + StartTime=start, + EndTime=end, + Period=86400, + Statistics=['Average'] + ) + + if response['Datapoints']: + key = "bucket_size_bytes" if metric_name == 'BucketSizeBytes' else "object_count" + metrics[key] = int(response['Datapoints'][-1]['Average']) + except: + pass # Return 0s if metrics fail (e.g., bucket in different region) + return metrics + +def account_task(account_session, account_id, account_name, region, bucket_regex=".*"): + results = {"alias": "None", "data": {}} try: iam = account_session.client('iam') @@ -15,37 +44,28 @@ def account_task(account_session, account_id, account_name, region): for reg in regions: config = account_session.client('config', region_name=reg) + channels = config.describe_delivery_channels().get('DeliveryChannels', []) - # Fetch Config state - recorders = config.describe_configuration_recorders().get('ConfigurationRecorders', []) - delivery = config.describe_delivery_channels().get('DeliveryChannels', []) - retention = config.describe_retention_configurations().get('RetentionConfigurations', []) - - # Store regional metrics reg_data = { - "recorder_enabled": "False", - "global_resources": "False", "s3_bucket": "N/A", - "sns_topic": "N/A", - "delivery_freq": "N/A", - "retention_days": "2555 (7yr)" + "bucket_regex_match": "N/A", + "bucket_size_bytes": 0, + "object_count": 0 } - if recorders: - reg_data["recorder_enabled"] = "True" - reg_data["global_resources"] = str(recorders[0].get('recordingGroup', {}).get('includeGlobalResourceTypes', False)) - - if delivery: - reg_data["s3_bucket"] = delivery[0].get('s3BucketName', 'N/A') - reg_data["sns_topic"] = delivery[0].get('snsTopicARN', 'N/A').split(':')[-1] - reg_data["delivery_freq"] = delivery[0].get('configSnapshotDeliveryProperties', {}).get('deliveryFrequency', '24h') - - if retention: - reg_data["retention_days"] = str(retention[0].get('RetentionPeriodInDays')) + if channels: + bucket = channels[0].get('s3BucketName', 'N/A') + reg_data["s3_bucket"] = bucket + reg_data["bucket_regex_match"] = str(bool(re.match(bucket_regex, bucket))) + + # Fetch metrics if bucket exists + if bucket != "N/A": + metrics = get_s3_metrics(account_session, bucket, reg) + reg_data.update(metrics) results["data"][reg] = reg_data except Exception as e: - results["status"] = f"ERROR: {str(e)}" + results["error"] = str(e) return results diff --git a/local-app/python-tools/cross-organization/org_runner.py b/local-app/python-tools/cross-organization/org_runner.py index 10802871..b308ce93 100755 --- a/local-app/python-tools/cross-organization/org_runner.py +++ b/local-app/python-tools/cross-organization/org_runner.py @@ -7,18 +7,19 @@ import json import csv import time +import re import importlib from datetime import datetime from pathlib import Path # --- VERSIONING --- -__version__ = "1.3.0" +__version__ = "1.4.0" class OrgTaskRunner: def __init__(self, args): self.args = args self.output_log = [] - self.full_results = [] # Internal list of dicts for JSON/CSV + self.full_results = [] self.hierarchy_cache = {} self.start_time = 0 self.org_id = "unknown" @@ -37,7 +38,6 @@ def run(self): self.org_id = org.describe_organization()['Organization']['Id'] except: pass - # Load dynamic tasks tasks = [] if self.args.enable_checks: sys.path.append(os.getcwd()) @@ -52,7 +52,7 @@ def run(self): all_accounts.sort(key=lambda x: x['Name' if self.args.sort == 'name' else 'Id'].lower()) - self.log_print(f"--- Starting Org Run (v{__version__}) | Accounts: {len(all_accounts)} ---") + self.log_print(f"--- Org Run v{__version__} | Regex: {self.args.central_bucket_regex} ---") for i, acc in enumerate(all_accounts, 1): acc_id, acc_name = acc['Id'], acc['Name'] @@ -70,10 +70,10 @@ def run(self): account_data = {"account_id": acc_id, "account_name": acc_name, "checks": {}} for t_func in tasks: - # Capture raw data from task - res = t_func(m_sess, acc_id, acc_name, self.args.region) + # Pass the regex to the task function + res = t_func(m_sess, acc_id, acc_name, self.args.region, self.args.central_bucket_regex) account_data["alias"] = res.get("alias") - account_data["checks"] = res.get("data", {}) # Expecting a dict of regions + account_data["checks"] = res.get("data", {}) self.full_results.append(account_data) self.log_print(f"[{i}/{len(all_accounts)}] Processed {acc_name}") @@ -86,11 +86,9 @@ def run(self): ds = datetime.now().strftime("%Y-%m-%dT%H%M%S") base = self.args.output if self.args.output != 'DEFAULT' else f"audit_{self.org_id}" - # 1. Save JSON with open(f"{base}.{ds}.json", 'w') as f: json.dump(self.full_results, f, indent=2) - # 2. Save CSV (Long Format) with open(f"{base}.{ds}.csv", 'w', newline='') as f: writer = csv.writer(f) writer.writerow(["account_id", "account_alias", "region", "field_name", "field_value"]) @@ -109,4 +107,5 @@ def run(self): p.add_argument("--sort", choices=['id', 'name'], default='name') p.add_argument("--output", nargs='?', const='DEFAULT') p.add_argument("--enable-checks", nargs='+') + p.add_argument("--central-bucket-regex", default=".*", help="Regex to validate Config S3 buckets") OrgTaskRunner(p.parse_args()).run()