In VyOS we use a Python function to list all user accounts on the device (pwd.getpwall()). However, this function can become very slow if external authentication services like RADIUS or TACACS are not responding, because the system waits for them to time out. Since we really only need information about the local user accounts, I’m considering skipping that slow function entirely and instead reading the local user list directly from the /etc/passwd file.
Would this be a better and faster approach? This is what this POC task is about.
POC snipped which needs to be integrated:
#!/usr/bin/env python3 from dataclasses import dataclass from typing import List @dataclass class PasswdEntry: pw_name: str pw_passwd: str pw_uid: int pw_gid: int pw_gecos: str pw_dir: str pw_shell: str def get_local_passwd_entries(path: str = "/etc/passwd") -> List[PasswdEntry]: entries = [] with open(path, "r") as f: for line in f: line = line.strip() if not line or line.startswith("#"): continue parts = line.split(":") if len(parts) != 7: # malformed line (rare but possible) continue entry = PasswdEntry( pw_name=parts[0], pw_passwd=parts[1], pw_uid=int(parts[2]), pw_gid=int(parts[3]), pw_gecos=parts[4], pw_dir=parts[5], pw_shell=parts[6], ) entries.append(entry) return entries if __name__ == "__main__": for e in get_local_passwd_entries(): print(e)
Will result in
lnx01:~ # /tmp/poc.py PasswdEntry(pw_name='root', pw_passwd='x', pw_uid=0, pw_gid=0, pw_gecos='root', pw_dir='/root', pw_shell='/bin/bash') PasswdEntry(pw_name='daemon', pw_passwd='x', pw_uid=1, pw_gid=1, pw_gecos='daemon', pw_dir='/usr/sbin', pw_shell='/usr/sbin/nologin') PasswdEntry(pw_name='bin', pw_passwd='x', pw_uid=2, pw_gid=2, pw_gecos='bin', pw_dir='/bin', pw_shell='/usr/sbin/nologin') PasswdEntry(pw_name='sys', pw_passwd='x', pw_uid=3, pw_gid=3, pw_gecos='sys', pw_dir='/dev', pw_shell='/usr/sbin/nologin') PasswdEntry(pw_name='sync', pw_passwd='x', pw_uid=4, pw_gid=65534, pw_gecos='sync', pw_dir='/bin', pw_shell='/bin/sync') PasswdEntry(pw_name='games', pw_passwd='x', pw_uid=5, pw_gid=60, pw_gecos='games', pw_dir='/usr/games', pw_shell='/usr/sbin/nologin') PasswdEntry(pw_name='man', pw_passwd='x', pw_uid=6, pw_gid=12, pw_gecos='man', pw_dir='/var/cache/man', pw_shell='/usr/sbin/nologin') PasswdEntry(pw_name='lp', pw_passwd='x', pw_uid=7, pw_gid=7, pw_gecos='lp', pw_dir='/var/spool/lpd', pw_shell='/usr/sbin/nologin') PasswdEntry(pw_name='mail', pw_passwd='x', pw_uid=8, pw_gid=8, pw_gecos='mail', pw_dir='/var/mail', pw_shell='/usr/sbin/nologin') PasswdEntry(pw_name='news', pw_passwd='x', pw_uid=9, pw_gid=9, pw_gecos='news', pw_dir='/var/spool/news', pw_shell='/usr/sbin/nologin') PasswdEntry(pw_name='uucp', pw_passwd='x', pw_uid=10, pw_gid=10, pw_gecos='uucp', pw_dir='/var/spool/uucp', pw_shell='/usr/sbin/nologin') PasswdEntry(pw_name='proxy', pw_passwd='x', pw_uid=13, pw_gid=13, pw_gecos='proxy', pw_dir='/bin', pw_shell='/usr/sbin/nologin') PasswdEntry(pw_name='www-data', pw_passwd='x', pw_uid=33, pw_gid=33, pw_gecos='www-data', pw_dir='/var/www', pw_shell='/usr/sbin/nologin') PasswdEntry(pw_name='backup', pw_passwd='x', pw_uid=34, pw_gid=34, pw_gecos='backup', pw_dir='/var/backups', pw_shell='/usr/sbin/nologin') PasswdEntry(pw_name='list', pw_passwd='x', pw_uid=38, pw_gid=38, pw_gecos='Mailing List Manager', pw_dir='/var/list', pw_shell='/usr/sbin/nologin') PasswdEntry(pw_name='irc', pw_passwd='x', pw_uid=39, pw_gid=39, pw_gecos='ircd', pw_dir='/run/ircd', pw_shell='/usr/sbin/nologin') PasswdEntry(pw_name='gnats', pw_passwd='x', pw_uid=41, pw_gid=41, pw_gecos='Gnats Bug-Reporting System (admin)', pw_dir='/var/lib/gnats', pw_shell='/usr/sbin/nologin') PasswdEntry(pw_name='nobody', pw_passwd='x', pw_uid=65534, pw_gid=65534, pw_gecos='nobody', pw_dir='/nonexistent', pw_shell='/usr/sbin/nologin') PasswdEntry(pw_name='_apt', pw_passwd='x', pw_uid=100, pw_gid=65534, pw_gecos='', pw_dir='/nonexistent', pw_shell='/usr/sbin/nologin') PasswdEntry(pw_name='systemd-timesync', pw_passwd='x', pw_uid=101, pw_gid=102, pw_gecos='systemd Time Synchronization,,,', pw_dir='/run/systemd', pw_shell='/usr/sbin/nologin') PasswdEntry(pw_name='systemd-network', pw_passwd='x', pw_uid=102, pw_gid=103, pw_gecos='systemd Network Management,,,', pw_dir='/run/systemd', pw_shell='/usr/sbin/nologin') PasswdEntry(pw_name='systemd-resolve', pw_passwd='x', pw_uid=103, pw_gid=104, pw_gecos='systemd Resolver,,,', pw_dir='/run/systemd', pw_shell='/usr/sbin/nologin') PasswdEntry(pw_name='messagebus', pw_passwd='x', pw_uid=104, pw_gid=110, pw_gecos='', pw_dir='/nonexistent', pw_shell='/usr/sbin/nologin') PasswdEntry(pw_name='sshd', pw_passwd='x', pw_uid=105, pw_gid=65534, pw_gecos='', pw_dir='/run/sshd', pw_shell='/usr/sbin/nologin') PasswdEntry(pw_name='systemd-coredump', pw_passwd='x', pw_uid=999, pw_gid=999, pw_gecos='systemd Core Dumper', pw_dir='/', pw_shell='/usr/sbin/nologin') PasswdEntry(pw_name='ntp', pw_passwd='x', pw_uid=106, pw_gid=112, pw_gecos='', pw_dir='/nonexistent', pw_shell='/usr/sbin/nologin') PasswdEntry(pw_name='sssd', pw_passwd='x', pw_uid=107, pw_gid=115, pw_gecos='SSSD system user,,,', pw_dir='/var/lib/sss', pw_shell='/usr/sbin/nologin') PasswdEntry(pw_name='Debian-exim', pw_passwd='x', pw_uid=108, pw_gid=116, pw_gecos='', pw_dir='/var/spool/exim4', pw_shell='/usr/sbin/nologin') PasswdEntry(pw_name='geoclue', pw_passwd='x', pw_uid=109, pw_gid=117, pw_gecos='', pw_dir='/var/lib/geoclue', pw_shell='/usr/sbin/nologin') PasswdEntry(pw_name='avahi', pw_passwd='x', pw_uid=110, pw_gid=118, pw_gecos='Avahi mDNS daemon,,,', pw_dir='/var/run/avahi-daemon', pw_shell='/usr/sbin/nologin') PasswdEntry(pw_name='Debian-ow', pw_passwd='x', pw_uid=111, pw_gid=119, pw_gecos='Debian OWFS system account,,,', pw_dir='/var/lib/owfs', pw_shell='/usr/sbin/nologin') PasswdEntry(pw_name='freerad', pw_passwd='x', pw_uid=112, pw_gid=121, pw_gecos='', pw_dir='/etc/freeradius', pw_shell='/usr/sbin/nologin') PasswdEntry(pw_name='uuidd', pw_passwd='x', pw_uid=113, pw_gid=122, pw_gecos='', pw_dir='/run/uuidd', pw_shell='/usr/sbin/nologin') PasswdEntry(pw_name='swift', pw_passwd='x', pw_uid=114, pw_gid=123, pw_gecos='', pw_dir='/var/lib/swift', pw_shell='/bin/false') PasswdEntry(pw_name='tcpdump', pw_passwd='x', pw_uid=115, pw_gid=124, pw_gecos='', pw_dir='/nonexistent', pw_shell='/usr/sbin/nologin') PasswdEntry(pw_name='arpwatch', pw_passwd='x', pw_uid=116, pw_gid=126, pw_gecos='ARP Watcher,,,', pw_dir='/var/lib/arpwatch', pw_shell='/bin/sh') PasswdEntry(pw_name='iperf3', pw_passwd='x', pw_uid=117, pw_gid=128, pw_gecos='', pw_dir='/nonexistent', pw_shell='/usr/sbin/nologin') PasswdEntry(pw_name='ntpsec', pw_passwd='x', pw_uid=118, pw_gid=129, pw_gecos='', pw_dir='/nonexistent', pw_shell='/usr/sbin/nologin') PasswdEntry(pw_name='polkitd', pw_passwd='x', pw_uid=996, pw_gid=996, pw_gecos='polkit', pw_dir='/nonexistent', pw_shell='/usr/sbin/nologin')
My local user-account cpo does not appear as it comes from sssd - so local only name resolution works
cpo lnx01:~ # id uid=1776001104(cpo) gid=1776001674(gr_linux) groups=1776001674(gr_linux),997(clab_admins),998(docker),1776000513(domain users),1776001110(a_rdp),1776001111(a_vpn)
Implementation
The previous implementation of "system login" relied on Python's pwd.getpwall() to enumerate user accounts. This forces a full walk through the NSS stack, which is acceptable in general but problematic for our use-case. VyOS only needs information about locally created accounts and not remote accounts
provided via AAA backends such as TACACS or RADIUS.
When TACACS servers are unreachable, NSS lookups become extremely slow due to repeated timeouts. As a result, any operation triggering pwd.getpwall() (including configuration commits) can stall for several minutes.
This change introduces a dedicated helper, get_local_passwd_entries(), which reads /etc/passwd directly and avoids NSS entirely. Since only local UIDs are relevant, this provides all required data with no external dependencies.
Performance improvement on VyOS 1.4.3 with two unreachable TACACS servers:
set system login tacacs server 192.168.1.50 key test123 set system login tacacs server 192.168.1.51 key test123 time commit
Before:
real 3m29.825s user 0m0.329s sys 0m0.246s
After:
real 0m1.464s user 0m0.337s sys 0m0.195s
This significantly improves commit performance and removes sensitivity to AAA server outages.