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.