From d688d756bd47812ffadc02f24b6ec27b3cd2e20d Mon Sep 17 00:00:00 2001 From: badra001 Date: Tue, 10 Mar 2026 10:10:28 -0400 Subject: [PATCH] initial new modules --- .../cross-organization/check_iam_roles.py | 59 +++++++++++++++++++ .../cross-organization/remediate_tgw_dns.py | 34 +++++++---- 2 files changed, 82 insertions(+), 11 deletions(-) create mode 100644 local-app/python-tools/cross-organization/check_iam_roles.py diff --git a/local-app/python-tools/cross-organization/check_iam_roles.py b/local-app/python-tools/cross-organization/check_iam_roles.py new file mode 100644 index 00000000..1a117453 --- /dev/null +++ b/local-app/python-tools/cross-organization/check_iam_roles.py @@ -0,0 +1,59 @@ +import boto3 +import json + +# --- VERSIONING --- +__version__ = "1.0.0" + +def account_task(account_session, account_id, account_name, region): + """ + Scans IAM roles to list metadata, policies, boundaries, and tags. + IAM is global; this runs once per account. + """ + results = {"alias": "N/A", "data": {}} + try: + iam = account_session.client('iam') + results["alias"] = iam.list_account_aliases().get('AccountAliases', ["N/A"])[0] + + roles_paginator = iam.get_paginator('list_roles') + + for page in roles_paginator.paginate(): + for role in page['Roles']: + role_name = role['RoleName'] + + # 1. Attached (Managed) Policies + attached_policies = [] + attached_paginator = iam.get_paginator('list_attached_role_policies') + for attached_page in attached_paginator.paginate(RoleName=role_name): + for policy in attached_page['AttachedPolicies']: + attached_policies.append(policy['PolicyName']) + + # 2. Inline Policies + inline_policies = [] + inline_paginator = iam.get_paginator('list_role_policies') + for inline_page in inline_paginator.paginate(RoleName=role_name): + inline_policies.extend(inline_page['PolicyNames']) + + # 3. Permissions Boundary + boundary = role.get('PermissionsBoundary', {}) + boundary_name = boundary.get('PermissionsBoundaryArn', 'N/A').split('/')[-1] if boundary else 'N/A' + + # 4. Tags + # Boto3's list_roles returns tags in the main response structure + tags = {t['Key']: t['Value'] for t in role.get('Tags', [])} + + results["data"][role_name] = { + "resource": role['Arn'], + "role_name": role_name, + "path": role['Path'], + "attached_policies": attached_policies, + "inline_policies": inline_policies, + "permissions_boundary": boundary_name, + "tags": tags + } + + results["data"]["account_summary"] = {"_summary": f"ROLES:{len(results['data'])}"} + + except Exception as e: + results["error"] = str(e) + + return results diff --git a/local-app/python-tools/cross-organization/remediate_tgw_dns.py b/local-app/python-tools/cross-organization/remediate_tgw_dns.py index d049219f..1c058b25 100644 --- a/local-app/python-tools/cross-organization/remediate_tgw_dns.py +++ b/local-app/python-tools/cross-organization/remediate_tgw_dns.py @@ -1,9 +1,12 @@ +#!/bin/env python3 + import boto3 import sys import os +import argparse # --- VERSIONING --- -__version__ = "1.0.0" +__version__ = "1.1.0" def get_session(account_id, role_name="OrganizationAccountAccessRole"): """Assumes a role in the target account to return a boto3 session.""" @@ -25,16 +28,24 @@ def get_session(account_id, role_name="OrganizationAccountAccessRole"): return None def main(): - input_file = "remediate_tgw_dns.txt" - if not os.path.exists(input_file): - print(f"Error: {input_file} not found. Run assessment script first.") + parser = argparse.ArgumentParser(description="TGW VPC Attachment DNS Remediator") + parser.add_argument("--input", default="remediate_tgw_dns.txt", help="Target list file") + parser.add_argument("--rollback", action="store_true", help="Re-enable DNS Support instead of disabling it") + args = parser.parse_args() + + if not os.path.exists(args.input): + print(f"Error: {args.input} not found. Run the assessment script first.") sys.exit(1) + # Determine action based on flag + desired_state = "enable" if args.rollback else "disable" + action_label = "ROLLBACK (Enabling)" if args.rollback else "REMEDIATION (Disabling)" + print("-" * 100) - print(f"TGW DNS SUPPORT REMEDIATION SCRIPT | Version {__version__}") + print(f"TGW DNS SUPPORT {action_label} | Version {__version__}") print("-" * 100) - with open(input_file, 'r') as f: + with open(args.input, 'r') as f: lines = f.readlines() for line in lines: @@ -47,26 +58,27 @@ def main(): region = parts[1].strip() attach_id = parts[2].strip() - print(f"Processing: Account {acc_id} | Region {region} | Attachment {attach_id}...") + print(f"Target: Account {acc_id} | Region {region} | Attachment {attach_id}") session = get_session(acc_id) if not session: + print(f" SKIPPING: Unable to access account {acc_id}") continue ec2 = session.client('ec2', region_name=region) try: - # Perform the modification + # Perform the modification based on the desired state response = ec2.modify_transit_gateway_vpc_attachment( TransitGatewayAttachmentId=attach_id, - Options={'DnsSupport': 'disable'} + Options={'DnsSupport': desired_state} ) state = response['TransitGatewayVpcAttachment']['State'] - print(f" SUCCESS: Status is now '{state}'") + print(f" SUCCESS: DNS Support set to '{desired_state}'. Current state: {state}") except Exception as e: print(f" FAILED: {e}") print("-" * 100) - print("Remediation Complete.") + print(f"{action_label} Complete.") if __name__ == "__main__": main()