# Test code for the user module. # (c) 2017, James Tanner # This file is part of Ansible # # Ansible is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # Ansible is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Ansible. If not, see . # ## user add - name: remove the test user user: name: ansibulluser state: absent - name: try to create a user user: name: ansibulluser state: present register: user_test0_0 - name: create the user again user: name: ansibulluser state: present register: user_test0_1 - debug: var: user_test0 verbosity: 2 - name: make a list of users script: userlist.sh {{ ansible_facts.distribution }} register: user_names - debug: var: user_names verbosity: 2 - name: validate results for testcase 0 assert: that: - user_test0_0 is changed - user_test0_1 is not changed - '"ansibulluser" in user_names.stdout_lines' # test adding user with uid # https://github.com/ansible/ansible/issues/62969 - name: remove the test user user: name: ansibulluser state: absent - name: try to create a user with uid user: name: ansibulluser state: present uid: 572 register: user_test01_0 - name: create the user again user: name: ansibulluser state: present uid: 572 register: user_test01_1 - name: validate results for testcase 0 assert: that: - user_test01_0 is changed - user_test01_1 is not changed # test user add with password - name: add an encrypted password for user user: name: ansibulluser password: "$6$rounds=656000$TT4O7jz2M57npccl$33LF6FcUMSW11qrESXL1HX0BS.bsiT6aenFLLiVpsQh6hDtI9pJh5iY7x8J7ePkN4fP8hmElidHXaeD51pbGS." state: present update_password: always register: test_user_encrypt0 - name: there should not be warnings assert: that: "'warnings' not in test_user_encrypt0" # https://github.com/ansible/ansible/issues/65711 - name: Test updating password only on creation user: name: ansibulluser password: '*' update_password: on_create register: test_user_update_password - name: Ensure password was not changed assert: that: - test_user_update_password is not changed - name: Verify password hash for Linux when: ansible_facts.os_family in ['RedHat', 'Debian', 'Suse'] block: - name: LINUX | Get shadow entry for ansibulluser getent: database: shadow key: ansibulluser - name: LINUX | Ensure password hash was not removed assert: that: - getent_shadow['ansibulluser'][1] != '*' - block: - name: add an plaintext password for user user: name: ansibulluser password: "plaintextpassword" state: present update_password: always register: test_user_encrypt1 - name: there should be a warning complains that the password is plaintext assert: that: "'warnings' in test_user_encrypt1" - name: add an invalid hashed password user: name: ansibulluser password: "$6$rounds=656000$tgK3gYTyRLUmhyv2$lAFrYUQwn7E6VsjPOwQwoSx30lmpiU9r/E0Al7tzKrR9mkodcMEZGe9OXD0H/clOn6qdsUnaL4zefy5fG+++++" state: present update_password: always register: test_user_encrypt2 - name: there should be a warning complains about the character set of password assert: that: "'warnings' in test_user_encrypt2" - name: change password to '!' user: name: ansibulluser password: '!' register: test_user_encrypt3 - name: change password to '*' user: name: ansibulluser password: '*' register: test_user_encrypt4 - name: change password to '*************' user: name: ansibulluser password: '*************' register: test_user_encrypt5 - name: there should be no warnings when setting the password to '!', '*' or '*************' assert: that: - "'warnings' not in test_user_encrypt3" - "'warnings' not in test_user_encrypt4" - "'warnings' not in test_user_encrypt5" when: ansible_facts.system != 'Darwin' # https://github.com/ansible/ansible/issues/42484 # Skipping macOS for now since there is a bug when changing home directory - block: - name: create user specifying home user: name: ansibulluser state: present home: "{{ user_home_prefix[ansible_facts.system] }}/ansibulluser" register: user_test3_0 - name: create user again specifying home user: name: ansibulluser state: present home: "{{ user_home_prefix[ansible_facts.system] }}/ansibulluser" register: user_test3_1 - name: change user home user: name: ansibulluser state: present home: "{{ user_home_prefix[ansible_facts.system] }}/ansibulluser-mod" register: user_test3_2 - name: change user home back user: name: ansibulluser state: present home: "{{ user_home_prefix[ansible_facts.system] }}/ansibulluser" register: user_test3_3 - name: validate results for testcase 3 assert: that: - user_test3_0 is not changed - user_test3_1 is not changed - user_test3_2 is changed - user_test3_3 is changed when: ansible_facts.system != 'Darwin' # https://github.com/ansible/ansible/issues/41393 # Create a new user account with a path that has parent directories that do not exist - name: Create user with home path that has parents that do not exist user: name: ansibulluser2 state: present home: "{{ user_home_prefix[ansible_facts.system] }}/in2deep/ansibulluser2" register: create_home_with_no_parent_1 - name: Create user with home path that has parents that do not exist again user: name: ansibulluser2 state: present home: "{{ user_home_prefix[ansible_facts.system] }}/in2deep/ansibulluser2" register: create_home_with_no_parent_2 - name: Check the created home directory stat: path: "{{ user_home_prefix[ansible_facts.system] }}/in2deep/ansibulluser2" register: home_with_no_parent_3 - name: Ensure user with non-existing parent paths was created successfully assert: that: - create_home_with_no_parent_1 is changed - create_home_with_no_parent_1.home == user_home_prefix[ansible_facts.system] ~ '/in2deep/ansibulluser2' - create_home_with_no_parent_2 is not changed - home_with_no_parent_3.stat.uid == create_home_with_no_parent_1.uid - home_with_no_parent_3.stat.gr_name == default_user_group[ansible_facts.distribution] | default('ansibulluser2') - name: Cleanup test account user: name: ansibulluser2 home: "{{ user_home_prefix[ansible_facts.system] }}/in2deep/ansibulluser2" state: absent remove: yes - name: Remove testing dir file: path: "{{ user_home_prefix[ansible_facts.system] }}/in2deep/" state: absent # https://github.com/ansible/ansible/issues/60307 # Make sure we can create a user when the home directory is missing - name: Create user with home path that does not exist user: name: ansibulluser3 state: present home: "{{ user_home_prefix[ansible_facts.system] }}/nosuchdir" createhome: no - name: Cleanup test account user: name: ansibulluser3 state: absent remove: yes ## user check - name: run existing user check tests user: name: "{{ user_names.stdout_lines | random }}" state: present create_home: no loop: "{{ range(1, 5+1) | list }}" register: user_test1 - debug: var: user_test1 verbosity: 2 - name: validate results for testcase 1 assert: that: - user_test1.results is defined - user_test1.results | length == 5 - name: validate changed results for testcase 1 assert: that: - "user_test1.results[0] is not changed" - "user_test1.results[1] is not changed" - "user_test1.results[2] is not changed" - "user_test1.results[3] is not changed" - "user_test1.results[4] is not changed" - "user_test1.results[0]['state'] == 'present'" - "user_test1.results[1]['state'] == 'present'" - "user_test1.results[2]['state'] == 'present'" - "user_test1.results[3]['state'] == 'present'" - "user_test1.results[4]['state'] == 'present'" ## user remove - name: try to delete the user user: name: ansibulluser state: absent force: true register: user_test2 - name: make a new list of users script: userlist.sh {{ ansible_facts.distribution }} register: user_names2 - debug: var: user_names2 verbosity: 2 - name: validate results for testcase 2 assert: that: - '"ansibulluser" not in user_names2.stdout_lines' ## create user without home and test fallback home dir create - block: - name: create the user user: name: ansibulluser - name: delete the user and home dir user: name: ansibulluser state: absent force: true remove: true - name: create the user without home user: name: ansibulluser create_home: no - name: create the user home dir user: name: ansibulluser register: user_create_home_fallback - name: stat home dir stat: path: '{{ user_create_home_fallback.home }}' register: user_create_home_fallback_dir - name: read UMASK from /etc/login.defs and return mode shell: | import re import os try: for line in open('/etc/login.defs').readlines(): m = re.match(r'^UMASK\s+(\d+)$', line) if m: umask = int(m.group(1), 8) except: umask = os.umask(0) mode = oct(0o777 & ~umask) print(str(mode).replace('o', '')) args: executable: "{{ ansible_python_interpreter }}" register: user_login_defs_umask - name: validate that user home dir is created assert: that: - user_create_home_fallback is changed - user_create_home_fallback_dir.stat.exists - user_create_home_fallback_dir.stat.isdir - user_create_home_fallback_dir.stat.pw_name == 'ansibulluser' - user_create_home_fallback_dir.stat.mode == user_login_defs_umask.stdout when: ansible_facts.system != 'Darwin' - block: - name: create non-system user on macOS to test the shell is set to /bin/bash user: name: macosuser register: macosuser_output - name: validate the shell is set to /bin/bash assert: that: - 'macosuser_output.shell == "/bin/bash"' - name: cleanup user: name: macosuser state: absent - name: create system user on macos to test the shell is set to /usr/bin/false user: name: macosuser system: yes register: macosuser_output - name: validate the shell is set to /usr/bin/false assert: that: - 'macosuser_output.shell == "/usr/bin/false"' - name: cleanup user: name: macosuser state: absent - name: create non-system user on macos and set the shell to /bin/sh user: name: macosuser shell: /bin/sh register: macosuser_output - name: validate the shell is set to /bin/sh assert: that: - 'macosuser_output.shell == "/bin/sh"' - name: cleanup user: name: macosuser state: absent when: ansible_facts.distribution == "MacOSX" ## user expires # Date is March 3, 2050 - name: Set user expiration user: name: ansibulluser state: present expires: 2529881062 register: user_test_expires1 tags: - timezone - name: Set user expiration again to ensure no change is made user: name: ansibulluser state: present expires: 2529881062 register: user_test_expires2 tags: - timezone - name: Ensure that account with expiration was created and did not change on subsequent run assert: that: - user_test_expires1 is changed - user_test_expires2 is not changed - name: Verify expiration date for Linux block: - name: LINUX | Get expiration date for ansibulluser getent: database: shadow key: ansibulluser - name: LINUX | Ensure proper expiration date was set assert: that: - getent_shadow['ansibulluser'][6] == '29281' when: ansible_facts.os_family in ['RedHat', 'Debian', 'Suse'] - name: Verify expiration date for BSD block: - name: BSD | Get expiration date for ansibulluser shell: 'grep ansibulluser /etc/master.passwd | cut -d: -f 7' changed_when: no register: bsd_account_expiration - name: BSD | Ensure proper expiration date was set assert: that: - bsd_account_expiration.stdout == '2529881062' when: ansible_facts.os_family == 'FreeBSD' - name: Change timezone timezone: name: America/Denver register: original_timezone tags: - timezone - name: Change system timezone to make sure expiration comparison works properly block: - name: Create user with expiration again to ensure no change is made in a new timezone user: name: ansibulluser state: present expires: 2529881062 register: user_test_different_tz tags: - timezone - name: Ensure that no change was reported assert: that: - user_test_different_tz is not changed tags: - timezone always: - name: Restore original timezone - {{ original_timezone.diff.before.name }} timezone: name: "{{ original_timezone.diff.before.name }}" when: original_timezone.diff.before.name != "n/a" tags: - timezone - name: Restore original timezone when n/a file: path: /etc/sysconfig/clock state: absent when: - original_timezone.diff.before.name == "n/a" - "'/etc/sysconfig/clock' in original_timezone.msg" tags: - timezone - name: Unexpire user user: name: ansibulluser state: present expires: -1 register: user_test_expires3 - name: Verify un expiration date for Linux block: - name: LINUX | Get expiration date for ansibulluser getent: database: shadow key: ansibulluser - name: LINUX | Ensure proper expiration date was set assert: msg: "expiry is supposed to be empty or -1, not {{ getent_shadow['ansibulluser'][6] }}" that: - not getent_shadow['ansibulluser'][6] or getent_shadow['ansibulluser'][6] | int < 0 when: ansible_facts.os_family in ['RedHat', 'Debian', 'Suse'] - name: Verify un expiration date for Linux/BSD block: - name: Unexpire user again to check for change user: name: ansibulluser state: present expires: -1 register: user_test_expires4 - name: Ensure first expiration reported a change and second did not assert: msg: The second run of the expiration removal task reported a change when it should not that: - user_test_expires3 is changed - user_test_expires4 is not changed when: ansible_facts.os_family in ['RedHat', 'Debian', 'Suse', 'FreeBSD'] - name: Verify un expiration date for BSD block: - name: BSD | Get expiration date for ansibulluser shell: 'grep ansibulluser /etc/master.passwd | cut -d: -f 7' changed_when: no register: bsd_account_expiration - name: BSD | Ensure proper expiration date was set assert: msg: "expiry is supposed to be '0', not {{ bsd_account_expiration.stdout }}" that: - bsd_account_expiration.stdout == '0' when: ansible_facts.os_family == 'FreeBSD' # Test setting no expiration when creating a new account # https://github.com/ansible/ansible/issues/44155 - name: Remove ansibulluser user: name: ansibulluser state: absent - name: Create user account without expiration user: name: ansibulluser state: present expires: -1 register: user_test_create_no_expires_1 - name: Create user account without expiration again user: name: ansibulluser state: present expires: -1 register: user_test_create_no_expires_2 - name: Ensure changes were made appropriately assert: msg: Setting 'expires='-1 resulted in incorrect changes that: - user_test_create_no_expires_1 is changed - user_test_create_no_expires_2 is not changed - name: Verify un expiration date for Linux block: - name: LINUX | Get expiration date for ansibulluser getent: database: shadow key: ansibulluser - name: LINUX | Ensure proper expiration date was set assert: msg: "expiry is supposed to be empty or -1, not {{ getent_shadow['ansibulluser'][6] }}" that: - not getent_shadow['ansibulluser'][6] or getent_shadow['ansibulluser'][6] | int < 0 when: ansible_facts.os_family in ['RedHat', 'Debian', 'Suse'] - name: Verify un expiration date for BSD block: - name: BSD | Get expiration date for ansibulluser shell: 'grep ansibulluser /etc/master.passwd | cut -d: -f 7' changed_when: no register: bsd_account_expiration - name: BSD | Ensure proper expiration date was set assert: msg: "expiry is supposed to be '0', not {{ bsd_account_expiration.stdout }}" that: - bsd_account_expiration.stdout == '0' when: ansible_facts.os_family == 'FreeBSD' # Test setting epoch 0 expiration when creating a new account, then removing the expiry # https://github.com/ansible/ansible/issues/47114 - name: Remove ansibulluser user: name: ansibulluser state: absent - name: Create user account with epoch 0 expiration user: name: ansibulluser state: present expires: 0 register: user_test_expires_create0_1 - name: Create user account with epoch 0 expiration again user: name: ansibulluser state: present expires: 0 register: user_test_expires_create0_2 - name: Change the user account to remove the expiry time user: name: ansibulluser expires: -1 register: user_test_remove_expires_1 - name: Change the user account to remove the expiry time again user: name: ansibulluser expires: -1 register: user_test_remove_expires_2 - name: Verify un expiration date for Linux block: - name: LINUX | Ensure changes were made appropriately assert: msg: Creating an account with 'expries=0' then removing that expriation with 'expires=-1' resulted in incorrect changes that: - user_test_expires_create0_1 is changed - user_test_expires_create0_2 is not changed - user_test_remove_expires_1 is changed - user_test_remove_expires_2 is not changed - name: LINUX | Get expiration date for ansibulluser getent: database: shadow key: ansibulluser - name: LINUX | Ensure proper expiration date was set assert: msg: "expiry is supposed to be empty or -1, not {{ getent_shadow['ansibulluser'][6] }}" that: - not getent_shadow['ansibulluser'][6] or getent_shadow['ansibulluser'][6] | int < 0 when: ansible_facts.os_family in ['RedHat', 'Debian', 'Suse'] - name: Verify proper expiration behavior for BSD block: - name: BSD | Ensure changes were made appropriately assert: msg: Creating an account with 'expries=0' then removing that expriation with 'expires=-1' resulted in incorrect changes that: - user_test_expires_create0_1 is changed - user_test_expires_create0_2 is not changed - user_test_remove_expires_1 is not changed - user_test_remove_expires_2 is not changed when: ansible_facts.os_family == 'FreeBSD' # Test expiration with a very large negative number. This should have the same # result as setting -1. - name: Set expiration date using very long negative number user: name: ansibulluser state: present expires: -2529881062 register: user_test_expires5 - name: Ensure no change was made assert: that: - user_test_expires5 is not changed - name: Verify un expiration date for Linux block: - name: LINUX | Get expiration date for ansibulluser getent: database: shadow key: ansibulluser - name: LINUX | Ensure proper expiration date was set assert: msg: "expiry is supposed to be empty or -1, not {{ getent_shadow['ansibulluser'][6] }}" that: - not getent_shadow['ansibulluser'][6] or getent_shadow['ansibulluser'][6] | int < 0 when: ansible_facts.os_family in ['RedHat', 'Debian', 'Suse'] - name: Verify un expiration date for BSD block: - name: BSD | Get expiration date for ansibulluser shell: 'grep ansibulluser /etc/master.passwd | cut -d: -f 7' changed_when: no register: bsd_account_expiration - name: BSD | Ensure proper expiration date was set assert: msg: "expiry is supposed to be '0', not {{ bsd_account_expiration.stdout }}" that: - bsd_account_expiration.stdout == '0' when: ansible_facts.os_family == 'FreeBSD' ## shadow backup - block: - name: Create a user to test shadow file backup user: name: ansibulluser state: present register: result - name: Find shadow backup files find: path: /etc patterns: 'shadow\..*~$' use_regex: yes register: shadow_backups - name: Assert that a backup file was created assert: that: - result.bakup - shadow_backups.files | map(attribute='path') | list | length > 0 when: ansible_facts.os_family == 'Solaris' # Test creating ssh key with passphrase - name: Remove ansibulluser user: name: ansibulluser state: absent - name: Create user with ssh key user: name: ansibulluser state: present generate_ssh_key: yes ssh_key_file: "{{ output_dir }}/test_id_rsa" ssh_key_passphrase: secret_passphrase - name: Unlock ssh key command: "ssh-keygen -y -f {{ output_dir }}/test_id_rsa -P secret_passphrase" register: result - name: Check that ssh key was unlocked successfully assert: that: - result.rc == 0 - name: Clean ssh key file: path: "{{ output_dir }}/test_id_rsa" 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'] ## Check local mode # Even if we don't have a system that is bound to a directory, it's useful # to run with local: true to exercise the code path that reads through the local # user database file. # https://github.com/ansible/ansible/issues/50947 - name: Create /etc/gshadow file: path: /etc/gshadow state: touch when: ansible_facts.os_family == 'Suse' tags: - user_test_local_mode - name: Create /etc/libuser.conf file: path: /etc/libuser.conf state: touch when: - ansible_facts.distribution == 'Ubuntu' - ansible_facts.distribution_major_version is version_compare('16', '==') tags: - user_test_local_mode - name: Ensure luseradd is present action: "{{ ansible_facts.pkg_mgr }}" args: name: libuser state: present when: ansible_facts.system in ['Linux'] tags: - user_test_local_mode - name: Create local account that already exists to check for warning user: name: root local: yes register: local_existing tags: - user_test_local_mode - name: Create local_ansibulluser user: name: local_ansibulluser state: present local: yes register: local_user_test_1 tags: - user_test_local_mode - name: Create local_ansibulluser again user: name: local_ansibulluser state: present local: yes register: local_user_test_2 tags: - user_test_local_mode - name: Remove local_ansibulluser user: name: local_ansibulluser state: absent remove: yes local: yes register: local_user_test_remove_1 tags: - user_test_local_mode - name: Remove local_ansibulluser again user: name: local_ansibulluser state: absent remove: yes local: yes register: local_user_test_remove_2 tags: - user_test_local_mode - name: Create test group group: name: testgroup tags: - user_test_local_mode - name: Create local_ansibulluser with groups user: name: local_ansibulluser state: present local: yes groups: testgroup register: local_user_test_3 ignore_errors: yes tags: - user_test_local_mode - name: Append groups for local_ansibulluser user: name: local_ansibulluser state: present local: yes append: yes register: local_user_test_4 ignore_errors: yes tags: - user_test_local_mode - name: Ensure local user accounts were created and removed properly assert: that: - local_user_test_1 is changed - local_user_test_2 is not changed - local_user_test_3 is failed - "local_user_test_3['msg'] is search('parameters are mutually exclusive: groups|local')" - local_user_test_4 is failed - "local_user_test_4['msg'] is search('parameters are mutually exclusive: groups|append')" - local_user_test_remove_1 is changed - local_user_test_remove_2 is not changed tags: - user_test_local_mode - name: Ensure warnings were displayed properly assert: that: - local_user_test_1['warnings'] | length > 0 - local_user_test_1['warnings'] | first is search('The local user account may already exist') - local_existing['warnings'] is not defined when: ansible_facts.system in ['Linux'] tags: - user_test_local_mode