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
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'
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))
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
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
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}')
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))
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
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
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
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
# # 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()
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')
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