From 9e8ba78f88411910ba2b81338e4f4ce01c8547b6 Mon Sep 17 00:00:00 2001 From: CentOS Sources Date: Tue, 26 Apr 2022 16:10:58 +0000 Subject: [PATCH] import oscap-anaconda-addon-1.2.1-6.el8 --- ...-addon-1.2.2-absent_appstream-PR_184.patch | 206 ++++++++++++++++++ ...naconda-addon-1.2.2-firstboot-PR_190.patch | 167 ++++++++++++++ ...anaconda-addon-1.3.0-fix_dsid-PR_194.patch | 72 ++++++ SPECS/oscap-anaconda-addon.spec | 14 +- 4 files changed, 458 insertions(+), 1 deletion(-) create mode 100644 SOURCES/oscap-anaconda-addon-1.2.2-absent_appstream-PR_184.patch create mode 100644 SOURCES/oscap-anaconda-addon-1.2.2-firstboot-PR_190.patch create mode 100644 SOURCES/oscap-anaconda-addon-1.3.0-fix_dsid-PR_194.patch diff --git a/SOURCES/oscap-anaconda-addon-1.2.2-absent_appstream-PR_184.patch b/SOURCES/oscap-anaconda-addon-1.2.2-absent_appstream-PR_184.patch new file mode 100644 index 0000000..7bd79c3 --- /dev/null +++ b/SOURCES/oscap-anaconda-addon-1.2.2-absent_appstream-PR_184.patch @@ -0,0 +1,206 @@ +From 8eacfad08b3c27aa9510f2c3337356581bd9bebd Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Mat=C4=9Bj=20T=C3=BD=C4=8D?= +Date: Mon, 3 Jan 2022 17:31:49 +0100 +Subject: [PATCH 1/3] Add oscap sanity check before attempting remediation + +If something is obviously wrong with the scanner, then don't attempt to remediate +and try to show relevant information in a dialog window. +--- + org_fedora_oscap/common.py | 39 ++++++++++++++++++++++++++++-------- + org_fedora_oscap/ks/oscap.py | 11 ++++++++++ + tests/test_common.py | 8 ++++++++ + 3 files changed, 50 insertions(+), 8 deletions(-) + +diff --git a/org_fedora_oscap/common.py b/org_fedora_oscap/common.py +index 884bbc8..05829ce 100644 +--- a/org_fedora_oscap/common.py ++++ b/org_fedora_oscap/common.py +@@ -139,7 +139,8 @@ def execute(self, ** kwargs): + proc = subprocess.Popen(self.args, stdout=subprocess.PIPE, + stderr=subprocess.PIPE, ** kwargs) + except OSError as oserr: +- msg = "Failed to run the oscap tool: %s" % oserr ++ msg = ("Failed to execute command '{command_string}': {oserr}" ++ .format(command_string=command_string, oserr=oserr)) + raise OSCAPaddonError(msg) + + (stdout, stderr) = proc.communicate() +@@ -215,6 +216,34 @@ def _run_oscap_gen_fix(profile, fpath, template, ds_id="", xccdf_id="", + return proc.stdout + + ++def do_chroot(chroot): ++ """Helper function doing the chroot if requested.""" ++ if chroot and chroot != "/": ++ os.chroot(chroot) ++ os.chdir("/") ++ ++ ++def assert_scanner_works(chroot, executable="oscap"): ++ args = [executable, "--version"] ++ command = " ".join(args) ++ ++ try: ++ proc = subprocess.Popen( ++ args, preexec_fn=lambda: do_chroot(chroot), ++ stdout=subprocess.PIPE, stderr=subprocess.PIPE) ++ (stdout, stderr) = proc.communicate() ++ stderr = stderr.decode(errors="replace") ++ except OSError as exc: ++ msg = _(f"Basic invocation '{command}' fails: {str(exc)}") ++ raise OSCAPaddonError(msg) ++ if proc.returncode != 0: ++ msg = _( ++ f"Basic scanner invocation '{command}' exited " ++ "with non-zero error code {proc.returncode}: {stderr}") ++ raise OSCAPaddonError(msg) ++ return True ++ ++ + def run_oscap_remediate(profile, fpath, ds_id="", xccdf_id="", tailoring="", + chroot=""): + """ +@@ -244,12 +273,6 @@ def run_oscap_remediate(profile, fpath, ds_id="", xccdf_id="", tailoring="", + if not profile: + return "" + +- def do_chroot(): +- """Helper function doing the chroot if requested.""" +- if chroot and chroot != "/": +- os.chroot(chroot) +- os.chdir("/") +- + # make sure the directory for the results exists + results_dir = os.path.dirname(RESULTS_PATH) + if chroot: +@@ -274,7 +297,7 @@ def do_chroot(): + args.append(fpath) + + proc = SubprocessLauncher(args) +- proc.execute(preexec_fn=do_chroot) ++ proc.execute(preexec_fn=lambda: do_chroot(chroot)) + proc.log_messages() + + if proc.returncode not in (0, 2): +diff --git a/org_fedora_oscap/ks/oscap.py b/org_fedora_oscap/ks/oscap.py +index 65d74cf..da1600f 100644 +--- a/org_fedora_oscap/ks/oscap.py ++++ b/org_fedora_oscap/ks/oscap.py +@@ -488,6 +488,17 @@ def execute(self, storage, ksdata, users, payload): + # selected + return + ++ try: ++ common.assert_scanner_works( ++ chroot=conf.target.system_root, executable="oscap") ++ except Exception as exc: ++ msg_lines = [_( ++ "The 'oscap' scanner doesn't work in the installed system: {error}" ++ .format(error=str(exc)))] ++ msg_lines.append(_("As a result, the installed system can't be hardened.")) ++ self._terminate("\n".join(msg_lines)) ++ return ++ + target_content_dir = utils.join_paths(conf.target.system_root, + common.TARGET_CONTENT_DIR) + utils.ensure_dir_exists(target_content_dir) +diff --git a/tests/test_common.py b/tests/test_common.py +index 9f7a16a..4f25379 100644 +--- a/tests/test_common.py ++++ b/tests/test_common.py +@@ -77,6 +77,14 @@ def _run_oscap(mock_subprocess, additional_args): + return expected_args, kwargs + + ++def test_oscap_works(): ++ assert common.assert_scanner_works(chroot="/") ++ with pytest.raises(common.OSCAPaddonError, match="No such file"): ++ common.assert_scanner_works(chroot="/", executable="i_dont_exist") ++ with pytest.raises(common.OSCAPaddonError, match="non-zero"): ++ common.assert_scanner_works(chroot="/", executable="false") ++ ++ + def test_run_oscap_remediate_profile_only(mock_subprocess, monkeypatch): + return run_oscap_remediate_profile( + mock_subprocess, monkeypatch, + +From b54cf2bddba56e5b776fb60514a5e29d47c74cac Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Mat=C4=9Bj=20T=C3=BD=C4=8D?= +Date: Mon, 3 Jan 2022 17:42:31 +0100 +Subject: [PATCH 2/3] Don't raise exceptions in execute() + +Those result in tracebacks during the installation, +while a dialog window presents a more useful form of user interaction. +--- + org_fedora_oscap/ks/oscap.py | 18 ++++++++++++------ + 1 file changed, 12 insertions(+), 6 deletions(-) + +diff --git a/org_fedora_oscap/ks/oscap.py b/org_fedora_oscap/ks/oscap.py +index da1600f..d3f0dbe 100644 +--- a/org_fedora_oscap/ks/oscap.py ++++ b/org_fedora_oscap/ks/oscap.py +@@ -513,8 +513,9 @@ def execute(self, storage, ksdata, users, payload): + ret = util.execInSysroot("yum", ["-y", "--nogpg", "install", + self.raw_postinst_content_path]) + if ret != 0: +- raise common.ExtractionError("Failed to install content " +- "RPM to the target system") ++ msg = _(f"Failed to install content RPM to the target system.") ++ self._terminate(msg) ++ return + elif self.content_type == "scap-security-guide": + # nothing needed + pass +@@ -525,10 +526,15 @@ def execute(self, storage, ksdata, users, payload): + if os.path.exists(self.preinst_tailoring_path): + shutil.copy2(self.preinst_tailoring_path, target_content_dir) + +- common.run_oscap_remediate(self.profile_id, self.postinst_content_path, +- self.datastream_id, self.xccdf_id, +- self.postinst_tailoring_path, +- chroot=conf.target.system_root) ++ try: ++ common.run_oscap_remediate(self.profile_id, self.postinst_content_path, ++ self.datastream_id, self.xccdf_id, ++ self.postinst_tailoring_path, ++ chroot=conf.target.system_root) ++ except Exception as exc: ++ msg = _(f"Something went wrong during the final hardening: {str(exc)}.") ++ self._terminate(msg) ++ return + + def clear_all(self): + """Clear all the stored values.""" + +From 00d770d1b7f8e1f0734e93da227f1c3e445033c8 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Mat=C4=9Bj=20T=C3=BD=C4=8D?= +Date: Mon, 3 Jan 2022 17:44:12 +0100 +Subject: [PATCH 3/3] Change the error feedback based on the installation mode + +The original approach was confusing, because non-interactive installs run without any user input, +and the message assumed that the user is able to answer installer's questions. +--- + org_fedora_oscap/ks/oscap.py | 5 +++-- + 1 file changed, 3 insertions(+), 2 deletions(-) + +diff --git a/org_fedora_oscap/ks/oscap.py b/org_fedora_oscap/ks/oscap.py +index d3f0dbe..ef34448 100644 +--- a/org_fedora_oscap/ks/oscap.py ++++ b/org_fedora_oscap/ks/oscap.py +@@ -372,13 +372,14 @@ def postinst_tailoring_path(self): + self.tailoring_path) + + def _terminate(self, message): +- message += "\n" + _("The installation should be aborted.") +- message += " " + _("Do you wish to continue anyway?") + if flags.flags.automatedInstall and not flags.flags.ksprompt: + # cannot have ask in a non-interactive kickstart + # installation ++ message += "\n" + _("Aborting the installation.") + raise errors.CmdlineError(message) + ++ message += "\n" + _("The installation should be aborted.") ++ message += " " + _("Do you wish to continue anyway?") + answ = errors.errorHandler.ui.showYesNoQuestion(message) + if answ == errors.ERROR_CONTINUE: + # prevent any futher actions here by switching to the dry diff --git a/SOURCES/oscap-anaconda-addon-1.2.2-firstboot-PR_190.patch b/SOURCES/oscap-anaconda-addon-1.2.2-firstboot-PR_190.patch new file mode 100644 index 0000000..3f1dd50 --- /dev/null +++ b/SOURCES/oscap-anaconda-addon-1.2.2-firstboot-PR_190.patch @@ -0,0 +1,167 @@ +From 6243c5621a6b76a4652ea68437f37d3b15bc93c7 Mon Sep 17 00:00:00 2001 +From: Matej Tyc +Date: Fri, 14 Jan 2022 17:24:00 +0100 +Subject: [PATCH] Add support for the firstboot remediation + +OpenSCAP can support a new remediation type - +a classical scan + remediate that is executed during the first boot. +This PR adds support for this functionality, +and allows to control it via a remediate kickstart property. +--- + org_fedora_oscap/common.py | 41 ++++++++++++++++++++++++++++++ + org_fedora_oscap/ks/oscap.py | 48 ++++++++++++++++++++++++++++-------- + 2 files changed, 79 insertions(+), 10 deletions(-) + +diff --git a/org_fedora_oscap/common.py b/org_fedora_oscap/common.py +index 05829ce..663c526 100644 +--- a/org_fedora_oscap/common.py ++++ b/org_fedora_oscap/common.py +@@ -29,6 +29,7 @@ + import subprocess + import zipfile + import tarfile ++import textwrap + + import cpioarchive + import re +@@ -309,6 +310,46 @@ def run_oscap_remediate(profile, fpath, ds_id="", xccdf_id="", tailoring="", + return proc.stdout + + ++def _schedule_firstboot_remediation( ++ chroot, profile, ds_path, results_path, report_path, ds_id, xccdf_id, tailoring_path): ++ config = textwrap.dedent(f"""\ ++ OSCAP_REMEDIATE_DS='{ds_path}' ++ OSCAP_REMEDIATE_PROFILE_ID='{profile}' ++ OSCAP_REMEDIATE_ARF_RESULT='{results_path}' ++ OSCAP_REMEDIATE_HTML_REPORT='{report_path}' ++ """) ++ if ds_id: ++ config += "OSCAP_REMEDIATE_DATASTREAM_ID='{ds_id}'\n" ++ if xccdf_id: ++ config += "OSCAP_REMEDIATE_XCCDF_ID='{xccdf_id}'\n" ++ if tailoring_path: ++ config += "OSCAP_REMEDIATE_TAILORING='{tailoring_path}'\n" ++ ++ relative_filename = "var/tmp/oscap-remediate-offline.conf.sh" ++ local_config_filename = f"/{relative_filename}" ++ chroot_config_filename = os.path.join(chroot, relative_filename) ++ with open(chroot_config_filename, "w") as f: ++ f.write(config) ++ os.symlink(local_config_filename, ++ os.path.join(chroot, "system-update")) ++ ++ ++def schedule_firstboot_remediation(chroot, profile, fpath, ds_id="", xccdf_id="", tailoring=""): ++ if not profile: ++ return "" ++ ++ # make sure the directory for the results exists ++ results_dir = os.path.dirname(RESULTS_PATH) ++ results_dir = os.path.normpath(chroot + "/" + results_dir) ++ utils.ensure_dir_exists(results_dir) ++ ++ log.info("OSCAP addon: Scheduling firstboot remediation") ++ _schedule_firstboot_remediation( ++ chroot, profile, fpath, RESULTS_PATH, REPORT_PATH, ds_id, xccdf_id, tailoring) ++ ++ return "" ++ ++ + def extract_data(archive, out_dir, ensure_has_files=None): + """ + Fuction that extracts the given archive to the given output directory. It +diff --git a/org_fedora_oscap/ks/oscap.py b/org_fedora_oscap/ks/oscap.py +index ef34448..8fef2aa 100644 +--- a/org_fedora_oscap/ks/oscap.py ++++ b/org_fedora_oscap/ks/oscap.py +@@ -54,7 +54,7 @@ + # LABEL:?, hdaX:?, + ) + +-REQUIRED_PACKAGES = ("openscap", "openscap-scanner", ) ++REQUIRED_PACKAGES = ("openscap", "openscap-scanner", "openscap-utils",) + + FINGERPRINT_REGEX = re.compile(r'^[a-z0-9]+$') + +@@ -100,6 +100,9 @@ def __init__(self, name, just_clear=False): + # certificate to verify HTTPS connection or signed data + self.certificates = "" + ++ # What remediation(s) to execute ++ self.remediate = "" ++ + # internal values + self.rule_data = rule_handling.RuleData() + self.dry_run = False +@@ -145,6 +148,9 @@ def key_value_pair(key, value, indent=4): + if self.certificates: + ret += "\n%s" % key_value_pair("certificates", self.certificates) + ++ if self.remediate: ++ ret += "\n%s" % key_value_pair("remediate", self.remediate) ++ + ret += "\n%end\n\n" + return ret + +@@ -203,6 +209,10 @@ def _parse_fingerprint(self, value): + def _parse_certificates(self, value): + self.certificates = value + ++ def _parse_remediate(self, value): ++ assert value in ("none", "post", "firstboot", "both") ++ self.remediate = value ++ + def handle_line(self, line): + """ + The handle_line method that is called with every line from this addon's +@@ -224,6 +234,7 @@ def handle_line(self, line): + "tailoring-path": self._parse_tailoring_path, + "fingerprint": self._parse_fingerprint, + "certificates": self._parse_certificates, ++ "remediate": self._parse_remediate, + } + + line = line.strip() +@@ -527,15 +538,32 @@ def execute(self, storage, ksdata, users, payload): + if os.path.exists(self.preinst_tailoring_path): + shutil.copy2(self.preinst_tailoring_path, target_content_dir) + +- try: +- common.run_oscap_remediate(self.profile_id, self.postinst_content_path, +- self.datastream_id, self.xccdf_id, +- self.postinst_tailoring_path, +- chroot=conf.target.system_root) +- except Exception as exc: +- msg = _(f"Something went wrong during the final hardening: {str(exc)}.") +- self._terminate(msg) +- return ++ if self.remediate in ("", "post", "both"): ++ try: ++ common.run_oscap_remediate(self.profile_id, self.postinst_content_path, ++ self.datastream_id, self.xccdf_id, ++ self.postinst_tailoring_path, ++ chroot=conf.target.system_root) ++ except Exception as exc: ++ msg = _(f"Something went wrong during the final hardening: {str(exc)}.") ++ self._terminate(msg) ++ return ++ ++ if self.remediate in ("", "firstboot", "both"): ++ try: ++ common.schedule_firstboot_remediation( ++ conf.target.system_root, ++ self.profile_id, ++ self.postinst_content_path, ++ self.datastream_id, ++ self.xccdf_id, ++ self.postinst_tailoring_path, ++ ) ++ except Exception as exc: ++ msg = _("Something went wrong when scheduling the first-boot remediation: {exc}." ++ .format(exc=str(exc))) ++ terminate(msg) ++ return + + def clear_all(self): + """Clear all the stored values.""" diff --git a/SOURCES/oscap-anaconda-addon-1.3.0-fix_dsid-PR_194.patch b/SOURCES/oscap-anaconda-addon-1.3.0-fix_dsid-PR_194.patch new file mode 100644 index 0000000..b355919 --- /dev/null +++ b/SOURCES/oscap-anaconda-addon-1.3.0-fix_dsid-PR_194.patch @@ -0,0 +1,72 @@ +From 353b2782ac4ec71c1f815915e03cefec075a5a3a Mon Sep 17 00:00:00 2001 +From: Matej Tyc +Date: Wed, 9 Mar 2022 11:36:44 +0100 +Subject: [PATCH] Fix firstboot remediation setup + +Expand all string substitutions, and +add a test that performs a basic sanity check of the generated config. +--- + org_fedora_oscap/common.py | 16 +++++++++++----- + tests/test_common.py | 15 +++++++++++++++ + 2 files changed, 26 insertions(+), 5 deletions(-) + +diff --git a/org_fedora_oscap/common.py b/org_fedora_oscap/common.py +index 663c526..99a3fbd 100644 +--- a/org_fedora_oscap/common.py ++++ b/org_fedora_oscap/common.py +@@ -310,8 +310,8 @@ def run_oscap_remediate(profile, fpath, ds_id="", xccdf_id="", tailoring="", + return proc.stdout + + +-def _schedule_firstboot_remediation( +- chroot, profile, ds_path, results_path, report_path, ds_id, xccdf_id, tailoring_path): ++def _create_firstboot_config_string( ++ profile, ds_path, results_path, report_path, ds_id, xccdf_id, tailoring_path): + config = textwrap.dedent(f"""\ + OSCAP_REMEDIATE_DS='{ds_path}' + OSCAP_REMEDIATE_PROFILE_ID='{profile}' +@@ -319,12 +319,18 @@ def _schedule_firstboot_remediation( + OSCAP_REMEDIATE_HTML_REPORT='{report_path}' + """) + if ds_id: +- config += "OSCAP_REMEDIATE_DATASTREAM_ID='{ds_id}'\n" ++ config += f"OSCAP_REMEDIATE_DATASTREAM_ID='{ds_id}'\n" + if xccdf_id: +- config += "OSCAP_REMEDIATE_XCCDF_ID='{xccdf_id}'\n" ++ config += f"OSCAP_REMEDIATE_XCCDF_ID='{xccdf_id}'\n" + if tailoring_path: +- config += "OSCAP_REMEDIATE_TAILORING='{tailoring_path}'\n" ++ config += f"OSCAP_REMEDIATE_TAILORING='{tailoring_path}'\n" ++ return config ++ + ++def _schedule_firstboot_remediation( ++ chroot, profile, ds_path, results_path, report_path, ds_id, xccdf_id, tailoring_path): ++ config = _create_firstboot_config_string( ++ profile, ds_path, results_path, report_path, ds_id, xccdf_id, tailoring_path) + relative_filename = "var/tmp/oscap-remediate-offline.conf.sh" + local_config_filename = f"/{relative_filename}" + chroot_config_filename = os.path.join(chroot, relative_filename) +diff --git a/tests/test_common.py b/tests/test_common.py +index 4f25379..d39898a 100644 +--- a/tests/test_common.py ++++ b/tests/test_common.py +@@ -274,3 +274,18 @@ def test_extract_tailoring_rpm_ensure_filename_there(): + in str(excinfo.value) + + shutil.rmtree(temp_path) ++ ++ ++def test_firstboot_config(): ++ config_args = dict( ++ profile="@PROFILE@", ++ ds_path="@DS_PATH@", ++ results_path="@RES_PATH@", ++ report_path="@REP_PATH", ++ ds_id="@DS_ID@", ++ xccdf_id="@XCCDF_ID@", ++ tailoring_path="@TAIL_PATH@", ++ ) ++ config_string = common._create_firstboot_config_string(** config_args) ++ for arg in config_args.values(): ++ assert arg in config_string diff --git a/SPECS/oscap-anaconda-addon.spec b/SPECS/oscap-anaconda-addon.spec index d187c88..10b7f8e 100644 --- a/SPECS/oscap-anaconda-addon.spec +++ b/SPECS/oscap-anaconda-addon.spec @@ -3,7 +3,7 @@ Name: oscap-anaconda-addon Version: 1.2.1 -Release: 5%{?dist} +Release: 6%{?dist} Summary: Anaconda addon integrating OpenSCAP to the installation process License: GPLv2+ @@ -21,6 +21,9 @@ Source0: %{name}-%{version}.tar.gz Patch1: lang.patch Patch2: oscap-anaconda-addon-1.2.2-content_ident-PR_167.patch Patch3: oscap-anaconda-addon-1.2.2-deep_archives-PR_168.patch +Patch4: oscap-anaconda-addon-1.2.2-absent_appstream-PR_184.patch +Patch5: oscap-anaconda-addon-1.2.2-firstboot-PR_190.patch +Patch6: oscap-anaconda-addon-1.3.0-fix_dsid-PR_194.patch BuildArch: noarch BuildRequires: make @@ -49,6 +52,9 @@ content. %patch1 -p1 %patch2 -p1 %patch3 -p1 +%patch4 -p1 +%patch5 -p1 +%patch6 -p1 # NOTE CONCERNING TRANSLATION PATCHES # When preparing translation patches, don't consider that some languages are unsupported - # we aim to include all applicable translation texts to the appropriate patch. @@ -70,6 +76,12 @@ make install DESTDIR=%{buildroot} %doc COPYING ChangeLog README.md %changelog +* Mon Mar 21 2022 Matej Tyc - 1.2.1-6 +- Introduce the firstboot remediation + Resolves: rhbz#1834716 +- Add better error handling of installation using unsupported installation sources + Resolves: rhbz#2007981 + * Fri Jan 21 2022 Matej Tyc - 1.2.1-5 - Updated translations Resolves: rhbz#2017356