Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
422541b
Updated with enhancements due to case-sensitivity in the API
paulbayer Sep 6, 2024
5a8ecf4
Updated to remove region being separate from the ocredentials object.…
paulbayer Sep 6, 2024
d446636
Updated exception logic to find elastic load balancers with no securi…
paulbayer Sep 9, 2024
0388b19
Merge remote-tracking branch 'origin/feature/find_security_groups' in…
paulbayer Sep 10, 2024
75dfa0e
Merge remote-tracking branch 'origin/main'
paulbayer Sep 10, 2024
f6b6d42
Brought files in line with both Inventory_Script repos
paulbayer Sep 10, 2024
e59fa50
Updated with changes from customer work...
paulbayer Sep 25, 2024
898dc2e
Merge pull request #3 from cloud-foundations-on-aws/main
paulbayer Sep 25, 2024
ba6bd3f
Updated all_my_enis.py with some additional cosmetic updates.
paulbayer Oct 24, 2024
0924bff
Updated with some additional cosmetic updates.
paulbayer Oct 24, 2024
e21ac50
Updated all_my_functions.py with proper error type.
paulbayer Oct 26, 2024
47092ac
Updates on progress bars, and using the right variables.
paulbayer Oct 26, 2024
e4931f3
Merge branch 'refs/heads/feature/find_security_groups'
paulbayer Oct 26, 2024
cd31022
Merge pull request #4 from cloud-foundations-on-aws/main
paulbayer Oct 28, 2024
3a463fd
Removed inventory-scripts directory
paulbayer Apr 4, 2025
bbf46bd
Merge commit '28ab3522ac32421430534e72151b5c5d2359841a' as 'inventory…
paulbayer Apr 4, 2025
28ab352
Squashed 'inventory-scripts/' content from commit 14d5610
paulbayer Apr 4, 2025
d91bfa4
Changed inventory-scripts to a subtree and updated the gitignore file
paulbayer Apr 4, 2025
3b20eed
Updated gitignore file
paulbayer Apr 4, 2025
00f2b8d
Merge branch 'cloud-foundations-on-aws:main' into main
paulbayer Apr 4, 2025
8198d1c
Merge branch 'main' into subtree-try
paulbayer Apr 4, 2025
c7b7e5f
Removed unneeded files from subtree
paulbayer Apr 4, 2025
8d88e02
Removed unneeded files
paulbayer Apr 4, 2025
657de61
Updated gitignore again
paulbayer Apr 4, 2025
18552a0
Squashed 'inventory-scripts/' changes from 14d5610..eaf0432
paulbayer Apr 8, 2025
a69f599
Merge commit '18552a042e8b2bc27e5d3db37b27312bf60ba4a7' into subtree-try
paulbayer Apr 8, 2025
f1398c3
Squashed 'inventory-scripts/' changes from eaf04325..15d25f3d
paulbayer Apr 9, 2025
ef2becc
Merge commit 'f1398c38de53e4b42c80ceb42f1279a10413bab0'
paulbayer Apr 9, 2025
9eb52ac
Squashed 'inventory-scripts/' changes from 15d25f3d..3e4d66be
paulbayer Apr 9, 2025
bd32551
Merge commit '9eb52ac0265143ea287df799adb7131e42f50e7d'
paulbayer Apr 9, 2025
53f69e0
Squashed 'inventory-scripts/' changes from 3e4d66be..a2de0ab3
paulbayer Apr 10, 2025
baba9b8
Merge commit '53f69e016eb2857eb4490ddfb4aa7af8b521c607'
paulbayer Apr 10, 2025
1096999
Squashed 'inventory-scripts/' changes from a2de0ab3..d7173c46
paulbayer Apr 21, 2025
1d72478
Merge commit '10969992860f3d0b686e1c2c5d6b1069933dfbd7'
paulbayer Apr 21, 2025
f188209
Squashed 'inventory-scripts/' changes from d7173c46..14e06dd7
paulbayer Oct 28, 2025
a445fcd
Merge commit 'f1882098bac0f155a56d6592e2be728735404c4e' into update-i…
paulbayer Oct 28, 2025
ba267f1
Merge branch 'main' into update-inventory-scripts-20251028
paulbayer Oct 28, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions inventory-scripts/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -352,3 +352,7 @@ src/.package

my_venv/
/stackoverflow_test/
.gitallowed
aws_organization
*.txt
*.csv
8 changes: 5 additions & 3 deletions inventory-scripts/DrawOrg.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from colorama import init, Fore
from ArgumentsClass import CommonArguments

__version__ = '2025.04.04'
__version__ = '2025.09.26'

init()

Expand All @@ -21,7 +21,8 @@
ou_fillcolor = 'burlywood'
ou_shape = 'box'
# aws_policy_type_list = ['SERVICE_CONTROL_POLICY', 'TAG_POLICY', 'BACKUP_POLICY',
# 'AISERVICES_OPT_OUT_POLICY', 'CHATBOT_POLICY', 'RESOURCE_CONTROL_POLICY', 'DECLARATIVE_POLICY_EC2']
# 'AISERVICES_OPT_OUT_POLICY', 'CHATBOT_POLICY', 'RESOURCE_CONTROL_POLICY',
# 'DECLARATIVE_POLICY_EC2', 'SECURITYHUB_POLICY']


#####################
Expand Down Expand Up @@ -182,6 +183,8 @@ def create_policy_nodes(f_enabled_policy_types: list):
policy_type = 'chatbot'
elif policy['Type'] == 'DECLARATIVE_POLICY_EC2':
policy_type = 'dcp'
elif policy['Type'] == 'SECURITYHUB_POLICY':
policy_type = 'sec'
else:
policy_type = policy['Type']

Expand Down Expand Up @@ -298,7 +301,6 @@ def traverse_ous_and_accounts(ou_id: str):
max_accounts_per_ou = find_max_accounts_per_ou(froot, max_accounts_per_ou)

# This tries to verticalize the diagram, so it doesn't look like a wide mess
dot.attr(rankdir='LR')
dot_unflat = dot.unflatten(stagger=round_up(max_accounts_per_ou / 5))

# Save the diagram to a PNG file
Expand Down
62 changes: 46 additions & 16 deletions inventory-scripts/Inventory_Modules.py
Original file line number Diff line number Diff line change
Expand Up @@ -4579,7 +4579,9 @@ def display_results(results_list, fdisplay_dict: dict, defaultAction=None, file_

Enhancements:
- How to create a break between rows, like after every account, or Management Org, or region, or whatever...
- How to do sub-sections, where there is more data to show per row...

TODO: Documentation on how to do the sub-sections - which has been written, but not explained.
- How to do sub-sections, where there is more data to show per row...
"""

def handle_list():
Expand Down Expand Up @@ -4912,6 +4914,7 @@ def get_all_credentials(fProfiles: list = None, fTiming: bool = False, fSkipProf
import logging
from account_class import aws_acct_access
# from time import time
from botocore.exceptions import NoCredentialsError
from colorama import init, Fore

init()
Expand All @@ -4930,11 +4933,15 @@ def get_all_credentials(fProfiles: list = None, fTiming: bool = False, fSkipProf
fRegionList = ['us-east-1']
if fProfiles is None: # Default use case from the classes
print("Getting Accounts to check: ", end='')
aws_acct = aws_acct_access()
# This doesn't mean the profile "default", this is just what the label for the Org Name will be, since there's no other text
profile = '-default-'
try:
aws_acct = aws_acct_access()
except NoCredentialsError as my_Error:
logging.error(f"Credentials error: {my_Error}")
raise Exception(f"Credential Error")
# This doesn't mean the profile "default" or 'None', this is just what the label for the Org Name will be, since there's no other text
profile = 'None'
RegionList = get_regions3(aws_acct, fRegionList)
logging.info(f"No profile string passed in. Using string '-default-'")
logging.info(f"No profile string passed in. Using string 'None'")
# This should populate the list "AllCreds" with the credentials for the relevant accounts.
AllCredentials.extend(get_credentials_for_accounts_in_org(aws_acct, fSkipAccounts, fRootOnly, fAccounts, profile, RegionList, RoleList, fTiming))
else:
Expand Down Expand Up @@ -5115,7 +5122,7 @@ def run(self):
return AllCreds


def get_org_accounts_from_profiles(fProfileList):
def get_org_accounts_from_profiles(fProfileList=None):
"""
Note that this function returns account_class objects based on the list of profiles passed to it
This function is fairly slow since it needs to call the aws_acct_access function for each profile.
Expand All @@ -5139,7 +5146,8 @@ def run(self):
while True:
# Get the work from the queue and expand the tuple
profile = self.queue.get()
pbar.update()
if profile is None:
break
Account = {'ErrorFlag' : False,
'Success' : False,
'RootAcct' : False,
Expand Down Expand Up @@ -5213,27 +5221,49 @@ def run(self):
logging.error("Credentials Error")
logging.error(my_Error)
finally:
# logging.info(f"Account: {Account}") ## Remove
# print(f"Account: {Account}") ## Remove
self.queue.task_done()
pbar.update()
AllAccounts.append(Account)

# print(f"ProfileList from within the mt function: {fProfileList}") ## Remove
AllAccounts = []
profilequeue = Queue()
# WorkerThreads = len(fProfileList)
WorkerThreads = 2
if fProfileList:
WorkerThreads = min(len(fProfileList), 2)
else:
WorkerThreads = 1
threads = []

# Create x worker threads
for x in range(WorkerThreads):
worker = AssembleCredentials(profilequeue)
# Setting daemon to True will let the main thread exit even though the workers are blocking
worker.daemon = True
worker.start()
threads.append(worker)

pbar = tqdm(desc=f'Getting accounts from {len(fProfileList)} profiles',
total=len(fProfileList)
)

for profile_item in fProfileList:
logging.info(f"Queuing profile {profile_item} / {len(fProfileList)} profiles")
profilequeue.put(profile_item)
if fProfileList is None:
logging.info("No profiles were found, using environment variables")
logging.error("No profiles were found, using environment variables")
pbar = tqdm(desc=f'Getting account from EnvVar', total=1)
profilequeue.put('EnvVar')
else:
logging.info(f"List of profiles being looked at is: {fProfileList}")
logging.error(f"List of profiles being looked at is: {fProfileList}")
pbar = tqdm(desc=f'Getting accounts from {len(fProfileList)} profiles',
total=len(fProfileList))
for profile_item in fProfileList:
profilequeue.put(profile_item)
profilequeue.join()
# Sends 'sentinel value' to threads to tell them we're done
for _ in threads:
logging.debug(f"Sending 'None' to queue, to ensure all threads finish")
profilequeue.put(None)
# Waits for all threads to be finished
for t in threads:
logging.debug(f"Waiting for thread {t.name} to finish")
t.join()
# print(f"AllAccounts - from within the multi-threaded function, just before return: {AllAccounts}") ## Remove
return AllAccounts
12 changes: 12 additions & 0 deletions inventory-scripts/RunOnMultiAccounts.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,17 @@ def participant_user(faws_acct, create=None, username=None):
return_response['ErrorMessage'] = ErrorMessage
return return_response


def display_firewall_manager(ocredentials):
"""
Description: Determine if firewall manager is running in this account
@param ocredentials: Credentials
@return:
"""
child_acct = aws_acct_access(ocredentials=ocredentials)
fw_mgr_client = child_acct.session.client('fms')
# response = fw_mgr_client.
# response =
#####################


Expand All @@ -173,6 +184,7 @@ def participant_user(faws_acct, create=None, username=None):
Put more commands here... Or you can write functions that represent your commands and call them from here.
"""
credentials = Inventory_Modules.get_child_access3(aws_acct, account_num, 'us-east-1', ['reinvent-Admin'])
display_firewall_manager(credentials)
tgt_aws_access = aws_acct_access(ocredentials=credentials)
username = 'Paul'
user_response = participant_user(tgt_aws_access, username=username)
Expand Down
6 changes: 4 additions & 2 deletions inventory-scripts/SC_Products_to_CFN_Stacks.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,9 @@ def run(self):
)
# The above command fails if the stack found (by the find_stacks3 function) has been deleted
# The following section determines the NEW Account's AccountEmail and AccountID
AccountEmail = AccountID = AccountStatus = None
AccountEmail = None
AccountID = None
AccountStatus = None
if 'Parameters' in stack_info['Stacks'][0].keys() and len(stack_info['Stacks'][0]['Parameters']) > 0:
for y in range(len(stack_info['Stacks'][0]['Parameters'])):
if stack_info['Stacks'][0]['Parameters'][y]['ParameterKey'] == 'AccountEmail':
Expand Down Expand Up @@ -273,7 +275,7 @@ def main():
# print(f"{ERASE_LINE}Found {len(result['ProductViewDetails'])} products | Total found: {len(prod_ids)}", end='\r')
# print()
for product in prod_ids:
if product['ProductViewSummary']['Name'].find('Account-Vending-Machine') > 0:
if product['ProductViewSummary']['Name'].find('Account-Vending-Machine') > 0 or product['ProductViewSummary']['Name'].find('AWS Control Tower Account Factory') > 0:
AVM_prod_id = product['ProductViewSummary']['ProductId']
elif pFragment is not None and not pExact:
result = client_sc.search_products_as_admin()
Expand Down
85 changes: 49 additions & 36 deletions inventory-scripts/Tests/common_test_functions.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from botocore import client
from botocore import session
import pytest
# import pytest

ERASE_LINE = '\x1b[2K'

Expand Down Expand Up @@ -28,7 +28,10 @@ def AWSKeyID_from_AWSAccount(AWSAccountNumber):
return AWSKey


def _amend_create_boto3_session(test_data, mocker):
def _amend_create_boto3_session(test_data, verbosity, mocker):
import logging
logging.basicConfig(level=verbosity, format="[%(filename)s:%(lineno)s - %(funcName)20s() ] %(message)s")

orig = session.Session.create_client

def amend_create_client(
Expand All @@ -46,12 +49,12 @@ def amend_create_client(
):
# Intercept boto3 Session, in hopes of sending back a client that includes the Account Number
# if aws_access_key_id == '*****AccessKeyHere*****':
print(test_data['FunctionName'])
logging.info(test_data['FunctionName'])
if aws_access_key_id == 'MeantToFail':
print(f"Failed Access Key: {aws_access_key_id}")
logging.info(f"Failed Access Key: {aws_access_key_id}")
return ()
else:
print(f"Not Failed Access Key: {aws_access_key_id}")
logging.info(f"Not Failed Access Key: {aws_access_key_id}")
return_response = orig(self,
service_name,
region_name,
Expand All @@ -69,7 +72,10 @@ def amend_create_client(
print()


def _amend_make_api_call_orig(test_key, test_value, mocker):
def _amend_make_api_call_orig(test_key, test_value, verbosity, mocker):
import logging
logging.basicConfig(level=verbosity, format="[%(filename)s:%(lineno)s - %(funcName)20s() ] %(message)s")

orig = client.BaseClient._make_api_call

def amend_make_api_call(self, operation_name, kwargs):
Expand All @@ -81,23 +87,26 @@ def amend_make_api_call(self, operation_name, kwargs):
if isinstance(test_value, Exception):
raise test_value
# Implied break and exit of the function here...
print(f"Operation Name mocked: {operation_name}\n"
f"Key Name: {test_key}\n"
f"kwargs: {kwargs}\n"
f"mocked return_response: {test_value}")
logging.info(f"Operation Name mocked: {operation_name}\n"
f"Key Name: {test_key}\n"
f"kwargs: {kwargs}\n"
f"mocked return_response: {test_value}")
return test_value

return_response = orig(self, operation_name, kwargs)
print(f"Operation Name passed through: {operation_name}\n"
f"Key name: {test_key}\n"
f"kwargs: {kwargs}\n"
f"Actual return response: {return_response}")
logging.info(f"Operation Name passed through: {operation_name}\n"
f"Key name: {test_key}\n"
f"kwargs: {kwargs}\n"
f"Actual return response: {return_response}")
return return_response

mocker.patch('botocore.client.BaseClient._make_api_call', new=amend_make_api_call)


def _amend_make_api_call(meta_key_dict, test_dict, mocker):
def _amend_make_api_call(meta_key_dict, test_dict, verbosity, mocker):
import logging
logging.basicConfig(level=verbosity, format="[%(filename)s:%(lineno)s - %(funcName)20s() ] %(message)s")

orig = client.BaseClient._make_api_call

def amend_make_api_call(self, operation_name, kwargs):
Expand All @@ -112,25 +121,28 @@ def amend_make_api_call(self, operation_name, kwargs):
if isinstance(test_value, Exception):
# Implied break and exit of the function here...
raise test_value
print(f"Operation Name mocked: {operation_name}\n"
f"Function Name: {meta_key_dict['FunctionName']}\n"
f"kwargs: {kwargs}\n"
f"mocked return_response: {op_name['test_result']}")
logging.info(f"Operation Name mocked: {operation_name}\n"
f"Function Name: {meta_key_dict['FunctionName']}\n"
f"kwargs: {kwargs}\n"
f"mocked return_response: {op_name['test_result']}")
return op_name['test_result']
try:
print(f"Trying: Operation Name passed through: {operation_name}\n"
f"Key Name: {meta_key_dict['FunctionName']}\n"
f"kwargs: {kwargs}\n")
logging.info(f"Trying: Operation Name passed through: {operation_name}\n"
f"Key Name: {meta_key_dict['FunctionName']}\n"
f"kwargs: {kwargs}\n")
return_response = orig(self, operation_name, kwargs)
print(f"Actual return_response: {return_response}")
logging.info(f"Actual return_response: {return_response}")
except Exception as my_Error:
raise ConnectionError("Operation Failed")
return return_response

mocker.patch('botocore.client.BaseClient._make_api_call', new=amend_make_api_call)


def _amend_make_api_call_specific(meta_key_dict, test_dict, mocker):
def _amend_make_api_call_specific(meta_key_dict, test_dict, verbosity, mocker):
import logging
logging.basicConfig(level=verbosity, format="[%(filename)s:%(lineno)s - %(funcName)20s() ] %(message)s")

orig = client.BaseClient._make_api_call

def amend_make_api_call(self, operation_name, kwargs):
Expand All @@ -152,30 +164,31 @@ def amend_make_api_call(self, operation_name, kwargs):
break
if test_value is not None and isinstance(test_value, Exception):
# Implied break and exit of the function here...
print("Expected Error...")
logging.info("Expected Error...")
raise test_value
elif test_value is None:
print(f"No test data offered for this credentials in region {region}")
logging.info(f"No test data offered for this credentials in region {region}")
continue
print(f"Operation Name mocked: {operation_name}\n"
f"Function Name: {meta_key_dict['FunctionName']}\n"
f"kwargs: {kwargs}\n"
f"mocked return_response: {test_value}")
logging.info(f"Operation Name mocked: {operation_name}\n"
f"Function Name: {meta_key_dict['FunctionName']}\n"
f"kwargs: {kwargs}\n"
f"mocked return_response: {test_value}")
return test_value

print(f"Operation Name passed through: {operation_name}\n"
f"Function Name: {meta_key_dict['FunctionName']}\n"
f"kwargs: {kwargs}\n")
logging.info(f"Operation Name passed through: {operation_name}\n"
f"Function Name: {meta_key_dict['FunctionName']}\n"
f"kwargs: {kwargs}\n")
return_response = orig(self, operation_name, kwargs)
print(f"Actual return_response: {return_response}")
logging.info(f"Actual return_response: {return_response}")
return return_response

mocker.patch('botocore.client.BaseClient._make_api_call', new=amend_make_api_call)
# mocker.patch('botocore.session', new=amend_make_api_call)


# mocker.patch('botocore.session', new=amend_make_api_call)


def mock_find_all_instances2(creds:dict, region:str):
def mock_find_all_instances2(creds: dict, region: str):
"""
This is a mock function that will return a list of all the instances in the region that we're looking for.
:param creds: Credentials object, where 'AccountNumber' is the account number of the account that we're looking for.
Expand Down
3 changes: 2 additions & 1 deletion inventory-scripts/Tests/test_Inventory_Modules.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,12 @@ def test_get_all_credentials(parameters, test_value_dict, mocker):
pTiming = parameters['pTiming']
pRootOnly = parameters['pRootOnly']
pRoleList = parameters['pRoleList']
verbose = parameters['pverbose']
test_data = {'FunctionName' : 'get_all_credentials',
'AccountSpecific': True,
'RegionSpecific' : True
}
_amend_make_api_call(test_data, test_value_dict, mocker)
_amend_make_api_call(test_data, test_value_dict, verbose, mocker)

# if isinstance(test_value, Exception):
# print("Expected Error...")
Expand Down
Loading