File: template.py

package info (click to toggle)
scap-security-guide 0.1.76-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 110,644 kB
  • sloc: xml: 241,883; sh: 73,777; python: 32,527; makefile: 27
file content (168 lines) | stat: -rw-r--r-- 6,388 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
from __future__ import print_function
import re
import json


def normalize_policy_path(path_string):
    """Generate a normalized path array from yaml.
       Paths are space-separated key names.
       All firefox policies are under the policies key.
    """
    path = []
    if len(path_string.strip()) > 0:
        path = re.split(" ", path_string)
    if path[0] != 'policies':
        path = ['policies'] + path
    return path


def make_json_boolean_match_regex(json_value):
    if json_value.lower() in ["false", "true"]:
        return r"[{0}{1}]{2}".format(json_value[0].upper(),
                                     json_value[0].lower(),
                                     json_value[1:]
                                     )
    return "\"{0}\"".format(json_value)


def make_json_boolean_remediate(json_value):
    if json_value.lower() in ["false", "true"]:
        return json_value.lower() == "true"
    return "\'{0}\'".format(json_value)


# These regexes work under the complexities currently observed with the Firefox
# policies.json, which is (so far) a subset of JSON that is just nested keys.
# No JSON arrays are supported.
# Remove the moment that OpenSCAP/OVAL spec supports a JSON configuration probe.
OVAL_MATCH_ANY_FOLLOWING = r"[^}]*\}"
OVAL_MATCH_OPEN_BRACES_FOR_BLOCK = r"\{[\s\S]*?"
OVAL_MATCH_ANYTHING_AFTER_OPEN_BRACE = r"\{[\s\S]*"

# Note: str.format() notation.
OVAL_MATCH_JSON_KEY = r'(?="{0}")"{0}"[\s]*:[\s]*'


def regex_escape(_search_string):
    return re.escape(_search_string)


def build_oval_search_regex_for_json_parameter(_path, parameter):
    parameter = r'(?=[^"]){0}"[\s]*:[\s]*([^,}}]+),?'.format(parameter)
    _result = parameter
    for p in reversed(_path):
        depth_match_block = OVAL_MATCH_OPEN_BRACES_FOR_BLOCK
        if p == "policies":
            depth_match_block = OVAL_MATCH_ANYTHING_AFTER_OPEN_BRACE

        _result = ''.join([OVAL_MATCH_JSON_KEY.format(regex_escape(p)),
                           depth_match_block,
                           _result])
    _result = r'^(?i)\{\s*' + _result + r'\s*'
    return _result


def build_python_json_notation(_path, depth=0):
    """Generate the Python call syntax for this path within a given distance
       from the top-level node.

       Parameters:
           _path: the list of the entire path in the JSON document to this
           particular key.
           depth: How far along the path to generate this syntax.
    """
    if depth > len(_path):
        return r''
    _result = r''
    for p in list(reversed(_path))[depth:]:
        _result = ''.join(["['{0}']".format(p), _result])
    return _result


def build_test_json(policy, _test, missing=False, wrong=False):
    """Generate a minimal JSON configuration for this particular policy object, based
       on a correct (set to value), missing, or wrong value.
       Returns a dict() containing this test entry.

       Parameters:
           policy: current policy item to generate for
           _test: dict() that has at least the value {"policies": {} }
    """
    _current = _test.get("policies")
    _key = policy["parameter"]
    _value = make_json_boolean_remediate(policy["value"])

    for p in policy["subpath"]:
        if p not in _current:
            _current[p] = dict()
        _current = _current.get(p)

    if wrong:
        _current[_key] = "VERYVERYBADVALUE"
    elif not missing:
        _current[_key] = _value
    return _test


def preprocess(data, lang):
    """Generate the appropriate regex for each policy path, search values, etc.
       Expects a dict of template arguments:
           path - Space-separated path in JSON to dictate where the parameter is. "policies" is
                  added to the front if not present or used if path is not present.
           parameter - Parameter name to search for the state of the value.
           value - Expected value to use for remediation.
           search_value (optional) - custom regular expression to use in the match for value.
    """
    _test_correct_config = {"policies": {}}
    _test_bad_missing = {"policies": {}}
    _test_bad_wrong = {"policies": {}}
    for i, _policy in enumerate(data.get("policies", [])):
        _path = normalize_policy_path(_policy.get("path", ""))

        # regex used in OVAL for detection
        _policy["oval_regex"] = build_oval_search_regex_for_json_parameter(_path,
                                                                           _policy["parameter"])
        _policy["oval_state"] = make_json_boolean_match_regex(_policy["value"])
        # Implement override for search_value
        if _policy.get("search_value", None):
            _policy["oval_state"] = _policy["search_value"]

        # path without "policies"
        _policy["subpath"] = _path[1:]
        # Precached notation for addressing JSON path
        _policy["path_python"] = [build_python_json_notation(_path, x)
                                  for x in range(0, len(_path) + 1)][:-1]
        # JSON path as iterable list, sans "policies"
        _policy["subpath_string"] = '_'.join(_path[1:])
        # entire JSON path to be set as an iterable list.
        _policy["path_list"] = _path
        # value with quotes added for simple insertion.
        _policy["value_escaped"] = make_json_boolean_remediate(_policy["value"])

        # correct_value.pass tests
        build_test_json(_policy,
                        _test_correct_config,
                        missing=False,
                        wrong=False)

        # missing_value.fail tests
        build_test_json(_policy,
                        _test_bad_missing,
                        missing=True,
                        wrong=False)

        # wrong_value.fail tests
        build_test_json(_policy,
                        _test_bad_wrong,
                        missing=False,
                        wrong=True)

        # Reassign the modified policy entry.
        data["policies"][i] = _policy

    # Pretty-print the output for our test JSON to variables
    # so that it can be used in the test templates.
    data["_correct_config"] = json.dumps(_test_correct_config, indent=4, separators=(',', ': '))
    data["_bad_missing"] = json.dumps(_test_bad_missing, indent=4, separators=(',', ': '))
    data["_bad_wrong"] = json.dumps(_test_bad_wrong, indent=4, separators=(',', ': '))
    return data