Completed
Push — master ( 291ee9...9d841e )
by Matěj
20s queued 12s
created

ssg._oval.add_oval_elements()   F

Complexity

Conditions 9

Size

Total Lines 32
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
eloc 24
nop 2
dl 0
loc 32
rs 3
c 0
b 0
f 0
1
import sys
2
import os
3
import re
4
import argparse
5
import tempfile
6
import subprocess
7
8
from ConfigParser import SafeConfigParser
9
10
from ssg._constants import oval_footer as footer
11
from ssg._constants import oval_namespace as ovalns
12
from ssg._constants import timestamp
13
from ssg._xml import ElementTree as ET
14
from ssg._xml import oval_generated_header
15
16
17
SHARED_OVAL = re.sub('shared.*', 'shared', __file__) + '/checks/oval/'
18
19
20
try:
21
    from openscap import oscap_get_version
22
    if oscap_get_version() < 1.2:
23
        oval_version = "5.10"
24
    else:
25
        oval_version = "5.11"
26
except ImportError:
27
    oval_version = "5.10"
28
29
# globals, to make recursion easier in case we encounter extend_definition
30
definitions = ET.Element("definitions")
31
tests = ET.Element("tests")
32
objects = ET.Element("objects")
33
states = ET.Element("states")
34
variables = ET.Element("variables")
35
36
37
# append new child ONLY if it's not a duplicate
38
def append(element, newchild):
39
    newid = newchild.get("id")
40
    existing = element.find(".//*[@id='" + newid + "']")
41
    if existing is not None:
42
        if not silent_mode:
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable silent_mode does not seem to be defined.
Loading history...
43
            sys.stderr.write("Notification: this ID is used more than once " +
44
                             "and should represent equivalent elements: " +
45
                             newid + "\n")
46
    else:
47
        element.append(newchild)
48
49
50
def add_oval_elements(body, header):
51
    """Add oval elements to the global Elements defined above"""
52
53
    tree = ET.fromstring(header + body + footer)
54
    tree = replace_external_vars(tree)
55
    # parse new file(string) as an etree, so we can arrange elements
56
    # appropriately
57
    for childnode in tree.findall("./{%s}def-group/*" % ovalns):
58
        # print "childnode.tag is " + childnode.tag
59
        if childnode.tag is ET.Comment:
60
            continue
61
        if childnode.tag == ("{%s}definition" % ovalns):
62
            append(definitions, childnode)
63
            defname = childnode.get("id")
64
            # extend_definition is a special case:  must include a whole other
65
            # definition
66
            for defchild in childnode.findall(".//{%s}extend_definition"
67
                                              % ovalns):
68
                defid = defchild.get("definition_ref")
69
                extend_ref = find_testfile(defid+".xml")
70
                includedbody = read_ovaldefgroup_file(extend_ref)
71
                # recursively add the elements in the other file
72
                add_oval_elements(includedbody, header)
73
        if childnode.tag.endswith("_test"):
74
            append(tests, childnode)
75
        if childnode.tag.endswith("_object"):
76
            append(objects, childnode)
77
        if childnode.tag.endswith("_state"):
78
            append(states, childnode)
79
        if childnode.tag.endswith("_variable"):
80
            append(variables, childnode)
81
    return defname
0 ignored issues
show
introduced by
The variable defname does not seem to be defined for all execution paths.
Loading history...
82
83
84
def replace_external_vars(tree):
85
    """Replace external_variables with local_variables, so the definition can be
86
       tested independently of an XCCDF file"""
87
88
    # external_variable is a special case: we turn it into a local_variable so
89
    # we can test
90
    for node in tree.findall(".//{%s}external_variable" % ovalns):
91
        print("External_variable with id : " + node.get("id"))
92
        extvar_id = node.get("id")
93
        # for envkey, envval in os.environ.iteritems():
94
        #     print envkey + " = " + envval
95
        # sys.exit()
96
        if extvar_id not in os.environ.keys():
97
            print("External_variable specified, but no value provided via "
98
                  "environment variable")
99
            sys.exit(2)
100
        # replace tag name: external -> local
101
        node.tag = "{%s}local_variable" % ovalns
102
        literal = ET.Element("literal_component")
103
        literal.text = os.environ[extvar_id]
104
        node.append(literal)
105
        # TODO: assignment of external_variable via environment vars, for
106
        # testing
107
    return tree
108
109
110
def find_testfile(testfile):
111
    """Find OVAL files in CWD or shared/oval"""
112
    for path in ['.', SHARED_OVAL]:
113
        for root, folder, files in os.walk(path):
114
            searchfile = root + '/' + testfile
115
            if not os.path.isfile(searchfile):
116
                searchfile = ""
117
            else:
118
                testfile = searchfile.strip()
119
                # Most likely found file, exit this loop
120
                break
121
122
    if not os.path.isfile(testfile):
123
        print("ERROR: %s does not exist! Please specify a valid OVAL file."
124
              % testfile)
125
        sys.exit(1)
126
127
    return testfile
128
129
130
def read_ovaldefgroup_file(testfile):
131
    """Read oval files"""
132
    with open(testfile, 'r') as test_file:
133
        body = test_file.read()
134
    return body
135
136
137
def parse_options():
138
    usage = "usage: %(prog)s [options] definition_file.xml"
139
    parser = argparse.ArgumentParser(usage=usage, version="%(prog)s ")
140
    # only some options are on by default
141
142
    parser.add_argument("--oval_version", default=oval_version,
143
                        dest="oval_version", action="store",
144
                        help="OVAL version to use. Example: 5.11, 5.10, ... \
145
                        [Default: %(default)s]")
146
    parser.add_argument("-q", "--quiet", "--silent", default=False,
147
                        action="store_true", dest="silent_mode",
148
                        help="Don't show any output when testing OVAL files")
149
    parser.add_argument("xmlfile", metavar="XMLFILE", help="OVAL XML file")
150
    args = parser.parse_args()
151
152
    return args
153
154
155
def main():
156
    global definitions
157
    global tests
158
    global objects
159
    global states
160
    global variables
161
    global silent_mode
162
163
    args = parse_options()
164
    silent_mode = args.silent_mode
165
    oval_version = args.oval_version
166
167
    testfile = args.xmlfile
168
    header = oval_generated_header("testoval.py", oval_version, "0.0.1")
169
    testfile = find_testfile(testfile)
170
    body = read_ovaldefgroup_file(testfile)
171
    defname = add_oval_elements(body, header)
172
    ovaltree = ET.fromstring(header + footer)
173
174
    # append each major element type, if it has subelements
175
    for element in [definitions, tests, objects, states, variables]:
176
        if element.getchildren():
177
            ovaltree.append(element)
178
    # re-map all the element ids from meaningful names to meaningless
179
    # numbers
180
    testtranslator = idtranslate.IDTranslator("scap-security-guide.testing")
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable idtranslate does not seem to be defined.
Loading history...
181
    ovaltree = testtranslator.translate(ovaltree)
182
    (ovalfile, fname) = tempfile.mkstemp(prefix=defname, suffix=".xml")
183
    os.write(ovalfile, ET.tostring(ovaltree))
184
    os.close(ovalfile)
185
    if not silent_mode:
186
        print("Evaluating with OVAL tempfile: " + fname)
187
        print("OVAL Schema Version: %s" % oval_version)
188
        print("Writing results to: " + fname + "-results")
189
    cmd = "oscap oval eval --results " + fname + "-results " + fname
190
    oscap_child = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True)
191
    cmd_out = oscap_child.communicate()[0]
192
    if not silent_mode:
193
        print cmd_out
0 ignored issues
show
introduced by
Missing parentheses in call to 'print'. Did you mean print(print cmd_out)? (<string>, line 193)
Loading history...
194
    if oscap_child.returncode != 0:
195
        if not silent_mode:
196
            print("Error launching 'oscap' command: \n\t" + cmd)
197
        sys.exit(2)
198
    if 'false' in cmd_out:
199
        # at least one from the evaluated OVAL definitions evaluated to
200
        # 'false' result, exit with '1' to indicate OVAL scan FAIL result
201
        sys.exit(1)
202
    # perhaps delete tempfile?
203
    definitions = ET.Element("definitions")
204
    tests = ET.Element("tests")
205
    objects = ET.Element("objects")
206
    states = ET.Element("states")
207
    variables = ET.Element("variables")
208
209
    # 'false' keyword wasn't found in oscap's command output
210
    # exit with '0' to indicate OVAL scan TRUE result
211
    sys.exit(0)
212