def test_process(): prior = [ struct_passwd(('A', 'x', 10000, 10000, '', '/home/A', '/bin/bash')), struct_passwd(('B', 'x', 10001, 10001, '', '/home/B', '/bin/bash')), struct_passwd(('C', 'x', 10099, 10099, '', '/home/C', '/bin/bash')), ] sprior = [ struct_spwd(('A', '*', 16593, 0, 99999, 7, -1, -1, -1)), struct_spwd(('B', '*', 16503, 0, 99999, 7, -1, -1, -1)), struct_spwd(('C', '*', 16513, 0, 99999, 7, -1, -1, -1)), ] user_pks = { 'john': [ 'ssh-rsa AAAAC...aliuh7', 'ssh-rsa AAAAC...qo874y', ], 'C': [ 'ssh-rsa AAAAC...7a6cs1', ] } passwd, shadow, sudo = iam_acctmgr.process(user_pks, prior, sprior) days_since_epoch = str( (datetime.utcnow() - iam_acctmgr.EPOCH).days ).encode('utf-8') assert passwd == [ b'A:x:10000:10000::/home/A:/bin/bash', b'B:x:10001:10001::/home/B:/bin/bash', b'C:x:10099:10099::/home/C:/bin/bash', b'john:x:10002:10002:IAM-USER:/home/john:/bin/bash', ] assert shadow == [ b'A:*:16593:0:99999:7:::', b'B:*:16503:0:99999:7:::', b'C:*:16513:0:99999:7:::', b'john:*:' + days_since_epoch + b':0:99999:7:::' ] assert sudo[0].decode('utf-8').startswith('# ') assert sudo[1:] == [ b'C ALL=(ALL) NOPASSWD:ALL', b'john ALL=(ALL) NOPASSWD:ALL', ]
def UserAdd_Shadow(User, Passwodr='*', ExpireDays=-1, ShadowFile='/etc/shadow'): # 1. temporary shadow file fd, TempShadowFile = mkstemp(prefix='shadow', dir='/tmp') # 2. get users passwd entries pwall = pwd.getpwall() pwall.sort(lambda a, b: cmp(a.pw_uid, b.pw_uid)) # 3. generate shadow entries CreatedDays = int(time() / 86400) if ExpireDays != -1: ExpireDays = CreatedDays + ExpireDays spall = [] for pw in pwall: try: sp = spwd.getspnam(pw.pw_name) except KeyError, e: sp = spwd.struct_spwd( sequence = ( User, '*', CreatedDays, 0, 99999, 7, -1, ExpireDays, -1)) spall.append(sp)
def test_process(): prior = [ struct_passwd(('A', 'x', 10000, 10000, '', '/home/A', '/bin/bash')), struct_passwd(('B', 'x', 10001, 10001, '', '/home/B', '/bin/bash')), struct_passwd(('C', 'x', 10099, 10099, '', '/home/C', '/bin/bash')), ] sprior = [ struct_spwd(('A', '*', 16593, 0, 99999, 7, -1, -1, -1)), struct_spwd(('B', '*', 16503, 0, 99999, 7, -1, -1, -1)), struct_spwd(('C', '*', 16513, 0, 99999, 7, -1, -1, -1)), ] user_pks = { 'john': [ 'ssh-rsa AAAAC...aliuh7', 'ssh-rsa AAAAC...qo874y', ], 'C': [ 'ssh-rsa AAAAC...7a6cs1', ] } passwd, shadow, sudo = iam_acctmgr.process(user_pks, prior, sprior) days_since_epoch = str( (datetime.utcnow() - iam_acctmgr.EPOCH).days).encode('utf-8') assert passwd == [ b'A:x:10000:10000::/home/A:/bin/bash', b'B:x:10001:10001::/home/B:/bin/bash', b'C:x:10099:10099::/home/C:/bin/bash', b'john:x:10002:10002:IAM-USER:/home/john:/bin/bash', ] assert shadow == [ b'A:*:16593:0:99999:7:::', b'B:*:16503:0:99999:7:::', b'C:*:16513:0:99999:7:::', b'john:*:' + days_since_epoch + b':0:99999:7:::' ] assert sudo[0].decode('utf-8').startswith('# ') assert sudo[1:] == [ b'C ALL=(ALL) NOPASSWD:ALL', b'john ALL=(ALL) NOPASSWD:ALL', ]
def test_info(self): """ Test if info shows the correct user information """ # First test is with a succesful call expected_result = [ ("expire", -1), ("inact", -1), ("lstchg", 31337), ("max", 99999), ("min", 0), ("name", "foo"), ("passwd", _HASHES["sha512"]["pw_hash"]), ("warn", 7), ] getspnam_return = spwd.struct_spwd( ["foo", _HASHES["sha512"]["pw_hash"], 31337, 0, 99999, 7, -1, -1, -1] ) with patch("spwd.getspnam", return_value=getspnam_return): result = shadow.info("foo") self.assertEqual( expected_result, sorted(result.items(), key=lambda x: x[0]) ) # The next two is for a non-existent user expected_result = [ ("expire", ""), ("inact", ""), ("lstchg", ""), ("max", ""), ("min", ""), ("name", ""), ("passwd", ""), ("warn", ""), ] # We get KeyError exception for non-existent users in glibc based systems getspnam_return = KeyError with patch("spwd.getspnam", side_effect=getspnam_return): result = shadow.info("foo") self.assertEqual( expected_result, sorted(result.items(), key=lambda x: x[0]) ) # And FileNotFoundError in musl based systems getspnam_return = FileNotFoundError with patch("spwd.getspnam", side_effect=getspnam_return): result = shadow.info("foo") self.assertEqual( expected_result, sorted(result.items(), key=lambda x: x[0]) )
def _getspall(root=None): ''' Alternative implementation for getspnam, that use only /etc/shadow ''' root = '/' if not root else root passwd = os.path.join(root, 'etc/shadow') with salt.utils.files.fopen(passwd) as fp_: for line in fp_: line = salt.utils.stringutils.to_unicode(line) comps = line.strip().split(':') # Generate a getspall compatible output for i in range(2, 9): comps[i] = int(comps[i]) if comps[i] else -1 yield spwd.struct_spwd(comps)
def _getspnam(name, root=None): """ Alternative implementation for getspnam, that use only /etc/shadow """ root = "/" if not root else root passwd = os.path.join(root, "etc/shadow") with salt.utils.files.fopen(passwd) as fp_: for line in fp_: line = salt.utils.stringutils.to_unicode(line) comps = line.strip().split(":") if comps[0] == name: # Generate a getspnam compatible output for i in range(2, 9): comps[i] = int(comps[i]) if comps[i] else -1 return spwd.struct_spwd(comps) raise KeyError
def getspall(cls): spwds = [] for line in open('/etc/shadow'): line = line.strip() data = line.split(':') if line and len(data) == 9: sp = spwd.struct_spwd() sp.sp_nam = data[0] sp.sp_pwd = data[1] sp.sp_lstchg = data[2] sp.sp_min = data[3] sp.sp_max = data[4] sp.sp_warn = data[5] sp.sp_inact = data[6] if data[7]: sp.sp_expire = data[7] else: sp.sp_expire = -1 sp.sp_flag = data[8] spwds.append(sp) return spwds
def load(self): pwds = pwd.getpwall() spwds = spwd.getspall() sn = {} for s in spwds: sn[s.sp_nam] = s for p in pwds: if p.pw_name in sn: s = sn[p.pw_name] else: s = spwd.struct_spwd([None] * 9) gecos = p.pw_gecos.split(',', 4) gecos += [''] * ( 5 - len(gecos) ) # Pad with empty strings so we have exactly 5 items rname, office, wphone, hphone, other = gecos u = User(p.pw_name, p.pw_uid, p.pw_gid, rname, office, wphone, hphone, other, p.pw_dir, p.pw_shell, [grp.getgrgid(p.pw_gid).gr_name], s.sp_lstchg, s.sp_min, s.sp_max, s.sp_warn, s.sp_inact, s.sp_expire, s.sp_pwd) self.users[u.name] = u groups = grp.getgrall() for group in groups: g = Group(group.gr_name, group.gr_gid) for member in group.gr_mem: if member in self.users: ugroups = self.users[member].groups if group.gr_name not in ugroups: self.users[member].groups.append(group.gr_name) g.members[member] = self.users[member] self.groups[group.gr_name] = g for user in self.users.values(): primary_group = grp.getgrgid(user.gid).gr_name if primary_group in self.groups: self.groups[primary_group].members[user.name] = user
def read_users_from_passwd(dirname="/etc"): """ Reads users from /etc/passwd, /etc/shadow (if it has access) and /etc/group """ pwds = pwd.getpwall() spwds = spwd.getspall() sn = {} for s in spwds: sn[s.sp_nam] = s users = {} for p in pwds: if p.pw_uid >= UID_MIN and p.pw_uid <= UID_MAX: if p.pw_name in sn: s = sn[p.pw_name] else: #print " * I couldn't find user %s in shadow file. Are you \ #root?" % p.pw_name s = spwd.struct_spwd(["", "x", "", "", "", "", "", "", ""]) rname, office, wphone, hphone = (p.pw_gecos + ",,,").split(",")[:4] u = User(p.pw_name, p.pw_uid, rname, office, wphone, hphone, p.pw_dir, p.pw_shell, [], s.sp_min, s.sp_max, s.sp_warn, s.sp_inact, s.sp_expire, s.sp_pwd, "") if u.inact == -1: u.inact = '' if u.expire == -1: u.expire = '' users[u.name] = u grps = grp.getgrall() for g in grps: for gu in g.gr_mem: if gu in users: users[gu].groups.append(g.gr_name) return sorted_users(users)
def _spwd_getspnam(self, username): return spwd.struct_spwd( (username, crypt.crypt(self.users[username], 'F/'), 0, 0, 99999, 7, -1, -1, -1))
def process(user_pks, pwall, spwall): '''Generate the passwd, shadow, and sudo fragments for IAM users. :param user_pks: Mapping of username (``str``) to public keys (``list`` of ``str``) retrieved from IAM. :type user_pks: dict :param pwall: A list of ``pwd.struct_passwd`` including all password entries found by NSS. Should include those users identified by libnss-extrausers. :type pwall: list ''' username_index = dict( (user.pw_name, user) for user in pwall if is_iam_user(user)) susername_index = dict( (user[0], user) for user in spwall if user[0] in username_index) uid_index = dict((int(user.pw_uid), user) for user in pwall) next_uid = MIN_USER_UID passwd, shadow, sudo = [], [], [] # Users that have been removed from IAM will keep their UIDs around in the # event that user IDs have. In practice, I don't anticipate this behavior # to be problematic since there is an abundance of UIDs available in the # default configuration UID range for all but the largest admin user pools. for old_username in set(username_index.keys()) - set(user_pks.keys()): passwd.append(username_index[old_username]) shadow.append(susername_index[old_username]) for username in user_pks.keys(): # Find the next gap in user IDs while next_uid in uid_index: next_uid += 1 if next_uid > MAX_USER_UID: LOG.error("User limit reached! Skipping user %s", username) break sudo.append('{} ALL=(ALL) NOPASSWD:ALL'.format(username)) if username in username_index: passwd.append(username_index[username]) shadow.append(susername_index[username]) else: passwd.append(pwd.struct_passwd(( username, 'x', next_uid, next_uid, 'IAM-USER', '/home/{}'.format(username), '/bin/bash', ))) shadow.append(spwd.struct_spwd(( username, '*', (datetime.datetime.utcnow() - EPOCH).days, 0, 99999, 7, -1, -1, -1, ))) next_uid += 1 sudo.sort() sudo.insert(0, '# Created by {} on {}'.format( __file__, datetime.datetime.utcnow().ctime())) return ( sorted(passwd_to_line(x) for x in passwd), sorted(shadow_to_line(x) for x in shadow), [x.encode('utf-8') for x in sudo] )
def process(user_pks, pwall, spwall, grpall, set_sudo, user_ids): '''Generate the passwd, shadow, and sudo fragments for IAM users. :param user_pks: Mapping of username (``str``) to public keys (``list`` of ``str``) retrieved from IAM. :type user_pks: dict :param pwall: A list of ``pwd.struct_passwd`` including all password entries found by NSS. Should include those users identified by libnss-extrausers. :type pwall: list ''' username_index = dict( (user.pw_name, user) for user in pwall if is_iam_user(user)) susername_index = dict( (user[0], user) for user in spwall if user[0] in username_index) group_index = dict( (group[0], group) for group in grpall if is_iam_group(group)) next_uid = MIN_USER_UID passwd, shadow, sudo, group = [], [], [], [] # Users that have been removed from IAM will keep their UIDs around in the # event that user IDs have. In practice, I don't anticipate this behavior # to be problematic since there is an abundance of UIDs available in the # default configuration UID range for all but the largest admin user pools. #for old_username in set(username_index.keys()) - set(user_pks.keys()): # passwd.append(username_index[old_username]) # shadow.append(susername_index[old_username]) LOG.info("Not appending old users") for username in user_pks.keys(): next_uid = aws_to_unix_id(user_ids[username]) LOG.info("Calculated UID for %s is %s", next_uid, username) if set_sudo is True: sudo.append('{} ALL=(ALL) NOPASSWD:ALL'.format(username)) if username in username_index: passwd.append(username_index[username]) shadow.append(susername_index[username]) group.append(group_index[username]) else: passwd.append( pwd.struct_passwd(( username, 'x', next_uid, next_uid, 'IAM-USER', '/home/{}'.format(username), '/bin/bash', ))) shadow.append( spwd.struct_spwd(( username, '*', (datetime.datetime.utcnow() - EPOCH).days, 0, 99999, 7, -1, -1, -1, ))) group.append(grp.struct_group((username, 'x', next_uid, ''))) if set_sudo is True: sudo.sort() sudo.insert( 0, '# Created by {} on {}'.format(__file__, datetime.datetime.utcnow().ctime())) return (sorted(passwd_to_line(x) for x in passwd), sorted(shadow_to_line(x) for x in shadow), [x.encode('utf-8') for x in sudo], sorted(":".join( '' if isinstance(y, list) and len(y) == 0 else str(y) for y in x).encode('utf-8') for x in group))
def process(user_pks, pwall, spwall): '''Generate the passwd, shadow, and sudo fragments for IAM users. :param user_pks: Mapping of username (``str``) to public keys (``list`` of ``str``) retrieved from IAM. :type user_pks: dict :param pwall: A list of ``pwd.struct_passwd`` including all password entries found by NSS. Should include those users identified by libnss-extrausers. :type pwall: list ''' username_index = dict( (user.pw_name, user) for user in pwall if is_iam_user(user)) susername_index = dict( (user[0], user) for user in spwall if user[0] in username_index) uid_index = dict((int(user.pw_uid), user) for user in pwall) next_uid = MIN_USER_UID passwd, shadow, sudo = [], [], [] # Users that have been removed from IAM will keep their UIDs around in the # event that user IDs have. In practice, I don't anticipate this behavior # to be problematic since there is an abundance of UIDs available in the # default configuration UID range for all but the largest admin user pools. for old_username in set(username_index.keys()) - set(user_pks.keys()): passwd.append(username_index[old_username]) shadow.append(susername_index[old_username]) for username in user_pks.keys(): # Find the next gap in user IDs while next_uid in uid_index: next_uid += 1 if next_uid > MAX_USER_UID: LOG.error("User limit reached! Skipping user %s", username) break sudo.append('{} ALL=(ALL) NOPASSWD:ALL'.format(username)) if username in username_index: passwd.append(username_index[username]) shadow.append(susername_index[username]) else: passwd.append( pwd.struct_passwd(( username, 'x', next_uid, next_uid, 'IAM-USER', '/home/{}'.format(username), '/bin/bash', ))) shadow.append( spwd.struct_spwd(( username, '*', (datetime.datetime.utcnow() - EPOCH).days, 0, 99999, 7, -1, -1, -1, ))) next_uid += 1 sudo.sort() sudo.insert( 0, '# Created by {} on {}'.format(__file__, datetime.datetime.utcnow().ctime())) return (sorted(passwd_to_line(x) for x in passwd), sorted(shadow_to_line(x) for x in shadow), [x.encode('utf-8') for x in sudo])