Repository navigation
Expand file tree
/
Copy pathverify_rule_package_consistency.py
More file actions
141 lines (125 loc) · 6.54 KB
/
Copy pathverify_rule_package_consistency.py
File metadata and controls
141 lines (125 loc) · 6.54 KB
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
from collections import defaultdict
import csv
import os
from pathlib import Path
import sys
import json
help_statement = """
Usage: {script_name}
A script which reports consistency issues between the rules.csv and rule package json files.
"""
if (len(sys.argv) == 2 and sys.argv[1] == "--help"):
print(help_statement.format(script_name=sys.argv[0]))
sys.exit(0)
if not len(sys.argv) == 2:
print("Error: incorrect number of arguments", file=sys.stderr)
print("Usage: " + sys.argv[0] + " [--help]", file=sys.stderr)
sys.exit(1)
repo_root = Path(__file__).parent.parent
rules_file_path = repo_root.joinpath('rules.csv')
language_name = sys.argv[1]
failed = False
package_rules_from_csv = defaultdict(set)
count = 0
try:
rules_file = open(rules_file_path, "r")
except PermissionError:
print("Error: No permission to read the rules file located at '" + str(rules_file_path) + "'")
sys.exit(1)
else:
with rules_file:
rules_reader = csv.reader(rules_file)
# Skip header row
next(rules_reader, None)
for rule in rules_reader:
language = rule[0]
# only validate rules for the specified language
if not language == language_name:
continue
standard = rule[1]
rule_id = rule[2]
supportable = rule[3]
obligation_level = rule[4]
enforcement_level = rule[5]
allocated_targets = rule[6]
rule_title = rule[7]
similar_query = rule[8]
package = rule[9]
difficulty = rule[10]
# If the rule is associated with a package
if package:
if not supportable == "Yes":
print(
f"ERROR: {standard} {rule_id} is included as part of package {package} but is not marked as supportable.")
failed = True
else:
package_rules_from_csv[package].add(rule_id)
count += 1
print(f"Found {count} implementable rules, verifying.")
rule_packages_file_path = repo_root.joinpath('rule_packages', language_name)
# Iterate over rule packages, verifying the contents
for rule_package_file_name in os.listdir(rule_packages_file_path):
try:
rule_package_file = open(rule_packages_file_path.joinpath(rule_package_file_name), "r")
except PermissionError:
print("Error: No permission to read the rule package file located at '" + str(rule_package_file_name) + "'")
sys.exit(1)
else:
with rule_package_file:
package_definition = json.load(rule_package_file)
package_name = Path(rule_package_file_name).stem
package_json_rule_ids = set()
for standard_name, rules in package_definition.items():
for rule_id, rule_details in rules.items():
package_json_rule_ids.add(rule_id)
standard_short_name = standard_name.split("-")[0].lower()
standard_dir = repo_root.joinpath(language_name).joinpath(standard_short_name)
# Identify the source pack for this standard
src_pack_dir = standard_dir.joinpath("src")
# Identify the rule src dir
rule_src_dir = src_pack_dir.joinpath("rules").joinpath(rule_id)
if len(rule_details["queries"]) == 0 and rule_id in package_rules_from_csv[package_name] and rule_src_dir.exists():
print(f" - ERROR: Rule {rule_id} missing metadata from {package_name}.json.")
failed = True
if not rule_id in package_rules_from_csv[package_name]:
print(
f" - ERROR: Rule {rule_id} included in {package_name}.json but not marked as supportable in rules.csv.")
failed = True
for query in rule_details["queries"]:
if standard_name == "MISRA-C-2012" and not any(tag for tag in query["tags"] if tag.startswith("external/misra/c/2012/")):
print(
f' - ERROR: MISRA C 2012 query {query["short_name"]}.ql for Rule {rule_id} in {package_name}.json is missing a `external/misra/c/2012/...` tag.')
failed = True
if not standard_name == "MISRA-C-2012" and any(tag for tag in query["tags"] if tag.startswith("external/misra/c/2012/")):
print(
f' - ERROR: {standard_name} query {query["short_name"]}.ql for Rule {rule_id} in {package_name}.json has a spurious `external/misra/c/2012/...` tag.')
failed = True
if standard_name == "CERT-C" or standard_name == "CERT-C++":
expected_properties = [
"severity",
"likelihood",
"remediation-cost",
"priority",
"level"
]
for expected_property in expected_properties:
if not any(tag for tag in query["tags"] if tag.startswith(f"external/cert/{expected_property}/")):
print(
f' - ERROR: {standard_name} query {query["short_name"]}.ql for Rule {rule_id} in {package_name}.json is missing a `external/cert/{expected_property}/...` tag.')
failed = True
if not standard_name == "CERT-C" and not standard_name == "CERT-C++":
if any(tag for tag in query["tags"] if tag.startswith("external/cert/")):
print(
f' - ERROR: {standard_name} query {query["short_name"]}.ql for Rule {rule_id} in {package_name}.json has a spurious `external/cert/...` tag.')
failed = True
rules_csv_rule_ids = package_rules_from_csv[package_name]
json_missing_rules = rules_csv_rule_ids.difference(package_json_rule_ids)
if json_missing_rules:
print(
f" - ERROR: Package file {package_name}.json is missing rule records for the following rules: { json_missing_rules }.")
failed = True
if failed:
print("ERROR: Consistency issues found")
sys.exit(1)
else:
print("No consistency issues found")