[cloud] Fix iam_role to compare trust policies accurately (#22936)
* Fix iam_role to compare trust policies accurately * Better exception handling and better handling of detaching all managed policies
This commit is contained in:
parent
4fc40304d5
commit
43fc97cad3
1 changed files with 58 additions and 59 deletions
|
@ -143,11 +143,14 @@ attached_policies:
|
||||||
]
|
]
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
from ansible.module_utils.basic import AnsibleModule
|
||||||
|
from ansible.module_utils.ec2 import camel_dict_to_snake_dict, ec2_argument_spec, get_aws_connection_info, boto3_conn, sort_json_policy_dict
|
||||||
import json
|
import json
|
||||||
|
import traceback
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import boto3
|
import boto3
|
||||||
from botocore.exceptions import ClientError, ParamValidationError
|
from botocore.exceptions import ClientError, NoCredentialsError
|
||||||
HAS_BOTO3 = True
|
HAS_BOTO3 = True
|
||||||
except ImportError:
|
except ImportError:
|
||||||
HAS_BOTO3 = False
|
HAS_BOTO3 = False
|
||||||
|
@ -155,10 +158,7 @@ except ImportError:
|
||||||
|
|
||||||
def compare_assume_role_policy_doc(current_policy_doc, new_policy_doc):
|
def compare_assume_role_policy_doc(current_policy_doc, new_policy_doc):
|
||||||
|
|
||||||
# Get proper JSON strings for both docs
|
if sort_json_policy_dict(current_policy_doc) == sort_json_policy_dict(json.loads(new_policy_doc)):
|
||||||
current_policy_doc = json.dumps(current_policy_doc)
|
|
||||||
|
|
||||||
if current_policy_doc == new_policy_doc:
|
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
@ -190,60 +190,63 @@ def create_or_update_role(connection, module):
|
||||||
changed = False
|
changed = False
|
||||||
|
|
||||||
# Get role
|
# Get role
|
||||||
role = get_role(connection, params['RoleName'], module)
|
role = get_role(connection, module, params['RoleName'])
|
||||||
|
|
||||||
# If role is None, create it
|
# If role is None, create it
|
||||||
if role is None:
|
if role is None:
|
||||||
try:
|
try:
|
||||||
role = connection.create_role(**params)
|
role = connection.create_role(**params)
|
||||||
changed = True
|
changed = True
|
||||||
except (ClientError, ParamValidationError) as e:
|
except ClientError as e:
|
||||||
module.fail_json(msg=e.message, **camel_dict_to_snake_dict(e.response))
|
module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
|
||||||
else:
|
else:
|
||||||
# Check Assumed Policy document
|
# Check Assumed Policy document
|
||||||
if not compare_assume_role_policy_doc(role['AssumeRolePolicyDocument'], params['AssumeRolePolicyDocument']):
|
if not compare_assume_role_policy_doc(role['AssumeRolePolicyDocument'], params['AssumeRolePolicyDocument']):
|
||||||
try:
|
try:
|
||||||
connection.update_assume_role_policy(RoleName=params['RoleName'], PolicyDocument=json.dumps(json.loads(params['AssumeRolePolicyDocument'])))
|
connection.update_assume_role_policy(RoleName=params['RoleName'], PolicyDocument=json.dumps(json.loads(params['AssumeRolePolicyDocument'])))
|
||||||
changed = True
|
changed = True
|
||||||
except (ClientError, ParamValidationError) as e:
|
except ClientError as e:
|
||||||
module.fail_json(msg=e.message, **camel_dict_to_snake_dict(e.response))
|
module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
|
||||||
|
|
||||||
# Check attached managed policies
|
if managed_policies is not None:
|
||||||
current_attached_policies = get_attached_policy_list(connection, params['RoleName'])
|
# Get list of current attached managed policies
|
||||||
if not compare_attached_role_policies(current_attached_policies, managed_policies):
|
current_attached_policies = get_attached_policy_list(connection, module, params['RoleName'])
|
||||||
# If managed_policies has a single empty element we want to remove all attached policies
|
|
||||||
if len(managed_policies) == 1 and managed_policies[0] == "":
|
# If a single empty list item then all managed policies to be removed
|
||||||
|
if len(managed_policies) == 1 and not managed_policies[0]:
|
||||||
for policy in current_attached_policies:
|
for policy in current_attached_policies:
|
||||||
try:
|
try:
|
||||||
connection.detach_role_policy(RoleName=params['RoleName'], PolicyArn=policy['PolicyArn'])
|
connection.detach_role_policy(RoleName=params['RoleName'], PolicyArn=policy['PolicyArn'])
|
||||||
except (ClientError, ParamValidationError) as e:
|
except ClientError as e:
|
||||||
module.fail_json(msg=e.message, **camel_dict_to_snake_dict(e.response))
|
module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
|
||||||
|
changed = True
|
||||||
# Detach policies not present
|
else:
|
||||||
|
# Make a list of the ARNs from the attached policies
|
||||||
current_attached_policies_arn_list = []
|
current_attached_policies_arn_list = []
|
||||||
for policy in current_attached_policies:
|
for policy in current_attached_policies:
|
||||||
current_attached_policies_arn_list.append(policy['PolicyArn'])
|
current_attached_policies_arn_list.append(policy['PolicyArn'])
|
||||||
|
|
||||||
|
# Detach roles not defined in task
|
||||||
for policy_arn in list(set(current_attached_policies_arn_list) - set(managed_policies)):
|
for policy_arn in list(set(current_attached_policies_arn_list) - set(managed_policies)):
|
||||||
try:
|
try:
|
||||||
connection.detach_role_policy(RoleName=params['RoleName'], PolicyArn=policy_arn)
|
connection.detach_role_policy(RoleName=params['RoleName'], PolicyArn=policy_arn)
|
||||||
except (ClientError, ParamValidationError) as e:
|
except ClientError as e:
|
||||||
module.fail_json(msg=e.message, **camel_dict_to_snake_dict(e.response))
|
module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
|
||||||
|
|
||||||
# Attach each policy
|
|
||||||
for policy_arn in managed_policies:
|
|
||||||
try:
|
|
||||||
connection.attach_role_policy(RoleName=params['RoleName'], PolicyArn=policy_arn)
|
|
||||||
except (ClientError, ParamValidationError) as e:
|
|
||||||
module.fail_json(msg=e.message, **camel_dict_to_snake_dict(e.response))
|
|
||||||
|
|
||||||
changed = True
|
changed = True
|
||||||
|
|
||||||
# We need to remove any instance profiles from the role before we delete it
|
# Attach roles not already attached
|
||||||
|
for policy_arn in list(set(managed_policies) - set(current_attached_policies_arn_list)):
|
||||||
|
try:
|
||||||
|
connection.attach_role_policy(RoleName=params['RoleName'], PolicyArn=policy_arn)
|
||||||
|
except ClientError as e:
|
||||||
|
module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
|
||||||
|
changed = True
|
||||||
|
|
||||||
|
# Instance profile
|
||||||
try:
|
try:
|
||||||
instance_profiles = connection.list_instance_profiles_for_role(RoleName=params['RoleName'])['InstanceProfiles']
|
instance_profiles = connection.list_instance_profiles_for_role(RoleName=params['RoleName'])['InstanceProfiles']
|
||||||
except ClientError as e:
|
except ClientError as e:
|
||||||
module.fail_json(msg=e.message, **camel_dict_to_snake_dict(e.response))
|
module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
|
||||||
if not any(p['InstanceProfileName'] == params['RoleName'] for p in instance_profiles):
|
if not any(p['InstanceProfileName'] == params['RoleName'] for p in instance_profiles):
|
||||||
# Make sure an instance profile is attached
|
# Make sure an instance profile is attached
|
||||||
try:
|
try:
|
||||||
|
@ -254,13 +257,13 @@ def create_or_update_role(connection, module):
|
||||||
if e.response['Error']['Code'] == 'EntityAlreadyExists':
|
if e.response['Error']['Code'] == 'EntityAlreadyExists':
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
module.fail_json(msg=e.message, **camel_dict_to_snake_dict(e.response))
|
module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
|
||||||
connection.add_role_to_instance_profile(InstanceProfileName=params['RoleName'], RoleName=params['RoleName'])
|
connection.add_role_to_instance_profile(InstanceProfileName=params['RoleName'], RoleName=params['RoleName'])
|
||||||
|
|
||||||
# Get the role again
|
# Get the role again
|
||||||
role = get_role(connection, params['RoleName'], module)
|
role = get_role(connection, module, params['RoleName'])
|
||||||
|
|
||||||
role['attached_policies'] = get_attached_policy_list(connection, params['RoleName'])
|
role['attached_policies'] = get_attached_policy_list(connection, module, params['RoleName'])
|
||||||
module.exit_json(changed=changed, iam_role=camel_dict_to_snake_dict(role))
|
module.exit_json(changed=changed, iam_role=camel_dict_to_snake_dict(role))
|
||||||
|
|
||||||
|
|
||||||
|
@ -269,53 +272,52 @@ def destroy_role(connection, module):
|
||||||
params = dict()
|
params = dict()
|
||||||
params['RoleName'] = module.params.get('name')
|
params['RoleName'] = module.params.get('name')
|
||||||
|
|
||||||
if get_role(connection, params['RoleName'], module):
|
if get_role(connection, module, params['RoleName']):
|
||||||
|
|
||||||
# We need to remove any instance profiles from the role before we delete it
|
# We need to remove any instance profiles from the role before we delete it
|
||||||
try:
|
try:
|
||||||
instance_profiles = connection.list_instance_profiles_for_role(RoleName=params['RoleName'])['InstanceProfiles']
|
instance_profiles = connection.list_instance_profiles_for_role(RoleName=params['RoleName'])['InstanceProfiles']
|
||||||
except ClientError as e:
|
except ClientError as e:
|
||||||
module.fail_json(msg=e.message, **camel_dict_to_snake_dict(e.response))
|
module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
|
||||||
|
|
||||||
# Now remove the role from the instance profile(s)
|
# Now remove the role from the instance profile(s)
|
||||||
for profile in instance_profiles:
|
for profile in instance_profiles:
|
||||||
try:
|
try:
|
||||||
connection.remove_role_from_instance_profile(InstanceProfileName=profile['InstanceProfileName'], RoleName=params['RoleName'])
|
connection.remove_role_from_instance_profile(InstanceProfileName=profile['InstanceProfileName'], RoleName=params['RoleName'])
|
||||||
except ClientError as e:
|
except ClientError as e:
|
||||||
module.fail_json(msg=e.message, **camel_dict_to_snake_dict(e.response))
|
module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
|
||||||
|
|
||||||
# Now remove any attached policies otherwise deletion fails
|
# Now remove any attached policies otherwise deletion fails
|
||||||
try:
|
try:
|
||||||
for policy in get_attached_policy_list(connection, params['RoleName']):
|
for policy in get_attached_policy_list(connection, module, params['RoleName']):
|
||||||
connection.detach_role_policy(RoleName=params['RoleName'], PolicyArn=policy['PolicyArn'])
|
connection.detach_role_policy(RoleName=params['RoleName'], PolicyArn=policy['PolicyArn'])
|
||||||
except (ClientError, ParamValidationError) as e:
|
except ClientError as e:
|
||||||
module.fail_json(msg=e.message, **camel_dict_to_snake_dict(e.response))
|
module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
connection.delete_role(**params)
|
connection.delete_role(**params)
|
||||||
except ClientError as e:
|
except ClientError as e:
|
||||||
module.fail_json(msg=e.message, **camel_dict_to_snake_dict(e.response))
|
module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
|
||||||
else:
|
else:
|
||||||
module.exit_json(changed=False)
|
module.exit_json(changed=False)
|
||||||
|
|
||||||
module.exit_json(changed=True)
|
module.exit_json(changed=True)
|
||||||
|
|
||||||
|
|
||||||
def get_role(connection, name, module):
|
def get_role(connection, module, name):
|
||||||
|
|
||||||
params = dict()
|
|
||||||
params['RoleName'] = name
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return connection.get_role(**params)['Role']
|
return connection.get_role(RoleName=name)['Role']
|
||||||
except ClientError as e:
|
except ClientError as e:
|
||||||
if e.response['Error']['Code'] == 'NoSuchEntity':
|
if e.response['Error']['Code'] == 'NoSuchEntity':
|
||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
module.fail_json(msg=e.message, **camel_dict_to_snake_dict(e.response))
|
module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
|
||||||
|
except NoCredentialsError as e:
|
||||||
|
module.fail_json(msg="AWS authentication problem. " + e.message, exception=traceback.format_exc())
|
||||||
|
|
||||||
|
|
||||||
def get_attached_policy_list(connection, name):
|
def get_attached_policy_list(connection, module, name):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return connection.list_attached_role_policies(RoleName=name)['AttachedPolicies']
|
return connection.list_attached_role_policies(RoleName=name)['AttachedPolicies']
|
||||||
|
@ -323,7 +325,7 @@ def get_attached_policy_list(connection, name):
|
||||||
if e.response['Error']['Code'] == 'NoSuchEntity':
|
if e.response['Error']['Code'] == 'NoSuchEntity':
|
||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
module.fail_json(msg=e.message, **camel_dict_to_snake_dict(e.response))
|
module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
@ -334,7 +336,7 @@ def main():
|
||||||
name=dict(required=True, type='str'),
|
name=dict(required=True, type='str'),
|
||||||
path=dict(default="/", required=False, type='str'),
|
path=dict(default="/", required=False, type='str'),
|
||||||
assume_role_policy_document=dict(required=False, type='json'),
|
assume_role_policy_document=dict(required=False, type='json'),
|
||||||
managed_policy=dict(default=[], required=False, type='list'),
|
managed_policy=dict(default=None, required=False, type='list'),
|
||||||
state=dict(default=None, choices=['present', 'absent'], required=True)
|
state=dict(default=None, choices=['present', 'absent'], required=True)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -359,8 +361,5 @@ def main():
|
||||||
else:
|
else:
|
||||||
destroy_role(connection, module)
|
destroy_role(connection, module)
|
||||||
|
|
||||||
from ansible.module_utils.basic import AnsibleModule
|
|
||||||
from ansible.module_utils.ec2 import *
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
|
Loading…
Reference in a new issue