Created
July 1, 2025 10:14
-
-
Save abdallah/a8d2d090b4dfa9b63ad2d2a8a0741001 to your computer and use it in GitHub Desktop.
AWS Transit Gateway CIDR Route Checker
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| """ | |
| AWS Transit Gateway CIDR Route Checker | |
| This script checks if a specific CIDR block exists in an AWS Transit Gateway route table | |
| and provides information about the associated account and attachment. | |
| Requirements: | |
| - boto3 (latest version) | |
| - AWS CLI configured with appropriate profiles | |
| - AWS credentials with permissions for: | |
| - ec2:SearchTransitGatewayRoutes | |
| - ec2:DescribeTransitGatewayAttachments | |
| - organizations:DescribeAccount (for account name lookup) | |
| Author: Abdallah Deeb <abdallah@deeb.me> | |
| Version: 1.1 | |
| """ | |
| import argparse | |
| import boto3 | |
| import ipaddress | |
| import sys | |
| from botocore.exceptions import ClientError, NoCredentialsError, ProfileNotFound | |
| def validate_cidr(cidr_block): | |
| """ | |
| Validate that the provided string is a valid CIDR block. | |
| Args: | |
| cidr_block (str): The CIDR block to validate | |
| Returns: | |
| bool: True if valid CIDR block, False otherwise | |
| """ | |
| try: | |
| ipaddress.ip_network(cidr_block, strict=False) | |
| return True | |
| except ValueError: | |
| return False | |
| def check_cidr_and_get_account(tgw_route_table_id, cidr_block, network_profile='network', org_profile='root'): | |
| """ | |
| Check if a CIDR block exists in a Transit Gateway route table and get associated account info. | |
| Args: | |
| tgw_route_table_id (str): The Transit Gateway Route Table ID | |
| cidr_block (str): The CIDR block to search for (e.g., '10.0.0.0/16') | |
| network_profile (str): AWS profile for EC2 operations (default: 'network') | |
| org_profile (str): AWS profile for Organizations operations (default: 'root') | |
| Returns: | |
| dict: Dictionary containing route information or None if not found | |
| """ | |
| # Validate CIDR block format | |
| if not validate_cidr(cidr_block): | |
| print(f"Error: '{cidr_block}' is not a valid CIDR block format.") | |
| return None | |
| try: | |
| # Create session with the specified profile | |
| session = boto3.Session(profile_name=network_profile) | |
| ec2_client = session.client('ec2') | |
| except ProfileNotFound: | |
| print(f"Error: AWS profile '{network_profile}' not found. Please check your AWS configuration.") | |
| return None | |
| except NoCredentialsError: | |
| print("Error: No AWS credentials found. Please configure your AWS credentials.") | |
| return None | |
| try: | |
| response = ec2_client.search_transit_gateway_routes( | |
| TransitGatewayRouteTableId=tgw_route_table_id, | |
| Filters=[ | |
| { | |
| 'Name': 'state', | |
| 'Values': ['active'] | |
| }, | |
| { | |
| 'Name': 'route-search.exact-match', | |
| 'Values': [cidr_block] | |
| } | |
| ] | |
| ) | |
| found_routes = response.get('Routes', []) | |
| if not found_routes: | |
| print(f"✗ CIDR block '{cidr_block}' not found in route table '{tgw_route_table_id}'.") | |
| return None | |
| route_info = [] | |
| for route in found_routes: | |
| route_data = { | |
| 'cidr_block': cidr_block, | |
| 'route_table_id': tgw_route_table_id, | |
| 'destination_cidr': route.get('DestinationCidrBlock'), | |
| 'state': route.get('State'), | |
| 'type': route.get('Type') | |
| } | |
| attachments = route.get('TransitGatewayAttachments', []) | |
| if not attachments: | |
| print(f"⚠ Found route for '{cidr_block}' but no attachment ID was associated with it.") | |
| route_data['status'] = 'no_attachment' | |
| route_info.append(route_data) | |
| continue | |
| attachment_id = attachments[0].get('TransitGatewayAttachmentId') | |
| route_data['attachment_id'] = attachment_id | |
| if attachment_id: | |
| try: | |
| attachment_response = ec2_client.describe_transit_gateway_attachments( | |
| TransitGatewayAttachmentIds=[attachment_id] | |
| ) | |
| attachment_details = attachment_response.get('TransitGatewayAttachments', []) | |
| if attachment_details: | |
| attachment = attachment_details[0] | |
| account_id = attachment.get('ResourceOwnerId') | |
| resource_type = attachment.get('ResourceType') | |
| resource_id = attachment.get('ResourceId') | |
| route_data.update({ | |
| 'account_id': account_id, | |
| 'resource_type': resource_type, | |
| 'resource_id': resource_id, | |
| 'attachment_state': attachment.get('State') | |
| }) | |
| print(f"✓ CIDR block '{cidr_block}' found in route table '{tgw_route_table_id}'") | |
| print(f" └─ Attachment ID: {attachment_id}") | |
| print(f" └─ Resource Type: {resource_type}") | |
| print(f" └─ Resource ID: {resource_id}") | |
| print(f" └─ Account ID: {account_id}") | |
| # Try to get account name from Organizations | |
| try: | |
| org_session = boto3.Session(profile_name=org_profile) | |
| org_client = org_session.client('organizations') | |
| account_details = org_client.describe_account(AccountId=account_id) | |
| account_name = account_details['Account']['Name'] | |
| route_data['account_name'] = account_name | |
| print(f" └─ Account Name: {account_name}") | |
| except ProfileNotFound: | |
| print(f" └─ Account Name: Unable to retrieve (profile '{org_profile}' not found)") | |
| except ClientError as org_e: | |
| error_code = org_e.response['Error']['Code'] | |
| if error_code == 'AccessDenied': | |
| print(f" └─ Account Name: Access denied to Organizations service") | |
| elif error_code == 'AccountNotFound': | |
| print(f" └─ Account Name: Account {account_id} not found in organization") | |
| else: | |
| print(f" └─ Account Name: Unable to retrieve ({error_code})") | |
| except Exception as org_e: | |
| print(f" └─ Account Name: Unable to retrieve ({str(org_e)})") | |
| else: | |
| print(f"⚠ Found route for '{cidr_block}' but could not retrieve details for attachment '{attachment_id}'.") | |
| route_data['status'] = 'attachment_details_unavailable' | |
| except ClientError as e: | |
| error_code = e.response['Error']['Code'] | |
| if error_code == 'InvalidTransitGatewayAttachmentID.NotFound': | |
| print(f"⚠ Attachment '{attachment_id}' not found.") | |
| else: | |
| print(f"⚠ Error retrieving attachment details: {error_code}") | |
| route_data['status'] = f'error_{error_code}' | |
| route_info.append(route_data) | |
| return route_info | |
| except ClientError as e: | |
| error_code = e.response['Error']['Code'] | |
| if error_code == 'InvalidRouteTableID.NotFound': | |
| print(f"Error: Transit Gateway Route Table '{tgw_route_table_id}' not found.") | |
| elif error_code == 'UnauthorizedOperation': | |
| print(f"Error: Insufficient permissions to access Transit Gateway route table.") | |
| else: | |
| print(f"AWS Error: {error_code} - {e.response['Error']['Message']}") | |
| return None | |
| except Exception as e: | |
| print(f"Unexpected error occurred: {e}") | |
| return None | |
| def main(): | |
| """Main function to handle command line arguments and execute the script.""" | |
| parser = argparse.ArgumentParser( | |
| description='Check if a CIDR block exists in an AWS Transit Gateway route table', | |
| formatter_class=argparse.RawDescriptionHelpFormatter, | |
| epilog=""" | |
| Examples: | |
| %(prog)s 10.0.0.0/16 | |
| %(prog)s 192.168.1.0/24 --route-table tgw-rtb-123456789abcdef0 | |
| %(prog)s 10.0.0.0/8 --network-profile my-network-profile --org-profile my-org-profile | |
| Required AWS Permissions: | |
| - ec2:SearchTransitGatewayRoutes | |
| - ec2:DescribeTransitGatewayAttachments | |
| - organizations:DescribeAccount (optional, for account name lookup) | |
| """ | |
| ) | |
| parser.add_argument( | |
| 'cidr_block', | |
| help='CIDR block to search for (e.g., 10.0.0.0/16)', | |
| nargs='?' | |
| ) | |
| parser.add_argument( | |
| '--route-table', '-r', | |
| default='tgw-rtb-057244a1527cfcf2c', | |
| help='Transit Gateway Route Table ID (default: tgw-rtb-057244a1527cfcf2c)' | |
| ) | |
| parser.add_argument( | |
| '--network-profile', '-n', | |
| default='network', | |
| help='AWS profile for EC2 operations (default: network)' | |
| ) | |
| parser.add_argument( | |
| '--org-profile', '-o', | |
| default='root', | |
| help='AWS profile for Organizations operations (default: root)' | |
| ) | |
| parser.add_argument( | |
| '--version', '-v', | |
| action='version', | |
| version='%(prog)s 1.1' | |
| ) | |
| args = parser.parse_args() | |
| # Get CIDR block from argument or prompt user | |
| if args.cidr_block: | |
| cidr_to_check = args.cidr_block | |
| else: | |
| try: | |
| cidr_to_check = input("Enter the CIDR block to check (e.g., 10.0.0.0/16): ").strip() | |
| if not cidr_to_check: | |
| print("Error: No CIDR block provided.") | |
| sys.exit(1) | |
| except KeyboardInterrupt: | |
| print("\nOperation cancelled by user.") | |
| sys.exit(0) | |
| if not args.route_table: | |
| print("Error: Transit Gateway Route Table ID is required.") | |
| sys.exit(1) | |
| print(f"Checking CIDR block: {cidr_to_check}") | |
| print(f"Transit Gateway Route Table: {args.route_table}") | |
| print("-" * 50) | |
| result = check_cidr_and_get_account( | |
| args.route_table, | |
| cidr_to_check, | |
| args.network_profile, | |
| args.org_profile | |
| ) | |
| if result is None: | |
| sys.exit(1) | |
| else: | |
| print("-" * 50) | |
| print(f"✓ Successfully found {len(result)} route(s) for CIDR {cidr_to_check}") | |
| if __name__ == "__main__": | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment