Skip to content

Run saltutil.runner/saltutil.wheel as the master's configured user#69240

Open
ggiesen wants to merge 1 commit into
saltstack:3006.xfrom
ggiesen:fix-67716-runner-as-master-user
Open

Run saltutil.runner/saltutil.wheel as the master's configured user#69240
ggiesen wants to merge 1 commit into
saltstack:3006.xfrom
ggiesen:fix-67716-runner-as-master-user

Conversation

@ggiesen
Copy link
Copy Markdown
Contributor

@ggiesen ggiesen commented May 27, 2026

What does this PR do?

saltutil.runner and saltutil.wheel execute master-side functions inside the minion's process, which usually runs as root. Since 3006 the packaged Salt master runs as the salt user by default, so these functions touched master-owned resources (the git_pillar/gitfs cache, the pki tree) as root -- leaving root-owned files behind in, and tripping git's safe.directory check on, the salt-owned master cache.

This makes both functions drop to the master's configured user (master_opts["user"]) before executing when invoked from a more-privileged process.

What issues does this PR fix or reference?

Fixes #67716

Previous Behavior

salt <master-minion> saltutil.runner git_pillar.update failed with FileserverConfigError: Failed to load git_pillar /detected dubious ownership in repository at .../git_pillar/<name>/_, because the runner ran as root against the salt-owned git_pillar cache.

New Behavior

The runner/wheel function runs as the master's configured user.

Scope and risk

Changing the user that saltutil.runner/saltutil.wheel execute under is a deliberate behavior change in a core path, so it is intentionally contained: the user is changed only when a master user is configured, it differs from the current user, and the current process is root. For the historical root-master case (current user == master user, or not root) the path
is byte-for-byte unchanged. In other words it is a no-op except in exactly the scenario that is currently broken -- a non-root (3006-default salt) master with a colocated root minion.

How this was tested

Because this is a fairly major architectural change, it was validated from several angles to build confidence that it both fixes the issue and does not regress unaffected calls.

Unit tests (tests/pytests/unit/modules/test_saltutil.py): the user-selection logic (_master_user_runas) across root/non-root and matching/differing users; the dispatch from both runner() and wheel() (drops when needed, runs in-process otherwise); the fork+drop helper returns the result and propagates errors; and an end-to-end test that actually drops to an unprivileged user (runs under root in CI).

Live, on a real 3006.25 install (in a container): master configured user: salt (the packaged default) and running as salt, with a colocated minion running as root. Each function was invoked as root via salt-call --local, against a real git_pillar remote whose cache had been populated by (and is owned by) the salt user:

invoked via saltutil unpatched (3006.25) patched
runner returning getpass.getuser() root salt
runner git_pillar.update detected dubious ownership ... git_pillar/<name>/_ -> False success
wheel returning getpass.getuser() root salt
runner fileserver.envs (regression) works works
wheel key.list_all (regression) works works

So it both fixes the reported failure and leaves unaffected runner/wheel calls behaving identically.

Implementation note

The privilege drop runs in a short-lived fork child viamultiprocessing.get_context("fork"). I'm aware that importing
multiprocessing was historically discouraged in Salt (I seem to recall @thatch45 said that importing multiprocessing was a fireable offence), but it is now used widely in-tree -- salt/scripts.py sets the fork start method, salt/state.py
uses get_context("fork"), and minion/master/gitfs import it.

Merge requirements satisfied?

  • Tests written/updated
  • Changelog

Commits signed with GPG?

No

saltutil.runner and saltutil.wheel execute master-side functions inside the
minion's process, which usually runs as root. Since the 3006 packages the
master runs as the "salt" user by default, so these functions touched
master-owned resources (the git_pillar/gitfs cache, the pki tree) as root --
leaving root-owned files behind and tripping git's safe.directory check.
git_pillar.update invoked via saltutil.runner then failed with
FileserverConfigError (saltstack#67716); the same class of failure affects other
runner/wheel functions.

When invoked from a more-privileged process these functions now drop to the
master's configured user (master_opts["user"]) in a short-lived fork child
before executing, returning the result to the parent. The user is changed only
when one is configured, differs from the current user, and the process is root;
otherwise behavior is unchanged.

Note: the fork child uses multiprocessing.get_context("fork") directly. While
importing multiprocessing was historically discouraged in Salt, it is now used
widely in-tree -- salt/scripts.py sets the fork start method, salt/state.py uses
get_context("fork"), and minion/master/gitfs import it.

Adds unit tests for the user-selection logic, the privilege-dropping dispatch,
and an end-to-end test that drops to an unprivileged user.

Fixes saltstack#67716
@ggiesen ggiesen force-pushed the fix-67716-runner-as-master-user branch from a0834bf to b17e307 Compare May 27, 2026 15:42
@ggiesen ggiesen changed the base branch from master to 3006.x May 27, 2026 15:42
@ggiesen
Copy link
Copy Markdown
Contributor Author

ggiesen commented May 27, 2026

Re-targeted to 3006.x (was master): #67716 is a bug fix, and 3006.x is the oldest supported branch with the affected salt/modules/saltutil.py code.

Explicit behaviour-change note (flagging since this targets a maintenance branch): previously saltutil.runner/saltutil.wheel ran the master-side function as the minion's user (typically root). With this change, when the master is configured to run as a non-root user (the default salt user in 3006+ onedir packages) and the minion runs as root, the function now forks and drops privileges to that user before executing. This fixes master-owned resources (git_pillar/gitfs cache, pki tree, ...) being created/touched as the wrong user, but it means any runner/wheel call that implicitly relied on running as root will now run as the master user. No change when the master already runs as the same user as the minion, or when the minion isn't root.

@ggiesen
Copy link
Copy Markdown
Contributor Author

ggiesen commented May 27, 2026

To be explicit given this is a behaviour change on a maintenance branch: if you'd rather it land on master (the next feature release) instead of 3006.x, I'm happy to rebase it there - just let me know.

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.

[BUG] salt.exceptions.FileserverConfigError: Failed to load git_pillar when git_pillar.update run from saltutil.runner

1 participant