def get_fixgroup_for_type(fixcontent, remediation_type): if remediation_type == 'anaconda': return ElementTree.SubElement( fixcontent, "fix-group", id="anaconda", system="urn:redhat:anaconda:pre", xmlns="http://checklists.nist.gov/xccdf/1.1") elif remediation_type == 'ansible': return ElementTree.SubElement( fixcontent, "fix-group", id="ansible", system="urn:xccdf:fix:script:ansible", xmlns="http://checklists.nist.gov/xccdf/1.1") elif remediation_type == 'bash': return ElementTree.SubElement( fixcontent, "fix-group", id="bash", system="urn:xccdf:fix:script:sh", xmlns="http://checklists.nist.gov/xccdf/1.1") elif remediation_type == 'puppet': return ElementTree.SubElement( fixcontent, "fix-group", id="puppet", system="urn:xccdf:fix:script:puppet", xmlns="http://checklists.nist.gov/xccdf/1.1") sys.stderr.write("ERROR: Unknown remediation type '%s'!\n" % (remediation_type)) sys.exit(1)
def to_xml_element(self): root = ET.Element('Benchmark') root.set('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance') root.set('xmlns:xhtml', 'http://www.w3.org/1999/xhtml') root.set('xmlns:dc', 'http://purl.org/dc/elements/1.1/') root.set('id', 'product-name') root.set('xsi:schemaLocation', 'http://checklists.nist.gov/xccdf/1.1 xccdf-1.1.4.xsd') root.set('style', 'SCAP_1.1') root.set('resolved', 'false') root.set('xml:lang', 'en-US') status = ET.SubElement(root, 'status') status.set('date', datetime.date.today().strftime("%Y-%m-%d")) status.text = self.status add_sub_element(root, "title", self.title) add_sub_element(root, "description", self.description) notice = add_sub_element(root, "notice", self.notice_description) notice.set('id', self.notice_id) add_sub_element(root, "front-matter", self.front_matter) add_sub_element(root, "rear-matter", self.rear_matter) for idref in self.cpes: plat = ET.SubElement(root, "platform") plat.set("idref", idref) version = ET.SubElement(root, 'version') version.text = self.version ET.SubElement(root, "metadata") for profile in self.profiles: if profile is not None: root.append(profile.to_xml_element()) for v in self.values.values(): root.append(v.to_xml_element()) if self.bash_remediation_fns_group is not None: root.append(self.bash_remediation_fns_group) for g in self.groups.values(): root.append(g.to_xml_element()) for r in self.rules.values(): root.append(r.to_xml_element()) return root
def to_xml_element(self): value = ET.Element('Value') value.set('id', self.id_) value.set('type', self.type_) if self.operator != "equals": # equals is the default value.set('operator', self.operator) if self.interactive: # False is the default value.set('interactive', 'true') title = ET.SubElement(value, 'title') title.text = self.title add_sub_element(value, 'description', self.description) add_warning_elements(value, self.warnings) for selector, option in self.options.items(): # do not confuse Value with big V with value with small v # value is child element of Value value_small = ET.SubElement(value, 'value') # by XCCDF spec, default value is value without selector if selector != "default": value_small.set('selector', str(selector)) value_small.text = str(option) return value
def to_xml_element(self): group = ET.Element('Group') group.set('id', self.id_) if self.prodtype != "all": group.set("prodtype", self.prodtype) title = ET.SubElement(group, 'title') title.text = self.title add_sub_element(group, 'description', self.description) add_warning_elements(group, self.warnings) for v in self.values.values(): group.append(v.to_xml_element()) for g in self.groups.values(): group.append(g.to_xml_element()) for r in self.rules.values(): group.append(r.to_xml_element()) return group
def translate(self, tree, store_defname=False): for element in tree.getiterator(): idname = element.get("id") if idname: # store the old name if requested (for OVAL definitions) if store_defname and \ element.tag == "{%s}definition" % oval_ns: metadata = element.find("{%s}metadata" % oval_ns) if metadata is None: metadata = ElementTree.SubElement(element, "metadata") defnam = ElementTree.Element("reference", ref_id=idname, source=self.content_id) metadata.append(defnam) # set the element to the new identifier element.set("id", self.generate_id(element.tag, idname)) # continue if element.tag == "{%s}filter" % oval_ns: element.text = self.generate_id("{%s}state" % oval_ns, element.text) continue if element.tag == "{%s#independent}var_ref" % oval_ns: element.text = self.generate_id("{%s}variable" % oval_ns, element.text) continue for attr in element.keys(): if attr in ovalrefattr_to_tag.keys(): element.set( attr, self.generate_id( "{%s}%s" % (oval_ns, ovalrefattr_to_tag[attr]), element.get(attr))) if attr in ocilrefattr_to_tag.keys(): element.set( attr, self.generate_id( "{%s}%s" % (ocil_ns, ocilrefattr_to_tag[attr]), element.get(attr))) if element.tag == "{%s}test_action_ref" % ocil_ns: element.text = self.generate_id("{%s}action" % ocil_ns, element.text) return tree
def to_xml_element(self): rule = ET.Element('Rule') rule.set('id', self.id_) if self.prodtype != "all": rule.set("prodtype", self.prodtype) rule.set('severity', self.severity) add_sub_element(rule, 'title', self.title) add_sub_element(rule, 'description', self.description) add_sub_element(rule, 'rationale', self.rationale) main_ident = ET.Element('ident') for ident_type, ident_val in self.identifiers.items(): if '@' in ident_type: # the ident is applicable only on some product # format : 'policy@product', eg. 'stigid@product' # for them, we create a separate <ref> element policy, product = ident_type.split('@') ident = ET.SubElement(rule, 'ident') ident.set(policy, ident_val) ident.set('prodtype', product) else: main_ident.set(ident_type, ident_val) if main_ident.attrib: rule.append(main_ident) main_ref = ET.Element('ref') for ref_type, ref_val in self.references.items(): if '@' in ref_type: # the reference is applicable only on some product # format : 'policy@product', eg. 'stigid@product' # for them, we create a separate <ref> element policy, product = ref_type.split('@') ref = ET.SubElement(rule, 'ref') ref.set(policy, ref_val) ref.set('prodtype', product) else: main_ref.set(ref_type, ref_val) if main_ref.attrib: rule.append(main_ref) if self.external_oval: check = ET.SubElement(rule, 'check') check.set("system", "http://oval.mitre.org/XMLSchema/oval-definitions-5") external_content = ET.SubElement(check, "check-content-ref") external_content.set("href", self.external_oval) else: # TODO: This is pretty much a hack, oval ID will be the same as rule ID # and we don't want the developers to have to keep them in sync. # Therefore let's just add an OVAL ref of that ID. oval_ref = ET.SubElement(rule, "oval") oval_ref.set("id", self.id_) if self.ocil or self.ocil_clause: ocil = add_sub_element(rule, 'ocil', self.ocil if self.ocil else "") if self.ocil_clause: ocil.set("clause", self.ocil_clause) add_warning_elements(rule, self.warnings) return rule
def expand_xccdf_subs(fix, remediation_type, remediation_functions): """For those remediation scripts utilizing some of the internal SCAP Security Guide remediation functions expand the selected shell variables and remediation functions calls with <xccdf:sub> element This routine translates any instance of the 'populate' function call in the form of: populate variable_name into variable_name="<sub idref="variable_name"/>" Also transforms any instance of the 'ansible-populate' function call in the form of: (ansible-populate variable_name) into <sub idref="variable_name"/> Also transforms any instance of some other known remediation function (e.g. 'replace_or_append' etc.) from the form of: function_name "arg1" "arg2" ... "argN" into: <sub idref="function_function_name"/> function_name "arg1" "arg2" ... "argN" """ if remediation_type == "ansible": fix_text = fix.text if "(ansible-populate " in fix_text: raise RuntimeError( "(ansible-populate VAR) has been deprecated. Please use " "(xccdf-var VAR) instead. Keep in mind that the latter will " "make an ansible variable out of XCCDF Value as opposed to " "substituting directly." ) fix_text = re.sub( r"- \(xccdf-var\s+(\S+)\)", r"- name: XCCDF Value \1 # promote to variable\n" r" set_fact:\n" r" \1: (ansible-populate \1)\n" r" tags:\n" r" - always", fix_text ) pattern = r'\(ansible-populate\s*(\S+)\)' # we will get list what looks like # [text, varname, text, varname, ..., text] parts = re.split(pattern, fix_text) fix.text = parts[0] # add first "text" for index in range(1, len(parts), 2): varname = parts[index] text_between_vars = parts[index + 1] # we cannot combine elements and text easily # so text is in ".tail" of element xccdfvarsub = ElementTree.SubElement(fix, "sub", idref=varname) xccdfvarsub.tail = text_between_vars return elif remediation_type == "puppet": pattern = r'\(puppet-populate\s*(\S+)\)' # we will get list what looks like # [text, varname, text, varname, ..., text] parts = re.split(pattern, fix.text) fix.text = parts[0] # add first "text" for index in range(1, len(parts), 2): varname = parts[index] text_between_vars = parts[index + 1] # we cannot combine elements and text easily # so text is in ".tail" of element xccdfvarsub = ElementTree.SubElement(fix, "sub", idref=varname) xccdfvarsub.tail = text_between_vars return elif remediation_type == "anaconda": pattern = r'\(anaconda-populate\s*(\S+)\)' # we will get list what looks like # [text, varname, text, varname, ..., text] parts = re.split(pattern, fix.text) fix.text = parts[0] # add first "text" for index in range(1, len(parts), 2): varname = parts[index] text_between_vars = parts[index + 1] # we cannot combine elements and text easily # so text is in ".tail" of element xccdfvarsub = ElementTree.SubElement(fix, "sub", idref=varname) xccdfvarsub.tail = text_between_vars return elif remediation_type == "bash": # This remediation script doesn't utilize internal remediation functions # Skip it without any further processing if 'remediation_functions' not in fix.text: return # This remediation script utilizes some of internal remediation functions # Expand shell variables and remediation functions calls with <xccdf:sub> # elements pattern = '\n+(\s*(?:' + '|'.join(remediation_functions) + ')[^\n]*)\n' patcomp = re.compile(pattern, re.DOTALL) fixparts = re.split(patcomp, fix.text) if fixparts[0] is not None: # Split the portion of fix.text from fix start to first call of # remediation function into two parts: # * head to hold inclusion of the remediation functions # * tail to hold part of the fix.text after inclusion, # but before first call of remediation function try: rfpattern = '(.*remediation_functions)(.*)' rfpatcomp = re.compile(rfpattern, re.DOTALL) _, head, tail, _ = re.split(rfpatcomp, fixparts[0], maxsplit=2) except ValueError: sys.stderr.write("Processing fix.text for: %s rule\n" % fix.get('rule')) sys.stderr.write("Unable to extract part of the fix.text " "after inclusion of remediation functions." " Aborting..\n") sys.exit(1) # If the 'tail' is not empty, make it new fix.text. # Otherwise use '' fix.text = tail if tail is not None else '' # Drop the first element of 'fixparts' since it has been processed fixparts.pop(0) # Perform sanity check on new 'fixparts' list content (to continue # successfully 'fixparts' has to contain even count of elements) if len(fixparts) % 2 != 0: sys.stderr.write("Error performing XCCDF expansion on " "remediation script: %s\n" % fix.get("rule")) sys.stderr.write("Invalid count of elements. Exiting!\n") sys.exit(1) # Process remaining 'fixparts' elements in pairs # First pair element is remediation function to be XCCDF expanded # Second pair element (if not empty) is the portion of the original # fix text to be used in newly added sublement's tail for idx in range(0, len(fixparts), 2): # We previously removed enclosing newlines when creating # fixparts list. Add them back and reuse the above 'pattern' fixparts[idx] = "\n%s\n" % fixparts[idx] # Sanity check (verify the first field truly contains call of # some of the remediation functions) if re.match(pattern, fixparts[idx], re.DOTALL) is not None: # This chunk contains call of 'populate' function if "populate" in fixparts[idx]: varname, fixtextcontrib = get_populate_replacement( remediation_type, fixparts[idx]) # Define new XCCDF <sub> element for the variable xccdfvarsub = ElementTree.Element("sub", idref=varname) # If this is first sub element, # the textcontribution needs to go to fix text # otherwise, append to last subelement nfixchildren = len(list(fix)) if nfixchildren == 0: fix.text += fixtextcontrib else: previouselem = fix[nfixchildren-1] previouselem.tail += fixtextcontrib # If second pair element is not empty, append it as # tail for the subelement (prefixed with closing '"') if fixparts[idx + 1] is not None: xccdfvarsub.tail = '"' + '\n' + fixparts[idx + 1] # Otherwise append just enclosing '"' else: xccdfvarsub.tail = '"' + '\n' # Append the new subelement to the fix element fix.append(xccdfvarsub) # This chunk contains call of other remediation function else: # Extract remediation function name funcname = re.search('\n\s*(\S+)(| .*)\n', fixparts[idx], re.DOTALL).group(1) # Define new XCCDF <sub> element for the function xccdffuncsub = ElementTree.Element( "sub", idref='function_%s' % funcname) # Append original function call into tail of the # subelement xccdffuncsub.tail = fixparts[idx] # If the second element of the pair is not empty, # append it to the tail of the subelement too if fixparts[idx + 1] is not None: xccdffuncsub.tail += fixparts[idx + 1] # Append the new subelement to the fix element fix.append(xccdffuncsub) # Ensure the newly added <xccdf:sub> element for the # function will be always inserted at newline # If xccdffuncsub is the first <xccdf:sub> element # being added as child of <fix> and fix.text doesn't # end up with newline character, append the newline # to the fix.text if list(fix).index(xccdffuncsub) == 0: if re.search('.*\n$', fix.text) is None: fix.text += '\n' # If xccdffuncsub isn't the first child (first # <xccdf:sub> being added), and tail of previous # child doesn't end up with newline, append the newline # to the tail of previous child else: previouselem = fix[list(fix).index(xccdffuncsub) - 1] if re.search('.*\n$', previouselem.tail) is None: previouselem.tail += '\n' # Perform a sanity check if all known remediation function calls have been # properly XCCDF substituted. Exit with failure if some wasn't # First concat output form of modified fix text (including text appended # to all children of the fix) modfix = [fix.text] for child in fix.getchildren(): if child is not None and child.text is not None: modfix.append(child.text) modfixtext = "".join(modfix) for f in remediation_functions: # Then efine expected XCCDF sub element form for this function funcxccdfsub = "<sub idref=\"function_%s\"" % f # Finally perform the sanity check -- if function was properly XCCDF # substituted both the original function call and XCCDF <sub> element # for that function need to be present in the modified text of the fix # Otherwise something went wrong, thus exit with failure if f in modfixtext and funcxccdfsub not in modfixtext: sys.stderr.write("Error performing XCCDF <sub> substitution " "for function %s in %s fix. Exiting...\n" % (f, fix.get("rule"))) sys.exit(1) else: sys.stderr.write("Unknown remediation type '%s'\n" % (remediation_type)) sys.exit(1)