Blame SOURCES/oscap-anaconda-addon-2.1.0-archive_handling-PR_224.patch

f43ec6
From a1b983b4b5f8e49daa978aec6f9d28ba6dcea20c Mon Sep 17 00:00:00 2001
f43ec6
From: Matej Tyc <matyc@redhat.com>
f43ec6
Date: Wed, 12 Oct 2022 11:37:04 +0200
f43ec6
Subject: [PATCH 1/5] Add capability to preselect content from archives
f43ec6
f43ec6
Users can specify content path and tailoring path in kickstarts,
f43ec6
and the addon should be able to assure that those files are available,
f43ec6
and that they have precedence over other files.
f43ec6
---
f43ec6
 org_fedora_oscap/content_discovery.py | 35 +++++++++++++++++++
f43ec6
 tests/test_content_discovery.py       | 48 +++++++++++++++++++++++++++
f43ec6
 2 files changed, 83 insertions(+)
f43ec6
 create mode 100644 tests/test_content_discovery.py
f43ec6
f43ec6
diff --git a/org_fedora_oscap/content_discovery.py b/org_fedora_oscap/content_discovery.py
f43ec6
index ccfe6c8..9ef144e 100644
f43ec6
--- a/org_fedora_oscap/content_discovery.py
f43ec6
+++ b/org_fedora_oscap/content_discovery.py
f43ec6
@@ -12,6 +12,7 @@
f43ec6
 from org_fedora_oscap import data_fetch, utils
f43ec6
 from org_fedora_oscap import common
f43ec6
 from org_fedora_oscap import content_handling
f43ec6
+from org_fedora_oscap.content_handling import CONTENT_TYPES
f43ec6
 from org_fedora_oscap import rule_handling
f43ec6
 
f43ec6
 from org_fedora_oscap.common import _
f43ec6
@@ -191,6 +192,38 @@ def _verify_fingerprint(self, dest_filename, fingerprint=""):
f43ec6
             raise content_handling.ContentCheckError(msg)
f43ec6
         log.info(f"Integrity check passed using {hash_obj.name} hash")
f43ec6
 
f43ec6
+    def filter_discovered_content(self, labelled_files):
f43ec6
+        expected_path = self._addon_data.content_path
f43ec6
+        categories = (CONTENT_TYPES["DATASTREAM"], CONTENT_TYPES["XCCDF_CHECKLIST"])
f43ec6
+        if expected_path:
f43ec6
+            labelled_files = self.reduce_files(labelled_files, expected_path, categories)
f43ec6
+
f43ec6
+        expected_path = self._addon_data.tailoring_path
f43ec6
+        categories = (CONTENT_TYPES["TAILORING"], )
f43ec6
+        if expected_path:
f43ec6
+            labelled_files = self.reduce_files(labelled_files, expected_path, categories)
f43ec6
+
f43ec6
+        expected_path = self._addon_data.cpe_path
f43ec6
+        categories = (CONTENT_TYPES["CPE_DICT"], )
f43ec6
+        if expected_path:
f43ec6
+            labelled_files = self.reduce_files(labelled_files, expected_path, categories)
f43ec6
+
f43ec6
+        return labelled_files
f43ec6
+
f43ec6
+    def reduce_files(self, labelled_files, expected_path, categories):
f43ec6
+        reduced_files = dict()
f43ec6
+        if expected_path not in labelled_files:
f43ec6
+            msg = (
f43ec6
+                f"Expected a file {expected_path} to be part of the supplied content, "
f43ec6
+                f"but it was not the case, got only {list(labelled_files.keys())}"
f43ec6
+            )
f43ec6
+            raise RuntimeError(msg)
f43ec6
+        for path, label in labelled_files.items():
f43ec6
+            if label in categories and path != expected_path:
f43ec6
+                continue
f43ec6
+            reduced_files[path] = label
f43ec6
+        return reduced_files
f43ec6
+
f43ec6
     def _finish_actual_fetch(self, wait_for, fingerprint, report_callback, dest_filename):
f43ec6
         if wait_for:
f43ec6
             log.info(f"OSCAP Addon: Waiting for thread {wait_for}")
f43ec6
@@ -210,6 +243,8 @@ def _finish_actual_fetch(self, wait_for, fingerprint, report_callback, dest_file
f43ec6
             structured_content.add_content_archive(dest_filename)
f43ec6
 
f43ec6
         labelled_files = content_handling.identify_files(fpaths)
f43ec6
+        labelled_files = self.filter_discovered_content(labelled_files)
f43ec6
+
f43ec6
         for fname, label in labelled_files.items():
f43ec6
             structured_content.add_file(fname, label)
f43ec6
 
f43ec6
diff --git a/tests/test_content_discovery.py b/tests/test_content_discovery.py
f43ec6
new file mode 100644
f43ec6
index 0000000..5463c9a
f43ec6
--- /dev/null
f43ec6
+++ b/tests/test_content_discovery.py
f43ec6
@@ -0,0 +1,48 @@
f43ec6
+import pytest
f43ec6
+
f43ec6
+import org_fedora_oscap.content_discovery as tested_module
f43ec6
+
f43ec6
+
f43ec6
+@pytest.fixture
f43ec6
+def labelled_files():
f43ec6
+    return {
f43ec6
+        "dir/datastream": "D",
f43ec6
+        "dir/datastream2": "D",
f43ec6
+        "dir/dir/datastream3": "D",
f43ec6
+        "dir/dir/datastream3": "D",
f43ec6
+        "dir/XCCDF": "X",
f43ec6
+        "XCCDF2": "X",
f43ec6
+        "cpe": "C",
f43ec6
+        "t1": "T",
f43ec6
+        "dir3/t2": "T",
f43ec6
+    }
f43ec6
+
f43ec6
+
f43ec6
+def test_reduce(labelled_files):
f43ec6
+    bringer = tested_module.ContentBringer(None)
f43ec6
+
f43ec6
+    d_count = 0
f43ec6
+    x_count = 0
f43ec6
+    for l in labelled_files.values():
f43ec6
+        if l == "D":
f43ec6
+            d_count += 1
f43ec6
+        elif l == "X":
f43ec6
+            x_count += 1
f43ec6
+
f43ec6
+    reduced = bringer.reduce_files(labelled_files, "dir/datastream", ["D"])
f43ec6
+    assert len(reduced) == len(labelled_files) - d_count + 1
f43ec6
+    assert "dir/datastream" in reduced
f43ec6
+
f43ec6
+    reduced = bringer.reduce_files(labelled_files, "dir/datastream", ["D", "X"])
f43ec6
+    assert len(reduced) == len(labelled_files) - d_count - x_count + 1
f43ec6
+    assert "dir/datastream" in reduced
f43ec6
+
f43ec6
+    reduced = bringer.reduce_files(labelled_files, "dir/XCCDF", ["D", "X"])
f43ec6
+    assert len(reduced) == len(labelled_files) - d_count - x_count + 1
f43ec6
+    assert "dir/XCCDF" in reduced
f43ec6
+
f43ec6
+    with pytest.raises(RuntimeError, match="dir/datastream4"):
f43ec6
+        bringer.reduce_files(labelled_files, "dir/datastream4", ["D"])
f43ec6
+
f43ec6
+    reduced = bringer.reduce_files(labelled_files, "cpe", ["C"])
f43ec6
+    assert reduced == labelled_files
f43ec6
f43ec6
From 2a536a8ec4cdf20e4f19e8175898b7ace3fc7ca4 Mon Sep 17 00:00:00 2001
f43ec6
From: Matej Tyc <matyc@redhat.com>
f43ec6
Date: Wed, 12 Oct 2022 11:40:11 +0200
f43ec6
Subject: [PATCH 2/5] Handle changes in content identification
f43ec6
f43ec6
The code is able to handle changes in the way how oscap identifies
f43ec6
content much more gracefully.
f43ec6
---
f43ec6
 org_fedora_oscap/content_discovery.py | 13 +++++++++----
f43ec6
 org_fedora_oscap/content_handling.py  |  5 +++++
f43ec6
 2 files changed, 14 insertions(+), 4 deletions(-)
f43ec6
f43ec6
diff --git a/org_fedora_oscap/content_discovery.py b/org_fedora_oscap/content_discovery.py
f43ec6
index 9ef144e..9ed643b 100644
f43ec6
--- a/org_fedora_oscap/content_discovery.py
f43ec6
+++ b/org_fedora_oscap/content_discovery.py
f43ec6
@@ -2,6 +2,7 @@
f43ec6
 import logging
f43ec6
 import pathlib
f43ec6
 import shutil
f43ec6
+import os
f43ec6
 from glob import glob
f43ec6
 from typing import List
f43ec6
 
f43ec6
@@ -242,11 +243,15 @@ def _finish_actual_fetch(self, wait_for, fingerprint, report_callback, dest_file
f43ec6
         if content_type in ("archive", "rpm"):
f43ec6
             structured_content.add_content_archive(dest_filename)
f43ec6
 
f43ec6
-        labelled_files = content_handling.identify_files(fpaths)
f43ec6
-        labelled_files = self.filter_discovered_content(labelled_files)
f43ec6
+        labelled_filenames = content_handling.identify_files(fpaths)
f43ec6
+        labelled_relative_filenames = {
f43ec6
+            os.path.relpath(path, self.CONTENT_DOWNLOAD_LOCATION): label
f43ec6
+            for path, label in labelled_filenames.items()}
f43ec6
+        labelled_relative_filenames = self.filter_discovered_content(labelled_relative_filenames)
f43ec6
 
f43ec6
-        for fname, label in labelled_files.items():
f43ec6
-            structured_content.add_file(fname, label)
f43ec6
+        for rel_fname, label in labelled_relative_filenames.items():
f43ec6
+            fname = self.CONTENT_DOWNLOAD_LOCATION / rel_fname
f43ec6
+            structured_content.add_file(str(fname), label)
f43ec6
 
f43ec6
         if fingerprint and dest_filename:
f43ec6
             structured_content.record_verification(dest_filename)
f43ec6
diff --git a/org_fedora_oscap/content_handling.py b/org_fedora_oscap/content_handling.py
f43ec6
index 65d5a28..3e2ecae 100644
f43ec6
--- a/org_fedora_oscap/content_handling.py
f43ec6
+++ b/org_fedora_oscap/content_handling.py
f43ec6
@@ -122,6 +122,11 @@ def get_doc_type(file_path):
f43ec6
             if line.startswith("Document type:"):
f43ec6
                 _prefix, _sep, type_info = line.partition(":")
f43ec6
                 content_type = type_info.strip()
f43ec6
+                if content_type not in CONTENT_TYPES.values():
f43ec6
+                    log.info(
f43ec6
+                        f"File {file_path} labelled by oscap as {content_type}, "
f43ec6
+                        "which is an unexpected type.")
f43ec6
+                    content_type = f"unknown - {content_type}"
f43ec6
                 break
f43ec6
     except OSError:
f43ec6
         # 'oscap info' exitted with a non-zero exit code -> unknown doc
f43ec6
f43ec6
From 17f80b71d17ce5a2bdbed87730133cdabec2e22b Mon Sep 17 00:00:00 2001
f43ec6
From: Matej Tyc <matyc@redhat.com>
f43ec6
Date: Wed, 12 Oct 2022 11:38:51 +0200
f43ec6
Subject: [PATCH 3/5] Remove unused code
f43ec6
f43ec6
The function is not referenced anywhere in the project
f43ec6
---
f43ec6
 org_fedora_oscap/content_handling.py | 40 ----------------------------
f43ec6
 1 file changed, 40 deletions(-)
f43ec6
f43ec6
diff --git a/org_fedora_oscap/content_handling.py b/org_fedora_oscap/content_handling.py
f43ec6
index 3e2ecae..5096bab 100644
f43ec6
--- a/org_fedora_oscap/content_handling.py
f43ec6
+++ b/org_fedora_oscap/content_handling.py
f43ec6
@@ -141,43 +141,3 @@ def get_doc_type(file_path):
f43ec6
     log.info("OSCAP addon: Identified {file_path} as {content_type}"
f43ec6
              .format(file_path=file_path, content_type=content_type))
f43ec6
     return content_type
f43ec6
-
f43ec6
-
f43ec6
-def explore_content_files(fpaths):
f43ec6
-    """
f43ec6
-    Function for finding content files in a list of file paths. SIMPLY PICKS
f43ec6
-    THE FIRST USABLE CONTENT FILE OF A PARTICULAR TYPE AND JUST PREFERS DATA
f43ec6
-    STREAMS OVER STANDALONE BENCHMARKS.
f43ec6
-
f43ec6
-    :param fpaths: a list of file paths to search for content files in
f43ec6
-    :type fpaths: [str]
f43ec6
-    :return: ContentFiles instance containing the file names of the XCCDF file,
f43ec6
-        CPE dictionary and tailoring file or "" in place of those items
f43ec6
-        if not found
f43ec6
-    :rtype: ContentFiles
f43ec6
-
f43ec6
-    """
f43ec6
-    xccdf_file = ""
f43ec6
-    cpe_file = ""
f43ec6
-    tailoring_file = ""
f43ec6
-    found_ds = False
f43ec6
-
f43ec6
-    for fpath in fpaths:
f43ec6
-        doc_type = get_doc_type(fpath)
f43ec6
-        if not doc_type:
f43ec6
-            continue
f43ec6
-
f43ec6
-        # prefer DS over standalone XCCDF
f43ec6
-        if doc_type == "Source Data Stream" and (not xccdf_file or not found_ds):
f43ec6
-            xccdf_file = fpath
f43ec6
-            found_ds = True
f43ec6
-        elif doc_type == "XCCDF Checklist" and not xccdf_file:
f43ec6
-            xccdf_file = fpath
f43ec6
-        elif doc_type == "CPE Dictionary" and not cpe_file:
f43ec6
-            cpe_file = fpath
f43ec6
-        elif doc_type == "XCCDF Tailoring" and not tailoring_file:
f43ec6
-            tailoring_file = fpath
f43ec6
-
f43ec6
-    # TODO: raise exception if no xccdf_file is found?
f43ec6
-    files = ContentFiles(xccdf_file, cpe_file, tailoring_file)
f43ec6
-    return files
f43ec6
f43ec6
From 3aff547e2689a1ede4236c9166b11c99f272e3f7 Mon Sep 17 00:00:00 2001
f43ec6
From: Matej Tyc <matyc@redhat.com>
f43ec6
Date: Thu, 13 Oct 2022 14:11:25 +0200
f43ec6
Subject: [PATCH 4/5] Dont use tailoring if it is not expected
f43ec6
f43ec6
Take tailorings into account only if it is specified in the kickstart.
f43ec6
Compulsive usage of tailoring may be unwanted.
f43ec6
---
f43ec6
 org_fedora_oscap/content_discovery.py | 17 +++++++++++++----
f43ec6
 1 file changed, 13 insertions(+), 4 deletions(-)
f43ec6
f43ec6
diff --git a/org_fedora_oscap/content_discovery.py b/org_fedora_oscap/content_discovery.py
f43ec6
index 9ed643b..4235af7 100644
f43ec6
--- a/org_fedora_oscap/content_discovery.py
f43ec6
+++ b/org_fedora_oscap/content_discovery.py
f43ec6
@@ -193,16 +193,25 @@ def _verify_fingerprint(self, dest_filename, fingerprint=""):
f43ec6
             raise content_handling.ContentCheckError(msg)
f43ec6
         log.info(f"Integrity check passed using {hash_obj.name} hash")
f43ec6
 
f43ec6
+    def allow_one_expected_tailoring_or_no_tailoring(self, labelled_files):
f43ec6
+        expected_tailoring = self._addon_data.tailoring_path
f43ec6
+        tailoring_label = CONTENT_TYPES["TAILORING"]
f43ec6
+        if expected_tailoring:
f43ec6
+            labelled_files = self.reduce_files(labelled_files, expected_tailoring, [tailoring_label])
f43ec6
+        else:
f43ec6
+            labelled_files = {
f43ec6
+                path: label for path, label in labelled_files.items()
f43ec6
+                if label != tailoring_label
f43ec6
+            }
f43ec6
+        return labelled_files
f43ec6
+
f43ec6
     def filter_discovered_content(self, labelled_files):
f43ec6
         expected_path = self._addon_data.content_path
f43ec6
         categories = (CONTENT_TYPES["DATASTREAM"], CONTENT_TYPES["XCCDF_CHECKLIST"])
f43ec6
         if expected_path:
f43ec6
             labelled_files = self.reduce_files(labelled_files, expected_path, categories)
f43ec6
 
f43ec6
-        expected_path = self._addon_data.tailoring_path
f43ec6
-        categories = (CONTENT_TYPES["TAILORING"], )
f43ec6
-        if expected_path:
f43ec6
-            labelled_files = self.reduce_files(labelled_files, expected_path, categories)
f43ec6
+        labelled_files = self.allow_one_expected_tailoring_or_no_tailoring(labelled_files)
f43ec6
 
f43ec6
         expected_path = self._addon_data.cpe_path
f43ec6
         categories = (CONTENT_TYPES["CPE_DICT"], )
f43ec6
f43ec6
From 56d8e497e0a4c394784b1c950bd1a148a6dc42ad Mon Sep 17 00:00:00 2001
f43ec6
From: =?UTF-8?q?Mat=C4=9Bj=20T=C3=BD=C4=8D?= <matyc@redhat.com>
f43ec6
Date: Thu, 10 Nov 2022 12:46:46 +0100
f43ec6
Subject: [PATCH 5/5] Make the content RPM installation robust
f43ec6
f43ec6
If a package manager fails to install the package,
f43ec6
use the rpm command directly and skip deps.
f43ec6
---
f43ec6
 org_fedora_oscap/service/installation.py | 48 +++++++++++++++++-------
f43ec6
 1 file changed, 34 insertions(+), 14 deletions(-)
f43ec6
f43ec6
diff --git a/org_fedora_oscap/service/installation.py b/org_fedora_oscap/service/installation.py
f43ec6
index 255b992..f667479 100644
f43ec6
--- a/org_fedora_oscap/service/installation.py
f43ec6
+++ b/org_fedora_oscap/service/installation.py
f43ec6
@@ -18,6 +18,7 @@
f43ec6
 import logging
f43ec6
 import os
f43ec6
 import shutil
f43ec6
+import io
f43ec6
 
f43ec6
 from pyanaconda.core import util
f43ec6
 from pyanaconda.modules.common.task import Task
f43ec6
@@ -198,21 +199,11 @@ def run(self):
f43ec6
         elif self._policy_data.content_type == "datastream":
f43ec6
             shutil.copy2(self._content_path, target_content_dir)
f43ec6
         elif self._policy_data.content_type == "rpm":
f43ec6
-            # copy the RPM to the target system
f43ec6
-            shutil.copy2(self._file_path, target_content_dir)
f43ec6
+            try:
f43ec6
+                self._copy_rpm_to_target_and_install(target_content_dir)
f43ec6
 
f43ec6
-            # get the path of the RPM
f43ec6
-            content_name = common.get_content_name(self._policy_data)
f43ec6
-            package_path = utils.join_paths(self._target_directory, content_name)
f43ec6
-
f43ec6
-            # and install it with yum
f43ec6
-            ret = util.execInSysroot(
f43ec6
-                "yum", ["-y", "--nogpg", "install", package_path]
f43ec6
-            )
f43ec6
-
f43ec6
-            if ret != 0:
f43ec6
-                msg = _(f"Failed to install content RPM to the target system.")
f43ec6
-                terminate(msg)
f43ec6
+            except Exception as exc:
f43ec6
+                terminate(str(exc))
f43ec6
                 return
f43ec6
         else:
f43ec6
             pattern = utils.join_paths(common.INSTALLATION_CONTENT_DIR, "*")
f43ec6
@@ -221,6 +212,35 @@ def run(self):
f43ec6
         if os.path.exists(self._tailoring_path):
f43ec6
             shutil.copy2(self._tailoring_path, target_content_dir)
f43ec6
 
f43ec6
+    def _attempt_rpm_installation(self, chroot_package_path):
f43ec6
+        log.info("OSCAP addon: Installing the security content RPM to the installed system.")
f43ec6
+        stdout = io.StringIO()
f43ec6
+        ret = util.execWithRedirect(
f43ec6
+                "dnf", ["-y", "--nogpg", "install", chroot_package_path],
f43ec6
+                stdout=stdout, root=self._sysroot)
f43ec6
+        stdout.seek(0)
f43ec6
+        if ret != 0:
f43ec6
+            log.error(
f43ec6
+                "OSCAP addon: Error installing security content RPM using yum: {0}",
f43ec6
+                stdout.read())
f43ec6
+
f43ec6
+            stdout = io.StringIO()
f43ec6
+            ret = util.execWithRedirect(
f43ec6
+                    "rpm", ["--install", "--nodeps", chroot_package_path],
f43ec6
+                    stdout=stdout, root=self._sysroot)
f43ec6
+            if ret != 0:
f43ec6
+                log.error(
f43ec6
+                    "OSCAP addon: Error installing security content RPM using rpm: {0}",
f43ec6
+                    stdout.read())
f43ec6
+                msg = _(f"Failed to install content RPM to the target system.")
f43ec6
+                raise RuntimeError(msg)
f43ec6
+
f43ec6
+    def _copy_rpm_to_target_and_install(self, target_content_dir):
f43ec6
+        shutil.copy2(self._file_path, target_content_dir)
f43ec6
+        content_name = common.get_content_name(self._policy_data)
f43ec6
+        chroot_package_path = utils.join_paths(self._target_directory, content_name)
f43ec6
+        self._attempt_rpm_installation(chroot_package_path)
f43ec6
+
f43ec6
 
f43ec6
 class RemediateSystemTask(Task):
f43ec6
     """The installation task for running the remediation."""