Skip to content

Commit

Permalink
iam_user password management support (ansible-collections#822)
Browse files Browse the repository at this point in the history
iam_user password management support

SUMMARY
The iam module currently supports password management for IAM users, but the newer  iam_user module does not currently. This PR adds the password management functionality to bring parity with the old module.
To ensure the IAM user is properly created before adding a login profile, the waiter for the IAM creation has also been added.
ISSUE TYPE

Feature Pull Request

COMPONENT NAME
iam_user
ADDITIONAL INFORMATION
The added functionality uses the create_login_profile and update_login_profile methods:
https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/iam.html#IAM.Client.create_login_profile
https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/iam.html#IAM.Client.update_login_profile
Local integration tests run:
ansible-test integration --docker centos8 -vv iam_user --allow-unsupported
...
PLAY RECAP *********************************************************************
testhost                   : ok=92   changed=24   unreachable=0    failed=0    skipped=0    rescued=0    ignored=2

AWS ACTIONS: ['iam:AddUserToGroup', 'iam:AttachUserPolicy', 'iam:CreateGroup', 'iam:CreateLoginProfile', 'iam:CreateUser', 'iam:DeleteGroup', 'iam:DeleteLoginProfile', 'iam:DeleteUser', 'iam:DetachUserPolicy', 'iam:GetGroup', 'iam:GetUser', 'iam:ListAccessKeys', 'iam:ListAttachedGroupPolicies', 'iam:ListAttachedUserPolicies', 'iam:ListGroupsForUser', 'iam:ListMFADevices', 'iam:ListPolicies', 'iam:ListSSHPublicKeys', 'iam:ListServiceSpecificCredentials', 'iam:ListSigningCertificates', 'iam:ListUserPolicies', 'iam:ListUsers', 'iam:RemoveUserFromGroup', 'iam:TagUser', 'iam:UntagUser', 'iam:UpdateLoginProfile']

Reviewed-by: Markus Bergholz <[email protected]>
Reviewed-by: Mark Chappell <None>
Reviewed-by: None <None>

This commit was initially merged in https://github.com/ansible-collections/community.aws
See: ansible-collections/community.aws@a3d940a
  • Loading branch information
marknet15 authored and GomathiselviS committed Sep 22, 2022
1 parent 1a3efa9 commit fe6af9c
Show file tree
Hide file tree
Showing 3 changed files with 315 additions and 112 deletions.
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

0 comments on commit fe6af9c

Please sign in to comment.