"""
Interface with a Vault (or OpenBao) server and the KV secret backend.
.. important::
This module requires the general :ref:`Vault setup <vault-setup>`.
"""
import logging
from typing import TYPE_CHECKING
from salt.defaults import NOT_SET
from salt.exceptions import CommandExecutionError
from salt.exceptions import SaltException
from saltext.vault.utils import vault
from saltext.vault.utils.versions import warn_until
if TYPE_CHECKING:
from saltext.vault.utils._types import SaltContext
from saltext.vault.utils._types import SaltFunctions
from saltext.vault.utils._types import SaltGrains
from saltext.vault.utils._types import SaltLogger
from saltext.vault.utils._types import SaltOpts
__opts__: SaltOpts
__context__: SaltContext
__salt__: SaltFunctions
__grains__: SaltGrains
log: "SaltLogger" = logging.getLogger(__name__) # type: ignore
ALIAS_WARNING = (
"The `vault.{old_name}` function was renamed to `{new_name}`. "
"Please adjust your calls accordingly. "
"This compatibility alias will be dropped in version {{version}}."
)
[docs]
def query(method, endpoint, payload=None):
"""
.. versionadded:: 1.0.0
Issue arbitrary queries against the Vault API.
CLI Example:
.. code-block:: bash
salt '*' vault.query GET auth/token/lookup-self
Required policy: Depends on the query.
You can ask the Vault CLI to output the necessary policy:
.. code-block:: bash
vault read -output-policy auth/token/lookup-self
method
HTTP method to use. Standard methods and ``LIST`` are dispatched
as logical operations via the corresponding ``api_*`` :py:mod:`utility functions <saltext.vault.utils.vault>`,
other methods are passed through to the API literally.
.. versionchanged:: 1.9.0
``LIST`` now follows the :vconf:`client:list_as_get` option.
``PUT`` is now issued as an equivalent ``POST`` request that
is marked as idempotent (safe to retry, without enabling :vconf:`client:retry_post`).
See the `general Vault API docs <https://developer.hashicorp.com/vault/api-docs#api-operations>`__
for details.
endpoint
Vault API endpoint to issue the request against. Omit ``/v1/``.
payload
Optional dictionary to use as JSON payload (``POST``/``PUT``/``PATCH``)
or query parameters (``GET``/``DELETE``/``LIST``).
"""
try:
if (func := getattr(vault, f"api_{method.lower().strip()}", None)) is not None:
return func( # pylint: disable=not-callable
endpoint, __opts__, __context__, payload=payload
)
return vault.query(method, endpoint, __opts__, __context__, payload=payload)
except SaltException as err:
raise CommandExecutionError(f"{type(err).__name__}: {err}") from err
[docs]
def clear_cache(connection=True, session=False):
"""
.. versionadded:: 1.0.0
Delete Vault caches. Ensures the current token and associated leases
are revoked by default.
The cache is organized in a hierarchy: ``/vault/connection/session/leases``.
(*italics* mark data that is only cached when receiving configuration from a master)
``connection`` contains KV metadata (by default), *configuration* and *(AppRole) auth credentials*.
``session`` contains the currently active token.
``leases`` contains leases issued to the currently active token like database credentials.
CLI Example:
.. code-block:: bash
salt '*' vault.clear_cache
salt '*' vault.clear_cache session=True
connection
Only clear the cached data scoped to a connection. This includes
configuration, auth credentials, the currently active auth token
as well as leases and KV metadata (by default). Defaults to true.
Set this to false to clear all Vault caches.
session
Only clear the cached data scoped to a session. This only includes
leases and the currently active auth token, but not configuration
or (AppRole) auth credentials. Defaults to false.
Setting this to true keeps the connection cache, regardless
of ``connection``.
"""
try:
return vault.clear_cache(__opts__, __context__, connection=connection, session=session)
except SaltException as err:
raise CommandExecutionError(f"{type(err).__name__}: {err}") from err
[docs]
def update_config(keep_session=False):
"""
.. versionadded:: 1.0.0
Attempt to update the cached configuration without clearing the
currently active Vault session.
.. note::
This is only relevant on minions that receive issued credentials
from the master. On locally configured minions, this does nothing.
CLI Example:
.. code-block:: bash
salt '*' vault.update_config
keep_session
Only update configuration that can be updated without
creating a new login session.
If this is false, still tries to keep the active session,
but might clear it if the server configuration has changed
significantly.
Defaults to False.
"""
try:
return vault.update_config(__opts__, __context__, keep_session=keep_session)
except SaltException as err:
raise CommandExecutionError(f"{type(err).__name__}: {err}") from err
[docs]
def get_server_config():
"""
.. versionadded:: 1.0.0
Return the server connection configuration that's currently in use by Salt.
Contains :vconf:`url <server:url>`, :vconf:`verify <server:verify>`,
:vconf:`namespace <server:namespace>` and :vconf:`url_alts <server:url_alts>`.
CLI Example:
.. code-block:: bash
salt '*' vault.get_server_config
"""
try:
client = vault.get_authd_client(__opts__, __context__)
return client.get_config()
except SaltException as err:
raise CommandExecutionError(f"{type(err).__name__}: {err}") from err
[docs]
def clear_token_cache():
"""
.. deprecated:: 1.0.0
.. versionchanged:: 1.0.0
This is now an alias for :func:`vault.clear_cache<clear_cache>` with ``connection=True``
and ``session=False`` (the defaults).
Delete minion Vault token cache.
CLI Example:
.. code-block:: bash
salt '*' vault.clear_token_cache
"""
log.debug("Deleting vault connection cache.")
warn_until(
2,
"The `vault.clear_token_cache` function is just an alias for `vault.clear_cache()`. "
"Please migrate. This alias will be dropped in version {version}",
)
return clear_cache(connection=True, session=False)
#############################################################################
# Deprecated aliases. They were refactored into separate execution modules: #
#############################################################################
def _log_kv_error(err, prefix):
# The new vault_secret functions raise errors prefixed like this,
# these aliases used to log them and return False instead.
err_msg = str(err)
if err_msg.startswith(prefix):
log.error(err_msg)
else:
log.error("%s %s: %s", prefix, type(err).__name__, err)
return False
[docs]
def read_secret(path, key=None, metadata=False, default=NOT_SET, version=None):
"""
.. deprecated:: 1.9.0
Renamed to :py:func:`vault_secret.read <saltext.vault.modules.vault_secret.read>`.
Please adjust your calls accordingly.
This compatibility alias will be dropped in the next major release.
Return the value of <key> at <path> in vault, or entire secret.
CLI Example:
.. code-block:: bash
salt '*' vault.read_secret salt/kv/secret
Required policy:
.. code-block:: vaultpolicy
path "<mount>/<secret>" {
capabilities = ["read"]
}
# or KV v2
path "<mount>/data/<secret>" {
capabilities = ["read"]
}
path
Path to the secret, including mount.
key
Field of secret at ``path`` to read.
If unspecified, returns the whole dataset.
metadata
If ``path`` is on a KV v2 backend, display full results, including metadata.
Only respected if ``key`` is not set. Defaults to False.
default
Instead of raising an exception, return this value when ``path``
is not found or the secret at ``path`` does not contain ``key``.
version
Version to read. If unset, reads the latest one.
.. versionadded:: 1.2.0
"""
warn_until(2, ALIAS_WARNING.format(old_name="read_secret", new_name="vault_secret.read"))
try:
return __salt__["vault_secret.read"](path, key=key, metadata=metadata, version=version)
except Exception as err: # pylint: disable=broad-except
# This used to swallow all exceptions
if default == NOT_SET or default is CommandExecutionError:
if not isinstance(err, CommandExecutionError) or not str(err).startswith(
"Failed to read secret!"
):
raise CommandExecutionError(
f"Failed to read secret! {type(err).__name__}: {err}"
) from err
raise
return default
[docs]
def write_secret(path, **kwargs):
"""
.. deprecated:: 1.9.0
Renamed to :py:func:`vault_secret.write <saltext.vault.modules.vault_secret.write>`.
Please adjust your calls accordingly.
This compatibility alias will be dropped in the next major release.
Set secret dataset at <path>.
Fields are specified as arbitrary keyword arguments.
CLI Example:
.. code-block:: bash
salt '*' vault.write_secret "secret/my/secret" user="foo" password="bar"
Required policy:
.. code-block:: vaultpolicy
path "<mount>/<secret>" {
capabilities = ["create", "update"]
}
# or KV v2
path "<mount>/data/<secret>" {
capabilities = ["create", "update"]
}
path
Path to the secret, including mount.
"""
warn_until(2, ALIAS_WARNING.format(old_name="write_secret", new_name="vault_secret.write"))
try:
return __salt__["vault_secret.write"](path, **kwargs)
except Exception as err: # pylint: disable=broad-except
return _log_kv_error(err, "Failed to write secret!")
[docs]
def write_raw(path, raw):
"""
.. deprecated:: 1.9.0
Renamed to :py:func:`vault_secret.write_raw <saltext.vault.modules.vault_secret.write_raw>`.
Please adjust your calls accordingly.
This compatibility alias will be dropped in the next major release.
Set raw data at <path>.
CLI Example:
.. code-block:: bash
salt '*' vault.write_raw "secret/my/secret" '{user: foo, password: bar}'
Required policy: see :func:`write_secret`
path
Path to the secret, including mount.
raw
Secret data to write to <path>. Has to be a mapping.
"""
warn_until(2, ALIAS_WARNING.format(old_name="write_raw", new_name="vault_secret.write_raw"))
try:
return __salt__["vault_secret.write_raw"](path, raw)
except Exception as err: # pylint: disable=broad-except
return _log_kv_error(err, "Failed to write secret!")
[docs]
def patch_secret(path, **kwargs):
"""
.. deprecated:: 1.9.0
Renamed to :py:func:`vault_secret.patch <saltext.vault.modules.vault_secret.patch>`.
Please adjust your calls accordingly.
This compatibility alias will be dropped in the next major release.
Patch secret dataset at <path>. Fields are specified as arbitrary keyword arguments.
.. note::
This works even for older Vault versions, KV v1 and with missing
``patch`` capability, but uses more than one request to simulate
the functionality by issuing a read and update request.
For proper, single-request patching, requires versions of KV v2 that
support the ``patch`` capability and the ``patch`` capability to be available
for the path.
.. note::
This uses JSON Merge Patch format internally.
Keys set to ``null`` (JSON/YAML)/``None`` (Python) are deleted.
CLI Example:
.. code-block:: bash
salt '*' vault.patch_secret "secret/my/secret" password="baz"
Required policy:
.. code-block:: vaultpolicy
# Proper patching
path "<mount>/data/<secret>" {
capabilities = ["patch"]
}
# OR (!), for older KV v2 setups:
path "<mount>/data/<secret>" {
capabilities = ["read", "update"]
}
# OR (!), for KV v1 setups:
path "<mount>/<secret>" {
capabilities = ["read", "update"]
}
path
Path to the secret, including mount.
"""
warn_until(2, ALIAS_WARNING.format(old_name="patch_secret", new_name="vault_secret.patch"))
try:
return __salt__["vault_secret.patch"](path, **kwargs)
except Exception as err: # pylint: disable=broad-except
return _log_kv_error(err, "Failed to patch secret!")
[docs]
def patch_raw(path, raw):
"""
.. deprecated:: 1.9.0
Renamed to :py:func:`vault_secret.patch_raw <saltext.vault.modules.vault_secret.patch_raw>`.
Please adjust your calls accordingly.
This compatibility alias will be dropped in the next major release.
.. versionadded:: 1.8.0
Patch raw data at <path>.
CLI Example:
.. code-block:: bash
salt '*' vault.patch_raw "secret/my/secret" '{user: foo, password: bar}'
Required policy: see :func:`patch_secret`
path
Path to the secret, including mount.
raw
Secret data to patch into <path>. Has to be a mapping.
Keys set to ``null`` (JSON/YAML)/``None`` (Python) are deleted.
"""
warn_until(2, ALIAS_WARNING.format(old_name="patch_raw", new_name="vault_secret.patch_raw"))
try:
return __salt__["vault_secret.patch_raw"](path, raw)
except Exception as err: # pylint: disable=broad-except
return _log_kv_error(err, "Failed to patch secret!")
[docs]
def delete_secret(path, *args, all_versions=False, **_):
"""
.. deprecated:: 1.9.0
Renamed to :py:func:`vault_secret.delete <saltext.vault.modules.vault_secret.delete>`.
Please adjust your calls accordingly.
This compatibility alias will be dropped in the next major release.
Delete secret at <path>. If <path> is on KV v2, the secret is soft-deleted.
CLI Example:
.. code-block:: bash
salt '*' vault.delete_secret "secret/my/secret"
salt '*' vault.delete_secret "secret/my/secret" 1 2 3
salt '*' vault.delete_secret "secret/my/secret" all_versions=true
Required policy:
.. code-block:: vaultpolicy
path "<mount>/<secret>" {
capabilities = ["delete"]
}
# or KV v2
path "<mount>/data/<secret>" {
capabilities = ["delete"]
}
# KV v2 versions
# all_versions=True additionally requires the policy for vault.read_secret_meta
path "<mount>/delete/<secret>" {
capabilities = ["update"]
}
path
Path to the secret, including mount.
all_versions
.. versionadded:: 1.2.0
Delete all versions of the secret for KV v2.
Can only be passed as a keyword argument.
Defaults to false.
.. versionadded:: 1.0.0
For KV v2, you can specify versions to soft-delete as supplemental
positional arguments.
"""
warn_until(2, ALIAS_WARNING.format(old_name="delete_secret", new_name="vault_secret.delete"))
try:
return __salt__["vault_secret.delete"](path, *args, all_versions=all_versions)
except Exception as err: # pylint: disable=broad-except
return _log_kv_error(err, "Failed to delete secret!")
[docs]
def restore_secret(path, *versions, all_versions=False, **_):
"""
.. deprecated:: 1.9.0
Renamed to :py:func:`vault_secret.restore <saltext.vault.modules.vault_secret.restore>`.
Please adjust your calls accordingly.
This compatibility alias will be dropped in the next major release.
.. versionadded:: 1.2.0
Restore specific versions of a secret path. Only supported on Vault KV v2.
CLI Example:
.. code-block:: bash
salt '*' vault.restore_secret secret/my/secret 1 2
Required policy:
.. code-block:: vaultpolicy
# all_versions=True or defaulting to the most recent version additionally
# requires the policy for vault.read_secret_meta
path "<mount>/undelete/<secret>" {
capabilities = ["update"]
}
path
Path to the secret, including mount.
all_versions
Restore all versions of the secret for KV v2.
Can only be passed as a keyword argument.
Defaults to false.
You can specify versions to restore as supplemental positional arguments.
If no version is specified, tries to restore the latest version, and if
the latest version has not been deleted, fails.
"""
warn_until(2, ALIAS_WARNING.format(old_name="restore_secret", new_name="vault_secret.restore"))
# This one always raised errors
return __salt__["vault_secret.restore"](path, *versions, all_versions=all_versions)
[docs]
def destroy_secret(path, *args, all_versions=False, **_):
"""
.. deprecated:: 1.9.0
Renamed to :py:func:`vault_secret.destroy <saltext.vault.modules.vault_secret.destroy>`.
Please adjust your calls accordingly.
This compatibility alias will be dropped in the next major release.
Destroy specified secret versions at <path>.
This makes a secret version unrecoverable.
On KV v1, there is no functional difference to ``delete``
because the backend does not support versioning.
Specifying versions fails there.
.. versionchanged:: 1.8.0
KV v1 secrets are now deleted instead of failing.
CLI Example:
.. code-block:: bash
salt '*' vault.destroy_secret "secret/my/secret"
salt '*' vault.destroy_secret "secret/my/secret" 1 2
salt '*' vault.destroy_secret "secret/my/secret" all_versions=true
Required policy:
.. code-block:: vaultpolicy
# all_versions=True or defaulting to the most recent version additionally
# requires the policy for vault.read_secret_meta
path "<mount>/destroy/<secret>" {
capabilities = ["update"]
}
path
Path to the secret, including mount.
all_versions
.. versionadded:: 1.2.0
Destroy all versions of the secret for KV v2.
Can only be passed as a keyword argument.
Defaults to false.
You can specify versions to destroy as supplemental positional arguments.
.. versionchanged:: 1.2.0
If no version was specified, defaults to the most recent one.
"""
warn_until(2, ALIAS_WARNING.format(old_name="destroy_secret", new_name="vault_secret.destroy"))
try:
return __salt__["vault_secret.destroy"](path, *args, all_versions=all_versions)
except Exception as err: # pylint: disable=broad-except
return _log_kv_error(err, "Failed to destroy secret!")
[docs]
def wipe_secret(path):
"""
.. deprecated:: 1.9.0
Renamed to :py:func:`vault_secret.wipe <saltext.vault.modules.vault_secret.wipe>`.
Please adjust your calls accordingly.
This compatibility alias will be dropped in the next major release.
.. versionadded:: 1.2.0
Remove all version history and data for the secret at <path>.
On KV v1, there is no functional difference to ``delete``
because the backend does not support versioning.
.. versionchanged:: 1.8.0
KV v1 secrets are now deleted instead of failing.
CLI Example:
.. code-block:: bash
salt '*' vault.wipe_secret "secret/my/secret"
Required policy:
.. code-block:: vaultpolicy
path "<mount>/metadata/<secret>" {
capabilities = ["delete"]
}
"""
warn_until(2, ALIAS_WARNING.format(old_name="wipe_secret", new_name="vault_secret.wipe"))
try:
return __salt__["vault_secret.wipe"](path)
except Exception as err: # pylint: disable=broad-except
return _log_kv_error(err, "Failed to wipe secret!")
[docs]
def list_secrets(path, default=NOT_SET, keys_only=None):
"""
.. deprecated:: 1.9.0
Renamed to :py:func:`vault_secret.list <saltext.vault.modules.vault_secret.list_>`.
Please adjust your calls accordingly.
This compatibility alias will be dropped in the next major release.
List secret keys at <path>. The path should end with a trailing slash.
CLI Example:
.. code-block:: bash
salt '*' vault.list_secrets "secret/my/"
Required policy:
.. code-block:: vaultpolicy
path "<mount>/<path>" {
capabilities = ["list"]
}
# or KV v2
path "<mount>/metadata/<path>" {
capabilities = ["list"]
}
path
Path to the secret, including mount.
default
When the path is not found, an exception is raised, unless a default
is provided here.
keys_only
.. versionadded:: 1.0.0
This function used to return a dictionary like ``{"keys": ["some/", "some/key"]}``.
Setting this to True only returns the list of keys.
For backwards-compatibility reasons, this defaults to False.
The :py:func:`migrated function <saltext.vault.modules.vault_secret.list_>` always
returns a list of keys and does not have this parameter.
"""
warn_until(2, ALIAS_WARNING.format(old_name="list_secrets", new_name="vault_secret.list"))
try:
res = __salt__["vault_secret.list"](path)
except Exception as err: # pylint: disable=broad-except
# This used to swallow all exceptions
if default == NOT_SET or default is CommandExecutionError:
if not isinstance(err, CommandExecutionError) or not str(err).startswith(
"Failed to list secrets!"
):
raise CommandExecutionError(
f"Failed to list secrets! {type(err).__name__}: {err}"
) from err
raise
return default
if keys_only:
return res
# this is the way Salt behaved previously
return {"keys": res}
[docs]
def policy_fetch(policy):
"""
.. deprecated:: 1.9.0
Renamed to :py:func:`vault_policy.fetch <saltext.vault.modules.vault_policy.fetch>`.
Please adjust your calls accordingly.
This compatibility alias will be dropped in the next major release.
Fetch the rules associated with an ACL policy. Returns ``None`` if the policy
does not exist.
CLI Example:
.. code-block:: bash
salt '*' vault.policy_fetch salt_minion
Required policy:
.. code-block:: vaultpolicy
path "sys/policy/<policy>" {
capabilities = ["read"]
}
policy
Name of the policy to fetch.
"""
warn_until(2, ALIAS_WARNING.format(old_name="policy_fetch", new_name="vault_policy.fetch"))
return __salt__["vault_policy.fetch"](policy)
[docs]
def policy_write(policy, rules):
"""
.. deprecated:: 1.9.0
Renamed to :py:func:`vault_policy.write <saltext.vault.modules.vault_policy.write>`.
Please adjust your calls accordingly.
This compatibility alias will be dropped in the next major release.
Create or update an ACL policy.
CLI Example:
.. code-block:: bash
salt '*' vault.policy_write salt_minion 'path "secret/foo" {...}'
Required policy:
.. code-block:: vaultpolicy
path "sys/policy/<policy>" {
capabilities = ["create", "update"]
}
policy
Name of the policy to create/update.
rules
Rules to write, formatted as in-line HCL.
"""
warn_until(2, ALIAS_WARNING.format(old_name="policy_write", new_name="vault_policy.write"))
return __salt__["vault_policy.write"](policy, rules=rules)
[docs]
def policy_delete(policy):
"""
.. deprecated:: 1.9.0
Renamed to :py:func:`vault_policy.delete <saltext.vault.modules.vault_policy.delete>`.
Please adjust your calls accordingly.
This compatibility alias will be dropped in the next major release.
Delete an ACL policy. Returns False if the policy does not exist.
CLI Example:
.. code-block:: bash
salt '*' vault.policy_delete salt_minion
Required policy:
.. code-block:: vaultpolicy
path "sys/policy/<policy>" {
capabilities = ["delete"]
}
policy
Name of the policy to delete.
"""
warn_until(2, ALIAS_WARNING.format(old_name="policy_delete", new_name="vault_policy.delete"))
return __salt__["vault_policy.delete"](policy)
[docs]
def policies_list():
"""
.. deprecated:: 1.9.0
Renamed to :py:func:`vault_policy.list <saltext.vault.modules.vault_policy.list_>`.
Please adjust your calls accordingly.
This compatibility alias will be dropped in the next major release.
List all ACL policies.
CLI Example:
.. code-block:: bash
salt '*' vault.policies_list
Required policy:
.. code-block:: vaultpolicy
path "sys/policy" {
capabilities = ["read"]
}
"""
warn_until(2, ALIAS_WARNING.format(old_name="policies_list", new_name="vault_policy.list"))
return __salt__["vault_policy.list"]()