mirror of
https://github.com/ansible/awx.git
synced 2026-05-23 08:37:48 -02:30
Update action plugin to use ansible-sign library
Signed-off-by: Rick Elrod <rick@elrod.me>
This commit is contained in:
@@ -631,8 +631,10 @@ class ProjectUpdate(UnifiedJob, ProjectOptions, JobNotificationMixin, TaskManage
|
|||||||
added_update_fields = []
|
added_update_fields = []
|
||||||
if not self.job_tags:
|
if not self.job_tags:
|
||||||
job_tags = ['update_{}'.format(self.scm_type), 'install_roles', 'install_collections']
|
job_tags = ['update_{}'.format(self.scm_type), 'install_roles', 'install_collections']
|
||||||
if self.project.signature_validation:
|
if self.project.signature_validation_credential is not None:
|
||||||
job_tags.append('playbook_integrity')
|
credential_type = self.project.signature_validation_credential.credential_type.namespace
|
||||||
|
job_tags.append(f'validation_{credential_type}')
|
||||||
|
job_tags.append('validation_checksum_manifest')
|
||||||
self.job_tags = ','.join(job_tags)
|
self.job_tags = ','.join(job_tags)
|
||||||
added_update_fields.append('job_tags')
|
added_update_fields.append('job_tags')
|
||||||
if self.scm_delete_on_update and 'delete' not in self.job_tags and self.job_type == 'check':
|
if self.scm_delete_on_update and 'delete' not in self.job_tags and self.job_type == 'check':
|
||||||
|
|||||||
@@ -2,391 +2,114 @@ from __future__ import absolute_import, division, print_function
|
|||||||
|
|
||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
import base64
|
|
||||||
import git
|
|
||||||
import gnupg
|
import gnupg
|
||||||
import hashlib
|
|
||||||
import os
|
import os
|
||||||
import tempfile
|
import tempfile
|
||||||
import traceback
|
|
||||||
from ansible.module_utils.basic import *
|
from ansible.module_utils.basic import *
|
||||||
from ansible.plugins.action import ActionBase
|
from ansible.plugins.action import ActionBase
|
||||||
from ansible.utils.display import Display
|
from ansible.utils.display import Display
|
||||||
|
|
||||||
|
from ansible_sign.checksum import (
|
||||||
|
ChecksumFile,
|
||||||
|
ChecksumMismatch,
|
||||||
|
InvalidChecksumLine,
|
||||||
|
)
|
||||||
|
from ansible_sign.checksum.differ import DistlibManifestChecksumFileExistenceDiffer
|
||||||
|
from ansible_sign.signing import *
|
||||||
|
|
||||||
display = Display()
|
display = Display()
|
||||||
|
|
||||||
|
|
||||||
|
VALIDATION_TYPES = (
|
||||||
|
"checksum_manifest",
|
||||||
|
"gpg",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ActionModule(ActionBase):
|
class ActionModule(ActionBase):
|
||||||
def run(self, tmp=None, task_vars=None):
|
def run(self, tmp=None, task_vars=None):
|
||||||
self._supports_check_mode = False
|
self._supports_check_mode = False
|
||||||
|
|
||||||
result = super(ActionModule, self).run(tmp, task_vars)
|
super(ActionModule, self).run(tmp, task_vars)
|
||||||
|
|
||||||
params = self._task.args
|
self.params = self._task.args
|
||||||
|
|
||||||
scm_type = params.get("scm_type")
|
self.project_path = self.params.get("project_path")
|
||||||
if scm_type is None:
|
if self.project_path is None:
|
||||||
return {
|
|
||||||
"failed": True,
|
|
||||||
"msg": "scm_type not given, needed for checksum verification.",
|
|
||||||
}
|
|
||||||
|
|
||||||
differ_cls = SCM_FILE_EXISTENCE_CHECKER_MAP.get(scm_type)
|
|
||||||
if differ_cls is None:
|
|
||||||
return {
|
|
||||||
"failed": True,
|
|
||||||
"msg": "scm_type given is not supported.",
|
|
||||||
}
|
|
||||||
|
|
||||||
project_path = params.get("project_path")
|
|
||||||
if project_path is None:
|
|
||||||
return {
|
return {
|
||||||
"failed": True,
|
"failed": True,
|
||||||
"msg": "No project path (project_path) was supplied.",
|
"msg": "No project path (project_path) was supplied.",
|
||||||
}
|
}
|
||||||
|
|
||||||
sig_type = params.get("sig_type")
|
validation_type = self.params.get("validation_type")
|
||||||
if sig_type is None:
|
if validation_type is None or validation_type not in VALIDATION_TYPES:
|
||||||
return {
|
return {"failed": True, "msg": "validation_type must be one of: " + ', '.join(VALIDATION_TYPES)}
|
||||||
"failed": True,
|
validation_method = getattr(self, f"validate_{validation_type}")
|
||||||
"msg": "No signature type (sig_type) was supplied. Try setting it to 'gpg'?",
|
return validation_method()
|
||||||
}
|
|
||||||
|
|
||||||
sig_verifier_cls = SIGNATURE_VALIDATION_MAP.get(sig_type)
|
def validate_gpg(self):
|
||||||
if sig_verifier_cls is None:
|
gpg_pubkey = self.params.get("gpg_pubkey")
|
||||||
return {
|
if gpg_pubkey is None:
|
||||||
"failed": True,
|
|
||||||
"msg": f"sig_type given is not supported, must be one of: {', '.join(SIGNATURE_VALIDATION_MAP.keys())}",
|
|
||||||
}
|
|
||||||
|
|
||||||
# NOTE: We currently do checksum validation before we do signature
|
|
||||||
# verification. We should probably flip them because the former takes
|
|
||||||
# more work, and if the signature check fails, there's no sense
|
|
||||||
# in doing that extra work.
|
|
||||||
|
|
||||||
# The validator will assume that the directory containing the checksum
|
|
||||||
# file is the root of the project.
|
|
||||||
checksum_path = os.path.join(project_path, "sha256sum.txt")
|
|
||||||
validator = ChecksumFileValidator.from_path(checksum_path, differ=differ_cls)
|
|
||||||
|
|
||||||
# Do checksum validation
|
|
||||||
try:
|
|
||||||
validator.verify()
|
|
||||||
except InvalidChecksumLine as e:
|
|
||||||
return {
|
|
||||||
"failed": True,
|
|
||||||
"msg": f"Checksum file contained a line that could not be parsed: {e}",
|
|
||||||
}
|
|
||||||
except ChecksumMismatch as e:
|
|
||||||
return {
|
|
||||||
"failed": True,
|
|
||||||
"msg": f"Checksum validation failed: {e}",
|
|
||||||
}
|
|
||||||
|
|
||||||
# Do signature validation
|
|
||||||
verifier = sig_verifier_cls(params)
|
|
||||||
verification = verifier.verify()
|
|
||||||
result.update(verification)
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
class ContentVerifier:
|
|
||||||
'''
|
|
||||||
Represents a way of performing content verification. It doesn't make any
|
|
||||||
assumptions about the kind of verification being done. The constructor takes
|
|
||||||
raw parameters from the action, and subclasses can make use of those as
|
|
||||||
needed to perform whatever kind of verification they want to.
|
|
||||||
'''
|
|
||||||
|
|
||||||
def __init__(self, params):
|
|
||||||
self.params = params
|
|
||||||
|
|
||||||
def verify(self):
|
|
||||||
'''
|
|
||||||
Does the actual verification.
|
|
||||||
'''
|
|
||||||
raise NotImplementedError("verify")
|
|
||||||
|
|
||||||
|
|
||||||
class GPGVerifier(ContentVerifier):
|
|
||||||
def __init__(self, params):
|
|
||||||
super(GPGVerifier, self).__init__(params)
|
|
||||||
self.pubkey = params.get("gpg_pubkey")
|
|
||||||
|
|
||||||
# We know this key exists, because we check for it before we do checksum
|
|
||||||
# validation.
|
|
||||||
self.project_path = params.get("project_path")
|
|
||||||
|
|
||||||
def verify(self):
|
|
||||||
if self.pubkey is None:
|
|
||||||
return {
|
return {
|
||||||
"failed": True,
|
"failed": True,
|
||||||
"msg": "No GPG public key (gpg_pubkey) was supplied.",
|
"msg": "No GPG public key (gpg_pubkey) was supplied.",
|
||||||
}
|
}
|
||||||
|
|
||||||
sha256sum_path = os.path.join(self.project_path, "sha256sum.txt")
|
signature_file = os.path.join(self.project_path, ".ansible-sign", "sha256sum.txt.sig")
|
||||||
sha256sum_sig_path = f"{sha256sum_path}.sig"
|
manifest_file = os.path.join(self.project_path, ".ansible-sign", "sha256sum.txt")
|
||||||
|
|
||||||
if not os.path.exists(sha256sum_sig_path):
|
for path in (signature_file, manifest_file):
|
||||||
|
if not os.path.exists(path):
|
||||||
return {
|
return {
|
||||||
"failed": True,
|
"failed": True,
|
||||||
"msg": "Project path existed but did not contain 'sha256sum.txt.sig'.",
|
"msg": f"Expected file not found: {path}",
|
||||||
}
|
}
|
||||||
|
|
||||||
result = {}
|
with tempfile.TemporaryDirectory() as gpg_home:
|
||||||
|
gpg = gnupg.GPG(gnupghome=gpg_home)
|
||||||
|
gpg.import_keys(gpg_pubkey)
|
||||||
|
verifier = GPGVerifier(
|
||||||
|
manifest_path=manifest_file,
|
||||||
|
detached_signature_path=signature_file,
|
||||||
|
gpg_home=gpg_home,
|
||||||
|
)
|
||||||
|
result = verifier.verify()
|
||||||
|
|
||||||
with tempfile.TemporaryDirectory() as tmpdir:
|
return {
|
||||||
gpg = gnupg.GPG(gnupghome=tmpdir)
|
"failed": not result.success,
|
||||||
import_result = gpg.import_keys(self.pubkey)
|
"msg": result.summary,
|
||||||
result["gpg_pubkeys_imported"] = import_result.count
|
"gpg_details": result.extra_information,
|
||||||
result["gpg_fingerprints"] = import_result.fingerprints
|
}
|
||||||
|
|
||||||
with open(sha256sum_sig_path, 'rb') as sha256sum_sig:
|
def validate_checksum_manifest(self):
|
||||||
verified = gpg.verify_file(sha256sum_sig, sha256sum_path)
|
checksum = ChecksumFile(self.project_path, differ=DistlibManifestChecksumFileExistenceDiffer)
|
||||||
|
manifest_file = os.path.join(self.project_path, ".ansible-sign", "sha256sum.txt")
|
||||||
|
|
||||||
if not verified:
|
if not os.path.exists(manifest_file):
|
||||||
return {
|
return {
|
||||||
"failed": True,
|
"failed": True,
|
||||||
"msg": "Verification of checksum file failed.",
|
"msg": f"Expected file not found: {path}",
|
||||||
"details": {
|
}
|
||||||
"gpg_stderr": verified.stderr,
|
|
||||||
},
|
checksum_file_contents = open(manifest_file, "r").read()
|
||||||
|
|
||||||
|
try:
|
||||||
|
manifest = checksum.parse(checksum_file_contents)
|
||||||
|
except InvalidChecksumLine as e:
|
||||||
|
return {
|
||||||
|
"failed": True,
|
||||||
|
"msg": f"Invalid line in checksum manifest: {e}",
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
checksum.verify(manifest)
|
||||||
|
except ChecksumMismatch as e:
|
||||||
|
return {
|
||||||
|
"failed": True,
|
||||||
|
"msg": str(e),
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"failed": False,
|
"failed": False,
|
||||||
"msg": "Verification of checksum file succeeded.",
|
"msg": "Checksum manifest is valid.",
|
||||||
"details": {
|
|
||||||
"gpg_stderr": verified.stderr,
|
|
||||||
"gpg_fingerprint": verified.fingerprint,
|
|
||||||
"gpg_creation_date": verified.creation_date,
|
|
||||||
"gpg_status": verified.status,
|
|
||||||
"gpg_timestamp": verified.timestamp,
|
|
||||||
# "gpg_expiry": verified.expiry,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class ChecksumFileExistenceDiffer:
|
|
||||||
'''
|
|
||||||
When checking checksum files, it can be important to ensure not only that
|
|
||||||
the files listed have correct hashes, but that no files were added that
|
|
||||||
are not listed.
|
|
||||||
|
|
||||||
This is particularly important in situations where files might get
|
|
||||||
"wildcard-included" -- whereby an extra file slipping in could present a
|
|
||||||
security risk.
|
|
||||||
|
|
||||||
This class, and subclasses of it, provide an implementation that
|
|
||||||
ChecksumFileValidator instances can use to list all "interesting" files that
|
|
||||||
should be listed in the checksum file.
|
|
||||||
'''
|
|
||||||
|
|
||||||
ignored_files = set(
|
|
||||||
[
|
|
||||||
"sha256sum.txt",
|
|
||||||
"sha256sum.txt.sig",
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
def __init__(self, root):
|
|
||||||
self.root = root
|
|
||||||
self.files = self.gather_files()
|
|
||||||
|
|
||||||
def gather_files(self):
|
|
||||||
return set()
|
|
||||||
|
|
||||||
def list_files(self):
|
|
||||||
return self.files - self.ignored_files
|
|
||||||
|
|
||||||
def compare_filelist(self, checksum_paths):
|
|
||||||
'''
|
|
||||||
Given a set of paths (from a checksum file), see if files have since
|
|
||||||
been added or removed from the root directory and any deeper
|
|
||||||
directories.
|
|
||||||
|
|
||||||
The given set of paths is used as the source of truth and additions
|
|
||||||
and deletions are list with respect to it.
|
|
||||||
'''
|
|
||||||
|
|
||||||
out = {
|
|
||||||
"added": set(),
|
|
||||||
"removed": set(),
|
|
||||||
}
|
|
||||||
real_paths = self.list_files()
|
|
||||||
out["added"] = real_paths - checksum_paths
|
|
||||||
out["removed"] = checksum_paths - real_paths
|
|
||||||
return out
|
|
||||||
|
|
||||||
|
|
||||||
class GitChecksumFileExistenceDiffer(ChecksumFileExistenceDiffer):
|
|
||||||
'''
|
|
||||||
Use gitpython to get walk the file tree of a git repository at HEAD of the
|
|
||||||
branch that is currently checked out.
|
|
||||||
'''
|
|
||||||
|
|
||||||
def gather_files(self):
|
|
||||||
repo = git.Repo(self.root)
|
|
||||||
files = set()
|
|
||||||
stack = [repo.head.commit.tree]
|
|
||||||
while stack:
|
|
||||||
tree = stack.pop()
|
|
||||||
for blob in tree.blobs:
|
|
||||||
files.add(blob.path)
|
|
||||||
for inner in tree.trees:
|
|
||||||
stack.append(inner)
|
|
||||||
return set(files)
|
|
||||||
|
|
||||||
|
|
||||||
class InvalidChecksumLine(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class NoDifferException(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class ChecksumMismatch(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class ChecksumFileValidator:
|
|
||||||
'''
|
|
||||||
Slurp a checksum file and be able to check and compare its contents to a
|
|
||||||
given root directory.
|
|
||||||
|
|
||||||
We assume sha256 for now.
|
|
||||||
'''
|
|
||||||
|
|
||||||
def __init__(self, raw_checksum_file, root, differ=None):
|
|
||||||
self._raw_checksum_file = raw_checksum_file
|
|
||||||
self.root = root
|
|
||||||
if differ is not None:
|
|
||||||
self.differ = differ(root=root)
|
|
||||||
self.parsed_checksums = self._parse()
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_path(cls, path, *args, **kwargs):
|
|
||||||
if not args and "root" not in kwargs:
|
|
||||||
kwargs["root"] = os.path.dirname(path)
|
|
||||||
return cls(open(path, 'r').read(), *args, **kwargs)
|
|
||||||
|
|
||||||
def _parse_bsd_style(self, line):
|
|
||||||
'''
|
|
||||||
Attempt to parse a BSD style checksum line, returning False if we
|
|
||||||
are unable to.
|
|
||||||
|
|
||||||
A BSD style line looks like this:
|
|
||||||
SHA256 (hello_world.yml) = f712979c4c5dfe739253908d122f5c87faa8b5de6f15ba7a1548ae028ff22d13
|
|
||||||
'''
|
|
||||||
|
|
||||||
# Each BSD line is prefixed with 'SHA256 ('. Then, starting from the
|
|
||||||
# right (and assuming sha256 only, for now) we can count 68
|
|
||||||
# characters ( sha length and ") = " ) to look for another pattern.
|
|
||||||
if line.startswith('SHA256 (') and line[-68:-64] == ') = ':
|
|
||||||
# If both of those criteria match, we are pretty confident this
|
|
||||||
# is a BSD style line. From the right, split once at the = sign
|
|
||||||
# and parse out the path, and we are done. If the split
|
|
||||||
# doesn't work, or the sha isn't length 64, then assume it's
|
|
||||||
# not a BSD line, after all.
|
|
||||||
parts = line.rsplit(' = ', 1)
|
|
||||||
if len(parts) == 2 and len(parts[1]) == 64:
|
|
||||||
path = parts[0][8:-1]
|
|
||||||
shasum = parts[1]
|
|
||||||
return (path, shasum)
|
|
||||||
return False
|
|
||||||
|
|
||||||
def _parse_gnu_style(self, line):
|
|
||||||
'''
|
|
||||||
Attempt to parse a GNU style line checksum line, returning False if
|
|
||||||
we are unable to.
|
|
||||||
|
|
||||||
A GNU style line looks like this:
|
|
||||||
f712979c4c5dfe739253908d122f5c87faa8b5de6f15ba7a1548ae028ff22d13 hello_world.yml
|
|
||||||
|
|
||||||
Or maybe like this:
|
|
||||||
f82da8b4f98a3d3125fbc98408911f65dbc8dc38c0f38e258ebe290a8ad3d3c0 *binary
|
|
||||||
'''
|
|
||||||
|
|
||||||
parts = line.split(' ', 1)
|
|
||||||
if len(parts) != 2 or len(parts[0]) != 64:
|
|
||||||
return False
|
|
||||||
|
|
||||||
if len(parts[1]) < 2 or parts[1][0] not in (' ', '*'):
|
|
||||||
return False
|
|
||||||
|
|
||||||
shasum = parts[0]
|
|
||||||
path = parts[1][1:]
|
|
||||||
return (path, shasum)
|
|
||||||
|
|
||||||
def _parse(self):
|
|
||||||
checksums = {}
|
|
||||||
for idx, line in enumerate(self._raw_checksum_file.splitlines()):
|
|
||||||
if not line.strip():
|
|
||||||
continue
|
|
||||||
parsed = self._parse_bsd_style(line)
|
|
||||||
if parsed is False:
|
|
||||||
parsed = self._parse_gnu_style(line)
|
|
||||||
if parsed is False:
|
|
||||||
raise InvalidChecksumLine(f"Unparsable checksum, line {idx + 1}: {line}")
|
|
||||||
path = parsed[0]
|
|
||||||
shasum = parsed[1]
|
|
||||||
if path in checksums:
|
|
||||||
raise InvalidChecksumLine(f"Duplicate path in checksum, line {idx + 1}: {line}")
|
|
||||||
checksums[path] = shasum
|
|
||||||
return checksums
|
|
||||||
|
|
||||||
def diff(self):
|
|
||||||
if self.differ is None:
|
|
||||||
raise NoDifferException("diff: No differ was associated with this instance")
|
|
||||||
paths = set(self.parsed_checksums.keys())
|
|
||||||
return self.differ.compare_filelist(paths)
|
|
||||||
|
|
||||||
def calculate_checksum(self, path):
|
|
||||||
fullpath = os.path.join(self.root, path)
|
|
||||||
shasum = hashlib.sha256()
|
|
||||||
with open(fullpath, 'rb') as f:
|
|
||||||
while True:
|
|
||||||
chunk = f.read(65536)
|
|
||||||
if not chunk:
|
|
||||||
break
|
|
||||||
shasum.update(chunk)
|
|
||||||
return shasum.hexdigest()
|
|
||||||
|
|
||||||
def calculate_checksums(self):
|
|
||||||
# This uses the paths found in the parsed checksum file.
|
|
||||||
# Just calling this is not enough in many cases- you want to ensure that
|
|
||||||
# the files in the checksum list are the same ones present in reality.
|
|
||||||
# diff() above does just that. Use that in combination with this method,
|
|
||||||
# or use verify() which does it for you.
|
|
||||||
out = {}
|
|
||||||
for path in self.parsed_checksums.keys():
|
|
||||||
shasum = self.calculate_checksum(path)
|
|
||||||
out[path] = shasum
|
|
||||||
return out
|
|
||||||
|
|
||||||
def verify(self):
|
|
||||||
# If there are any differences in existing paths, fail the check...
|
|
||||||
differences = self.diff()
|
|
||||||
if differences["added"] or differences["removed"]:
|
|
||||||
raise ChecksumMismatch(differences)
|
|
||||||
|
|
||||||
recalculated = self.calculate_checksums()
|
|
||||||
mismatches = set()
|
|
||||||
for parsed_path, parsed_checksum in self.parsed_checksums.items():
|
|
||||||
if recalculated[parsed_path] != parsed_checksum:
|
|
||||||
mismatches.add(parsed_path)
|
|
||||||
if mismatches:
|
|
||||||
raise ChecksumMismatch(f"Checksum mismatch: {', '.join(mismatches)}")
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
SCM_FILE_EXISTENCE_CHECKER_MAP = {
|
|
||||||
"git": GitChecksumFileExistenceDiffer,
|
|
||||||
}
|
|
||||||
|
|
||||||
SIGNATURE_VALIDATION_MAP = {
|
|
||||||
"gpg": GPGVerifier,
|
|
||||||
}
|
}
|
||||||
|
|||||||
65
awx/playbooks/library/playbook_integrity.py
Normal file
65
awx/playbooks/library/playbook_integrity.py
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
ANSIBLE_METADATA = {"metadata_version": "1.0", "status": ["stableinterface"], "supported_by": "community"}
|
||||||
|
|
||||||
|
|
||||||
|
DOCUMENTATION = """
|
||||||
|
---
|
||||||
|
module: playbook_integrity
|
||||||
|
short_description: verify that files within a project have not been tampered with.
|
||||||
|
description:
|
||||||
|
- Makes use of the 'ansible-sign' project as a library for ensuring that an
|
||||||
|
Ansible project has not been tampered with.
|
||||||
|
- There are multiple types of validation that this action plugin supports,
|
||||||
|
currently: GPG public/private key signing of a checksum manifest file, and
|
||||||
|
checking the checksum manifest file itself against the checksum of each file
|
||||||
|
that is being verified.
|
||||||
|
- In the future, other types of validation may be supported.
|
||||||
|
options:
|
||||||
|
project_path:
|
||||||
|
description:
|
||||||
|
- Directory of the project being verified. Expected to contain a
|
||||||
|
C(.ansible-sign) directory with a generated checksum manifest file and a
|
||||||
|
detached signature for it. These files are produced by the
|
||||||
|
C(ansible-sign) command-line utility.
|
||||||
|
required: true
|
||||||
|
validation_type:
|
||||||
|
description:
|
||||||
|
- Describes the kind of validation to perform on the project.
|
||||||
|
- I(validation_type=gpg) means that a GPG Public Key credential is being
|
||||||
|
used to verify the integrity of the checksum manifest (and therefore the
|
||||||
|
project).
|
||||||
|
- 'checksum_manifest' means that the signed checksum manifest is validated
|
||||||
|
against all files in the project listed by its MANIFEST.in file. Just
|
||||||
|
running this plugin with I(validation_type=checksum_manifest) is
|
||||||
|
typically B(NOT) enough. It should also be run with a I(validation_type)
|
||||||
|
that ensures that the manifest file itself has not changed, such as
|
||||||
|
I(validation_type=gpg).
|
||||||
|
required: true
|
||||||
|
choices:
|
||||||
|
- gpg
|
||||||
|
- checksum_manifest
|
||||||
|
gpg_pubkey:
|
||||||
|
description:
|
||||||
|
- The public key to validate a checksum manifest against. Must match the
|
||||||
|
detached signature in the project's C(.ansible-sign) directory.
|
||||||
|
- Required when I(validation_type=gpg).
|
||||||
|
author:
|
||||||
|
- Ansible AWX Team
|
||||||
|
"""
|
||||||
|
|
||||||
|
EXAMPLES = """
|
||||||
|
- name: Verify project content using GPG signature
|
||||||
|
playbook_integrity:
|
||||||
|
project_path: /srv/projects/example
|
||||||
|
validation_type: gpg
|
||||||
|
gpg_pubkey: |
|
||||||
|
-----BEING PGP PUBLIC KEY BLOCK-----
|
||||||
|
|
||||||
|
mWINAFXMtjsACADIf/zJS0V3UO3c+KAUcpVAcChpliM31ICDWydfIfF3dzMzLcCd
|
||||||
|
Cj2kk1mPWtP/JHfk1V5czcWWWWGC2Tw4g4IS+LokAAuwk7VKTlI34eeMl8SiZCAI
|
||||||
|
[...]
|
||||||
|
|
||||||
|
- name: Verify project content against checksum manifest
|
||||||
|
playbook_integrity:
|
||||||
|
project_path: /srv/projects/example
|
||||||
|
validation_type: checksum_manifest
|
||||||
|
"""
|
||||||
@@ -18,6 +18,7 @@
|
|||||||
# galaxy_task_env: environment variables to use specifically for ansible-galaxy commands
|
# galaxy_task_env: environment variables to use specifically for ansible-galaxy commands
|
||||||
# awx_version: Current running version of the awx or tower as a string
|
# awx_version: Current running version of the awx or tower as a string
|
||||||
# awx_license_type: "open" for AWX; else presume Tower
|
# awx_license_type: "open" for AWX; else presume Tower
|
||||||
|
# gpg_pubkey: the GPG public key to use for validation, when enabled
|
||||||
|
|
||||||
- hosts: localhost
|
- hosts: localhost
|
||||||
gather_facts: false
|
gather_facts: false
|
||||||
@@ -156,22 +157,24 @@
|
|||||||
- hosts: localhost
|
- hosts: localhost
|
||||||
gather_facts: false
|
gather_facts: false
|
||||||
connection: local
|
connection: local
|
||||||
name: Validate project content integrity
|
name: Perform project signature/checksum verification
|
||||||
tasks:
|
tasks:
|
||||||
- block:
|
- name: Verify project content using GPG signature
|
||||||
- name: verify project integrity
|
|
||||||
playbook_integrity:
|
playbook_integrity:
|
||||||
project_path: "{{ project_path | quote }}"
|
project_path: "{{ project_path | quote }}"
|
||||||
sig_type: gpg # TODO: unhardcode
|
validation_type: gpg
|
||||||
gpg_pubkey: "{{ gpg_pubkey }}"
|
gpg_pubkey: "{{ gpg_pubkey }}"
|
||||||
scm_type: git # TODO: unhardcode
|
register: gpg_result
|
||||||
register: playbook_integrity_check
|
|
||||||
- name: show integrity check result
|
|
||||||
debug:
|
|
||||||
var: playbook_integrity_check
|
|
||||||
tags:
|
tags:
|
||||||
- playbook_integrity_gpg
|
- validation_gpg_public_key
|
||||||
- playbook_integrity
|
|
||||||
|
- name: Verify project content against checksum manifest
|
||||||
|
playbook_integrity:
|
||||||
|
project_path: "{{ project_path | quote }}"
|
||||||
|
validation_type: checksum_manifest
|
||||||
|
register: checksum_result
|
||||||
|
tags:
|
||||||
|
- validation_checksum_manifest
|
||||||
|
|
||||||
- hosts: localhost
|
- hosts: localhost
|
||||||
gather_facts: false
|
gather_facts: false
|
||||||
|
|||||||
Reference in New Issue
Block a user