File add-cpe_name-for-osversion-grain-parsing-u-49946.patch of Package salt.10416
From b87f29243f2d6ae13d74ea1d93c83aad1fe47ef2 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 | 73 ++++++++++++++++++++++++++++++++++
2 files changed, 111 insertions(+), 5 deletions(-)
diff --git a/salt/grains/core.py b/salt/grains/core.py
index 5ad9efea17..301e20b952 100644
--- a/salt/grains/core.py
+++ b/salt/grains/core.py
@@ -1355,6 +1355,34 @@ def _parse_os_release():
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
@@ -1552,13 +1580,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 = ''
@@ -1664,8 +1699,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
@@ -1700,8 +1734,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 b75d2fd353..3b930b0475 100644
--- a/tests/unit/grains/test_core.py
+++ b/tests/unit/grains/test_core.py
@@ -55,6 +55,79 @@ class CoreGrainsTestCase(TestCase, LoaderModuleMockMixin):
def setup_loader_modules(self):
return {core: {}}
+ @patch("os.path.isfile")
+ 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.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)",
+ "ID": "ubuntu",
+ "ID_LIKE": "debian",
+ "PRETTY_NAME": "Ubuntu 17.10",
+ "VERSION_ID": "17.10",
+ "HOME_URL": "https://www.ubuntu.com/",
+ "SUPPORT_URL": "https://help.ubuntu.com/",
+ "BUG_REPORT_URL": "https://bugs.launchpad.net/ubuntu/",
+ "PRIVACY_POLICY_URL": "https://www.ubuntu.com/legal/terms-and-policies/privacy-policy",
+ "VERSION_CODENAME": "artful",
+ "UBUNTU_CODENAME": "artful",
+ })
+
+ 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')
def test_gnu_slash_linux_in_os_name(self):
'''
--
2.19.0