make password locking in user module idempotent (#43671)

* Simplify logic and add FreeBSD & NetBSD

* Remove incorrect flag for lock and unlock on FreeBSD

* Add tests and changelog


Co-authored-by: Chris Gadd <gaddman@email.com>
This commit is contained in:
Christopher Gadd 2018-11-09 15:29:38 +13:00 committed by Sam Doran
parent d7975462da
commit f75a84e382
4 changed files with 128 additions and 9 deletions

View file

@ -0,0 +1,2 @@
bugfixes:
- user - do not report changes every time when setting password_lock (https://github.com/ansible/ansible/issues/43670)

View file

@ -194,7 +194,7 @@ options:
- Lock the password (usermod -L, pw lock, usermod -C). - Lock the password (usermod -L, pw lock, usermod -C).
BUT implementation differs on different platforms, this option does not always mean the user cannot login via other methods. BUT implementation differs on different platforms, this option does not always mean the user cannot login via other methods.
This option does not disable the user, only lock the password. Do not change the password in the same task. This option does not disable the user, only lock the password. Do not change the password in the same task.
Currently supported on Linux, FreeBSD, DragonFlyBSD, NetBSD. Currently supported on Linux, FreeBSD, DragonFlyBSD, NetBSD, OpenBSD.
type: bool type: bool
version_added: "2.6" version_added: "2.6"
local: local:
@ -718,9 +718,11 @@ class User(object):
cmd.append('-e') cmd.append('-e')
cmd.append(time.strftime(self.DATE_FORMAT, self.expires)) cmd.append(time.strftime(self.DATE_FORMAT, self.expires))
if self.password_lock: # Lock if no password or unlocked, unlock only if locked
if self.password_lock and not info[1].startswith('!'):
cmd.append('-L') cmd.append('-L')
elif self.password_lock is not None: elif self.password_lock is False and info[1].startswith('!'):
# usermod will refuse to unlock a user with no password, module shows 'changed' regardless
cmd.append('-U') cmd.append('-U')
if self.update_password == 'always' and self.password is not None and info[1] != self.password: if self.update_password == 'always' and self.password is not None and info[1] != self.password:
@ -1214,22 +1216,20 @@ class FreeBsdUser(User):
return self.execute_command(cmd) return self.execute_command(cmd)
# we have to lock/unlock the password in a distinct command # we have to lock/unlock the password in a distinct command
if self.password_lock: if self.password_lock and not info[1].startswith('*LOCKED*'):
cmd = [ cmd = [
self.module.get_bin_path('pw', True), self.module.get_bin_path('pw', True),
'lock', 'lock',
'-n',
self.name self.name
] ]
if self.uid is not None and info[2] != int(self.uid): if self.uid is not None and info[2] != int(self.uid):
cmd.append('-u') cmd.append('-u')
cmd.append(self.uid) cmd.append(self.uid)
return self.execute_command(cmd) return self.execute_command(cmd)
elif self.password_lock is not None: elif self.password_lock is False and info[1].startswith('*LOCKED*'):
cmd = [ cmd = [
self.module.get_bin_path('pw', True), self.module.get_bin_path('pw', True),
'unlock', 'unlock',
'-n',
self.name self.name
] ]
if self.uid is not None and info[2] != int(self.uid): if self.uid is not None and info[2] != int(self.uid):
@ -1402,6 +1402,11 @@ class OpenBSDUser(User):
cmd.append('-L') cmd.append('-L')
cmd.append(self.login_class) cmd.append(self.login_class)
if self.password_lock and not info[1].startswith('*'):
cmd.append('-Z')
elif self.password_lock is False and info[1].startswith('*'):
cmd.append('-U')
if self.update_password == 'always' and self.password is not None \ if self.update_password == 'always' and self.password is not None \
and self.password != '*' and info[1] != self.password: and self.password != '*' and info[1] != self.password:
cmd.append('-p') cmd.append('-p')
@ -1562,9 +1567,9 @@ class NetBSDUser(User):
cmd.append('-p') cmd.append('-p')
cmd.append(self.password) cmd.append(self.password)
if self.password_lock: if self.password_lock and not info[1].startswith('*LOCKED*'):
cmd.append('-C yes') cmd.append('-C yes')
elif self.password_lock is not None: elif self.password_lock is False and info[1].startswith('*LOCKED*'):
cmd.append('-C no') cmd.append('-C no')
# skip if no changes to be made # skip if no changes to be made

View file

@ -628,3 +628,111 @@
file: file:
path: "{{ output_dir }}/test_id_rsa" path: "{{ output_dir }}/test_id_rsa"
state: absent state: absent
when: ansible_os_family == 'FreeBSD'
## password lock
- block:
- name: Set password for ansibulluser
user:
name: ansibulluser
password: "$6$rounds=656000$TT4O7jz2M57npccl$33LF6FcUMSW11qrESXL1HX0BS.bsiT6aenFLLiVpsQh6hDtI9pJh5iY7x8J7ePkN4fP8hmElidHXaeD51pbGS."
- name: Lock account
user:
name: ansibulluser
password_lock: yes
register: password_lock_1
- name: Lock account again
user:
name: ansibulluser
password_lock: yes
register: password_lock_2
- name: Unlock account
user:
name: ansibulluser
password_lock: no
register: password_lock_3
- name: Unlock account again
user:
name: ansibulluser
password_lock: no
register: password_lock_4
- name: Ensure task reported changes appropriately
assert:
msg: The password_lock tasks did not make changes appropriately
that:
- password_lock_1 is changed
- password_lock_2 is not changed
- password_lock_3 is changed
- password_lock_4 is not changed
- name: Lock account
user:
name: ansibulluser
password_lock: yes
- name: Verify account lock for BSD
block:
- name: BSD | Get account status
shell: "{{ status_command[ansible_facts['system']] }}"
register: account_status_locked
- name: Unlock account
user:
name: ansibulluser
password_lock: no
- name: BSD | Get account status
shell: "{{ status_command[ansible_facts['system']] }}"
register: account_status_unlocked
- name: FreeBSD | Ensure account is locked
assert:
that:
- "'LOCKED' in account_status_locked.stdout"
- "'LOCKED' not in account_status_unlocked.stdout"
when: ansible_facts['system'] == 'FreeBSD'
when: ansible_facts['system'] in ['FreeBSD', 'OpenBSD']
- name: Verify account lock for Linux
block:
- name: LINUX | Get account status
getent:
database: shadow
key: ansibulluser
- name: LINUX | Ensure account is locked
assert:
that:
- getent_shadow['ansibulluser'][0].startswith('!')
- name: Unlock account
user:
name: ansibulluser
password_lock: no
- name: LINUX | Get account status
getent:
database: shadow
key: ansibulluser
- name: LINUX | Ensure account is unlocked
assert:
that:
- not getent_shadow['ansibulluser'][0].startswith('!')
when: ansible_facts['system'] == 'Linux'
always:
- name: Unlock account
user:
name: ansibulluser
password_lock: no
when: ansible_facts['system'] in ['FreeBSD', 'OpenBSD', 'Linux']

View file

@ -3,3 +3,7 @@ user_home_prefix:
FreeBSD: '/home' FreeBSD: '/home'
SunOS: '/home' SunOS: '/home'
Darwin: '/Users' Darwin: '/Users'
status_command:
OpenBSD: "grep ansibulluser /etc/master.passwd | cut -d ':' -f 2"
FreeBSD: 'pw user show ansibulluser'