"""Manage Vault KV v1/v2 secrets statefully... versionadded:: 1.2.0.. important:: This module requires the general :ref:`Vault setup <vault-setup>`."""importcopyimportloggingfromsalt.exceptionsimportCommandExecutionErrorfromsalt.exceptionsimportSaltExceptionfromsalt.exceptionsimportSaltInvocationErrorlog=logging.getLogger(__name__)
[docs]defpresent(name,values,sync=False):""" Ensure a secret is present as specified. Does not report a diff. name The path of the secret. values A mapping of values the secret should expose. sync Ensure the secret only exposes ``values`` and delete unspecified ones. Defaults to false, which results in patching (merging over) existing data and deleting keys that are set to ``None``/``null``. For details, see https://datatracker.ietf.org/doc/html/draft-ietf-appsawg-json-merge-patch-07 """# TODO: manage KV v2 metadata?ret={"name":name,"result":True,"comment":"The secret is already present as specified","changes":{},}try:try:current=__salt__["vault.read_secret"](name)exceptCommandExecutionErroraserr:# VaultNotFoundError should be subclassed to# CommandExecutionError and not re-raised by the# execution module @FIXME?if"VaultNotFoundError"notinstr(err):raisecurrent=Noneelse:ifsync:ifcurrent==values:returnretelse:defapply_json_merge_patch(data,patch):ifnotpatch:returndataifnotisinstance(data,dict)ornotisinstance(patch,dict):raiseValueError("Data and patch must be dictionaries.")forkey,valueinpatch.items():ifvalueisNone:data.pop(key,None)elifisinstance(value,dict):data[key]=apply_json_merge_patch(data.get(key,{}),value)else:data[key]=valuereturndatanew=apply_json_merge_patch(copy.deepcopy(current),values)ifnew==current:returnretverb="patch"ifcurrentisnotNoneandnotsyncelse"write"pp="patched"ifverb=="patch"else"written"ret["changes"][pp]=nameif__opts__["test"]:ret["result"]=Noneret["comment"]=f"Would have {pp} the secret"returnretifnot__salt__[f"vault.{verb}_secret"](name,**values):# Only read_secret raises exceptions sadly FIXME?raiseCommandExecutionError(f"Failed to {verb} secret, see logs for details")ret["comment"]=f"The secret was {pp}"exceptSaltExceptionaserr:ret["result"]=Falseret["comment"]=str(err)ret["changes"]={}returnret
[docs]defabsent(name,operation="delete"):""" Ensure a secret is absent. This operates only on the most recent version for delete/destroy. Currently does not destroy/wipe a secret that has been made unreadable in some other way. name The path of the secret. operation The operation to perform to remove the secret. Only relevant for KV v2. Options are: ``delete`` (meaning: soft-delete), ``destroy`` (meaning delete unrecoverably) and ``wipe`` (forget about the secret completely). Defaults to ``delete``. KV v1 secrets are always wiped since the backend does not support versioning. """valid_ops=("delete","destroy","wipe")ifoperationnotinvalid_ops:raiseSaltInvocationError(f"Invalid operation '{operation}'. Valid: {', '.join(valid_ops)}")ret={"name":name,"result":True,"comment":"The secret is already absent","changes":{},}pp="destroyed"ifoperation=="destroy"elseoperation+"d"try:try:__salt__["vault.read_secret"](name)exceptCommandExecutionErroraserr:if"VaultNotFoundError"notinstr(err):raisereturnretret["changes"][pp]=nameif__opts__["test"]:ret["result"]=Noneret["comment"]=f"Would have {pp} the secret"returnretifnot__salt__[f"vault.{operation}_secret"](name):# Only read_secret raises exceptions sadly FIXME?raiseCommandExecutionError(f"Failed to {operation} secret, see logs for details")ret["comment"]=f"The secret has been {pp}"exceptSaltExceptionaserr:ret["result"]=Falseret["comment"]=str(err)ret["changes"]={}returnret