Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

iam_user password management support #822

Merged
merged 9 commits into from
Dec 13, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions changelogs/fragments/822-add-password-support-iam_user.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
minor_changes:
- iam_user - add password management support bringing parity with `iam` module (https://github.com/ansible-collections/community.aws/pull/822).
- iam_user - add boto3 waiter for iam user creation (https://github.com/ansible-collections/community.aws/pull/822).
130 changes: 126 additions & 4 deletions plugins/modules/iam_user.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,36 @@
version_added: 1.0.0
short_description: Manage AWS IAM users
description:
- Manage AWS IAM users.
- A module to manage AWS IAM users.
- The module does not manage groups that users belong to, groups memberships can be managed using `iam_group`.
author: Josh Souza (@joshsouza)
options:
name:
description:
- The name of the user to create.
required: true
type: str
password:
description:
- The password to apply to the user.
required: false
type: str
version_added: 2.2.0
update_password:
default: always
choices: ['always', 'on_create']
description:
- When to update user passwords.
- I(update_password=always) will ensure the password is set to I(password).
- I(update_password=on_create) will only set the password for newly created users.
type: str
version_added: 2.2.0
remove_password:
description:
- Option to delete user login passwords.
- This field is mutually exclusive to I(password).
type: 'bool'
version_added: 2.2.0
managed_policies:
description:
- A list of managed policy ARNs or friendly names to attach to the user.
Expand All @@ -36,7 +58,7 @@
type: str
purge_policies:
description:
- When I(purge_policies=true) any managed policies not listed in I(managed_policies) will be detatched.
- When I(purge_policies=true) any managed policies not listed in I(managed_policies) will be detached.
required: false
default: false
type: bool
Expand All @@ -53,6 +75,19 @@
default: true
type: bool
version_added: 2.1.0
wait:
description:
- When I(wait=True) the module will wait for up to I(wait_timeout) seconds
for IAM user creation before returning.
default: True
type: bool
version_added: 2.2.0
wait_timeout:
description:
- How long (in seconds) to wait for creation / updates to complete.
default: 120
type: int
version_added: 2.2.0
extends_documentation_fragment:
- amazon.aws.aws
- amazon.aws.ec2
Expand All @@ -70,6 +105,12 @@
name: testuser1
state: present

- name: Create a user with a password
community.aws.iam_user:
name: testuser1
password: SomeSecurePassword
state: present

- name: Create a user and attach a managed policy using its ARN
community.aws.iam_user:
name: testuser1
Expand Down Expand Up @@ -179,15 +220,75 @@ def convert_friendly_names_to_arns(connection, module, policy_names):
module.fail_json(msg="Couldn't find policy: " + str(e))


def wait_iam_exists(connection, module):
if module.check_mode:
return
if not module.params.get('wait'):
return

user_name = module.params.get('name')
wait_timeout = module.params.get('wait_timeout')

delay = min(wait_timeout, 5)
max_attempts = wait_timeout // delay

try:
waiter = connection.get_waiter('user_exists')
waiter.wait(
WaiterConfig={'Delay': delay, 'MaxAttempts': max_attempts},
UserName=user_name,
)
except botocore.exceptions.WaiterError as e:
module.fail_json_aws(e, msg='Timeout while waiting on IAM user creation')
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
module.fail_json_aws(e, msg='Failed while waiting on IAM user creation')


def create_or_update_login_profile(connection, module):

# Apply new password / update password for the user
user_params = dict()
user_params['UserName'] = module.params.get('name')
user_params['Password'] = module.params.get('password')

try:
connection.update_login_profile(**user_params)
except is_boto3_error_code('NoSuchEntity'):
try:
connection.create_login_profile(**user_params)
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
module.fail_json_aws(e, msg="Unable to create user login profile")
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: # pylint: disable=duplicate-except
module.fail_json_aws(e, msg="Unable to update user login profile")

return True


def delete_login_profile(connection, module):

user_params = dict()
user_params['UserName'] = module.params.get('name')

try:
connection.delete_login_profile(**user_params)
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
module.fail_json_aws(e, msg="Unable to delete user login profile")

return True


def create_or_update_user(connection, module):

params = dict()
params['UserName'] = module.params.get('name')
managed_policies = module.params.get('managed_policies')
purge_policies = module.params.get('purge_policies')

if module.params.get('tags') is not None:
params["Tags"] = ansible_dict_to_boto3_tag_list(module.params.get('tags'))

changed = False

if managed_policies:
managed_policies = convert_friendly_names_to_arns(connection, module, managed_policies)

Expand All @@ -205,8 +306,23 @@ def create_or_update_user(connection, module):
changed = True
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
module.fail_json_aws(e, msg="Unable to create user")

# Wait for user to be fully available before continuing
if module.params.get('wait'):
wait_iam_exists(connection, module)

if module.params.get('password') is not None:
create_or_update_login_profile(connection, module)
else:
changed = update_user_tags(connection, module, params, user)
login_profile_result = None
update_result = update_user_tags(connection, module, params, user)

if module.params['update_password'] == "always" and module.params.get('password') is not None:
login_profile_result = create_or_update_login_profile(connection, module)
elif module.params.get('remove_password'):
login_profile_result = delete_login_profile(connection, module)

changed = bool(update_result) or bool(login_profile_result)

# Manage managed policies
current_attached_policies = get_attached_policy_list(connection, module, params['UserName'])
Expand Down Expand Up @@ -388,16 +504,22 @@ def main():

argument_spec = dict(
name=dict(required=True, type='str'),
password=dict(type='str', no_log=True),
update_password=dict(default='always', choices=['always', 'on_create'], no_log=False),
remove_password=dict(type='bool'),
managed_policies=dict(default=[], type='list', aliases=['managed_policy'], elements='str'),
state=dict(choices=['present', 'absent'], required=True),
purge_policies=dict(default=False, type='bool', aliases=['purge_policy', 'purge_managed_policies']),
tags=dict(type='dict'),
purge_tags=dict(type='bool', default=True),
wait=dict(type='bool', default=True),
wait_timeout=dict(default=120, type='int'),
)

module = AnsibleAWSModule(
argument_spec=argument_spec,
supports_check_mode=True
supports_check_mode=True,
mutually_exclusive=[['password', 'remove_password']]
)

connection = module.client('iam')
Expand Down
4 changes: 4 additions & 0 deletions tests/integration/targets/iam_user/defaults/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
test_group: '{{ resource_prefix }}-group'
test_path: '/'
test_user: '{{ test_users[0] }}'
test_user3: '{{ test_users[2] }}'
test_password: ATotallySecureUncrackablePassword1!
test_new_password: ATotallyNewSecureUncrackablePassword1!
test_users:
- '{{ resource_prefix }}-user-a'
- '{{ resource_prefix }}-user-b'
- '{{ resource_prefix }}-user-c'
Loading