-
Notifications
You must be signed in to change notification settings - Fork 345
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1044 from GomathiselviS/promote_iam_policy
Migrate iam_policy* module and tests Depends-On: ansible/zuul-config#443 SUMMARY Migrate iam_policy* modules ISSUE TYPE Bugfix Pull Request Docs Pull Request Feature Pull Request New Module Pull Request COMPONENT NAME ADDITIONAL INFORMATION Reviewed-by: Alina Buzachis <None> Reviewed-by: Gonéri Le Bouder <[email protected]>
- Loading branch information
Showing
14 changed files
with
1,867 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
major_changes: | ||
- iam_policy - The module has been migrated from the ``community.aws`` collection. | ||
Playbooks using the Fully Qualified Collection Name for this module should be updated | ||
to use ``amazon.aws.iam_policy``. | ||
- iam_policy_info - The module has been migrated from the ``community.aws`` collection. | ||
Playbooks using the Fully Qualified Collection Name for this module should be updated | ||
to use ``amazon.aws.iam_policy_info``. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,349 @@ | ||
#!/usr/bin/python | ||
# This file is part of Ansible | ||
# 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 = ''' | ||
--- | ||
module: iam_policy | ||
version_added: 1.0.0 | ||
short_description: Manage inline IAM policies for users, groups, and roles | ||
description: | ||
- Allows uploading or removing inline IAM policies for IAM users, groups or roles. | ||
- To administer managed policies please see M(community.aws.iam_user), M(community.aws.iam_role), | ||
M(community.aws.iam_group) and M(community.aws.iam_managed_policy) | ||
options: | ||
iam_type: | ||
description: | ||
- Type of IAM resource. | ||
required: true | ||
choices: [ "user", "group", "role"] | ||
type: str | ||
iam_name: | ||
description: | ||
- Name of IAM resource you wish to target for policy actions. In other words, the user name, group name or role name. | ||
required: true | ||
type: str | ||
policy_name: | ||
description: | ||
- The name label for the policy to create or remove. | ||
required: true | ||
type: str | ||
policy_json: | ||
description: | ||
- A properly json formatted policy as string. | ||
type: json | ||
state: | ||
description: | ||
- Whether to create or delete the IAM policy. | ||
choices: [ "present", "absent"] | ||
default: present | ||
type: str | ||
skip_duplicates: | ||
description: | ||
- When I(skip_duplicates=true) the module looks for any policies that match the document you pass in. | ||
If there is a match it will not make a new policy object with the same rules. | ||
default: false | ||
type: bool | ||
author: | ||
- "Jonathan I. Davila (@defionscode)" | ||
- "Dennis Podkovyrin (@sbj-ss)" | ||
extends_documentation_fragment: | ||
- amazon.aws.aws | ||
- amazon.aws.ec2 | ||
''' | ||
|
||
EXAMPLES = ''' | ||
# Advanced example, create two new groups and add a READ-ONLY policy to both | ||
# groups. | ||
- name: Create Two Groups, Mario and Luigi | ||
community.aws.iam_group: | ||
name: "{{ item }}" | ||
state: present | ||
loop: | ||
- Mario | ||
- Luigi | ||
register: new_groups | ||
- name: Apply READ-ONLY policy to new groups that have been recently created | ||
amazon.aws.iam_policy: | ||
iam_type: group | ||
iam_name: "{{ item.iam_group.group.group_name }}" | ||
policy_name: "READ-ONLY" | ||
policy_json: "{{ lookup('template', 'readonly.json.j2') }}" | ||
state: present | ||
loop: "{{ new_groups.results }}" | ||
# Create a new S3 policy with prefix per user | ||
- name: Create S3 policy from template | ||
amazon.aws.iam_policy: | ||
iam_type: user | ||
iam_name: "{{ item.user }}" | ||
policy_name: "s3_limited_access_{{ item.prefix }}" | ||
state: present | ||
policy_json: "{{ lookup('template', 's3_policy.json.j2') }}" | ||
loop: | ||
- user: s3_user | ||
prefix: s3_user_prefix | ||
''' | ||
RETURN = ''' | ||
policy_names: | ||
description: A list of names of the inline policies embedded in the specified IAM resource (user, group, or role). | ||
returned: always | ||
type: list | ||
elements: str | ||
''' | ||
|
||
import json | ||
|
||
try: | ||
from botocore.exceptions import BotoCoreError, ClientError | ||
except ImportError: | ||
pass | ||
|
||
from ansible.module_utils.six import string_types | ||
from ansible_collections.amazon.aws.plugins.module_utils.core import AnsibleAWSModule | ||
from ansible_collections.amazon.aws.plugins.module_utils.ec2 import AWSRetry | ||
from ansible_collections.amazon.aws.plugins.module_utils.ec2 import compare_policies | ||
from ansible_collections.amazon.aws.plugins.module_utils.botocore import is_boto3_error_code | ||
|
||
|
||
class PolicyError(Exception): | ||
pass | ||
|
||
|
||
class Policy: | ||
|
||
def __init__(self, client, name, policy_name, policy_json, skip_duplicates, state, check_mode): | ||
self.client = client | ||
self.name = name | ||
self.policy_name = policy_name | ||
self.policy_json = policy_json | ||
self.skip_duplicates = skip_duplicates | ||
self.state = state | ||
self.check_mode = check_mode | ||
self.changed = False | ||
|
||
self.original_policies = self.get_all_policies().copy() | ||
self.updated_policies = {} | ||
|
||
@staticmethod | ||
def _iam_type(): | ||
return '' | ||
|
||
def _list(self, name): | ||
return {} | ||
|
||
def list(self): | ||
try: | ||
return self._list(self.name).get('PolicyNames', []) | ||
except is_boto3_error_code('AccessDenied'): | ||
return [] | ||
|
||
def _get(self, name, policy_name): | ||
return '{}' | ||
|
||
def get(self, policy_name): | ||
try: | ||
return self._get(self.name, policy_name)['PolicyDocument'] | ||
except is_boto3_error_code('AccessDenied'): | ||
return {} | ||
|
||
def _put(self, name, policy_name, policy_doc): | ||
pass | ||
|
||
def put(self, policy_doc): | ||
self.changed = True | ||
|
||
if self.check_mode: | ||
return | ||
|
||
self._put(self.name, self.policy_name, json.dumps(policy_doc, sort_keys=True)) | ||
|
||
def _delete(self, name, policy_name): | ||
pass | ||
|
||
def delete(self): | ||
self.updated_policies = self.original_policies.copy() | ||
|
||
if self.policy_name not in self.list(): | ||
self.changed = False | ||
return | ||
|
||
self.changed = True | ||
self.updated_policies.pop(self.policy_name, None) | ||
|
||
if self.check_mode: | ||
return | ||
|
||
self._delete(self.name, self.policy_name) | ||
|
||
def get_policy_text(self): | ||
try: | ||
if self.policy_json is not None: | ||
return self.get_policy_from_json() | ||
except json.JSONDecodeError as e: | ||
raise PolicyError('Failed to decode the policy as valid JSON: %s' % str(e)) | ||
return None | ||
|
||
def get_policy_from_json(self): | ||
if isinstance(self.policy_json, string_types): | ||
pdoc = json.loads(self.policy_json) | ||
else: | ||
pdoc = self.policy_json | ||
return pdoc | ||
|
||
def get_all_policies(self): | ||
policies = {} | ||
for pol in self.list(): | ||
policies[pol] = self.get(pol) | ||
return policies | ||
|
||
def create(self): | ||
matching_policies = [] | ||
policy_doc = self.get_policy_text() | ||
policy_match = False | ||
for pol in self.list(): | ||
if not compare_policies(self.original_policies[pol], policy_doc): | ||
matching_policies.append(pol) | ||
policy_match = True | ||
|
||
self.updated_policies = self.original_policies.copy() | ||
|
||
if self.policy_name in matching_policies: | ||
return | ||
if self.skip_duplicates and policy_match: | ||
return | ||
|
||
self.put(policy_doc) | ||
self.updated_policies[self.policy_name] = policy_doc | ||
|
||
def run(self): | ||
if self.state == 'present': | ||
self.create() | ||
elif self.state == 'absent': | ||
self.delete() | ||
return { | ||
'changed': self.changed, | ||
self._iam_type() + '_name': self.name, | ||
'policies': self.list(), | ||
'policy_names': self.list(), | ||
'diff': dict( | ||
before=self.original_policies, | ||
after=self.updated_policies, | ||
), | ||
} | ||
|
||
|
||
class UserPolicy(Policy): | ||
|
||
@staticmethod | ||
def _iam_type(): | ||
return 'user' | ||
|
||
def _list(self, name): | ||
return self.client.list_user_policies(aws_retry=True, UserName=name) | ||
|
||
def _get(self, name, policy_name): | ||
return self.client.get_user_policy(aws_retry=True, UserName=name, PolicyName=policy_name) | ||
|
||
def _put(self, name, policy_name, policy_doc): | ||
return self.client.put_user_policy(aws_retry=True, UserName=name, PolicyName=policy_name, PolicyDocument=policy_doc) | ||
|
||
def _delete(self, name, policy_name): | ||
return self.client.delete_user_policy(aws_retry=True, UserName=name, PolicyName=policy_name) | ||
|
||
|
||
class RolePolicy(Policy): | ||
|
||
@staticmethod | ||
def _iam_type(): | ||
return 'role' | ||
|
||
def _list(self, name): | ||
return self.client.list_role_policies(aws_retry=True, RoleName=name) | ||
|
||
def _get(self, name, policy_name): | ||
return self.client.get_role_policy(aws_retry=True, RoleName=name, PolicyName=policy_name) | ||
|
||
def _put(self, name, policy_name, policy_doc): | ||
return self.client.put_role_policy(aws_retry=True, RoleName=name, PolicyName=policy_name, PolicyDocument=policy_doc) | ||
|
||
def _delete(self, name, policy_name): | ||
return self.client.delete_role_policy(aws_retry=True, RoleName=name, PolicyName=policy_name) | ||
|
||
|
||
class GroupPolicy(Policy): | ||
|
||
@staticmethod | ||
def _iam_type(): | ||
return 'group' | ||
|
||
def _list(self, name): | ||
return self.client.list_group_policies(aws_retry=True, GroupName=name) | ||
|
||
def _get(self, name, policy_name): | ||
return self.client.get_group_policy(aws_retry=True, GroupName=name, PolicyName=policy_name) | ||
|
||
def _put(self, name, policy_name, policy_doc): | ||
return self.client.put_group_policy(aws_retry=True, GroupName=name, PolicyName=policy_name, PolicyDocument=policy_doc) | ||
|
||
def _delete(self, name, policy_name): | ||
return self.client.delete_group_policy(aws_retry=True, GroupName=name, PolicyName=policy_name) | ||
|
||
|
||
def main(): | ||
argument_spec = dict( | ||
iam_type=dict(required=True, choices=['user', 'group', 'role']), | ||
state=dict(default='present', choices=['present', 'absent']), | ||
iam_name=dict(required=True), | ||
policy_name=dict(required=True), | ||
policy_json=dict(type='json', default=None, required=False), | ||
skip_duplicates=dict(type='bool', default=False, required=False) | ||
) | ||
required_if = [ | ||
('state', 'present', ('policy_json',), True), | ||
] | ||
|
||
module = AnsibleAWSModule( | ||
argument_spec=argument_spec, | ||
required_if=required_if, | ||
supports_check_mode=True | ||
) | ||
|
||
args = dict( | ||
client=module.client('iam', retry_decorator=AWSRetry.jittered_backoff()), | ||
name=module.params.get('iam_name'), | ||
policy_name=module.params.get('policy_name'), | ||
policy_json=module.params.get('policy_json'), | ||
skip_duplicates=module.params.get('skip_duplicates'), | ||
state=module.params.get('state'), | ||
check_mode=module.check_mode, | ||
) | ||
iam_type = module.params.get('iam_type') | ||
|
||
try: | ||
if iam_type == 'user': | ||
policy = UserPolicy(**args) | ||
elif iam_type == 'role': | ||
policy = RolePolicy(**args) | ||
elif iam_type == 'group': | ||
policy = GroupPolicy(**args) | ||
|
||
module.deprecate("The 'policies' return key is deprecated and will be replaced by 'policy_names'. Both values are returned for now.", | ||
date='2024-08-01', collection_name='amazon.aws') | ||
|
||
module.exit_json(**(policy.run())) | ||
except (BotoCoreError, ClientError) as e: | ||
module.fail_json_aws(e) | ||
except PolicyError as e: | ||
module.fail_json(msg=str(e)) | ||
|
||
|
||
if __name__ == '__main__': | ||
main() |
Oops, something went wrong.