Skip to content

[3008.x] Add whitelist_state_modules minion option - #70193

Open
dwoz wants to merge 1 commit into
saltstack:3008.xfrom
dwoz:dwoz/feat/whitelist-state-modules
Open

[3008.x] Add whitelist_state_modules minion option#70193
dwoz wants to merge 1 commit into
saltstack:3008.xfrom
dwoz:dwoz/feat/whitelist-state-modules

Conversation

@dwoz

@dwoz dwoz commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Adds a new whitelist_state_modules minion option that restricts which state modules the state loader will load, complementing:

  • whitelist_modules — gates execution modules on the wire
  • renderer_whitelist — gates renderers

Motivation

whitelist_modules prevents wire callers from invoking non-permitted execution modules (salt <tgt> cmd.run 'id' is denied), but an SLS author with write access to the state tree can still write:

run_shell:
  cmd.run:                # <-- STATE module named cmd, function run
    - name: id

…and state.apply executes it unconditionally. For VCF-style hardening the operator needs a symmetric control over which state modules are loadable.

Design (minimal — single feature)

  • New opt whitelist_state_modules: [test, file, pkg, service, ...] in minion config. Case-sensitive module-name (not function-name) allowlist. Unset / empty list = no filtering (backward compat).
  • salt.loader.states now reads the opt when the caller doesn't pass an explicit whitelist= kwarg, mirroring salt.loader.minion_mods's pattern for whitelist_modules.
  • The value flows through to the underlying LazyLoader as whitelist= unchanged; LazyLoader._load already enforces the gate.
  • SLS referencing a non-whitelisted state module fails per-chunk with {"result": False, "comment": "Specified state '...' was not found", "changes": {}} — no silent execution.
  • whitelist_modules and whitelist_state_modules are strictly orthogonal.

Diff scope

File Change
salt/loader/__init__.py states() reads opts["whitelist_state_modules"] when whitelist not passed.
salt/config/__init__.py New whitelist_state_modules: list type entry + [] default in DEFAULT_MINION_OPTS.
conf/minion New commented-out example directly below whitelist_modules.
changelog/vcops-90587-state-whitelist.added.md One-line changelog (rename to <PR#>.added.md after review).

Total: ~30 lines of code + docs, 4 tests.

Test coverage (three tiers, no unit-only)

Tier File Tests
Unit tests/pytests/unit/loader/test_state_whitelist.py 4
Functional tests/pytests/functional/loader/test_state_whitelist.py 8
Integration tests/pytests/integration/states/test_whitelist.py 4

The integration tier boots a real salt-master + salt-minion pair, dispatches state.apply from the wire, and verifies both the allow path and the deny path — including a canary-file check to prove the non-whitelisted cmd.run state does NOT silently execute.

Merge requirements satisfied?

  • Docs — commented example in conf/minion, docstring update on salt.loader.states.
  • Changelog — changelog/vcops-90587-state-whitelist.added.md (rename to <PR#>.added.md after review).
  • Tests written — unit + functional + integration, 16 new.

Commits signed with GPG?

No — DCO sign-off only (Signed-off-by: trailer).

Relation to PR #70192

Independent of PR #70192 (whitelist_modules two-loader completion). This PR is strictly additive; the two overlap on salt.loader.states()'s function signature and can be rebased if #70192 merges first.

@dwoz
dwoz requested a review from a team as a code owner August 30, 2026 22:21
@dwoz
dwoz force-pushed the dwoz/feat/whitelist-state-modules branch from e172fad to 20b4fe7 Compare August 30, 2026 22:21
@dwoz dwoz added the test:full Run the full test suite label Aug 30, 2026
@twangboy twangboy added this to the Argon v3008.3 milestone Aug 31, 2026
twangboy
twangboy previously approved these changes Aug 31, 2026
Add a new ``whitelist_state_modules`` minion option that gates which
state modules the state loader will load, complementing the existing
``whitelist_modules`` (which gates execution modules) and
``renderer_whitelist`` (which gates renderers).

Motivation
----------

``whitelist_modules`` prevents wire callers from invoking non-permitted
execution modules -- ``salt <tgt> cmd.run 'id'`` is denied -- but an
SLS author with write access to the state tree can still write::

    run_shell:
      cmd.run:                # <-- STATE module named cmd, function run
        - name: id

...and ``state.apply`` executes it unconditionally.  For VCF-style
hardening the operator needs a symmetric control over which state
modules are loadable.

Implementation
--------------

* ``salt.loader.states`` now reads ``whitelist_state_modules`` from
  opts when the caller doesn't pass an explicit ``whitelist=``,
  mirroring the pattern that ``salt.loader.minion_mods`` already uses
  for ``whitelist_modules``.  The value flows through to the
  underlying LazyLoader as ``whitelist=`` unchanged; ``LazyLoader._load``
  already enforces the gate.

* ``salt.config``: new ``whitelist_state_modules: list`` entry in the
  minion opts type dict and a default of ``[]`` in
  ``DEFAULT_MINION_OPTS``.  Empty list (the default) is falsy and
  results in no filtering -- backward compatible with pre-fix minions.

* Documentation: ``conf/minion`` gains a commented-out example of the
  new opt directly below the existing ``whitelist_modules`` block.

Behaviour
---------

* SLS referencing a whitelisted state module -> compiles + runs
  normally.
* SLS referencing a non-whitelisted state module -> per-chunk result
  is ``{"result": False, "comment": "Specified state '...' was not
  found", "changes": {}}`` -- no silent execution.
* ``whitelist_modules`` and ``whitelist_state_modules`` are strictly
  orthogonal: setting one does not affect the other's loader.

Coverage
--------

* ``tests/pytests/unit/loader/test_state_whitelist.py`` -- 4 tests
  (opt flow, explicit-kwarg precedence, unset-means-no-filter,
  orthogonality to whitelist_modules).
* ``tests/pytests/functional/loader/test_state_whitelist.py`` -- 8
  tests (real state loader; whitelisted resolves, non-whitelisted
  KeyError; compile + template-string paths for both; backcompat
  when opt unset; cross-contamination check vs whitelist_modules).
* ``tests/pytests/integration/states/test_whitelist.py`` -- 4 tests
  (real master + minion pair; wire ``state.apply`` of whitelisted
  and non-whitelisted; wire dispatch of exec modules unaffected;
  trusted composition still works).

Signed-off-by: Daniel A. Wozniak <daniel.wozniak@broadcom.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

test:full Run the full test suite

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants