Welcome back Traveler..
In today’s mission we’ll go over how to “list IAM users and their email” (if they have such tag defined).
Yes, we can get all that info from a console and follow ‘clicky, clicky’ but that’s not how we operate and plus we want speeeed.
Also as a powerful sorcerer, one of the most important tools in Your arsenal is the ability to gather information and keep track of the people and resources under Your control. If you are not a sorcerer just yet, fear not.. as through our journey together you’ll become one very quickly.
The code we’re going to be working with makes use of the boto3 library, which is the official AWS SDK for Python. It allows us to harness the power of the cloud and interact with a wide range of AWS services, making it a powerful tool for any sorcerer.
import boto3 import configparser import csv from botocore.config import Config from botocore.exceptions import ClientError
We start by importing the necessary libraries. We have boto3, which we’ll be using to interact with the AWS IAM service, configparser for parsing the profile list file, csv for working with the CSV file we’re going to generate, and botocore.config and botocore.exceptions, which we’ll use for retries and error handling.
config = Config( retries = dict( max_attempts = 10 ) ) config_file = '/home/user1/.aws/.python-profiles.conf' report_file = ("/tmp/users_and_tags.csv") with open(report_file, "w") as file: file.write("USER;EMAIL_TAG;EMAIL_TAG_VALUE;ACCOUNT_NAME;ACCOUNT_NR\n")
Next, we have the Config object, which is used to configure retries. In this case, we’re setting the maximum number of attempts to 10, a powerful spell that ensures that our incantations will be successful, even in the face of adversity. We then set the paths for the config file and the report file that we’ll be generating. The config file is where we specify the profiles we want to work with, and the report file is where we’ll be storing the information we gather.
def read_profile_list(config_file): config = configparser.ConfigParser() config.read(config_file) profile_list = config.get('multipleProfiles', 'profile_list') profile_list = profile_list.split(' ') return profile_list
The read_profile_list function takes in the config file path and reads the file using the configparser library. It then retrieves the list of profiles specified in the file and splits it into a list of strings. This list of profiles is then returned, allowing us to cast our spell across multiple realms.
def get_email_tag_from_users(profileName): session = boto3.Session(profile_name=profileName) iam = session.client("iam",region_name='us-east-1', config=config) sts = session.client("sts",region_name='us-east-1', config=config) account_alias = iam.list_account_aliases() accountName = account_alias['AccountAliases'][0] print(f"Gathering information from {accountName} ..") whoami = sts.get_caller_identity() accountNr = whoami['Account']
We get the account name and number, then use the IAM client to list all the users.
users = iam.list_users() tags = [] email_tag_value = "" file = open(report_file, "a") for user in users['Users']: userName = user['UserName'] # CHECK IF EMAIL TAG EXISTS ( email or Email or ignore case) tag_list = iam.list_user_tags(UserName=userName) email_tag = list(filter(lambda x:x['Key'] == "email", tag_list["Tags"])) email_tag2 = list(filter(lambda x:x['Key'] == "Email", tag_list["Tags"]))
For each user, we check if an email tag exists, and if it does, we retrieve its value and write it to the report file along with the user’s name and the account name and number. If an email tag doesn’t exist, we write “N/A” to the report file, a powerful spell that ensures that we always have the information we need, even in the face of missing data.
if len(email_tag) > 0: user_email = email_tag[0]["Value"] write_line = (f"{userName};email;{user_email};{accountName};{accountNr}") with open(report_file, "a") as file: print(write_line, file=file) elif len(email_tag2) > 0: user_email = email_tag2[0]["Value"] write_line = (f"{userName};Email;{user_email};{accountName};{accountNr}") with open(report_file, "a") as file: print(write_line, file=file) else: user_email = "N/A" write_line = (f"{userName};MISSING;{user_email};{accountName};{accountNr}") with open(report_file, "a") as file: print(write_line, file=file)
We also have a function pretty_ssv(filename) which prints the report file in a tabular format.
def pretty_ssv(filename): with open(filename) as f: rows = list(csv.reader(f, delimiter=';')) num_columns = len(rows[0]) column_widths = [max(len(row[i]) for row in rows) for i in range(num_columns)] for row in rows: print(' '.join(f'{cell:<{column_widths[i]}}' for i, cell in enumerate(row)))
At last, we call the read_profile_list(config_file) function to read the profile list, and then iterate over the list of profiles, calling the get_email_tag_from_users(profileName) function for each profile.
profile_list = read_profile_list(config_file) for p in profile_list: profileName = p.replace('"', '') get_email_tag_from_users(profileName)
Example output from the script:
[user1@user1-vm python 2023-01-17_00:11:16] (main)$ ./list-users-and-tags.py Gathering information from <your-account-name> ..
Full code available on GITHUB:
https://github.com/wozoopa/python/blob/main/list-users-and-tags.py
That’s all in this short mission..
Until next time Traveler..