From 9ec1b29160a1cd5701b02abcadcbb51ce8c5f002 Mon Sep 17 00:00:00 2001 From: brent s Date: Fri, 6 Dec 2019 21:19:42 -0500 Subject: [PATCH] users is done --- aif.xsd | 2 + aif/system/users.py | 99 ++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 91 insertions(+), 10 deletions(-) diff --git a/aif.xsd b/aif.xsd index fdbff78..b2e8b87 100644 --- a/aif.xsd +++ b/aif.xsd @@ -1012,6 +1012,8 @@ + diff --git a/aif/system/users.py b/aif/system/users.py index b0e1c0d..a0305c5 100644 --- a/aif/system/users.py +++ b/aif/system/users.py @@ -7,6 +7,7 @@ import datetime import os import re +import shutil import warnings ## import passlib.context @@ -21,8 +22,6 @@ _now = datetime.datetime.utcnow() _epoch = datetime.datetime.fromtimestamp(0) _since_epoch = _now - _epoch -# TODO: still need to generate UID/GIDs for new groups and users - class Group(object): def __init__(self, group_xml): @@ -145,6 +144,7 @@ class User(object): self.primary_group = None self.password = None self.sudo = None + self.sudoPassword = True self.comment = None self.shell = None self.minimum_age = None @@ -152,6 +152,7 @@ class User(object): self.warning_period = None self.inactive_period = None self.expire_date = None + self.new = False self.groups = [] self.passwd_entry = [] self.shadow_entry = [] @@ -162,8 +163,11 @@ class User(object): # We manually assign these. return() self.name = self.xml.attrib['name'] + # XML declared users are always new. + self.new = True self.password = Password(self.xml.find('password')) 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.uid = self.xml.attrib.get('uid') if self.uid is not None: @@ -263,6 +267,7 @@ class UserDB(object): def __init__(self, chroot_base, rootpass_xml, users_xml): self.rootpass = Password(rootpass_xml) self.xml = users_xml + self.chroot_base = chroot_base self.sys_users = [] self.sys_groups = [] self.new_users = [] @@ -364,19 +369,30 @@ class UserDB(object): def _parseXML(self): for user_xml in self.xml.findall('user'): 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? + 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: u.uid = self.getAvailUID() if not u.primary_group.gid: - u.primary_group.gid = self.getAvailGID() + 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: + u.primary_group.gid = self.getAvailGID() + self.new_groups.append(u.primary_group) + 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: + g.gid = self.getAvailGID() + self.new_groups.append(g) + else: + if not g.create: + u.groups[idx] = new_group[0] self.new_users.append(u) - self.new_groups.append(u.primary_group) - for g in u.groups: - if not g.gid: - g.gid = self.getAvailGID() - self.new_groups.extend(u.groups) return() def getAvailUID(self, system = False): @@ -412,3 +428,66 @@ class UserDB(object): current_gids = set(i.gid for i in self.new_groups) gid = min(self._valid_gids[k] - current_gids) 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()