예제 #1
0
 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
예제 #2
0
 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
예제 #3
0
 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)
예제 #4
0
 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
예제 #5
0
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()
예제 #6
0
 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
예제 #7
0
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)
예제 #8
0
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
예제 #9
0
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
예제 #10
0
 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')
예제 #11
0
 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