Repository URL to install this package:
Version:
6.0.0 ▾
|
#!/usr/bin/python
# Copyright: Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = r'''
---
module: iam_managed_policy
version_added: 1.0.0
short_description: Manage User Managed IAM policies
description:
- Allows creating and removing managed IAM policies
options:
policy_name:
description:
- The name of the managed policy.
required: True
type: str
policy_description:
description:
- A helpful description of this policy, this value is immutable and only set when creating a new policy.
default: ''
type: str
policy:
description:
- A properly json formatted policy
type: json
make_default:
description:
- Make this revision the default revision.
default: True
type: bool
only_version:
description:
- Remove all other non default revisions, if this is used with C(make_default) it will result in all other versions of this policy being deleted.
type: bool
default: false
state:
description:
- Should this managed policy be present or absent. Set to absent to detach all entities from this policy and remove it if found.
default: present
choices: [ "present", "absent" ]
type: str
fail_on_delete:
description:
- The I(fail_on_delete) option does nothing and will be removed after 2022-06-01
type: bool
author: "Dan Kozlowski (@dkhenry)"
extends_documentation_fragment:
- amazon.aws.aws
- amazon.aws.ec2
'''
EXAMPLES = r'''
# Create a policy
- name: Create IAM Managed Policy
community.aws.iam_managed_policy:
policy_name: "ManagedPolicy"
policy_description: "A Helpful managed policy"
policy: "{{ lookup('template', 'managed_policy.json.j2') }}"
state: present
# Update a policy with a new default version
- name: Update an IAM Managed Policy with new default version
community.aws.iam_managed_policy:
policy_name: "ManagedPolicy"
policy: "{{ lookup('file', 'managed_policy_update.json') }}"
state: present
# Update a policy with a new non default version
- name: Update an IAM Managed Policy with a non default version
community.aws.iam_managed_policy:
policy_name: "ManagedPolicy"
policy:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Action: "logs:CreateLogGroup"
Resource: "*"
make_default: false
state: present
# Update a policy and make it the only version and the default version
- name: Update an IAM Managed Policy with default version as the only version
community.aws.iam_managed_policy:
policy_name: "ManagedPolicy"
policy: |
{
"Version": "2012-10-17",
"Statement":[{
"Effect": "Allow",
"Action": "logs:PutRetentionPolicy",
"Resource": "*"
}]
}
only_version: true
state: present
# Remove a policy
- name: Remove an existing IAM Managed Policy
community.aws.iam_managed_policy:
policy_name: "ManagedPolicy"
state: absent
'''
RETURN = r'''
policy:
description: Returns the policy json structure, when state == absent this will return the value of the removed policy.
returned: success
type: complex
contains: {}
sample: '{
"arn": "arn:aws:iam::aws:policy/AdministratorAccess "
"attachment_count": 0,
"create_date": "2017-03-01T15:42:55.981000+00:00",
"default_version_id": "v1",
"is_attachable": true,
"path": "/",
"policy_id": "ANPALM4KLDMTFXGOOJIHL",
"policy_name": "AdministratorAccess",
"update_date": "2017-03-01T15:42:55.981000+00:00"
}'
'''
import json
try:
import botocore
except ImportError:
pass # Handled by AnsibleAWSModule
from ansible.module_utils._text import to_native
from ansible.module_utils.common.dict_transformations import camel_dict_to_snake_dict
from ansible_collections.amazon.aws.plugins.module_utils.core import AnsibleAWSModule
from ansible_collections.amazon.aws.plugins.module_utils.core import is_boto3_error_code
from ansible_collections.amazon.aws.plugins.module_utils.ec2 import AWSRetry
from ansible_collections.amazon.aws.plugins.module_utils.ec2 import compare_policies
@AWSRetry.jittered_backoff(retries=5, delay=5, backoff=2.0)
def list_policies_with_backoff():
paginator = client.get_paginator('list_policies')
return paginator.paginate(Scope='Local').build_full_result()
def get_policy_by_name(name):
try:
response = list_policies_with_backoff()
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
module.fail_json_aws(e, msg="Couldn't list policies")
for policy in response['Policies']:
if policy['PolicyName'] == name:
return policy
return None
def delete_oldest_non_default_version(policy):
try:
versions = [v for v in client.list_policy_versions(PolicyArn=policy['Arn'])['Versions']
if not v['IsDefaultVersion']]
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
module.fail_json_aws(e, msg="Couldn't list policy versions")
versions.sort(key=lambda v: v['CreateDate'], reverse=True)
for v in versions[-1:]:
try:
client.delete_policy_version(PolicyArn=policy['Arn'], VersionId=v['VersionId'])
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
module.fail_json_aws(e, msg="Couldn't delete policy version")
# This needs to return policy_version, changed
def get_or_create_policy_version(policy, policy_document):
try:
versions = client.list_policy_versions(PolicyArn=policy['Arn'])['Versions']
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
module.fail_json_aws(e, msg="Couldn't list policy versions")
for v in versions:
try:
document = client.get_policy_version(PolicyArn=policy['Arn'], VersionId=v['VersionId'])['PolicyVersion']['Document']
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
module.fail_json_aws(e, msg="Couldn't get policy version {0}".format(v['VersionId']))
if module.check_mode and compare_policies(document, json.loads(to_native(policy_document))):
return v, True
# If the current policy matches the existing one
if not compare_policies(document, json.loads(to_native(policy_document))):
return v, False
# No existing version so create one
# There is a service limit (typically 5) of policy versions.
#
# Rather than assume that it is 5, we'll try to create the policy
# and if that doesn't work, delete the oldest non default policy version
# and try again.
try:
version = client.create_policy_version(PolicyArn=policy['Arn'], PolicyDocument=policy_document)['PolicyVersion']
return version, True
except is_boto3_error_code('LimitExceeded'):
delete_oldest_non_default_version(policy)
try:
version = client.create_policy_version(PolicyArn=policy['Arn'], PolicyDocument=policy_document)['PolicyVersion']
return version, True
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as second_e:
module.fail_json_aws(second_e, msg="Couldn't create policy version")
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: # pylint: disable=duplicate-except
module.fail_json_aws(e, msg="Couldn't create policy version")
def set_if_default(policy, policy_version, is_default):
if is_default and not policy_version['IsDefaultVersion']:
try:
client.set_default_policy_version(PolicyArn=policy['Arn'], VersionId=policy_version['VersionId'])
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
module.fail_json_aws(e, msg="Couldn't set default policy version")
return True
return False
def set_if_only(policy, policy_version, is_only):
if is_only:
try:
versions = [v for v in client.list_policy_versions(PolicyArn=policy['Arn'])[
'Versions'] if not v['IsDefaultVersion']]
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
module.fail_json_aws(e, msg="Couldn't list policy versions")
for v in versions:
try:
client.delete_policy_version(PolicyArn=policy['Arn'], VersionId=v['VersionId'])
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
module.fail_json_aws(e, msg="Couldn't delete policy version")
return len(versions) > 0
return False
def detach_all_entities(policy, **kwargs):
try:
entities = client.list_entities_for_policy(PolicyArn=policy['Arn'], **kwargs)
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
module.fail_json_aws(e, msg="Couldn't detach list entities for policy {0}".format(policy['PolicyName']))
for g in entities['PolicyGroups']:
try:
client.detach_group_policy(PolicyArn=policy['Arn'], GroupName=g['GroupName'])
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
module.fail_json_aws(e, msg="Couldn't detach group policy {0}".format(g['GroupName']))
for u in entities['PolicyUsers']:
try:
client.detach_user_policy(PolicyArn=policy['Arn'], UserName=u['UserName'])
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
module.fail_json_aws(e, msg="Couldn't detach user policy {0}".format(u['UserName']))
for r in entities['PolicyRoles']:
try:
client.detach_role_policy(PolicyArn=policy['Arn'], RoleName=r['RoleName'])
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
module.fail_json_aws(e, msg="Couldn't detach role policy {0}".format(r['RoleName']))
if entities['IsTruncated']:
detach_all_entities(policy, marker=entities['Marker'])
def create_or_update_policy(existing_policy):
name = module.params.get('policy_name')
description = module.params.get('policy_description')
default = module.params.get('make_default')
only = module.params.get('only_version')
policy = None
if module.params.get('policy') is not None:
policy = json.dumps(json.loads(module.params.get('policy')))
if existing_policy is None:
if module.check_mode:
module.exit_json(changed=True)
# Create policy when none already exists
try:
rvalue = client.create_policy(PolicyName=name, Path='/', PolicyDocument=policy, Description=description)
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
module.fail_json_aws(e, msg="Couldn't create policy {0}".format(name))
module.exit_json(changed=True, policy=camel_dict_to_snake_dict(rvalue['Policy']))
else:
policy_version, changed = get_or_create_policy_version(existing_policy, policy)
changed = set_if_default(existing_policy, policy_version, default) or changed
changed = set_if_only(existing_policy, policy_version, only) or changed
# If anything has changed we need to refresh the policy
if changed:
try:
updated_policy = client.get_policy(PolicyArn=existing_policy['Arn'])['Policy']
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
module.fail_json(msg="Couldn't get policy")
module.exit_json(changed=changed, policy=camel_dict_to_snake_dict(updated_policy))
else:
module.exit_json(changed=changed, policy=camel_dict_to_snake_dict(existing_policy))
def delete_policy(existing_policy):
# Check for existing policy
if existing_policy:
if module.check_mode:
module.exit_json(changed=True)
# Detach policy
detach_all_entities(existing_policy)
# Delete Versions
try:
versions = client.list_policy_versions(PolicyArn=existing_policy['Arn'])['Versions']
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
module.fail_json_aws(e, msg="Couldn't list policy versions")
for v in versions:
if not v['IsDefaultVersion']:
try:
client.delete_policy_version(PolicyArn=existing_policy['Arn'], VersionId=v['VersionId'])
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
module.fail_json_aws(
e, msg="Couldn't delete policy version {0}".format(v['VersionId']))
# Delete policy
try:
client.delete_policy(PolicyArn=existing_policy['Arn'])
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
module.fail_json_aws(e, msg="Couldn't delete policy {0}".format(existing_policy['PolicyName']))
# This is the one case where we will return the old policy
module.exit_json(changed=True, policy=camel_dict_to_snake_dict(existing_policy))
else:
module.exit_json(changed=False, policy=None)
def main():
global module
global client
argument_spec = dict(
policy_name=dict(required=True),
policy_description=dict(default=''),
policy=dict(type='json'),
make_default=dict(type='bool', default=True),
only_version=dict(type='bool', default=False),
fail_on_delete=dict(type='bool', removed_at_date='2022-06-01', removed_from_collection='community.aws'),
state=dict(default='present', choices=['present', 'absent']),
)
module = AnsibleAWSModule(
argument_spec=argument_spec,
required_if=[['state', 'present', ['policy']]],
supports_check_mode=True
)
name = module.params.get('policy_name')
state = module.params.get('state')
try:
client = module.client('iam', retry_decorator=AWSRetry.jittered_backoff())
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
module.fail_json_aws(e, msg='Failed to connect to AWS')
existing_policy = get_policy_by_name(name)
if state == 'present':
create_or_update_policy(existing_policy)
else:
delete_policy(existing_policy)
if __name__ == '__main__':
main()