Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
jillr authored and alinabuzachis committed Oct 6, 2023
1 parent af2c7d6 commit 140fad5
Show file tree
Hide file tree
Showing 3 changed files with 323 additions and 0 deletions.
216 changes: 216 additions & 0 deletions plugins/modules/iam_password_policy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
#!/usr/bin/python

# Copyright: (c) 2018, Aaron Smith <[email protected]>
# 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


ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}


DOCUMENTATION = '''
---
module: iam_password_policy
short_description: Update an IAM Password Policy
description:
- Module updates an IAM Password Policy on a given AWS account
requirements: [ 'botocore', 'boto3' ]
author:
- "Aaron Smith (@slapula)"
options:
state:
description:
- Specifies the overall state of the password policy.
required: true
choices: ['present', 'absent']
type: str
min_pw_length:
description:
- Minimum password length.
default: 6
aliases: [minimum_password_length]
type: int
require_symbols:
description:
- Require symbols in password.
default: false
type: bool
require_numbers:
description:
- Require numbers in password.
default: false
type: bool
require_uppercase:
description:
- Require uppercase letters in password.
default: false
type: bool
require_lowercase:
description:
- Require lowercase letters in password.
default: false
type: bool
allow_pw_change:
description:
- Allow users to change their password.
default: false
type: bool
aliases: [allow_password_change]
pw_max_age:
description:
- Maximum age for a password in days. When this option is 0 then passwords
do not expire automatically.
default: 0
aliases: [password_max_age]
type: int
pw_reuse_prevent:
description:
- Prevent re-use of passwords.
default: 0
aliases: [password_reuse_prevent, prevent_reuse]
type: int
pw_expire:
description:
- Prevents users from change an expired password.
default: false
type: bool
aliases: [password_expire, expire]
extends_documentation_fragment:
- ansible.amazon.aws
- ansible.amazon.ec2
'''

EXAMPLES = '''
- name: Password policy for AWS account
iam_password_policy:
state: present
min_pw_length: 8
require_symbols: false
require_numbers: true
require_uppercase: true
require_lowercase: true
allow_pw_change: true
pw_max_age: 60
pw_reuse_prevent: 5
pw_expire: false
'''

RETURN = ''' # '''

try:
import botocore
except ImportError:
pass # caught by AnsibleAWSModule

from ansible_collections.ansible.amazon.plugins.module_utils.aws.core import AnsibleAWSModule
from ansible_collections.ansible.amazon.plugins.module_utils.ec2 import camel_dict_to_snake_dict


class IAMConnection(object):
def __init__(self, module):
try:
self.connection = module.resource('iam')
self.module = module
except Exception as e:
module.fail_json(msg="Failed to connect to AWS: %s" % str(e))

def policy_to_dict(self, policy):
policy_attributes = [
'allow_users_to_change_password', 'expire_passwords', 'hard_expiry',
'max_password_age', 'minimum_password_length', 'password_reuse_prevention',
'require_lowercase_characters', 'require_numbers', 'require_symbols', 'require_uppercase_characters'
]
ret = {}
for attr in policy_attributes:
ret[attr] = getattr(policy, attr)
return ret

def update_password_policy(self, module, policy):
min_pw_length = module.params.get('min_pw_length')
require_symbols = module.params.get('require_symbols')
require_numbers = module.params.get('require_numbers')
require_uppercase = module.params.get('require_uppercase')
require_lowercase = module.params.get('require_lowercase')
allow_pw_change = module.params.get('allow_pw_change')
pw_max_age = module.params.get('pw_max_age')
pw_reuse_prevent = module.params.get('pw_reuse_prevent')
pw_expire = module.params.get('pw_expire')

update_parameters = dict(
MinimumPasswordLength=min_pw_length,
RequireSymbols=require_symbols,
RequireNumbers=require_numbers,
RequireUppercaseCharacters=require_uppercase,
RequireLowercaseCharacters=require_lowercase,
AllowUsersToChangePassword=allow_pw_change,
HardExpiry=pw_expire
)
if pw_reuse_prevent:
update_parameters.update(PasswordReusePrevention=pw_reuse_prevent)
if pw_max_age:
update_parameters.update(MaxPasswordAge=pw_max_age)

try:
original_policy = self.policy_to_dict(policy)
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
original_policy = {}

try:
results = policy.update(**update_parameters)
policy.reload()
updated_policy = self.policy_to_dict(policy)
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
self.module.fail_json_aws(e, msg="Couldn't update IAM Password Policy")

changed = (original_policy != updated_policy)
return (changed, updated_policy, camel_dict_to_snake_dict(results))

def delete_password_policy(self, policy):
try:
results = policy.delete()
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
if e.response['Error']['Code'] == 'NoSuchEntity':
self.module.exit_json(changed=False, task_status={'IAM': "Couldn't find IAM Password Policy"})
else:
self.module.fail_json_aws(e, msg="Couldn't delete IAM Password Policy")
return camel_dict_to_snake_dict(results)


def main():
module = AnsibleAWSModule(
argument_spec={
'state': dict(choices=['present', 'absent'], required=True),
'min_pw_length': dict(type='int', aliases=['minimum_password_length'], default=6),
'require_symbols': dict(type='bool', default=False),
'require_numbers': dict(type='bool', default=False),
'require_uppercase': dict(type='bool', default=False),
'require_lowercase': dict(type='bool', default=False),
'allow_pw_change': dict(type='bool', aliases=['allow_password_change'], default=False),
'pw_max_age': dict(type='int', aliases=['password_max_age'], default=0),
'pw_reuse_prevent': dict(type='int', aliases=['password_reuse_prevent', 'prevent_reuse'], default=0),
'pw_expire': dict(type='bool', aliases=['password_expire', 'expire'], default=False),
},
supports_check_mode=True,
)

resource = IAMConnection(module)
policy = resource.connection.AccountPasswordPolicy()

state = module.params.get('state')

if state == 'present':
(changed, new_policy, update_result) = resource.update_password_policy(module, policy)
module.exit_json(changed=changed, task_status={'IAM': update_result}, policy=new_policy)

if state == 'absent':
delete_result = resource.delete_password_policy(policy)
module.exit_json(changed=True, task_status={'IAM': delete_result})


if __name__ == '__main__':
main()
2 changes: 2 additions & 0 deletions tests/integration/targets/iam_password_policy/aliases
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
cloud/aws
unsupported
105 changes: 105 additions & 0 deletions tests/integration/targets/iam_password_policy/tasks/main.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
- module_defaults:
group/aws:
aws_access_key: "{{ aws_access_key }}"
aws_secret_key: "{{ aws_secret_key }}"
security_token: "{{ security_token | default(omit) }}"
region: "{{ aws_region }}"
block:
- name: set iam password policy
iam_password_policy:
state: present
min_pw_length: 8
require_symbols: false
require_numbers: true
require_uppercase: true
require_lowercase: true
allow_pw_change: true
pw_max_age: 60
pw_reuse_prevent: 5
pw_expire: false
register: result

- name: assert that changes were made
assert:
that:
- result.changed

- name: verify iam password policy has been created
iam_password_policy:
state: present
min_pw_length: 8
require_symbols: false
require_numbers: true
require_uppercase: true
require_lowercase: true
allow_pw_change: true
pw_max_age: 60
pw_reuse_prevent: 5
pw_expire: false
register: result

- name: assert that no changes were made
assert:
that:
- not result.changed

- name: update iam password policy with different settings
iam_password_policy:
state: present
min_pw_length: 15
require_symbols: true
require_numbers: true
require_uppercase: true
require_lowercase: true
allow_pw_change: true
pw_max_age: 30
pw_reuse_prevent: 10
pw_expire: true
register: result

- name: assert that updates were made
assert:
that:
- result.changed

# Test for regression of #59102
- name: update iam password policy without expiry
iam_password_policy:
state: present
min_pw_length: 15
require_symbols: true
require_numbers: true
require_uppercase: true
require_lowercase: true
allow_pw_change: true
register: result

- name: assert that changes were made
assert:
that:
- result.changed

- name: remove iam password policy
iam_password_policy:
state: absent
register: result

- name: assert password policy has been removed
assert:
that:
- result.changed

- name: verify password policy has been removed
iam_password_policy:
state: absent
register: result

- name: assert no changes were made
assert:
that:
- not result.changed
always:
- name: remove iam password policy
iam_password_policy:
state: absent
register: result

0 comments on commit 140fad5

Please sign in to comment.