salt/add-cpe_name-for-osversion-grain-parsing-u-49946.patch

195 lines
8.7 KiB
Diff

From 3bad9e211c2e76ddac48f7c8ff1632e32e0a256e Mon Sep 17 00:00:00 2001
From: Bo Maryniuk <bo@suse.de>
Date: Tue, 9 Oct 2018 14:08:50 +0200
Subject: [PATCH] Add CPE_NAME for osversion* grain parsing (U#49946)
Remove unnecessary linebreak
Override VERSION_ID from os-release, if CPE_NAME is given
Add unit test for WFN format of CPE_NAME
Add unit test for v2.3 of CPE format
Add unit test for broken CPE_NAME
Prevent possible crash if CPE_NAME is wrongly written in the distro
Add part parsing
Keep CPE_NAME only for opensuse series
Remove linebreak
Expand unit test to verify part name
Fix proper part name in the string-bound CPE
---
salt/grains/core.py | 43 +++++++++++++++++++++---
tests/unit/grains/test_core.py | 60 +++++++++++++++++++++++++++++-----
2 files changed, 90 insertions(+), 13 deletions(-)
diff --git a/salt/grains/core.py b/salt/grains/core.py
index 80eebd1c05..e41ab4e0ae 100644
--- a/salt/grains/core.py
+++ b/salt/grains/core.py
@@ -1355,6 +1355,34 @@ def _parse_os_release(os_release_files):
return data
+def _parse_cpe_name(cpe):
+ '''
+ Parse CPE_NAME data from the os-release
+
+ Info: https://csrc.nist.gov/projects/security-content-automation-protocol/scap-specifications/cpe
+
+ :param cpe:
+ :return:
+ '''
+ part = {
+ 'o': 'operating system',
+ 'h': 'hardware',
+ 'a': 'application',
+ }
+ ret = {}
+ cpe = (cpe or '').split(':')
+ if len(cpe) > 4 and cpe[0] == 'cpe':
+ if cpe[1].startswith('/'): # WFN to URI
+ ret['vendor'], ret['product'], ret['version'] = cpe[2:5]
+ ret['phase'] = cpe[5] if len(cpe) > 5 else None
+ ret['part'] = part.get(cpe[1][1:])
+ elif len(cpe) == 13 and cpe[1] == '2.3': # WFN to a string
+ ret['vendor'], ret['product'], ret['version'], ret['phase'] = [x if x != '*' else None for x in cpe[3:7]]
+ ret['part'] = part.get(cpe[2])
+
+ return ret
+
+
def os_data():
'''
Return grains pertaining to the operating system
@@ -1554,13 +1582,20 @@ def os_data():
codename = codename_match.group(1)
grains['lsb_distrib_codename'] = codename
if 'CPE_NAME' in os_release:
- if ":suse:" in os_release['CPE_NAME'] or ":opensuse:" in os_release['CPE_NAME']:
+ cpe = _parse_cpe_name(os_release['CPE_NAME'])
+ if not cpe:
+ log.error('Broken CPE_NAME format in /etc/os-release!')
+ elif cpe.get('vendor', '').lower() in ['suse', 'opensuse']:
grains['os'] = "SUSE"
# openSUSE `osfullname` grain normalization
if os_release.get("NAME") == "openSUSE Leap":
grains['osfullname'] = "Leap"
elif os_release.get("VERSION") == "Tumbleweed":
grains['osfullname'] = os_release["VERSION"]
+ # Override VERSION_ID, if CPE_NAME around
+ if cpe.get('version') and cpe.get('vendor') == 'opensuse': # Keep VERSION_ID for SLES
+ grains['lsb_distrib_release'] = cpe['version']
+
elif os.path.isfile('/etc/SuSE-release'):
grains['lsb_distrib_id'] = 'SUSE'
version = ''
@@ -1666,8 +1701,7 @@ def os_data():
# Commit introducing this comment should be reverted after the upstream bug is released.
if 'CentOS Linux 7' in grains.get('lsb_distrib_codename', ''):
grains.pop('lsb_distrib_release', None)
- grains['osrelease'] = \
- grains.get('lsb_distrib_release', osrelease).strip()
+ grains['osrelease'] = grains.get('lsb_distrib_release', osrelease).strip()
grains['oscodename'] = grains.get('lsb_distrib_codename', '').strip() or oscodename
if 'Red Hat' in grains['oscodename']:
grains['oscodename'] = oscodename
@@ -1702,8 +1736,7 @@ def os_data():
r'((?:Open|Oracle )?Solaris|OpenIndiana|OmniOS) (Development)?'
r'\s*(\d+\.?\d*|v\d+)\s?[A-Z]*\s?(r\d+|\d+\/\d+|oi_\S+|snv_\S+)?'
)
- osname, development, osmajorrelease, osminorrelease = \
- release_re.search(rel_data).groups()
+ osname, development, osmajorrelease, osminorrelease = release_re.search(rel_data).groups()
except AttributeError:
# Set a blank osrelease grain and fallback to 'Solaris'
# as the 'os' grain.
diff --git a/tests/unit/grains/test_core.py b/tests/unit/grains/test_core.py
index e973428add..2ab32ef41b 100644
--- a/tests/unit/grains/test_core.py
+++ b/tests/unit/grains/test_core.py
@@ -62,10 +62,11 @@ class CoreGrainsTestCase(TestCase, LoaderModuleMockMixin):
def test_parse_etc_os_release(self, path_isfile_mock):
path_isfile_mock.side_effect = lambda x: x == "/usr/lib/os-release"
with salt.utils.files.fopen(os.path.join(OS_RELEASE_DIR, "ubuntu-17.10")) as os_release_file:
- os_release_content = os_release_file.readlines()
- with patch("salt.utils.files.fopen", mock_open()) as os_release_file:
- os_release_file.return_value.__iter__.return_value = os_release_content
- os_release = core._parse_os_release(["/etc/os-release", "/usr/lib/os-release"])
+ os_release_content = os_release_file.read()
+ with patch("salt.utils.files.fopen", mock_open(read_data=os_release_content)):
+ os_release = core._parse_os_release(
+ '/etc/os-release',
+ '/usr/lib/os-release')
self.assertEqual(os_release, {
"NAME": "Ubuntu",
"VERSION": "17.10 (Artful Aardvark)",
@@ -81,10 +82,53 @@ class CoreGrainsTestCase(TestCase, LoaderModuleMockMixin):
"UBUNTU_CODENAME": "artful",
})
- @patch("os.path.isfile")
- def test_missing_os_release(self, path_isfile_mock):
- path_isfile_mock.return_value = False
- os_release = core._parse_os_release(["/etc/os-release", "/usr/lib/os-release"])
+ def test_parse_cpe_name_wfn(self):
+ '''
+ Parse correct CPE_NAME data WFN formatted
+ :return:
+ '''
+ for cpe, cpe_ret in [('cpe:/o:opensuse:leap:15.0',
+ {'phase': None, 'version': '15.0', 'product': 'leap',
+ 'vendor': 'opensuse', 'part': 'operating system'}),
+ ('cpe:/o:vendor:product:42:beta',
+ {'phase': 'beta', 'version': '42', 'product': 'product',
+ 'vendor': 'vendor', 'part': 'operating system'})]:
+ ret = core._parse_cpe_name(cpe)
+ for key in cpe_ret:
+ assert key in ret
+ assert cpe_ret[key] == ret[key]
+
+ def test_parse_cpe_name_v23(self):
+ '''
+ Parse correct CPE_NAME data v2.3 formatted
+ :return:
+ '''
+ for cpe, cpe_ret in [('cpe:2.3:o:microsoft:windows_xp:5.1.601:beta:*:*:*:*:*:*',
+ {'phase': 'beta', 'version': '5.1.601', 'product': 'windows_xp',
+ 'vendor': 'microsoft', 'part': 'operating system'}),
+ ('cpe:2.3:h:corellian:millenium_falcon:1.0:*:*:*:*:*:*:*',
+ {'phase': None, 'version': '1.0', 'product': 'millenium_falcon',
+ 'vendor': 'corellian', 'part': 'hardware'}),
+ ('cpe:2.3:*:dark_empire:light_saber:3.0:beta:*:*:*:*:*:*',
+ {'phase': 'beta', 'version': '3.0', 'product': 'light_saber',
+ 'vendor': 'dark_empire', 'part': None})]:
+ ret = core._parse_cpe_name(cpe)
+ for key in cpe_ret:
+ assert key in ret
+ assert cpe_ret[key] == ret[key]
+
+ def test_parse_cpe_name_broken(self):
+ '''
+ Parse broken CPE_NAME data
+ :return:
+ '''
+ for cpe in ['cpe:broken', 'cpe:broken:in:all:ways:*:*:*:*',
+ 'cpe:x:still:broken:123', 'who:/knows:what:is:here']:
+ assert core._parse_cpe_name(cpe) == {}
+
+ def test_missing_os_release(self):
+ with patch('salt.utils.files.fopen', mock_open(read_data={})):
+ os_release = core._parse_os_release('/etc/os-release', '/usr/lib/os-release')
self.assertEqual(os_release, {})
@skipIf(not salt.utils.platform.is_linux(), 'System is not Linux')
--
2.19.0