|
Packit |
792a06 |
#
|
|
Packit |
792a06 |
# Copyright (C) 2013 Red Hat, Inc.
|
|
Packit |
792a06 |
#
|
|
Packit |
792a06 |
# This copyrighted material is made available to anyone wishing to use,
|
|
Packit |
792a06 |
# modify, copy, or redistribute it subject to the terms and conditions of
|
|
Packit |
792a06 |
# the GNU General Public License v.2, or (at your option) any later version.
|
|
Packit |
792a06 |
# This program is distributed in the hope that it will be useful, but WITHOUT
|
|
Packit |
792a06 |
# ANY WARRANTY expressed or implied, including the implied warranties of
|
|
Packit |
792a06 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
|
Packit |
792a06 |
# Public License for more details. You should have received a copy of the
|
|
Packit |
792a06 |
# GNU General Public License along with this program; if not, write to the
|
|
Packit |
792a06 |
# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
|
Packit |
792a06 |
# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the
|
|
Packit |
792a06 |
# source code or documentation are not subject to the GNU General Public
|
|
Packit |
792a06 |
# License and may only be used or replicated with the express permission of
|
|
Packit |
792a06 |
# Red Hat, Inc.
|
|
Packit |
792a06 |
#
|
|
Packit |
792a06 |
# Red Hat Author(s): Vratislav Podzimek <vpodzime@redhat.com>
|
|
Packit |
792a06 |
#
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
"""
|
|
Packit |
792a06 |
Module with various classes and functions needed by the OSCAP addon that are
|
|
Packit |
792a06 |
not specific to any installation mode (tui, gui, ks).
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
"""
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
import os
|
|
Packit |
792a06 |
import tempfile
|
|
Packit |
792a06 |
import subprocess
|
|
Packit |
792a06 |
import zipfile
|
|
Packit |
792a06 |
import tarfile
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
import cpioarchive
|
|
Packit |
792a06 |
import re
|
|
Packit |
792a06 |
import logging
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
from collections import namedtuple
|
|
Packit |
792a06 |
import gettext
|
|
Packit |
792a06 |
from functools import wraps
|
|
Packit |
792a06 |
from pyanaconda.core import constants
|
|
Packit |
792a06 |
from pyanaconda.modules.common.constants.services import NETWORK
|
|
Packit |
792a06 |
from pyanaconda.threading import threadMgr, AnacondaThread
|
|
Packit |
792a06 |
from org_fedora_oscap import utils
|
|
Packit |
792a06 |
from org_fedora_oscap.data_fetch import fetch_data
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
log = logging.getLogger("anaconda")
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
# mimick pyanaconda/core/i18n.py
|
|
Packit |
792a06 |
def _(string):
|
|
Packit |
792a06 |
if string:
|
|
Packit |
792a06 |
return gettext.translation("oscap-anaconda-addon", fallback=True).gettext(string)
|
|
Packit |
792a06 |
else:
|
|
Packit |
792a06 |
return ""
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
def N_(string): return string
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
# everything else should be private
|
|
Packit |
792a06 |
__all__ = ["run_oscap_remediate", "get_fix_rules_pre",
|
|
Packit |
792a06 |
"wait_and_fetch_net_data", "extract_data", "strip_content_dir",
|
|
Packit |
792a06 |
"OSCAPaddonError"]
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
INSTALLATION_CONTENT_DIR = "/tmp/openscap_data/"
|
|
Packit |
792a06 |
TARGET_CONTENT_DIR = "/root/openscap_data/"
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
SSG_DIR = "/usr/share/xml/scap/ssg/content/"
|
|
Packit |
792a06 |
SSG_CONTENT = "ssg-rhel7-ds.xml"
|
|
Packit |
792a06 |
if constants.shortProductName != 'anaconda':
|
|
Packit |
792a06 |
if constants.shortProductName == 'fedora':
|
|
Packit |
792a06 |
SSG_CONTENT = "ssg-fedora-ds.xml"
|
|
Packit |
792a06 |
else:
|
|
Packit |
792a06 |
SSG_CONTENT = "ssg-%s%s-ds.xml" % (constants.shortProductName,
|
|
Packit |
792a06 |
constants.productVersion.strip(".")[0])
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
RESULTS_PATH = utils.join_paths(TARGET_CONTENT_DIR,
|
|
Packit |
792a06 |
"eval_remediate_results.xml")
|
|
Packit |
792a06 |
REPORT_PATH = utils.join_paths(TARGET_CONTENT_DIR,
|
|
Packit |
792a06 |
"eval_remediate_report.html")
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
PRE_INSTALL_FIX_SYSTEM_ATTR = "urn:redhat:anaconda:pre"
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
THREAD_FETCH_DATA = "AnaOSCAPdataFetchThread"
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
SUPPORTED_ARCHIVES = (".zip", ".tar", ".tar.gz", ".tar.bz2", )
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
# buffer size for reading and writing out data (in bytes)
|
|
Packit |
792a06 |
IO_BUF_SIZE = 2 * 1024 * 1024
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
class OSCAPaddonError(Exception):
|
|
Packit |
792a06 |
"""Exception class for OSCAP addon related errors."""
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
pass
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
class OSCAPaddonNetworkError(OSCAPaddonError):
|
|
Packit |
792a06 |
"""Exception class for OSCAP addon related network errors."""
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
pass
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
class ExtractionError(OSCAPaddonError):
|
|
Packit |
792a06 |
"""Exception class for the extraction errors."""
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
pass
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
MESSAGE_TYPE_FATAL = 0
|
|
Packit |
792a06 |
MESSAGE_TYPE_WARNING = 1
|
|
Packit |
792a06 |
MESSAGE_TYPE_INFO = 2
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
# namedtuple for messages returned from the rules evaluation
|
|
Packit |
792a06 |
# origin -- class (inherited from RuleHandler) that generated the message
|
|
Packit |
792a06 |
# type -- one of the MESSAGE_TYPE_* constants defined above
|
|
Packit |
792a06 |
# text -- the actual message that should be displayed, logged, ...
|
|
Packit |
792a06 |
RuleMessage = namedtuple("RuleMessage", ["origin", "type", "text"])
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
class SubprocessLauncher(object):
|
|
Packit |
792a06 |
def __init__(self, args):
|
|
Packit |
792a06 |
self.args = args
|
|
Packit |
792a06 |
self.stdout = ""
|
|
Packit |
792a06 |
self.stderr = ""
|
|
Packit |
792a06 |
self.messages = []
|
|
Packit |
792a06 |
self.returncode = None
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
def execute(self, ** kwargs):
|
|
Packit |
792a06 |
try:
|
|
Packit |
792a06 |
proc = subprocess.Popen(self.args, stdout=subprocess.PIPE,
|
|
Packit |
792a06 |
stderr=subprocess.PIPE, ** kwargs)
|
|
Packit |
792a06 |
except OSError as oserr:
|
|
Packit |
792a06 |
msg = "Failed to run the oscap tool: %s" % oserr
|
|
Packit |
792a06 |
raise OSCAPaddonError(msg)
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
(stdout, stderr) = proc.communicate()
|
|
Packit |
792a06 |
self.stdout = stdout.decode()
|
|
Packit Service |
a07ac4 |
self.stderr = stderr.decode()
|
|
Packit |
792a06 |
self.messages = re.findall(r'OpenSCAP Error:.*', self.stderr)
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
self.returncode = proc.returncode
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
def log_messages(self):
|
|
Packit |
792a06 |
for message in self.messages:
|
|
Packit |
792a06 |
log.warning("OSCAP addon: " + message)
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
def get_fix_rules_pre(profile, fpath, ds_id="", xccdf_id="", tailoring=""):
|
|
Packit |
792a06 |
"""
|
|
Packit |
792a06 |
Get fix rules for the pre-installation environment for a given profile in a
|
|
Packit |
792a06 |
given datastream and checklist in a given file.
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
:see: run_oscap_remediate
|
|
Packit |
792a06 |
:see: _run_oscap_gen_fix
|
|
Packit |
792a06 |
:return: fix rules for a given profile
|
|
Packit |
792a06 |
:rtype: str
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
"""
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
return _run_oscap_gen_fix(profile, fpath, PRE_INSTALL_FIX_SYSTEM_ATTR,
|
|
Packit |
792a06 |
ds_id=ds_id, xccdf_id=xccdf_id,
|
|
Packit |
792a06 |
tailoring=tailoring)
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
def _run_oscap_gen_fix(profile, fpath, template, ds_id="", xccdf_id="",
|
|
Packit |
792a06 |
tailoring=""):
|
|
Packit |
792a06 |
"""
|
|
Packit |
792a06 |
Run oscap tool on a given file to get the contents of fix elements with the
|
|
Packit |
792a06 |
'system' attribute equal to a given template for a given datastream,
|
|
Packit |
792a06 |
checklist and profile.
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
:see: run_oscap_remediate
|
|
Packit |
792a06 |
:param template: the value of the 'system' attribute of the fix elements
|
|
Packit |
792a06 |
:type template: str
|
|
Packit |
792a06 |
:return: oscap tool's stdout
|
|
Packit |
792a06 |
:rtype: str
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
"""
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
if not profile:
|
|
Packit |
792a06 |
return ""
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
args = ["oscap", "xccdf", "generate", "fix"]
|
|
Packit |
792a06 |
args.append("--template=%s" % template)
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
# oscap uses the default profile by default
|
|
Packit |
792a06 |
if profile.lower() != "default":
|
|
Packit |
792a06 |
args.append("--profile=%s" % profile)
|
|
Packit |
792a06 |
if ds_id:
|
|
Packit |
792a06 |
args.append("--datastream-id=%s" % ds_id)
|
|
Packit |
792a06 |
if xccdf_id:
|
|
Packit |
792a06 |
args.append("--xccdf-id=%s" % xccdf_id)
|
|
Packit |
792a06 |
if tailoring:
|
|
Packit |
792a06 |
args.append("--tailoring-file=%s" % tailoring)
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
args.append(fpath)
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
proc = SubprocessLauncher(args)
|
|
Packit |
792a06 |
proc.execute()
|
|
Packit |
792a06 |
proc.log_messages()
|
|
Packit |
792a06 |
if proc.returncode != 0:
|
|
Packit |
792a06 |
msg = "Failed to generate fix rules with the oscap tool: %s" % proc.stderr
|
|
Packit |
792a06 |
raise OSCAPaddonError(msg)
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
return proc.stdout
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
def run_oscap_remediate(profile, fpath, ds_id="", xccdf_id="", tailoring="",
|
|
Packit |
792a06 |
chroot=""):
|
|
Packit |
792a06 |
"""
|
|
Packit |
792a06 |
Run the evaluation and remediation with the oscap tool on a given file,
|
|
Packit |
792a06 |
doing the remediation as defined in a given profile defined in a given
|
|
Packit |
792a06 |
checklist that is a part of a given datastream. If requested, run in
|
|
Packit |
792a06 |
chroot.
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
:param profile: id of the profile that will drive the remediation
|
|
Packit |
792a06 |
:type profile: str
|
|
Packit |
792a06 |
:param fpath: path to a file with SCAP content
|
|
Packit |
792a06 |
:type fpath: str
|
|
Packit |
792a06 |
:param ds_id: ID of the datastream that contains the checklist defining
|
|
Packit |
792a06 |
the profile
|
|
Packit |
792a06 |
:type ds_id: str
|
|
Packit |
792a06 |
:param xccdf_id: ID of the checklist that defines the profile
|
|
Packit |
792a06 |
:type xccdf_id: str
|
|
Packit |
792a06 |
:param tailoring: path to a tailoring file
|
|
Packit |
792a06 |
:type tailoring: str
|
|
Packit |
792a06 |
:param chroot: path to the root the oscap tool should be run in
|
|
Packit |
792a06 |
:type chroot: str
|
|
Packit |
792a06 |
:return: oscap tool's stdout (summary of the rules, checks and fixes)
|
|
Packit |
792a06 |
:rtype: str
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
"""
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
if not profile:
|
|
Packit |
792a06 |
return ""
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
def do_chroot():
|
|
Packit |
792a06 |
"""Helper function doing the chroot if requested."""
|
|
Packit |
792a06 |
if chroot and chroot != "/":
|
|
Packit |
792a06 |
os.chroot(chroot)
|
|
Packit |
792a06 |
os.chdir("/")
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
# make sure the directory for the results exists
|
|
Packit |
792a06 |
results_dir = os.path.dirname(RESULTS_PATH)
|
|
Packit |
792a06 |
if chroot:
|
|
Packit |
792a06 |
results_dir = os.path.normpath(chroot + "/" + results_dir)
|
|
Packit |
792a06 |
utils.ensure_dir_exists(results_dir)
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
args = ["oscap", "xccdf", "eval"]
|
|
Packit |
792a06 |
args.append("--remediate")
|
|
Packit |
792a06 |
args.append("--results=%s" % RESULTS_PATH)
|
|
Packit |
792a06 |
args.append("--report=%s" % REPORT_PATH)
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
# oscap uses the default profile by default
|
|
Packit |
792a06 |
if profile.lower() != "default":
|
|
Packit |
792a06 |
args.append("--profile=%s" % profile)
|
|
Packit |
792a06 |
if ds_id:
|
|
Packit |
792a06 |
args.append("--datastream-id=%s" % ds_id)
|
|
Packit |
792a06 |
if xccdf_id:
|
|
Packit |
792a06 |
args.append("--xccdf-id=%s" % xccdf_id)
|
|
Packit |
792a06 |
if tailoring:
|
|
Packit |
792a06 |
args.append("--tailoring-file=%s" % tailoring)
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
args.append(fpath)
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
proc = SubprocessLauncher(args)
|
|
Packit |
792a06 |
proc.execute(preexec_fn=do_chroot)
|
|
Packit |
792a06 |
proc.log_messages()
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
if proc.returncode not in (0, 2):
|
|
Packit |
792a06 |
# 0 -- success; 2 -- no error, but checks/remediation failed
|
|
Packit |
792a06 |
msg = "Content evaluation and remediation with the oscap tool "\
|
|
Packit |
792a06 |
"failed: %s" % proc.stderr
|
|
Packit |
792a06 |
raise OSCAPaddonError(msg)
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
return proc.stdout
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
def wait_and_fetch_net_data(url, out_file, ca_certs=None):
|
|
Packit |
792a06 |
"""
|
|
Packit |
792a06 |
Function that waits for network connection and starts a thread that fetches
|
|
Packit |
792a06 |
data over network.
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
:see: org_fedora_oscap.data_fetch.fetch_data
|
|
Packit |
792a06 |
:return: the name of the thread running fetch_data
|
|
Packit |
792a06 |
:rtype: str
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
"""
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
# get thread that tries to establish a network connection
|
|
Packit |
792a06 |
nm_conn_thread = threadMgr.get(constants.THREAD_WAIT_FOR_CONNECTING_NM)
|
|
Packit |
792a06 |
if nm_conn_thread:
|
|
Packit |
792a06 |
# NM still connecting, wait for it to finish
|
|
Packit |
792a06 |
nm_conn_thread.join()
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
network_proxy = NETWORK.get_proxy()
|
|
Packit |
792a06 |
if not network_proxy.Connected:
|
|
Packit |
792a06 |
raise OSCAPaddonNetworkError("Network connection needed to fetch data.")
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
fetch_data_thread = AnacondaThread(name=THREAD_FETCH_DATA,
|
|
Packit |
792a06 |
target=fetch_data,
|
|
Packit |
792a06 |
args=(url, out_file, ca_certs),
|
|
Packit |
792a06 |
fatal=False)
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
# register and run the thread
|
|
Packit |
792a06 |
threadMgr.add(fetch_data_thread)
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
return THREAD_FETCH_DATA
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
def extract_data(archive, out_dir, ensure_has_files=None):
|
|
Packit |
792a06 |
"""
|
|
Packit |
792a06 |
Fuction that extracts the given archive to the given output directory. It
|
|
Packit |
792a06 |
tries to find out the archive type by the file name.
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
:param archive: path to the archive file that should be extracted
|
|
Packit |
792a06 |
:type archive: str
|
|
Packit |
792a06 |
:param out_dir: output directory the archive should be extracted to
|
|
Packit |
792a06 |
:type out_dir: str
|
|
Packit |
792a06 |
:param ensure_has_files: relative paths to the files that must exist in the
|
|
Packit |
792a06 |
archive
|
|
Packit |
792a06 |
:type ensure_has_files: iterable of strings or None
|
|
Packit |
792a06 |
:return: a list of files and directories extracted from the archive
|
|
Packit |
792a06 |
:rtype: [str]
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
"""
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
# get rid of empty file paths
|
|
Packit |
792a06 |
ensure_has_files = [fpath for fpath in ensure_has_files if fpath]
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
if archive.endswith(".zip"):
|
|
Packit |
792a06 |
# ZIP file
|
|
Packit |
792a06 |
try:
|
|
Packit |
792a06 |
zfile = zipfile.ZipFile(archive, "r")
|
|
Packit |
792a06 |
except zipfile.BadZipfile as err:
|
|
Packit |
792a06 |
raise ExtractionError(str(err))
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
# generator for the paths of the files found in the archive (dirs end
|
|
Packit |
792a06 |
# with "/")
|
|
Packit |
792a06 |
files = set(info.filename for info in zfile.filelist
|
|
Packit |
792a06 |
if not info.filename.endswith("/"))
|
|
Packit |
792a06 |
for fpath in ensure_has_files or ():
|
|
Packit |
792a06 |
if fpath not in files:
|
|
Packit |
792a06 |
msg = "File '%s' not found in the archive '%s'" % (fpath,
|
|
Packit |
792a06 |
archive)
|
|
Packit |
792a06 |
raise ExtractionError(msg)
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
utils.ensure_dir_exists(out_dir)
|
|
Packit |
792a06 |
zfile.extractall(path=out_dir)
|
|
Packit |
792a06 |
result = [utils.join_paths(out_dir, info.filename) for info in zfile.filelist]
|
|
Packit |
792a06 |
zfile.close()
|
|
Packit |
792a06 |
return result
|
|
Packit |
792a06 |
elif archive.endswith(".tar"):
|
|
Packit |
792a06 |
# plain tarball
|
|
Packit |
792a06 |
return _extract_tarball(archive, out_dir, ensure_has_files, None)
|
|
Packit |
792a06 |
elif archive.endswith(".tar.gz"):
|
|
Packit |
792a06 |
# gzipped tarball
|
|
Packit |
792a06 |
return _extract_tarball(archive, out_dir, ensure_has_files, "gz")
|
|
Packit |
792a06 |
elif archive.endswith(".tar.bz2"):
|
|
Packit |
792a06 |
# bzipped tarball
|
|
Packit |
792a06 |
return _extract_tarball(archive, out_dir, ensure_has_files, "bz2")
|
|
Packit |
792a06 |
elif archive.endswith(".rpm"):
|
|
Packit |
792a06 |
# RPM
|
|
Packit |
792a06 |
return _extract_rpm(archive, out_dir, ensure_has_files)
|
|
Packit |
792a06 |
# elif other types of archives
|
|
Packit |
792a06 |
else:
|
|
Packit |
792a06 |
raise ExtractionError("Unsuported archive type")
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
def _extract_tarball(archive, out_dir, ensure_has_files, alg):
|
|
Packit |
792a06 |
"""
|
|
Packit |
792a06 |
Extract the given TAR archive to the given output directory and make sure
|
|
Packit |
792a06 |
the given file exists in the archive.
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
:see: extract_data
|
|
Packit |
792a06 |
:param alg: compression algorithm used for the tarball
|
|
Packit |
792a06 |
:type alg: str (one of "gz", "bz2") or None
|
|
Packit |
792a06 |
:return: a list of files and directories extracted from the archive
|
|
Packit |
792a06 |
:rtype: [str]
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
"""
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
if alg and alg not in ("gz", "bz2",):
|
|
Packit |
792a06 |
raise ExtractionError("Unsupported compression algorithm")
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
mode = "r"
|
|
Packit |
792a06 |
if alg:
|
|
Packit |
792a06 |
mode += ":%s" % alg
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
try:
|
|
Packit |
792a06 |
tfile = tarfile.TarFile.open(archive, mode)
|
|
Packit |
792a06 |
except tarfile.TarError as err:
|
|
Packit |
792a06 |
raise ExtractionError(str(err))
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
# generator for the paths of the files found in the archive
|
|
Packit |
792a06 |
files = set(member.path for member in tfile.getmembers()
|
|
Packit |
792a06 |
if member.isfile())
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
for fpath in ensure_has_files or ():
|
|
Packit |
792a06 |
if fpath not in files:
|
|
Packit |
792a06 |
msg = "File '%s' not found in the archive '%s'" % (fpath, archive)
|
|
Packit |
792a06 |
raise ExtractionError(msg)
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
utils.ensure_dir_exists(out_dir)
|
|
Packit |
792a06 |
tfile.extractall(path=out_dir)
|
|
Packit |
792a06 |
result = [utils.join_paths(out_dir, member.path) for member in tfile.getmembers()]
|
|
Packit |
792a06 |
tfile.close()
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
return result
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
def _extract_rpm(rpm_path, root="/", ensure_has_files=None):
|
|
Packit |
792a06 |
"""
|
|
Packit |
792a06 |
Extract the given RPM into the directory tree given by the root argument
|
|
Packit |
792a06 |
and make sure the given file exists in the archive.
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
:param rpm_path: path to the RPM file that should be extracted
|
|
Packit |
792a06 |
:type rpm_path: str
|
|
Packit |
792a06 |
:param root: root of the directory tree the RPM should be extracted into
|
|
Packit |
792a06 |
:type root: str
|
|
Packit |
792a06 |
:param ensure_has_files: relative paths to the files that must exist in the
|
|
Packit |
792a06 |
RPM
|
|
Packit |
792a06 |
:type ensure_has_files: iterable of strings or None
|
|
Packit |
792a06 |
:return: a list of files and directories extracted from the archive
|
|
Packit |
792a06 |
:rtype: [str]
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
"""
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
# run rpm2cpio and process the output with the cpioarchive module
|
|
Packit |
792a06 |
temp_fd, temp_path = tempfile.mkstemp(prefix="oscap_rpm")
|
|
Packit |
792a06 |
proc = subprocess.Popen(["rpm2cpio", rpm_path], stdout=temp_fd)
|
|
Packit |
792a06 |
proc.wait()
|
|
Packit |
792a06 |
if proc.returncode != 0:
|
|
Packit |
792a06 |
msg = "Failed to convert RPM '%s' to cpio archive" % rpm_path
|
|
Packit |
792a06 |
raise ExtractionError(msg)
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
os.close(temp_fd)
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
try:
|
|
Packit |
792a06 |
archive = cpioarchive.CpioArchive(temp_path)
|
|
Packit |
792a06 |
except cpioarchive.CpioError as err:
|
|
Packit |
792a06 |
raise ExtractionError(str(err))
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
# get entries from the archive (supports only iteration over entries)
|
|
Packit |
792a06 |
entries = set(entry for entry in archive)
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
# cpio entry names (paths) start with the dot
|
|
Packit |
792a06 |
entry_names = [entry.name.lstrip(".") for entry in entries]
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
for fpath in ensure_has_files or ():
|
|
Packit |
792a06 |
# RPM->cpio entries have absolute paths
|
|
Packit |
792a06 |
if fpath not in entry_names and \
|
|
Packit |
792a06 |
os.path.join("/", fpath) not in entry_names:
|
|
Packit |
792a06 |
msg = "File '%s' not found in the archive '%s'" % (fpath, rpm_path)
|
|
Packit |
792a06 |
raise ExtractionError(msg)
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
try:
|
|
Packit |
792a06 |
for entry in entries:
|
|
Packit |
792a06 |
if entry.size == 0:
|
|
Packit |
792a06 |
continue
|
|
Packit |
792a06 |
dirname = os.path.dirname(entry.name.lstrip("."))
|
|
Packit |
792a06 |
out_dir = os.path.normpath(root + dirname)
|
|
Packit |
792a06 |
utils.ensure_dir_exists(out_dir)
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
out_fpath = os.path.normpath(root + entry.name.lstrip("."))
|
|
Packit |
792a06 |
if os.path.exists(out_fpath):
|
|
Packit |
792a06 |
continue
|
|
Packit |
792a06 |
with open(out_fpath, "wb") as out_file:
|
|
Packit |
792a06 |
buf = entry.read(IO_BUF_SIZE)
|
|
Packit |
792a06 |
while buf:
|
|
Packit |
792a06 |
out_file.write(buf)
|
|
Packit |
792a06 |
buf = entry.read(IO_BUF_SIZE)
|
|
Packit |
792a06 |
except (IOError, cpioarchive.CpioError) as e:
|
|
Packit |
792a06 |
raise ExtractionError(e)
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
# cleanup
|
|
Packit |
792a06 |
archive.close()
|
|
Packit |
792a06 |
os.unlink(temp_path)
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
return [os.path.normpath(root + name) for name in entry_names]
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
def strip_content_dir(fpaths, phase="preinst"):
|
|
Packit |
792a06 |
"""
|
|
Packit |
792a06 |
Strip content directory prefix from the file paths for either
|
|
Packit |
792a06 |
pre-installation or post-installation phase.
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
:param fpaths: iterable of file paths to strip content directory prefix
|
|
Packit |
792a06 |
from
|
|
Packit |
792a06 |
:type fpaths: iterable of strings
|
|
Packit |
792a06 |
:param phase: specifies pre-installation or post-installation phase
|
|
Packit |
792a06 |
:type phase: "preinst" or "postinst"
|
|
Packit |
792a06 |
:return: the same iterable of file paths as given with the content
|
|
Packit |
792a06 |
directory prefix stripped
|
|
Packit |
792a06 |
:rtype: same type as fpaths
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
"""
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
if phase == "preinst":
|
|
Packit |
792a06 |
remove_prefix = lambda x: x[len(INSTALLATION_CONTENT_DIR):]
|
|
Packit |
792a06 |
else:
|
|
Packit |
792a06 |
remove_prefix = lambda x: x[len(TARGET_CONTENT_DIR):]
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
return utils.keep_type_map(remove_prefix, fpaths)
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
def ssg_available(root="/"):
|
|
Packit |
792a06 |
"""
|
|
Packit |
792a06 |
Tries to find the SCAP Security Guide under the given root.
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
:return: True if SSG was found under the given root, False otherwise
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
"""
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
return os.path.exists(utils.join_paths(root, SSG_DIR + SSG_CONTENT))
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
def dry_run_skip(func):
|
|
Packit |
792a06 |
"""
|
|
Packit |
792a06 |
Decorator that makes sure the decorated function is noop in the dry-run
|
|
Packit |
792a06 |
mode.
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
:param func: a decorated function that needs to have the first parameter an
|
|
Packit |
792a06 |
object with the _addon_data attribute referencing the OSCAP
|
|
Packit |
792a06 |
addon's ksdata
|
|
Packit |
792a06 |
"""
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
@wraps(func)
|
|
Packit |
792a06 |
def decorated(self, *args, **kwargs):
|
|
Packit |
792a06 |
if self._addon_data.dry_run:
|
|
Packit |
792a06 |
return
|
|
Packit |
792a06 |
else:
|
|
Packit |
792a06 |
return func(self, *args, **kwargs)
|
|
Packit |
792a06 |
|
|
Packit |
792a06 |
return decorated
|