Blame SOURCES/oscap-anaconda-addon-1.3.0-better_archive_handling-PR_220.patch

36d401
From e8e303aa3ca9db564ea52258de15a81851c3b265 Mon Sep 17 00:00:00 2001
36d401
From: Matej Tyc <matyc@redhat.com>
36d401
Date: Wed, 12 Oct 2022 11:37:04 +0200
36d401
Subject: [PATCH 1/5] Add capability to preselect content from archives
36d401
36d401
Users can specify content path and tailoring path in kickstarts,
36d401
and the addon should be able to assure that those files are available,
36d401
and that they have precedence over other files.
36d401
---
36d401
 org_fedora_oscap/content_discovery.py | 35 +++++++++++++++++++
36d401
 tests/test_content_discovery.py       | 48 +++++++++++++++++++++++++++
36d401
 2 files changed, 83 insertions(+)
36d401
 create mode 100644 tests/test_content_discovery.py
36d401
36d401
diff --git a/org_fedora_oscap/content_discovery.py b/org_fedora_oscap/content_discovery.py
36d401
index 5fc7343..f654449 100644
36d401
--- a/org_fedora_oscap/content_discovery.py
36d401
+++ b/org_fedora_oscap/content_discovery.py
36d401
@@ -11,6 +11,7 @@
36d401
 from org_fedora_oscap import data_fetch, utils
36d401
 from org_fedora_oscap import common
36d401
 from org_fedora_oscap import content_handling
36d401
+from org_fedora_oscap.content_handling import CONTENT_TYPES
36d401
 
36d401
 from org_fedora_oscap.common import _
36d401
 
36d401
@@ -167,6 +168,38 @@ def _verify_fingerprint(self, dest_filename, fingerprint=""):
36d401
             msg = _(f"Integrity check of the content failed - {hash_obj.name} hash didn't match")
36d401
             raise content_handling.ContentCheckError(msg)
36d401
 
36d401
+    def filter_discovered_content(self, labelled_files):
36d401
+        expected_path = self._addon_data.content_path
36d401
+        categories = (CONTENT_TYPES["DATASTREAM"], CONTENT_TYPES["XCCDF_CHECKLIST"])
36d401
+        if expected_path:
36d401
+            labelled_files = self.reduce_files(labelled_files, expected_path, categories)
36d401
+
36d401
+        expected_path = self._addon_data.tailoring_path
36d401
+        categories = (CONTENT_TYPES["TAILORING"], )
36d401
+        if expected_path:
36d401
+            labelled_files = self.reduce_files(labelled_files, expected_path, categories)
36d401
+
36d401
+        expected_path = self._addon_data.cpe_path
36d401
+        categories = (CONTENT_TYPES["CPE_DICT"], )
36d401
+        if expected_path:
36d401
+            labelled_files = self.reduce_files(labelled_files, expected_path, categories)
36d401
+
36d401
+        return labelled_files
36d401
+
36d401
+    def reduce_files(self, labelled_files, expected_path, categories):
36d401
+        reduced_files = dict()
36d401
+        if expected_path not in labelled_files:
36d401
+            msg = (
36d401
+                f"Expected a file {expected_path} to be part of the supplied content, "
36d401
+                f"but it was not the case, got only {list(labelled_files.keys())}"
36d401
+            )
36d401
+            raise RuntimeError(msg)
36d401
+        for path, label in labelled_files.items():
36d401
+            if label in categories and path != expected_path:
36d401
+                continue
36d401
+            reduced_files[path] = label
36d401
+        return reduced_files
36d401
+
36d401
     def _finish_actual_fetch(self, wait_for, fingerprint, report_callback, dest_filename):
36d401
         threadMgr.wait(wait_for)
36d401
         actually_fetched_content = wait_for is not None
36d401
@@ -182,6 +215,8 @@ def _finish_actual_fetch(self, wait_for, fingerprint, report_callback, dest_file
36d401
             structured_content.add_content_archive(dest_filename)
36d401
 
36d401
         labelled_files = content_handling.identify_files(fpaths)
36d401
+        labelled_files = self.filter_discovered_content(labelled_files)
36d401
+
36d401
         for fname, label in labelled_files.items():
36d401
             structured_content.add_file(fname, label)
36d401
 
36d401
diff --git a/tests/test_content_discovery.py b/tests/test_content_discovery.py
36d401
new file mode 100644
36d401
index 0000000..5463c9a
36d401
--- /dev/null
36d401
+++ b/tests/test_content_discovery.py
36d401
@@ -0,0 +1,48 @@
36d401
+import pytest
36d401
+
36d401
+import org_fedora_oscap.content_discovery as tested_module
36d401
+
36d401
+
36d401
+@pytest.fixture
36d401
+def labelled_files():
36d401
+    return {
36d401
+        "dir/datastream": "D",
36d401
+        "dir/datastream2": "D",
36d401
+        "dir/dir/datastream3": "D",
36d401
+        "dir/dir/datastream3": "D",
36d401
+        "dir/XCCDF": "X",
36d401
+        "XCCDF2": "X",
36d401
+        "cpe": "C",
36d401
+        "t1": "T",
36d401
+        "dir3/t2": "T",
36d401
+    }
36d401
+
36d401
+
36d401
+def test_reduce(labelled_files):
36d401
+    bringer = tested_module.ContentBringer(None)
36d401
+
36d401
+    d_count = 0
36d401
+    x_count = 0
36d401
+    for l in labelled_files.values():
36d401
+        if l == "D":
36d401
+            d_count += 1
36d401
+        elif l == "X":
36d401
+            x_count += 1
36d401
+
36d401
+    reduced = bringer.reduce_files(labelled_files, "dir/datastream", ["D"])
36d401
+    assert len(reduced) == len(labelled_files) - d_count + 1
36d401
+    assert "dir/datastream" in reduced
36d401
+
36d401
+    reduced = bringer.reduce_files(labelled_files, "dir/datastream", ["D", "X"])
36d401
+    assert len(reduced) == len(labelled_files) - d_count - x_count + 1
36d401
+    assert "dir/datastream" in reduced
36d401
+
36d401
+    reduced = bringer.reduce_files(labelled_files, "dir/XCCDF", ["D", "X"])
36d401
+    assert len(reduced) == len(labelled_files) - d_count - x_count + 1
36d401
+    assert "dir/XCCDF" in reduced
36d401
+
36d401
+    with pytest.raises(RuntimeError, match="dir/datastream4"):
36d401
+        bringer.reduce_files(labelled_files, "dir/datastream4", ["D"])
36d401
+
36d401
+    reduced = bringer.reduce_files(labelled_files, "cpe", ["C"])
36d401
+    assert reduced == labelled_files
36d401
36d401
From 82c1950903fcce079cd71f021c1fde25f75f9521 Mon Sep 17 00:00:00 2001
36d401
From: Matej Tyc <matyc@redhat.com>
36d401
Date: Wed, 12 Oct 2022 11:40:11 +0200
36d401
Subject: [PATCH 2/5] Handle changes in content identification
36d401
36d401
The code is able to handle changes in the way how oscap identifies
36d401
content much more gracefully.
36d401
---
36d401
 org_fedora_oscap/content_discovery.py | 13 +++++++++----
36d401
 org_fedora_oscap/content_handling.py  |  5 +++++
36d401
 2 files changed, 14 insertions(+), 4 deletions(-)
36d401
36d401
diff --git a/org_fedora_oscap/content_discovery.py b/org_fedora_oscap/content_discovery.py
36d401
index f654449..b20f3a6 100644
36d401
--- a/org_fedora_oscap/content_discovery.py
36d401
+++ b/org_fedora_oscap/content_discovery.py
36d401
@@ -2,6 +2,7 @@
36d401
 import logging
36d401
 import pathlib
36d401
 import shutil
36d401
+import os
36d401
 from glob import glob
36d401
 
36d401
 from pyanaconda.core import constants
36d401
@@ -214,11 +215,15 @@ def _finish_actual_fetch(self, wait_for, fingerprint, report_callback, dest_file
36d401
         if content_type in ("archive", "rpm"):
36d401
             structured_content.add_content_archive(dest_filename)
36d401
 
36d401
-        labelled_files = content_handling.identify_files(fpaths)
36d401
-        labelled_files = self.filter_discovered_content(labelled_files)
36d401
+        labelled_filenames = content_handling.identify_files(fpaths)
36d401
+        labelled_relative_filenames = {
36d401
+            os.path.relpath(path, self.CONTENT_DOWNLOAD_LOCATION): label
36d401
+            for path, label in labelled_filenames.items()}
36d401
+        labelled_relative_filenames = self.filter_discovered_content(labelled_relative_filenames)
36d401
 
36d401
-        for fname, label in labelled_files.items():
36d401
-            structured_content.add_file(fname, label)
36d401
+        for rel_fname, label in labelled_relative_filenames.items():
36d401
+            fname = self.CONTENT_DOWNLOAD_LOCATION / rel_fname
36d401
+            structured_content.add_file(str(fname), label)
36d401
 
36d401
         if fingerprint and dest_filename:
36d401
             structured_content.record_verification(dest_filename)
36d401
diff --git a/org_fedora_oscap/content_handling.py b/org_fedora_oscap/content_handling.py
36d401
index 65d5a28..3e2ecae 100644
36d401
--- a/org_fedora_oscap/content_handling.py
36d401
+++ b/org_fedora_oscap/content_handling.py
36d401
@@ -122,6 +122,11 @@ def get_doc_type(file_path):
36d401
             if line.startswith("Document type:"):
36d401
                 _prefix, _sep, type_info = line.partition(":")
36d401
                 content_type = type_info.strip()
36d401
+                if content_type not in CONTENT_TYPES.values():
36d401
+                    log.info(
36d401
+                        f"File {file_path} labelled by oscap as {content_type}, "
36d401
+                        "which is an unexpected type.")
36d401
+                    content_type = f"unknown - {content_type}"
36d401
                 break
36d401
     except OSError:
36d401
         # 'oscap info' exitted with a non-zero exit code -> unknown doc
36d401
36d401
From b6bf5a6c96f5dbbd78043455802ebc0033cf1a6a Mon Sep 17 00:00:00 2001
36d401
From: Matej Tyc <matyc@redhat.com>
36d401
Date: Wed, 12 Oct 2022 11:38:51 +0200
36d401
Subject: [PATCH 3/5] Remove unused code
36d401
36d401
The function is not referenced anywhere in the project
36d401
---
36d401
 org_fedora_oscap/content_handling.py | 40 ----------------------------
36d401
 1 file changed, 40 deletions(-)
36d401
36d401
diff --git a/org_fedora_oscap/content_handling.py b/org_fedora_oscap/content_handling.py
36d401
index 3e2ecae..5096bab 100644
36d401
--- a/org_fedora_oscap/content_handling.py
36d401
+++ b/org_fedora_oscap/content_handling.py
36d401
@@ -141,43 +141,3 @@ def get_doc_type(file_path):
36d401
     log.info("OSCAP addon: Identified {file_path} as {content_type}"
36d401
              .format(file_path=file_path, content_type=content_type))
36d401
     return content_type
36d401
-
36d401
-
36d401
-def explore_content_files(fpaths):
36d401
-    """
36d401
-    Function for finding content files in a list of file paths. SIMPLY PICKS
36d401
-    THE FIRST USABLE CONTENT FILE OF A PARTICULAR TYPE AND JUST PREFERS DATA
36d401
-    STREAMS OVER STANDALONE BENCHMARKS.
36d401
-
36d401
-    :param fpaths: a list of file paths to search for content files in
36d401
-    :type fpaths: [str]
36d401
-    :return: ContentFiles instance containing the file names of the XCCDF file,
36d401
-        CPE dictionary and tailoring file or "" in place of those items
36d401
-        if not found
36d401
-    :rtype: ContentFiles
36d401
-
36d401
-    """
36d401
-    xccdf_file = ""
36d401
-    cpe_file = ""
36d401
-    tailoring_file = ""
36d401
-    found_ds = False
36d401
-
36d401
-    for fpath in fpaths:
36d401
-        doc_type = get_doc_type(fpath)
36d401
-        if not doc_type:
36d401
-            continue
36d401
-
36d401
-        # prefer DS over standalone XCCDF
36d401
-        if doc_type == "Source Data Stream" and (not xccdf_file or not found_ds):
36d401
-            xccdf_file = fpath
36d401
-            found_ds = True
36d401
-        elif doc_type == "XCCDF Checklist" and not xccdf_file:
36d401
-            xccdf_file = fpath
36d401
-        elif doc_type == "CPE Dictionary" and not cpe_file:
36d401
-            cpe_file = fpath
36d401
-        elif doc_type == "XCCDF Tailoring" and not tailoring_file:
36d401
-            tailoring_file = fpath
36d401
-
36d401
-    # TODO: raise exception if no xccdf_file is found?
36d401
-    files = ContentFiles(xccdf_file, cpe_file, tailoring_file)
36d401
-    return files
36d401
36d401
From a990568ccddb2864c8daeae91fdc1f6588b3c6f3 Mon Sep 17 00:00:00 2001
36d401
From: Matej Tyc <matyc@redhat.com>
36d401
Date: Thu, 13 Oct 2022 14:11:25 +0200
36d401
Subject: [PATCH 4/5] Dont use tailoring if it is not expected
36d401
36d401
Take tailorings into account only if it is specified in the kickstart.
36d401
Compulsive usage of tailoring may be unwanted.
36d401
---
36d401
 org_fedora_oscap/content_discovery.py | 17 +++++++++++++----
36d401
 1 file changed, 13 insertions(+), 4 deletions(-)
36d401
36d401
diff --git a/org_fedora_oscap/content_discovery.py b/org_fedora_oscap/content_discovery.py
36d401
index b20f3a6..e9cf34a 100644
36d401
--- a/org_fedora_oscap/content_discovery.py
36d401
+++ b/org_fedora_oscap/content_discovery.py
36d401
@@ -169,16 +169,25 @@ def _verify_fingerprint(self, dest_filename, fingerprint=""):
36d401
             msg = _(f"Integrity check of the content failed - {hash_obj.name} hash didn't match")
36d401
             raise content_handling.ContentCheckError(msg)
36d401
 
36d401
+    def allow_one_expected_tailoring_or_no_tailoring(self, labelled_files):
36d401
+        expected_tailoring = self._addon_data.tailoring_path
36d401
+        tailoring_label = CONTENT_TYPES["TAILORING"]
36d401
+        if expected_tailoring:
36d401
+            labelled_files = self.reduce_files(labelled_files, expected_tailoring, [tailoring_label])
36d401
+        else:
36d401
+            labelled_files = {
36d401
+                path: label for path, label in labelled_files.items()
36d401
+                if label != tailoring_label
36d401
+            }
36d401
+        return labelled_files
36d401
+
36d401
     def filter_discovered_content(self, labelled_files):
36d401
         expected_path = self._addon_data.content_path
36d401
         categories = (CONTENT_TYPES["DATASTREAM"], CONTENT_TYPES["XCCDF_CHECKLIST"])
36d401
         if expected_path:
36d401
             labelled_files = self.reduce_files(labelled_files, expected_path, categories)
36d401
 
36d401
-        expected_path = self._addon_data.tailoring_path
36d401
-        categories = (CONTENT_TYPES["TAILORING"], )
36d401
-        if expected_path:
36d401
-            labelled_files = self.reduce_files(labelled_files, expected_path, categories)
36d401
+        labelled_files = self.allow_one_expected_tailoring_or_no_tailoring(labelled_files)
36d401
 
36d401
         expected_path = self._addon_data.cpe_path
36d401
         categories = (CONTENT_TYPES["CPE_DICT"], )
36d401
36d401
From c4cb296ca3838a0967c8258b9ed5221691884a36 Mon Sep 17 00:00:00 2001
36d401
From: Matej Tyc <matyc@redhat.com>
36d401
Date: Tue, 8 Nov 2022 10:46:59 +0100
36d401
Subject: [PATCH 5/5] Make the content RPM installation robust
36d401
36d401
If a package manager fails to install the package,
36d401
use the rpm command directly and skip deps.
36d401
---
36d401
 org_fedora_oscap/ks/oscap.py | 41 ++++++++++++++++++++++++++++--------
36d401
 1 file changed, 32 insertions(+), 9 deletions(-)
36d401
36d401
diff --git a/org_fedora_oscap/ks/oscap.py b/org_fedora_oscap/ks/oscap.py
36d401
index e47d6ba..dac273d 100644
36d401
--- a/org_fedora_oscap/ks/oscap.py
36d401
+++ b/org_fedora_oscap/ks/oscap.py
36d401
@@ -23,6 +23,7 @@
36d401
 import shutil
36d401
 import re
36d401
 import os
36d401
+import io
36d401
 import time
36d401
 import logging
36d401
 import pathlib
36d401
@@ -473,6 +474,33 @@ def setup(self, storage, ksdata, payload):
36d401
             if pkg not in ksdata.packages.packageList:
36d401
                 ksdata.packages.packageList.append(pkg)
36d401
 
36d401
+    def _attempt_rpm_installation(self):
36d401
+        log.info("OSCAP addon: Installing the security content RPM to the installed system.")
36d401
+        stdout = io.StringIO()
36d401
+        ret = util.execWithRedirect(
36d401
+                "yum", ["-y", "--nogpg", "install", self.raw_postinst_content_path],
36d401
+                stdout=stdout, root=conf.target.system_root)
36d401
+        stdout.seek(0)
36d401
+        if ret != 0:
36d401
+            log.error(
36d401
+                "OSCAP addon: Error installing security content RPM using yum: {0}",
36d401
+                stdout.read())
36d401
+
36d401
+            stdout = io.StringIO()
36d401
+            ret = util.execWithRedirect(
36d401
+                    "rpm", ["--install", "--nodeps", self.raw_postinst_content_path],
36d401
+                    stdout=stdout, root=conf.target.system_root)
36d401
+            if ret != 0:
36d401
+                log.error(
36d401
+                    "OSCAP addon: Error installing security content RPM using rpm: {0}",
36d401
+                    stdout.read())
36d401
+                msg = _(f"Failed to install content RPM to the target system.")
36d401
+                raise RuntimeError(msg)
36d401
+
36d401
+    def _copy_rpm_to_target_and_install(self, target_content_dir):
36d401
+        shutil.copy2(self.raw_preinst_content_path, target_content_dir)
36d401
+        self._attempt_rpm_installation()
36d401
+
36d401
     def execute(self, storage, ksdata, users, payload):
36d401
         """
36d401
         The execute method that should make changes to the installed system. It
36d401
@@ -507,15 +535,10 @@ def execute(self, storage, ksdata, users, payload):
36d401
         if self.content_type == "datastream":
36d401
             shutil.copy2(self.preinst_content_path, target_content_dir)
36d401
         elif self.content_type == "rpm":
36d401
-            # copy the RPM to the target system
36d401
-            shutil.copy2(self.raw_preinst_content_path, target_content_dir)
36d401
-
36d401
-            # and install it with yum
36d401
-            ret = util.execInSysroot("yum", ["-y", "--nogpg", "install",
36d401
-                                             self.raw_postinst_content_path])
36d401
-            if ret != 0:
36d401
-                msg = _(f"Failed to install content RPM to the target system.")
36d401
-                self._terminate(msg)
36d401
+            try:
36d401
+                self._copy_rpm_to_target_and_install(target_content_dir)
36d401
+            except Exception as exc:
36d401
+                self._terminate(str(exc))
36d401
                 return
36d401
         elif self.content_type == "scap-security-guide":
36d401
             # nothing needed