Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 50 additions & 46 deletions salt/pillar/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -956,54 +956,58 @@ def render_pillar(self, matches, errors=None):
Extract the sls pillar files from the matches and render them into the
pillar
"""
pillar = copy.copy(self.pillar_override)
if errors is None:
errors = []
for saltenv, pstates in matches.items():
pstatefiles = []
mods = {}
for sls_match in pstates:
matched_pstates = []
try:
matched_pstates = fnmatch.filter(self.avail[saltenv], sls_match)
except KeyError:
errors.extend(
[
"No matching pillar environment for environment "
"'{}' found".format(saltenv)
]
)
if matched_pstates:
pstatefiles.extend(matched_pstates)
else:
pstatefiles.append(sls_match)

for sls in pstatefiles:
pstate, mods, err = self.render_pstate(sls, saltenv, mods)

if err:
errors += err

if pstate is not None:
if not isinstance(pstate, dict):
log.error(
"The rendered pillar sls file, '%s' state did "
"not return the expected data format. This is "
"a sign of a malformed pillar sls file. Returned "
"errors: %s",
sls,
", ".join([f"'{e}'" for e in errors]),
_token = salt.utils.secret.mask_pillar.set(False)
try:
pillar = copy.copy(self.pillar_override)
if errors is None:
errors = []
for saltenv, pstates in matches.items():
pstatefiles = []
mods = {}
for sls_match in pstates:
matched_pstates = []
try:
matched_pstates = fnmatch.filter(self.avail[saltenv], sls_match)
except KeyError:
errors.extend(
[
"No matching pillar environment for environment "
"'{}' found".format(saltenv)
]
)
if matched_pstates:
pstatefiles.extend(matched_pstates)
else:
pstatefiles.append(sls_match)

for sls in pstatefiles:
pstate, mods, err = self.render_pstate(sls, saltenv, mods)

if err:
errors += err

if pstate is not None:
if not isinstance(pstate, dict):
log.error(
"The rendered pillar sls file, '%s' state did "
"not return the expected data format. This is "
"a sign of a malformed pillar sls file. Returned "
"errors: %s",
sls,
", ".join([f"'{e}'" for e in errors]),
)
continue
pillar = salt.utils.dictupdate.merge(
pillar,
pstate,
self.merge_strategy,
self.opts.get("renderer", "yaml"),
self.opts.get("pillar_merge_lists", False),
)
continue
pillar = salt.utils.dictupdate.merge(
pillar,
pstate,
self.merge_strategy,
self.opts.get("renderer", "yaml"),
self.opts.get("pillar_merge_lists", False),
)

return pillar, errors
return pillar, errors
finally:
salt.utils.secret.mask_pillar.reset(_token)

def _external_pillar_data(self, pillar, val, key):
"""
Expand Down
52 changes: 52 additions & 0 deletions tests/pytests/functional/pillar/test_pillar_masking.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
"""
Functional tests for pillar masking behaviour: render_pillar() must set
mask_pillar=False so that pillar.get() calls inside pillar SLS renderers
return plain values instead of **********-redacted ones.
"""

import salt.loader
import salt.pillar
import salt.utils.secret


def test_render_pillar_py_renderer_sees_unmasked_values(
temp_salt_master, temp_salt_minion
):
"""Pillar SLS files using the #!py renderer must receive plain pillar
values from pillar.get(), not **********-redacted ones.

Without the fix, render_pillar() never sets mask_pillar=False. The
Python renderer calls mod.run() directly with no render_tmpl() wrapper,
so mask_pillar stays True and pillar.get() calls serial(), replacing all
string values (even in plain Python lists) with **********.
"""
py_pillar_sls = """\
#!py
def run():
# Without render_pillar() setting mask_pillar=False, pillar.get()
# calls serial() and returns ['**********', ...] for list values.
return {"derived_list": __salt__["pillar.get"]("base_list")}
"""
top_sls = """
base:
'*':
- py_pillar
"""
opts = temp_salt_master.config.copy()
# plain Python list — serial() redacts string elements when mask_pillar=True
# even without any MaskedDict/MaskedList wrapping.
opts["pillar"] = {"base_list": ["a", "b", "c"]}

with temp_salt_master.pillar_tree.base.temp_file(
"top.sls", top_sls
), temp_salt_master.pillar_tree.base.temp_file("py_pillar.sls", py_pillar_sls):
grains = salt.loader.grains(opts)
pillar_obj = salt.pillar.Pillar(opts, grains, temp_salt_minion.id, "base")
result = pillar_obj.compile_pillar()

assert result.get("derived_list") == ["a", "b", "c"], (
f"Expected plain list values but got: {result.get('derived_list')!r}. "
"render_pillar() must set mask_pillar=False so that pillar.get() "
"inside #!py SLS files returns expose()d values instead of "
"serial()-redacted ones."
)
Loading