Skip to content

Commit b4e780d

Browse files
authored
feat: Update inventory scripts with new script "all_my_ram_shares" (#103)
* Updated with enhancements due to case-sensitivity in the API * Updated to remove region being separate from the ocredentials object. Now it's a field within the object. * Updated exception logic to find elastic load balancers with no security groups attached * Brought files in line with both Inventory_Script repos * Updated with changes from customer work... * Updated all_my_enis.py with some additional cosmetic updates. * Updated with some additional cosmetic updates. * Updated all_my_functions.py with proper error type. * Updates on progress bars, and using the right variables. * Removed inventory-scripts directory * Squashed 'inventory-scripts/' content from commit 14d5610 git-subtree-dir: inventory-scripts git-subtree-split: 14d5610f6414518210d059b1c25da25a5975e4d4 * Changed inventory-scripts to a subtree and updated the gitignore file * Updated gitignore file * Removed unneeded files from subtree * Removed unneeded files * Updated gitignore again * Squashed 'inventory-scripts/' changes from 14d5610..eaf0432 eaf0432 Updated RDS script c0435ab Updated gitignore file c1d2b36 Updated to only check for enabled policy types. Should make this significantly faster. 0f2838d Merge remote-tracking branch 'origin/combined' into combined f3cb5a0 Updated small changes - mostly to version numbers edec2c6 Edit Discovery.md 3aea3d6 Updated files to sync public and private repos git-subtree-dir: inventory-scripts git-subtree-split: eaf04325b08f3241feba24183a0459dfde455106 * Squashed 'inventory-scripts/' changes from eaf04325..15d25f3d 15d25f3d Renamed the profile when we can't find one, from "-default-" to "None" e745d92b Updated phzs script to remove regionality 6990f1d4 Updated orgs script for version 23434747 Updated orgs script to be able to run without a profile, using environment variables d7637b57 New files git-subtree-dir: inventory-scripts git-subtree-split: 15d25f3dfd6e51d4eba18ae5bf5d4f7080f27d51 * Squashed 'inventory-scripts/' changes from 15d25f3d..3e4d66be 3e4d66be Updated the account_class.py library to better support the use of environment variables for credentials, instead of profiles git-subtree-dir: inventory-scripts git-subtree-split: 3e4d66be90d9b863b78c7e11e070e2fa3ade5ebb * Squashed 'inventory-scripts/' changes from 3e4d66be..a2de0ab3 a2de0ab3 Updated tgw script to accommodate env vars as credentials, instead of a profile git-subtree-dir: inventory-scripts git-subtree-split: a2de0ab32c28e228363a5ec6a304385e68ad4d0f * Squashed 'inventory-scripts/' changes from a2de0ab3..d7173c46 d7173c46 Enabled the output to include the connected TGW for all resources. 39802ee8 Updating .gitignore with ignored suffixes 51c18421 Fixed imports f74a44e0 Updated with encoding for opened files 4d6ba49c Merge branch 'feature/find_global_accelerators' into development Added the .gitallowed file 09869a7b Merge branch 'test/development' into development Adding more test cases from a test/dev branch d9978000 Added gitallowed file for example secrets (account numbers) bb20c5c6 Updated the version of the account_class.py library 62a2ecca Updated the account_class.py library to better support the use of environment variables for credentials, instead of profiles e615c538 Updated gitignore file 3f78e6d7 More test cases git-subtree-dir: inventory-scripts git-subtree-split: d7173c4665b2d654a86a46c9c2e76ff2c74df12b * Squashed 'inventory-scripts/' changes from d7173c46..14e06dd7 14e06dd7 Updated for fqdn typo 55f1ce19 Version updates a16974ee Added / Updated policy listing 56059efa Added ability to resolve FQDNs to the ENI script ad7e718c Fixed error when short-form was used 9a477c58 Merge branch 'refs/heads/fix/mod_my_cfnstacksets' into development cfcd860d Added pytest to the requirements.txt 80d1ab30 Fixed problem for when profile is embedded in the Environment Variables 393c96c0 Made significant changes to all_my_orgs, and associated account classes, and supporting libraries 7ae901e7 Small comment changes 294132f9 Added new script (created by Q) for network visibility... 08be53f6 Updated 683a5276 Updated all_my_functions.py to fix errors when no functions were found 83fe2304 Updated for the case where the profile specified doesn't exist or work properly 95620a8d Fixed the tgw diagram naming issue git-subtree-dir: inventory-scripts git-subtree-split: 14e06dd70eebc9edde45aeeff24e305a08cd0df4
1 parent 71a267a commit b4e780d

21 files changed

+1472
-209
lines changed

inventory-scripts/.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,3 +352,7 @@ src/.package
352352

353353
my_venv/
354354
/stackoverflow_test/
355+
.gitallowed
356+
aws_organization
357+
*.txt
358+
*.csv

inventory-scripts/DrawOrg.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from colorama import init, Fore
99
from ArgumentsClass import CommonArguments
1010

11-
__version__ = '2025.04.04'
11+
__version__ = '2025.09.26'
1212

1313
init()
1414

@@ -21,7 +21,8 @@
2121
ou_fillcolor = 'burlywood'
2222
ou_shape = 'box'
2323
# aws_policy_type_list = ['SERVICE_CONTROL_POLICY', 'TAG_POLICY', 'BACKUP_POLICY',
24-
# 'AISERVICES_OPT_OUT_POLICY', 'CHATBOT_POLICY', 'RESOURCE_CONTROL_POLICY', 'DECLARATIVE_POLICY_EC2']
24+
# 'AISERVICES_OPT_OUT_POLICY', 'CHATBOT_POLICY', 'RESOURCE_CONTROL_POLICY',
25+
# 'DECLARATIVE_POLICY_EC2', 'SECURITYHUB_POLICY']
2526

2627

2728
#####################
@@ -182,6 +183,8 @@ def create_policy_nodes(f_enabled_policy_types: list):
182183
policy_type = 'chatbot'
183184
elif policy['Type'] == 'DECLARATIVE_POLICY_EC2':
184185
policy_type = 'dcp'
186+
elif policy['Type'] == 'SECURITYHUB_POLICY':
187+
policy_type = 'sec'
185188
else:
186189
policy_type = policy['Type']
187190

@@ -298,7 +301,6 @@ def traverse_ous_and_accounts(ou_id: str):
298301
max_accounts_per_ou = find_max_accounts_per_ou(froot, max_accounts_per_ou)
299302

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

304306
# Save the diagram to a PNG file

inventory-scripts/Inventory_Modules.py

Lines changed: 46 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4579,7 +4579,9 @@ def display_results(results_list, fdisplay_dict: dict, defaultAction=None, file_
45794579
45804580
Enhancements:
45814581
- How to create a break between rows, like after every account, or Management Org, or region, or whatever...
4582-
- How to do sub-sections, where there is more data to show per row...
4582+
4583+
TODO: Documentation on how to do the sub-sections - which has been written, but not explained.
4584+
- How to do sub-sections, where there is more data to show per row...
45834585
"""
45844586

45854587
def handle_list():
@@ -4912,6 +4914,7 @@ def get_all_credentials(fProfiles: list = None, fTiming: bool = False, fSkipProf
49124914
import logging
49134915
from account_class import aws_acct_access
49144916
# from time import time
4917+
from botocore.exceptions import NoCredentialsError
49154918
from colorama import init, Fore
49164919

49174920
init()
@@ -4930,11 +4933,15 @@ def get_all_credentials(fProfiles: list = None, fTiming: bool = False, fSkipProf
49304933
fRegionList = ['us-east-1']
49314934
if fProfiles is None: # Default use case from the classes
49324935
print("Getting Accounts to check: ", end='')
4933-
aws_acct = aws_acct_access()
4934-
# 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
4935-
profile = '-default-'
4936+
try:
4937+
aws_acct = aws_acct_access()
4938+
except NoCredentialsError as my_Error:
4939+
logging.error(f"Credentials error: {my_Error}")
4940+
raise Exception(f"Credential Error")
4941+
# 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
4942+
profile = 'None'
49364943
RegionList = get_regions3(aws_acct, fRegionList)
4937-
logging.info(f"No profile string passed in. Using string '-default-'")
4944+
logging.info(f"No profile string passed in. Using string 'None'")
49384945
# This should populate the list "AllCreds" with the credentials for the relevant accounts.
49394946
AllCredentials.extend(get_credentials_for_accounts_in_org(aws_acct, fSkipAccounts, fRootOnly, fAccounts, profile, RegionList, RoleList, fTiming))
49404947
else:
@@ -5115,7 +5122,7 @@ def run(self):
51155122
return AllCreds
51165123

51175124

5118-
def get_org_accounts_from_profiles(fProfileList):
5125+
def get_org_accounts_from_profiles(fProfileList=None):
51195126
"""
51205127
Note that this function returns account_class objects based on the list of profiles passed to it
51215128
This function is fairly slow since it needs to call the aws_acct_access function for each profile.
@@ -5139,7 +5146,8 @@ def run(self):
51395146
while True:
51405147
# Get the work from the queue and expand the tuple
51415148
profile = self.queue.get()
5142-
pbar.update()
5149+
if profile is None:
5150+
break
51435151
Account = {'ErrorFlag' : False,
51445152
'Success' : False,
51455153
'RootAcct' : False,
@@ -5213,27 +5221,49 @@ def run(self):
52135221
logging.error("Credentials Error")
52145222
logging.error(my_Error)
52155223
finally:
5224+
# logging.info(f"Account: {Account}") ## Remove
5225+
# print(f"Account: {Account}") ## Remove
52165226
self.queue.task_done()
5227+
pbar.update()
52175228
AllAccounts.append(Account)
52185229

5230+
# print(f"ProfileList from within the mt function: {fProfileList}") ## Remove
52195231
AllAccounts = []
52205232
profilequeue = Queue()
5221-
# WorkerThreads = len(fProfileList)
5222-
WorkerThreads = 2
5233+
if fProfileList:
5234+
WorkerThreads = min(len(fProfileList), 2)
5235+
else:
5236+
WorkerThreads = 1
5237+
threads = []
52235238

52245239
# Create x worker threads
52255240
for x in range(WorkerThreads):
52265241
worker = AssembleCredentials(profilequeue)
52275242
# Setting daemon to True will let the main thread exit even though the workers are blocking
52285243
worker.daemon = True
52295244
worker.start()
5245+
threads.append(worker)
52305246

5231-
pbar = tqdm(desc=f'Getting accounts from {len(fProfileList)} profiles',
5232-
total=len(fProfileList)
5233-
)
5234-
5235-
for profile_item in fProfileList:
5236-
logging.info(f"Queuing profile {profile_item} / {len(fProfileList)} profiles")
5237-
profilequeue.put(profile_item)
5247+
if fProfileList is None:
5248+
logging.info("No profiles were found, using environment variables")
5249+
logging.error("No profiles were found, using environment variables")
5250+
pbar = tqdm(desc=f'Getting account from EnvVar', total=1)
5251+
profilequeue.put('EnvVar')
5252+
else:
5253+
logging.info(f"List of profiles being looked at is: {fProfileList}")
5254+
logging.error(f"List of profiles being looked at is: {fProfileList}")
5255+
pbar = tqdm(desc=f'Getting accounts from {len(fProfileList)} profiles',
5256+
total=len(fProfileList))
5257+
for profile_item in fProfileList:
5258+
profilequeue.put(profile_item)
52385259
profilequeue.join()
5260+
# Sends 'sentinel value' to threads to tell them we're done
5261+
for _ in threads:
5262+
logging.debug(f"Sending 'None' to queue, to ensure all threads finish")
5263+
profilequeue.put(None)
5264+
# Waits for all threads to be finished
5265+
for t in threads:
5266+
logging.debug(f"Waiting for thread {t.name} to finish")
5267+
t.join()
5268+
# print(f"AllAccounts - from within the multi-threaded function, just before return: {AllAccounts}") ## Remove
52395269
return AllAccounts

inventory-scripts/RunOnMultiAccounts.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,17 @@ def participant_user(faws_acct, create=None, username=None):
151151
return_response['ErrorMessage'] = ErrorMessage
152152
return return_response
153153

154+
155+
def display_firewall_manager(ocredentials):
156+
"""
157+
Description: Determine if firewall manager is running in this account
158+
@param ocredentials: Credentials
159+
@return:
160+
"""
161+
child_acct = aws_acct_access(ocredentials=ocredentials)
162+
fw_mgr_client = child_acct.session.client('fms')
163+
# response = fw_mgr_client.
164+
# response =
154165
#####################
155166

156167

@@ -173,6 +184,7 @@ def participant_user(faws_acct, create=None, username=None):
173184
Put more commands here... Or you can write functions that represent your commands and call them from here.
174185
"""
175186
credentials = Inventory_Modules.get_child_access3(aws_acct, account_num, 'us-east-1', ['reinvent-Admin'])
187+
display_firewall_manager(credentials)
176188
tgt_aws_access = aws_acct_access(ocredentials=credentials)
177189
username = 'Paul'
178190
user_response = participant_user(tgt_aws_access, username=username)

inventory-scripts/SC_Products_to_CFN_Stacks.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,9 @@ def run(self):
9292
)
9393
# The above command fails if the stack found (by the find_stacks3 function) has been deleted
9494
# The following section determines the NEW Account's AccountEmail and AccountID
95-
AccountEmail = AccountID = AccountStatus = None
95+
AccountEmail = None
96+
AccountID = None
97+
AccountStatus = None
9698
if 'Parameters' in stack_info['Stacks'][0].keys() and len(stack_info['Stacks'][0]['Parameters']) > 0:
9799
for y in range(len(stack_info['Stacks'][0]['Parameters'])):
98100
if stack_info['Stacks'][0]['Parameters'][y]['ParameterKey'] == 'AccountEmail':
@@ -273,7 +275,7 @@ def main():
273275
# print(f"{ERASE_LINE}Found {len(result['ProductViewDetails'])} products | Total found: {len(prod_ids)}", end='\r')
274276
# print()
275277
for product in prod_ids:
276-
if product['ProductViewSummary']['Name'].find('Account-Vending-Machine') > 0:
278+
if product['ProductViewSummary']['Name'].find('Account-Vending-Machine') > 0 or product['ProductViewSummary']['Name'].find('AWS Control Tower Account Factory') > 0:
277279
AVM_prod_id = product['ProductViewSummary']['ProductId']
278280
elif pFragment is not None and not pExact:
279281
result = client_sc.search_products_as_admin()

inventory-scripts/Tests/common_test_functions.py

Lines changed: 49 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from botocore import client
22
from botocore import session
3-
import pytest
3+
# import pytest
44

55
ERASE_LINE = '\x1b[2K'
66

@@ -28,7 +28,10 @@ def AWSKeyID_from_AWSAccount(AWSAccountNumber):
2828
return AWSKey
2929

3030

31-
def _amend_create_boto3_session(test_data, mocker):
31+
def _amend_create_boto3_session(test_data, verbosity, mocker):
32+
import logging
33+
logging.basicConfig(level=verbosity, format="[%(filename)s:%(lineno)s - %(funcName)20s() ] %(message)s")
34+
3235
orig = session.Session.create_client
3336

3437
def amend_create_client(
@@ -46,12 +49,12 @@ def amend_create_client(
4649
):
4750
# Intercept boto3 Session, in hopes of sending back a client that includes the Account Number
4851
# if aws_access_key_id == '*****AccessKeyHere*****':
49-
print(test_data['FunctionName'])
52+
logging.info(test_data['FunctionName'])
5053
if aws_access_key_id == 'MeantToFail':
51-
print(f"Failed Access Key: {aws_access_key_id}")
54+
logging.info(f"Failed Access Key: {aws_access_key_id}")
5255
return ()
5356
else:
54-
print(f"Not Failed Access Key: {aws_access_key_id}")
57+
logging.info(f"Not Failed Access Key: {aws_access_key_id}")
5558
return_response = orig(self,
5659
service_name,
5760
region_name,
@@ -69,7 +72,10 @@ def amend_create_client(
6972
print()
7073

7174

72-
def _amend_make_api_call_orig(test_key, test_value, mocker):
75+
def _amend_make_api_call_orig(test_key, test_value, verbosity, mocker):
76+
import logging
77+
logging.basicConfig(level=verbosity, format="[%(filename)s:%(lineno)s - %(funcName)20s() ] %(message)s")
78+
7379
orig = client.BaseClient._make_api_call
7480

7581
def amend_make_api_call(self, operation_name, kwargs):
@@ -81,23 +87,26 @@ def amend_make_api_call(self, operation_name, kwargs):
8187
if isinstance(test_value, Exception):
8288
raise test_value
8389
# Implied break and exit of the function here...
84-
print(f"Operation Name mocked: {operation_name}\n"
85-
f"Key Name: {test_key}\n"
86-
f"kwargs: {kwargs}\n"
87-
f"mocked return_response: {test_value}")
90+
logging.info(f"Operation Name mocked: {operation_name}\n"
91+
f"Key Name: {test_key}\n"
92+
f"kwargs: {kwargs}\n"
93+
f"mocked return_response: {test_value}")
8894
return test_value
8995

9096
return_response = orig(self, operation_name, kwargs)
91-
print(f"Operation Name passed through: {operation_name}\n"
92-
f"Key name: {test_key}\n"
93-
f"kwargs: {kwargs}\n"
94-
f"Actual return response: {return_response}")
97+
logging.info(f"Operation Name passed through: {operation_name}\n"
98+
f"Key name: {test_key}\n"
99+
f"kwargs: {kwargs}\n"
100+
f"Actual return response: {return_response}")
95101
return return_response
96102

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

99105

100-
def _amend_make_api_call(meta_key_dict, test_dict, mocker):
106+
def _amend_make_api_call(meta_key_dict, test_dict, verbosity, mocker):
107+
import logging
108+
logging.basicConfig(level=verbosity, format="[%(filename)s:%(lineno)s - %(funcName)20s() ] %(message)s")
109+
101110
orig = client.BaseClient._make_api_call
102111

103112
def amend_make_api_call(self, operation_name, kwargs):
@@ -112,25 +121,28 @@ def amend_make_api_call(self, operation_name, kwargs):
112121
if isinstance(test_value, Exception):
113122
# Implied break and exit of the function here...
114123
raise test_value
115-
print(f"Operation Name mocked: {operation_name}\n"
116-
f"Function Name: {meta_key_dict['FunctionName']}\n"
117-
f"kwargs: {kwargs}\n"
118-
f"mocked return_response: {op_name['test_result']}")
124+
logging.info(f"Operation Name mocked: {operation_name}\n"
125+
f"Function Name: {meta_key_dict['FunctionName']}\n"
126+
f"kwargs: {kwargs}\n"
127+
f"mocked return_response: {op_name['test_result']}")
119128
return op_name['test_result']
120129
try:
121-
print(f"Trying: Operation Name passed through: {operation_name}\n"
122-
f"Key Name: {meta_key_dict['FunctionName']}\n"
123-
f"kwargs: {kwargs}\n")
130+
logging.info(f"Trying: Operation Name passed through: {operation_name}\n"
131+
f"Key Name: {meta_key_dict['FunctionName']}\n"
132+
f"kwargs: {kwargs}\n")
124133
return_response = orig(self, operation_name, kwargs)
125-
print(f"Actual return_response: {return_response}")
134+
logging.info(f"Actual return_response: {return_response}")
126135
except Exception as my_Error:
127136
raise ConnectionError("Operation Failed")
128137
return return_response
129138

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

132141

133-
def _amend_make_api_call_specific(meta_key_dict, test_dict, mocker):
142+
def _amend_make_api_call_specific(meta_key_dict, test_dict, verbosity, mocker):
143+
import logging
144+
logging.basicConfig(level=verbosity, format="[%(filename)s:%(lineno)s - %(funcName)20s() ] %(message)s")
145+
134146
orig = client.BaseClient._make_api_call
135147

136148
def amend_make_api_call(self, operation_name, kwargs):
@@ -152,30 +164,31 @@ def amend_make_api_call(self, operation_name, kwargs):
152164
break
153165
if test_value is not None and isinstance(test_value, Exception):
154166
# Implied break and exit of the function here...
155-
print("Expected Error...")
167+
logging.info("Expected Error...")
156168
raise test_value
157169
elif test_value is None:
158-
print(f"No test data offered for this credentials in region {region}")
170+
logging.info(f"No test data offered for this credentials in region {region}")
159171
continue
160-
print(f"Operation Name mocked: {operation_name}\n"
161-
f"Function Name: {meta_key_dict['FunctionName']}\n"
162-
f"kwargs: {kwargs}\n"
163-
f"mocked return_response: {test_value}")
172+
logging.info(f"Operation Name mocked: {operation_name}\n"
173+
f"Function Name: {meta_key_dict['FunctionName']}\n"
174+
f"kwargs: {kwargs}\n"
175+
f"mocked return_response: {test_value}")
164176
return test_value
165177

166-
print(f"Operation Name passed through: {operation_name}\n"
167-
f"Function Name: {meta_key_dict['FunctionName']}\n"
168-
f"kwargs: {kwargs}\n")
178+
logging.info(f"Operation Name passed through: {operation_name}\n"
179+
f"Function Name: {meta_key_dict['FunctionName']}\n"
180+
f"kwargs: {kwargs}\n")
169181
return_response = orig(self, operation_name, kwargs)
170-
print(f"Actual return_response: {return_response}")
182+
logging.info(f"Actual return_response: {return_response}")
171183
return return_response
172184

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

176187

188+
# mocker.patch('botocore.session', new=amend_make_api_call)
189+
177190

178-
def mock_find_all_instances2(creds:dict, region:str):
191+
def mock_find_all_instances2(creds: dict, region: str):
179192
"""
180193
This is a mock function that will return a list of all the instances in the region that we're looking for.
181194
:param creds: Credentials object, where 'AccountNumber' is the account number of the account that we're looking for.

inventory-scripts/Tests/test_Inventory_Modules.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,12 @@ def test_get_all_credentials(parameters, test_value_dict, mocker):
3535
pTiming = parameters['pTiming']
3636
pRootOnly = parameters['pRootOnly']
3737
pRoleList = parameters['pRoleList']
38+
verbose = parameters['pverbose']
3839
test_data = {'FunctionName' : 'get_all_credentials',
3940
'AccountSpecific': True,
4041
'RegionSpecific' : True
4142
}
42-
_amend_make_api_call(test_data, test_value_dict, mocker)
43+
_amend_make_api_call(test_data, test_value_dict, verbose, mocker)
4344

4445
# if isinstance(test_value, Exception):
4546
# print("Expected Error...")

0 commit comments

Comments
 (0)