Fix create home dir fallback (#49262)
When a user home dir is not created with `useradd`, the home dir will now be created with umask from /etc/login.defs. Also fixed a bug in which after a local user is deleted, and the same user exists in the central user management system, the module would create that user's home.
This commit is contained in:
parent
37960ccc87
commit
eb8294e6d9
3 changed files with 157 additions and 76 deletions
6
changelogs/fragments/49262-user.yml
Normal file
6
changelogs/fragments/49262-user.yml
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
bugfixes:
|
||||||
|
- "user - fixed the fallback mechanism for creating a user home directory when
|
||||||
|
the directory isn't created with `useradd` command. Home directory will now
|
||||||
|
have a correct mode and it won't be created in a rare situation when a local
|
||||||
|
user is being deleted but it exists on a central user system
|
||||||
|
(https://github.com/ansible/ansible/pull/49262)."
|
|
@ -421,6 +421,7 @@ class User(object):
|
||||||
distribution = None
|
distribution = None
|
||||||
SHADOWFILE = '/etc/shadow'
|
SHADOWFILE = '/etc/shadow'
|
||||||
SHADOWFILE_EXPIRE_INDEX = 7
|
SHADOWFILE_EXPIRE_INDEX = 7
|
||||||
|
LOGIN_DEFS = '/etc/login.defs'
|
||||||
DATE_FORMAT = '%Y-%m-%d'
|
DATE_FORMAT = '%Y-%m-%d'
|
||||||
|
|
||||||
def __new__(cls, *args, **kwargs):
|
def __new__(cls, *args, **kwargs):
|
||||||
|
@ -854,7 +855,8 @@ class User(object):
|
||||||
elif self.SHADOWFILE:
|
elif self.SHADOWFILE:
|
||||||
# Read shadow file for user's encrypted password string
|
# Read shadow file for user's encrypted password string
|
||||||
if os.path.exists(self.SHADOWFILE) and os.access(self.SHADOWFILE, os.R_OK):
|
if os.path.exists(self.SHADOWFILE) and os.access(self.SHADOWFILE, os.R_OK):
|
||||||
for line in open(self.SHADOWFILE).readlines():
|
with open(self.SHADOWFILE, 'r') as f:
|
||||||
|
for line in f:
|
||||||
if line.startswith('%s:' % self.name):
|
if line.startswith('%s:' % self.name):
|
||||||
passwd = line.split(':')[1]
|
passwd = line.split(':')[1]
|
||||||
expires = line.split(':')[self.SHADOWFILE_EXPIRE_INDEX] or -1
|
expires = line.split(':')[self.SHADOWFILE_EXPIRE_INDEX] or -1
|
||||||
|
@ -970,9 +972,8 @@ class User(object):
|
||||||
def get_ssh_public_key(self):
|
def get_ssh_public_key(self):
|
||||||
ssh_public_key_file = '%s.pub' % self.get_ssh_key_path()
|
ssh_public_key_file = '%s.pub' % self.get_ssh_key_path()
|
||||||
try:
|
try:
|
||||||
f = open(ssh_public_key_file)
|
with open(ssh_public_key_file, 'r') as f:
|
||||||
ssh_public_key = f.read().strip()
|
ssh_public_key = f.read().strip()
|
||||||
f.close()
|
|
||||||
except IOError:
|
except IOError:
|
||||||
return None
|
return None
|
||||||
return ssh_public_key
|
return ssh_public_key
|
||||||
|
@ -1006,6 +1007,18 @@ class User(object):
|
||||||
os.makedirs(path)
|
os.makedirs(path)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
self.module.exit_json(failed=True, msg="%s" % to_native(e))
|
self.module.exit_json(failed=True, msg="%s" % to_native(e))
|
||||||
|
# get umask from /etc/login.defs and set correct home mode
|
||||||
|
if os.path.exists(self.LOGIN_DEFS):
|
||||||
|
with open(self.LOGIN_DEFS, 'r') as f:
|
||||||
|
for line in f:
|
||||||
|
m = re.match(r'^UMASK\s+(\d+)$', line)
|
||||||
|
if m:
|
||||||
|
umask = int(m.group(1), 8)
|
||||||
|
mode = 0o777 & ~umask
|
||||||
|
try:
|
||||||
|
os.chmod(path, mode)
|
||||||
|
except OSError as e:
|
||||||
|
self.module.exit_json(failed=True, msg="%s" % to_native(e))
|
||||||
|
|
||||||
def chown_homedir(self, uid, gid, path):
|
def chown_homedir(self, uid, gid, path):
|
||||||
try:
|
try:
|
||||||
|
@ -1173,7 +1186,8 @@ class FreeBsdUser(User):
|
||||||
# find current login class
|
# find current login class
|
||||||
user_login_class = None
|
user_login_class = None
|
||||||
if os.path.exists(self.SHADOWFILE) and os.access(self.SHADOWFILE, os.R_OK):
|
if os.path.exists(self.SHADOWFILE) and os.access(self.SHADOWFILE, os.R_OK):
|
||||||
for line in open(self.SHADOWFILE).readlines():
|
with open(self.SHADOWFILE, 'r') as f:
|
||||||
|
for line in f:
|
||||||
if line.startswith('%s:' % self.name):
|
if line.startswith('%s:' % self.name):
|
||||||
user_login_class = line.split(':')[4]
|
user_login_class = line.split(':')[4]
|
||||||
|
|
||||||
|
@ -1632,7 +1646,8 @@ class SunOS(User):
|
||||||
minweeks = ''
|
minweeks = ''
|
||||||
maxweeks = ''
|
maxweeks = ''
|
||||||
warnweeks = ''
|
warnweeks = ''
|
||||||
for line in open("/etc/default/passwd", 'r'):
|
with open("/etc/default/passwd", 'r') as f:
|
||||||
|
for line in f:
|
||||||
line = line.strip()
|
line = line.strip()
|
||||||
if (line.startswith('#') or line == ''):
|
if (line.startswith('#') or line == ''):
|
||||||
continue
|
continue
|
||||||
|
@ -1724,7 +1739,8 @@ class SunOS(User):
|
||||||
minweeks, maxweeks, warnweeks = self.get_password_defaults()
|
minweeks, maxweeks, warnweeks = self.get_password_defaults()
|
||||||
try:
|
try:
|
||||||
lines = []
|
lines = []
|
||||||
for line in open(self.SHADOWFILE, 'rb').readlines():
|
with open(self.SHADOWFILE, 'rb') as f:
|
||||||
|
for line in f:
|
||||||
line = to_native(line, errors='surrogate_or_strict')
|
line = to_native(line, errors='surrogate_or_strict')
|
||||||
fields = line.strip().split(':')
|
fields = line.strip().split(':')
|
||||||
if not fields[0] == self.name:
|
if not fields[0] == self.name:
|
||||||
|
@ -1752,7 +1768,8 @@ class SunOS(User):
|
||||||
pass
|
pass
|
||||||
line = ':'.join(fields)
|
line = ':'.join(fields)
|
||||||
lines.append('%s\n' % line)
|
lines.append('%s\n' % line)
|
||||||
open(self.SHADOWFILE, 'w+').writelines(lines)
|
with open(self.SHADOWFILE, 'w+') as f:
|
||||||
|
f.writelines(lines)
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
self.module.fail_json(msg="failed to update users password: %s" % to_native(err))
|
self.module.fail_json(msg="failed to update users password: %s" % to_native(err))
|
||||||
|
|
||||||
|
@ -1843,7 +1860,8 @@ class SunOS(User):
|
||||||
minweeks, maxweeks, warnweeks = self.get_password_defaults()
|
minweeks, maxweeks, warnweeks = self.get_password_defaults()
|
||||||
try:
|
try:
|
||||||
lines = []
|
lines = []
|
||||||
for line in open(self.SHADOWFILE, 'rb').readlines():
|
with open(self.SHADOWFILE, 'rb') as f:
|
||||||
|
for line in f:
|
||||||
line = to_native(line, errors='surrogate_or_strict')
|
line = to_native(line, errors='surrogate_or_strict')
|
||||||
fields = line.strip().split(':')
|
fields = line.strip().split(':')
|
||||||
if not fields[0] == self.name:
|
if not fields[0] == self.name:
|
||||||
|
@ -1859,7 +1877,8 @@ class SunOS(User):
|
||||||
fields[5] = str(int(warnweeks) * 7)
|
fields[5] = str(int(warnweeks) * 7)
|
||||||
line = ':'.join(fields)
|
line = ':'.join(fields)
|
||||||
lines.append('%s\n' % line)
|
lines.append('%s\n' % line)
|
||||||
open(self.SHADOWFILE, 'w+').writelines(lines)
|
with open(self.SHADOWFILE, 'w+'):
|
||||||
|
f.writelines(lines)
|
||||||
rc = 0
|
rc = 0
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
self.module.fail_json(msg="failed to update users password: %s" % to_native(err))
|
self.module.fail_json(msg="failed to update users password: %s" % to_native(err))
|
||||||
|
@ -2638,7 +2657,7 @@ def main():
|
||||||
if err:
|
if err:
|
||||||
result['stderr'] = err
|
result['stderr'] = err
|
||||||
|
|
||||||
if user.user_exists():
|
if user.user_exists() and user.state == 'present':
|
||||||
info = user.user_info()
|
info = user.user_info()
|
||||||
if info is False:
|
if info is False:
|
||||||
result['msg'] = "failed to look up user name: %s" % user.name
|
result['msg'] = "failed to look up user name: %s" % user.name
|
||||||
|
|
|
@ -229,6 +229,62 @@
|
||||||
- '"ansibulluser" not in user_names2.stdout_lines'
|
- '"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: python
|
||||||
|
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:
|
- block:
|
||||||
- name: create non-system user on macOS to test the shell is set to /bin/bash
|
- name: create non-system user on macOS to test the shell is set to /bin/bash
|
||||||
user:
|
user:
|
||||||
|
|
Loading…
Add table
Reference in a new issue