File add-gopass-jinja_filter.patch of Package salt
diff --git a/salt/utils/gopass.py b/salt/utils/gopass.py
new file mode 100644
index 0000000000..dff76179b6
--- /dev/null
+++ b/salt/utils/gopass.py
@@ -0,0 +1,134 @@
+import logging
+import os
+from os.path import expanduser
+from subprocess import PIPE, Popen
+
+
+import salt.utils.path
+from salt.exceptions import SaltConfigurationError, SaltRenderError
+from salt.utils.decorators.jinja import jinja_filter
+from salt.utils.yamlencoding import yaml_squote
+
+
+log = logging.getLogger(__name__)
+
+
+def _get_pass_exec():
+ """
+ Return the pass executable or raise an error
+ """
+ pass_exec = salt.utils.path.which("gopass")
+ if pass_exec:
+ return pass_exec
+ else:
+ raise SaltRenderError("gopass unavailable")
+
+
+def _fetch_from_recursive_secret(pass_path, password_only=True, opts={}):
+ # 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)
+
+ return _fetch_single_secret(pass_path, password_only, opts)
+
+
+def _fetch_single_secret(pass_path, password_only=True, opts={}):
+ """
+ Fetch secret from pass based on pass_path. If there is
+ any error, return back the original pass_path value
+ """
+ pass_exec = _get_pass_exec()
+
+ # Make a backup in case we want to return the original value without stripped whitespaces
+ original_pass_path = pass_path
+
+ # Remove whitespaces from the pass_path
+ pass_path = pass_path.strip()
+
+ cmd = [pass_exec, "show"]
+ if password_only:
+ cmd.append("--password")
+ cmd.append(pass_path)
+
+ log.debug("Fetching secret: %s", " ".join(cmd))
+
+ # Make sure environment variable HOME is set, since Pass looks for the
+ # password-store under ~/.password-store.
+ env = os.environ.copy()
+ env["HOME"] = expanduser("~")
+
+ pass_strict_fetch = opts["pass_strict_fetch"]
+ 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
+
+ try:
+ proc = Popen(cmd, stdout=PIPE, stderr=PIPE, env=env, encoding="utf-8")
+ pass_data, pass_error = proc.communicate()
+ pass_returncode = proc.returncode
+ except (OSError, UnicodeDecodeError) as e:
+ pass_data, pass_error = "", str(e)
+ pass_returncode = 1
+
+ # The version of pass used during development sent output to
+ # stdout instead of stderr even though its returncode was non zero.
+ if pass_returncode or not pass_data:
+ msg = f"Could not fetch secret '{pass_path}' from the password store: {pass_error}"
+ if pass_strict_fetch:
+ raise SaltRenderError(msg)
+ else:
+ log.warning(msg)
+ return original_pass_path
+ return pass_data.rstrip("\r\n")
+
+
+def _decrypt_object(obj):
+ """
+ Recursively try to find a pass path (string) that can be handed off to pass
+ """
+ if isinstance(obj, str):
+ return _fetch_from_recursive_secret(obj)
+ elif isinstance(obj, dict):
+ for pass_key, pass_path in obj.items():
+ obj[pass_key] = _decrypt_object(pass_path)
+ elif isinstance(obj, list):
+ for pass_key, pass_path in enumerate(obj):
+ obj[pass_key] = _decrypt_object(pass_path)
+ return obj
+
+
+@jinja_filter("gopass")
+def gopass_filter(pass_info, quoted=True, password_only=True, opts=None, node="master"):
+ if opts is None:
+ if node == "master":
+ opts = salt.config.master_config(
+ os.path.join(salt.syspaths.CONFIG_DIR, "master")
+ )
+ elif node == "minion":
+ opts = salt.config.minion_config(
+ os.path.join(salt.syspaths.CONFIG_DIR, "minion")
+ )
+ else:
+ opts = {}
+ log.debug(f"querying gopass filter with {pass_info}")
+ resolved = _fetch_single_secret(pass_info, password_only, opts)
+ if quoted:
+ resolved = yaml_squote(resolved)
+ return resolved
diff --git a/salt/utils/templates.py b/salt/utils/templates.py
index 8639ea703e..ecf1e17123 100644
--- a/salt/utils/templates.py
+++ b/salt/utils/templates.py
@@ -21,6 +21,7 @@ import salt.utils.hashutils
import salt.utils.http
import salt.utils.jinja
import salt.utils.network
+import salt.utils.gopass
import salt.utils.platform
import salt.utils.stringutils
import salt.utils.yamlencoding