blob: 5f90ef0e8a3d3ff0f002604b864d5ef8222bc09a [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
Dmitry Teselkinebdb3bf2019-05-06 18:54:48 +030065def _get_rules(document, groups):
66 def _append_rule(rules, rule):
67 rules[rule['id']] = {
68 'title': rule.title.cdata,
69 'severity': rule['severity'],
70 'ident': rule.ident.cdata,
71 'description': rule.description.cdata}
72
Pavlo Shchelokovskyy4a8f1c12018-09-21 19:17:19 +030073 rules = {}
Dmitry Teselkinebdb3bf2019-05-06 18:54:48 +030074 if hasattr(document, 'Rule'):
75 for rule in document.Rule:
76 _append_rule(rules, rule)
Pavlo Shchelokovskyy4a8f1c12018-09-21 19:17:19 +030077 for group in groups:
78 if hasattr(group, 'Rule'):
79 for rule in group.Rule:
Dmitry Teselkinebdb3bf2019-05-06 18:54:48 +030080 _append_rule(rules, rule)
Pavlo Shchelokovskyy4a8f1c12018-09-21 19:17:19 +030081 return rules
82
83
84def _parse_xccdf_doc(document):
85 groups = _get_flatten_groups(document.Benchmark)
Dmitry Teselkinebdb3bf2019-05-06 18:54:48 +030086 rules = _get_rules(document.Benchmark, groups)
Pavlo Shchelokovskyy4a8f1c12018-09-21 19:17:19 +030087
88 results = []
89 for result in document.Benchmark.TestResult.rule_result:
90 results.append({
91 'rule': result['idref'],
92 'result': result.result.cdata,
93 'severity': result['severity'],
94 'weight': result['weight'],
95 'title': rules[result['idref']]['title'],
Pavlo Shchelokovskyy5ccb6362018-10-10 15:16:14 +030096 'cis_code': rules[result['idref']]['ident'],
Pavlo Shchelokovskyy4a8f1c12018-09-21 19:17:19 +030097 'description': rules[result['idref']]['title']
98 })
99
100 return results
101
102
103def _sanitize_xccdf_xml(data):
104 data = data.replace(
105 '<html:code xmlns:html="http://www.w3.org/1999/xhtml">', '')
106 data = data.replace('</html:code>', '')
107 data = data.replace(
108 '<html:pre xmlns:html="http://www.w3.org/1999/xhtml">', '')
109 data = data.replace('<html:pre>', '')
110 data = data.replace('</html:pre>', '')
111 data = data.replace('<html:code>', '')
112 data = data.replace('<html:li>', '')
113 data = data.replace('</html:li>', '')
114 data = data.replace(
115 '<html:pre xmlns:html="http://www.w3.org/1999/xhtml" '
116 'xmlns:ns0="http://checklists.nist.gov/xccdf/1.1">',
117 '')
118 data = data.replace(
119 '<html:br xmlns:html="http://www.w3.org/1999/xhtml"/>', '')
120 data = data.replace(
121 '<html:code xmlns:html="http://www.w3.org/1999/xhtml" '
122 'xmlns:ns0="http://checklists.nist.gov/xccdf/1.1">',
123 '')
124 return data
125
126
127def xccdf_xml_to_json(xml_file):
128 with open(xml_file) as in_file:
129 raw_xml = in_file.read()
130 doc = untangle.parse(_sanitize_xccdf_xml(raw_xml))
131 results = _parse_xccdf_doc(doc)
132 with open(os.path.splitext(xml_file)[0] + '.json', 'w') as json_file:
133 # NOTE(pas-ha) the src/com/mirantis/mk.Common.parseJSON method
134 # from mk/pipeline-library that is used in our Jenkins pipelines
135 # can not parse the string representation of a list!
136 # only dict structure is supported as a top-level one there
137 json.dump({"results": results}, json_file)
138
139
140def _parse_oval_definitions(document):
141 definitions = {}
142 def_list = document.oval_results.oval_definitions.definitions.definition
143 for definition in def_list:
144 try:
145 definitions[definition['id']] = {'class': definition['class']}
146 def_dict = definitions[definition['id']]
147 def_dict['title'] = definition.metadata.title.cdata
148 def_dict['description'] = definition.metadata.description.cdata
149 def_dict['ref_id'] = definition.metadata.reference['ref_id']
150 def_dict['link'] = definition.metadata.reference['ref_url']
151 def_dict['severity'] = definition.metadata.advisory.severity.cdata
Pavlo Shchelokovskyy016de2d2018-10-09 16:17:51 +0300152 except (AttributeError, IndexError):
153 # NOTE(e0ne): inventory does't have some element
Pavlo Shchelokovskyy4a8f1c12018-09-21 19:17:19 +0300154 pass
155
156 return definitions
157
158
159def oval_xml_to_json(xml_file):
160 document = untangle.parse(xml_file)
161 definitions = _parse_oval_definitions(document)
162 results = []
163 for defn in document.oval_results.results.system.definitions.definition:
164 res = collections.defaultdict(lambda: None)
165 res['id'] = defn['definition_id']
166 res['result'] = defn['result']
167 res.update(definitions[defn['definition_id']])
168 results.append(res)
169 with open(os.path.splitext(xml_file)[0] + '.json', 'w') as json_file:
170 # NOTE(pas-ha) the src/com/mirantis/mk.Common.parseJSON method
171 # from mk/pipeline-library that is used in our Jenkins pipelines
172 # can not parse the string representation of a list!
173 # only dict structure is supported as a top-level one there
174 json.dump({"results": results}, json_file)