Make the pass renderer more configurable

1. Allow us to make the pass renderer fail during pillar rendering
   when a secret corresponding with a pass path cannot be fetched.
   For this we add a master config variable pass_strict_fetch.

2. Allow to have prefix for variables that should be processed
   with the pass renderer.
   For this we add a master config variable pass_variable_prefix.

3. Allow us to configure pass' GNUPGHOME and PASSWORD_STORE_DIR
   environmental variables.
   For this we add master config variables pass_gnupghome and pass_dir.
This commit is contained in:
Marcus Rückert 2022-05-25 15:13:12 +02:00 committed by Megan Wilhite
parent b327996cf5
commit bcf5dd82ed
3 changed files with 82 additions and 3 deletions

4
changelog/62120.added Normal file
View file

@ -0,0 +1,4 @@
Config option pass_variable_prefix allows to distinguish variables that contain paths to pass secrets.
Config option pass_strict_fetch allows to error out when a secret cannot be fetched from pass.
Config option pass_dir allows setting the PASSWORD_STORE_DIR env for pass.
Config option pass_gnupghome allows setting the $GNUPGHOME env for pass.

View file

@ -964,6 +964,14 @@ VALID_OPTS = immutabletypes.freeze(
# The port to be used when checking if a master is connected to a
# minion
"remote_minions_port": int,
# pass renderer: Fetch secrets only for the template variables matching the prefix
"pass_variable_prefix": str,
# pass renderer: Whether to error out when unable to fetch a secret
"pass_strict_fetch": bool,
# pass renderer: Set GNUPGHOME env for Pass
"pass_gnupghome": str,
# pass renderer: Set PASSWORD_STORE_DIR env for Pass
"pass_dir": str,
}
)
@ -1604,6 +1612,10 @@ DEFAULT_MASTER_OPTS = immutabletypes.freeze(
"fips_mode": False,
"detect_remote_minions": False,
"remote_minions_port": 22,
"pass_variable_prefix": "",
"pass_strict_fetch": False,
"pass_gnupghome": "",
"pass_dir": "",
}
)

View file

@ -45,6 +45,34 @@ Install pass binary
pass:
pkg.installed
Salt master configuration options
.. code-block:: yaml
# If the prefix is *not* set (default behavior), all template variables are
# considered for fetching secrets from Pass. Those that cannot be resolved
# to a secret are passed through.
#
# If the prefix is set, only the template variables with matching prefix are
# considered for fetching the secrets, other variables are passed through.
#
# For ease of use it is recommended to set the following options as well:
# renderer: 'jinja|yaml|pass'
# pass_strict_fetch: true
#
pass_variable_prefix: 'pass:'
# If set to 'true', error out when unable to fetch a secret for a template variable.
pass_strict_fetch: true
# Set GNUPGHOME env for Pass.
# Defaults to: ~/.gnupg
pass_gnupghome: <path>
# Set PASSWORD_STORE_DIR env for Pass.
# Defaults to: ~/.password-store
pass_dir: <path>
"""
@ -54,7 +82,7 @@ from os.path import expanduser
from subprocess import PIPE, Popen
import salt.utils.path
from salt.exceptions import SaltRenderError
from salt.exceptions import SaltConfigurationError, SaltRenderError
log = logging.getLogger(__name__)
@ -80,6 +108,23 @@ def _fetch_secret(pass_path):
# Make a backup in case we want to return the original value without stripped whitespaces
original_pass_path = pass_path
# Remove the optional prefix from pass path
pass_prefix = __opts__["pass_variable_prefix"]
if pass_prefix:
# If we do not see our prefix we do not want to process this variable
# and we return the unmodified pass path
if not pass_path.startswith(pass_prefix):
return pass_path
# strip the prefix from the start of the string
pass_path = pass_path[len(pass_prefix) :]
# The pass_strict_fetch option must be used with pass_variable_prefix
pass_strict_fetch = __opts__["pass_strict_fetch"]
if pass_strict_fetch and not pass_prefix:
msg = "The 'pass_strict_fetch' option requires 'pass_variable_prefix' option enabled"
raise SaltConfigurationError(msg)
# Remove whitespaces from the pass_path
pass_path = pass_path.strip()
@ -91,14 +136,32 @@ def _fetch_secret(pass_path):
env = os.environ.copy()
env["HOME"] = expanduser("~")
pass_dir = __opts__["pass_dir"]
if pass_dir:
env["PASSWORD_STORE_DIR"] = pass_dir
pass_gnupghome = __opts__["pass_gnupghome"]
if pass_gnupghome:
env["GNUPGHOME"] = pass_gnupghome
proc = Popen(cmd, stdout=PIPE, stderr=PIPE, env=env)
pass_data, pass_error = proc.communicate()
# The version of pass used during development sent output to
# stdout instead of stderr even though its returncode was non zero.
if proc.returncode or not pass_data:
log.warning("Could not fetch secret: %s %s", pass_data, pass_error)
return original_pass_path
try:
pass_error = pass_error.decode("utf-8")
except (AttributeError, ValueError):
pass
msg = "Could not fetch secret '{}' from the password store: {}".format(
pass_path, pass_error
)
if pass_strict_fetch:
raise SaltRenderError(msg)
else:
log.warning(msg)
return original_pass_path
return pass_data.rstrip("\r\n")