From a3bd618ff14f44c59e8521e4939a5546594bb71c Mon Sep 17 00:00:00 2001 From: mgeeky Date: Tue, 19 Nov 2019 11:20:03 +0100 Subject: [PATCH 1/5] Introduced support for --profile parameter and started working towards better Ctrl-C handling. --- enumerate-iam.py | 32 +++++++++++++++++++++++++------- enumerate_iam/main.py | 9 +++++++++ 2 files changed, 34 insertions(+), 7 deletions(-) diff --git a/enumerate-iam.py b/enumerate-iam.py index c2b3bf5..0d827b7 100755 --- a/enumerate-iam.py +++ b/enumerate-iam.py @@ -1,24 +1,42 @@ #!/usr/bin/env python +import sys import argparse +from boto3 import Session from enumerate_iam.main import enumerate_iam - def main(): parser = argparse.ArgumentParser(description='Enumerate IAM permissions') - parser.add_argument('--access-key', help='AWS access key', required=True) - parser.add_argument('--secret-key', help='AWS secret key', required=True) + parser.add_argument('--profile', help='AWS profile name fetched from credentials file. Specify this parameter or access-key and secret-key manually.') + parser.add_argument('--access-key', help='AWS access key if profile was not used') + parser.add_argument('--secret-key', help='AWS secret key if profile was not used') parser.add_argument('--session-token', help='STS session token') parser.add_argument('--region', help='AWS region to send API requests to', default='us-east-1') args = parser.parse_args() - enumerate_iam(args.access_key, - args.secret_key, - args.session_token, + if args.profile and (args.access_key or args.secret_key or args.session_token): + sys.stderr.write('error: Profile and raw AWS credential options are mutually exclusive.\n') + sys.stderr.write(' Please specify either --profile or --access-key and --secret-key.\n') + sys.exit(2) + + access_key = args.access_key + secret_key = args.secret_key + session_token = args.session_token + + if args.profile: + session = Session(profile_name = args.profile) + credentials = session.get_credentials() + currcreds = credentials.get_frozen_credentials() + access_key = currcreds.access_key + secret_key = currcreds.secret_key + session_token = currcreds.token + + enumerate_iam(access_key, + secret_key, + session_token, args.region) - if __name__ == '__main__': main() diff --git a/enumerate_iam/main.py b/enumerate_iam/main.py index fee5601..9762fef 100644 --- a/enumerate_iam/main.py +++ b/enumerate_iam/main.py @@ -33,6 +33,7 @@ MAX_THREADS = 25 CLIENT_POOL = {} +STOP_SIGNAL = False def report_arn(candidate): @@ -62,6 +63,7 @@ def enumerate_using_bruteforce(access_key, secret_key, session_token, region): """ Attempt to brute-force common describe calls. """ + global STOP_SIGNAL output = dict() logger = logging.getLogger() @@ -73,6 +75,7 @@ def enumerate_using_bruteforce(access_key, secret_key, session_token, region): try: results = pool.map(check_one_permission, args_generator) except KeyboardInterrupt: + STOP_SIGNAL = True print('') results = [] @@ -85,6 +88,7 @@ def enumerate_using_bruteforce(access_key, secret_key, session_token, region): pool.join() except KeyboardInterrupt: print('') + STOP_SIGNAL = True return output for thread_result in results: @@ -152,6 +156,9 @@ def check_one_permission(arg_tuple): access_key, secret_key, session_token, region, service_name, operation_name = arg_tuple logger = logging.getLogger() + if STOP_SIGNAL: + return + service_client = get_client(access_key, secret_key, session_token, service_name, region) if service_client is None: return @@ -167,6 +174,8 @@ def check_one_permission(arg_tuple): logger.debug('Testing %s.%s() in region %s' % (service_name, operation_name, region)) try: + if STOP_SIGNAL: + return action_response = action_function() except (botocore.exceptions.ClientError, botocore.exceptions.EndpointConnectionError, From 8cd7a3bf0caf393fbc5b1cf5379e31a684282d35 Mon Sep 17 00:00:00 2001 From: mgeeky Date: Tue, 19 Nov 2019 11:32:59 +0100 Subject: [PATCH 2/5] Enhanced Ctrl-C handling. --- enumerate_iam/main.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/enumerate_iam/main.py b/enumerate_iam/main.py index 9762fef..a179e56 100644 --- a/enumerate_iam/main.py +++ b/enumerate_iam/main.py @@ -19,6 +19,7 @@ import re import json import logging +import signal import boto3 import botocore import random @@ -69,11 +70,16 @@ def enumerate_using_bruteforce(access_key, secret_key, session_token, region): logger = logging.getLogger() logger.info('Attempting common-service describe / list brute force.') + # Ignore SIGINT signals so that child processes inherit SIGINT handler + original_sigint_handler = signal.signal(signal.SIGINT, signal.SIG_IGN) pool = ThreadPool(MAX_THREADS) + signal.signal(signal.SIGINT, original_sigint_handler) + args_generator = generate_args(access_key, secret_key, session_token, region) try: - results = pool.map(check_one_permission, args_generator) + results = pool.map_async(check_one_permission, args_generator) + results.get(600) except KeyboardInterrupt: STOP_SIGNAL = True print('') From 79b8a4b75ccb6cb50f00f4563379b6650416f49b Mon Sep 17 00:00:00 2001 From: mgeeky Date: Tue, 19 Nov 2019 11:34:46 +0100 Subject: [PATCH 3/5] Added print_help --- enumerate-iam.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/enumerate-iam.py b/enumerate-iam.py index 0d827b7..c9e506c 100755 --- a/enumerate-iam.py +++ b/enumerate-iam.py @@ -18,7 +18,8 @@ def main(): if args.profile and (args.access_key or args.secret_key or args.session_token): sys.stderr.write('error: Profile and raw AWS credential options are mutually exclusive.\n') - sys.stderr.write(' Please specify either --profile or --access-key and --secret-key.\n') + sys.stderr.write(' Please specify either --profile or --access-key and --secret-key.\n\n') + parser.print_help() sys.exit(2) access_key = args.access_key From 9b4cfc224cad7f422279e59770c8ba115106bb2d Mon Sep 17 00:00:00 2001 From: mgeeky Date: Tue, 19 Nov 2019 17:51:53 +0100 Subject: [PATCH 4/5] Added couple of additional program options, added dumping of user/group/role policies to the stdout --- enumerate-iam.py | 23 ++++++-- enumerate_iam/main.py | 87 +++++++++++++++++++++++-------- enumerate_iam/utils/json_utils.py | 2 - 3 files changed, 86 insertions(+), 26 deletions(-) diff --git a/enumerate-iam.py b/enumerate-iam.py index c9e506c..1fbaa3f 100755 --- a/enumerate-iam.py +++ b/enumerate-iam.py @@ -1,9 +1,12 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 import sys +import json +import logging import argparse from boto3 import Session from enumerate_iam.main import enumerate_iam +from enumerate_iam.utils.json_utils import json_encoder def main(): parser = argparse.ArgumentParser(description='Enumerate IAM permissions') @@ -13,6 +16,10 @@ def main(): parser.add_argument('--secret-key', help='AWS secret key if profile was not used') parser.add_argument('--session-token', help='STS session token') parser.add_argument('--region', help='AWS region to send API requests to', default='us-east-1') + parser.add_argument('--output', help='File to write output JSON containing all of the collected permissions') + parser.add_argument('--timeout', help='Timeout in minutes for permissions brute-forcing activity. Def: 15.', type=int, default=15) + #parser.add_argument('--verbose', action='store_true', help='Enable verbose output.') + parser.add_argument('--debug', action='store_true', help='Enable debug output.') args = parser.parse_args() @@ -34,10 +41,20 @@ def main(): secret_key = currcreds.secret_key session_token = currcreds.token - enumerate_iam(access_key, + level = logging.INFO + if args.debug: + level = logging.DEBUG + + output = enumerate_iam(access_key, secret_key, session_token, - args.region) + args.region, + args.timeout * 60, + level) + + if args.output: + with open(args.output, 'w') as f: + f.write(json.dumps(output, indent=4, default=json_encoder)) if __name__ == '__main__': main() diff --git a/enumerate_iam/main.py b/enumerate_iam/main.py index a179e56..873c87b 100644 --- a/enumerate_iam/main.py +++ b/enumerate_iam/main.py @@ -26,15 +26,17 @@ from botocore.client import Config from botocore.endpoint import MAX_POOL_CONNECTIONS -from multiprocessing.dummy import Pool as ThreadPool +from multiprocessing import TimeoutError +from multiprocessing.dummy import Pool as ThreadPool, Manager, Value from enumerate_iam.utils.remove_metadata import remove_metadata from enumerate_iam.utils.json_utils import json_encoder from enumerate_iam.bruteforce_tests import BRUTEFORCE_TESTS -MAX_THREADS = 25 +MAX_THREADS = 1 CLIENT_POOL = {} -STOP_SIGNAL = False +MANAGER = Manager() +STOP_SIGNAL = MANAGER.Value('i', 0) def report_arn(candidate): @@ -60,7 +62,7 @@ def report_arn(candidate): return None, None, None -def enumerate_using_bruteforce(access_key, secret_key, session_token, region): +def enumerate_using_bruteforce(access_key, secret_key, session_token, region, timeout): """ Attempt to brute-force common describe calls. """ @@ -75,13 +77,17 @@ def enumerate_using_bruteforce(access_key, secret_key, session_token, region): pool = ThreadPool(MAX_THREADS) signal.signal(signal.SIGINT, original_sigint_handler) - args_generator = generate_args(access_key, secret_key, session_token, region) + args_generator = generate_args(access_key, secret_key, session_token, region, STOP_SIGNAL) try: results = pool.map_async(check_one_permission, args_generator) - results.get(600) + results.get(timeout) + except TimeoutError: + logger.info('Brute-forcing permissions timed out.') + STOP_SIGNAL.value = 1 + except KeyboardInterrupt: - STOP_SIGNAL = True + STOP_SIGNAL.value = 1 print('') results = [] @@ -94,7 +100,6 @@ def enumerate_using_bruteforce(access_key, secret_key, session_token, region): pool.join() except KeyboardInterrupt: print('') - STOP_SIGNAL = True return output for thread_result in results: @@ -110,7 +115,7 @@ def enumerate_using_bruteforce(access_key, secret_key, session_token, region): return output -def generate_args(access_key, secret_key, session_token, region): +def generate_args(access_key, secret_key, session_token, region, stop_signal): service_names = list(BRUTEFORCE_TESTS.keys()) @@ -121,7 +126,7 @@ def generate_args(access_key, secret_key, session_token, region): random.shuffle(actions) for action in actions: - yield access_key, secret_key, session_token, region, service_name, action + yield access_key, secret_key, session_token, region, stop_signal, service_name, action def get_client(access_key, secret_key, session_token, service_name, region): @@ -159,10 +164,10 @@ def get_client(access_key, secret_key, session_token, service_name, region): def check_one_permission(arg_tuple): - access_key, secret_key, session_token, region, service_name, operation_name = arg_tuple + access_key, secret_key, session_token, region, stop_signal, service_name, operation_name = arg_tuple logger = logging.getLogger() - if STOP_SIGNAL: + if stop_signal.value == 1: return service_client = get_client(access_key, secret_key, session_token, service_name, region) @@ -180,7 +185,7 @@ def check_one_permission(arg_tuple): logger.debug('Testing %s.%s() in region %s' % (service_name, operation_name, region)) try: - if STOP_SIGNAL: + if stop_signal.value == 1: return action_response = action_function() except (botocore.exceptions.ClientError, @@ -201,9 +206,9 @@ def check_one_permission(arg_tuple): return key, remove_metadata(action_response) -def configure_logging(): +def configure_logging(level): logging.basicConfig( - level=logging.INFO, + level=level, format='%(asctime)s - %(process)d - [%(levelname)s] %(message)s', ) @@ -222,17 +227,17 @@ def configure_logging(): urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) -def enumerate_iam(access_key, secret_key, session_token, region): +def enumerate_iam(access_key, secret_key, session_token, region, timeout, level = logging.INFO): """IAM Account Enumerator. This code provides a mechanism to attempt to validate the permissions assigned to a given set of AWS tokens. """ output = dict() - configure_logging() + configure_logging(level) output['iam'] = enumerate_using_iam(access_key, secret_key, session_token, region) - output['bruteforce'] = enumerate_using_bruteforce(access_key, secret_key, session_token, region) + output['bruteforce'] = enumerate_using_bruteforce(access_key, secret_key, session_token, region, timeout) return output @@ -258,8 +263,8 @@ def enumerate_using_iam(access_key, secret_key, session_token, region): botocore.exceptions.ReadTimeoutError): pass else: - logger.info('Run for the hills, get_account_authorization_details worked!') - logger.info('-- %s', json.dumps(everything, indent=4, default=json_encoder)) + logger.debug('Run for the hills, get_account_authorization_details worked!') + logger.debug('%s', json.dumps(everything, indent=4, default=json_encoder)) output['iam.get_account_authorization_details'] = remove_metadata(everything) @@ -308,6 +313,7 @@ def enumerate_role(iam_client, output): pass else: output['iam.list_attached_role_policies'] = remove_metadata(role_policies) + logger.debug('%s', json.dumps(role_policies, indent=4, default=json_encoder)) logger.info( 'Role "%s" has %0d attached policies', @@ -319,6 +325,15 @@ def enumerate_role(iam_client, output): for policy in role_policies['AttachedPolicies']: logger.info('-- Policy "%s" (%s)', policy['PolicyName'], policy['PolicyArn']) + get_policy = iam.get_role_policy(PolicyName=policy['PolicyName']) + policy_version = iam_client.get_policy_version(PolicyArn=policy['PolicyArn'], VersionId=policy['DefaultVersionId']) + logger.debug('Role attached policy: {}'.format(policy['PolicyName'])) + logger.debug('%s', json.dumps(policy_version, indent=4, default=json_encoder)) + + key = 'iam.role_attached_policies' + if key not in output.keys(): output[key] = [] + output[key].append(remove_metadata(policy_version)) + # Attempt to get inline policies for this user. try: role_policies = iam_client.list_role_policies(RoleName=role_name) @@ -328,7 +343,7 @@ def enumerate_role(iam_client, output): output['iam.list_role_policies'] = remove_metadata(role_policies) logger.info( - 'User "%s" has %0d inline policies', + 'Role "%s" has %0d inline policies', role['Role']['RoleName'], len(role_policies['PolicyNames']) ) @@ -337,6 +352,13 @@ def enumerate_role(iam_client, output): for policy in role_policies['PolicyNames']: logger.info('-- Policy "%s"', policy) + get_policy = iam_client.get_user_policy(RoleName=role_name, PolicyName=policy) + logger.debug('Role inline policy:\n%s', json.dumps(get_policy['PolicyDocument'], indent=4, default=json_encoder)) + + key = 'iam.role_inline_policies' + if key not in output.keys(): output[key] = [] + output[key].append(remove_metadata(get_policy['PolicyDocument'])) + return output @@ -390,6 +412,14 @@ def enumerate_user(iam_client, output): for policy in user_policies['AttachedPolicies']: logger.info('-- Policy "%s" (%s)', policy['PolicyName'], policy['PolicyArn']) + get_policy = iam_client.get_policy(PolicyArn=policy['PolicyArn']) + policy_version = iam_client.get_policy_version(PolicyArn=policy['PolicyArn'], VersionId=get_policy['Policy']['DefaultVersionId']) + logger.debug('User attached policy:\n%s', json.dumps(policy_version['PolicyVersion'], indent=4, default=json_encoder)) + + key = 'iam.user_attached_policies' + if key not in output.keys(): output[key] = [] + output[key].append(remove_metadata(policy_version['PolicyVersion'])) + # Attempt to get inline policies for this user. try: user_policies = iam_client.list_user_policies(UserName=user_name) @@ -408,6 +438,13 @@ def enumerate_user(iam_client, output): for policy in user_policies['PolicyNames']: logger.info('-- Policy "%s"', policy) + get_policy = iam_client.get_user_policy(UserName=user_name, PolicyName=policy) + logger.debug('User inline policy:\n%s', json.dumps(get_policy['PolicyDocument'], indent=4, default=json_encoder)) + + key = 'iam.user_inline_policies' + if key not in output.keys(): output[key] = [] + output[key].append(remove_metadata(get_policy['PolicyDocument'])) + # Attempt to get the groups attached to this user. user_groups = dict() user_groups['Groups'] = [] @@ -443,6 +480,14 @@ def enumerate_user(iam_client, output): # List all group policy names. for policy in group_policy['PolicyNames']: logger.info('---- Policy "%s"', policy) + + get_policy = iam_client.get_group_policy(GroupName=group['GroupName'], PolicyName=policy) + logger.debug('Group inline policy:\n%s', json.dumps(get_policy['PolicyDocument'], indent=4, default=json_encoder)) + + key = 'iam.group_inline_policies' + if key not in output.keys(): output[key] = [] + output[key].append(remove_metadata(get_policy['PolicyDocument'])) + except botocore.exceptions.ClientError as err: pass diff --git a/enumerate_iam/utils/json_utils.py b/enumerate_iam/utils/json_utils.py index 783a61f..6b44e19 100644 --- a/enumerate_iam/utils/json_utils.py +++ b/enumerate_iam/utils/json_utils.py @@ -5,14 +5,12 @@ DEFAULT_ENCODING = 'utf-8' - def map_nested_dicts(ob, func): if isinstance(ob, collections.Mapping): return {k: map_nested_dicts(v, func) for k, v in ob.iteritems()} else: return func(ob) - def json_encoder(o): if type(o) is datetime.date or type(o) is datetime.datetime: return o.isoformat() From 21cbc5b3231616db9ae5bc10f8617d41c9591ae8 Mon Sep 17 00:00:00 2001 From: mgeeky Date: Wed, 20 Nov 2019 16:44:22 +0100 Subject: [PATCH 5/5] Fixes --- enumerate_iam/main.py | 78 +++++++++++++++++++++++++------------------ 1 file changed, 46 insertions(+), 32 deletions(-) diff --git a/enumerate_iam/main.py b/enumerate_iam/main.py index 873c87b..2d2192c 100644 --- a/enumerate_iam/main.py +++ b/enumerate_iam/main.py @@ -33,7 +33,7 @@ from enumerate_iam.utils.json_utils import json_encoder from enumerate_iam.bruteforce_tests import BRUTEFORCE_TESTS -MAX_THREADS = 1 +MAX_THREADS = 25 CLIENT_POOL = {} MANAGER = Manager() STOP_SIGNAL = MANAGER.Value('i', 0) @@ -72,7 +72,6 @@ def enumerate_using_bruteforce(access_key, secret_key, session_token, region, ti logger = logging.getLogger() logger.info('Attempting common-service describe / list brute force.') - # Ignore SIGINT signals so that child processes inherit SIGINT handler original_sigint_handler = signal.signal(signal.SIGINT, signal.SIG_IGN) pool = ThreadPool(MAX_THREADS) signal.signal(signal.SIGINT, original_sigint_handler) @@ -184,9 +183,9 @@ def check_one_permission(arg_tuple): logger.debug('Testing %s.%s() in region %s' % (service_name, operation_name, region)) + if stop_signal.value == 1: + return try: - if stop_signal.value == 1: - return action_response = action_function() except (botocore.exceptions.ClientError, botocore.exceptions.EndpointConnectionError, @@ -325,14 +324,17 @@ def enumerate_role(iam_client, output): for policy in role_policies['AttachedPolicies']: logger.info('-- Policy "%s" (%s)', policy['PolicyName'], policy['PolicyArn']) - get_policy = iam.get_role_policy(PolicyName=policy['PolicyName']) - policy_version = iam_client.get_policy_version(PolicyArn=policy['PolicyArn'], VersionId=policy['DefaultVersionId']) - logger.debug('Role attached policy: {}'.format(policy['PolicyName'])) - logger.debug('%s', json.dumps(policy_version, indent=4, default=json_encoder)) + try: + get_policy = iam.get_role_policy(PolicyName=policy['PolicyName']) + policy_version = iam_client.get_policy_version(PolicyArn=policy['PolicyArn'], VersionId=policy['DefaultVersionId']) + logger.debug('Role attached policy: {}'.format(policy['PolicyName'])) + logger.debug('%s', json.dumps(policy_version, indent=4, default=json_encoder)) - key = 'iam.role_attached_policies' - if key not in output.keys(): output[key] = [] - output[key].append(remove_metadata(policy_version)) + key = 'iam.role_attached_policies' + if key not in output.keys(): output[key] = [] + output[key].append(remove_metadata(policy_version)) + except: + pass # Attempt to get inline policies for this user. try: @@ -352,12 +354,15 @@ def enumerate_role(iam_client, output): for policy in role_policies['PolicyNames']: logger.info('-- Policy "%s"', policy) - get_policy = iam_client.get_user_policy(RoleName=role_name, PolicyName=policy) - logger.debug('Role inline policy:\n%s', json.dumps(get_policy['PolicyDocument'], indent=4, default=json_encoder)) + try: + get_policy = iam_client.get_user_policy(RoleName=role_name, PolicyName=policy) + logger.debug('Role inline policy:\n%s', json.dumps(get_policy['PolicyDocument'], indent=4, default=json_encoder)) - key = 'iam.role_inline_policies' - if key not in output.keys(): output[key] = [] - output[key].append(remove_metadata(get_policy['PolicyDocument'])) + key = 'iam.role_inline_policies' + if key not in output.keys(): output[key] = [] + output[key].append(remove_metadata(get_policy['PolicyDocument'])) + except: + pass return output @@ -412,13 +417,16 @@ def enumerate_user(iam_client, output): for policy in user_policies['AttachedPolicies']: logger.info('-- Policy "%s" (%s)', policy['PolicyName'], policy['PolicyArn']) - get_policy = iam_client.get_policy(PolicyArn=policy['PolicyArn']) - policy_version = iam_client.get_policy_version(PolicyArn=policy['PolicyArn'], VersionId=get_policy['Policy']['DefaultVersionId']) - logger.debug('User attached policy:\n%s', json.dumps(policy_version['PolicyVersion'], indent=4, default=json_encoder)) + try: + get_policy = iam_client.get_policy(PolicyArn=policy['PolicyArn']) + policy_version = iam_client.get_policy_version(PolicyArn=policy['PolicyArn'], VersionId=get_policy['Policy']['DefaultVersionId']) + logger.debug('User attached policy:\n%s', json.dumps(policy_version['PolicyVersion'], indent=4, default=json_encoder)) - key = 'iam.user_attached_policies' - if key not in output.keys(): output[key] = [] - output[key].append(remove_metadata(policy_version['PolicyVersion'])) + key = 'iam.user_attached_policies' + if key not in output.keys(): output[key] = [] + output[key].append(remove_metadata(policy_version['PolicyVersion'])) + except: + pass # Attempt to get inline policies for this user. try: @@ -438,12 +446,15 @@ def enumerate_user(iam_client, output): for policy in user_policies['PolicyNames']: logger.info('-- Policy "%s"', policy) - get_policy = iam_client.get_user_policy(UserName=user_name, PolicyName=policy) - logger.debug('User inline policy:\n%s', json.dumps(get_policy['PolicyDocument'], indent=4, default=json_encoder)) + try: + get_policy = iam_client.get_user_policy(UserName=user_name, PolicyName=policy) + logger.debug('User inline policy:\n%s', json.dumps(get_policy['PolicyDocument'], indent=4, default=json_encoder)) - key = 'iam.user_inline_policies' - if key not in output.keys(): output[key] = [] - output[key].append(remove_metadata(get_policy['PolicyDocument'])) + key = 'iam.user_inline_policies' + if key not in output.keys(): output[key] = [] + output[key].append(remove_metadata(get_policy['PolicyDocument'])) + except: + pass # Attempt to get the groups attached to this user. user_groups = dict() @@ -481,12 +492,15 @@ def enumerate_user(iam_client, output): for policy in group_policy['PolicyNames']: logger.info('---- Policy "%s"', policy) - get_policy = iam_client.get_group_policy(GroupName=group['GroupName'], PolicyName=policy) - logger.debug('Group inline policy:\n%s', json.dumps(get_policy['PolicyDocument'], indent=4, default=json_encoder)) + try: + get_policy = iam_client.get_group_policy(GroupName=group['GroupName'], PolicyName=policy) + logger.debug('Group inline policy:\n%s', json.dumps(get_policy['PolicyDocument'], indent=4, default=json_encoder)) - key = 'iam.group_inline_policies' - if key not in output.keys(): output[key] = [] - output[key].append(remove_metadata(get_policy['PolicyDocument'])) + key = 'iam.group_inline_policies' + if key not in output.keys(): output[key] = [] + output[key].append(remove_metadata(get_policy['PolicyDocument'])) + except: + pass except botocore.exceptions.ClientError as err: pass