Exemple #1
0
def test_add(tmp_path: pathlib.Path, keep_cwd: pathlib.Path) -> None:
    """Test Add.add() method used by trestle CreateCmd."""
    file_path = pathlib.Path(
        test_utils.JSON_TEST_DATA_PATH) / 'minimal_catalog_missing_roles.json'
    minimal_catalog_missing_roles = Catalog.oscal_read(file_path)

    # expected catalog after first add of Role
    file_path = pathlib.Path.joinpath(test_utils.JSON_TEST_DATA_PATH,
                                      'minimal_catalog_roles.json')
    expected_catalog_roles1 = Element(Catalog.oscal_read(file_path))

    # expected catalog after second add of Role
    file_path = pathlib.Path.joinpath(test_utils.JSON_TEST_DATA_PATH,
                                      'minimal_catalog_roles_double.json')
    expected_catalog_roles2 = Element(Catalog.oscal_read(file_path))

    # expected catalog after add of Responsible-Party
    file_path = pathlib.Path.joinpath(test_utils.JSON_TEST_DATA_PATH,
                                      'minimal_catalog_roles_double_rp.json')
    expected_catalog_roles2_rp = Element(Catalog.oscal_read(file_path))

    content_type = FileContentType.JSON

    _, _ = test_utils.prepare_trestle_project_dir(
        tmp_path, content_type, minimal_catalog_missing_roles,
        test_utils.CATALOGS_DIR)

    # Execute first _add
    element_path = ElementPath('catalog.metadata.roles')
    catalog_element = Element(minimal_catalog_missing_roles)
    expected_update_action_1 = UpdateAction(
        expected_catalog_roles1.get_at(element_path), catalog_element,
        element_path)
    actual_update_action, actual_catalog_roles = Add.add(
        element_path, catalog_element, False)

    assert actual_catalog_roles == expected_catalog_roles1
    assert actual_update_action == expected_update_action_1

    # Execute second _add - this time roles already exists, so this adds a roles object to roles array
    catalog_element = actual_catalog_roles
    expected_update_action_2 = UpdateAction(
        expected_catalog_roles2.get_at(element_path), catalog_element,
        element_path)
    actual_update_action2, actual_catalog_roles2 = Add.add(
        element_path, catalog_element, False)
    assert actual_catalog_roles2 == expected_catalog_roles2
    assert actual_update_action2 == expected_update_action_2

    # Execute _add for responsible-parties to the same catalog
    element_path = ElementPath('catalog.metadata.responsible-parties')
    catalog_element = actual_catalog_roles2
    expected_update_action_3 = UpdateAction(
        expected_catalog_roles2_rp.get_at(element_path), catalog_element,
        element_path)
    actual_update_action3, actual_catalog_roles2_rp = Add.add(
        element_path, catalog_element, False)
    assert actual_catalog_roles2_rp == expected_catalog_roles2_rp
    assert actual_update_action3 == expected_update_action_3
def test_run(tmp_path: pathlib.Path, sample_catalog_missing_roles,
             monkeypatch: MonkeyPatch):
    """Test _run for RemoveCmd."""
    # 1. Test trestle remove for one element.
    # expected catalog after remove of Responsible-Party
    file_path = pathlib.Path.joinpath(
        test_utils.JSON_TEST_DATA_PATH,
        'minimal_catalog_no_responsible-parties.json')
    expected_catalog_no_rp = Catalog.oscal_read(file_path)

    content_type = FileContentType.JSON

    # Create a temporary file with responsible-parties to be removed.
    catalog_def_dir, catalog_def_file = test_utils.prepare_trestle_project_dir(
        tmp_path, content_type, sample_catalog_missing_roles,
        test_utils.CATALOGS_DIR)

    testargs = [
        'trestle', 'remove', '-tr',
        str(tmp_path), '-f',
        str(catalog_def_file), '-e', 'catalog.metadata.responsible-parties'
    ]

    monkeypatch.setattr(sys, 'argv', testargs)
    assert Trestle().run() == 0

    actual_catalog = Catalog.oscal_read(catalog_def_file)
    assert expected_catalog_no_rp == actual_catalog

    # 2. Test trestle remove for multiple comma-separated elements.
    # minimal catalog with Roles and Resposibile-Parties.
    file_path = pathlib.Path.joinpath(test_utils.JSON_TEST_DATA_PATH,
                                      'minimal_catalog_roles.json')
    catalog_with_roles_responsible_parties = Catalog.oscal_read(file_path)

    # Create a temporary file with Roles and Responsible-Parties to be removed.
    catalog_def_dir, catalog_def_file_2 = test_utils.prepare_trestle_project_dir(
        tmp_path, content_type, catalog_with_roles_responsible_parties,
        test_utils.CATALOGS_DIR)

    testargs = [
        'trestle', 'remove', '-tr',
        str(tmp_path), '-f',
        str(catalog_def_file_2), '-e',
        'catalog.metadata.responsible-parties,catalog.metadata.roles'
    ]

    monkeypatch.setattr(sys, 'argv', testargs)
    assert Trestle().run() == 0

    actual_catalog = Catalog.oscal_read(catalog_def_file_2)
    assert expected_catalog_no_rp == actual_catalog
def test_assemble_catalog_all(
    testdata_dir: pathlib.Path, tmp_trestle_dir: pathlib.Path, monkeypatch: MonkeyPatch
) -> None:
    """Test assembling all catalogs in trestle dir."""
    shutil.rmtree(pathlib.Path('dist'))
    catalogs_dir = tmp_trestle_dir / 'catalogs'
    my_names = ['mycatalog1', 'mycatalog2', 'mycatalog3']
    for my_name in my_names:
        test_data_source = testdata_dir / 'split_merge/step4_split_groups_array/catalogs/mycatalog'
        shutil.copytree(test_data_source, catalogs_dir / my_name)

    testargs = ['trestle', 'assemble', 'catalog', '-t', '-x', 'json']
    monkeypatch.setattr(sys, 'argv', testargs)
    rc = Trestle().run()
    assert rc == 0

    # Read assembled model
    for my_name in my_names:
        _, _, expected_model = ModelUtils.load_distributed(catalogs_dir / f'{my_name}/catalog.json', tmp_trestle_dir)
        actual_model = Catalog.oscal_read(pathlib.Path(f'dist/catalogs/{my_name}.json'))
        assert actual_model == expected_model

    testargs = ['trestle', 'assemble', 'profile', '-t', '-x', 'json']
    # Tests should pass on empty set of directories.
    monkeypatch.setattr(sys, 'argv', testargs)
    rc = Trestle().run()
    assert rc == 0
Exemple #4
0
def main(title, source, ars_version, oscal_version, minimize):
    """
    Create an OSCAL Catalog from Excel (.xlsx) spreadsheet.
    Source: https://security.cms.gov/library/cms-acceptable-risk-safeguards-ars
    """
    p = Path(source)
    groups = create_groups(p, minimize)
    uuid = uuid4().urn


    # today = datetime.now(timezone(timedelta(hours=-6))).isoformat(sep='T')
    today = datetime.now().astimezone(timezone.utc).isoformat(sep='T')

    # FIXME: missing 'T' separator in last-modified
    md = Metadata(
        title=title,
        last_modified=today,
        version=ars_version,
        oscal_version=oscal_version
    )

    catalog = Catalog(
            uuid=uuid[9:],
            metadata=md,
            groups=groups,
        )
    root = Model(catalog=catalog).dict(by_alias=True, exclude_unset=True, exclude_none=True)
    print(json.dumps(root, indent=2, default=str))
def _validate(tmp_path: pathlib.Path):
    # read catalog
    file_path = tmp_path / 'catalog.json'
    catalog = Catalog.oscal_read(file_path)
    # spot check
    assert len(catalog.groups) == 2
    # group 0
    group = catalog.groups[0]
    assert group.title == '1 Control Plane Components'
    assert len(group.groups) == 2
    assert len(group.groups[0].controls) == 3
    assert group.groups[0].title == '1.2 API Server'
    assert group.groups[0].controls[
        0].title == '1.2.1 Ensure that the --anonymous-auth argument is set to false'
    assert group.groups[0].controls[
        1].title == '1.2.2 Ensure that the --basic-auth-file argument is not set'
    assert group.groups[0].controls[
        2].title == '1.2.3 Ensure that the --token-auth-file parameter is not set'
    assert group.groups[1].title == '1.3 Controller Manager'
    assert len(group.groups[1].controls) == 1
    assert group.groups[1].controls[
        0].title == '1.3.2 Ensure that controller manager healthz endpoints are protected by RBAC. (Automated)'
    # group 1
    group = catalog.groups[1]
    assert group.title == '2 etcd'
    assert len(group.controls) == 2
    assert group.controls[
        0].title == '2.1 Ensure that the --cert-file and --key-file arguments are set as appropriate'
    assert group.controls[
        1].title == '2.2 Ensure that the --client-cert-auth argument is set to true'
Exemple #6
0
def main(title, source):
    """
    Create an OSCAL Catalog.
    """
    p = Path(source)
    groups = create_groups(p)
    uuid = uuid4().urn

    # today = datetime.now(timezone(timedelta(hours=-6))).isoformat(sep='T')
    today = datetime.now().astimezone(timezone.utc).isoformat(sep='T')

    # FIXME: missing 'T' separator in last-modified
    md = Metadata(title=title,
                  last_modified=today,
                  version='1.1',
                  oscal_version='1.0.2')

    catalog = Catalog(
        uuid=uuid[9:],
        metadata=md,
        groups=groups,
    )
    root = Model(catalog=catalog).dict(by_alias=True,
                                       exclude_unset=True,
                                       exclude_none=True)
    print(json.dumps(root, indent=2, default=str))
Exemple #7
0
def test_load_distributed(testdata_dir, tmp_trestle_dir):
    """Test massive distributed load, that includes recursive load and list."""
    # prepare trestle project dir with the file
    test_utils.ensure_trestle_config_dir(tmp_trestle_dir)

    test_data_source = testdata_dir / 'split_merge/step4_split_groups_array/catalogs'

    catalogs_dir = tmp_trestle_dir / 'catalogs'
    mycatalog_dir = catalogs_dir / 'mycatalog'
    catalog_file = mycatalog_dir / 'catalog.json'

    # Copy files from test/data/split_merge/step4
    shutil.rmtree(catalogs_dir)
    shutil.copytree(test_data_source, catalogs_dir)

    actual_model_type, actual_model_alias, actual_model_instance = ModelUtils.load_distributed(
        catalog_file, tmp_trestle_dir)

    expected_model_instance = Catalog.oscal_read(
        testdata_dir / 'split_merge/load_distributed/catalog.json')

    assert actual_model_type == Catalog
    assert actual_model_alias == 'catalog'
    assert test_utils.models_are_equivalent(expected_model_instance,
                                            actual_model_instance)

    # confirm it fails attempting to load collection type that is not a list
    with pytest.raises(TrestleError):
        actual_model_type, actual_model_alias, actual_model_instance = ModelUtils.load_distributed(
            catalog_file, tmp_trestle_dir, Dict)
def test_remove_failure(tmp_path, sample_catalog_minimal):
    """Test failure of RemoveCmd.remove() method for trestle remove."""
    # Note: minimal catalog does have responsible-parties but doesn't have Roles.
    file_path = pathlib.Path.joinpath(test_utils.JSON_TEST_DATA_PATH,
                                      'minimal_catalog.json')
    catalog_with_responsible_parties = Element(Catalog.oscal_read(file_path))

    # Supply nonexistent element Roles for removal:
    element_path = ElementPath('catalog.metadata.roles')
    try:
        actual_remove_action, actual_catalog_removed_responsible_parties = RemoveCmd.remove(
            element_path, Catalog, catalog_with_responsible_parties)
    except Exception:
        assert True
    else:
        AssertionError()

    # Supply a wildcard element for removal:
    element_path = ElementPath('catalog.*')
    try:
        actual_remove_action, actual_catalog_removed_responsible_parties = RemoveCmd.remove(
            element_path, Catalog, catalog_with_responsible_parties)
    except Exception:
        assert True
    else:
        AssertionError()
def test_load_distributed(testdata_dir, tmp_trestle_dir):
    """Test massive distributed load, that includes recusive load, list and dict."""
    # prepare trestle project dir with the file
    test_utils.ensure_trestle_config_dir(tmp_trestle_dir)

    test_data_source = testdata_dir / 'split_merge/step4_split_groups_array/catalogs'

    catalogs_dir = Path('catalogs/')
    mycatalog_dir = catalogs_dir / 'mycatalog'
    catalog_file = mycatalog_dir / 'catalog.json'

    # Copy files from test/data/split_merge/step4
    shutil.rmtree(catalogs_dir)
    shutil.copytree(test_data_source, catalogs_dir)

    actual_model_type, actual_model_alias, actual_model_instance = load_distributed(
        catalog_file)

    expected_model_type, _ = fs.get_contextual_model_type(
        catalog_file.absolute())

    expected_model_instance = Catalog.oscal_read(
        testdata_dir / 'split_merge/load_distributed/catalog.json')

    assert actual_model_type == expected_model_type
    assert actual_model_alias == 'catalog'
    assert len(
        list(dictdiffer.diff(expected_model_instance,
                             actual_model_instance))) == 0
def test_import_from_url(tmp_trestle_dir: pathlib.Path) -> None:
    """Test import via url."""
    uri = 'https://raw.githubusercontent.com/IBM/compliance-trestle/develop/tests/data/json/minimal_catalog.json'
    args = argparse.Namespace(trestle_root=tmp_trestle_dir,
                              file=uri,
                              output='my_catalog',
                              verbose=1,
                              regenerate=False)
    i = importcmd.ImportCmd()
    assert i._run(args) == 0
    imported_catalog = Catalog.oscal_read(tmp_trestle_dir /
                                          'catalogs/my_catalog/catalog.json')

    test_catalog = test_utils.JSON_TEST_DATA_PATH / 'minimal_catalog.json'
    catalog_data = Catalog.oscal_read(test_catalog)

    assert test_utils.models_are_equivalent(catalog_data, imported_catalog)
def test_oscal_version_validator(tmp_trestle_dir: pathlib.Path,
                                 sample_catalog_minimal: Catalog, code: int,
                                 monkeypatch: MonkeyPatch) -> None:
    """
    Test oscal version validator.

    There is no actual validator for oscal version but it happens in base_model when a file is loaded.
    """
    mycat_dir = tmp_trestle_dir / 'catalogs/mycat'
    mycat_dir.mkdir()
    sample_catalog_minimal.oscal_write(mycat_dir / 'catalog.json')
    testcmd = 'trestle validate -t catalog'
    monkeypatch.setattr(sys, 'argv', testcmd.split())
    with pytest.raises(SystemExit) as pytest_wrapped_e:
        cli.run()
    assert pytest_wrapped_e.type == SystemExit
    assert pytest_wrapped_e.value.code == code
Exemple #12
0
    def _flatten_catalog(self, catalog: cat.Catalog,
                         as_is: bool) -> cat.Catalog:
        """Flatten the groups of the catalog if as_is is False."""
        if as_is or catalog.groups is None:
            return catalog

        # as_is is False so flatten the controls into a single list
        catalog.controls = as_list(catalog.controls)
        catalog.params = as_list(catalog.params)
        for group in catalog.groups:
            new_controls, new_params = self._group_contents(group)
            catalog.controls.extend(new_controls)
            catalog.params.extend(new_params)
        catalog.controls = none_if_empty(catalog.controls)
        catalog.params = none_if_empty(catalog.params)
        catalog.groups = None
        return catalog
Exemple #13
0
def test_run_iof(tmp_trestle_dir: pathlib.Path,
                 monkeypatch: MonkeyPatch) -> None:
    """Test _run for create element method in CreateCmd with iof."""
    original_catalog_path = pathlib.Path.joinpath(
        test_utils.JSON_TEST_DATA_PATH, 'minimal_catalog_missing_roles.json')
    dest_file_folder = tmp_trestle_dir / 'catalogs' / 'test_catalog'
    dest_file_location = dest_file_folder / 'catalog.yml'
    dest_file_folder.mkdir(parents=True)
    Catalog.oscal_read(original_catalog_path).oscal_write(dest_file_location)
    testargs = [
        'trestle', 'create', '-f',
        str(dest_file_location), '-e',
        'catalog.metadata.roles, catalog.metadata.roles, catalog.metadata.responsible-parties',
        IOF_SHORT
    ]

    monkeypatch.setattr(sys, 'argv', testargs)
    assert Trestle().run() == 0
def test_catalog_assemble_version(sample_catalog_rich_controls: cat.Catalog,
                                  tmp_trestle_dir: pathlib.Path) -> None:
    """Test catalog assemble version."""
    cat_name = 'my_cat'
    md_name = 'my_md'
    new_version = '1.2.3'
    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'
    sample_catalog_rich_controls.oscal_write(catalog_path)
    markdown_path = tmp_trestle_dir / md_name
    catalog_generate = CatalogGenerate()
    catalog_generate.generate_markdown(tmp_trestle_dir, catalog_path,
                                       markdown_path, {}, False)
    CatalogAssemble.assemble_catalog(tmp_trestle_dir, md_name,
                                     assembled_cat_name, cat_name, False,
                                     False, new_version)
    assembled_cat, assembled_cat_path = ModelUtils.load_top_level_model(
        tmp_trestle_dir, assembled_cat_name, cat.Catalog)
    assert assembled_cat.metadata.version.__root__ == new_version
    assert ModelUtils.model_age(
        assembled_cat) < test_utils.NEW_MODEL_AGE_SECONDS

    creation_time = assembled_cat_path.stat().st_mtime

    # assemble same way again and confirm no new write
    CatalogAssemble.assemble_catalog(tmp_trestle_dir, md_name,
                                     assembled_cat_name, assembled_cat_name,
                                     False, False, new_version)

    assert creation_time == assembled_cat_path.stat().st_mtime

    # change version and confirm write
    CatalogAssemble.assemble_catalog(tmp_trestle_dir, md_name,
                                     assembled_cat_name, assembled_cat_name,
                                     False, False, 'xx')

    assert creation_time < assembled_cat_path.stat().st_mtime
def run(path: pathlib.Path, count: int) -> None:
    """Run the benchmark."""
    my_catalog = Catalog.oscal_read(path)
    tick = timeit.default_timer()
    for ii in range(count):
        if ii % 2 == 0:
            stuff = my_catalog.oscal_serialize_json(pretty=True)
        else:
            stuff = my_catalog.oscal_serialize_json()
    tock = timeit.default_timer()
    # This line is needed. Without if stuff is not used strange behaviour can occur.
    logger.debug(stuff)
    logger.info('-----------------------------')
    logger.info(f'Time to complete {count} iterations:  {tock - tick}')
Exemple #16
0
def test_run(tmp_dir, sample_catalog_minimal):
    """Test _run for AddCmd."""
    # expected catalog after add of Responsible-Party
    file_path = pathlib.Path.joinpath(test_utils.JSON_TEST_DATA_PATH,
                                      'minimal_catalog_roles_double_rp.json')
    expected_catalog_roles2_rp = Catalog.oscal_read(file_path)

    content_type = FileContentType.YAML

    catalog_def_dir, catalog_def_file = test_utils.prepare_trestle_project_dir(
        tmp_dir, content_type, sample_catalog_minimal, test_utils.CATALOGS_DIR)

    testargs = [
        'trestle', 'add', '-f',
        str(catalog_def_file), '-e',
        'catalog.metadata.roles, catalog.metadata.roles, catalog.metadata.responsible-parties'
    ]

    with patch.object(sys, 'argv', testargs):
        Trestle().run()

    actual_catalog = Catalog.oscal_read(catalog_def_file)
    assert expected_catalog_roles2_rp == actual_catalog
 def write_parameters_catalog(
     self,
     parameters: Dict[str, Parameter],
     timestamp: str,
     oscal_version: str,
     version: str,
     ofile: str,
     verbose: bool,
 ) -> None:
     """Write parameters catalog."""
     parameter_metadata = Metadata(
         title='Component Parameters',
         last_modified=timestamp,
         oscal_version=oscal_version,
         version=version,
     )
     parameter_catalog = Catalog(
         uuid=str(uuid.uuid4()),
         metadata=parameter_metadata,
         params=list(parameters.values()),
     )
     if verbose:
         logger.info(f'output: {ofile}')
     parameter_catalog.oscal_write(pathlib.Path(ofile))
Exemple #18
0
    def _merge_catalog(self, merged: Optional[cat.Catalog],
                       catalog: cat.Catalog) -> cat.Catalog:
        """Merge the controls in the catalog into merged catalog."""
        # no merge means keep, including dups
        # same for merge with no combine
        # groups are merged only if separate directive such as as-is is given
        # use-first is a merge combination rule
        # merge is a merge combination rule for controls.  groups are not merged by this rule
        # merge/as-is and merge/custom are used for merging groups
        # if neither as-is nor custom is specified - just get single list of controls
        # unstructured controls should appear after any loose params

        # make copies to avoid changing input objects
        local_cat = catalog.copy(deep=True)
        local_merged = merged.copy(deep=True) if merged else None

        merge_method = prof.Method.keep
        as_is = False
        if self._profile.merge is not None:
            if self._profile.merge.custom is not None:
                raise TrestleError(
                    'Profile with custom merge is not supported.')
            if self._profile.merge.as_is is not None:
                as_is = self._profile.merge.as_is
            if self._profile.merge.combine is None:
                logger.warning(
                    'Profile has merge but no combine so defaulting to combine/merge.'
                )
                merge_method = prof.Method.merge
            else:
                merge_combine = self._profile.merge.combine
                if merge_combine.method is None:
                    logger.warning(
                        'Profile has merge combine but no method.  Defaulting to merge.'
                    )
                    merge_method = prof.Method.merge
                else:
                    merge_method = merge_combine.method

        if local_merged is None:
            return self._flatten_catalog(local_cat, as_is)

        # merge the incoming catalog with merged based on merge_method and as_is
        return self._merge_two_catalogs(local_merged, local_cat, merge_method,
                                        as_is)
def test_assemble_catalog(testdata_dir: pathlib.Path, tmp_trestle_dir: pathlib.Path) -> None:
    """Test assembling a catalog."""
    test_data_source = testdata_dir / 'split_merge/step4_split_groups_array/catalogs'
    catalogs_dir = pathlib.Path('catalogs/')
    mycatalog_dir = catalogs_dir / 'mycatalog'
    # Copy files from test/data/split_merge/step4
    shutil.rmtree(catalogs_dir)
    shutil.rmtree(pathlib.Path('dist'))
    shutil.copytree(test_data_source, catalogs_dir)

    testargs = ['trestle', 'assemble', 'catalog', '-n', 'mycatalog', '-x', 'json']
    with mock.patch.object(sys, 'argv', testargs):
        rc = Trestle().run()
        assert rc == 0

    # Read assembled model
    actual_model = Catalog.oscal_read(pathlib.Path('dist/catalogs/mycatalog.json'))
    _, _, expected_model = load_distributed(mycatalog_dir / 'catalog.json')

    assert actual_model == expected_model
Exemple #20
0
def test_add(tmp_dir, sample_catalog_minimal):
    """Test AddCmd.add() method for trestle add."""
    # expected catalog after first add of Role
    file_path = pathlib.Path.joinpath(test_utils.JSON_TEST_DATA_PATH,
                                      'minimal_catalog_roles.json')
    expected_catalog_roles1 = Catalog.oscal_read(file_path)

    # expected catalog after second add of Role
    file_path = pathlib.Path.joinpath(test_utils.JSON_TEST_DATA_PATH,
                                      'minimal_catalog_roles_double.json')
    expected_catalog_roles2 = Catalog.oscal_read(file_path)

    # expected catalog after add of Responsible-Party
    file_path = pathlib.Path.joinpath(test_utils.JSON_TEST_DATA_PATH,
                                      'minimal_catalog_roles_double_rp.json')
    expected_catalog_roles2_rp = Catalog.oscal_read(file_path)

    content_type = FileContentType.JSON

    catalog_def_dir, catalog_def_file = test_utils.prepare_trestle_project_dir(
        tmp_dir, content_type, sample_catalog_minimal, test_utils.CATALOGS_DIR)

    # Execute first _add
    element_path = ElementPath('catalog.metadata.roles')
    catalog_element = Element(sample_catalog_minimal)
    AddCmd.add(catalog_def_dir / catalog_def_file, element_path, Catalog,
               catalog_element)

    actual_catalog_roles = Catalog.oscal_read(catalog_def_dir /
                                              catalog_def_file)
    assert actual_catalog_roles == expected_catalog_roles1

    # Execute second _add - this time roles already exists, so this adds a roles object to roles array
    catalog_element = Element(actual_catalog_roles)
    AddCmd.add(catalog_def_dir / catalog_def_file, element_path, Catalog,
               catalog_element)
    actual_catalog_roles2 = Catalog.oscal_read(catalog_def_dir /
                                               catalog_def_file)
    assert actual_catalog_roles2 == expected_catalog_roles2

    # Execute _add for responsible-parties to the same catalog
    element_path = ElementPath('catalog.metadata.responsible-parties')
    catalog_element = Element(actual_catalog_roles2)
    AddCmd.add(catalog_def_dir / catalog_def_file, element_path, Catalog,
               catalog_element)
    actual_catalog_roles2_rp = Catalog.oscal_read(catalog_def_dir /
                                                  catalog_def_file)
    assert actual_catalog_roles2_rp == expected_catalog_roles2_rp
Exemple #21
0
def sample_catalog_minimal():
    """Return a valid catalog object with minimum fields necessary."""
    file_path = pathlib.Path.joinpath(test_utils.JSON_TEST_DATA_PATH,
                                      'minimal_catalog.json')
    catalog_obj = Catalog.oscal_read(file_path)
    return catalog_obj
Exemple #22
0
def sample_catalog():
    """Return a valid catalog object."""
    file_path = pathlib.Path.joinpath(test_utils.JSON_TEST_DATA_PATH,
                                      'good_catalog.json')
    catalog_obj = Catalog.oscal_read(file_path)
    return catalog_obj
def test_remove(tmp_path, sample_catalog_minimal):
    """Test RemoveCmd.remove() method for trestle remove: removing Roles and Responsible-Parties."""
    # 1. Remove responsible-parties
    # Note: minimal catalog does have responsible-parties but doesn't have Roles.
    file_path = pathlib.Path.joinpath(test_utils.JSON_TEST_DATA_PATH,
                                      'minimal_catalog.json')
    catalog_with_responsible_parties = Element(Catalog.oscal_read(file_path))

    # minimal catalog with responsible-parties (dict) removed
    file_path = pathlib.Path.joinpath(
        test_utils.JSON_TEST_DATA_PATH,
        'minimal_catalog_no_responsible-parties.json')
    expected_catalog_responsible_parties_removed = Element(
        Catalog.oscal_read(file_path))

    # Target path for removal:
    element_path = ElementPath('catalog.metadata.responsible-parties')
    expected_remove_action = RemoveAction(catalog_with_responsible_parties,
                                          element_path)

    # Call remove() method
    actual_remove_action, actual_catalog_removed_responsible_parties = RemoveCmd.remove(
        element_path, Catalog, catalog_with_responsible_parties)

    # 1.1 Assertion about action
    assert expected_remove_action == actual_remove_action

    add_plan = Plan()
    add_plan.add_action(actual_remove_action)
    add_plan.simulate()
    add_plan.execute()

    # 1.2 Assertion about resulting element after removal
    assert expected_catalog_responsible_parties_removed == actual_catalog_removed_responsible_parties

    # 2. Remove roles
    # Note: minimal catalog does have responsible-parties but doesn't have Roles.
    file_path = pathlib.Path.joinpath(test_utils.JSON_TEST_DATA_PATH,
                                      'minimal_catalog.json')
    catalog_without_roles = Element(Catalog.oscal_read(file_path))

    # minimal catalog with Roles
    file_path = pathlib.Path.joinpath(test_utils.JSON_TEST_DATA_PATH,
                                      'minimal_catalog_roles.json')
    catalog_with_roles = Element(Catalog.oscal_read(file_path))

    # Target path for removal:
    element_path = ElementPath('catalog.metadata.roles')
    expected_remove_action = RemoveAction(catalog_with_roles, element_path)

    # Call remove() method
    actual_remove_action, actual_catalog_removed_roles = RemoveCmd.remove(
        element_path, Catalog, catalog_with_roles)

    # 2.1 Assertion about action
    assert expected_remove_action == actual_remove_action

    add_plan = Plan()
    add_plan.add_action(actual_remove_action)
    add_plan.simulate()
    add_plan.execute()

    # 2.2 Assertion about resulting element after removal
    assert catalog_without_roles == actual_catalog_removed_roles
 def __init__(self, catalog_file: str) -> None:
     """Initialize."""
     self._catalog = Catalog.oscal_read(pathlib.Path(catalog_file))
     logger.debug(f'catalog: {catalog_file}')
def sample_catalog():
    """Return a valid catalog object."""
    file_path = pathlib.Path.joinpath(test_utils.JSON_NIST_DATA_PATH,
                                      test_utils.JSON_NIST_CATALOG_NAME)
    catalog_obj = Catalog.oscal_read(file_path)
    return catalog_obj
Exemple #26
0
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from trestle.oscal.catalog import Catalog, Metadata
from datetime import datetime
from uuid import uuid4

print('> Deliberately fail to make a Catalog properly')

try:
    c = Catalog()
except Exception as e:
    print(e)

print()
print('> Make some Metadata')
m = Metadata(
    **{
        'title': 'my cool catalog',
        'last-modified': datetime.now(),
        'version': '0.0.1',
        'oscal-version': '1.0.0'
    })
print(m)

print()
Exemple #27
0
 def _execute(self) -> TaskOutcome:
     """Wrap the execute for exception handling."""
     if not self._config:
         logger.error('config missing')
         return TaskOutcome('failure')
     try:
         idir = self._config['input-dir']
         odir = self._config['output-dir']
     except KeyError as e:
         logger.info(f'key {e.args[0]} missing')
         return TaskOutcome('failure')
     # 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 = 'catalog.json'
     ofile = opth / oname
     if not overwrite and pathlib.Path(ofile).exists():
         logger.error(f'output: {ofile} already exists')
         return TaskOutcome('failure')
     # metadata links (optional)
     metadata_links = self._config.get('metadata-links')
     # get list or <name>.profile files
     filelist = self._get_filelist(idir)
     if len(filelist) < 1:
         logger.error(f'input: {idir} no .profile file found')
         return TaskOutcome('failure')
     # initialize node list
     self._node_map = {}
     # process files
     for fp in filelist:
         lines = self._get_content(fp)
         self._parse(lines)
     # get root nodes
     root_nodes = self._get_root_nodes()
     # groups and controls
     root = Group(title='root', groups=[])
     for node in root_nodes:
         group = Group(title=f'{node.name} {node.description}')
         root.groups.append(group)
         depth = self._depth(node.name)
         if depth == 3:
             self._add_groups(group, node.name, depth)
         if depth == 2:
             self._add_controls(group, node.name, depth)
     # metadata
     metadata = Metadata(
         title=self._title, last_modified=self._timestamp, oscal_version=OSCAL_VERSION, version=trestle.__version__
     )
     # metadata links
     if metadata_links is not None:
         metadata.links = []
         for item in metadata_links.split():
             link = Link(href=item)
             metadata.links.append(link)
     # catalog
     catalog = Catalog(uuid=_uuid(), metadata=metadata, groups=root.groups)
     # write OSCAL ComponentDefinition to file
     if verbose:
         logger.info(f'output: {ofile}')
     catalog.oscal_write(pathlib.Path(ofile))
     return TaskOutcome('success')
Exemple #28
0
    def _modify_controls(self, catalog: cat.Catalog) -> cat.Catalog:
        """Modify the controls based on the profile."""
        logger.debug(
            f'modify specify catalog {catalog.metadata.title} for profile {self._profile.metadata.title}'
        )
        self._catalog_interface = CatalogInterface(catalog)
        alters: Optional[List[prof.Alter]] = None
        # find the modify and alters
        if self._profile.modify:
            # change all parameter values
            if self._profile.modify.set_parameters and not self._block_params:
                set_param_list = self._profile.modify.set_parameters
                for set_param in set_param_list:
                    self._set_parameter_in_control(set_param)
            alters = self._profile.modify.alters

        if alters is not None:
            title = self._profile.metadata.title
            for alter in alters:
                if alter.control_id is None:
                    logger.warning(
                        f'Alter must have control id specified in profile {title}.'
                    )
                    continue
                id_ = alter.control_id
                if alter.removes is not None:
                    logger.warning(
                        f'Alter not supported for removes in profile {title} control {id_}'
                    )
                    continue
                # we want a warning about adds even if adds are blocked, as in profile generate
                if alter.adds is None:
                    logger.warning(
                        f'Alter has no adds in profile {title} control {id_}')
                    continue
                if not self._block_adds:
                    for add in alter.adds:
                        if add.position is None and add.parts is not None:
                            msg = f'Alter/Add position is not specified in profile {title} control {id_}'
                            msg += ' when adding part, so defaulting to ending.'
                            logger.warning(msg)
                            add.position = prof.Position.ending
                        control = self._catalog_interface.get_control(id_)
                        if control is None:
                            logger.warning(
                                f'Alter/Add refers to control {id_} but it is not found in the import '
                                +
                                f'for profile {self._profile.metadata.title}')
                        else:
                            self._add_to_control(control, add)
                            self._catalog_interface.replace_control(control)

        if self._change_prose:
            # go through all controls and fix the prose based on param values
            self._change_prose_with_param_values()

        catalog = self._catalog_interface.get_catalog()

        # update the original profile metadata with new contents
        # roles and responsible-parties will be pulled in with new uuid's
        new_metadata = self._profile.metadata
        new_metadata.title = f'{catalog.metadata.title}: Resolved by profile {self._profile.metadata.title}'
        links: List[common.Link] = []
        for import_ in self._profile.imports:
            links.append(
                common.Link(**{
                    'href': import_.href,
                    'rel': 'resolution-source'
                }))
        new_metadata.links = links

        # move catalog controls from dummy group '' into the catalog
        for group in as_list(catalog.groups):
            if not group.id:
                catalog.controls = group.controls
                catalog.groups.remove(group)
                break

        catalog.metadata = new_metadata

        return catalog