users is done
This commit is contained in:
parent
af32ba1eed
commit
9ec1b29160
2
aif.xsd
2
aif.xsd
@ -1012,6 +1012,8 @@
|
|||||||
<xs:attribute name="comment" type="aif:t_nonempty" use="optional"/>
|
<xs:attribute name="comment" type="aif:t_nonempty" use="optional"/>
|
||||||
<xs:attribute name="sudo" type="xs:boolean" use="optional"
|
<xs:attribute name="sudo" type="xs:boolean" use="optional"
|
||||||
default="false"/>
|
default="false"/>
|
||||||
|
<xs:attribute name="sudoPassword" type="xs:boolean" use="optional"
|
||||||
|
default="true"/>
|
||||||
<xs:attribute name="shell" type="aif:t_filepath" use="optional"
|
<xs:attribute name="shell" type="aif:t_filepath" use="optional"
|
||||||
default="/bin/bash"/>
|
default="/bin/bash"/>
|
||||||
<!-- TODO: change the positiveIntegers to xs:duration? or union? -->
|
<!-- TODO: change the positiveIntegers to xs:duration? or union? -->
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
import datetime
|
import datetime
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
import shutil
|
||||||
import warnings
|
import warnings
|
||||||
##
|
##
|
||||||
import passlib.context
|
import passlib.context
|
||||||
@ -21,8 +22,6 @@ _now = datetime.datetime.utcnow()
|
|||||||
_epoch = datetime.datetime.fromtimestamp(0)
|
_epoch = datetime.datetime.fromtimestamp(0)
|
||||||
_since_epoch = _now - _epoch
|
_since_epoch = _now - _epoch
|
||||||
|
|
||||||
# TODO: still need to generate UID/GIDs for new groups and users
|
|
||||||
|
|
||||||
|
|
||||||
class Group(object):
|
class Group(object):
|
||||||
def __init__(self, group_xml):
|
def __init__(self, group_xml):
|
||||||
@ -145,6 +144,7 @@ class User(object):
|
|||||||
self.primary_group = None
|
self.primary_group = None
|
||||||
self.password = None
|
self.password = None
|
||||||
self.sudo = None
|
self.sudo = None
|
||||||
|
self.sudoPassword = True
|
||||||
self.comment = None
|
self.comment = None
|
||||||
self.shell = None
|
self.shell = None
|
||||||
self.minimum_age = None
|
self.minimum_age = None
|
||||||
@ -152,6 +152,7 @@ class User(object):
|
|||||||
self.warning_period = None
|
self.warning_period = None
|
||||||
self.inactive_period = None
|
self.inactive_period = None
|
||||||
self.expire_date = None
|
self.expire_date = None
|
||||||
|
self.new = False
|
||||||
self.groups = []
|
self.groups = []
|
||||||
self.passwd_entry = []
|
self.passwd_entry = []
|
||||||
self.shadow_entry = []
|
self.shadow_entry = []
|
||||||
@ -162,8 +163,11 @@ class User(object):
|
|||||||
# We manually assign these.
|
# We manually assign these.
|
||||||
return()
|
return()
|
||||||
self.name = self.xml.attrib['name']
|
self.name = self.xml.attrib['name']
|
||||||
|
# XML declared users are always new.
|
||||||
|
self.new = True
|
||||||
self.password = Password(self.xml.find('password'))
|
self.password = Password(self.xml.find('password'))
|
||||||
self.sudo = aif.utils.xmlBool(self.xml.attrib.get('sudo', 'false'))
|
self.sudo = aif.utils.xmlBool(self.xml.attrib.get('sudo', 'false'))
|
||||||
|
self.sudoPassword = aif.utils.xmlBool(self.xml.attrib.get('sudoPassword', 'true'))
|
||||||
self.home = self.xml.attrib.get('home', '/home/{0}'.format(self.name))
|
self.home = self.xml.attrib.get('home', '/home/{0}'.format(self.name))
|
||||||
self.uid = self.xml.attrib.get('uid')
|
self.uid = self.xml.attrib.get('uid')
|
||||||
if self.uid is not None:
|
if self.uid is not None:
|
||||||
@ -263,6 +267,7 @@ class UserDB(object):
|
|||||||
def __init__(self, chroot_base, rootpass_xml, users_xml):
|
def __init__(self, chroot_base, rootpass_xml, users_xml):
|
||||||
self.rootpass = Password(rootpass_xml)
|
self.rootpass = Password(rootpass_xml)
|
||||||
self.xml = users_xml
|
self.xml = users_xml
|
||||||
|
self.chroot_base = chroot_base
|
||||||
self.sys_users = []
|
self.sys_users = []
|
||||||
self.sys_groups = []
|
self.sys_groups = []
|
||||||
self.new_users = []
|
self.new_users = []
|
||||||
@ -364,19 +369,30 @@ class UserDB(object):
|
|||||||
def _parseXML(self):
|
def _parseXML(self):
|
||||||
for user_xml in self.xml.findall('user'):
|
for user_xml in self.xml.findall('user'):
|
||||||
u = User(user_xml)
|
u = User(user_xml)
|
||||||
# TODO: need to do unique checks for users and groups (especially for groups if create = True)
|
|
||||||
# TODO: writer? sort by uid/gid? group membership parsing with create = False?
|
|
||||||
# TODO: system accounts?
|
# TODO: system accounts?
|
||||||
|
if u.name in [i.name for i in self.new_users]:
|
||||||
|
warnings.warn(('User {0} already specified; skipping').format(u.name))
|
||||||
|
continue
|
||||||
if not u.uid:
|
if not u.uid:
|
||||||
u.uid = self.getAvailUID()
|
u.uid = self.getAvailUID()
|
||||||
|
if not u.primary_group.gid:
|
||||||
|
new_group = [i.name for i in self.new_groups]
|
||||||
|
if u.primary_group.name not in new_group:
|
||||||
if not u.primary_group.gid:
|
if not u.primary_group.gid:
|
||||||
u.primary_group.gid = self.getAvailGID()
|
u.primary_group.gid = self.getAvailGID()
|
||||||
self.new_users.append(u)
|
|
||||||
self.new_groups.append(u.primary_group)
|
self.new_groups.append(u.primary_group)
|
||||||
for g in u.groups:
|
else:
|
||||||
|
u.primary_group = new_group[0]
|
||||||
|
for idx, g in enumerate(u.groups[:]):
|
||||||
|
new_group = [i.name for i in self.new_groups]
|
||||||
|
if g.name not in new_group:
|
||||||
if not g.gid:
|
if not g.gid:
|
||||||
g.gid = self.getAvailGID()
|
g.gid = self.getAvailGID()
|
||||||
self.new_groups.extend(u.groups)
|
self.new_groups.append(g)
|
||||||
|
else:
|
||||||
|
if not g.create:
|
||||||
|
u.groups[idx] = new_group[0]
|
||||||
|
self.new_users.append(u)
|
||||||
return()
|
return()
|
||||||
|
|
||||||
def getAvailUID(self, system = False):
|
def getAvailUID(self, system = False):
|
||||||
@ -412,3 +428,66 @@ class UserDB(object):
|
|||||||
current_gids = set(i.gid for i in self.new_groups)
|
current_gids = set(i.gid for i in self.new_groups)
|
||||||
gid = min(self._valid_gids[k] - current_gids)
|
gid = min(self._valid_gids[k] - current_gids)
|
||||||
return(gid)
|
return(gid)
|
||||||
|
|
||||||
|
def writeConf(self):
|
||||||
|
# We shouldn't really use this, because root should be at the beginning.
|
||||||
|
users_by_name = sorted(self.new_users, key = lambda x: x.name)
|
||||||
|
# This automatically puts root first (uid = 0)
|
||||||
|
users_by_uid = sorted(self.new_users, key = lambda x: x.uid)
|
||||||
|
# Ditto.
|
||||||
|
groups_by_name = sorted(self.new_groups, key = lambda x: x.name)
|
||||||
|
groups_by_gid = sorted(self.new_groups, key = lambda x: x.gid)
|
||||||
|
for x in (self.new_users, self.new_groups):
|
||||||
|
for i in x:
|
||||||
|
i.genFileLine()
|
||||||
|
for f in (self.passwd_file, self.shadow_file, self.group_file, self.gshadow_file):
|
||||||
|
backup = '{0}-'.format(f)
|
||||||
|
shutil.copy2(f, backup)
|
||||||
|
with open(self.passwd_file, 'w') as fh:
|
||||||
|
for u in users_by_uid:
|
||||||
|
fh.write(':'.join(u.passwd_entry))
|
||||||
|
fh.write('\n')
|
||||||
|
with open(self.shadow_file, 'w') as fh:
|
||||||
|
for u in self.new_users:
|
||||||
|
fh.write(':'.join(u.shadow_entry))
|
||||||
|
fh.write('\n')
|
||||||
|
with open(self.group_file, 'w') as fh:
|
||||||
|
for g in groups_by_gid:
|
||||||
|
fh.write(':'.join(g.group_entry))
|
||||||
|
fh.write('\n')
|
||||||
|
with open(self.gshadow_file, 'w') as fh:
|
||||||
|
for g in self.new_users:
|
||||||
|
fh.write(':'.join(g.gshadow_entry))
|
||||||
|
fh.write('\n')
|
||||||
|
for u in self.new_users:
|
||||||
|
if u.new:
|
||||||
|
homedir = os.path.join(self.chroot_base, u.home)
|
||||||
|
# We only set perms for the homedir itself. It's up to the user to specify in a post script if this
|
||||||
|
# needs to be different.
|
||||||
|
if os.path.isdir(homedir):
|
||||||
|
os.stat(homedir)
|
||||||
|
# TODO: log original owner, permissions, etc.
|
||||||
|
os.makedirs(homedir, exist_ok = True)
|
||||||
|
shutil.copytree(os.path.join(self.chroot_base, 'etc', 'skel'), homedir)
|
||||||
|
os.chown(homedir, u.uid, u.primary_group.gid)
|
||||||
|
os.chmod(homedir, 0o0750)
|
||||||
|
for root, dirs, files in os.walk(homedir):
|
||||||
|
for d in dirs:
|
||||||
|
dpath = os.path.join(root, d)
|
||||||
|
os.chown(dpath, u.uid, u.primary_group.gid)
|
||||||
|
os.chmod(dpath, 0o0700)
|
||||||
|
for f in files:
|
||||||
|
fpath = os.path.join(root, f)
|
||||||
|
os.chown(fpath, u.uid, u.primary_group.gid)
|
||||||
|
os.chmod(fpath, 0o0600)
|
||||||
|
if not u.sudo:
|
||||||
|
continue
|
||||||
|
sudo_file = os.path.join(self.chroot_base, 'etc', 'sudoers.d', u.name)
|
||||||
|
with open(sudo_file, 'w') as fh:
|
||||||
|
fh.write(('# Generated by AIF-NG.\n'
|
||||||
|
'Defaults:{0} !lecture\n'
|
||||||
|
'{0} ALL=(ALL) {1}ALL\n').format(u.name,
|
||||||
|
('NOPASSWD: ' if not u.sudoPassword else '')))
|
||||||
|
os.chown(sudo_file, 0, 0)
|
||||||
|
os.chmod(sudo_file, 0o0440)
|
||||||
|
return()
|
||||||
|
Loading…
Reference in New Issue
Block a user