mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
[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
This commit is contained in:
parent
e2e3bf4073
commit
43c508b3c3
1 changed files with 64 additions and 26 deletions
|
@ -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 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()))
|
||||
|
||||
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)
|
||||
|
||||
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…
Reference in a new issue