def _get_inventory_properties_checked(self, rule_use): """Get inventory properties, with checking.""" props = [] if rule_use.host_name is None: props.append(Property(name='target', value=rule_use.target, ns=self._ns, class_='scc_inventory_item_id')) props.append(Property(name='target_type', value=rule_use.target_type, ns=self._ns)) else: props.append(Property(name='target', value=rule_use.target, ns=self._ns)) props.append(Property(name='target_type', value=rule_use.target_type, ns=self._ns)) props.append( Property(name='host_name', value=rule_use.host_name, ns=self._ns, class_='scc_inventory_item_id') ) return props
def _build_implemented_requirements( self, profile_set: Dict[str, str], responsible_roles: List[ResponsibleRole] ) -> List[ImplementedRequirement]: """Build implemented requirements.""" implemented_requirements = [] profile_file = profile_set['profile-file'] rules = self._get_cis_rules(profile_file) controls = self._get_controls(rules) rule_prefix = 'xccdf_org.ssgproject.content_rule_' cac_openshift = f'{self._folder_cac}/applications/openshift' for rule in rules: if self._is_excluded(rule, rules[rule][1], controls): continue remarks = self._get_title(rule, cac_openshift) prop = Property(class_='scc_goal_name_id', ns=profile_set['profile-ns'], name='XCCDF_rule', value=f'{rule_prefix}{rule}', remarks=f'{remarks}') props = [prop] implemented_requirement = ImplementedRequirement( uuid=f'{str(uuid.uuid4())}', control_id=f'CIS-{rules[rule][1]}', description=f'{rules[rule][2]}', props=props, responsible_roles=responsible_roles, ) set_parameter = self._get_set_parameter(rule) if set_parameter is not None: implemented_requirement.set_parameters = [set_parameter] implemented_requirements.append(implemented_requirement) return implemented_requirements
def _add_implemented_requirements(self, row: int, controls: Dict[str, List[str]], component_name: str, parameter_name: str, responsible_roles: List[ResponsibleRole], goal_name_id: str) -> None: """Add implemented requirements.""" goal_remarks = self.xlsx_helper.get_goal_remarks(row) parameter_value_default = self.xlsx_helper.get_parameter_value_default( row) for control in controls.keys(): control_uuid = str(uuid.uuid4()) prop1 = Property( name='goal_name_id', class_=self._get_class_for_property_name('goal_name_id'), value=goal_name_id, ns=self._get_namespace(), remarks=Remarks(__root__=str(goal_remarks))) prop2 = Property( name='goal_version', class_=self._get_class_for_property_name('goal_version'), value=self._get_goal_version(), ns=self._get_namespace(), remarks=Remarks(__root__=str(goal_name_id))) props = [prop1, prop2] control_id, status = self.catalog_helper.find_control_id(control) if control_id is None: logger.info( f'row {row} control {control} not found in catalog') control_id = control # implemented_requirement implemented_requirement = ImplementedRequirement( uuid=control_uuid, description=control, props=props, control_id=control_id, responsible_roles=responsible_roles, ) # add statements self._add_statements(row, control, controls, component_name, implemented_requirement) # add set_parameter self._add_set_parameter(row, parameter_name, parameter_value_default, implemented_requirement) # implemented_requirements self.implemented_requirements.append(implemented_requirement)
def _derive_inventory_unchecked(self) -> Dict[str, InventoryItem]: """Derive inventory from RuleUse list, properties unchecked.""" self._inventory_map = {} for rule_use in self._rule_use_list: if rule_use.tanium_client_ip_address in self._inventory_map: continue inventory = InventoryItem(uuid=_uuid_inventory(), description='inventory') inventory.props = [ Property.construct(name='Computer_Name', value=rule_use.computer_name, ns=self._ns), Property.construct( name='Tanium_Client_IP_Address', value=rule_use.tanium_client_ip_address, ns=self._ns, class_='scc_inventory_item_id' ), Property.construct(name='IP_Address', value=rule_use.ip_address, ns=self._ns), Property.construct(name='Count', value=rule_use.count, ns=self._ns) ] component_uuid = self._get_component_ref(rule_use) if component_uuid is not None: inventory.implemented_components = [ImplementedComponent(component_uuid=component_uuid)] self._inventory_map[rule_use.tanium_client_ip_address] = inventory
def test_catalog_generate_withdrawn( tmp_path: pathlib.Path, sample_catalog_rich_controls: cat.Catalog) -> None: """Test catalog generate when some controls are marked withdrawn.""" control_a = sample_catalog_rich_controls.groups[0].controls[0] control_b = sample_catalog_rich_controls.groups[0].controls[1] group_id = sample_catalog_rich_controls.groups[0].id if not control_b.props: control_b.props = [] control_b.props.append(Property(name='status', value='Withdrawn')) catalog_interface = CatalogInterface(sample_catalog_rich_controls) catalog_interface.write_catalog_as_markdown(tmp_path, {}, None, False) # confirm that the first control was written out but not the second path_a = tmp_path / group_id / (control_a.id + '.md') assert path_a.exists() path_b = tmp_path / group_id / (control_b.id + '.md') assert not path_b.exists()
def _get_observtion_properties_unchecked(self, rule_use): """Get observation properties, without checking.""" props = [ Property.construct(name='Check_ID', value=rule_use.check_id, ns=self._ns), Property.construct( name='Check_ID_Benchmark', value=rule_use.check_id_benchmark, ns=self._ns, class_='scc_predefined_profile' ), Property.construct( name='Check_ID_Version', value=rule_use.check_id_version, ns=self._ns, class_='scc_predefined_profile_version' ), Property.construct(name='Check_ID_Level', value=rule_use.check_id_level, ns=self._ns), Property.construct(name='Rule_ID', value=rule_use.rule_id, ns=self._ns, class_='scc_goal_description'), Property.construct(name='Rule_ID', value=rule_use.rule_id, ns=self._ns, class_='scc_check_name_id'), Property.construct(name='State', value=rule_use.state, ns=self._ns, class_='scc_result'), Property.construct(name='Timestamp', value=rule_use.timestamp, ns=self._ns, class_='scc_timestamp'), ] return props
def test_catalog_generate_assemble(set_parameters: bool, make_change: bool, use_orig_cat: bool, add_header: bool, use_cli: bool, dir_exists: bool, tmp_trestle_dir: pathlib.Path, monkeypatch: MonkeyPatch) -> None: """Test the catalog markdown generator.""" nist_catalog_path = test_utils.JSON_TEST_DATA_PATH / test_utils.SIMPLIFIED_NIST_CATALOG_NAME cat_name = 'my_cat' md_name = 'my_md' assembled_cat_name = 'my_assembled_cat' catalog_dir = tmp_trestle_dir / f'catalogs/{cat_name}' catalog_dir.mkdir(parents=True, exist_ok=True) catalog_path = catalog_dir / 'catalog.json' shutil.copy(nist_catalog_path, catalog_path) markdown_path = tmp_trestle_dir / md_name markdown_path.mkdir(parents=True, exist_ok=True) ac1_path = markdown_path / 'ac/ac-1.md' new_prose = 'My added item' assembled_cat_dir = tmp_trestle_dir / f'catalogs/{assembled_cat_name}' yaml_header_path = test_utils.YAML_TEST_DATA_PATH / 'good_simple.yaml' # convert catalog to markdown then assemble it after adding an item to a control if use_cli: test_args = f'trestle author catalog-generate -n {cat_name} -o {md_name}'.split( ) if add_header: test_args.extend(['-y', str(yaml_header_path)]) monkeypatch.setattr(sys, 'argv', test_args) assert Trestle().run() == 0 assert ac1_path.exists() _change_params(ac1_path, new_prose, make_change) test_args = f'trestle author catalog-assemble -m {md_name} -o {assembled_cat_name}'.split( ) if set_parameters: test_args.append('-sp') if use_orig_cat: test_args.extend(f'-n {cat_name}'.split()) if dir_exists: assembled_cat_dir.mkdir() monkeypatch.setattr(sys, 'argv', test_args) assert Trestle().run() == 0 else: catalog_generate = CatalogGenerate() yaml_header = {} if add_header: yaml = YAML(typ='safe') yaml_header = yaml.load(yaml_header_path.open('r')) catalog_generate.generate_markdown(tmp_trestle_dir, catalog_path, markdown_path, yaml_header, False) assert (markdown_path / 'ac/ac-1.md').exists() _change_params(ac1_path, new_prose, make_change) if dir_exists: assembled_cat_dir.mkdir() orig_cat_name = cat_name if use_orig_cat else None CatalogAssemble.assemble_catalog(tmp_trestle_dir, md_name, assembled_cat_name, orig_cat_name, set_parameters, False, '') orig_cat: cat.Catalog = cat.Catalog.oscal_read(catalog_path) assembled_cat: cat.Catalog = cat.Catalog.oscal_read(assembled_cat_dir / 'catalog.json') assert (orig_cat.metadata.title == assembled_cat.metadata.title ) == use_orig_cat interface_orig = CatalogInterface(orig_cat) # need to delete withdrawn controls because they won't be in the assembled catalog interface_orig.delete_withdrawn_controls() ac1 = interface_orig.get_control('ac-1') if make_change: # add the item manually to the original catalog so we can confirm the item was loaded correctly prop = Property(name='label', value='d.') new_part = Part(id='ac-1_smt.d', name='item', props=[prop], prose=new_prose) ac1.parts[0].parts.append(new_part) interface_orig.replace_control(ac1) orig_cat = interface_orig.get_catalog() if set_parameters: ac1.params[0].values = [ParameterValue(__root__='new value')] ac1.params[2].values = [ParameterValue(__root__='added param 3 value')] interface_orig.replace_control(ac1) orig_cat = interface_orig.get_catalog() elif not use_orig_cat: ac1.params = None interface_orig.replace_control(ac1) orig_cat = interface_orig.get_catalog() assert test_utils.catalog_interface_equivalent(interface_orig, assembled_cat, False)
def create_groups(p): """ Parse the spreadsheet to get groups by family. """ with open(Path.cwd().joinpath('complianceio', 'header_map.json'), 'r') as f: c_map = json.load(f) wb = openpyxl.load_workbook(filename=p, data_only=True) if "Full" not in wb.sheetnames: print(f"Sheetname 'Full' not found in: {wb.sheetnames}") quit() ws = wb["Full"] # header = [cell for cell in ws['A2:XFD2'][0] # if cell.value is not None and cell.value.strip() != ''] groups = [] family_id = None group = None controls = [] for row in ws.iter_rows(min_row=3, max_col=19, max_row=1890): if row[0].value is not None: family = row[c_map.get('family')].value control_id = row[c_map.get('control_id')].value group = control_id[:2].lower() name = row[c_map.get('name')].value control_text = row[c_map.get('control')].value responsibility = row[c_map.get('responsiblity')].value related = row[c_map.get('responsiblity')].value reference = row[c_map.get('reference')].value baseline = row[c_map.get('baseline')].value priority = row[c_map.get('priority')].value # ignore duplicated parts (elements) if baseline is None: continue # check for ac--09(02) control_id = control_id.replace('--', '-') # check for AU14-(01) control_id = re.sub(r'([A-Z][A-Z])([0-9][0-9])\-', r"\1-\2", control_id) # # replace S1- with SI- # control_id = re.sub(r'S1-([0-9][0-9])', r"SI-\1", control_id) if family_id is not None and family != family_id: g = add_group(group_id, family_id, controls) groups.append(g) controls = [] if control_text is not None: cid = control_to_statement_id(control_id) parts = [] part = parse_parts(control_text, cid) parts.append(part) imp = row[c_map.get('implementation')].value if imp and imp.strip(): pid = cid.replace('_smt', '_imp') part = add_additional(pid, 'implementation', imp.strip()) parts.append(part) hva = row[c_map.get('hva_standards')].value if hva and hva.strip(): pid = cid.replace('_smt', '_hva') part = add_additional(pid, 'hva', hva.strip()) parts.append(part) prv = row[c_map.get('privacy_standards')].value if prv and prv.strip(): pid = cid.replace('_smt', '_prv') part = add_additional(pid, 'privacy', prv.strip()) parts.append(part) gdn = row[c_map.get('discussion')].value if gdn and gdn.strip(): pid = cid.replace('_smt', '_gdn') part = add_additional(pid, 'guidance', gdn.strip()) parts.append(part) links = [] related = row[c_map.get('related')].value if related and related.strip(): rlt = related.strip().replace(' ', ',').split(',') for r in rlt: if r[:4] != 'None': link = r.strip() if link: # clean up Redacted links if "Redacted" in link: continue links.append( Link(href=f'#{link}', rel='related')) l = links if len(links) > 0 else None props = [ Property(name='label', value=control_id), Property(name='sort-id', value=control_id.lower()), Property(name='responsibility', value=str( responsibility.replace( '\n', ',').strip().rstrip(','))), Property(name='baseline', value=str( baseline.replace('\n', ',').rstrip(','))) ] if priority and priority.strip(): props.append( Property(name='priority', value=priority.strip())) controls.append( Control( id=cid.split('_')[0], class_='ARS-5.0-Mandatory', title=name, props=props, links=l, parts=parts, )) family_id = family group_id = group g = add_group(group_id, family, controls) groups.append(g) return groups
def create_parts(parts): part = [] first = None secondary = None tertiary = None for k, pv in parts.items(): if 'children' in pv: first = [] for f, fv in pv.get('children').items(): if 'children' in fv: secondary = [] for s, sv in fv.get('children').items(): if 'children' in sv: tertiary = [] for t, tv in sv.get('children').items(): logging.info(f'{k}.{f}.{s}.{t}') tertiary.append( Part(id=f'{k}.{f}.{s}.{t}', name='item', props=[ Property( name='label', value=t, ) ], prose=tv.get('prose'))) tp = tertiary if len(tertiary) > 0 else None logging.info(f'{k}.{f}.{s}') secondary.append( Part( id=f'{k}.{f}.{s}', name='item', props=[Property( name='label', value=s, )], parts=tp, prose=sv.get('prose'), )) sp = secondary if len(secondary) > 0 else None logging.info(f'{k}.{f}') first.append( Part( id=f'{k}.{f}', name='item', props=[Property( name='label', value=f, )], parts=sp, prose=fv.get('prose'), )) fp = first if len(first) > 0 else None logging.info(f'{k}') part.append( Part( id=f'{k}', name='item', props=[Property( name='label', value=k, )], parts=fp, prose=pv.get('prose'), )) return part
def _execute(self) -> TaskOutcome: if not self._config: logger.error('config missing') return TaskOutcome('failure') try: component_name = self._config['component-name'] org_name = self._config['org-name'] org_remarks = self._config['org-remarks'] self._folder_cac = self._config['folder-cac'] profile_check_version = self._config['profile-check-version'] profile_type = self._config['profile-type'] profile_mnemonic = self._config['profile-mnemonic'] profile_name = self._config['profile-name'] profile_ns = self._config['profile-ns'] profile_version = self._config['profile-version'] profile_sets = {} profile_list = self._config['profile-list'].split() for profile in profile_list: profile_sets[profile] = {} profile_sets[profile]['profile-file'] = self._config[ f'profile-file.{profile}'] profile_sets[profile]['profile-url'] = self._config[ f'profile-url.{profile}'] profile_sets[profile]['profile-title'] = self._config[ f'profile-title.{profile}'] profile_sets[profile]['profile-ns'] = profile_ns profile_sets[profile]['component-name'] = component_name odir = self._config['output-dir'] except KeyError as e: logger.info(f'key {e.args[0]} missing') return TaskOutcome('failure') # selected rules self._selected_rules = self._get_filter_rules('selected-rules', 'selected') # enabled rules self._enabled_rules = self._get_filter_rules('enabled-rules', 'enabled') # verbosity quiet = self._config.get('quiet', False) verbose = not quiet # output overwrite = self._config.getboolean('output-overwrite', True) opth = pathlib.Path(odir) # insure output dir exists opth.mkdir(exist_ok=True, parents=True) # calculate output file name & check writability oname = 'component-definition.json' ofile = opth / oname if not overwrite and pathlib.Path(ofile).exists(): logger.error(f'output: {ofile} already exists') return TaskOutcome('failure') # fetch rule to parameters map self._rule_to_parm_map = self._get_parameters_map( 'rule-to-parameters-map') # roles, responsible_roles, parties, responsible parties party_uuid_01 = str(uuid.uuid4()) party_uuid_02 = str(uuid.uuid4()) party_uuid_03 = str(uuid.uuid4()) roles = self._build_roles() responsible_roles = self._build_responsible_roles( party_uuid_01, party_uuid_02, party_uuid_03) parties = self._build_parties(org_name, org_remarks, party_uuid_01, party_uuid_02, party_uuid_03) responsible_parties = self._build_responsible_parties( party_uuid_01, party_uuid_02, party_uuid_03) # metadata metadata = Metadata( title=f'Component definition for {profile_type} profiles', last_modified=self._timestamp, oscal_version=OSCAL_VERSION, version=trestle.__version__, roles=roles, parties=parties, responsible_parties=responsible_parties) # defined component component_title = component_name component_description = component_name defined_component = DefinedComponent( uuid=str(uuid.uuid4()), description=component_description, title=component_title, type='Service', ) # add control implementation per profile prop1 = Property( name='profile_name', value=profile_name, class_='scc_profile_name', ns=profile_ns, ) prop2 = Property( name='profile_mnemonic', value=profile_mnemonic, class_='scc_profile_mnemonic', ns=profile_ns, ) prop3 = Property( name='profile_version', value=profile_version, class_='scc_profile_version', ns=profile_ns, ) prop4 = Property( name='profile_check_version', value=profile_check_version, ) props = [prop1, prop2, prop3, prop4] for profile in profile_list: profile_set = profile_sets[profile] control_implementation = self._build_control_implementation( profile_set, responsible_roles, props) if control_implementation is not None: if defined_component.control_implementations is None: defined_component.control_implementations = [ control_implementation ] else: defined_component.control_implementations.append( control_implementation) # defined components defined_components = [defined_component] # component definition component_definition = ComponentDefinition( uuid=str(uuid.uuid4()), metadata=metadata, components=defined_components, ) # write OSCAL ComponentDefinition to file if verbose: logger.info(f'output: {ofile}') component_definition.oscal_write(pathlib.Path(ofile)) return TaskOutcome('success')
def _get_observation_properties_unchecked(self, rule_use): """Get observation properties, without checking.""" props = [] props.append(Property.construct(name='scanner_name', value=rule_use.scanner_name, ns=self._ns)) props.append(Property.construct(name='scanner_version', value=rule_use.scanner_version, ns=self._ns)) props.append(Property.construct(name='idref', value=rule_use.idref, ns=self._ns, class_='scc_check_name_id')) props.append( Property.construct(name='version', value=rule_use.version, ns=self._ns, class_='scc_check_version') ) props.append(Property.construct(name='result', value=rule_use.result, ns=self._ns, class_='scc_result')) props.append(Property.construct(name='time', value=rule_use.time, ns=self._ns, class_='scc_timestamp')) props.append( Property.construct(name='severity', value=rule_use.severity, ns=self._ns, class_='scc_check_severity') ) props.append(Property.construct(name='weight', value=rule_use.weight, ns=self._ns)) props.append(Property.construct(name='benchmark_id', value=rule_use.benchmark_id, ns=self._ns)) props.append(Property.construct(name='benchmark_href', value=rule_use.benchmark_href, ns=self._ns)) props.append(Property.construct(name='id', value=rule_use.id_, ns=self._ns, class_='scc_predefined_profile')) return props