Finding an EC2 Instance in an AWS Organization Member Account

How do I find an EC2 Instance in a large AWS organization?

I have recently faced the issue repeatedly: a server needs attention, and all info I get for it is a server name and that it’s hosted in AWS. No account ID, no region, no instance ID, not even the team responsible for it.

Since our organization has enough accounts with enough regions that it’s completely unfeasible to look for them manually, I’ve created a small python script that loops through all member accounts, loops through all regions, and grabs the EC2 instances with a specific tag.

Technical Reasons

EC2 uses two naming schemas for the instances – an Instance ID, which is a globally unique identifier for your instance, and a Name, which is set through the Name resource tag. The names set through the tags are the ones usually used to identify servers (at least in my environment), and they cannot be queried through AWS Config – at least not at the time of writing.

Due to this limitation, finding an instance in a large organization can be annoying if you have nothing to go on. This script should alleviate that.

Script

The script itself is simple. You need to execute it with the organization management account’s profile. It grabs every account ID that doesn’t belong to a suspended account, then assumes the default OrganizationAccountAccessRole in all of them, then loops through all regions, and looks for an instance with the correct tags. It takes one functional argument, -s --server , which probably needs no explanations.

import botocore
import boto3
import argparse
import logging

# Function to set up logging with an option for verbose output.
def setup_logging(verbose=False):
    logging.basicConfig(level=logging.DEBUG if verbose else logging.INFO)
    logging.getLogger('botocore').setLevel(logging.CRITICAL)
    logging.getLogger('boto3').setLevel(logging.CRITICAL)

# Function to retrieve all accounts from an AWS Organization.
# It uses AWS Organizations' Paginator to handle large numbers of accounts efficiently.
# The function returns a list of accounts.
def get_all_accounts(org_client, accounts=[]) -> list:
    accounts = []
    paginator = org_client.get_paginator('list_accounts')
    accs = paginator.paginate()

    for acc in accs:
        for a in acc['Accounts']:
            accounts.append(a)

    return(accounts)

# Function to find a server (EC2 instance) within an AWS Organization by its name.
def find_server_in_org(name, org_client):
    acc = get_all_accounts(org_client)

    for a in acc:
        if a['Status'] != 'SUSPENDED':
            # Constructs the ARN for the role to be assumed in the account
            role_arn = f'arn:aws:iam::{a["Id"]}:role/OrganizationAccountAccessRole'

            # Creates a STS client and assumes the specified role
            sts_connection = boto3.client('sts')
            acct_b = sts_connection.assume_role(
                RoleArn=role_arn,
                RoleSessionName="valtma-eni-discovery"
            )

            # Extracts temporary credentials from the assumed role
            r = {}

            r['ACCESS_KEY'] = acct_b['Credentials']['AccessKeyId']
            r['SECRET_KEY'] = acct_b['Credentials']['SecretAccessKey']
            r['SESSION_TOKEN'] = acct_b['Credentials']['SessionToken']

            # Creates an EC2 client with the temporary credentials
            ec2 = boto3.client(
                'ec2',
                aws_access_key_id=r['ACCESS_KEY'],
                aws_secret_access_key = r['SECRET_KEY'],
                aws_session_token = r['SESSION_TOKEN']
            )

            # Retrieves a list of all regions to search for the instance
            regions = ec2.describe_regions()

            for region in regions['Regions']:
                # Creates an EC2 client for each region
                ec2_instances = boto3.client(
                    'ec2',
                    region_name=region['RegionName'],
                    aws_access_key_id=r['ACCESS_KEY'],
                    aws_secret_access_key = r['SECRET_KEY'],
                    aws_session_token = r['SESSION_TOKEN']
                )

                # Searches for instances with the specified name in the region
                instances=ec2_instances.describe_instances(
                    Filters=[
                        {
                            'Name': 'tag:Name',
                            'Values': [name]
                        }
                    ]
                )

                # Checks if any instances were found and logs the information
                if len(instances['Reservations']) != 0:
                    logging.info(f"Found {name} in {region['RegionName']} in account {a['Id']}")
                    return 0

# Main function that sets up command line argument parsing and initiates the server search.
# It takes the server name and verbosity level as command line arguments.
# The AWS Organizations client is created and passed to the find server function.
def main():
    parser = argparse.ArgumentParser()
    parser.add_argument(
        '-s',
        '--server',
        help='Name of the server to search for',
        required=True
    )
    parser.add_argument(
        '-v',
        '--verbose',
        action='store_true',
        help='Enable verbose output',
        required=False
    )
    args = parser.parse_args()

    name = args.server
    setup_logging(args.verbose)

    logging.info(f"Searching for server: {name}")

    org_client = boto3.client('organizations')

    find_server_in_org(name, org_client)

if __name__ == '__main__':
    main()

 

I work in a large company, where all IT departments are terribly understaffed. If you’ve made it here, you’re probably in the same boat: people with little cloud knowledge working with cloud, people having permissions they shouldn’t have, and you having to support shadow IT.

While I do like to bounce tickets back for undocumented servers, a lot of the people who need an urgent check or security group adjustment are not the ones responsible for it, and I don’t like being a dick for no reason.

Leave a Reply