Skip to content

Fix managing users on NAPALM proxy minions (#62170)#69792

Open
ggiesen wants to merge 1 commit into
saltstack:3006.xfrom
ggiesen:fix-62170-netusers
Open

Fix managing users on NAPALM proxy minions (#62170)#69792
ggiesen wants to merge 1 commit into
saltstack:3006.xfrom
ggiesen:fix-62170-netusers

Conversation

@ggiesen

@ggiesen ggiesen commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Restores user management on NAPALM (proxy) minions, which has been broken since
the Sodium release. Two distinct failures are fixed:

1. netusers.managed crash when no defaults are declared.
managed(name, users=None, defaults=None) passes defaults to _expand_users
as common_users, then does copy.deepcopy(common_users).update(...). With no
defaults (the common case, and the case in the issue's SLS), common_users is
None, so the state dies with:

File ".../salt/states/netusers.py", line 72, in _expand_users
    expected_users.update(device_users)
AttributeError: 'NoneType' object has no attribute 'update'

_expand_users now treats a missing defaults as an empty mapping.

Because that crash previously masked a hazard -- netusers.managed is
declarative, so an empty expanded user set means "remove every user" -- managed
now refuses to proceed when neither users nor defaults yields anyone to
manage, instead of silently wiping (and committing) every account on the device.
This closes an admin-lockout foot-gun, e.g. a pillar lookup that renders to {}.

2. users.set_users / users.delete_users -> "Local file source set_users does not exist".
These functions call net.load_template("set_users", ...) with a bare template
name. net.load_template used to route bare names into NAPALM's own renderer,
but that path was removed in Sodium (#57370). The bare name now falls through to
the fileserver, which reports it as a missing local file. Even after working
around bug 1, netusers.managed therefore fails with Cannot configure new users: Local file source set_users does not exist.

Why this is a regression, not a deprecation

The Sodium deprecation warning that preceded the removal said (emphasis added):

Native NAPALM templates support will be removed in the Sodium release. Please
consider using the Salt rendering pipeline instead. If you are using the
'netntp', 'netsnmp', or 'netusers' Salt State modules, you can ignore this
message.

The net<x> state modules and their backing set_*/delete_* functions take
no template argument -- there is nowhere for a user to supply a salt://
template -- so the "use the Salt rendering pipeline" guidance was never aimed at
them, and the warning explicitly told their users to ignore it. But #57370
deleted the entire native-template branch, including the code path those
exempted modules relied on. This PR fixes the netusers half of that overshoot.

Fix

set_users/delete_users now resolve the NAPALM-shipped per-driver template
(set_users.j2 / delete_users.j2, which NAPALM still ships next to each driver
module) to an absolute path via a small _napalm_template_path helper that
mirrors NAPALM's own class-MRO template search, then hand that absolute path to
net.load_template. The template is rendered through the Salt pipeline --
which is what the deprecation nudged toward -- instead of NAPALM's removed native
path. If a driver ships no such template (e.g. ios has no user templates), the
functions now return a clear "template is not available for the ''
driver" message instead of the misleading fileserver error.

Validation on real hardware

Reproduced both failures, then validated the fix end-to-end against a live
Juniper EX3400 (Junos 23.4R2) over NETCONF/SSH, driving a real napalm/junos
proxy minion (onedir 3006.x):

  • netusers.managed with no defaults, test=True: crashes with the
    AttributeError before the fix; after the fix returns a proper diff and
    "Testing mode: configuration was not changed!".
  • users.set_users {'u': {'level': 1}} (class-only) and
    users.set_users {'u': {'level': 5, 'sshkeys': [...]}} (ssh key): before the
    fix, "Local file source ... does not exist"; after the fix the user is
    rendered, committed, and confirmed present in the device config, then removed
    again with users.delete_users.

Note: NAPALM's shipped set_users.j2 emits plain-text-password, which Junos
rejects over NETCONF (it is an interactive-only statement) -- so cleartext
passwords still fail, independent of this change and identically on NAPALM's own
renderer. SSH keys and class-only users work. That template-content limitation
is a NAPALM concern, not Salt's.

Scope

Fixes the netusers/napalm_users path reported in the issue. napalm_ntp and
napalm_snmp (the other two modules the deprecation exempted) carry the
identical bare-name regression and could get the same treatment in a follow-up.

What issues does this PR fix or reference?

Fixes #62170

Merge requirements satisfied?

  • Docs
  • Changelog
  • Tests written/updated

Direct-altitude unit tests: _expand_users(None) no longer raises (and still
merges provided defaults); managed refuses an empty user set instead of
wiping the device; _napalm_template_path resolves a shipped template
(exercising the class-MRO walk and the object -> exception fall-through) and
returns None when absent; set_users/delete_users pass the resolved
absolute path -- and the correct template name -- to net.load_template, and
return a clear error when the driver ships no template. All fail against the
current code, and the routing/MRO tests are mutation-checked (a set/delete name
swap or a collapsed MRO walk makes them fail).

Commits signed with GPG?

No

Two failures made `netusers.managed` / `users.set_users` unusable on NAPALM
(proxy) minions:

- `netusers.managed` passes its optional `defaults` to `_expand_users` as
  `common_users`, then does `copy.deepcopy(common_users).update(...)`. With no
  `defaults` (the common case) that is `copy.deepcopy(None).update(...)`, which
  raises `AttributeError: 'NoneType' object has no attribute 'update'`.

- `users.set_users` / `users.delete_users` call `net.load_template("set_users",
  ...)` with a bare template name. `net.load_template` used to route bare names
  into NAPALM's own renderer, but that path was removed in the Sodium release
  (saltstack#57370) -- whose own deprecation warning explicitly told `netusers`/`netntp`/
  `netsnmp` users they could ignore it. The bare name now falls through to the
  fileserver as `Local file source set_users does not exist`.

`_expand_users` now treats a missing `defaults` as `{}`. `set_users` /
`delete_users` resolve the NAPALM-shipped per-driver template (`set_users.j2` /
`delete_users.j2`, which NAPALM still ships, searched along the driver class
MRO) to an absolute path and render it through the Salt pipeline, and return a
clear message when a driver ships no such template.

Because the crash previously masked it, `managed` now refuses to proceed when
the expanded user set is empty rather than removing (and committing) every
account on the device -- an admin-lockout foot-gun, e.g. an empty pillar lookup.

Validated end-to-end against a live Juniper EX3400 (Junos 23.4R2) over NETCONF:
users are rendered, committed, confirmed on-device, and removed again.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant