主机及主机组管理、hotpatch插件、定时下载安全公告、定时漏洞扫描、数据矫正

This commit is contained in:
gongzt 2023-04-17 21:38:42 +08:00
parent 9c9b6162b0
commit c6f1d0e390
7 changed files with 12 additions and 1446 deletions

View File

@ -1,252 +0,0 @@
From ff5a842960179f8399434cfd36caeed23bb5c218 Mon Sep 17 00:00:00 2001
From: young <954906362@qq.com>
Date: Wed, 14 Dec 2022 21:36:46 +0800
Subject: [PATCH 1/3] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=B8=8A=E4=BC=A0?=
=?UTF-8?q?=E6=96=87=E4=BB=B6=E5=92=8C=E6=8E=A5=E5=8F=A3=E4=B8=8D=E4=B8=80?=
=?UTF-8?q?=E8=87=B4=E4=BD=86=E6=B2=A1=E6=8A=A5=E9=94=99=E7=9A=84=E9=97=AE?=
=?UTF-8?q?=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../cve_handler/manager/parse_advisory.py | 9 ++++++---
.../cve_handler/manager/parse_unaffected.py | 8 ++++++++
apollo/handler/cve_handler/view.py | 18 ++++++++++++++----
3 files changed, 28 insertions(+), 7 deletions(-)
diff --git a/apollo/handler/cve_handler/manager/parse_advisory.py b/apollo/handler/cve_handler/manager/parse_advisory.py
index 773f746..4848ea3 100644
--- a/apollo/handler/cve_handler/manager/parse_advisory.py
+++ b/apollo/handler/cve_handler/manager/parse_advisory.py
@@ -15,12 +15,12 @@ Time:
Author:
Description: parse security advisory xml file, insert into database
"""
+from collections import defaultdict
from xml.etree import cElementTree as ET
from xml.etree.ElementTree import ParseError
-from collections import defaultdict
-from vulcanus.log.log import LOGGER
from apollo.function.customize_exception import ParseAdvisoryError
+from vulcanus.log.log import LOGGER
__all__ = ["parse_security_advisory"]
@@ -99,7 +99,10 @@ def parse_cvrf_dict(cvrf_dict):
ParseXmlError
"""
# affected package of this security advisory. joined with ',' if have multiple packages
- cvrf_note = cvrf_dict["cvrfdoc"]["DocumentNotes"]["Note"]
+ cve_document_notes = cvrf_dict["cvrfdoc"].get("DocumentNotes", "")
+ if not cve_document_notes:
+ return [], [], []
+ cvrf_note = cve_document_notes["Note"]
affected_pkgs = ""
for info in cvrf_note:
if info["Title"] == "Affected Component":
diff --git a/apollo/handler/cve_handler/manager/parse_unaffected.py b/apollo/handler/cve_handler/manager/parse_unaffected.py
index 9b4ae03..7212a5c 100644
--- a/apollo/handler/cve_handler/manager/parse_unaffected.py
+++ b/apollo/handler/cve_handler/manager/parse_unaffected.py
@@ -76,7 +76,13 @@ def parse_cvrf_dict(cvrf_dict):
Raises:
ParseXmlError
"""
+ cvrf_note = cvrf_dict["cvrfdoc"].get("DocumentNotes", "")
+ if cvrf_note:
+ return [], [], []
+
cve_info_list = cvrf_dict["cvrfdoc"]["Vulnerability"]
+ if isinstance(cve_info_list, dict):
+ cve_info_list = [cve_info_list]
cve_table_rows = []
cve_pkg_rows = []
doc_list = []
@@ -87,6 +93,8 @@ def parse_cvrf_dict(cvrf_dict):
remediation = cve_info["Remediations"]["Remediation"]
if isinstance(remediation, list):
remediation = remediation[0]
+ if remediation["Type"] != "Unaffected":
+ continue
cvss_score = cve_info["CVSSScoreSets"]["ScoreSet"]["BaseScore"]
severity = parse_cve_severity(cvss_score)
cve_row = {
diff --git a/apollo/handler/cve_handler/view.py b/apollo/handler/cve_handler/view.py
index 4bfde0f..f90bd8e 100644
--- a/apollo/handler/cve_handler/view.py
+++ b/apollo/handler/cve_handler/view.py
@@ -229,8 +229,10 @@ class VulUploadAdvisory(BaseResponse):
def _save_single_advisory(proxy, file_path):
file_name = os.path.basename(file_path)
try:
- cve_rows, cve_pkg_rows, cve_pkg_docs = parse_security_advisory(
- file_path)
+ cve_rows, cve_pkg_rows, cve_pkg_docs = parse_security_advisory(file_path)
+ if cve_rows == [] and cve_pkg_rows == [] and cve_pkg_docs == []:
+ os.remove(file_path)
+ return WRONG_FILE_FORMAT
os.remove(file_path)
except (KeyError, ParseAdvisoryError) as error:
os.remove(file_path)
@@ -264,8 +266,10 @@ class VulUploadAdvisory(BaseResponse):
for file_path in file_path_list:
file_name = os.path.basename(file_path)
try:
- cve_rows, cve_pkg_rows, cve_pkg_docs = parse_security_advisory(
- file_path)
+ cve_rows, cve_pkg_rows, cve_pkg_docs = parse_security_advisory(file_path)
+ if cve_rows == [] and cve_pkg_rows == [] and cve_pkg_docs == []:
+ shutil.rmtree(folder_path)
+ return WRONG_FILE_FORMAT
except (KeyError, ParseAdvisoryError) as error:
fail_list.append(file_name)
LOGGER.error(
@@ -356,6 +360,9 @@ class VulUploadUnaffected(BaseResponse):
file_name = os.path.basename(file_path)
try:
cve_rows, cve_pkg_rows, doc_list = parse_unaffected_cve(file_path)
+ if cve_rows == [] and cve_pkg_rows == [] and doc_list == []:
+ os.remove(file_path)
+ return WRONG_FILE_FORMAT
os.remove(file_path)
except (KeyError, ParseAdvisoryError) as error:
os.remove(file_path)
@@ -387,6 +394,9 @@ class VulUploadUnaffected(BaseResponse):
file_name = os.path.basename(file_path)
try:
cve_rows, cve_pkg_rows, doc_list = parse_unaffected_cve(file_path)
+ if cve_rows == [] and cve_pkg_rows == [] and doc_list == []:
+ shutil.rmtree(folder_path)
+ return WRONG_FILE_FORMAT
except (KeyError, ParseAdvisoryError) as error:
fail_list.append(file_name)
LOGGER.error("Some error occurred when parsing unaffected cve advisory '%s'." % file_name)
--
Gitee
From 574bbe874c9f87f7e2fff223fb48da047be8b83c Mon Sep 17 00:00:00 2001
From: young <954906362@qq.com>
Date: Thu, 15 Dec 2022 10:03:58 +0800
Subject: [PATCH 2/3] =?UTF-8?q?=E6=96=87=E4=BB=B6=E4=B8=8E=E6=8E=A5?=
=?UTF-8?q?=E5=8F=A3=E4=B8=8D=E4=B8=80=E8=87=B4=E7=9A=84=E9=94=99=E8=AF=AF?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
apollo/handler/cve_handler/view.py | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/apollo/handler/cve_handler/view.py b/apollo/handler/cve_handler/view.py
index f90bd8e..cea00eb 100644
--- a/apollo/handler/cve_handler/view.py
+++ b/apollo/handler/cve_handler/view.py
@@ -265,6 +265,9 @@ class VulUploadAdvisory(BaseResponse):
fail_list = []
for file_path in file_path_list:
file_name = os.path.basename(file_path)
+ suffix = file_name.split('.')[-1]
+ if suffix != "xml":
+ return WRONG_FILE_FORMAT
try:
cve_rows, cve_pkg_rows, cve_pkg_docs = parse_security_advisory(file_path)
if cve_rows == [] and cve_pkg_rows == [] and cve_pkg_docs == []:
@@ -392,6 +395,9 @@ class VulUploadUnaffected(BaseResponse):
fail_list = []
for file_path in file_path_list:
file_name = os.path.basename(file_path)
+ suffix = file_name.split('.')[-1]
+ if suffix != "xml":
+ return WRONG_FILE_FORMAT
try:
cve_rows, cve_pkg_rows, doc_list = parse_unaffected_cve(file_path)
if cve_rows == [] and cve_pkg_rows == [] and doc_list == []:
--
Gitee
From b5e456ab33a323c8156a024c64b5a2193883347d Mon Sep 17 00:00:00 2001
From: young <954906362@qq.com>
Date: Thu, 15 Dec 2022 16:40:08 +0800
Subject: [PATCH 3/3] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=BB=A3=E7=A0=81?=
=?UTF-8?q?=E6=A3=80=E8=A7=86=E9=97=AE=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../handler/cve_handler/manager/parse_unaffected.py | 2 --
apollo/handler/cve_handler/view.py | 13 +++++++------
2 files changed, 7 insertions(+), 8 deletions(-)
diff --git a/apollo/handler/cve_handler/manager/parse_unaffected.py b/apollo/handler/cve_handler/manager/parse_unaffected.py
index 7212a5c..6338cd1 100644
--- a/apollo/handler/cve_handler/manager/parse_unaffected.py
+++ b/apollo/handler/cve_handler/manager/parse_unaffected.py
@@ -93,8 +93,6 @@ def parse_cvrf_dict(cvrf_dict):
remediation = cve_info["Remediations"]["Remediation"]
if isinstance(remediation, list):
remediation = remediation[0]
- if remediation["Type"] != "Unaffected":
- continue
cvss_score = cve_info["CVSSScoreSets"]["ScoreSet"]["BaseScore"]
severity = parse_cve_severity(cvss_score)
cve_row = {
diff --git a/apollo/handler/cve_handler/view.py b/apollo/handler/cve_handler/view.py
index cea00eb..8c161a3 100644
--- a/apollo/handler/cve_handler/view.py
+++ b/apollo/handler/cve_handler/view.py
@@ -230,10 +230,9 @@ class VulUploadAdvisory(BaseResponse):
file_name = os.path.basename(file_path)
try:
cve_rows, cve_pkg_rows, cve_pkg_docs = parse_security_advisory(file_path)
- if cve_rows == [] and cve_pkg_rows == [] and cve_pkg_docs == []:
- os.remove(file_path)
- return WRONG_FILE_FORMAT
os.remove(file_path)
+ if not all([cve_rows, cve_pkg_rows, cve_pkg_docs]):
+ return WRONG_FILE_FORMAT
except (KeyError, ParseAdvisoryError) as error:
os.remove(file_path)
LOGGER.error(
@@ -267,10 +266,11 @@ class VulUploadAdvisory(BaseResponse):
file_name = os.path.basename(file_path)
suffix = file_name.split('.')[-1]
if suffix != "xml":
+ shutil.rmtree(folder_path)
return WRONG_FILE_FORMAT
try:
cve_rows, cve_pkg_rows, cve_pkg_docs = parse_security_advisory(file_path)
- if cve_rows == [] and cve_pkg_rows == [] and cve_pkg_docs == []:
+ if not all([cve_rows, cve_pkg_rows, cve_pkg_docs]):
shutil.rmtree(folder_path)
return WRONG_FILE_FORMAT
except (KeyError, ParseAdvisoryError) as error:
@@ -363,7 +363,7 @@ class VulUploadUnaffected(BaseResponse):
file_name = os.path.basename(file_path)
try:
cve_rows, cve_pkg_rows, doc_list = parse_unaffected_cve(file_path)
- if cve_rows == [] and cve_pkg_rows == [] and doc_list == []:
+ if not all([cve_rows, cve_pkg_rows, doc_list]):
os.remove(file_path)
return WRONG_FILE_FORMAT
os.remove(file_path)
@@ -397,10 +397,11 @@ class VulUploadUnaffected(BaseResponse):
file_name = os.path.basename(file_path)
suffix = file_name.split('.')[-1]
if suffix != "xml":
+ shutil.rmtree(folder_path)
return WRONG_FILE_FORMAT
try:
cve_rows, cve_pkg_rows, doc_list = parse_unaffected_cve(file_path)
- if cve_rows == [] and cve_pkg_rows == [] and doc_list == []:
+ if not all([cve_rows, cve_pkg_rows, doc_list]):
shutil.rmtree(folder_path)
return WRONG_FILE_FORMAT
except (KeyError, ParseAdvisoryError) as error:
--
Gitee

View File

@ -1,749 +0,0 @@
From b316b4ec37fdca20c314b9755a81416c1f10a68f Mon Sep 17 00:00:00 2001
From: wang-guangge <wangguangge@huawei.com>
Date: Fri, 24 Mar 2023 22:56:26 +0800
Subject: [PATCH] add dnf hot patch list plugin
---
hotpatch/baseclass.py | 192 +++++++++++++++++++
hotpatch/hotpatch.py | 201 ++++++++++++++++++++
hotpatch/hotpatch_updateinfo.py | 321 ++++++++++++++++++++++++++++++++
3 files changed, 714 insertions(+)
create mode 100644 hotpatch/baseclass.py
create mode 100644 hotpatch/hotpatch.py
create mode 100644 hotpatch/hotpatch_updateinfo.py
diff --git a/hotpatch/baseclass.py b/hotpatch/baseclass.py
new file mode 100644
index 0000000..6dd1330
--- /dev/null
+++ b/hotpatch/baseclass.py
@@ -0,0 +1,192 @@
+class Hotpatch(object):
+ __slots__ = ['_name', '_version', '_cves',
+ '_advisory', '_arch', '_filename', '_state']
+
+ def __init__(self,
+ name,
+ version,
+ arch,
+ filename,
+ release=''):
+ """
+ name: str
+ version: str
+ arch: str
+ filename: str
+ release: str
+ """
+ self._name = name
+ self._version = version
+ self._arch = arch
+ self._filename = filename
+ self._cves = []
+ self._advisory = None
+ self._state = ''
+
+ @property
+ def state(self):
+ return self._state
+
+ @state.setter
+ def state(self, value):
+ self._state = value
+
+ @property
+ def name(self):
+ return self._name
+
+ @property
+ def version(self):
+ return self._version
+
+ @property
+ def src_pkg_nevre(self):
+ src_pkg = self.name[self.name.index('-')+1:self.name.rindex('-')]
+ release_pos = src_pkg.rindex('-')
+ version_pos = src_pkg.rindex('-', 0, release_pos)
+ src_pkg_name, src_pkg_version, src_pkg_release = src_pkg[
+ 0:version_pos], src_pkg[version_pos+1:release_pos], src_pkg[release_pos+1:]
+ return src_pkg_name, src_pkg_version, src_pkg_release
+
+ @property
+ def nevra(self):
+ """
+ nevra: name-version-release.arch
+ """
+ return self.filename[0:self.filename.rindex('.')]
+
+ @property
+ def hotpatch_name(self):
+ hotpatch_name = self.name[self.name.rindex('-')+1:]
+ return hotpatch_name
+
+ @property
+ def syscare_name(self):
+ src_pkg = '%s-%s-%s' % (self.src_pkg_nevre)
+ return '%s/%s' % (src_pkg, self.hotpatch_name)
+
+ @property
+ def cves(self):
+ return self._cves
+
+ @cves.setter
+ def cves(self, cves):
+ self._cves = cves
+
+ @property
+ def advisory(self):
+ return self._advisory
+
+ @advisory.setter
+ def advisory(self, advisory):
+ self._advisory = advisory
+
+ @property
+ def arch(self):
+ return self._arch
+
+ @property
+ def filename(self):
+ return self._filename
+
+
+class Cve(object):
+ __slots__ = ['_cve_id', '_hotpatch']
+
+ def __init__(self,
+ id,
+ href='',
+ title='',
+ type='cve'):
+ """
+ id: str
+ href: str
+ title: str
+ type: str
+ """
+ self._cve_id = id
+ self._hotpatch = None
+
+ @property
+ def hotpatch(self):
+ return self._hotpatch
+
+ @hotpatch.setter
+ def hotpatch(self, hotpatch: Hotpatch):
+ self._hotpatch = hotpatch
+
+ @property
+ def cve_id(self):
+ return self._cve_id
+
+
+class Advisory(object):
+ __slots__ = ['_id', '_type', '_title', '_severity',
+ '_description', '_updated', '_hotpatches', '_cves']
+
+ def __init__(self,
+ id,
+ type,
+ title,
+ severity,
+ description,
+ updated="1970-01-01 08:00:00",
+ release="",
+ issued=""):
+ """
+ id: str
+ type: str
+ title: str
+ severity: str
+ description: str
+ updated: str
+ release: str
+ issued: str
+ """
+ self._id = id
+ self._type = type
+ self._title = title
+ self._severity = severity
+ self._description = description
+ self._updated = updated
+ self._cves = {}
+ self._hotpatches = []
+
+ @property
+ def id(self):
+ return self._id
+
+ @property
+ def type(self):
+ return self._type
+
+ @property
+ def title(self):
+ return self._title
+
+ @property
+ def severity(self):
+ return self._severity
+
+ @property
+ def description(self):
+ return self._description
+
+ @property
+ def updated(self):
+ return self._updated
+
+ @property
+ def cves(self):
+ return self._cves
+
+ @cves.setter
+ def cves(self, advisory_cves):
+ self._cves = advisory_cves
+
+ @property
+ def hotpatches(self):
+ return self._hotpatches
+
+ def add_hotpatch(self, hotpatch: Hotpatch):
+ self._hotpatches.append(hotpatch)
diff --git a/hotpatch/hotpatch.py b/hotpatch/hotpatch.py
new file mode 100644
index 0000000..80cc69b
--- /dev/null
+++ b/hotpatch/hotpatch.py
@@ -0,0 +1,201 @@
+import dnf
+from dnf.i18n import _
+from dnf.cli.commands.updateinfo import UpdateInfoCommand
+import hawkey
+from .hotpatch_updateinfo import HotpatchUpdateInfo
+
+
+class Versions:
+ """
+ Version number processing
+ """
+
+ separator = (".", "-")
+ _connector = "&"
+
+ def _order(self, version, separator=None):
+ """
+ Version of the cutting
+ Args:
+ version: version
+ separator: separator
+
+ Returns:
+
+ """
+ if not separator:
+ separator = self._connector
+ return tuple([int(v) for v in version.split(separator) if v.isdigit()])
+
+ def lgt(self, version, compare_version):
+ """
+ Returns true if the size of the compared version is greater
+ than that of the compared version, or false otherwise
+
+ """
+ for separator in self.separator:
+ version = self._connector.join(
+ [v for v in version.split(separator)])
+ compare_version = self._connector.join(
+ [v for v in compare_version.split(separator)]
+ )
+ version = self._order(version)
+ compare_version = self._order(compare_version)
+ return version >= compare_version
+
+
+@dnf.plugin.register_command
+class HotpatchCommand(dnf.cli.Command):
+ aliases = ['hotpatch']
+ summary = _('show hotpatch info')
+
+ def __init__(self, cli):
+ """
+ Initialize the command
+ """
+ super(HotpatchCommand, self).__init__(cli)
+
+ @staticmethod
+ def set_argparser(parser):
+ output_format = parser.add_mutually_exclusive_group()
+ output_format.add_argument("--list", dest='_spec_action', const='list',
+ action='store_const',
+ help=_('show list of cves'))
+
+ def configure(self):
+ demands = self.cli.demands
+ demands.sack_activation = True
+ demands.available_repos = True
+
+ self.filter_cves = self.opts.cves if self.opts.cves else None
+
+ def run(self):
+ self.hp_hawkey = HotpatchUpdateInfo(self.cli.base, self.cli)
+
+ if self.opts._spec_action == 'list':
+ self.display()
+
+ def get_mapping_nevra_cve(self) -> dict:
+ """
+ Get cve nevra mapping based on the UpdateInfoCommand of 'dnf updateinfo list cves'
+
+ Returns:
+ {
+ (nevra, advisory.updated):
+ cve_id: {
+ (advisory.type, advisory.severity),
+ ...
+ }
+ ...
+ }
+ """
+ # configure UpdateInfoCommand with 'dnf updateinfo list cves'
+ updateinfo = UpdateInfoCommand(self.cli)
+ updateinfo.opts = self.opts
+
+ updateinfo.opts.spec_action = 'list'
+ updateinfo.opts.with_cve = True
+ updateinfo.opts.spec = '*'
+ updateinfo.opts._advisory_types = set()
+ updateinfo.opts.availability = 'available'
+ self.updateinfo = updateinfo
+
+ apkg_adv_insts = updateinfo.available_apkg_adv_insts(
+ updateinfo.opts.spec)
+
+ mapping_nevra_cve = dict()
+ for apkg, advisory, _ in apkg_adv_insts:
+ nevra = (apkg.name, apkg.evr, apkg.arch)
+ for ref in advisory.references:
+ if ref.type != hawkey.REFERENCE_CVE:
+ continue
+ mapping_nevra_cve.setdefault((nevra, advisory.updated), dict())[
+ ref.id] = (advisory.type, advisory.severity)
+
+ return mapping_nevra_cve
+
+ def _filter_and_format_list_output(self, echo_lines: list, fixed_cve_id: set, fixed_coldpatches: set):
+ """
+ Only show specified cve information that have not been fixed, and format output
+ """
+ def is_patch_fixed(coldpatch, fixed_coldpatches):
+ """
+ Check whether the coldpatch is fixed
+ """
+ for fixed_coldpatch in fixed_coldpatches:
+ pkg_name, pkg_evr, _ = coldpatch
+ fixed_pkg_name, fixed_pkg_evr, _ = fixed_coldpatch
+ if pkg_name != fixed_pkg_name:
+ continue
+ version = Versions()
+ if version.lgt(fixed_pkg_evr, pkg_evr):
+ return True
+ return False
+
+ idw = tiw = ciw = 0
+ format_lines = set()
+ for echo_line in echo_lines:
+ cve_id, type, coldpatch, hotpatch = echo_line[0], echo_line[1], echo_line[2], echo_line[3]
+ if self.filter_cves is not None and cve_id not in self.filter_cves:
+ continue
+ if cve_id in fixed_cve_id:
+ continue
+ if not isinstance(coldpatch, str):
+ if is_patch_fixed(coldpatch, fixed_coldpatches):
+ continue
+ else:
+ pkg_name, pkg_evr, pkg_arch = coldpatch
+ coldpatch = '%s-%s.%s' % (pkg_name, pkg_evr, pkg_arch)
+
+ idw = max(idw, len(cve_id))
+ tiw = max(tiw, len(type))
+ ciw = max(ciw, len(coldpatch))
+ format_lines.add((cve_id, type, coldpatch, hotpatch))
+ for format_line in sorted(format_lines, key=lambda x: x[2]):
+ print('%-*s %-*s %-*s %s' %
+ (idw, format_line[0], tiw, format_line[1], ciw, format_line[2], format_line[3]))
+
+ def display(self):
+ """
+ Append hotpatch information according to the output of 'dnf updateinfo list cves'
+
+ echo lines:
+ [
+ [cve_id, type, coldpatch, hotpatch]
+ ]
+ """
+
+ def type2label(updateinfo, typ, sev):
+ if typ == hawkey.ADVISORY_SECURITY:
+ return updateinfo.SECURITY2LABEL.get(sev, _('Unknown/Sec.'))
+ else:
+ return updateinfo.TYPE2LABEL.get(typ, _('unknown'))
+
+ mapping_nevra_cve = self.get_mapping_nevra_cve()
+ echo_lines = []
+ fixed_cve_id = set()
+ fixed_coldpatches = set()
+ iterated_cve_id = set()
+ for ((nevra), aupdated), id2type in sorted(mapping_nevra_cve.items(), key=lambda x: x[0]):
+ pkg_name, pkg_evr, pkg_arch = nevra
+ for cve_id, atypesev in id2type.items():
+ iterated_cve_id.add(cve_id)
+ label = type2label(self.updateinfo, *atypesev)
+ echo_line = [cve_id, label, nevra, '-']
+ if cve_id in self.hp_hawkey.hotpatch_cves:
+ hotpatch = self.hp_hawkey.hotpatch_cves[cve_id].hotpatch
+ if hotpatch is not None and hotpatch.src_pkg_nevre[0] == pkg_name:
+ if hotpatch.state == self.hp_hawkey.INSTALLED:
+ # record the fixed cves
+ for cve_id in hotpatch.cves:
+ fixed_cve_id.add(cve_id)
+ # record the fixed coldpatch to filter the cves of the corresponding coldpatch with the lower version
+ fixed_coldpatches.add((nevra))
+ continue
+ elif hotpatch.state == self.hp_hawkey.INSTALLABLE:
+ echo_line[3] = hotpatch.nevra
+
+ echo_lines.append(echo_line)
+
+ self._filter_and_format_list_output(
+ echo_lines, fixed_cve_id, fixed_coldpatches)
diff --git a/hotpatch/hotpatch_updateinfo.py b/hotpatch/hotpatch_updateinfo.py
new file mode 100644
index 0000000..4e0b702
--- /dev/null
+++ b/hotpatch/hotpatch_updateinfo.py
@@ -0,0 +1,321 @@
+from .baseclass import Hotpatch, Cve, Advisory
+from .syscare import Syscare
+import os
+from typing import Optional
+import gzip
+import xml.etree.ElementTree as ET
+import datetime
+
+
+class HotpatchUpdateInfo(object):
+ """
+ Hotpatch relevant updateinfo processing
+ """
+
+ UNINSTALLABLE = 0
+ INSTALLED = 1
+ INSTALLABLE = 2
+
+ def __init__(self, base, cli):
+ self.base = base
+ self.cli = cli
+ # dict {advisory_id: Advisory}
+ self._hotpatch_advisories = {}
+ # dict {cve_id: Cve}
+ self._hotpatch_cves = {}
+ # list [{'Uuid': uuid, 'Name':name, 'Status': status}]
+ self._hotpatch_status = []
+
+ self.init_hotpatch_info()
+
+ def init_hotpatch_info(self):
+ """
+ Initialize hotpatch information
+ """
+ self._get_installed_pkgs()
+ self._parse_and_store_hotpatch_info_from_updateinfo()
+ self._init_hotpatch_status_from_syscare()
+ self._init_hotpatch_state()
+
+ @property
+ def hotpatch_cves(self):
+ return self._hotpatch_cves
+
+ @property
+ def hotpatch_status(self):
+ return self._hotpatch_status
+
+ def _get_installed_pkgs(self):
+ """
+ Get installed packages by setting the hawkey
+ """
+ sack = self.base.sack
+ # the latest installed packages
+ q = sack.query().installed().latest(1)
+ # plus packages of the running kernel
+ kernel_q = sack.query().filterm(empty=True)
+ kernel = sack.get_running_kernel()
+ if kernel:
+ kernel_q = kernel_q.union(
+ sack.query().filterm(sourcerpm=kernel.sourcerpm))
+ q = q.union(kernel_q.installed())
+ q = q.apply()
+
+ self._inst_pkgs_query = q
+
+ def _parse_and_store_hotpatch_info_from_updateinfo(self):
+ """
+ Initialize hotpatch information from repos
+ """
+ # get xxx-hotpatch.xml.gz file paths by traversing the system_cachedir(/var/cache/dnf)
+ system_cachedir = self.cli.base.conf.system_cachedir
+ all_repos = self.cli.base.repos
+ map_repo_updateinfoxml = {}
+
+ for file in os.listdir(system_cachedir):
+ file_path = os.path.join(system_cachedir, file)
+ if os.path.isdir(file_path):
+ repodata_path = os.path.join(file_path, "repodata")
+ if not os.path.isdir(repodata_path):
+ continue
+
+ for xml_file in os.listdir(repodata_path):
+ # the hotpatch relevant updateinfo is recorded in xxx-hotpatch.xml.gz
+ if "hotpatch" in xml_file:
+ repo_name = file.split("-")[0]
+ cache_updateinfo_xml_path = os.path.join(
+ repodata_path, xml_file)
+ map_repo_updateinfoxml[repo_name] = cache_updateinfo_xml_path
+
+ # only hotpatch relevant updateinfo from enabled repos are parsed and stored
+ for repo in all_repos.iter_enabled():
+ repo_id = repo.id
+ if repo_id in map_repo_updateinfoxml:
+ updateinfoxml_path = map_repo_updateinfoxml[repo_id]
+ self._parse_and_store_from_xml(updateinfoxml_path)
+
+ def _parse_pkglist(self, pkglist):
+ """
+ Parse the pkglist information, filter the hotpatches with different arches
+ """
+ hotpatches = []
+ hot_patch_collection = pkglist.find('collection')
+ arches = self.base.sack.list_arches()
+ if not hot_patch_collection:
+ return hotpatches
+ for package in hot_patch_collection.iter('package'):
+ hotpatch = {key: value for key, value in package.items()}
+ if hotpatch['arch'] not in arches:
+ continue
+ hotpatch['filename'] = package.find('filename').text
+ hotpatches.append(hotpatch)
+ return hotpatches
+
+ def _parse_references(self, reference):
+ """
+ Parse the reference information, check whether the 'id' is missing
+ """
+ cves = []
+ for ref in reference:
+ cve = {key: value for key, value in ref.items()}
+ if 'id' not in cve:
+ continue
+ cves.append(cve)
+ return cves
+
+ def _verify_date_str_lawyer(self, datetime_str: str) -> str:
+ """
+ Check whether the 'datetime' field is legal, if not return default value
+ """
+ if datetime_str.isdigit() and len(datetime_str) == 10:
+ datetime_str = int(datetime_str)
+ datetime_str = datetime.datetime.fromtimestamp(
+ datetime_str).strftime("%Y-%m-%d %H:%M:%S")
+ try:
+ datetime.datetime.strptime(datetime_str, '%Y-%m-%d %H:%M:%S')
+ return datetime_str
+ except ValueError:
+ return "1970-01-01 08:00:00"
+
+ def _parse_advisory(self, update):
+ """
+ Parse the advisory information: check whether the 'datetime' field is legal, parse the 'references'
+ field and the 'pkglist' field, save 'type' information
+ """
+ advisory = {}
+ for node in update:
+ if node.tag == 'datetime':
+ advisory[node.tag] = self._verify_date_str_lawyer(
+ update.find(node.tag).text)
+ elif node.tag == 'references':
+ advisory[node.tag] = self._parse_references(node)
+ elif node.tag == 'pkglist':
+ advisory['hotpatches'] = self._parse_pkglist(node)
+ else:
+ advisory[node.tag] = update.find(node.tag).text
+ advisory['type'] = update.get('type')
+ return advisory
+
+ def _store_advisory_info(self, advisory_kwargs: dict()):
+ """
+ Instantiate Cve, Hotpatch and Advisory object according to the advisory kwargs
+ """
+ advisory_references = advisory_kwargs.pop('references')
+ advisory_hotpatches = advisory_kwargs.pop('hotpatches')
+ advisory = Advisory(**advisory_kwargs)
+ advisory_cves = {}
+ for cve_kwargs in advisory_references:
+ cve = Cve(**cve_kwargs)
+ self._hotpatch_cves[cve.cve_id] = cve
+ advisory_cves[cve.cve_id] = cve
+ advisory.cves = advisory_cves
+
+ for hotpatch_kwargs in advisory_hotpatches:
+ hotpatch = Hotpatch(**hotpatch_kwargs)
+ hotpatch.advisory = advisory
+ hotpatch.cves = advisory_cves.keys()
+
+ advisory.add_hotpatch(hotpatch)
+
+ for cve in advisory_cves.values():
+ cve.hotpatch = hotpatch
+
+ self._hotpatch_advisories.setdefault(
+ advisory_kwargs['id'], list()).append(advisory)
+
+ def _init_hotpatch_state(self):
+ """
+ Initialize the hotpatch state
+
+ each hotpatch has three states:
+ 1. UNINSTALLABLE: can not be installed due to the source package version mismatch
+ 2. INSTALLED: has been installed and actived in syscare
+ 3. INSTALLABLE: can be installed
+
+ """
+ for advisories in self._hotpatch_advisories.values():
+ for advisory in advisories:
+ for hotpatch in advisory.hotpatches:
+ src_pkg_name, src_pkg_version, src_pkg_release = hotpatch.src_pkg_nevre
+ inst_pkgs = self._inst_pkgs_query.filter(name=src_pkg_name)
+ hotpatch.state = self.UNINSTALLABLE
+ # check whether the relevant source package is installed on this machine
+ if not inst_pkgs:
+ continue
+ for inst_pkg in inst_pkgs:
+ inst_pkg_vere = '%s-%s' % (inst_pkg.version,
+ inst_pkg.release)
+ hp_vere = '%s-%s' % (src_pkg_version, src_pkg_release)
+ if hp_vere != inst_pkg_vere:
+ continue
+ elif self._get_hotpatch_status_in_syscare(hotpatch) == 'ACTIVED':
+ hotpatch.state = self.INSTALLED
+ else:
+ hotpatch.state = self.INSTALLABLE
+
+ def _parse_and_store_from_xml(self, updateinfoxml):
+ """
+ Parse and store hotpatch update information from xxx-hotpatch.xml.gz
+
+ xxx-hotpatch.xml.gz e.g.
+
+ <?xml version="1.0" encoding="UTF-8"?>
+ <updates>
+ <update from="openeuler.org" type="security" status="stable">
+ <id>openEuler-SA-2022-1</id>
+ <title>An update for mariadb is now available for openEuler-22.03-LTS</title>
+ <severity>Important</severity>
+ <release>openEuler</release>
+ <issued date="2022-04-16"></issued>
+ <references>
+ <reference href="https://nvd.nist.gov/vuln/detail/CVE-2021-46658" id="CVE-2021-1" title="CVE-2021-1" type="cve"></reference>
+ </references>
+ <description>patch-redis-6.2.5-1-HP001.(CVE-2022-24048)</description>
+ <pkglist>
+ <collection>
+ <name>openEuler</name>
+ <package arch="aarch64" name="patch-redis-6.2.5-1-HP001" release="0" version="1">
+ <filename>patch-redis-6.2.5-1-HP001-0-1.aarch64.rpm</filename>
+ </package>
+ <package arch="x86_64" name="patch-redis-6.2.5-1-HP001" release="0" version="1">
+ <filename>patch-redis-6.2.5-1-HP001-0-1.x86_64.rpm</filename>
+ </package>
+ <collection>
+ </pkglist>
+ </update>
+ ...
+ </updates>
+ """
+ content = gzip.open(updateinfoxml)
+ tree = ET.parse(content)
+ root = tree.getroot()
+ for update in root.iter('update'):
+ advisory = self._parse_advisory(update)
+ self._store_advisory_info(advisory)
+
+ def _init_hotpatch_status_from_syscare(self):
+ """
+ Initialize hotpatch status from syscare
+ """
+ self._hotpatch_status = Syscare().list()
+
+ self._hotpatch_state = {}
+ for hotpatch_info in self._hotpatch_status:
+ self._hotpatch_state[hotpatch_info['Name']
+ ] = hotpatch_info['Status']
+
+ def _get_hotpatch_status_in_syscare(self, hotpatch: Hotpatch) -> str:
+ """
+ Get hotpatch status in syscare
+ """
+ if hotpatch.syscare_name not in self._hotpatch_state:
+ return ''
+ return self._hotpatch_state[hotpatch.syscare_name]
+
+ def get_hotpatches_from_cve(self, cves: Optional[list[str]] = []) -> dict():
+ """
+ Get hotpatches from specified cve
+
+ Args:
+ cves: [cve_id_1, cve_id_2]
+
+ Returns:
+ {
+ cve_id_1: [hotpatch1],
+ cve_id_2: []
+ }
+ """
+ mapping_cve_hotpatches = dict()
+ for cve_id in cves:
+ mapping_cve_hotpatches[cve_id] = []
+ if cve_id not in self.hotpatch_cves:
+ continue
+ hotpatch = self.hotpatch_cves[cve_id].hotpatch
+ if hotpatch is not None and hotpatch.state == self.INSTALLABLE:
+ mapping_cve_hotpatches[cve_id].append(hotpatch.nevra)
+ return mapping_cve_hotpatches
+
+ def get_hotpatches_from_advisories(self, advisories: Optional[list[str]] = []) -> dict():
+ """
+ Get hotpatches from specified advisories
+
+ Args:
+ advisories: [advisory_id_1, advisory_id_2]
+
+ Return:
+ {
+ advisory_id_1: [hotpatch1],
+ advisory_id_2: []
+ }
+ """
+ mapping_advisory_hotpatches = dict()
+ for advisory_id in advisories:
+ mapping_advisory_hotpatches[advisory_id] = []
+ if advisory_id not in self._hotpatch_advisories:
+ continue
+ for advisory in self._hotpatch_advisories[advisory_id]:
+ for hotpatch in advisory.hotpatches:
+ if hotpatch.state == self.INSTALLABLE:
+ mapping_advisory_hotpatches[advisory_id].append(
+ hotpatch.nevra)
+ return mapping_advisory_hotpatches
--
2.33.0

View File

@ -1,399 +0,0 @@
From 20c09ffb55a630a4259b7c87cd88da2ce6f28a07 Mon Sep 17 00:00:00 2001
From: zhu-yuncheng <zhuyuncheng@huawei.com>
Date: Sat, 25 Mar 2023 14:43:20 +0800
Subject: [PATCH] add dnf hot upgrade plugin
---
hotpatch/hot-upgrade.py | 275 ++++++++++++++++++++++++++++++++++++++++
hotpatch/syscare.py | 96 ++++++++++++++
2 files changed, 371 insertions(+)
create mode 100644 hotpatch/hot-upgrade.py
create mode 100644 hotpatch/syscare.py
diff --git a/hotpatch/hot-upgrade.py b/hotpatch/hot-upgrade.py
new file mode 100644
index 0000000..7a4c3c6
--- /dev/null
+++ b/hotpatch/hot-upgrade.py
@@ -0,0 +1,275 @@
+# supplies the dnf 'diff' command.
+#
+# Copyright (C) 2018 Red Hat, Inc.
+# Written by Pavel Raiskup <praiskup@redhat.com>.
+#
+# This copyrighted material is made available to anyone wishing to use,
+# modify, copy, or redistribute it subject to the terms and conditions of
+# the GNU General Public License v.2, or (at your option) any later version.
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY expressed or implied, including the implied warranties of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+# Public License for more details. You should have received a copy of the
+# GNU General Public License along with this program; if not, write to the
+# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the
+# source code or documentation are not subject to the GNU General Public
+# License and may only be used or replicated with the express permission of
+# Red Hat, Inc.
+
+from __future__ import print_function
+
+import dnf.base
+import dnf.exceptions
+import hawkey
+from dnf.cli import commands
+from dnf.cli.option_parser import OptionParser
+from dnf.cli.output import Output
+from dnfpluginscore import _, logger
+
+from .syscare import Syscare
+from .hotpatch_updateinfo import HotpatchUpdateInfo
+
+
+@dnf.plugin.register_command
+class HotupgradeCommand(dnf.cli.Command):
+ aliases = ("hotupgrade",)
+ summary = "Hot upgrade package using hot patch."
+ usage = ""
+ syscare = Syscare()
+ hp_list = []
+
+ @staticmethod
+ def set_argparser(parser):
+ parser.add_argument('packages', nargs='*', help=_('Package to upgrade'),
+ action=OptionParser.ParseSpecGroupFileCallback,
+ metavar=_('PACKAGE'))
+
+ def configure(self):
+ """Verify that conditions are met so that this command can run.
+ These include that there are enabled repositories with gpg
+ keys, and that this command is being run by the root user.
+ """
+ demands = self.cli.demands
+ demands.sack_activation = True
+ demands.available_repos = True
+ demands.resolving = True
+ demands.root_user = True
+
+ commands._checkGPGKey(self.base, self.cli)
+ if not self.opts.filenames:
+ commands._checkEnabledRepo(self.base)
+
+ def run(self):
+ if self.opts.pkg_specs:
+ self.hp_list = self.opts.pkg_specs
+ elif self.opts.cves or self.opts.advisory:
+ cve_pkgs = self.get_hotpatch_based_on_cve(self.opts.cves)
+ advisory_pkgs = self.get_hotpatch_based_on_advisory(self.opts.advisory)
+ self.hp_list = cve_pkgs + advisory_pkgs
+ else:
+ raise dnf.exceptions.Error(_('No qualified rpm package name or cve/advisory id.'))
+
+ hp_target_map = self._get_available_hotpatches(self.hp_list)
+ if not hp_target_map:
+ raise dnf.exceptions.Error(_('No hot patches marked for install.'))
+
+ target_patch_map = self._get_applied_old_patch(list(hp_target_map.values()))
+ if target_patch_map:
+ self._remove_hot_patches(target_patch_map)
+ else:
+ self.syscare.save()
+ success = self._install_hot_patch(list(hp_target_map.keys()))
+ if not success:
+ output, status = self.syscare.restore()
+ if status:
+ raise dnf.exceptions.Error(_('Roll back failed.'))
+ raise dnf.exceptions.Error(_("Roll back succeed."))
+ return
+
+ def run_transaction(self) -> None:
+ """
+ apply hot patches
+ Returns:
+ None
+ """
+ logger.info(_('Applying hot patch'))
+ if not self.base.transaction:
+ for hp in self.hp_list:
+ self._apply_hp(hp)
+ return
+
+ for ts_item in self.base.transaction:
+ if ts_item.action not in dnf.transaction.FORWARD_ACTIONS:
+ continue
+ self._apply_hp(str(ts_item.pkg))
+
+ def _apply_hp(self, hp_full_name):
+ pkg_info = self._parse_hp_name(hp_full_name)
+ hp_full_name = "-".join([pkg_info["name"], pkg_info["version"], pkg_info["release"]]) \
+ + '/' + pkg_info["hp_name"]
+ output, status = self.syscare.apply(hp_full_name)
+ if status:
+ logger.info(_('Apply hot patch failed: %s.'), hp_full_name)
+ else:
+ logger.info(_('Apply hot patch succeed: %s.'), hp_full_name)
+
+ def _get_available_hotpatches(self, pkg_specs: list) -> dict:
+ """
+ check two conditions:
+ 1. the hot patch rpm package exists in repositories
+ 2. the hot patch's target package with specific version and release already installed
+ Args:
+ pkg_specs: full names of hot patches' rpm packages
+
+ Returns:
+ dict: key is available hot patches' full name, value is target package's name-version-release
+ """
+ hp_target_map = {}
+ installed_packages = self.base.sack.query().installed()
+ for pkg_spec in set(pkg_specs):
+ query = self.base.sack.query()
+ # check the package exist in repo or not
+ subj = dnf.subject.Subject(pkg_spec)
+ parsed_nevras = subj.get_nevra_possibilities(forms=[hawkey.FORM_NEVRA])
+ if len(parsed_nevras) != 1:
+ logger.info(_('Cannot parse NEVRA for package "{nevra}"').format(nevra=pkg_spec))
+ continue
+
+ parsed_nevra = parsed_nevras[0]
+ available_hp = query.available().filter(name=parsed_nevra.name, version=parsed_nevra.version,
+ release=parsed_nevra.release, arch=parsed_nevra.arch)
+ if not available_hp:
+ logger.info(_('No match for argument: %s'), self.base.output.term.bold(pkg_spec))
+ continue
+
+ # check the hot patch's target package installed or not
+ pkg_info = self._parse_hp_name(pkg_spec)
+ installed_pkg = installed_packages.filter(name=pkg_info["name"],
+ version=pkg_info["version"],
+ release=pkg_info["release"]).run()
+ if not installed_pkg:
+ logger.info(_("The hot patch's target package is not installed: %s"),
+ self.base.output.term.bold(pkg_spec))
+ continue
+
+ if len(installed_pkg) != 1:
+ logger.info(_("The hot patch '%s' has multiple target packages, please check."),
+ self.base.output.term.bold(pkg_spec))
+ continue
+ target = "-".join([pkg_info["name"], pkg_info["version"], pkg_info["release"]])
+ hp_target_map[pkg_spec] = target
+ return hp_target_map
+
+ def _get_applied_old_patch(self, targets: list):
+ """
+ get targets' applied hot patches
+ Args:
+ targets: target RPMs' name-version-release. e.g. redis-1.0-1
+
+ Returns:
+ dict: targets' applied hot patches. e.g. {'redis-1.0-1': 'redis-1.0-1/HP001'}
+ """
+ target_patch_map = {}
+ hps_info = Syscare.list()
+ for hp_info in hps_info:
+ target, hp_name = hp_info["Name"].split('/')
+ if target in targets and hp_info["Status"] != "NOT-APPLIED":
+ logger.info(_("The target package '%s' has a hotpatch '%s' applied"),
+ self.base.output.term.bold(target),
+ self.base.output.term.bold(hp_name))
+ target_patch_map[target] = hp_info["Name"]
+ return target_patch_map
+
+ def _remove_hot_patches(self, target_patch_map: dict) -> None:
+ output = Output(self.base, dnf.conf.Conf())
+ logger.info(_("Gonna remove these hot patches: %s"), list(target_patch_map.values()))
+ remove_flag = output.userconfirm()
+ if not remove_flag:
+ raise dnf.exceptions.Error(_('Operation aborted.'))
+
+ self.syscare.save()
+ for target, hp_name in target_patch_map.items():
+ logger.info(_("Remove hot patch %s."), hp_name)
+ output, status = self.syscare.remove(hp_name)
+ if status:
+ logger.info(_("Remove hot patch '%s' failed, roll back to original status."),
+ self.base.output.term.bold(hp_name))
+ output, status = self.syscare.restore()
+ if status:
+ raise dnf.exceptions.Error(_('Roll back failed.'))
+ raise dnf.exceptions.Error(_('Roll back succeed.'))
+
+ @staticmethod
+ def _parse_hp_name(hp_filename: str) -> dict:
+ """
+ parse hot patch's name, get target rpm's name, version, release and hp's name.
+ Args:
+ hp_filename: hot patch's name, in the format of
+ 'patch-{pkg_name}-{pkg_version}-{pkg_release}-{patchname}-{patch_version}-{patch_release}.rpm'
+ e.g. patch-kernel-5.10.0-60.66.0.91.oe2203-HP001-1-1.x86_64.rpm
+ pkg_name may have '-' in it, patch name cannot have '-'.
+ Returns:
+ dict: rpm info. {"name": "", "version": "", "release": "", "hp_name": ""}
+ """
+ splitted_hp_filename = hp_filename.split('-')
+ try:
+ rpm_info = {"release": splitted_hp_filename[-4], "version": splitted_hp_filename[-5],
+ "name": "-".join(splitted_hp_filename[1:-5]), "hp_name": splitted_hp_filename[-3]}
+ except IndexError as e:
+ raise dnf.exceptions.Error(_('Parse hot patch name failed. Please insert correct hot patch name.'))
+ return rpm_info
+
+ def _install_hot_patch(self, pkg_specs: list) -> bool:
+ """
+ install hot patches
+ Args:
+ pkg_specs: hot patches' full name
+
+ Returns:
+ bool
+ """
+ success = True
+ for pkg_spec in pkg_specs:
+ try:
+ self.base.install(pkg_spec)
+ except dnf.exceptions.MarkingError as e:
+ logger.info(_('No match for argument: %s.'),
+ self.base.output.term.bold(pkg_spec))
+ success = False
+ return success
+
+ def get_hotpatch_based_on_cve(self, cves: list) -> list:
+ """
+ Get the hot patches corresponding to CVEs
+ Args:
+ cves: cve id list
+
+ Returns:
+ list: list of hot patches full name. e.g.["tmp2-tss-3.1.0-3.oe2203sp1"]
+ """
+ updateinfo = HotpatchUpdateInfo(self.cli.base, self.cli)
+ hp_list = []
+ cve_hp_dict = updateinfo.get_hotpatches_from_cve(cves)
+ for cve, hp in cve_hp_dict.items():
+ if not hp:
+ logger.info(_("The cve doesn't exist: %s"), cve)
+ continue
+ hp_list += hp
+ return list(set(hp_list))
+
+ def get_hotpatch_based_on_advisory(self, advisories: list) -> list:
+ """
+ Get the hot patches corresponding to advisories
+ Args:
+ advisories: advisory id list
+
+ Returns:
+ list: list of hot patches full name. e.g.["tmp2-tss-3.1.0-3.oe2203sp1"]
+ """
+ updateinfo = HotpatchUpdateInfo(self.cli.base, self.cli)
+ hp_list = []
+ advisory_hp_dict = updateinfo.get_hotpatches_from_advisories(advisories)
+ for hp in advisory_hp_dict.values():
+ hp_list += hp
+ return list(set(hp_list))
diff --git a/hotpatch/syscare.py b/hotpatch/syscare.py
new file mode 100644
index 0000000..b24b07e
--- /dev/null
+++ b/hotpatch/syscare.py
@@ -0,0 +1,96 @@
+import subprocess
+from typing import List
+
+
+SUCCEED = 0
+FAIL = 255
+
+def cmd_output(cmd):
+ try:
+ result = subprocess.Popen(cmd, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ result.wait()
+ return result.stdout.read().decode('utf-8'), result.returncode
+ except Exception as e:
+ print("error: ", e)
+ return str(e), FAIL
+
+class Syscare:
+ @classmethod
+ def list(cls, condition=None) -> List[dict]:
+ """
+ Target Name Status
+ redis-6.2.5-1.oe2203 CVE-2021-23675 ACTIVED
+ kernel-5.10.0-60.80.0.104.oe2203 modify-proc-version ACTIVED
+ """
+ cmd = ["syscare", "list"]
+ list_output, return_code = cmd_output(cmd)
+ if return_code != SUCCEED:
+ return []
+
+ content = list_output.split('\n')
+ if len(content) <= 2:
+ return []
+
+ header = content[0].split()
+ result = []
+ for item in content[1:-1]:
+ tmp = dict(zip(header, item.split()))
+ if not condition or cls.judge(tmp, condition):
+ result.append(tmp)
+ return result
+
+ @staticmethod
+ def judge(content: dict, condition: dict):
+ for key, value in condition.items():
+ if content.get(key) != value:
+ return False
+ return True
+
+ @staticmethod
+ def status(patch_name: str):
+ cmd = ["syscare", "status", patch_name]
+ output, return_code = cmd_output(cmd)
+
+ return output, return_code
+
+ @staticmethod
+ def active(patch_name: str):
+ cmd = ["syscare", "active", patch_name]
+ output, return_code = cmd_output(cmd)
+
+ return output, return_code
+
+ @staticmethod
+ def deactive(patch_name: str):
+ cmd = ["syscare", "deactive", patch_name]
+ output, return_code = cmd_output(cmd)
+
+ return output, return_code
+
+ @staticmethod
+ def remove(patch_name: str):
+ cmd = ["syscare", "remove", patch_name]
+ output, return_code = cmd_output(cmd)
+
+ return output, return_code
+
+ @staticmethod
+ def apply(patch_name: str):
+ cmd = ["syscare", "apply", patch_name]
+ output, return_code = cmd_output(cmd)
+
+ return output, return_code
+
+ @staticmethod
+ def save():
+ cmd = ["syscare", "save"]
+ output, return_code = cmd_output(cmd)
+
+ return output, return_code
+
+ @staticmethod
+ def restore():
+ cmd = ["syscare", "restore"]
+ output, return_code = cmd_output(cmd)
+
+ return output, return_code
\ No newline at end of file
--
2.30.0

View File

@ -1,25 +0,0 @@
From a1ccf84bf28323ce613e0c1d1819c31d0d6cd13b Mon Sep 17 00:00:00 2001
From: zhu-yuncheng <zhuyuncheng@huawei.com>
Date: Mon, 27 Mar 2023 23:45:18 +0800
Subject: [PATCH] better hotupgrade command output
---
hotpatch/hot-upgrade.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/hotpatch/hot-upgrade.py b/hotpatch/hot-upgrade.py
index 7a4c3c6..06f32ad 100644
--- a/hotpatch/hot-upgrade.py
+++ b/hotpatch/hot-upgrade.py
@@ -253,7 +253,7 @@ class HotupgradeCommand(dnf.cli.Command):
cve_hp_dict = updateinfo.get_hotpatches_from_cve(cves)
for cve, hp in cve_hp_dict.items():
if not hp:
- logger.info(_("The cve doesn't exist: %s"), cve)
+ logger.info(_("The cve's hot patch doesn't exist: %s"), cve)
continue
hp_list += hp
return list(set(hp_list))
--
2.30.0

Binary file not shown.

BIN
aops-apollo-v1.2.0.tar.gz Normal file

Binary file not shown.

View File

@ -1,20 +1,17 @@
Name: aops-apollo
Version: v1.1.2
Release: 7
Version: v1.2.0
Release: 1
Summary: Cve management service, monitor machine vulnerabilities and provide fix functions.
License: MulanPSL2
URL: https://gitee.com/openeuler/%{name}
Source0: %{name}-%{version}.tar.gz
Patch0001: 0001-fix-partial-succeed-bug.patch
Patch0002: 0002-add-dnf-hot-patch-list-plugin.patch
Patch0003: 0003-add-dnf-hot-upgrade-plugin.patch
Patch0004: 0004-better-hotupgrade-command-output.patch
BuildRequires: python3-setuptools
Requires: aops-vulcanus >= v1.0.0
Requires: aops-vulcanus >= v1.2.0
Requires: python3-elasticsearch python3-flask-restful python3-marshmallow >= 3.13.0
Requires: python3-sqlalchemy python3-PyMySQL python3-Flask-APScheduler >= 1.11.0
Requires: python3-PyYAML python3-flask
Requires: python3-retrying python3-lxml
Provides: aops-apollo
@ -29,7 +26,8 @@ Requires: python3-hawkey python3-dnf syscare
dnf hotpatch plugin, it's about hotpatch query and fix
%prep
%autosetup -n %{name}-%{version} -p1
%autosetup -n %{name}-%{version}
# build for aops-apollo
%py3_build
@ -41,10 +39,10 @@ dnf hotpatch plugin, it's about hotpatch query and fix
#install for aops-dnf-plugin
cp -r hotpatch %{buildroot}/%{python3_sitelib}/dnf-plugins/
%files
%doc README.*
%attr(0644,root,root) %{_sysconfdir}/aops/apollo.ini
%attr(0644,root,root) %{_sysconfdir}/aops/apollo_crontab.ini
%attr(0755,root,root) %{_bindir}/aops-apollo
%attr(0755,root,root) /usr/lib/systemd/system/aops-apollo.service
%{python3_sitelib}/aops_apollo*.egg-info
@ -53,25 +51,18 @@ cp -r hotpatch %{buildroot}/%{python3_sitelib}/dnf-plugins/
%files -n dnf-hotpatch-plugin
%{python3_sitelib}/dnf-plugins/*
%changelog
* Mon Mar 27 2023 zhu-yuncheng<zhuyuncheng@huawei.com> - v1.1.2-7
- better hotupgrade command output when cve exists but hot patch doesn't
* Sat Mar 25 2023 wangguangge<wangguangge@huawei.com> - v1.1.2-6
- fix baseclass.py bug and add syscare require in spec
* Sat Mar 25 2023 zhu-yuncheng<zhuyuncheng@huawei.com> - v1.1.2-5
- add dnf hot upgrade plugin
* Fri Mar 24 2023 wangguangge<wangguangge@huawei.com> - v1.1.2-4
* Mon Apr 17 2023 gongzhengtang<gong_zhengtang@163.com> - v1.2.0-1
- add updated security advisory at regular time
- add execute the CVE scan command at regular time
- add correct abnormal data at regular time
- add dnf hotpatch list plugin
* Tue Dec 27 2022 wenxin<shusheng.wen@outlook.com> - v1.1.2-3
- modify version for vulcanus
* Thu Dec 15 2022 ptyang<1475324955@qq.com> - v1.1.2-2
- fix "PARTIAL_SUCCEED" bug
- fix "PARTIAL_SUCCEED" bug
* Wed Dec 07 2022 wenxin<shusheng.wen@outlook.com> - v1.1.2-1
- modify status code for upload security advisories;fix cve query error