blob: 973d514f06d68dcb2c2e26663e43b2df8e2667ca [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'],
73 'description': rule.description.cdata}
74 return rules
75
76
77def _parse_xccdf_doc(document):
78 groups = _get_flatten_groups(document.Benchmark)
79 rules = _get_rules(groups)
80
81 results = []
82 for result in document.Benchmark.TestResult.rule_result:
83 results.append({
84 'rule': result['idref'],
85 'result': result.result.cdata,
86 'severity': result['severity'],
87 'weight': result['weight'],
88 'title': rules[result['idref']]['title'],
89 'description': rules[result['idref']]['title']
90 })
91
92 return results
93
94
95def _sanitize_xccdf_xml(data):
96 data = data.replace(
97 '<html:code xmlns:html="http://www.w3.org/1999/xhtml">', '')
98 data = data.replace('</html:code>', '')
99 data = data.replace(
100 '<html:pre xmlns:html="http://www.w3.org/1999/xhtml">', '')
101 data = data.replace('<html:pre>', '')
102 data = data.replace('</html:pre>', '')
103 data = data.replace('<html:code>', '')
104 data = data.replace('<html:li>', '')
105 data = data.replace('</html:li>', '')
106 data = data.replace(
107 '<html:pre xmlns:html="http://www.w3.org/1999/xhtml" '
108 'xmlns:ns0="http://checklists.nist.gov/xccdf/1.1">',
109 '')
110 data = data.replace(
111 '<html:br xmlns:html="http://www.w3.org/1999/xhtml"/>', '')
112 data = data.replace(
113 '<html:code xmlns:html="http://www.w3.org/1999/xhtml" '
114 'xmlns:ns0="http://checklists.nist.gov/xccdf/1.1">',
115 '')
116 return data
117
118
119def xccdf_xml_to_json(xml_file):
120 with open(xml_file) as in_file:
121 raw_xml = in_file.read()
122 doc = untangle.parse(_sanitize_xccdf_xml(raw_xml))
123 results = _parse_xccdf_doc(doc)
124 with open(os.path.splitext(xml_file)[0] + '.json', 'w') as json_file:
125 # NOTE(pas-ha) the src/com/mirantis/mk.Common.parseJSON method
126 # from mk/pipeline-library that is used in our Jenkins pipelines
127 # can not parse the string representation of a list!
128 # only dict structure is supported as a top-level one there
129 json.dump({"results": results}, json_file)
130
131
132def _parse_oval_definitions(document):
133 definitions = {}
134 def_list = document.oval_results.oval_definitions.definitions.definition
135 for definition in def_list:
136 try:
137 definitions[definition['id']] = {'class': definition['class']}
138 def_dict = definitions[definition['id']]
139 def_dict['title'] = definition.metadata.title.cdata
140 def_dict['description'] = definition.metadata.description.cdata
141 def_dict['ref_id'] = definition.metadata.reference['ref_id']
142 def_dict['link'] = definition.metadata.reference['ref_url']
143 def_dict['severity'] = definition.metadata.advisory.severity.cdata
Pavlo Shchelokovskyy016de2d2018-10-09 16:17:51 +0300144 except (AttributeError, IndexError):
145 # NOTE(e0ne): inventory does't have some element
Pavlo Shchelokovskyy4a8f1c12018-09-21 19:17:19 +0300146 pass
147
148 return definitions
149
150
151def oval_xml_to_json(xml_file):
152 document = untangle.parse(xml_file)
153 definitions = _parse_oval_definitions(document)
154 results = []
155 for defn in document.oval_results.results.system.definitions.definition:
156 res = collections.defaultdict(lambda: None)
157 res['id'] = defn['definition_id']
158 res['result'] = defn['result']
159 res.update(definitions[defn['definition_id']])
160 results.append(res)
161 with open(os.path.splitext(xml_file)[0] + '.json', 'w') as json_file:
162 # NOTE(pas-ha) the src/com/mirantis/mk.Common.parseJSON method
163 # from mk/pipeline-library that is used in our Jenkins pipelines
164 # can not parse the string representation of a list!
165 # only dict structure is supported as a top-level one there
166 json.dump({"results": results}, json_file)