mirror of
https://github.com/saltstack/salt.git
synced 2025-04-17 10:10:20 +00:00
Add signature verification to file.managed/archive.extracted
This commit is contained in:
parent
da4579e3e1
commit
0ff2d2b7a8
16 changed files with 1155 additions and 4 deletions
1
changelog/63143.added
Normal file
1
changelog/63143.added
Normal file
|
@ -0,0 +1 @@
|
|||
Added signature verification to file.managed/archive.extraced
|
|
@ -766,6 +766,11 @@ def get_source_sum(
|
|||
source_hash_name=None,
|
||||
saltenv="base",
|
||||
verify_ssl=True,
|
||||
source_hash_sig=None,
|
||||
signed_by_any=None,
|
||||
signed_by_all=None,
|
||||
keyring=None,
|
||||
gnupghome=None,
|
||||
):
|
||||
"""
|
||||
.. versionadded:: 2016.11.0
|
||||
|
@ -806,6 +811,39 @@ def get_source_sum(
|
|||
|
||||
.. versionadded:: 3002
|
||||
|
||||
source_hash_sig
|
||||
When ``source_hash`` is a file, ensure a valid GPG signature exists on
|
||||
the source hash file.
|
||||
Set this to ``true`` for an inline (clearsigned) signature, or to a file URI
|
||||
retrievable by :py:func:`cp.cache_file <salt.modules.cp.cache_file>`
|
||||
for a detached one.
|
||||
|
||||
.. versionadded:: 3007
|
||||
|
||||
signed_by_any
|
||||
When verifying ``source_hash_sig``, require at least one valid signature
|
||||
from one of a list of key fingerprints. This is passed to :py:func:`gpg.verify
|
||||
<salt.modules.gpg.verify>`.
|
||||
|
||||
.. versionadded:: 3007
|
||||
|
||||
signed_by_all
|
||||
When verifying ``source_hash_sig``, require a valid signature from each
|
||||
of the key fingerprints in this list. This is passed to :py:func:`gpg.verify
|
||||
<salt.modules.gpg.verify>`.
|
||||
|
||||
.. versionadded:: 3007
|
||||
|
||||
keyring
|
||||
When verifying ``source_hash_sig``, use this keyring.
|
||||
|
||||
.. versionadded:: 3007
|
||||
|
||||
gnupghome
|
||||
When verifying ``source_hash_sig``, use this GnuPG home.
|
||||
|
||||
.. versionadded:: 3007
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
@ -846,6 +884,20 @@ def get_source_sum(
|
|||
raise CommandExecutionError(
|
||||
f"Source hash file {source_hash} not found"
|
||||
)
|
||||
if source_hash_sig:
|
||||
_check_sig(
|
||||
hash_fn,
|
||||
signature=source_hash_sig
|
||||
if isinstance(source_hash_sig, str)
|
||||
else None,
|
||||
signed_by_any=signed_by_any,
|
||||
signed_by_all=signed_by_all,
|
||||
keyring=keyring,
|
||||
gnupghome=gnupghome,
|
||||
saltenv=saltenv,
|
||||
verify_ssl=verify_ssl,
|
||||
)
|
||||
|
||||
else:
|
||||
if proto != "":
|
||||
# Some unsupported protocol (e.g. foo://) is being used.
|
||||
|
@ -967,6 +1019,54 @@ def check_hash(path, file_hash):
|
|||
return get_hash(path, hash_type) == hash_value
|
||||
|
||||
|
||||
def _check_sig(
|
||||
on_file,
|
||||
signature=None,
|
||||
signed_by_any=None,
|
||||
signed_by_all=None,
|
||||
keyring=None,
|
||||
gnupghome=None,
|
||||
saltenv="base",
|
||||
verify_ssl=True,
|
||||
):
|
||||
try:
|
||||
verify = __salt__["gpg.verify"]
|
||||
except KeyError:
|
||||
raise CommandExecutionError(
|
||||
"Signature verification requires the gpg module, "
|
||||
"which could not be found. Make sure you have the "
|
||||
"necessary tools and libraries intalled (gpg, python-gnupg)"
|
||||
)
|
||||
sig = None
|
||||
if signature is not None:
|
||||
# Fetch detached signature
|
||||
sig = __salt__["cp.cache_file"](signature, saltenv, verify_ssl=verify_ssl)
|
||||
if not sig:
|
||||
raise CommandExecutionError(
|
||||
f"Detached signature file {signature} not found"
|
||||
)
|
||||
|
||||
res = verify(
|
||||
filename=on_file,
|
||||
signature=sig,
|
||||
keyring=keyring,
|
||||
gnupghome=gnupghome,
|
||||
signed_by_any=signed_by_any,
|
||||
signed_by_all=signed_by_all,
|
||||
)
|
||||
|
||||
if res["res"] is True:
|
||||
return
|
||||
# Ensure detached signature and file are deleted from cache
|
||||
# on signature verification failure.
|
||||
if sig:
|
||||
salt.utils.files.safe_rm(sig)
|
||||
salt.utils.files.safe_rm(on_file)
|
||||
raise CommandExecutionError(
|
||||
f"The file's signature could not be verified: {res['message']}"
|
||||
)
|
||||
|
||||
|
||||
def find(path, *args, **kwargs):
|
||||
"""
|
||||
Approximate the Unix ``find(1)`` command and return a list of paths that
|
||||
|
@ -4595,6 +4695,11 @@ def get_managed(
|
|||
skip_verify=False,
|
||||
verify_ssl=True,
|
||||
use_etag=False,
|
||||
source_hash_sig=None,
|
||||
signed_by_any=None,
|
||||
signed_by_all=None,
|
||||
keyring=None,
|
||||
gnupghome=None,
|
||||
**kwargs,
|
||||
):
|
||||
"""
|
||||
|
@ -4660,6 +4765,39 @@ def get_managed(
|
|||
|
||||
.. versionadded:: 3005
|
||||
|
||||
source_hash_sig
|
||||
When ``source_hash`` is a file and ``skip_verify`` is not true and ``use_etag``
|
||||
is not true, ensure a valid GPG signature exists on the source hash file.
|
||||
Set this to ``true`` for an inline (clearsigned) signature, or to a file URI
|
||||
retrievable by ``cp.cache_file`` for a detached one. The cached file
|
||||
will be deleted if the signature verification fails.
|
||||
|
||||
.. versionadded:: 3007
|
||||
|
||||
signed_by_any
|
||||
When verifying ``source_hash_sig``, require at least one valid signature
|
||||
from one of a list of key fingerprints. This is passed to :py:func:`gpg.verify
|
||||
<salt.modules.gpg.verify>`.
|
||||
|
||||
.. versionadded:: 3007
|
||||
|
||||
signed_by_all
|
||||
When verifying ``source_hash_sig``, require a valid signature from each
|
||||
of the key fingerprints in this list. This is passed to :py:func:`gpg.verify
|
||||
<salt.modules.gpg.verify>`.
|
||||
|
||||
.. versionadded:: 3007
|
||||
|
||||
keyring
|
||||
When verifying ``source_hash_sig``, use this keyring.
|
||||
|
||||
.. versionadded:: 3007
|
||||
|
||||
gnupghome
|
||||
When verifying ``source_hash_sig``, use this GnuPG home.
|
||||
|
||||
.. versionadded:: 3007
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
@ -4728,6 +4866,11 @@ def get_managed(
|
|||
source_hash_name,
|
||||
saltenv,
|
||||
verify_ssl=verify_ssl,
|
||||
source_hash_sig=source_hash_sig,
|
||||
signed_by_any=signed_by_any,
|
||||
signed_by_all=signed_by_all,
|
||||
keyring=keyring,
|
||||
gnupghome=gnupghome,
|
||||
)
|
||||
except CommandExecutionError as exc:
|
||||
return "", {}, exc.strerror
|
||||
|
@ -5978,6 +6121,12 @@ def manage_file(
|
|||
serange=None,
|
||||
verify_ssl=True,
|
||||
use_etag=False,
|
||||
signature=None,
|
||||
source_hash_sig=None,
|
||||
signed_by_any=None,
|
||||
signed_by_all=None,
|
||||
keyring=None,
|
||||
gnupghome=None,
|
||||
**kwargs,
|
||||
):
|
||||
"""
|
||||
|
@ -6107,6 +6256,53 @@ def manage_file(
|
|||
|
||||
.. versionadded:: 3005
|
||||
|
||||
signature
|
||||
Ensure a valid GPG signature exists on the selected ``source`` file.
|
||||
Set this to true for inline signatures, or to a file URI retrievable by
|
||||
``cp.cache_file`` for a detached one. The cached file will be deleted
|
||||
if the signature verification fails.
|
||||
|
||||
.. note::
|
||||
|
||||
This signature will be enforced regardless of source type and will be
|
||||
required on the final output, therefore this does not lend itself well
|
||||
when templates are rendered.
|
||||
|
||||
.. versionadded:: 3007
|
||||
|
||||
source_hash_sig
|
||||
When ``source_hash`` is a file and ``skip_verify`` is not true and ``use_etag``
|
||||
is not true, ensure a valid GPG signature exists on the source hash file.
|
||||
Set this to ``true`` for an inline (clearsigned) signature, or to a file URI
|
||||
retrievable by ``cp.cache_file`` for a detached one. The cached file
|
||||
will be deleted if the signature verification fails.
|
||||
|
||||
.. versionadded:: 3007
|
||||
|
||||
signed_by_any
|
||||
When verifying signatures either on the managed file or its source hash file,
|
||||
require at least one valid signature from one of a list of key fingerprints.
|
||||
This is passed to :py:func:`gpg.verify <salt.modules.gpg.verify>`.
|
||||
|
||||
.. versionadded:: 3007
|
||||
|
||||
signed_by_all
|
||||
When verifying signatures either on the managed file or its source hash file,
|
||||
require a valid signature from each of the key fingerprints in this list.
|
||||
This is passed to :py:func:`gpg.verify <salt.modules.gpg.verify>`.
|
||||
|
||||
.. versionadded:: 3007
|
||||
|
||||
keyring
|
||||
When verifying signatures, use this keyring.
|
||||
|
||||
.. versionadded:: 3007
|
||||
|
||||
gnupghome
|
||||
When verifying signatures, use this GnuPG home.
|
||||
|
||||
.. versionadded:: 3007
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
@ -6192,6 +6388,23 @@ def manage_file(
|
|||
ret["result"] = False
|
||||
return ret
|
||||
|
||||
if signature:
|
||||
try:
|
||||
_check_sig(
|
||||
sfn,
|
||||
signature=signature if isinstance(signature, str) else None,
|
||||
signed_by_any=signed_by_any,
|
||||
signed_by_all=signed_by_all,
|
||||
keyring=keyring,
|
||||
gnupghome=gnupghome,
|
||||
saltenv=saltenv,
|
||||
verify_ssl=verify_ssl,
|
||||
)
|
||||
except CommandExecutionError as err:
|
||||
ret["result"] = False
|
||||
ret["comment"] = f"Failed checking new file's signature: {err}"
|
||||
return ret
|
||||
|
||||
# Print a diff equivalent to diff -u old new
|
||||
if __salt__["config.option"]("obfuscate_templates"):
|
||||
ret["changes"]["diff"] = "<Obfuscated Template>"
|
||||
|
@ -6286,6 +6499,23 @@ def manage_file(
|
|||
ret["result"] = False
|
||||
return ret
|
||||
|
||||
if signature:
|
||||
try:
|
||||
_check_sig(
|
||||
sfn,
|
||||
signature=signature if isinstance(signature, str) else None,
|
||||
signed_by_any=signed_by_any,
|
||||
signed_by_all=signed_by_all,
|
||||
keyring=keyring,
|
||||
gnupghome=gnupghome,
|
||||
saltenv=saltenv,
|
||||
verify_ssl=verify_ssl,
|
||||
)
|
||||
except CommandExecutionError as err:
|
||||
ret["result"] = False
|
||||
ret["comment"] = f"Failed checking new file's signature: {err}"
|
||||
return ret
|
||||
|
||||
try:
|
||||
salt.utils.files.copyfile(
|
||||
sfn,
|
||||
|
@ -6394,6 +6624,24 @@ def manage_file(
|
|||
)
|
||||
ret["result"] = False
|
||||
return ret
|
||||
|
||||
if signature:
|
||||
try:
|
||||
_check_sig(
|
||||
sfn,
|
||||
signature=signature if isinstance(signature, str) else None,
|
||||
signed_by_any=signed_by_any,
|
||||
signed_by_all=signed_by_all,
|
||||
keyring=keyring,
|
||||
gnupghome=gnupghome,
|
||||
saltenv=saltenv,
|
||||
verify_ssl=verify_ssl,
|
||||
)
|
||||
except CommandExecutionError as err:
|
||||
ret["result"] = False
|
||||
ret["comment"] = f"Failed checking new file's signature: {err}"
|
||||
return ret
|
||||
|
||||
# It is a new file, set the diff accordingly
|
||||
ret["changes"]["diff"] = "New file"
|
||||
if not os.path.isdir(contain_dir):
|
||||
|
|
|
@ -165,6 +165,52 @@ def _cleanup_destdir(name):
|
|||
pass
|
||||
|
||||
|
||||
def _check_sig(
|
||||
on_file,
|
||||
signature,
|
||||
signed_by_any=None,
|
||||
signed_by_all=None,
|
||||
keyring=None,
|
||||
gnupghome=None,
|
||||
):
|
||||
try:
|
||||
verify = __salt__["gpg.verify"]
|
||||
except KeyError:
|
||||
raise CommandExecutionError(
|
||||
"Signature verification requires the gpg module, "
|
||||
"which could not be found. Make sure you have the "
|
||||
"necessary tools and libraries intalled (gpg, python-gnupg)"
|
||||
)
|
||||
sig = None
|
||||
if signature is not None:
|
||||
# fetch detached signature
|
||||
sig = __salt__["cp.cache_file"](signature, __env__)
|
||||
if not sig:
|
||||
raise CommandExecutionError(
|
||||
f"Detached signature file {signature} not found"
|
||||
)
|
||||
|
||||
res = verify(
|
||||
filename=on_file,
|
||||
signature=sig,
|
||||
keyring=keyring,
|
||||
gnupghome=gnupghome,
|
||||
signed_by_any=signed_by_any,
|
||||
signed_by_all=signed_by_all,
|
||||
)
|
||||
|
||||
if res["res"] is True:
|
||||
return
|
||||
# Ensure detached signature and file are deleted from cache
|
||||
# on signature verification failure.
|
||||
if sig:
|
||||
salt.utils.files.safe_rm(sig)
|
||||
salt.utils.files.safe_rm(on_file)
|
||||
raise CommandExecutionError(
|
||||
f"The file's signature could not be verified: {res['message']}"
|
||||
)
|
||||
|
||||
|
||||
def extracted(
|
||||
name,
|
||||
source,
|
||||
|
@ -190,7 +236,13 @@ def extracted(
|
|||
enforce_ownership_on=None,
|
||||
archive_format=None,
|
||||
use_etag=False,
|
||||
**kwargs
|
||||
signature=None,
|
||||
source_hash_sig=None,
|
||||
signed_by_any=None,
|
||||
signed_by_all=None,
|
||||
keyring=None,
|
||||
gnupghome=None,
|
||||
**kwargs,
|
||||
):
|
||||
"""
|
||||
.. versionadded:: 2014.1.0
|
||||
|
@ -671,6 +723,47 @@ def extracted(
|
|||
|
||||
.. versionadded:: 3005
|
||||
|
||||
signature
|
||||
Ensure a valid GPG signature exists on the selected ``source`` file.
|
||||
This needs to be a file URI retrievable by ``cp.cache_file`` which
|
||||
identifies a detached signature.
|
||||
This signature will be enforced regardless of source type.
|
||||
|
||||
.. versionadded:: 3007
|
||||
|
||||
source_hash_sig
|
||||
When ``source_hash`` is a file and ``skip_verify`` is not true and ``use_etag``
|
||||
is not true, ensure a valid GPG signature exists on the source hash file.
|
||||
Set this to ``true`` for an inline (clearsigned) signature, or to a file URI
|
||||
retrievable by ``cp.cache_file`` for a detached one. The cached file
|
||||
will be deleted if the signature verification fails.
|
||||
|
||||
.. versionadded:: 3007
|
||||
|
||||
signed_by_any
|
||||
When verifying signatures either on the managed file or its source hash file,
|
||||
require at least one valid signature from one of a list of key fingerprints.
|
||||
This is passed to ``gpg.verify``.
|
||||
|
||||
.. versionadded:: 3007
|
||||
|
||||
signed_by_all
|
||||
When verifying signatures either on the managed file or its source hash file,
|
||||
require a valid signature from each of the key fingerprints in this list.
|
||||
This is passed to ``gpg.verify``.
|
||||
|
||||
.. versionadded:: 3007
|
||||
|
||||
keyring
|
||||
When verifying signatures, use this keyring.
|
||||
|
||||
.. versionadded:: 3007
|
||||
|
||||
gnupghome
|
||||
When verifying signatures, use this GnuPG home.
|
||||
|
||||
.. versionadded:: 3007
|
||||
|
||||
**Examples**
|
||||
|
||||
1. tar with lmza (i.e. xz) compression:
|
||||
|
@ -824,6 +917,16 @@ def extracted(
|
|||
"'source_hash' is not also specified."
|
||||
)
|
||||
|
||||
if signature or source_hash_sig:
|
||||
# Fail early in case the gpg module is not present
|
||||
try:
|
||||
__salt__["gpg.verify"]
|
||||
except KeyError:
|
||||
ret[
|
||||
"comment"
|
||||
] = "Cannot verify signatures because the gpg module was not loaded"
|
||||
return ret
|
||||
|
||||
try:
|
||||
source_match = __salt__["file.source_list"](source, source_hash, __env__)[0]
|
||||
except CommandExecutionError as exc:
|
||||
|
@ -983,6 +1086,11 @@ def extracted(
|
|||
source_hash=source_hash,
|
||||
source_hash_name=source_hash_name,
|
||||
saltenv=__env__,
|
||||
source_hash_sig=source_hash_sig,
|
||||
signed_by_any=signed_by_any,
|
||||
signed_by_all=signed_by_all,
|
||||
keyring=keyring,
|
||||
gnupghome=gnupghome,
|
||||
)
|
||||
except CommandExecutionError as exc:
|
||||
ret["comment"] = exc.strerror
|
||||
|
@ -1063,6 +1171,11 @@ def extracted(
|
|||
skip_verify=skip_verify,
|
||||
saltenv=__env__,
|
||||
use_etag=use_etag,
|
||||
source_hash_sig=source_hash_sig,
|
||||
signed_by_any=signed_by_any,
|
||||
signed_by_all=signed_by_all,
|
||||
keyring=keyring,
|
||||
gnupghome=gnupghome,
|
||||
)
|
||||
except Exception as exc: # pylint: disable=broad-except
|
||||
msg = "Failed to cache {}: {}".format(
|
||||
|
@ -1084,6 +1197,20 @@ def extracted(
|
|||
)
|
||||
return result
|
||||
|
||||
if signature:
|
||||
try:
|
||||
_check_sig(
|
||||
cached,
|
||||
signature,
|
||||
signed_by_any=signed_by_any,
|
||||
signed_by_all=signed_by_all,
|
||||
keyring=keyring,
|
||||
gnupghome=gnupghome,
|
||||
)
|
||||
except CommandExecutionError as err:
|
||||
ret["comment"] = f"Failed verifying the source file's signature: {err}"
|
||||
return ret
|
||||
|
||||
existing_cached_source_sum = _read_cached_checksum(cached)
|
||||
|
||||
if source_hash and source_hash_update and not skip_verify:
|
||||
|
@ -1396,7 +1523,7 @@ def extracted(
|
|||
options=options,
|
||||
trim_output=trim_output,
|
||||
password=password,
|
||||
**kwargs
|
||||
**kwargs,
|
||||
)
|
||||
except (CommandExecutionError, CommandNotFoundError) as exc:
|
||||
ret["comment"] = exc.strerror
|
||||
|
@ -1409,7 +1536,7 @@ def extracted(
|
|||
trim_output=trim_output,
|
||||
password=password,
|
||||
extract_perms=extract_perms,
|
||||
**kwargs
|
||||
**kwargs,
|
||||
)
|
||||
elif archive_format == "rar":
|
||||
try:
|
||||
|
|
|
@ -2315,6 +2315,12 @@ def managed(
|
|||
win_perms_reset=False,
|
||||
verify_ssl=True,
|
||||
use_etag=False,
|
||||
signature=None,
|
||||
source_hash_sig=None,
|
||||
signed_by_any=None,
|
||||
signed_by_all=None,
|
||||
keyring=None,
|
||||
gnupghome=None,
|
||||
**kwargs,
|
||||
):
|
||||
r"""
|
||||
|
@ -2911,6 +2917,54 @@ def managed(
|
|||
the ``source_hash`` parameter.
|
||||
|
||||
.. versionadded:: 3005
|
||||
|
||||
signature
|
||||
Ensure a valid GPG signature exists on the selected ``source`` file.
|
||||
Set this to true for inline signatures, or to a file URI retrievable by
|
||||
``cp.cache_file`` for a detached one.
|
||||
|
||||
.. note::
|
||||
|
||||
This signature will be enforced regardless of source type and will be
|
||||
required on the final output, therefore this does not lend itself well
|
||||
when templates are rendered.
|
||||
The file will not be modified, meaning inline signatures are not
|
||||
removed.
|
||||
|
||||
.. versionadded:: 3007
|
||||
|
||||
source_hash_sig
|
||||
When ``source_hash`` is a file and ``skip_verify`` is not true and ``use_etag``
|
||||
is not true, ensure a valid GPG signature exists on the source hash file.
|
||||
Set this to ``true`` for an inline (clearsigned) signature, or to a file URI
|
||||
retrievable by ``cp.cache_file`` for a detached one. The cached file
|
||||
will be deleted if the signature verification fails.
|
||||
|
||||
.. versionadded:: 3007
|
||||
|
||||
signed_by_any
|
||||
When verifying signatures either on the managed file or its source hash file,
|
||||
require at least one valid signature from one of a list of key fingerprints.
|
||||
This is passed to ``gpg.verify``.
|
||||
|
||||
.. versionadded:: 3007
|
||||
|
||||
signed_by_all
|
||||
When verifying signatures either on the managed file or its source hash file,
|
||||
require a valid signature from each of the key fingerprints in this list.
|
||||
This is passed to ``gpg.verify``.
|
||||
|
||||
.. versionadded:: 3007
|
||||
|
||||
keyring
|
||||
When verifying signatures, use this keyring.
|
||||
|
||||
.. versionadded:: 3007
|
||||
|
||||
gnupghome
|
||||
When verifying signatures, use this GnuPG home.
|
||||
|
||||
.. versionadded:: 3007
|
||||
"""
|
||||
if "env" in kwargs:
|
||||
# "env" is not supported; Use "saltenv".
|
||||
|
@ -2932,6 +2986,15 @@ def managed(
|
|||
if selinux is not None and not salt.utils.platform.is_linux():
|
||||
return _error(ret, "The 'selinux' option is only supported on Linux")
|
||||
|
||||
if signature or source_hash_sig:
|
||||
# Fail early in case the gpg module is not present
|
||||
try:
|
||||
__salt__["gpg.verify"]
|
||||
except KeyError:
|
||||
_error(
|
||||
ret, "Cannot verify signatures because the gpg module was not loaded"
|
||||
)
|
||||
|
||||
if selinux:
|
||||
seuser = selinux.get("seuser", None)
|
||||
serole = selinux.get("serole", None)
|
||||
|
@ -3220,6 +3283,11 @@ def managed(
|
|||
serange=serange,
|
||||
verify_ssl=verify_ssl,
|
||||
follow_symlinks=follow_symlinks,
|
||||
source_hash_sig=source_hash_sig,
|
||||
signed_by_any=signed_by_any,
|
||||
signed_by_all=signed_by_all,
|
||||
keyring=keyring,
|
||||
gnupghome=gnupghome,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
|
@ -3283,6 +3351,11 @@ def managed(
|
|||
skip_verify,
|
||||
verify_ssl=verify_ssl,
|
||||
use_etag=use_etag,
|
||||
source_hash_sig=source_hash_sig,
|
||||
signed_by_any=signed_by_any,
|
||||
signed_by_all=signed_by_all,
|
||||
keyring=keyring,
|
||||
gnupghome=gnupghome,
|
||||
**kwargs,
|
||||
)
|
||||
except Exception as exc: # pylint: disable=broad-except
|
||||
|
@ -3338,6 +3411,11 @@ def managed(
|
|||
setype=setype,
|
||||
serange=serange,
|
||||
use_etag=use_etag,
|
||||
signature=signature,
|
||||
signed_by_any=signed_by_any,
|
||||
signed_by_all=signed_by_all,
|
||||
keyring=keyring,
|
||||
gnupghome=gnupghome,
|
||||
**kwargs,
|
||||
)
|
||||
except Exception as exc: # pylint: disable=broad-except
|
||||
|
@ -3417,6 +3495,11 @@ def managed(
|
|||
setype=setype,
|
||||
serange=serange,
|
||||
use_etag=use_etag,
|
||||
signature=signature,
|
||||
signed_by_any=signed_by_any,
|
||||
signed_by_all=signed_by_all,
|
||||
keyring=keyring,
|
||||
gnupghome=gnupghome,
|
||||
**kwargs,
|
||||
)
|
||||
except Exception as exc: # pylint: disable=broad-except
|
||||
|
@ -8939,6 +9022,11 @@ def cached(
|
|||
skip_verify=False,
|
||||
saltenv="base",
|
||||
use_etag=False,
|
||||
source_hash_sig=None,
|
||||
signed_by_any=None,
|
||||
signed_by_all=None,
|
||||
keyring=None,
|
||||
gnupghome=None,
|
||||
):
|
||||
"""
|
||||
.. versionadded:: 2017.7.3
|
||||
|
@ -8996,6 +9084,38 @@ def cached(
|
|||
|
||||
.. versionadded:: 3005
|
||||
|
||||
source_hash_sig
|
||||
When ``source_hash`` is a file and ``skip_verify`` is not true and ``use_etag``
|
||||
is not true, ensure a valid GPG signature exists on the source hash file.
|
||||
Set this to ``true`` for an inline (clearsigned) signature, or to a file URI
|
||||
retrievable by ``cp.cache_file`` for a detached one. The cached file
|
||||
will be deleted if the signature verification fails.
|
||||
|
||||
.. versionadded:: 3007
|
||||
|
||||
signed_by_any
|
||||
When verifying signatures either on the managed file or its source hash file,
|
||||
require at least one valid signature from one of a list of key fingerprints.
|
||||
This is passed to ``gpg.verify``.
|
||||
|
||||
.. versionadded:: 3007
|
||||
|
||||
signed_by_all
|
||||
When verifying signatures either on the managed file or its source hash file,
|
||||
require a valid signature from each of the key fingerprints in this list.
|
||||
This is passed to ``gpg.verify``.
|
||||
|
||||
.. versionadded:: 3007
|
||||
|
||||
keyring
|
||||
When verifying signatures, use this keyring.
|
||||
|
||||
.. versionadded:: 3007
|
||||
|
||||
gnupghome
|
||||
When verifying signatures, use this GnuPG home.
|
||||
|
||||
.. versionadded:: 3007
|
||||
|
||||
This state will in most cases not be useful in SLS files, but it is useful
|
||||
when writing a state or remote-execution module that needs to make sure
|
||||
|
@ -9062,6 +9182,11 @@ def cached(
|
|||
source_hash=source_hash,
|
||||
source_hash_name=source_hash_name,
|
||||
saltenv=saltenv,
|
||||
source_hash_sig=source_hash_sig,
|
||||
signed_by_any=signed_by_any,
|
||||
signed_by_all=signed_by_all,
|
||||
keyring=keyring,
|
||||
gnupghome=gnupghome,
|
||||
)
|
||||
except CommandExecutionError as exc:
|
||||
ret["comment"] = exc.strerror
|
||||
|
|
1
tests/integration/files/file/base/custom.tar.gz.SHA256
Normal file
1
tests/integration/files/file/base/custom.tar.gz.SHA256
Normal file
|
@ -0,0 +1 @@
|
|||
9591159d86f0a180e4e0645b2320d0235e23e66c66797df61508bf185e0ac1d2 custom.tar.gz
|
|
@ -0,0 +1,8 @@
|
|||
-----BEGIN PGP SIGNATURE-----
|
||||
|
||||
iLMEAAEIAB0WIQT4zMjLXh2IaNqlhZqx+apXxJfnHAUCY9vQUwAKCRCx+apXxJfn
|
||||
HPoFA/9NbyrFi+PtSXFsVqetAc4iLQVjgYoaq2UnJNkYg9peD+lvz0qdYpWVh/4E
|
||||
z5Etr3ggs+2Ff5JEpMDBopyTxsE24Dfrk64JML5s+l9VFnbOgH3QBYzDtxHqBmG6
|
||||
CymTEWsFkdsWSzzBoVkym28FEJPH7q/grAtjDS9FZqqBsD4mfg==
|
||||
=/ffy
|
||||
-----END PGP SIGNATURE-----
|
|
@ -0,0 +1,12 @@
|
|||
-----BEGIN PGP SIGNED MESSAGE-----
|
||||
Hash: SHA256
|
||||
|
||||
9591159d86f0a180e4e0645b2320d0235e23e66c66797df61508bf185e0ac1d2 custom.tar.gz
|
||||
-----BEGIN PGP SIGNATURE-----
|
||||
|
||||
iLMEAQEIAB0WIQT4zMjLXh2IaNqlhZqx+apXxJfnHAUCY9vQKwAKCRCx+apXxJfn
|
||||
HKmyBADACOiaroBrJKmqy0vqrySy7iTiMdTAv75YheaXLeFgUrdktfWDzNU1kFkB
|
||||
VTZRyX+og8yi7601ls1jpuDHyBz2sT93zOWvw2iIdYBRKSenh+fHS0aAeRN1Zc9b
|
||||
DzivTpN6CBVJFpBBxF8Ro/gob9Pek1+rqVFt12azCdZH/hlsOw==
|
||||
=9QTd
|
||||
-----END PGP SIGNATURE-----
|
8
tests/integration/files/file/base/custom.tar.gz.asc
Normal file
8
tests/integration/files/file/base/custom.tar.gz.asc
Normal file
|
@ -0,0 +1,8 @@
|
|||
-----BEGIN PGP SIGNATURE-----
|
||||
|
||||
iLMEAAEIAB0WIQT4zMjLXh2IaNqlhZqx+apXxJfnHAUCY9vP4wAKCRCx+apXxJfn
|
||||
HCOLA/sEH71Nph/3HVcmXfey9Fv58ekoJ16eKyfiYziBLH/mpwsdE9m3Hp7VHKlb
|
||||
I0mnwsbmQnOGytsSLeepa5kwUOXw15z523N2egMXdcxyv3Vn0RRVwFw3MCd7pWRr
|
||||
p8xmp2RLGuQjzHA+ymVmFiEfna2l3EknISHktMXv46jCWLL/HQ==
|
||||
=mJSJ
|
||||
-----END PGP SIGNATURE-----
|
1
tests/integration/files/file/base/grail/scene33.SHA256
Normal file
1
tests/integration/files/file/base/grail/scene33.SHA256
Normal file
|
@ -0,0 +1 @@
|
|||
2689de4b07720aafe8f709619f142021ae8dbdd2742a3c86701cd794f97f506c scene33
|
17
tests/integration/files/file/base/grail/scene33.SHA256.asc
Normal file
17
tests/integration/files/file/base/grail/scene33.SHA256.asc
Normal file
|
@ -0,0 +1,17 @@
|
|||
-----BEGIN PGP SIGNATURE-----
|
||||
|
||||
iLMEAAEIAB0WIQT4zMjLXh2IaNqlhZqx+apXxJfnHAUCY9p1FwAKCRCx+apXxJfn
|
||||
HBb4BACEdEmETIy6bo16qS+vh8U4WC0V6/toslO5dokBpKAaD2Xg1+mtyaUhmXTu
|
||||
e6OCxqMGxVbUgmOpo4r4TX+FeqASQVNB4Kk9urwUuSa1FKZTngm+bKGnFBLbJKjm
|
||||
SKZBmmvtc4iIUWZtucLJWgzbD2bv/fcEI8A/8euSrfM1ArWQHw==
|
||||
=BsFN
|
||||
-----END PGP SIGNATURE-----
|
||||
|
||||
-----BEGIN PGP SIGNATURE-----
|
||||
|
||||
iLMEAAEIAB0WIQSN8J3lSrZ/YDHXHW8h5Z/XBbOHgQUCY9p1KAAKCRAh5Z/XBbOH
|
||||
gaoTA/0V75HNnbA/+nuaw7pBJr1EBUh74+qAb1QQvqNrNdJhgZGLlEwz43kPopUr
|
||||
MxFVBaz82lQ4nxaZXT/06trjNqnaacLcvRD67iwCPTBO3UR5AEfjZlP1ahAkCRQD
|
||||
DF6gLHkm00u7hXEBH3rfp5lkWt0sdHLzuiQ6YVIAwOP412fpoQ==
|
||||
=2cF/
|
||||
-----END PGP SIGNATURE-----
|
|
@ -0,0 +1,12 @@
|
|||
-----BEGIN PGP SIGNED MESSAGE-----
|
||||
Hash: SHA256
|
||||
|
||||
2689de4b07720aafe8f709619f142021ae8dbdd2742a3c86701cd794f97f506c scene33
|
||||
-----BEGIN PGP SIGNATURE-----
|
||||
|
||||
iLMEAQEIAB0WIQT4zMjLXh2IaNqlhZqx+apXxJfnHAUCY9p0PAAKCRCx+apXxJfn
|
||||
HP9QBADbGuPV6iFeij6ArezoFkfp2CTUT/wX94KXeXuKGh/8o2wH3xPRx5cDj04E
|
||||
L+iU6N/MTAfVwAF+aJwRJt1pjSGWLYnUHLqxRDGRq5CTliWksLCjsV2MK0dAEU58
|
||||
952bfcFq50ECOYq316Fjg/0cizaVcqyqOtwy+2ggGloqWUjkEA==
|
||||
=LR1/
|
||||
-----END PGP SIGNATURE-----
|
17
tests/integration/files/file/base/grail/scene33.asc
Normal file
17
tests/integration/files/file/base/grail/scene33.asc
Normal file
|
@ -0,0 +1,17 @@
|
|||
-----BEGIN PGP SIGNATURE-----
|
||||
|
||||
iLMEAAEIAB0WIQT4zMjLXh2IaNqlhZqx+apXxJfnHAUCY9p0owAKCRCx+apXxJfn
|
||||
HJVmA/0QXQ8C/+XoUqzTh94+NQ8+hwaKz44LFKAAMfGAHUdKx6qGVa0rv9nIfRJT
|
||||
O+4XgKiqOeUxtF4sXL9nNu5zuPkW3+prfEum1hAC/owm6z0O1WFvO9oFN25n/Q64
|
||||
cvuWQEXMjJHIXF2IdDKame2+JK3KHn/Ng9bxjqdg4KmlGhhajA==
|
||||
=qQmG
|
||||
-----END PGP SIGNATURE-----
|
||||
|
||||
-----BEGIN PGP SIGNATURE-----
|
||||
|
||||
iLMEAAEIAB0WIQSN8J3lSrZ/YDHXHW8h5Z/XBbOHgQUCY9p02QAKCRAh5Z/XBbOH
|
||||
gRwQBAC7vGMkoDFImhwmXg98gGsaxpGY2SD7rkoN/uO1u4IWufviyDULHX2K6zxa
|
||||
bYPZpt6NPB385Vr3t7sEGu+gNapUmUrLPBFughaEdDvOkJhnQtq31kL4qoFIVGNs
|
||||
PI093+VRCQBG7cjpNMAeRRk4TlE3BtuFQwoIaKGEPhUC/VfThQ==
|
||||
=KaYP
|
||||
-----END PGP SIGNATURE-----
|
107
tests/integration/files/file/base/grail/scene33.clearsign.asc
Normal file
107
tests/integration/files/file/base/grail/scene33.clearsign.asc
Normal file
|
@ -0,0 +1,107 @@
|
|||
-----BEGIN PGP SIGNED MESSAGE-----
|
||||
Hash: SHA256
|
||||
|
||||
Scene 33
|
||||
|
||||
|
||||
[clop clop whinny]
|
||||
KNIGHT: They're nervous, sire.
|
||||
ARTHUR: Then we'd best leave them here and carry on on foot. Dis-mount!
|
||||
TIM: Behold the cave of Kyre Banorg!
|
||||
ARTHUR: Right! Keep me covered.
|
||||
KNIGHT: What with?
|
||||
ARTHUR: Just keep me covered.
|
||||
TIM: Too late!
|
||||
[chord]
|
||||
ARTHUR: What?
|
||||
TIM: There he is!
|
||||
ARTHUR: Where?
|
||||
TIM: There!
|
||||
ARTHUR: What, behind the rabbit?
|
||||
TIM: It is the {{ spam }}!
|
||||
ARTHUR: You silly sod! You got us all worked up!
|
||||
TIM: Well, that's no ordinary rabbit. That's the most foul, cruel,
|
||||
and bad-tempered rodent you ever set eyes on.
|
||||
ROBIN: You tit! I soiled my armor I was so scared!
|
||||
TIM: Look, that rabbit's got a vicious streak a mile wide, it's a
|
||||
killer!
|
||||
KNIGHT: Get stuffed!
|
||||
TIM: It'll do you a trick, mate!
|
||||
KNIGHT: Oh, yeah?
|
||||
ROBIN: You mangy Scot git!
|
||||
TIM: I'm warning you!
|
||||
ROBIN: What's he do, nibble your bum?
|
||||
TIM: He's got huge, sharp-- he can leap about-- look at the bones!
|
||||
ARTHUR: Go on, Boris. Chop his head off!
|
||||
BORIS: Right! Silly little bleeder. One rabbit stew comin' right up!
|
||||
TIM: Look!
|
||||
[squeak]
|
||||
BORIS: Aaaugh!
|
||||
[chord]
|
||||
ARTHUR: Jesus Christ!
|
||||
TIM: I warned you!
|
||||
ROBIN: I peed again!
|
||||
TIM: I warned you! But did you listen to me? Oh, no, you knew it all,
|
||||
didn't you? Oh, it's just a harmless little bunny, isn't it? Well,
|
||||
it's always the same, I always--
|
||||
ARTHUR: Oh, shut up!
|
||||
TIM: --But do they listen to me?--
|
||||
ARTHUR: Right!
|
||||
TIM: -Oh, no--
|
||||
KNIGHTS: Charge!
|
||||
[squeak squeak]
|
||||
KNIGHTS: Aaaaugh! Aaaugh! etc.
|
||||
KNIGHTS: Run away! Run away!
|
||||
TIM: Haw haw haw. Haw haw haw. Haw haw.
|
||||
ARTHUR: Right. How many did we lose?
|
||||
KNIGHT: Gawain.
|
||||
KNIGHT: Hector.
|
||||
ARTHUR: And Boris. That's five.
|
||||
GALAHAD: Three, sir.
|
||||
ARTHUR: Three. Three. And we'd better not risk another frontal
|
||||
assault, that rabbit's dynamite.
|
||||
ROBIN: Would it help to confuse it if we run away more?
|
||||
ARTHUR: Oh, shut up and go and change your armor.
|
||||
GALAHAD: Let us taunt it! It may become so cross that it will make
|
||||
a mistake.
|
||||
ARTHUR: Like what?
|
||||
GALAHAD: Well,....
|
||||
ARTHUR: Have we got bows?
|
||||
KNIGHT: No.
|
||||
LAUNCELOT: We have the Holy Hand Grenade.
|
||||
ARTHUR: Yes, of course! The Holy Hand Grenade of Antioch! 'Tis one
|
||||
of the sacred relics Brother Maynard carries with him! Brother Maynard!
|
||||
Bring up the Holy Hand Grenade!
|
||||
[singing]
|
||||
How does it, uh... how does it work?
|
||||
KNIGHT: I know not, my liege.
|
||||
ARTHUR: Consult the Book of Armaments!
|
||||
MAYNARD: Armaments, Chapter Two, Verses Nine to Twenty-One.
|
||||
BROTHER: "And Saint Atila raised the hand grenade up on high, saying,
|
||||
'Oh, Lord, bless this thy hand grenade that with it thou mayest blow
|
||||
thy enemies to tiny bits, in thy mercy.' And the Lord did grin, and
|
||||
people did feast upon the lambs, and sloths, and carp, and anchovies,
|
||||
and orangutans, and breakfast cereals, and fruit bats, and large --"
|
||||
MAYNARD: Skip a bit, Brother.
|
||||
BROTHER: "And the Lord spake, saying, 'First shalt thou take out the
|
||||
Holy Pin. Then, shalt thou count to three, no more, no less. Three
|
||||
shalt be the number thou shalt count, and the number of the counting
|
||||
shalt be three. Four shalt thou not count, nor either count thou two,
|
||||
excepting that thou then proceed to three. Five is right out. Once
|
||||
the number three, being the third number, be reached, then lobbest thou
|
||||
thy Holy Hand Grenade of Antioch towards thou foe, who being naughty
|
||||
in my sight, shall snuff it.'"
|
||||
MAYNARD: Amen.
|
||||
ALL: Amen.
|
||||
ARTHUR: Right! One... two... five!
|
||||
KNIGHT: Three, sir!
|
||||
ARTHUR: Three!
|
||||
[boom]
|
||||
-----BEGIN PGP SIGNATURE-----
|
||||
|
||||
iLMEAQEIAB0WIQT4zMjLXh2IaNqlhZqx+apXxJfnHAUCY9p0bAAKCRCx+apXxJfn
|
||||
HGt0BAC9HNKB8ticKt4xToPpiide8pa2L19msWJ9tbSp7INQiGFKU/bGBzf3Rg/j
|
||||
41BCL4L9dszcmpoa147pG3/A7UPyYzbA9fT3AvjX9Cc2OIzgmWwzSaccbZVFyctC
|
||||
Z9x6uIWrCY/AiSekoIWri4Xwi0u2TzTxRKaraHASxYzdcV111w==
|
||||
=0u4u
|
||||
-----END PGP SIGNATURE-----
|
|
@ -42,11 +42,21 @@ def grail_scene33_file(grail):
|
|||
return grail / "scene33"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def grail_scene33_clearsign_file(grail_scene33_file):
|
||||
return grail_scene33_file.with_suffix(".clearsign.asc")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def grail_scene33_file_hash(grail_scene33_file):
|
||||
return hashlib.sha256(grail_scene33_file.read_bytes()).hexdigest()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def grail_scene33_clearsign_file_hash(grail_scene33_clearsign_file):
|
||||
return hashlib.sha256(grail_scene33_clearsign_file.read_bytes()).hexdigest()
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def state_file_account():
|
||||
with pytest.helpers.create_account(create_group=True) as system_account:
|
||||
|
|
|
@ -3,13 +3,22 @@ import hashlib
|
|||
import os
|
||||
import shutil
|
||||
import stat
|
||||
import subprocess
|
||||
import types
|
||||
|
||||
import psutil
|
||||
import pytest
|
||||
|
||||
import salt.utils.files
|
||||
import salt.utils.platform
|
||||
|
||||
try:
|
||||
import gnupg as gnupglib
|
||||
|
||||
HAS_GNUPG = True
|
||||
except ImportError:
|
||||
HAS_GNUPG = False
|
||||
|
||||
pytestmark = [
|
||||
pytest.mark.windows_whitelisted,
|
||||
]
|
||||
|
@ -19,14 +28,125 @@ BINARY_FILE = b"GIF89a\x01\x00\x01\x00\x80\x00\x00\x05\x04\x04\x00\x00\x00,\x00\
|
|||
|
||||
|
||||
@pytest.fixture
|
||||
def remote_grail_scene33(webserver, grail_scene33_file, grail_scene33_file_hash):
|
||||
def remote_grail_scene33(
|
||||
webserver,
|
||||
grail_scene33_file,
|
||||
grail_scene33_file_hash,
|
||||
grail_scene33_clearsign_file,
|
||||
grail_scene33_clearsign_file_hash,
|
||||
):
|
||||
return types.SimpleNamespace(
|
||||
file=grail_scene33_file,
|
||||
file_clearsign=grail_scene33_clearsign_file,
|
||||
hash=grail_scene33_file_hash,
|
||||
hash_clearsign=grail_scene33_clearsign_file_hash,
|
||||
hash_file=grail_scene33_file.with_suffix(".SHA256"),
|
||||
url=webserver.url("grail/scene33"),
|
||||
url_hash=webserver.url("grail/scene33.SHA256"),
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def gpghome(tmp_path):
|
||||
root = tmp_path / "gpghome"
|
||||
root.mkdir(mode=0o0700)
|
||||
try:
|
||||
yield root
|
||||
finally:
|
||||
# Make sure we don't leave any gpg-agents running behind
|
||||
gpg_connect_agent = shutil.which("gpg-connect-agent")
|
||||
if gpg_connect_agent:
|
||||
gnupghome = root / ".gnupg"
|
||||
if not gnupghome.is_dir():
|
||||
gnupghome = root
|
||||
try:
|
||||
subprocess.run(
|
||||
[gpg_connect_agent, "killagent", "/bye"],
|
||||
env={"GNUPGHOME": str(gnupghome)},
|
||||
shell=False,
|
||||
check=True,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
)
|
||||
except subprocess.CalledProcessError:
|
||||
# This is likely CentOS 7 or Amazon Linux 2
|
||||
pass
|
||||
|
||||
# If the above errored or was not enough, as a last resort, let's check
|
||||
# the running processes.
|
||||
for proc in psutil.process_iter():
|
||||
try:
|
||||
if "gpg-agent" in proc.name():
|
||||
for arg in proc.cmdline():
|
||||
if str(root) in arg:
|
||||
proc.terminate()
|
||||
except Exception: # pylint: disable=broad-except
|
||||
pass
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def gnupg(gpghome):
|
||||
return gnupglib.GPG(gnupghome=str(gpghome))
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def a_pubkey():
|
||||
return """\
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
mI0EY9pxawEEAPpBbXxRYFUm6np5h746Nch7+OrbLdtBxP8x7VDOockr/x7drssb
|
||||
llVFuK4HmiJg+Nkyakn3XmVYBHY2yBIkN/MP+R1zRxiFmniKOTD15UuHSQaWZTqh
|
||||
qac6XrLZ20BiWl1fKweCz1wGUcMZaOBs0WVB0sIupqfS90Ub93VC/+oxABEBAAG0
|
||||
JlNhbHRTdGFjayBBIFRlc3QgPGF0ZXN0QHNhbHRzdGFjay5jb20+iNEEEwEIADsW
|
||||
IQT4zMjLXh2IaNqlhZqx+apXxJfnHAUCY9pxawIbAwULCQgHAgIiAgYVCgkICwIE
|
||||
FgIDAQIeBwIXgAAKCRCx+apXxJfnHFDhA/47t5yYdCcjxXu/1Kn9sQwI+aq/S3x9
|
||||
/ZKE+RodlryqA43BUT7N6JLQ5zJO6p+kRhMwCcVfBeDNJANqVi63HEDp8q3633BF
|
||||
q1Cbi3BG0ugBdCADIETYBwl/ytMSgYwRO8b4TkYCyhWuWAgliVF3ceX0AVsng8pF
|
||||
o6Vh4A3SqosQgA==
|
||||
=eHpb
|
||||
-----END PGP PUBLIC KEY BLOCK-----"""
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def a_fp():
|
||||
return "F8CCC8CB5E1D8868DAA5859AB1F9AA57C497E71C"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def b_pubkey():
|
||||
return """\
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
mI0EY9pxigEEANbHCh566IEbp9Ez1WE3oEi+XXyf7H3GDgrVc8v9COMexpAFkJa1
|
||||
gG+yCm4bOZ5vHAXbP2rGvlOEcao3y3evj2TWahg0+05CDugRjL0pO4JcMUBV1mBZ
|
||||
ynUGoQ5T+WtKilJ5k/JrSRpJW3y//46q0g5c470qVNn9ZX0YZW/b7DFXABEBAAG0
|
||||
JlNhbHRTdGFjayBCIFRlc3QgPGJ0ZXN0QHNhbHRzdGFjay5jb20+iNEEEwEIADsW
|
||||
IQSN8J3lSrZ/YDHXHW8h5Z/XBbOHgQUCY9pxigIbAwULCQgHAgIiAgYVCgkICwIE
|
||||
FgIDAQIeBwIXgAAKCRAh5Z/XBbOHgTCuA/9mYXAehM9avvq0Jm2dVbPidqxLstki
|
||||
tgo3gCWmO1b5dXEBrhOZ8pZAktQ3WWoRrbwpNA7NAEIDF5l6uwMLLbGPQ5jreOdP
|
||||
uzHpHONR1WWAzw2dj3v+5IcLDQ4sLi9VRgJqtMasTd8TpqMCVNcMArDBiy5hRF/e
|
||||
XWEkf19Nb8qrdg==
|
||||
=OEiT
|
||||
-----END PGP PUBLIC KEY BLOCK-----"""
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def b_fp():
|
||||
return "8DF09DE54AB67F6031D71D6F21E59FD705B38781"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def gpg_keys_present(gnupg, a_pubkey, b_pubkey, a_fp, b_fp):
|
||||
pubkeys = [a_pubkey, b_pubkey]
|
||||
fingerprints = [a_fp, b_fp]
|
||||
gnupg.import_keys("\n".join(pubkeys))
|
||||
present_keys = gnupg.list_keys()
|
||||
for fp in fingerprints:
|
||||
assert any(x["fingerprint"] == fp for x in present_keys)
|
||||
yield
|
||||
# cleanup is taken care of by gpghome and tmp_path
|
||||
|
||||
|
||||
def _format_ids(key, value):
|
||||
return "{}={}".format(key, value)
|
||||
|
||||
|
@ -801,6 +921,113 @@ def test_verify_ssl_https_source(file, tmp_path, ssl_webserver, verify_ssl):
|
|||
assert name.exists()
|
||||
|
||||
|
||||
@pytest.mark.skipif(HAS_GNUPG is False, reason="Needs python-gnupg library")
|
||||
@pytest.mark.usefixtures("gpg_keys_present")
|
||||
@pytest.mark.parametrize("signature", [True, ".asc"])
|
||||
def test_file_managed_signature(
|
||||
file, tmp_path, signature, remote_grail_scene33, gpghome
|
||||
):
|
||||
name = tmp_path / "test_file_managed_signature.txt"
|
||||
source = remote_grail_scene33.url
|
||||
if signature is True:
|
||||
source += ".clearsign.asc"
|
||||
contents_file = remote_grail_scene33.file_clearsign
|
||||
source_hash = remote_grail_scene33.hash_clearsign
|
||||
else:
|
||||
signature = source + signature
|
||||
contents_file = remote_grail_scene33.file
|
||||
source_hash = remote_grail_scene33.hash
|
||||
ret = file.managed(
|
||||
str(name),
|
||||
source=source,
|
||||
source_hash=source_hash,
|
||||
signature=signature,
|
||||
gnupghome=str(gpghome),
|
||||
)
|
||||
assert ret.result is True
|
||||
assert ret.changes
|
||||
assert name.exists()
|
||||
assert name.read_text() == contents_file.read_text()
|
||||
|
||||
|
||||
@pytest.mark.skipif(HAS_GNUPG is False, reason="Needs python-gnupg library")
|
||||
@pytest.mark.usefixtures("gpg_keys_present")
|
||||
def test_file_managed_signature_fail(
|
||||
file, tmp_path, remote_grail_scene33, gpghome, modules
|
||||
):
|
||||
name = tmp_path / "test_file_managed_signature_fail.txt"
|
||||
source = remote_grail_scene33.url
|
||||
signature = source + ".asc"
|
||||
source_hash = remote_grail_scene33.hash
|
||||
# although there are valid signatures, this will be denied since the one below is required
|
||||
signed_by_all = ["DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF"]
|
||||
ret = file.managed(
|
||||
str(name),
|
||||
source=source,
|
||||
source_hash=source_hash,
|
||||
signature=signature,
|
||||
gnupghome=str(gpghome),
|
||||
signed_by_all=signed_by_all,
|
||||
)
|
||||
assert ret.result is False
|
||||
assert not ret.changes
|
||||
assert not name.exists()
|
||||
# Ensure that a new state run will attempt to redownload the source
|
||||
# instead of verifying the invalid signature again
|
||||
assert not modules.cp.is_cached(source)
|
||||
assert not modules.cp.is_cached(signature)
|
||||
|
||||
|
||||
@pytest.mark.skipif(HAS_GNUPG is False, reason="Needs python-gnupg library")
|
||||
@pytest.mark.usefixtures("gpg_keys_present")
|
||||
@pytest.mark.parametrize("sig", [True, ".asc"])
|
||||
def test_file_managed_source_hash_sig(
|
||||
file, tmp_path, sig, remote_grail_scene33, gpghome
|
||||
):
|
||||
name = tmp_path / "test_file_managed_source_hash_sig.txt"
|
||||
source = remote_grail_scene33.url
|
||||
source_hash = remote_grail_scene33.url_hash
|
||||
contents_file = remote_grail_scene33.file
|
||||
if sig is True:
|
||||
source_hash += ".clearsign.asc"
|
||||
else:
|
||||
sig = source_hash + sig
|
||||
ret = file.managed(
|
||||
str(name),
|
||||
source=source,
|
||||
source_hash=source_hash,
|
||||
source_hash_sig=sig,
|
||||
gnupghome=str(gpghome),
|
||||
)
|
||||
assert ret.result is True
|
||||
assert ret.changes
|
||||
assert name.exists()
|
||||
assert name.read_text() == contents_file.read_text()
|
||||
|
||||
|
||||
@pytest.mark.skipif(HAS_GNUPG is False, reason="Needs python-gnupg library")
|
||||
@pytest.mark.usefixtures("gpg_keys_present")
|
||||
def test_file_managed_source_hash_sig_fail(
|
||||
file, tmp_path, remote_grail_scene33, gpghome
|
||||
):
|
||||
name = tmp_path / "test_file_managed_source_hash_sig.txt"
|
||||
source = remote_grail_scene33.url
|
||||
source_hash = remote_grail_scene33.url_hash
|
||||
sig = source_hash + ".asc"
|
||||
signed_by_all = ["DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF"]
|
||||
ret = file.managed(
|
||||
str(name),
|
||||
source=source,
|
||||
source_hash=source_hash,
|
||||
source_hash_sig=sig,
|
||||
gnupghome=str(gpghome),
|
||||
signed_by_all=signed_by_all,
|
||||
)
|
||||
assert ret.result is False
|
||||
assert not ret.changes
|
||||
assert not name.exists()
|
||||
|
||||
|
||||
def test_issue_60203(
|
||||
file,
|
||||
tmp_path,
|
||||
|
|
|
@ -6,12 +6,23 @@ import os
|
|||
import random
|
||||
import shutil
|
||||
import socket
|
||||
import subprocess
|
||||
import sys
|
||||
from contextlib import closing
|
||||
from pathlib import Path
|
||||
|
||||
import psutil
|
||||
import pytest
|
||||
|
||||
import salt.utils.files
|
||||
from tests.support.runtests import RUNTIME_VARS
|
||||
|
||||
try:
|
||||
import gnupg as gnupglib
|
||||
|
||||
HAS_GNUPG = True
|
||||
except ImportError:
|
||||
HAS_GNUPG = False
|
||||
|
||||
|
||||
class TestRequestHandler(http.server.SimpleHTTPRequestHandler):
|
||||
|
@ -224,3 +235,222 @@ def test_archive_extracted_web_source_etag_operation(
|
|||
|
||||
# The modified time of the cached file now changes
|
||||
assert cached_file_mtime != os.path.getmtime(cached_file)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def gpghome(tmp_path):
|
||||
root = tmp_path / "gpghome"
|
||||
root.mkdir(mode=0o0700)
|
||||
try:
|
||||
yield root
|
||||
finally:
|
||||
# Make sure we don't leave any gpg-agents running behind
|
||||
gpg_connect_agent = shutil.which("gpg-connect-agent")
|
||||
if gpg_connect_agent:
|
||||
gnupghome = root / ".gnupg"
|
||||
if not gnupghome.is_dir():
|
||||
gnupghome = root
|
||||
try:
|
||||
subprocess.run(
|
||||
[gpg_connect_agent, "killagent", "/bye"],
|
||||
env={"GNUPGHOME": str(gnupghome)},
|
||||
shell=False,
|
||||
check=True,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
)
|
||||
except subprocess.CalledProcessError:
|
||||
# This is likely CentOS 7 or Amazon Linux 2
|
||||
pass
|
||||
|
||||
# If the above errored or was not enough, as a last resort, let's check
|
||||
# the running processes.
|
||||
for proc in psutil.process_iter():
|
||||
try:
|
||||
if "gpg-agent" in proc.name():
|
||||
for arg in proc.cmdline():
|
||||
if str(root) in arg:
|
||||
proc.terminate()
|
||||
except Exception: # pylint: disable=broad-except
|
||||
pass
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def gnupg(gpghome):
|
||||
return gnupglib.GPG(gnupghome=str(gpghome))
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def a_pubkey():
|
||||
return """\
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
mI0EY9pxawEEAPpBbXxRYFUm6np5h746Nch7+OrbLdtBxP8x7VDOockr/x7drssb
|
||||
llVFuK4HmiJg+Nkyakn3XmVYBHY2yBIkN/MP+R1zRxiFmniKOTD15UuHSQaWZTqh
|
||||
qac6XrLZ20BiWl1fKweCz1wGUcMZaOBs0WVB0sIupqfS90Ub93VC/+oxABEBAAG0
|
||||
JlNhbHRTdGFjayBBIFRlc3QgPGF0ZXN0QHNhbHRzdGFjay5jb20+iNEEEwEIADsW
|
||||
IQT4zMjLXh2IaNqlhZqx+apXxJfnHAUCY9pxawIbAwULCQgHAgIiAgYVCgkICwIE
|
||||
FgIDAQIeBwIXgAAKCRCx+apXxJfnHFDhA/47t5yYdCcjxXu/1Kn9sQwI+aq/S3x9
|
||||
/ZKE+RodlryqA43BUT7N6JLQ5zJO6p+kRhMwCcVfBeDNJANqVi63HEDp8q3633BF
|
||||
q1Cbi3BG0ugBdCADIETYBwl/ytMSgYwRO8b4TkYCyhWuWAgliVF3ceX0AVsng8pF
|
||||
o6Vh4A3SqosQgA==
|
||||
=eHpb
|
||||
-----END PGP PUBLIC KEY BLOCK-----"""
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def a_fp():
|
||||
return "F8CCC8CB5E1D8868DAA5859AB1F9AA57C497E71C"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def b_pubkey():
|
||||
return """\
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
mI0EY9pxigEEANbHCh566IEbp9Ez1WE3oEi+XXyf7H3GDgrVc8v9COMexpAFkJa1
|
||||
gG+yCm4bOZ5vHAXbP2rGvlOEcao3y3evj2TWahg0+05CDugRjL0pO4JcMUBV1mBZ
|
||||
ynUGoQ5T+WtKilJ5k/JrSRpJW3y//46q0g5c470qVNn9ZX0YZW/b7DFXABEBAAG0
|
||||
JlNhbHRTdGFjayBCIFRlc3QgPGJ0ZXN0QHNhbHRzdGFjay5jb20+iNEEEwEIADsW
|
||||
IQSN8J3lSrZ/YDHXHW8h5Z/XBbOHgQUCY9pxigIbAwULCQgHAgIiAgYVCgkICwIE
|
||||
FgIDAQIeBwIXgAAKCRAh5Z/XBbOHgTCuA/9mYXAehM9avvq0Jm2dVbPidqxLstki
|
||||
tgo3gCWmO1b5dXEBrhOZ8pZAktQ3WWoRrbwpNA7NAEIDF5l6uwMLLbGPQ5jreOdP
|
||||
uzHpHONR1WWAzw2dj3v+5IcLDQ4sLi9VRgJqtMasTd8TpqMCVNcMArDBiy5hRF/e
|
||||
XWEkf19Nb8qrdg==
|
||||
=OEiT
|
||||
-----END PGP PUBLIC KEY BLOCK-----"""
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def b_fp():
|
||||
return "8DF09DE54AB67F6031D71D6F21E59FD705B38781"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def gpg_keys_present(gnupg, a_pubkey, b_pubkey, a_fp, b_fp):
|
||||
pubkeys = [a_pubkey, b_pubkey]
|
||||
fingerprints = [a_fp, b_fp]
|
||||
gnupg.import_keys("\n".join(pubkeys))
|
||||
present_keys = gnupg.list_keys()
|
||||
for fp in fingerprints:
|
||||
assert any(x["fingerprint"] == fp for x in present_keys)
|
||||
yield
|
||||
# cleanup is taken care of by gpghome and tmp_path
|
||||
|
||||
|
||||
@pytest.fixture(scope="module", autouse=True)
|
||||
def sig_files_present(web_root, modules):
|
||||
base = Path(RUNTIME_VARS.BASE_FILES)
|
||||
for file in [
|
||||
"custom.tar.gz",
|
||||
"custom.tar.gz.asc",
|
||||
"custom.tar.gz.SHA256",
|
||||
"custom.tar.gz.SHA256.clearsign.asc",
|
||||
"custom.tar.gz.SHA256.asc",
|
||||
]:
|
||||
modules.file.copy(base / file, Path(web_root) / file)
|
||||
|
||||
|
||||
@pytest.mark.skipif(HAS_GNUPG is False, reason="Needs python-gnupg library")
|
||||
@pytest.mark.usefixtures("gpg_keys_present")
|
||||
def test_archive_extracted_signature(tmp_path, gpghome, free_port, modules, states):
|
||||
name = tmp_path / "test_archive_extracted_signature"
|
||||
source = f"http://localhost:{free_port}/custom.tar.gz"
|
||||
signature = source + ".asc"
|
||||
source_hash = source + ".SHA256"
|
||||
ret = states.archive.extracted(
|
||||
str(name),
|
||||
source=source,
|
||||
source_hash=source_hash,
|
||||
archive_format="tar",
|
||||
options="z",
|
||||
signature=signature,
|
||||
gnupghome=str(gpghome),
|
||||
)
|
||||
assert ret.result is True
|
||||
assert ret.changes
|
||||
assert name.exists()
|
||||
assert modules.file.find(str(name))
|
||||
|
||||
|
||||
@pytest.mark.skipif(HAS_GNUPG is False, reason="Needs python-gnupg library")
|
||||
@pytest.mark.usefixtures("gpg_keys_present")
|
||||
def test_archive_extracted_signature_fail(
|
||||
tmp_path, gpghome, free_port, modules, states
|
||||
):
|
||||
name = tmp_path / "test_archive_extracted_signature_fail"
|
||||
source = f"http://localhost:{free_port}/custom.tar.gz"
|
||||
signature = source + ".asc"
|
||||
source_hash = source + ".SHA256"
|
||||
# although there are valid signatures, this will be denied since the one below is required
|
||||
signed_by_all = ["DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF"]
|
||||
ret = states.archive.extracted(
|
||||
str(name),
|
||||
source=source,
|
||||
source_hash=source_hash,
|
||||
archive_format="tar",
|
||||
options="z",
|
||||
signature=signature,
|
||||
signed_by_all=signed_by_all,
|
||||
gnupghome=str(gpghome),
|
||||
)
|
||||
assert ret.result is False
|
||||
assert not ret.changes
|
||||
assert not name.exists()
|
||||
assert not modules.cp.is_cached(source)
|
||||
assert not modules.cp.is_cached(signature)
|
||||
|
||||
|
||||
@pytest.mark.skipif(HAS_GNUPG is False, reason="Needs python-gnupg library")
|
||||
@pytest.mark.usefixtures("gpg_keys_present")
|
||||
@pytest.mark.parametrize("sig", [True, ".asc"])
|
||||
def test_archive_extracted_source_hash_sig(
|
||||
tmp_path, sig, gpghome, free_port, modules, states
|
||||
):
|
||||
name = tmp_path / "test_archive_extracted_source_hash_sig"
|
||||
source = f"http://localhost:{free_port}/custom.tar.gz"
|
||||
source_hash = source + ".SHA256"
|
||||
if sig is True:
|
||||
source_hash += ".clearsign.asc"
|
||||
else:
|
||||
sig = source_hash + sig
|
||||
ret = states.archive.extracted(
|
||||
str(name),
|
||||
source=source,
|
||||
source_hash=source_hash,
|
||||
archive_format="tar",
|
||||
options="z",
|
||||
source_hash_sig=sig,
|
||||
gnupghome=str(gpghome),
|
||||
)
|
||||
assert ret.result is True
|
||||
assert ret.changes
|
||||
assert name.exists()
|
||||
assert modules.file.find(str(name))
|
||||
|
||||
|
||||
@pytest.mark.skipif(HAS_GNUPG is False, reason="Needs python-gnupg library")
|
||||
@pytest.mark.usefixtures("gpg_keys_present")
|
||||
@pytest.mark.parametrize("sig", [True, ".asc"])
|
||||
def test_archive_extracted_source_hash_sig_fail(
|
||||
tmp_path, sig, gpghome, free_port, modules, states
|
||||
):
|
||||
name = tmp_path / "test_archive_extracted_source_hash_sig_fail"
|
||||
source = f"http://localhost:{free_port}/custom.tar.gz"
|
||||
source_hash = source + ".SHA256.clearsign.asc"
|
||||
signed_by_any = ["DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF"]
|
||||
ret = states.archive.extracted(
|
||||
str(name),
|
||||
source=source,
|
||||
source_hash=source_hash,
|
||||
archive_format="tar",
|
||||
options="z",
|
||||
source_hash_sig=True,
|
||||
signed_by_any=signed_by_any,
|
||||
gnupghome=str(gpghome),
|
||||
)
|
||||
assert ret.result is False
|
||||
assert not ret.changes
|
||||
assert not name.exists()
|
||||
assert not modules.cp.is_cached(source)
|
||||
assert not modules.cp.is_cached(source_hash)
|
||||
|
|
Loading…
Add table
Reference in a new issue