def test_ssp_writer(testdata_dir: pathlib.Path, tmp_trestle_dir: pathlib.Path, monkeypatch: MonkeyPatch) -> None: """Test ssp writer from cli.""" gen_args, _, _ = setup_for_ssp(True, True, tmp_trestle_dir, prof_name, ssp_name) profile_path, ssp_obj = setup_test(tmp_trestle_dir, testdata_dir, gen_args.trestle_root) resolved_catalog = profile_resolver.ProfileResolver.get_resolved_profile_catalog( tmp_trestle_dir, profile_path) ssp_writer = SSPMarkdownWriter(gen_args.trestle_root) ssp_writer.set_catalog(resolved_catalog) md_text = ssp_writer.get_control_statement('ac-2', 1) assert md_text is not None ssp_writer.set_ssp(ssp_obj) roles_md = ssp_writer.get_responsible_roles_table('ac-2', 1) assert roles_md md_text1 = ssp_writer._parameter_table('ac-2', 1) assert md_text1 md_text3 = ssp_writer.get_fedramp_control_tables('ac-2', 1) assert md_text3 md_text4 = ssp_writer.get_control_part('ac-2', 'item', 1) assert md_text4
def test_ssp_generate(import_cat, specify_sections, tmp_trestle_dir: pathlib.Path) -> None: """Test the ssp generator.""" args, sections, yaml_path = setup_for_ssp(True, False, tmp_trestle_dir, prof_name, ssp_name, import_cat) if specify_sections: args.allowed_sections = 'ImplGuidance,ExpectedEvidence' ssp_cmd = SSPGenerate() # run the command for happy path assert ssp_cmd._run(args) == 0 ac_dir = tmp_trestle_dir / (ssp_name + '/ac') ac_1 = ac_dir / 'ac-1.md' ac_2 = ac_dir / 'ac-2.md' assert ac_1.exists() assert ac_2.exists() assert ac_1.stat().st_size > 1000 assert ac_2.stat().st_size > 2000 with open(yaml_path, 'r', encoding=const.FILE_ENCODING) as f: yaml = YAML() expected_header = yaml.load(f) sections_dict = sections_to_dict(sections) expected_header[const.SECTIONS_TAG] = sections_dict assert test_utils.confirm_text_in_file( ac_1, '## Control', '## Control Guidance') != specify_sections md_api = MarkdownAPI() header, tree = md_api.processor.process_markdown(ac_1) assert tree is not None assert expected_header == header header, tree = md_api.processor.process_markdown(ac_2) assert tree is not None assert expected_header == header
def test_ssp_generate_fail_statement_section( tmp_trestle_dir: pathlib.Path) -> None: """ Test the ssp generator fails if 'statement' is provided. Also checking code where not label is provided. """ args, _, _ = setup_for_ssp(False, False, tmp_trestle_dir, prof_name, ssp_name) args.sections = 'statement' ssp_cmd = SSPGenerate() # run the command for happy path assert ssp_cmd._run(args) > 0
def test_ssp_generate_resolved_catalog(tmp_trestle_dir: pathlib.Path) -> None: """Test the ssp generator to create a resolved profile catalog.""" _, _, _ = setup_for_ssp(False, True, tmp_trestle_dir, prof_name, ssp_name) profile_path = tmp_trestle_dir / f'profiles/{prof_name}/profile.json' new_catalog_dir = tmp_trestle_dir / f'catalogs/{prof_name}_resolved_catalog' new_catalog_dir.mkdir(parents=True, exist_ok=True) new_catalog_path = new_catalog_dir / 'catalog.json' profile_resolver = ProfileResolver() resolved_catalog = profile_resolver.get_resolved_profile_catalog( tmp_trestle_dir, profile_path) assert resolved_catalog # FIXME this should test with a more complex catalog assert len(resolved_catalog.groups) == 1 resolved_catalog.oscal_write(new_catalog_path)
def test_ssp_generate_header_edit(yaml_header: bool, tmp_trestle_dir: pathlib.Path) -> None: """Test ssp generate does not overwrite header edits.""" # always start by creating the markdown with the yaml header args, sections, yaml_path = setup_for_ssp(True, False, tmp_trestle_dir, prof_name, ssp_name) ssp_cmd = SSPGenerate() assert ssp_cmd._run(args) == 0 ac_dir = tmp_trestle_dir / (ssp_name + '/ac') ac_1 = ac_dir / 'ac-1.md' with open(yaml_path, 'r', encoding=const.FILE_ENCODING) as f: yaml = YAML() yaml_header = yaml.load(f) md_api = MarkdownAPI() header, tree = md_api.processor.process_markdown(ac_1) assert tree is not None # remove the sections that were added to original header so we can check other changes in header header.pop(const.SECTIONS_TAG) assert yaml_header == header # edit the header by adding a list item and removing a value assert test_utils.insert_text_in_file(ac_1, 'System Specific', ' - My new edits\n') assert test_utils.delete_line_in_file(ac_1, 'Corporate') # if the yaml header is not written out, the new header should be the one currently in the control # if the yaml header is written out, it is merged with the current header giving priority to current header # so if not written out, the header should have one item added and another deleted due to edits in this test # if written out, it should just have the one added item because the deleted one will be put back in # tell it not to add the yaml header if not yaml_header: args.yaml_header = None assert ssp_cmd._run(args) == 0 header, tree = md_api.processor.process_markdown(ac_1) assert tree is not None assert len(header['control-origination']) == 2 if not yaml_header: assert 'new' in header['control-origination'][0] else: assert 'new' not in header['control-origination'][0]
def test_ssp_get_control_response(tmp_trestle_dir: pathlib.Path, monkeypatch: MonkeyPatch) -> None: """Test generating SSP from the sample profile and generate markdown representation of it.""" args, _, _ = setup_for_ssp(True, True, tmp_trestle_dir, prof_name, ssp_name) ssp_cmd = SSPGenerate() assert ssp_cmd._run(args) == 0 # set responses assert insert_prose(tmp_trestle_dir, 'ac-1_smt.b', 'This is a response') assert insert_prose(tmp_trestle_dir, 'ac-1_smt.c', 'This is also a response.') assert insert_prose(tmp_trestle_dir, 'ac-1_smt.a', 'This is a response.') command_ssp_gen = 'trestle author ssp-assemble -m my_ssp -o ssp_json' execute_command_and_assert(command_ssp_gen, 0, monkeypatch) ssp_json_path = tmp_trestle_dir / 'system-security-plans/ssp_json/system-security-plan.json' profile_path = tmp_trestle_dir / 'profiles/main_profile/profile.json' fetcher = cache.FetcherFactory.get_fetcher(tmp_trestle_dir, str(ssp_json_path)) ssp_obj, _ = fetcher.get_oscal(True) resolved_catalog = profile_resolver.ProfileResolver.get_resolved_profile_catalog( tmp_trestle_dir, profile_path) ssp_io = SSPMarkdownWriter(tmp_trestle_dir) ssp_io.set_catalog(resolved_catalog) ssp_io.set_ssp(ssp_obj) md_text = ssp_io.get_control_response('ac-1', 1, True) assert md_text tree = MarkdownNode.build_tree_from_markdown(md_text.split('\n')) assert tree.get_node_for_key('## Part a.') assert tree.get_node_for_key('## Part c.') assert len(list(tree.get_all_headers_for_level(2))) == 3 md_text = ssp_io.get_control_response('ac-1', 2, False) tree = MarkdownNode.build_tree_from_markdown(md_text.split('\n')) assert tree.get_node_for_key('### Part a.') assert tree.get_node_for_key('### Part c.') assert len(list(tree.get_all_headers_for_level(3))) == 3
def test_ssp_generate_no_header(tmp_trestle_dir: pathlib.Path) -> None: """Test the ssp generator with no yaml header.""" args, _, _ = setup_for_ssp(False, False, tmp_trestle_dir, prof_name, ssp_name) ssp_cmd = SSPGenerate() args.sections = None # run the command for happy path assert ssp_cmd._run(args) == 0 ac_dir = tmp_trestle_dir / (ssp_name + '/ac') ac_1 = ac_dir / 'ac-1.md' ac_2 = ac_dir / 'ac-2.md' assert ac_1.exists() assert ac_2.exists() assert ac_1.stat().st_size > 1000 assert ac_2.stat().st_size > 2000 md_api = MarkdownAPI() header, tree = md_api.processor.process_markdown(ac_1) assert tree is not None assert not header header, tree = md_api.processor.process_markdown(ac_2) assert tree is not None assert not header
def test_ssp_filter(tmp_trestle_dir: pathlib.Path) -> None: """Test the ssp filter.""" # install the catalog and profiles gen_args, _, _ = setup_for_ssp(False, False, tmp_trestle_dir, prof_name, ssp_name, True) # create markdown with profile a gen_args.profile = 'test_profile_a' ssp_gen = SSPGenerate() assert ssp_gen._run(gen_args) == 0 # create ssp from the markdown ssp_assemble = SSPAssemble() args = argparse.Namespace(trestle_root=tmp_trestle_dir, markdown=ssp_name, output=ssp_name, verbose=0, name=None, version=None, regenerate=False) assert ssp_assemble._run(args) == 0 # load the ssp so we can add a setparameter to it for more test coverage ssp, _ = ModelUtils.load_top_level_model(tmp_trestle_dir, ssp_name, ossp.SystemSecurityPlan, FileContentType.JSON) new_setparam = ossp.SetParameter(param_id='ac-1_prm_1', values=['new_value']) ssp.control_implementation.set_parameters = [new_setparam] ModelUtils.save_top_level_model(ssp, tmp_trestle_dir, ssp_name, FileContentType.JSON) filtered_name = 'filtered_ssp' # now filter the ssp through test_profile_d args = argparse.Namespace(trestle_root=tmp_trestle_dir, name=ssp_name, profile='test_profile_d', output=filtered_name, verbose=0, regenerate=False, version=None) ssp_filter = SSPFilter() assert ssp_filter._run(args) == 0 orig_uuid = test_utils.get_model_uuid(tmp_trestle_dir, filtered_name, ossp.SystemSecurityPlan) # filter it again to confirm uuid is same args = argparse.Namespace(trestle_root=tmp_trestle_dir, name=ssp_name, profile='test_profile_d', output=filtered_name, verbose=0, regenerate=False, version=None) ssp_filter = SSPFilter() assert ssp_filter._run(args) == 0 assert orig_uuid == test_utils.get_model_uuid(tmp_trestle_dir, filtered_name, ossp.SystemSecurityPlan) # filter again to confirm uuid is different with regen args = argparse.Namespace(trestle_root=tmp_trestle_dir, name=ssp_name, profile='test_profile_d', output=filtered_name, verbose=0, regenerate=True, version=None) ssp_filter = SSPFilter() assert ssp_filter._run(args) == 0 assert orig_uuid != test_utils.get_model_uuid( tmp_trestle_dir, filtered_name, ossp.SystemSecurityPlan) # now filter the ssp through test_profile_b to force error because b references controls not in the ssp args = argparse.Namespace(trestle_root=tmp_trestle_dir, name=ssp_name, profile='test_profile_b', output=filtered_name, verbose=0, regenerate=True, version=None) ssp_filter = SSPFilter() assert ssp_filter._run(args) == 1
def test_ssp_assemble(tmp_trestle_dir: pathlib.Path) -> None: """Test ssp assemble from cli.""" gen_args, _, _ = setup_for_ssp(True, True, tmp_trestle_dir, prof_name, ssp_name) # first create the markdown ssp_gen = SSPGenerate() assert ssp_gen._run(gen_args) == 0 acme_string = 'Do the ACME requirements' new_version = '1.2.3' prose_a = 'Hello there\n How are you\n line with more text\n\ndouble line' prose_b = 'This is fun\nline with *bold* text\n\n### ACME Component\n\n' + acme_string # edit it a bit assert insert_prose(tmp_trestle_dir, 'ac-1_smt.a', prose_a) assert insert_prose(tmp_trestle_dir, 'ac-1_smt.b', prose_b) # generate markdown again on top of previous markdown to make sure it is not removed ssp_gen = SSPGenerate() assert ssp_gen._run(gen_args) == 0 # now assemble the edited controls into json ssp ssp_assemble = SSPAssemble() args = argparse.Namespace(trestle_root=tmp_trestle_dir, markdown=ssp_name, output=ssp_name, verbose=0, regenerate=False, version=new_version, name=None) assert ssp_assemble._run(args) == 0 orig_ssp, orig_ssp_path = ModelUtils.load_top_level_model( tmp_trestle_dir, ssp_name, ossp.SystemSecurityPlan) orig_uuid = orig_ssp.uuid assert len(orig_ssp.system_implementation.components) == 2 assert orig_ssp.metadata.version.__root__ == new_version assert ModelUtils.model_age(orig_ssp) < test_utils.NEW_MODEL_AGE_SECONDS orig_file_creation = orig_ssp_path.stat().st_mtime # now write it back out and confirm text is still there assert ssp_gen._run(gen_args) == 0 assert confirm_control_contains(tmp_trestle_dir, 'ac-1', 'a.', 'Hello there') assert confirm_control_contains(tmp_trestle_dir, 'ac-1', 'a.', 'line with more text') assert confirm_control_contains(tmp_trestle_dir, 'ac-1', 'b.', 'This is fun') # now assemble it again but don't regen uuid's and don't change version args = argparse.Namespace(trestle_root=tmp_trestle_dir, markdown=ssp_name, output=ssp_name, verbose=0, regenerate=False, name=None, version=None) assert ssp_assemble._run(args) == 0 # confirm the file was not written out since no change assert orig_ssp_path.stat().st_mtime == orig_file_creation repeat_ssp, _ = ModelUtils.load_top_level_model(tmp_trestle_dir, ssp_name, ossp.SystemSecurityPlan) assert orig_ssp.control_implementation == repeat_ssp.control_implementation assert orig_ssp.system_implementation == repeat_ssp.system_implementation assert len(repeat_ssp.system_implementation.components) == 2 assert repeat_ssp.metadata.version.__root__ == new_version found_it = False for imp_req in repeat_ssp.control_implementation.implemented_requirements: if imp_req.control_id == 'ac-1': statements = imp_req.statements assert len(statements) == 3 for statement in statements: for by_component in statement.by_components: if by_component.description == acme_string: found_it = True assert len(statement.by_components) == 2 break if found_it: break assert found_it # assemble it again but regen uuid's # this should not regen uuid's because the file is not written out if only difference is uuid's args = argparse.Namespace(trestle_root=tmp_trestle_dir, markdown=ssp_name, output=ssp_name, verbose=0, regenerate=True, name=None, version=None) assert ssp_assemble._run(args) == 0 assert orig_uuid == test_utils.get_model_uuid(tmp_trestle_dir, ssp_name, ossp.SystemSecurityPlan) # confirm the file was not written out since no change assert orig_ssp_path.stat().st_mtime == orig_file_creation # assemble it again but give new version and regen uuid's args = argparse.Namespace(trestle_root=tmp_trestle_dir, markdown=ssp_name, output=ssp_name, verbose=0, regenerate=True, name=None, version='new version to force write') assert ssp_assemble._run(args) == 0 assert orig_uuid != test_utils.get_model_uuid(tmp_trestle_dir, ssp_name, ossp.SystemSecurityPlan) # confirm the file was not written out since no change assert orig_ssp_path.stat().st_mtime > orig_file_creation