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 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446
|
from __future__ import absolute_import
from __future__ import print_function
import sys
import os
from .constants import (
OSCAP_RULE, OSCAP_VALUE, oval_namespace, XCCDF12_NS, cce_uri, ocil_cs,
ocil_namespace, OVAL_TO_XCCDF_DATATYPE_CONSTRAINTS
)
from . import utils
from .xml import parse_file, map_elements_to_their_ids
from .oval_object_model import load_oval_document, OVALDefinitionReference
from .checks import get_content_ref_if_exists_and_not_remote
from .cce import is_cce_value_valid, is_cce_format_valid
from .utils import SSGError
from .xml import ElementTree as ET
oval_ns = oval_namespace
oval_cs = oval_namespace
class FileLinker(object):
"""
Bass class which represents the linking of checks to their identifiers.
"""
CHECK_SYSTEM = None
CHECK_NAMESPACE = None
def __init__(self, translator, xccdftree, checks, output_file_name):
self.translator = translator
self.checks_related_to_us = self.get_related_checks(checks)
self.fname = self._get_input_fname()
self.tree = None
self.linked_fname = output_file_name
self.linked_fname_basename = os.path.basename(self.linked_fname)
self.xccdftree = xccdftree
def get_related_checks(self, checks):
"""
Returns a list of checks which have the same check system as this
class.
"""
return [ch for ch in checks if ch.get("system") == self.CHECK_SYSTEM]
def _get_fnames_from_related_checks(self):
"""
Returns a list of filenames from non-remote check content href
attributes.
"""
checkfiles = set()
for check in self.checks_related_to_us:
# Include the file in the particular check system only if it's NOT
# a remotely located file (to allow OVAL checks to reference http://
# and https:// formatted URLs)
checkcontentref = get_content_ref_if_exists_and_not_remote(check)
if checkcontentref is not None:
checkfiles.add(checkcontentref.get("href"))
return checkfiles
def _get_input_fname(self):
"""
Returns the input filename referenced from the related check.
Raises SSGError if there are more than one filenames related to
this check system.
"""
fnames = self._get_fnames_from_related_checks()
if len(fnames) > 1:
msg = ("referencing more than one file per check system "
"is not yet supported by this script.")
raise SSGError(msg)
return fnames.pop() if fnames else None
def save_linked_tree(self):
"""
Write internal tree to the file in self.linked_fname.
"""
assert self.tree is not None, \
"There is no tree to save, you have probably skipped the linking phase"
if hasattr(ET, "indent"):
ET.indent(self.tree, space=" ", level=0)
ET.ElementTree(self.tree).write(self.linked_fname, xml_declaration=True, encoding="utf-8")
def _get_checkid_string(self):
raise NotImplementedError()
def add_missing_check_exports(self, check, checkcontentref):
pass
def link_xccdf(self):
for check in self.checks_related_to_us:
checkcontentref = get_content_ref_if_exists_and_not_remote(check)
if checkcontentref is None:
continue
self.add_missing_check_exports(check, checkcontentref)
checkexports = check.findall("./{%s}check-export" % XCCDF12_NS)
self._link_xccdf_checkcontentref(checkcontentref, checkexports)
def _link_xccdf_checkcontentref(self, checkcontentref, checkexports):
checkid = self.translator.generate_id(
self._get_checkid_string(), checkcontentref.get("name"))
checkcontentref.set("name", checkid)
checkcontentref.set("href", self.linked_fname_basename)
variable_str = "{%s}variable" % self.CHECK_NAMESPACE
for checkexport in checkexports:
newexportname = self.translator.generate_id(
variable_str, checkexport.get("export-name"))
checkexport.set("export-name", newexportname)
class OVALFileLinker(FileLinker):
CHECK_SYSTEM = oval_cs
CHECK_NAMESPACE = oval_ns
build_ovals_dir = None
def __init__(self, translator, xccdftree, checks, output_file_name):
super(OVALFileLinker, self).__init__(
translator, xccdftree, checks, output_file_name)
self.oval_document = None
def _get_checkid_string(self):
return "{%s}definition" % self.CHECK_NAMESPACE
def _translate_name_to_oval_definition_id(self, name):
return self.translator.generate_id(self._get_checkid_string(), name)
def _get_path_for_oval_document(self, name):
if self.build_ovals_dir:
utils.mkdir_p(self.build_ovals_dir)
return os.path.join(self.build_ovals_dir, name + ".xml")
def _get_list_of_names_of_oval_checks(self):
out = []
for _, rule in rules_with_ids_generator(self.xccdftree):
for check in rule.findall(".//{%s}check" % (XCCDF12_NS)):
checkcontentref = get_content_ref_if_exists_and_not_remote(check)
if checkcontentref is None or check.get("system") != oval_cs:
continue
out.append(checkcontentref.get("name"))
return out
def save_oval_document_for_each_xccdf_rule(self, file_name_prefix=""):
for name in self._get_list_of_names_of_oval_checks():
if name in self.oval_document.definitions:
oval_def = self.oval_document.definitions[name]
name = oval_def.name
oval_id = self._translate_name_to_oval_definition_id(name)
refs = self.oval_document.get_all_references_of_definition(oval_id)
path = self._get_path_for_oval_document(file_name_prefix + name)
with open(path, "wb+") as fd:
self.oval_document.save_as_xml(fd, refs)
def save_linked_tree(self):
"""
Write internal tree to the file in self.linked_fname.
"""
with open(self.linked_fname, "wb+") as fd:
self.oval_document.save_as_xml(fd)
if self.build_ovals_dir:
self.save_oval_document_for_each_xccdf_rule()
def link(self):
self.oval_document = load_oval_document(parse_file(self.fname))
self.oval_document.product_name = "OVALFileLinker"
try:
self._link_oval_tree()
# Verify if CCE identifiers present in the XCCDF follow the required form
# (either CCE-XXXX-X, or CCE-XXXXX-X). Drop from XCCDF those who don't follow it
verify_correct_form_of_referenced_cce_identifiers(self.xccdftree)
except SSGError as exc:
raise SSGError("Error processing {0}: {1}".format(self.fname, str(exc)))
self.oval_document = self.translator.translate_oval_document(
self.oval_document, store_defname=True
)
def _link_oval_tree(self):
self.oval_document.validate_references()
xccdf_to_cce_id_mapping = create_xccdf_id_to_cce_id_mapping(self.xccdftree)
self._add_cce_id_refs_to_oval_checks(xccdf_to_cce_id_mapping)
# Verify all by XCCDF referenced (local) OVAL checks are defined in OVAL file
# If not drop the <check-content> OVAL checksystem reference from XCCDF
self._ensure_by_xccdf_referenced_oval_def_is_defined_in_oval_file()
self._ensure_by_xccdf_referenced_oval_no_extra_def_in_oval_file()
check_and_correct_xccdf_to_oval_data_export_matching_constraints(
self.xccdftree, self.oval_document
)
def _add_cce_id_refs_to_oval_checks(self, idmappingdict):
"""
For each XCCDF rule ID having <ident> CCE set and
having OVAL check implemented (remote OVAL isn't sufficient!)
add a new <reference> element into the OVAL definition having the
following form:
<reference source="CCE" ref_id="CCE-ID" />
where "CCE-ID" is the CCE identifier for that particular rule
retrieved from the XCCDF file
"""
for oval_definition in self.oval_document.definitions.values():
ovalid = oval_definition.id_
assert ovalid is not None, "An OVAL rule doesn't have an ID"
if ovalid not in idmappingdict:
continue
xccdfcceid = idmappingdict[ovalid]
if is_cce_format_valid(xccdfcceid) and is_cce_value_valid(xccdfcceid):
# Then append the <reference source="CCE" ref_id="CCE-ID" /> element right
# after <description> element of specific OVAL check
oval_definition.metadata.add_reference(xccdfcceid, "CCE")
def add_missing_check_exports(self, check, checkcontentref):
check_name = checkcontentref.get("name")
if check_name is None:
return
oval_def_id = self._translate_name_to_oval_definition_id(check_name)
oval_def = self.oval_document.definitions.get(oval_def_id)
if oval_def is None:
return
all_vars = set()
references = self.oval_document.get_all_references_of_definition(oval_def_id)
for var_id in references.variables:
variable = self.oval_document.variables[var_id]
if "external_variable" in variable.tag:
all_vars.add(variable.name)
for varname in sorted(all_vars):
export = ET.Element("{%s}check-export" % XCCDF12_NS)
export.attrib["export-name"] = varname
export.attrib["value-id"] = OSCAP_VALUE + varname
check.insert(0, export)
def _ensure_by_xccdf_referenced_oval_def_is_defined_in_oval_file(self):
# Ensure all OVAL checks referenced by XCCDF are implemented in OVAL file
# Drop the reference from XCCDF to OVAL definition if:
# * Particular OVAL definition isn't present in OVAL file,
# * That OVAL definition doesn't constitute a remote OVAL
# (@href of <check-content-ref> doesn't start with 'http'
for xccdfid, rule in rules_with_ids_generator(self.xccdftree):
# Search OVAL ID in OVAL document
ovalid = self.oval_document.definitions.get(xccdfid)
if ovalid is not None:
# The OVAL check was found, we can continue
continue
for check in rule.findall(".//{%s}check" % (XCCDF12_NS)):
if check.get("system") != oval_cs:
continue
if get_content_ref_if_exists_and_not_remote(check) is None:
continue
# For local OVAL drop the reference to OVAL definition from XCCDF document
# in the case:
# * OVAL definition is referenced from XCCDF file,
# * But not defined in OVAL file
rule.remove(check)
def _ensure_by_xccdf_referenced_oval_no_extra_def_in_oval_file(self):
# Remove all OVAL checks that are not referenced by XCCDF Rules (checks)
# or internally via extend-definition
xccdf_oval_check_refs = self._get_list_of_names_of_oval_checks()
document_def_keys = list(self.oval_document.definitions.keys())
references_from_xccdf_to_keep = OVALDefinitionReference()
for def_id in document_def_keys:
if def_id in xccdf_oval_check_refs:
oval_def_refs = self.oval_document.get_all_references_of_definition(def_id)
references_from_xccdf_to_keep += oval_def_refs
self.oval_document.keep_referenced_components(references_from_xccdf_to_keep)
class OCILFileLinker(FileLinker):
CHECK_SYSTEM = ocil_cs
CHECK_NAMESPACE = ocil_namespace
def _get_checkid_string(self):
return "{%s}questionnaire" % self.CHECK_NAMESPACE
def link(self, tree):
self.tree = tree
self.tree = self.translator.translate(self.tree, store_defname=True)
def _find_identcce(rule, namespace=XCCDF12_NS):
for ident in rule.findall("./{%s}ident" % namespace):
if ident.get("system") == cce_uri:
return ident
return None
def rules_with_ids_generator(xccdftree):
xccdfrules = xccdftree.findall(".//{%s}Rule" % XCCDF12_NS)
for rule in xccdfrules:
xccdfid = rule.get("id").replace(OSCAP_RULE, "")
if xccdfid is None:
continue
yield xccdfid, rule
def create_xccdf_id_to_cce_id_mapping(xccdftree):
#
# Create dictionary having form of
#
# 'XCCDF ID' : 'CCE ID'
#
# for each XCCDF rule having <ident system='http://cce.mitre.org'>CCE-ID</ident>
# element set in the XCCDF document
xccdftocce_idmapping = {}
for xccdfid, rule in rules_with_ids_generator(xccdftree):
identcce = _find_identcce(rule)
if identcce is None:
continue
xccdftocce_idmapping[xccdfid] = identcce.text
return xccdftocce_idmapping
def check_and_correct_xccdf_to_oval_data_export_matching_constraints(
xccdftree, oval_document
):
"""
Verify if <xccdf:Value> 'type' to corresponding OVAL variable
'datatype' export matching constraint:
http://csrc.nist.gov/publications/nistpubs/800-126-rev2/SP800-126r2.pdf#page=30&zoom=auto,69,313
is met. Also correct the 'type' attribute of those <xccdf:Value> elements where necessary
in order the produced content to meet this constraint.
To correct the constraint we use simpler approach - prefer to fix
'type' attribute of <xccdf:Value> rather than 'datatype' attribute
of the corresponding OVAL variable since there might be additional
OVAL variables, derived from the affected OVAL variable, and in that
case we would need to fix the 'datatype' attribute in each of them.
Define the <xccdf:Value> 'type' to OVAL variable 'datatype' export matching
constraints mapping as specified in Table 16 of XCCDF v1.2 standard:
http://csrc.nist.gov/publications/nistpubs/800-126-rev2/SP800-126r2.pdf#page=30&zoom=auto,69,313
"""
indexed_xccdf_values = map_elements_to_their_ids(
xccdftree, ".//{%s}Value" % (XCCDF12_NS)
)
# Loop through all <external_variables> in the OVAL document
oval_extvars = [
var
for var in oval_document.variables.values()
if "external_variable" in var.tag
]
for oval_extvar in oval_extvars:
oval_var_id = oval_extvar.id_
oval_var_type = oval_extvar.data_type
# Locate the corresponding <xccdf:Value> with the same ID in the XCCDF
xccdf_var = indexed_xccdf_values.get(oval_var_id)
if xccdf_var is None:
return
xccdf_var_type = xccdf_var.get("type")
# Verify the found value has 'type' attribute set
if xccdf_var_type is None:
msg = "Invalid XCCDF variable '{0}': Missing the 'type' attribute.".format(
xccdf_var.get("id")
)
raise SSGError(msg)
# This is the required XCCDF 'type' for <xccdf:Value> derived
# from OVAL variable 'datatype' and mapping above
assert (
oval_var_type in OVAL_TO_XCCDF_DATATYPE_CONSTRAINTS
), 'datatype not known: "%s" "%s known types "%s"' % (
oval_var_id,
oval_var_type,
OVAL_TO_XCCDF_DATATYPE_CONSTRAINTS,
)
req_xccdf_type = OVAL_TO_XCCDF_DATATYPE_CONSTRAINTS[oval_var_type]
# Compare the actual value of 'type' of <xccdf:Value> with the requirement
if xccdf_var_type != req_xccdf_type:
# If discrepancy is found, issue a warning
sys.stderr.write(
"Warning: XCCDF 'type' of \"%s\" value does "
"not meet the XCCDF value 'type' to OVAL "
"variable 'datatype' export matching "
'constraint! Got: "%s", Expected: "%s". '
"Resetting it! Set 'type' of \"%s\" "
"<xccdf:value> to '%s' directly in the XCCDF "
"content to dismiss this warning!\n"
% (
oval_var_id,
xccdf_var_type,
req_xccdf_type,
oval_var_id,
req_xccdf_type,
)
)
# And reset the 'type' attribute of such a <xccdf:Value> to the required type
xccdf_var.attrib["type"] = req_xccdf_type
def verify_correct_form_of_referenced_cce_identifiers(xccdftree):
"""
In SSG benchmarks, the CCEs till unassigned have the form of e.g. "RHEL7-CCE-TBD"
(or any other format possibly not matching the above two requirements)
If this is the case for specific SSG product, drop such CCE identifiers from the XCCDF
since they are in invalid format!
"""
xccdfrules = xccdftree.findall(".//{%s}Rule" % XCCDF12_NS)
for rule in xccdfrules:
identcce = _find_identcce(rule)
if identcce is None:
continue
cceid = identcce.text
if not is_cce_format_valid(cceid):
msg = (
"Warning: CCE '{0}' is invalid for rule '{1}'. "
"Removing CCE...".format(cceid, rule.get("id")))
raise SSGError(msg)
|