blob: d81825d577bcbc6506c6a82be3500724b486d443 [file] [log] [blame]
Pavlo Shchelokovskyy4a8f1c12018-09-21 19:17:19 +03001import collections
2import datetime
3import json
Ivan Suzdal184c4e32018-06-06 13:55:30 +04004from lxml.etree import Element, SubElement, tostring
Pavlo Shchelokovskyy4a8f1c12018-09-21 19:17:19 +03005import os.path
6import re
Ivan Suzdal184c4e32018-06-06 13:55:30 +04007from subprocess import Popen, PIPE
8import shlex
Ivan Suzdal184c4e32018-06-06 13:55:30 +04009
10import salt.ext.six as six
11
Pavlo Shchelokovskyy4a8f1c12018-09-21 19:17:19 +030012from oscap import untangle
13
14
Ivan Suzdal184c4e32018-06-06 13:55:30 +040015def normalize_id(id,
16 xccdf_version='1.2',
17 typeof='profile',
18 vendor='mirantis'):
19
20 if xccdf_version == '1.2':
21 if not re.match('^xccdf_[^_]+_{}_.+'.format(typeof), id):
22 return 'xccdf_org.{0}.content_{1}_{2}'.format(vendor, typeof, id)
23 return id
24
Pavlo Shchelokovskyy4a8f1c12018-09-21 19:17:19 +030025
Ivan Suzdal184c4e32018-06-06 13:55:30 +040026def build_tailoring(data, id):
27 xccdf_version = data.get('xccdf_version', '1.2')
28 ns = {None: 'http://checklists.nist.gov/xccdf/{}'.format(xccdf_version)}
29 tid = normalize_id(id, xccdf_version, typeof='tailoring')
30 pid = normalize_id(data['profile'], xccdf_version, vendor='customer')
31 ext = normalize_id(data['extends'], xccdf_version)
32 tailoring = Element('Tailoring', nsmap=ns, id=tid)
33 tailoring.append(Element('benchmark', {'href': ext}))
34
35 now = datetime.datetime.now().isoformat()
Pavlo Shchelokovskyy4a8f1c12018-09-21 19:17:19 +030036 SubElement(tailoring, 'version', time=now).text = '1'
Ivan Suzdal184c4e32018-06-06 13:55:30 +040037
38 profile = SubElement(tailoring, 'Profile', id=pid, extends=ext)
39
Pavlo Shchelokovskyy4a8f1c12018-09-21 19:17:19 +030040 SubElement(profile, 'title').text = 'Extends {}'.format(ext)
Ivan Suzdal184c4e32018-06-06 13:55:30 +040041
42 for key, value in six.iteritems(data.get('values', {})):
43 idref = normalize_id(key, xccdf_version, typeof='value')
44 elem = SubElement(profile, 'set-value', idref=idref)
45 elem.text = str(value)
46 return tostring(tailoring, pretty_print=True)
47
Pavlo Shchelokovskyy4a8f1c12018-09-21 19:17:19 +030048
Ivan Suzdal184c4e32018-06-06 13:55:30 +040049def run(cmd, cwd=None):
50 # The Popen used here because the __salt__['cmd.run'] returns only stdout
51 proc = Popen(shlex.split(cmd), stdout=PIPE, stderr=PIPE, cwd=cwd)
52 (stdout, stderr) = proc.communicate()
53 return stdout, stderr, proc.returncode
Pavlo Shchelokovskyy4a8f1c12018-09-21 19:17:19 +030054
55
56def _get_flatten_groups(document, groups=None):
57 groups = groups if groups else []
58 if hasattr(document, 'Group'):
59 for group in document.Group:
60 groups.append(group)
61 groups = _get_flatten_groups(group, groups)
62 return groups
63
64
65def _get_rules(groups):
66 rules = {}
67 for group in groups:
68 if hasattr(group, 'Rule'):
69 for rule in group.Rule:
70 rules[rule['id']] = {
71 'title': rule.title.cdata,
72 'severity': rule['severity'],
Pavlo Shchelokovskyy5ccb6362018-10-10 15:16:14 +030073 'ident': rule.ident.cdata,
Pavlo Shchelokovskyy4a8f1c12018-09-21 19:17:19 +030074 'description': rule.description.cdata}
75 return rules
76
77
78def _parse_xccdf_doc(document):
79 groups = _get_flatten_groups(document.Benchmark)
80 rules = _get_rules(groups)
81
82 results = []
83 for result in document.Benchmark.TestResult.rule_result:
84 results.append({
85 'rule': result['idref'],
86 'result': result.result.cdata,
87 'severity': result['severity'],
88 'weight': result['weight'],
89 'title': rules[result['idref']]['title'],
Pavlo Shchelokovskyy5ccb6362018-10-10 15:16:14 +030090 'cis_code': rules[result['idref']]['ident'],
Pavlo Shchelokovskyy4a8f1c12018-09-21 19:17:19 +030091 'description': rules[result['idref']]['title']
92 })
93
94 return results
95
96
97def _sanitize_xccdf_xml(data):
98 data = data.replace(
99 '<html:code xmlns:html="http://www.w3.org/1999/xhtml">', '')
100 data = data.replace('</html:code>', '')
101 data = data.replace(
102 '<html:pre xmlns:html="http://www.w3.org/1999/xhtml">', '')
103 data = data.replace('<html:pre>', '')
104 data = data.replace('</html:pre>', '')
105 data = data.replace('<html:code>', '')
106 data = data.replace('<html:li>', '')
107 data = data.replace('</html:li>', '')
108 data = data.replace(
109 '<html:pre xmlns:html="http://www.w3.org/1999/xhtml" '
110 'xmlns:ns0="http://checklists.nist.gov/xccdf/1.1">',
111 '')
112 data = data.replace(
113 '<html:br xmlns:html="http://www.w3.org/1999/xhtml"/>', '')
114 data = data.replace(
115 '<html:code xmlns:html="http://www.w3.org/1999/xhtml" '
116 'xmlns:ns0="http://checklists.nist.gov/xccdf/1.1">',
117 '')
118 return data
119
120
121def xccdf_xml_to_json(xml_file):
122 with open(xml_file) as in_file:
123 raw_xml = in_file.read()
124 doc = untangle.parse(_sanitize_xccdf_xml(raw_xml))
125 results = _parse_xccdf_doc(doc)
126 with open(os.path.splitext(xml_file)[0] + '.json', 'w') as json_file:
127 # NOTE(pas-ha) the src/com/mirantis/mk.Common.parseJSON method
128 # from mk/pipeline-library that is used in our Jenkins pipelines
129 # can not parse the string representation of a list!
130 # only dict structure is supported as a top-level one there
131 json.dump({"results": results}, json_file)
132
133
134def _parse_oval_definitions(document):
135 definitions = {}
136 def_list = document.oval_results.oval_definitions.definitions.definition
137 for definition in def_list:
138 try:
139 definitions[definition['id']] = {'class': definition['class']}
140 def_dict = definitions[definition['id']]
141 def_dict['title'] = definition.metadata.title.cdata
142 def_dict['description'] = definition.metadata.description.cdata
143 def_dict['ref_id'] = definition.metadata.reference['ref_id']
144 def_dict['link'] = definition.metadata.reference['ref_url']
145 def_dict['severity'] = definition.metadata.advisory.severity.cdata
Pavlo Shchelokovskyy016de2d2018-10-09 16:17:51 +0300146 except (AttributeError, IndexError):
147 # NOTE(e0ne): inventory does't have some element
Pavlo Shchelokovskyy4a8f1c12018-09-21 19:17:19 +0300148 pass
149
150 return definitions
151
152
153def oval_xml_to_json(xml_file):
154 document = untangle.parse(xml_file)
155 definitions = _parse_oval_definitions(document)
156 results = []
157 for defn in document.oval_results.results.system.definitions.definition:
158 res = collections.defaultdict(lambda: None)
159 res['id'] = defn['definition_id']
160 res['result'] = defn['result']
161 res.update(definitions[defn['definition_id']])
162 results.append(res)
163 with open(os.path.splitext(xml_file)[0] + '.json', 'w') as json_file:
164 # NOTE(pas-ha) the src/com/mirantis/mk.Common.parseJSON method
165 # from mk/pipeline-library that is used in our Jenkins pipelines
166 # can not parse the string representation of a list!
167 # only dict structure is supported as a top-level one there
168 json.dump({"results": results}, json_file)