iam_role : support managing max session duration and deleting the instance profile it creates (#62014)
* iam_role: Add support for managing MaxSessionDuration * iam_role: Add support for deleting the IAM Instance Profiles we created * iam_role: migrate all boto failures to fail_json_aws for consistency * iam_role: test validity of path so we can throw a more understandable error * iam_role: (integration tests) Split iam_role integration tests from sts_assume_role tests - Make the iam_role tests more comprehensive - Add tests for iam_role_info * iam_role: (integration tests) Make some of our pauses optional If the tests appear to be flakey we may need to enable standard_pauses
This commit is contained in:
parent
679d3a46fa
commit
40660e7f6e
12 changed files with 1379 additions and 59 deletions
|
@ -0,0 +1,3 @@
|
||||||
|
minor_changes:
|
||||||
|
- iam_role - Add support for removing the related instance profile when we delete the role
|
||||||
|
- iam_role - Add support for managing the maximum session duration
|
|
@ -27,14 +27,31 @@
|
||||||
"Effect": "Allow",
|
"Effect": "Allow",
|
||||||
"Sid": "AllowReadOnlyIAMUse"
|
"Sid": "AllowReadOnlyIAMUse"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"Action": [
|
||||||
|
"iam:CreatePolicy",
|
||||||
|
"iam:ListPolicyVersions",
|
||||||
|
"iam:ListEntitiesForPolicy",
|
||||||
|
"iam:DeletePolicy"
|
||||||
|
],
|
||||||
|
"Resource": "arn:aws:iam::{{ aws_account }}:policy/ansible-test-*",
|
||||||
|
"Effect": "Allow",
|
||||||
|
"Sid": "AllowManagementOfSpecificPolicies"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"Action": [
|
"Action": [
|
||||||
"iam:AttachRolePolicy",
|
"iam:AttachRolePolicy",
|
||||||
"iam:CreateRole",
|
"iam:CreateRole",
|
||||||
"iam:DeleteRole",
|
"iam:DeleteRole",
|
||||||
|
"iam:DeleteRolePolicy",
|
||||||
|
"iam:DeleteRolePermissionsBoundary",
|
||||||
"iam:DetachRolePolicy",
|
"iam:DetachRolePolicy",
|
||||||
"iam:PassRole",
|
"iam:PassRole",
|
||||||
|
"iam:PutRolePolicy",
|
||||||
|
"iam:PutRolePermissionsBoundary",
|
||||||
"iam:UpdateAssumeRolePolicy",
|
"iam:UpdateAssumeRolePolicy",
|
||||||
|
"iam:UpdateRole",
|
||||||
|
"iam:UpdateRoleDescription",
|
||||||
"sts:AssumeRole"
|
"sts:AssumeRole"
|
||||||
],
|
],
|
||||||
"Resource": "arn:aws:iam::{{ aws_account }}:role/ansible-test-*",
|
"Resource": "arn:aws:iam::{{ aws_account }}:role/ansible-test-*",
|
||||||
|
|
|
@ -44,6 +44,11 @@ options:
|
||||||
- A list of managed policy ARNs or, since Ansible 2.4, a list of either managed policy ARNs or friendly names.
|
- A list of managed policy ARNs or, since Ansible 2.4, a list of either managed policy ARNs or friendly names.
|
||||||
To embed an inline policy, use M(iam_policy). To remove existing policies, use an empty list item.
|
To embed an inline policy, use M(iam_policy). To remove existing policies, use an empty list item.
|
||||||
aliases: [ managed_policies ]
|
aliases: [ managed_policies ]
|
||||||
|
max_session_duration:
|
||||||
|
description:
|
||||||
|
- The maximum duration (in seconds) of a session when assuming the role.
|
||||||
|
- Valid values are between 1 and 12 hours (3600 and 43200 seconds).
|
||||||
|
version_added: "2.10"
|
||||||
purge_policies:
|
purge_policies:
|
||||||
description:
|
description:
|
||||||
- Detaches any managed policies not listed in the "managed_policy" option. Set to false if you want to attach policies elsewhere.
|
- Detaches any managed policies not listed in the "managed_policy" option. Set to false if you want to attach policies elsewhere.
|
||||||
|
@ -59,8 +64,16 @@ options:
|
||||||
description:
|
description:
|
||||||
- Creates an IAM instance profile along with the role
|
- Creates an IAM instance profile along with the role
|
||||||
type: bool
|
type: bool
|
||||||
default: yes
|
default: true
|
||||||
version_added: "2.5"
|
version_added: "2.5"
|
||||||
|
delete_instance_profile:
|
||||||
|
description:
|
||||||
|
- When deleting a role will also delete the instance profile created with
|
||||||
|
the same name as the role
|
||||||
|
- Only applies when C(state=absent)
|
||||||
|
type: bool
|
||||||
|
default: false
|
||||||
|
version_added: "2.10"
|
||||||
requirements: [ botocore, boto3 ]
|
requirements: [ botocore, boto3 ]
|
||||||
extends_documentation_fragment:
|
extends_documentation_fragment:
|
||||||
- aws
|
- aws
|
||||||
|
@ -206,7 +219,7 @@ def convert_friendly_names_to_arns(connection, module, policy_names):
|
||||||
try:
|
try:
|
||||||
return [allpolicies[policy] for policy in policy_names]
|
return [allpolicies[policy] for policy in policy_names]
|
||||||
except KeyError as e:
|
except KeyError as e:
|
||||||
module.fail_json(msg="Couldn't find policy: " + str(e))
|
module.fail_json_aws(e, msg="Couldn't find policy")
|
||||||
|
|
||||||
|
|
||||||
def remove_policies(connection, module, policies_to_remove, params):
|
def remove_policies(connection, module, policies_to_remove, params):
|
||||||
|
@ -216,11 +229,9 @@ def remove_policies(connection, module, policies_to_remove, params):
|
||||||
if not module.check_mode:
|
if not module.check_mode:
|
||||||
connection.detach_role_policy(RoleName=params['RoleName'], PolicyArn=policy)
|
connection.detach_role_policy(RoleName=params['RoleName'], PolicyArn=policy)
|
||||||
except ClientError as e:
|
except ClientError as e:
|
||||||
module.fail_json(msg="Unable to detach policy {0} from {1}: {2}".format(policy, params['RoleName'], to_native(e)),
|
module.fail_json_aws(e, msg="Unable to detach policy {0} from {1}".format(policy, params['RoleName']))
|
||||||
exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
|
|
||||||
except BotoCoreError as e:
|
except BotoCoreError as e:
|
||||||
module.fail_json(msg="Unable to detach policy {0} from {1}: {2}".format(policy, params['RoleName'], to_native(e)),
|
module.fail_json_aws(e, msg="Unable to detach policy {0} from {1}".format(policy, params['RoleName']))
|
||||||
exception=traceback.format_exc())
|
|
||||||
changed = True
|
changed = True
|
||||||
return changed
|
return changed
|
||||||
|
|
||||||
|
@ -230,6 +241,8 @@ def create_or_update_role(connection, module):
|
||||||
params['Path'] = module.params.get('path')
|
params['Path'] = module.params.get('path')
|
||||||
params['RoleName'] = module.params.get('name')
|
params['RoleName'] = module.params.get('name')
|
||||||
params['AssumeRolePolicyDocument'] = module.params.get('assume_role_policy_document')
|
params['AssumeRolePolicyDocument'] = module.params.get('assume_role_policy_document')
|
||||||
|
if module.params.get('max_session_duration') is not None:
|
||||||
|
params['MaxSessionDuration'] = module.params.get('max_session_duration')
|
||||||
if module.params.get('description') is not None:
|
if module.params.get('description') is not None:
|
||||||
params['Description'] = module.params.get('description')
|
params['Description'] = module.params.get('description')
|
||||||
if module.params.get('boundary') is not None:
|
if module.params.get('boundary') is not None:
|
||||||
|
@ -257,11 +270,9 @@ def create_or_update_role(connection, module):
|
||||||
role['AssumeRolePolicyDocument'] = json.loads(params['AssumeRolePolicyDocument'])
|
role['AssumeRolePolicyDocument'] = json.loads(params['AssumeRolePolicyDocument'])
|
||||||
changed = True
|
changed = True
|
||||||
except ClientError as e:
|
except ClientError as e:
|
||||||
module.fail_json(msg="Unable to create role: {0}".format(to_native(e)),
|
module.fail_json_aws(e, msg="Unable to create role")
|
||||||
exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
|
|
||||||
except BotoCoreError as e:
|
except BotoCoreError as e:
|
||||||
module.fail_json(msg="Unable to create role: {0}".format(to_native(e)),
|
module.fail_json_aws(e, msg="Unable to create role")
|
||||||
exception=traceback.format_exc())
|
|
||||||
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']):
|
||||||
|
@ -270,11 +281,9 @@ def create_or_update_role(connection, module):
|
||||||
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 as e:
|
except ClientError as e:
|
||||||
module.fail_json(msg="Unable to update assume role policy for role {0}: {1}".format(params['RoleName'], to_native(e)),
|
module.fail_json_aws(e, msg="Unable to update assume role policy for role {0}".format(params['RoleName']))
|
||||||
exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
|
|
||||||
except BotoCoreError as e:
|
except BotoCoreError as e:
|
||||||
module.fail_json(msg="Unable to update assume role policy for role {0}: {1}".format(params['RoleName'], to_native(e)),
|
module.fail_json_aws(e, msg="Unable to update assume role policy for role {0}".format(params['RoleName']))
|
||||||
exception=traceback.format_exc())
|
|
||||||
|
|
||||||
if managed_policies is not None:
|
if managed_policies is not None:
|
||||||
# Get list of current attached managed policies
|
# Get list of current attached managed policies
|
||||||
|
@ -301,11 +310,9 @@ def create_or_update_role(connection, module):
|
||||||
if not module.check_mode:
|
if not module.check_mode:
|
||||||
connection.attach_role_policy(RoleName=params['RoleName'], PolicyArn=policy_arn)
|
connection.attach_role_policy(RoleName=params['RoleName'], PolicyArn=policy_arn)
|
||||||
except ClientError as e:
|
except ClientError as e:
|
||||||
module.fail_json(msg="Unable to attach policy {0} to role {1}: {2}".format(policy_arn, params['RoleName'], to_native(e)),
|
module.fail_json_aws(e, msg="Unable to attach policy {0} to role {1}".format(policy_arn, params['RoleName']))
|
||||||
exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
|
|
||||||
except BotoCoreError as e:
|
except BotoCoreError as e:
|
||||||
module.fail_json(msg="Unable to attach policy {0} to role {1}: {2}".format(policy_arn, params['RoleName'], to_native(e)),
|
module.fail_json_aws(e, msg="Unable to attach policy {0} to role {1}".format(policy_arn, params['RoleName']))
|
||||||
exception=traceback.format_exc())
|
|
||||||
changed = True
|
changed = True
|
||||||
|
|
||||||
# Instance profile
|
# Instance profile
|
||||||
|
@ -313,11 +320,9 @@ def create_or_update_role(connection, module):
|
||||||
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="Unable to list instance profiles for role {0}: {1}".format(params['RoleName'], to_native(e)),
|
module.fail_json_aws(e, msg="Unable to list instance profiles for role {0}".format(params['RoleName']))
|
||||||
exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
|
|
||||||
except BotoCoreError as e:
|
except BotoCoreError as e:
|
||||||
module.fail_json(msg="Unable to list instance profiles for role {0}: {1}".format(params['RoleName'], to_native(e)),
|
module.fail_json_aws(e, msg="Unable to list instance profiles for role {0}".format(params['RoleName']))
|
||||||
exception=traceback.format_exc())
|
|
||||||
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:
|
||||||
|
@ -329,11 +334,9 @@ 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="Unable to create instance profile for role {0}: {1}".format(params['RoleName'], to_native(e)),
|
module.fail_json_aws(e, msg="Unable to create instance profile for role {0}".format(params['RoleName']))
|
||||||
exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
|
|
||||||
except BotoCoreError as e:
|
except BotoCoreError as e:
|
||||||
module.fail_json(msg="Unable to create instance profile for role {0}: {1}".format(params['RoleName'], to_native(e)),
|
module.fail_json_aws(e, msg="Unable to create instance profile for role {0}".format(params['RoleName']))
|
||||||
exception=traceback.format_exc())
|
|
||||||
if not module.check_mode:
|
if not module.check_mode:
|
||||||
connection.add_role_to_instance_profile(InstanceProfileName=params['RoleName'], RoleName=params['RoleName'])
|
connection.add_role_to_instance_profile(InstanceProfileName=params['RoleName'], RoleName=params['RoleName'])
|
||||||
|
|
||||||
|
@ -345,8 +348,17 @@ def create_or_update_role(connection, module):
|
||||||
|
|
||||||
changed = True
|
changed = True
|
||||||
except (BotoCoreError, ClientError) as e:
|
except (BotoCoreError, ClientError) as e:
|
||||||
module.fail_json(msg="Unable to update description for role {0}: {1}".format(params['RoleName'], to_native(e)),
|
module.fail_json_aws(e, msg="Unable to update description for role {0}".format(params['RoleName']))
|
||||||
exception=traceback.format_exc())
|
|
||||||
|
# Check MaxSessionDuration update
|
||||||
|
if not role.get('MadeInCheckMode') and params.get('MaxSessionDuration') and role.get('MaxSessionDuration') != params['MaxSessionDuration']:
|
||||||
|
try:
|
||||||
|
if not module.check_mode:
|
||||||
|
connection.update_role(RoleName=params['RoleName'], MaxSessionDuration=params['MaxSessionDuration'])
|
||||||
|
|
||||||
|
changed = True
|
||||||
|
except (BotoCoreError, ClientError) as e:
|
||||||
|
module.fail_json_aws(e, msg="Unable to update maximum session duration for role {0}".format(params['RoleName']))
|
||||||
|
|
||||||
# Check if permission boundary needs update
|
# Check if permission boundary needs update
|
||||||
if not role.get('MadeInCheckMode') and (
|
if not role.get('MadeInCheckMode') and (
|
||||||
|
@ -362,14 +374,14 @@ def create_or_update_role(connection, module):
|
||||||
connection.delete_role_permissions_boundary(RoleName=params['RoleName'])
|
connection.delete_role_permissions_boundary(RoleName=params['RoleName'])
|
||||||
changed = True
|
changed = True
|
||||||
except (BotoCoreError, ClientError) as e:
|
except (BotoCoreError, ClientError) as e:
|
||||||
module.fail_json_aws(e, msg="Unable to remove permission boundary for role {0}: {1}".format(params['RoleName'], to_native(e)))
|
module.fail_json_aws(e, msg="Unable to remove permission boundary for role {0}".format(params['RoleName']))
|
||||||
elif (role.get('PermissionsBoundary') or {}).get('PermissionsBoundaryArn') != params['PermissionsBoundary']:
|
elif (role.get('PermissionsBoundary') or {}).get('PermissionsBoundaryArn') != params['PermissionsBoundary']:
|
||||||
try:
|
try:
|
||||||
if not module.check_mode:
|
if not module.check_mode:
|
||||||
connection.put_role_permissions_boundary(RoleName=params['RoleName'], PermissionsBoundary=params['PermissionsBoundary'])
|
connection.put_role_permissions_boundary(RoleName=params['RoleName'], PermissionsBoundary=params['PermissionsBoundary'])
|
||||||
changed = True
|
changed = True
|
||||||
except (BotoCoreError, ClientError) as e:
|
except (BotoCoreError, ClientError) as e:
|
||||||
module.fail_json_aws(e, msg="Unable to update permission boundary for role {0}: {1}".format(params['RoleName'], to_native(e)))
|
module.fail_json_aws(e, msg="Unable to update permission boundary for role {0}".format(params['RoleName']))
|
||||||
|
|
||||||
# Get the role again
|
# Get the role again
|
||||||
if not role.get('MadeInCheckMode', False):
|
if not role.get('MadeInCheckMode', False):
|
||||||
|
@ -392,31 +404,33 @@ def destroy_role(connection, module):
|
||||||
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="Unable to list instance profiles for role {0}: {1}".format(params['RoleName'], to_native(e)),
|
module.fail_json_aws(e, msg="Unable to list instance profiles for role {0}".format(params['RoleName']))
|
||||||
exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
|
|
||||||
except BotoCoreError as e:
|
except BotoCoreError as e:
|
||||||
module.fail_json(msg="Unable to list instance profiles for role {0}: {1}".format(params['RoleName'], to_native(e)),
|
module.fail_json_aws(e, msg="Unable to list instance profiles for role {0}".format(params['RoleName']))
|
||||||
exception=traceback.format_exc())
|
|
||||||
|
|
||||||
if role.get('PermissionsBoundary') is not None:
|
if role.get('PermissionsBoundary') is not None:
|
||||||
try:
|
try:
|
||||||
connection.delete_role_permissions_boundary(RoleName=params['RoleName'])
|
connection.delete_role_permissions_boundary(RoleName=params['RoleName'])
|
||||||
except (ClientError, BotoCoreError) as e:
|
except (ClientError, BotoCoreError) as e:
|
||||||
module.fail_json_aws(e, msg="Could not delete role permission boundary on role {0}: {1}".format(params['RoleName'], e))
|
module.fail_json_aws(e, msg="Could not delete role permission boundary on role {0}".format(params['RoleName']))
|
||||||
|
|
||||||
# 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:
|
||||||
if not module.check_mode:
|
if not module.check_mode:
|
||||||
connection.remove_role_from_instance_profile(InstanceProfileName=profile['InstanceProfileName'], RoleName=params['RoleName'])
|
connection.remove_role_from_instance_profile(InstanceProfileName=profile['InstanceProfileName'], RoleName=params['RoleName'])
|
||||||
|
if profile['InstanceProfileName'] == params['RoleName']:
|
||||||
|
if module.params.get("delete_instance_profile"):
|
||||||
|
try:
|
||||||
|
connection.delete_instance_profile(InstanceProfileName=profile['InstanceProfileName'])
|
||||||
|
except ClientError as e:
|
||||||
|
module.fail_json_aws(e, msg="Unable to remove instance profile {0}".format(profile['InstanceProfileName']))
|
||||||
except ClientError as e:
|
except ClientError as e:
|
||||||
module.fail_json(msg="Unable to remove role {0} from instance profile {1}: {2}".format(
|
module.fail_json_aws(e, msg="Unable to remove role {0} from instance profile {1}".format(
|
||||||
params['RoleName'], profile['InstanceProfileName'], to_native(e)),
|
params['RoleName'], profile['InstanceProfileName']))
|
||||||
exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
|
|
||||||
except BotoCoreError as e:
|
except BotoCoreError as e:
|
||||||
module.fail_json(msg="Unable to remove role {0} from instance profile {1}: {2}".format(
|
module.fail_json_aws(e, msg="Unable to remove role {0} from instance profile {1}".format(
|
||||||
params['RoleName'], profile['InstanceProfileName'], to_native(e)),
|
params['RoleName'], profile['InstanceProfileName']))
|
||||||
exception=traceback.format_exc())
|
|
||||||
|
|
||||||
# Now remove any attached policies otherwise deletion fails
|
# Now remove any attached policies otherwise deletion fails
|
||||||
try:
|
try:
|
||||||
|
@ -424,20 +438,17 @@ def destroy_role(connection, module):
|
||||||
if not module.check_mode:
|
if not module.check_mode:
|
||||||
connection.detach_role_policy(RoleName=params['RoleName'], PolicyArn=policy['PolicyArn'])
|
connection.detach_role_policy(RoleName=params['RoleName'], PolicyArn=policy['PolicyArn'])
|
||||||
except ClientError as e:
|
except ClientError as e:
|
||||||
module.fail_json(msg="Unable to detach policy {0} from role {1}: {2}".format(policy['PolicyArn'], params['RoleName'], to_native(e)),
|
module.fail_json_aws(e, msg="Unable to detach policy {0} from role {1}".format(policy['PolicyArn'], params['RoleName']))
|
||||||
exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
|
|
||||||
except BotoCoreError as e:
|
except BotoCoreError as e:
|
||||||
module.fail_json(msg="Unable to detach policy {0} from role {1}: {2}".format(policy['PolicyArn'], params['RoleName'], to_native(e)),
|
module.fail_json_aws(e, msg="Unable to detach policy {0} from role {1}".format(policy['PolicyArn'], params['RoleName']))
|
||||||
exception=traceback.format_exc())
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if not module.check_mode:
|
if not module.check_mode:
|
||||||
connection.delete_role(**params)
|
connection.delete_role(**params)
|
||||||
except ClientError as e:
|
except ClientError as e:
|
||||||
module.fail_json(msg="Unable to delete role: {0}".format(to_native(e)),
|
module.fail_json_aws(e, msg="Unable to delete role")
|
||||||
exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
|
|
||||||
except BotoCoreError as e:
|
except BotoCoreError as e:
|
||||||
module.fail_json(msg="Unable to delete role: {0}".format(to_native(e)), exception=traceback.format_exc())
|
module.fail_json_aws(e, msg="Unable to delete role")
|
||||||
else:
|
else:
|
||||||
module.exit_json(changed=False)
|
module.exit_json(changed=False)
|
||||||
|
|
||||||
|
@ -448,7 +459,7 @@ def get_role_with_backoff(connection, module, name):
|
||||||
try:
|
try:
|
||||||
return AWSRetry.jittered_backoff(catch_extra_error_codes=['NoSuchEntity'])(connection.get_role)(RoleName=name)['Role']
|
return AWSRetry.jittered_backoff(catch_extra_error_codes=['NoSuchEntity'])(connection.get_role)(RoleName=name)['Role']
|
||||||
except (BotoCoreError, ClientError) as e:
|
except (BotoCoreError, ClientError) as e:
|
||||||
module.fail_json(msg="Unable to get role {0}: {1}".format(name, to_native(e)), exception=traceback.format_exc())
|
module.fail_json_aws(e, msg="Unable to get role {0}".format(name))
|
||||||
|
|
||||||
|
|
||||||
def get_role(connection, module, name):
|
def get_role(connection, module, name):
|
||||||
|
@ -458,10 +469,9 @@ def get_role(connection, module, name):
|
||||||
if e.response['Error']['Code'] == 'NoSuchEntity':
|
if e.response['Error']['Code'] == 'NoSuchEntity':
|
||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
module.fail_json(msg="Unable to get role {0}: {1}".format(name, to_native(e)),
|
module.fail_json_aws(e, msg="Unable to get role {0}".format(name))
|
||||||
exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
|
|
||||||
except BotoCoreError as e:
|
except BotoCoreError as e:
|
||||||
module.fail_json(msg="Unable to get role {0}: {1}".format(name, to_native(e)), exception=traceback.format_exc())
|
module.fail_json_aws(e, msg="Unable to get role {0}".format(name))
|
||||||
|
|
||||||
|
|
||||||
def get_attached_policy_list(connection, module, name):
|
def get_attached_policy_list(connection, module, name):
|
||||||
|
@ -472,11 +482,9 @@ def get_attached_policy_list(connection, module, name):
|
||||||
if e.response['Error']['Code'] == 'NoSuchEntity':
|
if e.response['Error']['Code'] == 'NoSuchEntity':
|
||||||
return []
|
return []
|
||||||
else:
|
else:
|
||||||
module.fail_json(msg="Unable to list attached policies for role {0}: {1}".format(name, to_native(e)),
|
module.fail_json_aws(e, msg="Unable to list attached policies for role {0}".format(name))
|
||||||
exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
|
|
||||||
except BotoCoreError as e:
|
except BotoCoreError as e:
|
||||||
module.fail_json(msg="Unable to list attached policies for role {0}: {1}".format(name, to_native(e)),
|
module.fail_json_aws(e, msg="Unable to list attached policies for role {0}".format(name))
|
||||||
exception=traceback.format_exc())
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
@ -486,21 +494,34 @@ def main():
|
||||||
path=dict(type='str', default="/"),
|
path=dict(type='str', default="/"),
|
||||||
assume_role_policy_document=dict(type='json'),
|
assume_role_policy_document=dict(type='json'),
|
||||||
managed_policy=dict(type='list', aliases=['managed_policies']),
|
managed_policy=dict(type='list', aliases=['managed_policies']),
|
||||||
|
max_session_duration=dict(type='int'),
|
||||||
state=dict(type='str', choices=['present', 'absent'], default='present'),
|
state=dict(type='str', choices=['present', 'absent'], default='present'),
|
||||||
description=dict(type='str'),
|
description=dict(type='str'),
|
||||||
boundary=dict(type='str', aliases=['boundary_policy_arn']),
|
boundary=dict(type='str', aliases=['boundary_policy_arn']),
|
||||||
create_instance_profile=dict(type='bool', default=True),
|
create_instance_profile=dict(type='bool', default=True),
|
||||||
|
delete_instance_profile=dict(type='bool', default=False),
|
||||||
purge_policies=dict(type='bool', default=True),
|
purge_policies=dict(type='bool', default=True),
|
||||||
)
|
)
|
||||||
module = AnsibleAWSModule(argument_spec=argument_spec,
|
module = AnsibleAWSModule(argument_spec=argument_spec,
|
||||||
required_if=[('state', 'present', ['assume_role_policy_document'])],
|
required_if=[('state', 'present', ['assume_role_policy_document'])],
|
||||||
supports_check_mode=True)
|
supports_check_mode=True)
|
||||||
|
|
||||||
if module.params.get('boundary') and module.params.get('create_instance_profile'):
|
if module.params.get('boundary'):
|
||||||
module.fail_json(msg="When using a boundary policy, `create_instance_profile` must be set to `false`.")
|
if module.params.get('create_instance_profile'):
|
||||||
|
module.fail_json(msg="When using a boundary policy, `create_instance_profile` must be set to `false`.")
|
||||||
|
if not module.params.get('boundary').startswith('arn:aws:iam'):
|
||||||
|
module.fail_json(msg="Boundary policy must be an ARN")
|
||||||
if module.params.get('boundary') is not None and not module.botocore_at_least('1.10.57'):
|
if module.params.get('boundary') is not None and not module.botocore_at_least('1.10.57'):
|
||||||
module.fail_json(msg="When using a boundary policy, botocore must be at least v1.10.57. "
|
module.fail_json(msg="When using a boundary policy, botocore must be at least v1.10.57. "
|
||||||
"Current versions: boto3-{boto3_version} botocore-{botocore_version}".format(**module._gather_versions()))
|
"Current versions: boto3-{boto3_version} botocore-{botocore_version}".format(**module._gather_versions()))
|
||||||
|
if module.params.get('max_session_duration'):
|
||||||
|
max_session_duration = module.params.get('max_session_duration')
|
||||||
|
if max_session_duration < 3600 or max_session_duration > 43200:
|
||||||
|
module.fail_json(msg="max_session_duration must be between 1 and 12 hours (3600 and 43200 seconds)")
|
||||||
|
if module.params.get('path'):
|
||||||
|
path = module.params.get('path')
|
||||||
|
if not path.endswith('/') or not path.startswith('/'):
|
||||||
|
module.fail_json(msg="path must begin and end with /")
|
||||||
|
|
||||||
connection = module.client('iam')
|
connection = module.client('iam')
|
||||||
|
|
||||||
|
|
3
test/integration/targets/iam_role/aliases
Normal file
3
test/integration/targets/iam_role/aliases
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
iam_role_info
|
||||||
|
unsupported
|
||||||
|
cloud/aws
|
8
test/integration/targets/iam_role/defaults/main.yml
Normal file
8
test/integration/targets/iam_role/defaults/main.yml
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
---
|
||||||
|
test_role: '{{ resource_prefix }}-role'
|
||||||
|
test_path: '/{{ resource_prefix }}/'
|
||||||
|
safe_managed_policy: 'AWSDenyAll'
|
||||||
|
custom_policy_name: '{{ resource_prefix }}-denyall'
|
||||||
|
boundary_policy: 'arn:aws:iam::aws:policy/AWSDenyAll'
|
||||||
|
paranoid_pauses: no
|
||||||
|
standard_pauses: no
|
13
test/integration/targets/iam_role/files/deny-all-a.json
Normal file
13
test/integration/targets/iam_role/files/deny-all-a.json
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"Version": "2012-10-17",
|
||||||
|
"Statement": [
|
||||||
|
{
|
||||||
|
"Action": [
|
||||||
|
"*"
|
||||||
|
],
|
||||||
|
"Effect": "Deny",
|
||||||
|
"Resource": "*",
|
||||||
|
"Sid": "DenyA"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
13
test/integration/targets/iam_role/files/deny-all-b.json
Normal file
13
test/integration/targets/iam_role/files/deny-all-b.json
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"Version": "2012-10-17",
|
||||||
|
"Statement": [
|
||||||
|
{
|
||||||
|
"Action": [
|
||||||
|
"*"
|
||||||
|
],
|
||||||
|
"Effect": "Deny",
|
||||||
|
"Resource": "*",
|
||||||
|
"Sid": "DenyB"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
12
test/integration/targets/iam_role/files/deny-all.json
Normal file
12
test/integration/targets/iam_role/files/deny-all.json
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"Version": "2012-10-17",
|
||||||
|
"Statement": [
|
||||||
|
{
|
||||||
|
"Action": [
|
||||||
|
"*"
|
||||||
|
],
|
||||||
|
"Effect": "Deny",
|
||||||
|
"Resource": "*"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
10
test/integration/targets/iam_role/files/deny-assume.json
Normal file
10
test/integration/targets/iam_role/files/deny-assume.json
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"Version": "2012-10-17",
|
||||||
|
"Statement": [
|
||||||
|
{
|
||||||
|
"Action": "sts:AssumeRole",
|
||||||
|
"Principal": { "Service": "ec2.amazonaws.com" },
|
||||||
|
"Effect": "Deny"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
3
test/integration/targets/iam_role/meta/main.yml
Normal file
3
test/integration/targets/iam_role/meta/main.yml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
dependencies:
|
||||||
|
- prepare_tests
|
||||||
|
- setup_ec2
|
1218
test/integration/targets/iam_role/tasks/main.yml
Normal file
1218
test/integration/targets/iam_role/tasks/main.yml
Normal file
File diff suppressed because it is too large
Load diff
|
@ -1,3 +1,2 @@
|
||||||
cloud/aws
|
cloud/aws
|
||||||
shippable/aws/group2
|
shippable/aws/group2
|
||||||
iam_role
|
|
||||||
|
|
Loading…
Reference in a new issue