summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDaniel Goldman <merkavabuilder@gmail.com>2022-01-24 20:52:38 +0100
committerGitHub <noreply@github.com>2022-01-24 20:52:38 +0100
commitdbde2c2ae3b03469abbe8f2c98b50ffedcf7975f (patch)
tree30568ce73a9e016ee84279bacaf478de0694baf2
parentremove ansibot from collection docs (#76817) (diff)
downloadansible-dbde2c2ae3b03469abbe8f2c98b50ffedcf7975f.tar.xz
ansible-dbde2c2ae3b03469abbe8f2c98b50ffedcf7975f.zip
user module password expiration fixes (#75390)
* allow inputting 0 for password_expire_{min|max} 0 is meaningful for min days (any time) 0 is technically valid for max_days * add test for setting both min and max expiry * [0] return result of execute_command from set_password_expire* * [1] better return for set_password_expire * [2] handle returns from set_password_expire* * only set password expiry if user exists * collect return-handling code * combine password min and max into one execution * handle case where spwd is not present like on macOS and FreeBSD Co-authored-by: Sam Doran <sdoran@redhat.com>
-rw-r--r--changelogs/fragments/75017-user-password-expiry.yml3
-rw-r--r--lib/ansible/modules/user.py53
-rw-r--r--test/integration/targets/user/tasks/main.yml5
-rw-r--r--test/integration/targets/user/tasks/test_expires_min_max.yml18
4 files changed, 51 insertions, 28 deletions
diff --git a/changelogs/fragments/75017-user-password-expiry.yml b/changelogs/fragments/75017-user-password-expiry.yml
new file mode 100644
index 0000000000..b264f07f74
--- /dev/null
+++ b/changelogs/fragments/75017-user-password-expiry.yml
@@ -0,0 +1,3 @@
+bugfixes:
+ - user - allow ``password_expiry_min`` and ``password_expiry_min`` to be set to ``0`` (https://github.com/ansible/ansible/issues/75017)
+ - user - allow password min and max to be set at the same time (https://github.com/ansible/ansible/issues/75017)
diff --git a/lib/ansible/modules/user.py b/lib/ansible/modules/user.py
index 62b7a6fd69..3c089ddf00 100644
--- a/lib/ansible/modules/user.py
+++ b/lib/ansible/modules/user.py
@@ -1048,29 +1048,27 @@ class User(object):
info[1] = self.user_password()[0]
return info
- def set_password_expire_max(self):
- command_name = 'chage'
- cmd = [self.module.get_bin_path(command_name, True)]
- cmd.append('-M')
- cmd.append(self.password_expire_max)
- cmd.append(self.name)
- if self.password_expire_max == spwd.getspnam(self.name).sp_max:
- self.module.exit_json(changed=False)
- else:
- self.execute_command(cmd)
- self.module.exit_json(changed=True)
+ def set_password_expire(self):
+ min_needs_change = self.password_expire_min is not None
+ max_needs_change = self.password_expire_max is not None
+
+ if HAVE_SPWD:
+ shadow_info = spwd.getspnam(self.name)
+ min_needs_change &= self.password_expire_min != shadow_info.sp_min
+ max_needs_change &= self.password_expire_max != shadow_info.sp_max
+
+ if not (min_needs_change or max_needs_change):
+ return (None, '', '') # target state already reached
- def set_password_expire_min(self):
command_name = 'chage'
cmd = [self.module.get_bin_path(command_name, True)]
- cmd.append('-m')
- cmd.append(self.password_expire_min)
+ if min_needs_change:
+ cmd.extend(["-m", self.password_expire_min])
+ if max_needs_change:
+ cmd.extend(["-M", self.password_expire_max])
cmd.append(self.name)
- if self.password_expire_min == spwd.getspnam(self.name).sp_min:
- self.module.exit_json(changed=False)
- else:
- self.execute_command(cmd)
- self.module.exit_json(changed=True)
+
+ return self.execute_command(cmd)
def user_password(self):
passwd = ''
@@ -3207,15 +3205,14 @@ def main():
result['ssh_key_file'] = user.get_ssh_key_path()
result['ssh_public_key'] = user.get_ssh_public_key()
- # deal with password expire max
- if user.password_expire_max:
- if user.user_exists():
- user.set_password_expire_max()
-
- # deal with password expire min
- if user.password_expire_min:
- if user.user_exists():
- user.set_password_expire_min()
+ (rc, out, err) = user.set_password_expire()
+ if rc is None:
+ pass # target state reached, nothing to do
+ else:
+ if rc != 0:
+ module.fail_json(name=user.name, msg=err, rc=rc)
+ else:
+ result['changed'] = True
module.exit_json(**result)
diff --git a/test/integration/targets/user/tasks/main.yml b/test/integration/targets/user/tasks/main.yml
index 5e1d2d220d..3807831f03 100644
--- a/test/integration/targets/user/tasks/main.yml
+++ b/test/integration/targets/user/tasks/main.yml
@@ -21,6 +21,11 @@
meta: end_host
when: ansible_distribution == 'Alpine'
+- name: ensure output directory exists
+ file:
+ dest: "{{ output_dir }}"
+ state: directory
+
- import_tasks: test_create_user.yml
- import_tasks: test_create_system_user.yml
- import_tasks: test_create_user_uid.yml
diff --git a/test/integration/targets/user/tasks/test_expires_min_max.yml b/test/integration/targets/user/tasks/test_expires_min_max.yml
index 80e607b6fc..0b8037918d 100644
--- a/test/integration/targets/user/tasks/test_expires_min_max.yml
+++ b/test/integration/targets/user/tasks/test_expires_min_max.yml
@@ -53,3 +53,21 @@
that:
- ansible_facts.getent_shadow['ansibulluser'][2] == '5'
- ansible_facts.getent_shadow['ansibulluser'][3] == '10'
+
+ - name: Set min and max at the same time
+ user:
+ name: ansibulluser
+ # also checks that assigning 0 works
+ password_expire_min: 0
+ password_expire_max: 0
+
+ - name: Get shadow data for ansibulluser
+ getent:
+ database: shadow
+ key: ansibulluser
+
+ - name: Ensure password expiration was set properly
+ assert:
+ that:
+ - ansible_facts.getent_shadow['ansibulluser'][2] == '0'
+ - ansible_facts.getent_shadow['ansibulluser'][3] == '0'