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

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