[AWS] iam_role->AnsibleAWSModule and support IAM permission boundaries (#43270)

* Convert to AnsibleAWSModule and support IAM permission boundaries

* Handle adding boundary to existing role that lacks one

* Properly clean up role boundary associations on delete

* Handle case when policy boundary is `""` but does not exist
pull/43458/head
Ryan Brown 6 years ago committed by GitHub
parent e2e3bf4073
commit 43c508b3c3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -27,6 +27,14 @@ options:
description:
- Provide a description of the new role
version_added: "2.5"
boundary:
description:
- Add the ARN of an IAM managed policy to restrict the permissions this role can pass on to IAM roles/users that it creates.
- Boundaries cannot be set on Instance Profiles, so if this option is specified then C(create_instance_profile) must be false.
- This is intended for roles/users that have permissions to create new IAM objects.
- For more information on boundaries, see U(https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_boundaries.html)
aliases: [boundary_policy_arn]
version_added: "2.7"
assume_role_policy_document:
description:
- The trust relationship policy document that grants an entity permission to assume the role.
@ -150,9 +158,9 @@ iam_role:
'''
from ansible.module_utils._text import to_native
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.aws.core import AnsibleAWSModule
from ansible.module_utils.ec2 import camel_dict_to_snake_dict, ec2_argument_spec, get_aws_connection_info, boto3_conn, sort_json_policy_dict
from ansible.module_utils.ec2 import HAS_BOTO3, AWSRetry
from ansible.module_utils.ec2 import AWSRetry
import json
import traceback
@ -160,7 +168,7 @@ import traceback
try:
from botocore.exceptions import ClientError, BotoCoreError
except ImportError:
pass # caught by imported HAS_BOTO3
pass # caught by AnsibleAWSModule
def compare_assume_role_policy_doc(current_policy_doc, new_policy_doc):
@ -218,13 +226,14 @@ def remove_policies(connection, module, policies_to_remove, params):
def create_or_update_role(connection, module):
params = dict()
params['Path'] = module.params.get('path')
params['RoleName'] = module.params.get('name')
params['AssumeRolePolicyDocument'] = module.params.get('assume_role_policy_document')
if module.params.get('description') is not None:
params['Description'] = module.params.get('description')
if module.params.get('boundary') is not None:
params['PermissionsBoundary'] = module.params.get('boundary')
managed_policies = module.params.get('managed_policy')
create_instance_profile = module.params.get('create_instance_profile')
if managed_policies:
@ -339,6 +348,29 @@ def create_or_update_role(connection, module):
module.fail_json(msg="Unable to update description for role {0}: {1}".format(params['RoleName'], to_native(e)),
exception=traceback.format_exc())
# Check if permission boundary needs update
if not role.get('MadeInCheckMode') and (
(role.get('PermissionsBoundary') or {}).get('PermissionsBoundaryArn') or
params.get('PermissionsBoundary') is not None):
# the existing role has a boundary
if module.params.get('boundary') is None:
pass
elif module.params.get('boundary') == '':
if (role.get('PermissionsBoundary') or {}).get('PermissionsBoundaryArn'):
try:
if not module.check_mode:
connection.delete_role_permissions_boundary(RoleName=params['RoleName'])
changed = True
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)))
elif (role.get('PermissionsBoundary') or {}).get('PermissionsBoundaryArn') != params['PermissionsBoundary']:
try:
if not module.check_mode:
connection.put_role_permissions_boundary(RoleName=params['RoleName'], PermissionsBoundary=params['PermissionsBoundary'])
changed = True
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)))
# Get the role again
if not role.get('MadeInCheckMode', False):
role = get_role(connection, module, params['RoleName'])
@ -352,7 +384,9 @@ def destroy_role(connection, module):
params = dict()
params['RoleName'] = module.params.get('name')
if get_role(connection, module, params['RoleName']):
role = get_role(connection, module, params['RoleName'])
if role:
# We need to remove any instance profiles from the role before we delete it
try:
@ -364,6 +398,12 @@ def destroy_role(connection, module):
module.fail_json(msg="Unable to list instance profiles for role {0}: {1}".format(params['RoleName'], to_native(e)),
exception=traceback.format_exc())
if role.get('PermissionsBoundary') is not None:
try:
connection.delete_role_permissions_boundary(RoleName=params['RoleName'])
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))
# Now remove the role from the instance profile(s)
for profile in instance_profiles:
try:
@ -441,30 +481,28 @@ def get_attached_policy_list(connection, module, name):
def main():
argument_spec = ec2_argument_spec()
argument_spec.update(
dict(
name=dict(type='str', required=True),
path=dict(type='str', default="/"),
assume_role_policy_document=dict(type='json'),
managed_policy=dict(type='list', aliases=['managed_policies']),
state=dict(type='str', choices=['present', 'absent'], default='present'),
description=dict(type='str'),
create_instance_profile=dict(type='bool', default=True),
purge_policies=dict(type='bool', default=True),
)
argument_spec = dict(
name=dict(type='str', required=True),
path=dict(type='str', default="/"),
assume_role_policy_document=dict(type='json'),
managed_policy=dict(type='list', aliases=['managed_policies']),
state=dict(type='str', choices=['present', 'absent'], default='present'),
description=dict(type='str'),
boundary=dict(type='str', aliases=['boundary_policy_arn']),
create_instance_profile=dict(type='bool', default=True),
purge_policies=dict(type='bool', default=True),
)
module = AnsibleAWSModule(argument_spec=argument_spec,
required_if=[('state', 'present', ['assume_role_policy_document'])],
supports_check_mode=True)
module = AnsibleModule(argument_spec=argument_spec,
required_if=[('state', 'present', ['assume_role_policy_document'])],
supports_check_mode=True)
if not HAS_BOTO3:
module.fail_json(msg='boto3 required for this module')
region, ec2_url, aws_connect_params = get_aws_connection_info(module, boto3=True)
if module.params.get('boundary') and module.params.get('create_instance_profile'):
module.fail_json(msg="When using a boundary policy, `create_instance_profile` must be set to `false`.")
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. "
"Current versions: boto3-{boto3_version} botocore-{botocore_version}".format(**module._gather_versions()))
connection = boto3_conn(module, conn_type='client', resource='iam', region=region, endpoint=ec2_url, **aws_connect_params)
connection = module.client('iam')
state = module.params.get("state")

Loading…
Cancel
Save