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 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189
|
#!/usr/bin/python3
from __future__ import print_function
import sys
import argparse
import os
import ssg
import ssg.xml
import xml.dom.minidom
from ssg.constants import XCCDF11_NS, XCCDF12_NS, OSCAP_RULE
from ssg.utils import mkdir_p
ET = ssg.xml.ElementTree
owner = "disastig"
stig_ns = ["https://public.cyber.mil/stigs/downloads/?_dl_facet_stigs=operating-systems%2Cunix-linux",
"https://public.cyber.mil/stigs/downloads/?_dl_facet_stigs=operating-systems%2Cgeneral-purpose-os",
"https://public.cyber.mil/stigs/downloads/?_dl_facet_stigs=app-security%2Capplication-servers",
"https://public.cyber.mil/stigs/downloads/?_dl_facet_stigs=app-security%2Capp-security-dev"]
dc_ns = "http://purl.org/dc/elements/1.1/"
outfile = "stig_overlay.xml"
def yes_no_prompt():
prompt = "Would you like to proceed? (Y/N): "
while True:
data = str(input(prompt)).lower()
if data in ("yes", "y"):
return True
elif data in ("n", "no"):
return False
def element_value(element, element_obj):
for elem in element_obj.findall("./{%s}%s" % (XCCDF11_NS, element)):
elem = elem.text
try:
return elem
except UnboundLocalError as e:
return ""
def ssg_xccdf_stigid_mapping(ssgtree):
xccdf_ns = ssg.xml.determine_xccdf_tree_namespace(ssgtree)
xccdftostig_idmapping = {}
for rule in ssgtree.findall(".//{%s}Rule" % xccdf_ns):
srgs = []
rhid = []
xccdfid = rule.get("id")
if xccdf_ns == XCCDF12_NS:
xccdfid = xccdfid.replace(OSCAP_RULE, "")
if xccdfid is not None:
for references in stig_ns:
stig = [ids for ids in rule.findall(".//{%s}reference[@href='%s']" % (xccdf_ns, references))]
for ref in reversed(stig):
if not ref.text.startswith("SRG-"):
rhid.append(ref.text)
else:
srgs.append(ref.text)
for id in rhid:
xccdftostig_idmapping.update({id: {xccdfid: srgs}})
return xccdftostig_idmapping
def getkey(elem):
return elem.get("ownerid")
def new_stig_overlay(xccdftree, ssgtree, outfile, quiet):
if not ssgtree:
ssg_mapping = False
else:
ssg_mapping = ssg_xccdf_stigid_mapping(ssgtree)
new_stig_overlay = ET.Element("overlays", xmlns=XCCDF11_NS)
for group in xccdftree.findall("./{%s}Group" % XCCDF11_NS):
vkey = group.get("id").strip('V-')
for title in group.findall("./{%s}title" % XCCDF11_NS):
srg = title.text
for rule in group.findall("./{%s}Rule" % XCCDF11_NS):
svkey_raw = rule.get("id")
svkey = svkey_raw.strip()[3:9]
severity = rule.get("severity")
release = svkey_raw.strip()[10:-5]
version = element_value("version", rule)
rule_title = element_value("title", rule)
ident = element_value("ident", rule).strip("CCI-").lstrip("0")
if not ssgtree:
mapped_id = "XXXX"
else:
try:
mapped_id = ''.join(ssg_mapping[version].keys())
except KeyError as e:
mapped_id = "XXXX"
overlay = ET.SubElement(new_stig_overlay, "overlay", owner=owner,
ruleid=mapped_id, ownerid=version, disa=ident,
severity=severity)
vmsinfo = ET.SubElement(overlay, "VMSinfo", VKey=vkey,
SVKey=svkey, VRelease=release)
title = ET.SubElement(overlay, "title", text=rule_title)
lines = new_stig_overlay.findall("overlay")
new_stig_overlay[:] = sorted(lines, key=getkey)
try:
et_str = ET.tostring(new_stig_overlay, encoding="UTF-8", xml_declaration=True)
except TypeError:
et_str = ET.tostring(new_stig_overlay, encoding="UTF-8")
dom = xml.dom.minidom.parseString(et_str)
pretty_xml_as_string = dom.toprettyxml(indent=' ', encoding="UTF-8")
overlay_directory = os.path.dirname(outfile)
if mkdir_p(overlay_directory) and not quiet:
print("\nOverlay directory created: %s" % overlay_directory)
with open(outfile, 'wb') as f:
f.write(pretty_xml_as_string)
if not quiet:
print("\nGenerated the new STIG overlay file: %s" % outfile)
def parse_args():
parser = argparse.ArgumentParser()
parser.add_argument("--disa-xccdf", default=False, required=True,
action="store", dest="disa_xccdf_filename",
help="A DISA generated XCCDF Manual checks file. \
For example: disa-stig-rhel8-v1r12-xccdf-manual.xml")
parser.add_argument("--ssg-xccdf", default=None,
action="store", dest="ssg_xccdf_filename",
help="A SSG generated XCCDF file. Can be XCCDF 1.1 or XCCDF 1.2 \
For example: ssg-rhel8-xccdf.xml")
parser.add_argument("-o", "--output", required=True,
action="store", dest="output_file",
help="STIG overlay XML content file \
[default: %s]" % outfile)
parser.add_argument("-q", "--quiet", dest="quiet", default=False,
action="store_true", help="Do not print anything and assume yes for everything")
return parser.parse_args()
def main():
args = parse_args()
disa_xccdftree = ET.parse(args.disa_xccdf_filename)
if not args.ssg_xccdf_filename:
prompt = True
if not args.quiet:
print("WARNING: You are generating a STIG overlay XML file without mapping it "
"to existing SSG content.")
prompt = yes_no_prompt()
if not prompt:
sys.exit(0)
ssg_xccdftree = False
else:
ssg_xccdftree = ET.parse(args.ssg_xccdf_filename)
ssg = ssg_xccdftree.find(".//{%s}publisher" % dc_ns).text
if ssg != "SCAP Security Guide Project":
if not args.quiet:
sys.exit("%s is not a valid SSG generated XCCDF file." % args.ssg_xccdf_filename)
else:
sys.exit(1)
disa = disa_xccdftree.find(".//{%s}source" % dc_ns).text
if disa != "STIG.DOD.MIL":
if not args.quiet:
sys.exit("%s is not a valid DISA generated manual XCCDF file." % args.disa_xccdf_filename)
else:
sys.exit(2)
new_stig_overlay(disa_xccdftree, ssg_xccdftree, args.output_file, args.quiet)
if __name__ == "__main__":
main()
|