File: gen_stig_table.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 (98 lines) | stat: -rw-r--r-- 3,103 bytes parent folder | download
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
#!/usr/bin/python3
import argparse
import os
from utils.template_renderer import render_template
from ssg.xml import ElementTree as ET
from ssg.xml import determine_xccdf_tree_namespace


TABLE_DIR = os.path.join(os.path.dirname(__file__), "tables")
STIG_TEMPLATE = os.path.join(TABLE_DIR, "stig_template.html")


def parse_args():
    parser = argparse.ArgumentParser(
        description="Create a HTML STIG table")
    parser.add_argument("input", help="Input XCCDF file")
    parser.add_argument("output", help="Output HTML file")
    return parser.parse_args()


def get_stats(root, ns):
    total = 0
    missing = 0
    missing_rules = []
    for rule in root.findall("xccdf:Group/xccdf:Rule", ns):
        total += 1
        if rule.get("id") == "Missing Rule":
            missing += 1
            version_text = rule.find("xccdf:version", ns).text
            missing_rules.append(version_text)
    implemented = total - missing
    stats = dict()
    stats["total"] = total
    stats["missing"] = missing
    stats["implemented"] = implemented
    if total != 0:
        stats["coverage"] = implemented / total * 100
    else:
        stats["coverage"] = 0
    stats["missing_rules"] = missing_rules
    return stats


def subtree_texts(el):
    if el is None:
        return ""
    else:
        return "".join(el.itertext())


def parse_description(rule_el, ns):
    description = subtree_texts(rule_el.find("./xccdf:description", ns))
    if description is None:
        return ""
    elif "<VulnDiscussion>" in description:
        _, _, after_open = description.partition("<VulnDiscussion>")
        before_close, _, _ = after_open.partition("</VulnDiscussion>")
        return before_close
    else:
        return description


def get_rules(root, ns):
    rules = []
    for group in root.findall(".//xccdf:Group", ns):
        rule = dict()
        rule_el = group.find("./xccdf:Rule", ns)
        rule["V-ID"] = group.get("id")
        ident = rule_el.find("./xccdf:ident", ns)
        rule["CCI"] = ident.text if ident.text is not None else ""
        rule["CAT"] = rule_el.get("severity")
        rule["title"] = rule_el.find("./xccdf:title", ns).text
        rule["SRG"] = group.find("./xccdf:title", ns).text
        rule["description"] = parse_description(rule_el, ns)
        check = rule_el.find("./xccdf:check/xccdf:check-content", ns)
        rule["check"] = check.text if check.text is not None else ""
        rule["fixtext"] = subtree_texts(rule_el.find("./xccdf:fixtext", ns))
        rule["version"] = rule_el.find("./xccdf:version", ns).text
        rule["mapped_rule"] = rule_el.get("id")
        rules.append(rule)
    return rules


def main():
    args = parse_args()
    tree = ET.parse(args.input)
    xccdf = determine_xccdf_tree_namespace(tree)
    ns = {"xccdf": xccdf}
    data = dict()
    root = tree.getroot()
    data["title"] = "Rules in " + root.find("./xccdf:title", ns).text
    data["stats"] = get_stats(root, ns)
    data["rules"] = get_rules(root, ns)
    render_template(data, STIG_TEMPLATE, args.output)


if __name__ == "__main__":
    main()