From 39280870b836a6bcdf95fd79cd3e428e0d204055 Mon Sep 17 00:00:00 2001 From: rabbitali Date: Tue, 5 Sep 2023 11:30:58 +0800 Subject: [PATCH] adapted to cve rollback and update return value (cherry picked from commit e5c877ce2918b54bf529e7aba93d11e34e763438) --- ...cve-rollback-and-update-return-value.patch | 291 ++++++++++++++++++ aops-ceres.spec | 6 +- 2 files changed, 296 insertions(+), 1 deletion(-) create mode 100644 0003-adapted-to-cve-rollback-and-update-return-value.patch diff --git a/0003-adapted-to-cve-rollback-and-update-return-value.patch b/0003-adapted-to-cve-rollback-and-update-return-value.patch new file mode 100644 index 0000000..d1ded65 --- /dev/null +++ b/0003-adapted-to-cve-rollback-and-update-return-value.patch @@ -0,0 +1,291 @@ +From d7d97d24c0b2b7e0f5686fc9f9bd1674c94c53eb Mon Sep 17 00:00:00 2001 +From: rabbitali +Date: Mon, 4 Sep 2023 17:06:06 +0800 +Subject: [PATCH] adapted to cve rollback and update return value +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +--- + ceres/conf/constant.py | 5 ++ + ceres/function/status.py | 2 + + ceres/manages/vulnerability_manage.py | 114 ++++++++++++++------------ + 3 files changed, 68 insertions(+), 53 deletions(-) + +diff --git a/ceres/conf/constant.py b/ceres/conf/constant.py +index 8c7723b..79594ae 100644 +--- a/ceres/conf/constant.py ++++ b/ceres/conf/constant.py +@@ -70,3 +70,8 @@ REGISTER_HELP_INFO = """ + class CommandExitCode: + SUCCEED = 0 + FAIL = 255 ++ ++ ++class TaskExecuteRes: ++ SUCCEED = "succeed" ++ FAIL = "fail" +diff --git a/ceres/function/status.py b/ceres/function/status.py +index 70fc212..d1fe088 100644 +--- a/ceres/function/status.py ++++ b/ceres/function/status.py +@@ -25,6 +25,7 @@ REPO_CONTENT_INCORRECT = "Repo.Content.Incorrect" + REPO_NOT_SET = "Repo.Not.Set" + NO_COMMAND = "No.Command" + NOT_PATCH = "Not.Patch" ++PRE_CHECK_ERROR = "Pre.Check.Error" + + COMMAND_EXEC_ERROR = "Command.Error" + +@@ -49,6 +50,7 @@ class StatusCode: + NO_COMMAND: {"msg": "command not found"}, + NOT_PATCH: {"msg": "no valid hot patch is matched"}, + COMMAND_EXEC_ERROR: {"msg": "the input command is incorrect"}, ++ PRE_CHECK_ERROR: {"msg": "Preset check item detection failed"}, + } + + @classmethod +diff --git a/ceres/manages/vulnerability_manage.py b/ceres/manages/vulnerability_manage.py +index 747df61..20bfe1e 100644 +--- a/ceres/manages/vulnerability_manage.py ++++ b/ceres/manages/vulnerability_manage.py +@@ -16,19 +16,10 @@ import json + from collections import defaultdict + from typing import Dict, List, Tuple + +-from ceres.conf.constant import REPO_ID_FOR_CVE_MANAGE, CommandExitCode ++from ceres.conf.constant import REPO_ID_FOR_CVE_MANAGE, CommandExitCode, TaskExecuteRes + from ceres.function.check import PreCheck + from ceres.function.log import LOGGER +-from ceres.function.status import ( +- FAIL, +- NOT_PATCH, +- PARAM_ERROR, +- REPO_CONTENT_INCORRECT, +- REPO_NOT_SET, +- SUCCESS, +- StatusCode, +- COMMAND_EXEC_ERROR, +-) ++from ceres.function.status import * + from ceres.function.util import execute_shell_command + + +@@ -131,7 +122,7 @@ class VulnerabilityManage: + cve_scan_result["check_items"] = items_check_log + if not check_result: + LOGGER.info("The pre-check is failed before execute command!") +- return FAIL, cve_scan_result ++ return PRE_CHECK_ERROR, cve_scan_result + + self.installed_rpm_info = self._query_installed_rpm() + self.available_rpm_info = self._query_available_rpm() +@@ -200,7 +191,7 @@ class VulnerabilityManage: + # kernel-debuginfo.x86_64 5.10.0-60.105.0.132.oe2203 update + # kernel-debugsource.x86_64 5.10.0-60.105.0.132.oe2203 update + # kernel-devel.x86_64 5.10.0-60.105.0.132.oe2203 update +- code, stdout, stderr = execute_shell_command("dnf list available") ++ code, stdout, stderr = execute_shell_command("dnf list available|grep -v '.src'") + if code != CommandExitCode.SUCCEED: + LOGGER.error(stderr) + return result +@@ -435,7 +426,7 @@ class VulnerabilityManage: + if not applied_hotpatch_info_list: + return result + +- record_key_set = {} ++ record_key_set = set() + for cve_id, patch_name, hotpatch_status in applied_hotpatch_info_list: + rpm = patch_name.split("-", 1)[0] + # Refer to this example, the CVE can be marked as fixed only if all hotpatch are applied. +@@ -513,10 +504,10 @@ class VulnerabilityManage: + def gen_failed_result(cves: dict, log: str): + result = [] + for cve in cves: +- cve_fix_result = {"cve_id": cve.get("cve_id"), "result": "failed", "rpms": []} ++ cve_fix_result = {"cve_id": cve.get("cve_id"), "result": TaskExecuteRes.FAIL, "rpms": []} + for rpm in cve.get("rpms"): + cve_fix_result["rpms"].append( +- {"installed_rpm": rpm.get("installed_rpm"), "result": "fail", "log": log} ++ {"installed_rpm": rpm.get("installed_rpm"), "result": TaskExecuteRes.FAIL, "log": log} + ) + result.append(cve_fix_result) + return result +@@ -528,14 +519,14 @@ class VulnerabilityManage: + if not check_result: + LOGGER.info("The pre-check is failed before execute command!") + result["cves"] = gen_failed_result(unfixed_cve_info.get("cves"), "pre-check items check failed") +- return FAIL, result ++ return PRE_CHECK_ERROR, result + + all_cve_fix_result, all_cve_fix_info = [], [] + for cve_info in unfixed_cve_info.get("cves"): + rpms_fix_result, rpm_fix_result_list = [], [] + for rpm_info in cve_info.get("rpms"): + update_result, log = self._update_rpm_by_dnf(rpm_info) +- rpms_fix_result.append(update_result == SUCCESS) ++ rpms_fix_result.append(update_result == TaskExecuteRes.SUCCEED) + rpm_fix_result_list.append( + {"installed_rpm": rpm_info.get("installed_rpm"), "result": update_result, "log": log} + ) +@@ -544,13 +535,13 @@ class VulnerabilityManage: + all_cve_fix_info.append( + { + "cve_id": cve_info.get("cve_id"), +- "result": SUCCESS if all(rpms_fix_result) else FAIL, ++ "result": TaskExecuteRes.SUCCEED if all(rpms_fix_result) else TaskExecuteRes.FAIL, + "rpms": rpm_fix_result_list, + } + ) + + result.update({"cves": all_cve_fix_info}) +- return SUCCESS if all(all_cve_fix_result) else FAIL, result ++ return TaskExecuteRes.SUCCEED if all(all_cve_fix_result) else TaskExecuteRes.FAIL, result + + def _update_rpm_by_dnf(self, cve: dict) -> Tuple[str, str]: + """ +@@ -587,10 +578,10 @@ class VulnerabilityManage: + """ + code, stdout, stderr = execute_shell_command(f"dnf update {rpm_name} -y") + if code != CommandExitCode.SUCCEED: +- return FAIL, stderr ++ return TaskExecuteRes.FAIL, stderr + if "Complete" not in stdout: +- return FAIL, stdout +- return SUCCESS, stdout ++ return TaskExecuteRes.FAIL, stdout ++ return TaskExecuteRes.SUCCEED, stdout + + def _update_hotpatch_by_dnf_plugin(self, hotpatch_pkg: str) -> Tuple[str, str]: + """ +@@ -611,10 +602,10 @@ class VulnerabilityManage: + + code, stdout, stderr = execute_shell_command(update_command) + if code != CommandExitCode.SUCCEED: +- return FAIL, stderr ++ return TaskExecuteRes.FAIL, stderr + + if "Apply hot patch succeed" not in stdout and "No hot patches marked for install" not in stdout: +- return FAIL, stdout ++ return TaskExecuteRes.FAIL, stdout + + if not self.takeover and self.accepted: + try: +@@ -626,7 +617,7 @@ class VulnerabilityManage: + LOGGER.error(error) + stdout += "\n" + "hotpatch status set failed due to can't get correct hotpatch name!" + +- return SUCCESS, stdout ++ return TaskExecuteRes.SUCCEED, stdout + + @staticmethod + def _set_hotpatch_status_by_dnf_plugin(hotpatch: str, operation: str) -> Tuple[bool, str]: +@@ -703,25 +694,47 @@ class VulnerabilityManage: + log = "No valid hot patch is matched." + return NOT_PATCH, [dict(cve_id=cve["cve_id"], log=log, result="fail") for cve in cves] + +- cmd_execute_result = [] +- for base_pkg, hotpatch_cves in hotpatch_list.items(): +- rollback_result, log = self._hotpatch_rollback(base_pkg) +- result = 'succeed' if rollback_result else 'fail' +- cmd_execute_result.extend([dict(cve_id=cve, log=log, result=result) for cve in hotpatch_cves]) ++ wait_to_rollback_patch = [] ++ for cve_info in cves: ++ wait_to_rollback_patch.extend(hotpatch_list.get(cve_info["cve_id"], [])) + +- not_rollback_cve = set([cve["cve_id"] for cve in cves]).difference( +- set([cve_info["cve_id"] for cve_info in cmd_execute_result]) +- ) ++ hotpatch_rollback_res = {} ++ for patch in set(wait_to_rollback_patch): ++ rollback_result, log = self._hotpatch_rollback(patch) ++ hotpatch_rollback_res[patch] = { ++ "result": TaskExecuteRes.SUCCEED if rollback_result else TaskExecuteRes.FAIL, ++ "log": log, ++ } ++ ++ cve_rollback_result = [] ++ ++ for cve_info in cves: ++ if cve_info["cve_id"] not in hotpatch_list: ++ fail_result = { ++ "cve_id": cve_info["cve_id"], ++ "log": "No valid hot patch is matched." ++ if cve_info["hotpatch"] ++ else "Cold patch rollback is not supported.", ++ "result": "fail", ++ } ++ cve_rollback_result.append(fail_result) ++ else: ++ tmp_result_list = [] ++ tmp_log = [] + +- if not_rollback_cve: +- for cve in [cve for cve in cves if cve["cve_id"] in not_rollback_cve]: +- if cve["hotpatch"]: +- log = "No valid hot patch is matched." +- else: +- log = "Cold patch rollback is not supported." +- cmd_execute_result.append(dict(cve_id=cve["cve_id"], log=log, result="fail")) ++ for patch in hotpatch_list.get(cve_info["cve_id"]): ++ tmp_result_list.append(hotpatch_rollback_res[patch]["result"] == TaskExecuteRes.SUCCEED) ++ tmp_log.append(hotpatch_rollback_res[patch]["log"]) + +- return SUCCESS, cmd_execute_result ++ cve_rollback_result.append( ++ { ++ "cve_id": cve_info["cve_id"], ++ "log": "\n".join(tmp_log), ++ "result": TaskExecuteRes.SUCCEED if all(tmp_result_list) else TaskExecuteRes.FAIL, ++ } ++ ) ++ ++ return SUCCESS, cve_rollback_result + + @staticmethod + def _hotpatch_list_cve() -> dict: +@@ -731,15 +744,14 @@ class VulnerabilityManage: + Returns: + dict: e.g + { +- "base-pkg/hotpatch-1": [cve_id1,cveid2], +- "base-pkg/hotpatch-2": [cve_id1,cveid2] ++ "CVE-XXXX-XXX": ["patch 1", "patch 2"] + } + """ + # Run the dnf command to query the hotpatch list,e.g + # Last metadata expiration check: +- # CVE id base-pkg/hotpatch status +- # CVE-1 A-1.1-1/HP1 ACTIVED +- # CVE-2 A-1.1-1/HP1 ACTIVED ++ # CVE id base-pkg/hotpatch status ++ # CVE-1 A-1.1-1/ACC-1-1/binary_file1 ACTIVED ++ # CVE-2 A-1.1-1/ACC-1-1/binary_file2 ACTIVED + code, hotpatch_list_output, _ = execute_shell_command(f"dnf hotpatch --list cve") + if code != CommandExitCode.SUCCEED: + LOGGER.error(f"Failed to hotpatch list cve.") +@@ -754,7 +766,7 @@ class VulnerabilityManage: + cve_id, base_pkg, status = [info.strip() for info in hotpatch_info.split()] + if status != "ACTIVED" and status != "ACCEPTED": + continue +- hotpatch_list[base_pkg].append(cve_id) ++ hotpatch_list[cve_id].append(base_pkg) + + return hotpatch_list + +@@ -765,11 +777,7 @@ class VulnerabilityManage: + Args: + cve_id: cve is rolled back + """ +- execute_result, hotpatch_release_info = self._hotpatch_info(base_pkg_hotpatch) +- if not execute_result: +- return False, "Failed to query patch information." +- +- hotpatch_name = "patch-%s-%s-%s-%s" % tuple(base_pkg_hotpatch.split("/") + list(hotpatch_release_info.values())) ++ hotpatch_name = "patch-%s-%s" % tuple(base_pkg_hotpatch.rsplit("/", 2)[:2]) + + _, stdout, stderr = execute_shell_command(f"dnf remove {hotpatch_name} -y") + return True, stdout + stderr +-- +Gitee + diff --git a/aops-ceres.spec b/aops-ceres.spec index 668c6b9..eb14b67 100644 --- a/aops-ceres.spec +++ b/aops-ceres.spec @@ -1,12 +1,13 @@ Name: aops-ceres Version: v1.3.0 -Release: 3 +Release: 4 Summary: An agent which needs to be adopted in client, it managers some plugins, such as gala-gopher(kpi collection), fluentd(log collection) and so on. License: MulanPSL2 URL: https://gitee.com/openeuler/%{name} Source0: %{name}-%{version}.tar.gz Patch0001: 0001-fix-bug-repeated-display-of-vulnerabilities.patch Patch0002: 0002-update-query-disk-info-func.patch +Patch0003: 0003-adapted-to-cve-rollback-and-update-return-value.patch BuildRequires: python3-setuptools Requires: python3-requests python3-jsonschema python3-libconf @@ -41,6 +42,9 @@ An agent which needs to be adopted in client, it managers some plugins, such as %changelog +* Tue Sep 05 2023 wenxin - v1.3.0-4 +- adapted to cve rollback and update return value + * Wed Aug 30 2023 wenxin - v1.3.0-3 - update query disk info func -- Gitee