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
|