Issues (70)

utils/create-stig-overlay.py (1 issue)

1
#!/usr/bin/python3
2
3
from __future__ import print_function
4
5
import sys
6
import argparse
7
import os
8
import ssg
9
import ssg.xml
10
import xml.dom.minidom
11
12
from ssg.constants import XCCDF11_NS, XCCDF12_NS, OSCAP_RULE
13
from ssg.utils import mkdir_p
14
15
16
ET = ssg.xml.ElementTree
17
18
19
owner = "disastig"
20
stig_ns = ["https://public.cyber.mil/stigs/downloads/?_dl_facet_stigs=operating-systems%2Cunix-linux",
21
           "https://public.cyber.mil/stigs/downloads/?_dl_facet_stigs=operating-systems%2Cgeneral-purpose-os",
22
           "https://public.cyber.mil/stigs/downloads/?_dl_facet_stigs=app-security%2Capplication-servers",
23
           "https://public.cyber.mil/stigs/downloads/?_dl_facet_stigs=app-security%2Capp-security-dev"]
24
dc_ns = "http://purl.org/dc/elements/1.1/"
25
outfile = "stig_overlay.xml"
26
27
28
def yes_no_prompt():
29
    prompt = "Would you like to proceed? (Y/N): "
30
31
    while True:
32
        data = str(input(prompt)).lower()
33
34
        if data in ("yes", "y"):
35
            return True
36
        elif data in ("n", "no"):
37
            return False
38
39
40
def element_value(element, element_obj):
41
    for elem in element_obj.findall("./{%s}%s" % (XCCDF11_NS, element)):
42
        elem = elem.text
43
    try:
44
        return elem
45
    except UnboundLocalError as e:
46
        return ""
47
48
49
def ssg_xccdf_stigid_mapping(ssgtree):
50
    xccdf_ns = ssg.xml.determine_xccdf_tree_namespace(ssgtree)
51
    xccdftostig_idmapping = {}
52
53
    for rule in ssgtree.findall(".//{%s}Rule" % xccdf_ns):
54
        srgs = []
55
        rhid = []
56
57
        xccdfid = rule.get("id")
58
        if xccdf_ns == XCCDF12_NS:
59
            xccdfid = xccdfid.replace(OSCAP_RULE, "")
60
        if xccdfid is not None:
61
            for references in stig_ns:
62
                stig = [ids for ids in rule.findall(".//{%s}reference[@href='%s']" % (xccdf_ns, references))]
63
                for ref in reversed(stig):
64
                    if not ref.text.startswith("SRG-"):
65
                        rhid.append(ref.text)
66
                    else:
67
                        srgs.append(ref.text)
68
            for id in rhid:
69
                xccdftostig_idmapping.update({id: {xccdfid: srgs}})
70
71
    return xccdftostig_idmapping
72
73
74
def getkey(elem):
75
    return elem.get("ownerid")
76
77
78
def new_stig_overlay(xccdftree, ssgtree, outfile, quiet):
79
    if not ssgtree:
80
        ssg_mapping = False
81
    else:
82
        ssg_mapping = ssg_xccdf_stigid_mapping(ssgtree)
83
84
    new_stig_overlay = ET.Element("overlays", xmlns=XCCDF11_NS)
85
    for group in xccdftree.findall("./{%s}Group" % XCCDF11_NS):
86
        vkey = group.get("id").strip('V-')
87
        for title in group.findall("./{%s}title" % XCCDF11_NS):
88
            srg = title.text
89
        for rule in group.findall("./{%s}Rule" % XCCDF11_NS):
90
            svkey_raw = rule.get("id")
91
            svkey = svkey_raw.strip()[3:9]
92
            severity = rule.get("severity")
93
            release = svkey_raw.strip()[10:-5]
94
            version = element_value("version", rule)
95
            rule_title = element_value("title", rule)
96
            ident = element_value("ident", rule).strip("CCI-").lstrip("0")
97
98
        if not ssgtree:
99
            mapped_id = "XXXX"
100
        else:
101
            try:
102
                mapped_id = ''.join(ssg_mapping[version].keys())
103
            except KeyError as e:
104
                mapped_id = "XXXX"
105
106
        overlay = ET.SubElement(new_stig_overlay, "overlay", owner=owner,
107
                                ruleid=mapped_id, ownerid=version, disa=ident,
108
                                severity=severity)
109
        vmsinfo = ET.SubElement(overlay, "VMSinfo", VKey=vkey,
110
                                SVKey=svkey, VRelease=release)
111
        title = ET.SubElement(overlay, "title", text=rule_title)
0 ignored issues
show
The variable rule_title does not seem to be defined for all execution paths.
Loading history...
112
113
    lines = new_stig_overlay.findall("overlay")
114
    new_stig_overlay[:] = sorted(lines, key=getkey)
115
116
    try:
117
        et_str = ET.tostring(new_stig_overlay, encoding="UTF-8", xml_declaration=True)
118
    except TypeError:
119
        et_str = ET.tostring(new_stig_overlay, encoding="UTF-8")
120
121
    dom = xml.dom.minidom.parseString(et_str)
122
    pretty_xml_as_string = dom.toprettyxml(indent='  ', encoding="UTF-8")
123
124
    overlay_directory = os.path.dirname(outfile)
125
    if mkdir_p(overlay_directory) and not quiet:
126
        print("\nOverlay directory created: %s" % overlay_directory)
127
128
    with open(outfile, 'wb') as f:
129
        f.write(pretty_xml_as_string)
130
131
    if not quiet:
132
        print("\nGenerated the new STIG overlay file: %s" % outfile)
133
134
135
def parse_args():
136
    parser = argparse.ArgumentParser()
137
    parser.add_argument("--disa-xccdf", default=False, required=True,
138
                        action="store", dest="disa_xccdf_filename",
139
                        help="A DISA generated XCCDF Manual checks file. \
140
                              For example: disa-stig-rhel8-v1r12-xccdf-manual.xml")
141
    parser.add_argument("--ssg-xccdf", default=None,
142
                        action="store", dest="ssg_xccdf_filename",
143
                        help="A SSG generated XCCDF file. Can be XCCDF 1.1 or XCCDF 1.2 \
144
                              For example: ssg-rhel8-xccdf.xml")
145
    parser.add_argument("-o", "--output", required=True,
146
                        action="store", dest="output_file",
147
                        help="STIG overlay XML content file \
148
                            [default: %s]" % outfile)
149
    parser.add_argument("-q", "--quiet", dest="quiet", default=False,
150
                      action="store_true", help="Do not print anything and assume yes for everything")
151
152
    return parser.parse_args()
153
154
155
def main():
156
    args = parse_args()
157
158
    disa_xccdftree = ET.parse(args.disa_xccdf_filename)
159
160
    if not args.ssg_xccdf_filename:
161
        prompt = True
162
        if not args.quiet:
163
            print("WARNING: You are generating a STIG overlay XML file without mapping it "
164
                "to existing SSG content.")
165
            prompt = yes_no_prompt()
166
        if not prompt:
167
            sys.exit(0)
168
        ssg_xccdftree = False
169
    else:
170
        ssg_xccdftree = ET.parse(args.ssg_xccdf_filename)
171
        ssg = ssg_xccdftree.find(".//{%s}publisher" % dc_ns).text
172
        if ssg != "SCAP Security Guide Project":
173
            if not args.quiet:
174
                sys.exit("%s is not a valid SSG generated XCCDF file." % args.ssg_xccdf_filename)
175
            else:
176
                sys.exit(1)
177
178
    disa = disa_xccdftree.find(".//{%s}source" % dc_ns).text
179
    if disa != "STIG.DOD.MIL":
180
        if not args.quiet:
181
            sys.exit("%s is not a valid DISA generated manual XCCDF file." % args.disa_xccdf_filename)
182
        else:
183
            sys.exit(2)
184
185
    new_stig_overlay(disa_xccdftree, ssg_xccdftree, args.output_file, args.quiet)
186
187
188
if __name__ == "__main__":
189
    main()
190