Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
5a77f71
Redact pillar secrets in output
Akm0d Apr 22, 2026
d21f022
Unblock prepare-release man build for 3008.x
dwoz Apr 22, 2026
477e050
Fix tests
Akm0d Apr 23, 2026
d9e4ccc
pass pre-commit
Akm0d Apr 24, 2026
6e7ef30
Expose Secret base for type checking
Akm0d Apr 24, 2026
6911d47
fix tests with secrets
Akm0d Apr 24, 2026
2a724e1
pass pre-commit
Akm0d Apr 24, 2026
5ff4198
pass pre-commit
Akm0d Apr 24, 2026
68a23ec
Test if secret is wrapped properly
Akm0d Apr 27, 2026
16a1201
fix pre-commit
Akm0d Apr 27, 2026
5791ae9
fix gpg test
Akm0d Apr 28, 2026
489f587
Handle secrets better
Akm0d Apr 28, 2026
6f556e3
no log with hide secret
Akm0d Apr 28, 2026
d4a2d68
match setdefault contract
Akm0d Apr 28, 2026
62398b2
expose should return native python types
Akm0d Apr 28, 2026
7403e15
Fix secret rendering
Akm0d Apr 29, 2026
ddc5020
fix most tests
Akm0d Apr 30, 2026
b8bfb6a
Fix issues with minion pillar
Akm0d Apr 30, 2026
b36cd7c
expose pilar everywhere it is used
Akm0d May 1, 2026
503b230
fix incorrect schedule
Akm0d May 3, 2026
ecca1e2
serialize no-log ouptut
Akm0d May 3, 2026
53afd11
Fix pillar.get serial
Akm0d May 3, 2026
ef4ca33
fix some secret pillar bugs
Akm0d May 3, 2026
c6913fd
fix pre-commit
Akm0d May 4, 2026
e0f4b81
Preserve pillar structure and just hide it on get
Akm0d May 4, 2026
b559744
Optimized pillar masking usage
Akm0d May 4, 2026
292e984
fix lint issues
Akm0d May 4, 2026
90211d2
fix pre-commit
Akm0d May 4, 2026
e09d6cf
handle pillar unmasking in specific scenarios
Akm0d May 5, 2026
62058da
pass pre-commit, use copy
Akm0d May 6, 2026
bac81eb
refactor of pillar masking simplifying the whole thing
Akm0d May 7, 2026
f6b0bfd
fix tests
Akm0d May 7, 2026
a2d7137
allow templates to use pillar.get plain
Akm0d May 7, 2026
821765c
remove circular import
Akm0d May 7, 2026
57bf247
remove exra file
Akm0d May 8, 2026
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
1 change: 1 addition & 0 deletions changelog/68907.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Pillar data is now wrapped in SafeDict/SafeList with Pydantic SecretStr/SecretBytes for safer logging and output; optional state `no_log` and automatic redaction of pillar literals in state returns and minion job logs.
16 changes: 9 additions & 7 deletions salt/client/ssh/wrapper/pillar.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import salt.pillar
import salt.utils.data
import salt.utils.dictupdate
import salt.utils.secret
from salt.defaults import DEFAULT_TARGET_DELIM

try:
Expand Down Expand Up @@ -56,13 +57,13 @@ def get(key, default="", merge=False, delimiter=DEFAULT_TARGET_DELIM):
"""
if merge:
ret = salt.utils.data.traverse_dict_and_list(
__pillar__.value(), key, {}, delimiter
salt.utils.secret.expose(__pillar__.value()), key, {}, delimiter
)
if isinstance(ret, Mapping) and isinstance(default, Mapping):
return salt.utils.dictupdate.update(default, ret)

return salt.utils.data.traverse_dict_and_list(
__pillar__.value(), key, default, delimiter
salt.utils.secret.expose(__pillar__.value()), key, default, delimiter
)


Expand All @@ -82,7 +83,7 @@ def item(*args):
ret = {}
for arg in args:
try:
ret[arg] = __pillar__[arg]
ret[arg] = salt.utils.secret.serial(__pillar__[arg])
except KeyError:
pass
return ret
Expand All @@ -109,7 +110,7 @@ def raw(key=None):
else:
ret = __pillar__.value()

return ret
return salt.utils.secret.expose(ret)


def keys(key, delimiter=DEFAULT_TARGET_DELIM):
Expand All @@ -131,7 +132,7 @@ def keys(key, delimiter=DEFAULT_TARGET_DELIM):
salt '*' pillar.keys web:sites
"""
ret = salt.utils.data.traverse_dict_and_list(
__pillar__.value(), key, KeyError, delimiter
salt.utils.secret.expose(__pillar__.value()), key, KeyError, delimiter
)

if ret is KeyError:
Expand Down Expand Up @@ -191,14 +192,15 @@ def filter_by(lookup_dict, pillar, merge=None, default="default", base=None):

salt '*' pillar.filter_by '{web: Serve it up, db: I query, default: x_x}' role
"""
return salt.utils.data.filter_by(
ret = salt.utils.data.filter_by(
lookup_dict=lookup_dict,
lookup=pillar,
traverse=__pillar__.value(),
traverse=salt.utils.secret.expose(__pillar__.value()),
merge=merge,
default=default,
base=base,
)
return ret


# Allow pillar.data to also be used to return pillar data
Expand Down
75 changes: 61 additions & 14 deletions salt/modules/pillar.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import salt.utils.data
import salt.utils.dictupdate
import salt.utils.functools
import salt.utils.secret
import salt.utils.yaml
from salt.defaults import DEFAULT_TARGET_DELIM, NOT_SET
from salt.exceptions import CommandExecutionError
Expand All @@ -30,6 +31,7 @@ def get(
delimiter=DEFAULT_TARGET_DELIM,
pillarenv=None,
saltenv=None,
unmask=None,
):
"""
.. versionadded:: 0.14.0
Expand Down Expand Up @@ -115,6 +117,11 @@ def get(

.. versionadded:: 2017.7.0

unmask
If set to ``True``, the pillar data will be unmasked.

.. versionadded:: 3008.0

CLI Example:

.. code-block:: bash
Expand All @@ -138,16 +145,22 @@ def get(
else items(saltenv=saltenv, pillarenv=pillarenv)
)

if unmask is None:
unmask = not salt.utils.secret.mask_pillar.get()

if merge:
if isinstance(default, dict):
ret = salt.utils.data.traverse_dict_and_list(
pillar_dict, key, {}, delimiter
)
if isinstance(ret, Mapping):
default = copy.deepcopy(default)
return salt.utils.dictupdate.update(
merged = salt.utils.dictupdate.update(
default, ret, merge_lists=opt_merge_lists
)
if unmask:
return salt.utils.secret.expose(merged)
return salt.utils.secret.serial(merged)
else:
log.error(
"pillar.get: Default (%s) is a dict, but the returned "
Expand All @@ -164,7 +177,9 @@ def get(
if isinstance(ret, list):
default = copy.deepcopy(default)
default.extend([x for x in ret if x not in default])
return default
if unmask:
return salt.utils.secret.expose(default)
return salt.utils.secret.serial(default)
else:
log.error(
"pillar.get: Default (%s) is a list, but the returned "
Expand All @@ -186,10 +201,14 @@ def get(
if ret is KeyError:
raise KeyError(f"Pillar key not found: {key}")

return ret
if unmask:
return salt.utils.secret.expose(ret)
return salt.utils.secret.serial(ret)


def items(*args, pillar=None, pillar_enc=None, pillarenv=None, saltenv=None):
def items(
*args, pillar=None, pillar_enc=None, pillarenv=None, saltenv=None, unmask=None
):
"""
Calls the master for a fresh pillar and generates the pillar data on the
fly
Expand Down Expand Up @@ -234,6 +253,11 @@ def items(*args, pillar=None, pillar_enc=None, pillarenv=None, saltenv=None):
Included only for compatibility with
:conf_minion:`pillarenv_from_saltenv`, and is otherwise ignored.

unmask
If set to ``True``, the pillar data will be unmasked.

.. versionadded:: 3008.0

CLI Example:

.. code-block:: bash
Expand Down Expand Up @@ -271,11 +295,19 @@ def items(*args, pillar=None, pillar_enc=None, pillarenv=None, saltenv=None):
pillar_override=pillar_override,
pillarenv=pillarenv,
)
return pillar.compile_pillar()
ret = pillar.compile_pillar()
if unmask is None:
unmask = not salt.utils.secret.mask_pillar.get()
if unmask:
return salt.utils.secret.expose(ret)
else:
return salt.utils.secret.serial(ret)


# Allow pillar.data to also be used to return pillar data
def data(*args, pillar=None, pillar_enc=None, pillarenv=None, saltenv=None):
def data(
*args, pillar=None, pillar_enc=None, pillarenv=None, saltenv=None, unmask=None
):
"""
Calls the master for a fresh pillar, generates the pillar data on the
fly (same as :py:func:`items`)
Expand Down Expand Up @@ -324,6 +356,7 @@ def data(*args, pillar=None, pillar_enc=None, pillarenv=None, saltenv=None):
pillar_enc=pillar_enc,
pillarenv=pillarenv,
saltenv=saltenv,
unmask=unmask,
)


Expand Down Expand Up @@ -439,7 +472,9 @@ def ls(*args, pillar=None, pillar_enc=None, pillarenv=None, saltenv=None):
)


def item(*args, default=None, delimiter=None, pillarenv=None, saltenv=None):
def item(
*args, default=None, delimiter=None, pillarenv=None, saltenv=None, unmask=None
):
"""
.. versionadded:: 0.16.2

Expand Down Expand Up @@ -484,6 +519,11 @@ def item(*args, default=None, delimiter=None, pillarenv=None, saltenv=None):

.. versionadded:: 2017.7.6,2018.3.1

unmask
If set to ``True``, the pillar data will be unmasked.

.. versionadded:: 3008.0

CLI Examples:

.. code-block:: bash
Expand All @@ -506,6 +546,9 @@ def item(*args, default=None, delimiter=None, pillarenv=None, saltenv=None):
else items(saltenv=saltenv, pillarenv=pillarenv)
)

if unmask is None:
unmask = not salt.utils.secret.mask_pillar.get()

try:
for arg in args:
ret[arg] = salt.utils.data.traverse_dict_and_list(
Expand All @@ -514,7 +557,10 @@ def item(*args, default=None, delimiter=None, pillarenv=None, saltenv=None):
except KeyError:
pass

return ret
if unmask:
return salt.utils.secret.expose(ret)
else:
return salt.utils.secret.serial(ret)


def raw(key=None):
Expand All @@ -536,9 +582,9 @@ def raw(key=None):
salt '*' pillar.raw key='roles'
"""
if key:
ret = __pillar__.get(key, {})
ret = salt.utils.secret.expose(__pillar__.get(key, {}))
else:
ret = dict(__pillar__)
ret = dict(salt.utils.secret.expose(__pillar__))

return ret

Expand Down Expand Up @@ -608,7 +654,7 @@ def ext(external, pillar=None):

ret = pillar_obj.compile_pillar()

return ret
return salt.utils.secret.serial(ret)


def keys(key, delimiter=DEFAULT_TARGET_DELIM):
Expand All @@ -634,7 +680,7 @@ def keys(key, delimiter=DEFAULT_TARGET_DELIM):
if ret is KeyError:
raise KeyError(f"Pillar key not found: {key}")

if not isinstance(ret, dict):
if not isinstance(ret, Mapping):
raise ValueError(f"Pillar value in key {key} is not a dict")

return list(ret)
Expand Down Expand Up @@ -741,11 +787,12 @@ def filter_by(lookup_dict, pillar, merge=None, default="default", base=None):

salt '*' pillar.filter_by '{web: Serve it up, db: I query, default: x_x}' role
"""
return salt.utils.data.filter_by(
ret = salt.utils.data.filter_by(
lookup_dict=lookup_dict,
lookup=pillar,
traverse=__pillar__,
traverse=salt.utils.secret.expose(__pillar__),
merge=merge,
default=default,
base=base,
)
return ret
2 changes: 2 additions & 0 deletions salt/output/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import salt.loader
import salt.utils.files
import salt.utils.platform
import salt.utils.secret
import salt.utils.stringutils

# Are you really sure !!!
Expand All @@ -31,6 +32,7 @@ def try_printout(data, out, opts, **kwargs):
Safely get the string to print out, try the configured outputter, then
fall back to nested and then to raw
"""
data = salt.utils.secret.mask_output(data)
try:
printout = get_printout(out, opts)(data, **kwargs)
if printout is not None:
Expand Down
Loading
Loading