From 4263f1a72fa9a3ebea01b3b5c301cf89a962bf9c Mon Sep 17 00:00:00 2001 From: mrgadgil <49280244+mrgadgil@users.noreply.github.com> Date: Tue, 16 May 2023 10:00:41 -0400 Subject: [PATCH 01/10] fix: update readme with webex details (#1383) --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index dff5206ab..52e2dcec4 100644 --- a/README.md +++ b/README.md @@ -88,16 +88,16 @@ Compliance trestle is currently stable and is based on NIST OSCAL version 1.0.4, ## Community call We would like to share development in progress for compliance trestle, coming soon and get feedback from community on what features would they like to see in compliance trestle.\ -The community call will happen every 2 week(s) on Tuesday at 10am EST.\ +The community call will happen every 2 week(s) on Tuesday at 10.00am EST.\ Meeting information: ``` Compliance Trestle Community Call Hosted by MANJIREE GADGIL -https://ibm.webex.com/ibm/j.php?MTID=m46740e85f87f290d0848c6941c489b0a -Tuesday, May 16, 2023 10:30 AM | 30 minutes | (UTC-04:00) Eastern Time (US & Canada) -Occurs every 2 week(s) on Tuesday effective 5/16/2023 from 10:30 AM to 11:00 AM, (UTC-04:00) Eastern Time (US & Canada) +https://ibm.webex.com/ibm/j.php?MTID=mcf86d5904761ea884ec248c006fc3b81 +Tuesday, May 16, 2023 10:00 AM | 30 minutes | (UTC-04:00) Eastern Time (US & Canada) +Occurs every 2 week(s) on Tuesday effective 5/16/2023 from 10:00 AM to 10:30 AM, (UTC-04:00) Eastern Time (US & Canada) Join by phone 1-844-531-0958 United States Toll Free From f0ffdecb963d7cd341b6b40be3a02efd3e76748d Mon Sep 17 00:00:00 2001 From: Lou DeGenaro Date: Sat, 20 May 2023 07:47:15 -0400 Subject: [PATCH 02/10] fix: some tests failing on linux (#1387) Signed-off-by: degenaro --- .../core/commands/author/command_test.py | 8 +-- tests/trestle/core/commands/href_test.py | 18 ++++--- tests/trestle/core/commands/remove_test.py | 51 +++++++++++++------ 3 files changed, 50 insertions(+), 27 deletions(-) diff --git a/tests/trestle/core/commands/author/command_test.py b/tests/trestle/core/commands/author/command_test.py index 19ad4f8b1..c45b32a81 100644 --- a/tests/trestle/core/commands/author/command_test.py +++ b/tests/trestle/core/commands/author/command_test.py @@ -22,6 +22,7 @@ import pytest import trestle.cli +from trestle.core.commands.common.return_codes import CmdReturnCodes def test_governed_docs_cli(tmp_trestle_dir: pathlib.Path, monkeypatch: MonkeyPatch) -> None: @@ -32,7 +33,7 @@ def test_governed_docs_cli(tmp_trestle_dir: pathlib.Path, monkeypatch: MonkeyPat trestle.cli.run() # FIXME: Needs to be changed once implemented. assert wrapped_error.type == SystemExit - assert wrapped_error.value.code == 2 + assert wrapped_error.value.code == CmdReturnCodes.INCORRECT_ARGS.value def test_governed_folders_cli(tmp_trestle_dir: pathlib.Path, monkeypatch: MonkeyPatch) -> None: @@ -43,7 +44,7 @@ def test_governed_folders_cli(tmp_trestle_dir: pathlib.Path, monkeypatch: Monkey trestle.cli.run() # FIXME: Needs to be changed once implemented. assert wrapped_error.type == SystemExit - assert wrapped_error.value.code == 2 + assert wrapped_error.value.code == CmdReturnCodes.INCORRECT_ARGS.value @pytest.mark.parametrize( @@ -52,8 +53,9 @@ def test_governed_folders_cli(tmp_trestle_dir: pathlib.Path, monkeypatch: Monkey def test_failure_not_trestle(command_string, tmp_path: pathlib.Path, monkeypatch: MonkeyPatch) -> None: """Test for failure based on not in trestle directory.""" monkeypatch.setattr(sys, 'argv', command_string.split()) + monkeypatch.chdir(tmp_path) with pytest.raises(SystemExit) as wrapped_error: trestle.cli.run() # FIXME: Needs to be changed once implemented. assert wrapped_error.type == SystemExit - assert wrapped_error.value.code == 5 + assert wrapped_error.value.code == CmdReturnCodes.TRESTLE_ROOT_ERROR.value diff --git a/tests/trestle/core/commands/href_test.py b/tests/trestle/core/commands/href_test.py index 516a12e14..fbd23588d 100644 --- a/tests/trestle/core/commands/href_test.py +++ b/tests/trestle/core/commands/href_test.py @@ -24,6 +24,7 @@ from trestle.cli import Trestle from trestle.common.model_utils import ModelUtils +from trestle.core.commands.common.return_codes import CmdReturnCodes from trestle.core.models.file_content_type import FileContentType from trestle.oscal import profile @@ -70,6 +71,14 @@ def test_href_failures( tmp_path: pathlib.Path, keep_cwd: pathlib.Path, simplified_nist_profile: profile.Profile, monkeypatch: MonkeyPatch ) -> None: """Test href failure modes.""" + cmd_string = 'trestle href -n my_test_model -hr foobar' + + # not in trestle project so fail + monkeypatch.setattr(sys, 'argv', cmd_string.split()) + monkeypatch.chdir(tmp_path) + rc = Trestle().run() + assert rc == CmdReturnCodes.TRESTLE_ROOT_ERROR.value + # prepare trestle project dir with the file models_path, profile_path = test_utils.prepare_trestle_project_dir( tmp_path, @@ -77,13 +86,6 @@ def test_href_failures( simplified_nist_profile, test_utils.PROFILES_DIR) - cmd_string = 'trestle href -n my_test_model -hr foobar' - - # not in trestle project so fail - monkeypatch.setattr(sys, 'argv', cmd_string.split()) - rc = Trestle().run() - assert rc == 5 - os.chdir(models_path) cmd_string = 'trestle href -n my_test_model -hr foobar -i 2' @@ -93,4 +95,4 @@ def test_href_failures( simplified_nist_profile.oscal_write(profile_path) monkeypatch.setattr(sys, 'argv', cmd_string.split()) rc = Trestle().run() - assert rc == 1 + assert rc == CmdReturnCodes.COMMAND_ERROR.value diff --git a/tests/trestle/core/commands/remove_test.py b/tests/trestle/core/commands/remove_test.py index a796220e9..76f744b82 100644 --- a/tests/trestle/core/commands/remove_test.py +++ b/tests/trestle/core/commands/remove_test.py @@ -27,6 +27,7 @@ import trestle.common.err as err from trestle.cli import Trestle +from trestle.core.commands.common.return_codes import CmdReturnCodes from trestle.core.commands.remove import RemoveCmd from trestle.core.models.actions import RemoveAction from trestle.core.models.elements import Element, ElementPath @@ -124,20 +125,20 @@ def test_remove_failure(tmp_path: pathlib.Path): def test_run_failure_switches(tmp_path: pathlib.Path, monkeypatch: MonkeyPatch): """Test failure of _run on bad switches for RemoveCmd.""" # 1. Missing --file argument. - testargs = ['trestle', 'remove', '-e', 'catalog.metadata.roles'] + testargs = ['trestle', 'remove', '-e', 'catalog.metadata.roles', '--trestle-root', str(tmp_path)] monkeypatch.setattr(sys, 'argv', testargs) with pytest.raises(SystemExit) as e: Trestle().run() assert e.type == SystemExit - assert e.value.code == 2 + assert e.value.code == CmdReturnCodes.INCORRECT_ARGS.value # 2. Missing --element argument. - testargs = ['trestle', 'remove', '-f', './catalog.json'] + testargs = ['trestle', 'remove', '-f', './catalog.json', '--trestle-root', str(tmp_path)] monkeypatch.setattr(sys, 'argv', testargs) with pytest.raises(SystemExit) as e: Trestle().run() assert e.type == SystemExit - assert e.value.code == 2 + assert e.value.code == CmdReturnCodes.INCORRECT_ARGS.value def test_run_failure_nonexistent_element( @@ -154,18 +155,27 @@ def test_run_failure_nonexistent_element( ) # 1. self.remove() fails -- Should happen if wildcard is given, or nonexistent element. - testargs = ['trestle', 'remove', '-f', str(catalog_def_file), '-e', 'catalog.blah'] + testargs = ['trestle', 'remove', '-f', str(catalog_def_file), '-e', 'catalog.blah', '--trestle-root', str(tmp_path)] monkeypatch.setattr(sys, 'argv', testargs) exitcode = Trestle().run() - assert exitcode == 5 + assert exitcode == CmdReturnCodes.COMMAND_ERROR.value # 2. Corrupt json file source_file_path = pathlib.Path.joinpath(test_utils.JSON_TEST_DATA_PATH, 'bad_simple.json') shutil.copyfile(source_file_path, catalog_def_file) - testargs = ['trestle', 'remove', '-f', str(catalog_def_file), '-e', 'catalog.metadata.roles'] + testargs = [ + 'trestle', + 'remove', + '-f', + str(catalog_def_file), + '-e', + 'catalog.metadata.roles', + '--trestle-root', + str(tmp_path) + ] monkeypatch.setattr(sys, 'argv', testargs) exitcode = Trestle().run() - assert exitcode == 5 + assert exitcode == CmdReturnCodes.COMMAND_ERROR.value def test_run_failure_wildcard(tmp_path: pathlib.Path, sample_catalog_minimal: Catalog, monkeypatch: MonkeyPatch): @@ -178,10 +188,10 @@ def test_run_failure_wildcard(tmp_path: pathlib.Path, sample_catalog_minimal: Ca sample_catalog_minimal, test_utils.CATALOGS_DIR ) - testargs = ['trestle', 'remove', '-f', str(catalog_def_file), '-e', 'catalog.*'] + testargs = ['trestle', 'remove', '-f', str(catalog_def_file), '-e', 'catalog.*', '--trestle-root', str(tmp_path)] monkeypatch.setattr(sys, 'argv', testargs) exitcode = Trestle().run() - assert exitcode == 5 + assert exitcode == CmdReturnCodes.COMMAND_ERROR.value def test_run_failure_required_element( @@ -198,10 +208,12 @@ def test_run_failure_required_element( ) # 4. simulate() fails -- Should happen if required element is target for deletion monkeypatch.chdir(tmp_path) - testargs = ['trestle', 'remove', '-f', str(catalog_def_file), '-e', 'catalog.metadata'] + testargs = [ + 'trestle', 'remove', '-f', str(catalog_def_file), '-e', 'catalog.metadata', '--trestle-root', str(tmp_path) + ] monkeypatch.setattr(sys, 'argv', testargs) exitcode = Trestle().run() - assert exitcode == 1 + assert exitcode == CmdReturnCodes.COMMAND_ERROR.value def test_run_failure_project_not_found( @@ -217,10 +229,10 @@ def test_run_failure_project_not_found( test_utils.CATALOGS_DIR ) # 5. get_contextual_model_type() fails, i.e., "Trestle project not found" - testargs = ['trestle', 'remove', '-f', '/dev/null', '-e', 'catalog.metadata'] + testargs = ['trestle', 'remove', '-f', '/dev/null', '-e', 'catalog.metadata', '--trestle-root', str(tmp_path)] monkeypatch.setattr(sys, 'argv', testargs) exitcode = Trestle().run() - assert exitcode == 5 + assert exitcode == CmdReturnCodes.COMMAND_ERROR.value def test_run_failure_filenotfounderror( @@ -238,11 +250,18 @@ def test_run_failure_filenotfounderror( # 6. oscal_read fails because file is not found # Must specify catalogs/ location, not catalogs/my_test_model/. testargs = [ - 'trestle', 'remove', '-f', re.sub('my_test_model/', '', str(catalog_def_file)), '-e', 'catalog.metadata' + 'trestle', + 'remove', + '-f', + re.sub('my_test_model/', '', str(catalog_def_file)), + '-e', + 'catalog.metadata', + '--trestle-root', + str(tmp_path) ] monkeypatch.setattr(sys, 'argv', testargs) exitcode = Trestle().run() - assert exitcode == 5 + assert exitcode == CmdReturnCodes.COMMAND_ERROR.value def test_run_failure_plan_execute( From 509afa7df124f8a6c3516ad06db256777baaef98 Mon Sep 17 00:00:00 2001 From: Jennifer Power Date: Tue, 23 May 2023 09:21:30 -0400 Subject: [PATCH 03/10] feat: adds control origination to ssp-filter (#1375) * feat(cli): adds logic to filter ssp by control origination Adds test to test one and multiple control origination value inputs Adds test to test bad control origintation value input Adds filtering logic to ssp.py Closes #1361 Signed-off-by: Jennifer Power * docs: updates trestle author docs with ssp-filter changes for control origination Signed-off-by: Jennifer Power * fix: updates control origination flag value in ssp.py Signed-off-by: Jennifer Power * fix: adds break to remove duplicate implemented requirements When filtering for control origination, the property could be specified more than one time. This change adds a break and changes to the test component defintion to ensure this case is covered. Signed-off-by: Jennifer Power --------- Signed-off-by: Jennifer Power --- docs/trestle_author.md | 10 +- .../ssp_profile_catalog_authoring.md | 6 +- tests/data/json/comp_def_b.json | 12 ++ .../trestle/core/commands/author/ssp_test.py | 124 ++++++++++++++++-- trestle/common/const.py | 12 ++ trestle/core/commands/author/ssp.py | 52 +++++++- 6 files changed, 195 insertions(+), 21 deletions(-) diff --git a/docs/trestle_author.md b/docs/trestle_author.md index 780882599..0b2a7bb13 100644 --- a/docs/trestle_author.md +++ b/docs/trestle_author.md @@ -613,15 +613,17 @@ CLI evocation: > trestle author ssp-filter -The `ssp-filter` sub-command takes a given SSP and filters its contents based on a given profile, list of components, and/or control implementation status. +The `ssp-filter` sub-command takes a given SSP and filters its contents based on a given profile, list of components, control implementation status and/or control origination. If filtering by profile, the SSP is assumed to contain a superset of controls needed by the profile, and the filter operation generates a new SSP with just the controls needed by that profile. If the profile references a control not in the SSP, the routine fails with an error. -If filtering by components, a colon-delimited list of components should be provided, with `This system` as the default name for the overall required component for the entire system. Case and spaces are ignored in the component names, so the names could be specified as `--components "this system: my component"`. The resulting, filtered ssp will have updated implementated requirements with filtered by_components on each requirement, and filtered by_components on each statement. +If filtering by components, a colon-delimited list of components should be provided, with `This system` as the default name for the overall required component for the entire system. Case and spaces are ignored in the component names, so the names could be specified as `--components "this system: my component"`. The resulting, filtered ssp will have updated implemented requirements with filtered by_components on each requirement, and filtered by_components on each statement. -If filtering by control implementation status, a comma-demilited list of implementation status values should be provided. These values must comply with the OSCAL SSP format references's allowed values, which are as follows: implemented, partial, planned, alternative, and not-applicable. +If filtering by control implementation status, a comma-delimited list of implementation status values should be provided. These values must comply with the OSCAL SSP format references's allowed values, which are as follows: implemented, partial, planned, alternative, and not-applicable. -You may filter by a combination of a profile, list of component names, and implementation statuses. +If filtering by control origination, a comma-delimited list of control origination values should be provided. These values must comply with the OSCAL SSP format references's allowed values for the control origination property, which are as follows: system-specific, inherited, organization, customer-configured, and customer-provided. + +You may filter by a combination of a profile, list of component names, implementation statuses, and control origination values. As with the other related author commands, if an existing destination file already exists, it is not updated if no changes would be made. diff --git a/docs/tutorials/ssp_profile_catalog_authoring/ssp_profile_catalog_authoring.md b/docs/tutorials/ssp_profile_catalog_authoring/ssp_profile_catalog_authoring.md index 6639bc64e..4ce1ef4e3 100644 --- a/docs/tutorials/ssp_profile_catalog_authoring/ssp_profile_catalog_authoring.md +++ b/docs/tutorials/ssp_profile_catalog_authoring/ssp_profile_catalog_authoring.md @@ -1010,13 +1010,13 @@ If you do not specify component-defintions during assembly, the markdown should trestle author ssp-filter -Once you have an SSP in the trestle directory you can filter its contents with a profile, list of components, or list of implementation status values by using the command `trestle author ssp-filter`. The SSP is assumed to contain a superset of the controls needed by the profile if a profile is specified, and the filter operation will generate a new SSP with only those controls needed by the profile. If a list of component names is provided, only the specified components will appear in the system implementation of the ssp. If a list of implementation statuses is provided, controls with implementations including those statuses will appear in the control implementation of the ssp. +Once you have an SSP in the trestle directory you can filter its contents with a profile, list of components, list of implementation statuses, or list control origination values by using the command `trestle author ssp-filter`. The SSP is assumed to contain a superset of the controls needed by the profile if a profile is specified, and the filter operation will generate a new SSP with only those controls needed by the profile. If a list of component names is provided, only the specified components will appear in the system implementation of the ssp. If a list of implementation statuses is provided, controls with implementations including those statuses will appear in the control implementation of the ssp. Similarly, if a list of control origination values is provided, implemented requirements with a control origination property value included in the provided values will appear in the control implementation of the ssp. The filter command is invoked as: -`trestle author ssp-filter --name my_ssp --profile my_profile --components comp_a:comp_b --implementation-status "planned,partial" --output my_culled_ssp` +`trestle author ssp-filter --name my_ssp --profile my_profile --components comp_a:comp_b --implementation-status "planned,partial" --control-origination "customer-configured" --output my_culled_ssp` -The SSP must be present in the trestle workspace and, if filtering by profile, that profile must also be in the trestle workspace. This command will generate a new SSP in the workspace. If the profile makes reference to a control not in the SSP then the routine will fail with an error message. Similarly, if one of the components is not present in the ssp the routine will also fail. The implementation statuses must be one of the allowed values as defined in the OSCAL SSP JSON format reference. Those include the following: implemented, partial, planned, alternative, and not-applicable. If an invalid value is provided, an error is returned. +The SSP must be present in the trestle workspace and, if filtering by profile, that profile must also be in the trestle workspace. This command will generate a new SSP in the workspace. If the profile makes reference to a control not in the SSP then the routine will fail with an error message. Similarly, if one of the components is not present in the ssp the routine will also fail. The implementation statuses must be one of the allowed values as defined in the OSCAL SSP JSON format reference. Those include the following: implemented, partial, planned, alternative, and not-applicable. If an invalid value is provided, an error is returned. The control origination values also must be one of the allowed values as defined in the OSCAL SSP JSON format reference. Those include the following: system-specific, inherited, customer-configured, customer-provided, and organization. If an invalid value is provided, an error is returned. diff --git a/tests/data/json/comp_def_b.json b/tests/data/json/comp_def_b.json index 607b80c72..9490f2a35 100644 --- a/tests/data/json/comp_def_b.json +++ b/tests/data/json/comp_def_b.json @@ -166,6 +166,14 @@ { "name": "implementation-status", "value": "implemented" + }, + { + "name": "control-origination", + "value": "system-specific" + }, + { + "name": "control-origination", + "value": "customer-configured" } ], "responsible-roles": [ @@ -300,6 +308,10 @@ { "name": "implementation-status", "value": "implemented" + }, + { + "name": "control-origination", + "value": "system-specific" } ], "responsible-roles": [ diff --git a/tests/trestle/core/commands/author/ssp_test.py b/tests/trestle/core/commands/author/ssp_test.py index 64d5611b5..34288b135 100644 --- a/tests/trestle/core/commands/author/ssp_test.py +++ b/tests/trestle/core/commands/author/ssp_test.py @@ -486,7 +486,8 @@ def test_ssp_filter(tmp_trestle_dir: pathlib.Path) -> None: regenerate=False, version=None, components=None, - implementation_status=None + implementation_status=None, + control_origination=None ) ssp_filter = SSPFilter() assert ssp_filter._run(args) == 0 @@ -514,7 +515,8 @@ def test_ssp_filter(tmp_trestle_dir: pathlib.Path) -> None: regenerate=True, version=None, components='comp_aa', - implementation_status=None + implementation_status=None, + control_origination=None ) ssp_filter = SSPFilter() assert ssp_filter._run(args) == 0 @@ -536,7 +538,8 @@ def test_ssp_filter(tmp_trestle_dir: pathlib.Path) -> None: regenerate=True, version=None, components='comp_aa', - implementation_status=None + implementation_status=None, + control_origination=None ) ssp_filter = SSPFilter() assert ssp_filter._run(args) == 0 @@ -551,7 +554,8 @@ def test_ssp_filter(tmp_trestle_dir: pathlib.Path) -> None: regenerate=False, version=None, components=None, - implementation_status='not-applicable,implemented' + implementation_status='not-applicable,implemented', + control_origination=None ) ssp_filter = SSPFilter() assert ssp_filter._run(args) == 0 @@ -578,7 +582,8 @@ def test_ssp_filter(tmp_trestle_dir: pathlib.Path) -> None: regenerate=False, version=None, components=None, - implementation_status='not-applicable' + implementation_status='not-applicable', + control_origination=None ) ssp_filter = SSPFilter() assert ssp_filter._run(args) == 0 @@ -602,8 +607,11 @@ def test_ssp_filter(tmp_trestle_dir: pathlib.Path) -> None: verbose=0, regenerate=True, version=None, - components=None + components=None, + implementation_status=None, + control_origination=None ) + ssp_filter = SSPFilter() assert ssp_filter._run(args) == 1 @@ -618,7 +626,9 @@ def test_ssp_filter(tmp_trestle_dir: pathlib.Path) -> None: verbose=0, regenerate=True, version=None, - components=None + components=None, + implementation_status=None, + control_origination=None ) ssp_filter = SSPFilter() assert ssp_filter._run(args) == 1 @@ -634,7 +644,105 @@ def test_ssp_filter(tmp_trestle_dir: pathlib.Path) -> None: regenerate=True, version=None, components=None, - implementation_status=bad_impl + implementation_status=bad_impl, + control_origination=None + ) + ssp_filter = SSPFilter() + assert ssp_filter._run(args) == 1 + + +def test_ssp_filter_control_origination(tmp_trestle_dir: pathlib.Path) -> None: + """Test the ssp filter when filtering by control origination.""" + gen_args, _ = setup_for_ssp(tmp_trestle_dir, prof_name, ssp_name) + 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, + compdefs=gen_args.compdefs + ) + assert ssp_assemble._run(args) == 0 + + ssp: ossp.SystemSecurityPlan + ssp, _ = ModelUtils.load_model_for_class(tmp_trestle_dir, ssp_name, ossp.SystemSecurityPlan, FileContentType.JSON) + + assert len(ssp.control_implementation.implemented_requirements) == 8 + + filtered_name = 'filtered_ssp' + + # now filter the ssp by multiple control origination values + args = argparse.Namespace( + trestle_root=tmp_trestle_dir, + name=ssp_name, + profile=None, + output=filtered_name, + verbose=0, + regenerate=False, + version=None, + components=None, + implementation_status=None, + control_origination='customer-configured,system-specific' + ) + ssp_filter = SSPFilter() + assert ssp_filter._run(args) == 0 + + ssp, _ = ModelUtils.load_model_for_class( + tmp_trestle_dir, + filtered_name, + ossp.SystemSecurityPlan, + FileContentType.JSON + ) + + # confirm the imp_reqs have been culled to two controls + assert len(ssp.control_implementation.implemented_requirements) == 2 + + # now filter the ssp by a control origination that is unused + args = argparse.Namespace( + trestle_root=tmp_trestle_dir, + name=ssp_name, + profile=None, + output=filtered_name, + verbose=0, + regenerate=False, + version=None, + components=None, + implementation_status=None, + control_origination='inherited' + ) + ssp_filter = SSPFilter() + assert ssp_filter._run(args) == 0 + + ssp, _ = ModelUtils.load_model_for_class( + tmp_trestle_dir, + filtered_name, + ossp.SystemSecurityPlan, + FileContentType.JSON + ) + + # confirm the imp_reqs have been culled to zero controls + assert len(ssp.control_implementation.implemented_requirements) == 0 + + # filter with an invalid control origination to trigger error + bad_co = 'co_bad' + args = argparse.Namespace( + trestle_root=tmp_trestle_dir, + name=ssp_name, + profile=None, + output=filtered_name, + verbose=0, + regenerate=True, + version=None, + components=None, + implementation_status=None, + control_origination=bad_co ) ssp_filter = SSPFilter() assert ssp_filter._run(args) == 1 diff --git a/trestle/common/const.py b/trestle/common/const.py index 1f9f985cc..e8f224f6d 100644 --- a/trestle/common/const.py +++ b/trestle/common/const.py @@ -555,3 +555,15 @@ CONTROL_IMPLEMENTATION = 'control-implementation' IMPLEMENTED_REQUIREMENT = 'implemented-requirement' + +# Following 5 are allowed control origination values for +# SSP -> ControlImplementation -> ImplementedRequirements -> prop[@name='control-origination']/@value +ORIGINATION_ORGANIZATION = 'organization' + +ORIGINATION_SYSTEM_SPECIFIC = 'system-specific' + +ORIGINATION_CUSTOMER_CONFIGURED = 'customer-configured' + +ORIGINATION_CUSTOMER_PROVIDED = 'customer-provided' + +ORIGINATION_INHERITED = 'inherited' diff --git a/trestle/core/commands/author/ssp.py b/trestle/core/commands/author/ssp.py index b6e15eb48..45bb30e64 100644 --- a/trestle/core/commands/author/ssp.py +++ b/trestle/core/commands/author/ssp.py @@ -589,7 +589,7 @@ class SSPFilter(AuthorCommonCommand): Filter the controls in an ssp. The filtered ssp is based on controls included by the following: - profile, components, and/or implementation status. + profile, components, implementation status, and/or control origination. """ name = 'ssp-filter' @@ -607,6 +607,8 @@ def _init_arguments(self) -> None: self.add_argument('-c', '--components', help=comp_help_str, required=False, type=str) is_help_str = 'Comma-delimited list of control implementation statuses to include in filtered ssp.' self.add_argument('-is', '--implementation-status', help=is_help_str, required=False, type=str) + co_help_str = 'Comma-delimited list of control origination values to include in filtered ssp.' + self.add_argument('-co', '--control-origination', help=co_help_str, required=False, type=str) def _run(self, args: argparse.Namespace) -> int: try: @@ -614,11 +616,12 @@ def _run(self, args: argparse.Namespace) -> int: trestle_root = pathlib.Path(args.trestle_root) comp_names: Optional[List[str]] = None impl_status_values: Optional[List[str]] = None + co_values: Optional[List[str]] = None - if not (args.components or args.implementation_status or args.profile): + if not (args.components or args.implementation_status or args.profile or args.control_origination): logger.warning( 'You must specify at least one, or a combination of: profile, list of component names' - ', or list of implementation statuses for ssp-filter.' + ', list of implementation statuses, or list of control origination values for ssp-filter.' ) return CmdReturnCodes.COMMAND_ERROR.value @@ -643,6 +646,24 @@ def _run(self, args: argparse.Namespace) -> int: ) return CmdReturnCodes.COMMAND_ERROR.value + if args.control_origination: + co_values = args.control_origination.split(',') + allowed_co_values = { + const.ORIGINATION_ORGANIZATION, + const.ORIGINATION_SYSTEM_SPECIFIC, + const.ORIGINATION_INHERITED, + const.ORIGINATION_CUSTOMER_CONFIGURED, + const.ORIGINATION_CUSTOMER_PROVIDED + } + allowed_co_string = ', '.join(str(item) for item in allowed_co_values) + for co in co_values: + if co not in allowed_co_values: + logger.warning( + f'Provided control origination "{co}" is invalid.\n' + f'Please use the following for ssp-filter: {allowed_co_string}' + ) + return CmdReturnCodes.COMMAND_ERROR.value + return self.filter_ssp( trestle_root, args.name, @@ -651,7 +672,8 @@ def _run(self, args: argparse.Namespace) -> int: args.regenerate, args.version, comp_names, - impl_status_values + impl_status_values, + co_values ) except Exception as e: # pragma: no cover return handle_generic_command_exception(e, logger, 'Error generating the filtered ssp') @@ -665,13 +687,14 @@ def filter_ssp( regenerate: bool, version: Optional[str], components: Optional[List[str]] = None, - implementation_status: Optional[List[str]] = None + implementation_status: Optional[List[str]] = None, + control_origination: Optional[List[str]] = None ) -> int: """ Filter the ssp and output new ssp. The filtered ssp is based on controls included by the following: - profile, components, and/or implementation status. + profile, components, implementation status, and/or control origination. Args: trestle_root: root directory of the trestle workspace @@ -682,6 +705,7 @@ def filter_ssp( version: new version for the model components: optional list of component names used for filtering implementation_status: optional list of implementation statuses for filtering + control_origination: optional list of control origination values for filtering Returns: 0 on success, 1 otherwise @@ -798,6 +822,22 @@ def filter_ssp( ssp.control_implementation.implemented_requirements = new_imp_reqs + # filter implemented requirements by control origination property. + # this will remove any implemented requirements without the control origination + # property set + if control_origination: + new_imp_reqs: List[ossp.ImplementedRequirement] = [] + + for imp_requirement in ssp.control_implementation.implemented_requirements: + if imp_requirement.props: + for prop in imp_requirement.props: + if prop.name == const.CONTROL_ORIGINATION and prop.value in control_origination: + new_imp_reqs.append(imp_requirement) + # only add the imp requirement one time + break + + ssp.control_implementation.implemented_requirements = new_imp_reqs + if version: ssp.metadata.version = version From c53faa40ce23a5ad5476cbb6e2c3d32a8e6818dc Mon Sep 17 00:00:00 2001 From: mrgadgil <49280244+mrgadgil@users.noreply.github.com> Date: Wed, 31 May 2023 07:32:30 -0400 Subject: [PATCH 04/10] docs: update maintainers list (#1394) --- MAINTAINERS.md | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/MAINTAINERS.md b/MAINTAINERS.md index 21e60e2de..a97300306 100644 --- a/MAINTAINERS.md +++ b/MAINTAINERS.md @@ -1,23 +1,15 @@ Trestle was designed and open sourced by a team based at [IBM Research](https://www.research.ibm.com/) and others around the world. The list includes: -Christopher Butler - [butler54](https://github.com/butler54) - -Bruno Marques - [brunomarq](https://github.com/brunomarq) +Alejandro Jose Leiva Palomo [AleJo2995](https://github.com/AleJo2995) -Lenin Mehedy - [leninmehedy](https://github.com/leninmehedy) +Christopher Butler [butler54](https://github.com/butler54) -Simon Metson - [drsm79](https://github.com/drsm79) +Lou Degenaro [degenaro](https://github.com/degenaro) -Frank Suits - [fsuits](https://github.com/fsuits) +Frank Suits [fsuits](https://github.com/fsuits) -Jeff Tan - [jeffdmgit](https://github.com/jeffdmgit) +Jennifer Power [jpower432](https://github.com/jpower432) -Nebula Alam - [aNebula](https://github.com/aNebula) +Manjiree Gadgil [mrgadgil](https://github.com/mrgadgil) Vikas Agarwal [vikas-agarwal76](https://github.com/vikas-agarwal76) - -Lou Degenaro [degenaro](https://github.com/degenaro) - -Ekaterina Nikonova [enikonovad](https://github.com/enikonovad) - -Alejandro Jose Leiva Palomo [AleJo2995](https://github.com/AleJo2995) From 5427fbb445e9a54a2ede1caa7e15c15b8977dd10 Mon Sep 17 00:00:00 2001 From: srmamit <100720927+srmamit@users.noreply.github.com> Date: Thu, 1 Jun 2023 12:22:33 -0400 Subject: [PATCH 05/10] fix: use empty string if prose in part is None while writing to markdown (#1390) * fix: use empty string if part prose is None Signed-off-by: Sharma-Amit * test: add test for checking no prose in part Signed-off-by: Sharma-Amit --------- Signed-off-by: Sharma-Amit Co-authored-by: AleJo2995 --- .../profile_to_docs_no_part_prose.md.jinja | 17 ++++ tests/data/json/comp_prof_part_none.json | 89 +++++++++++++++++++ .../core/commands/author/jinja_cmd_test.py | 22 ++++- trestle/core/docs_control_writer.py | 3 +- 4 files changed, 128 insertions(+), 3 deletions(-) create mode 100644 tests/data/jinja/profile_to_docs_no_part_prose.md.jinja create mode 100644 tests/data/json/comp_prof_part_none.json diff --git a/tests/data/jinja/profile_to_docs_no_part_prose.md.jinja b/tests/data/jinja/profile_to_docs_no_part_prose.md.jinja new file mode 100644 index 000000000..d92b30a1b --- /dev/null +++ b/tests/data/jinja/profile_to_docs_no_part_prose.md.jinja @@ -0,0 +1,17 @@ +# Control Page + +{{ +control_writer.write_control_with_sections(control, profile, group_title, +['statement', 'objective', 'ExpectedEvidence', 'guidance', 'table_of_parameters', 'instructions', 'additional_instructions'], +{'statement':'Statement Header', +'objective':'Control Objective Header', +'guidance':'Implementation Guidance', +'instructions': 'Instructions With Prose', +'additional_instructions': 'Additional Instructions (No Prose)', +'table_of_parameters':'Table of Control Parameters' +}, +label_column=True, +add_group_to_title=False +) +| safe +}} diff --git a/tests/data/json/comp_prof_part_none.json b/tests/data/json/comp_prof_part_none.json new file mode 100644 index 000000000..9c8caff8f --- /dev/null +++ b/tests/data/json/comp_prof_part_none.json @@ -0,0 +1,89 @@ +{ +"profile": { + "uuid": "A0000000-0000-4000-8000-000000000014", + "metadata": { + "title": "comp prof aa", + "last-modified": "2021-01-01T00:00:00.000+00:00", + "version": "2021-01-01", + "oscal-version": "1.0.0" + }, + "imports": [ + { + "href": "trestle://catalogs/simplified_nist_catalog/catalog.json", + "include-controls": [ + { + "with-ids": [ + "ac-1", + "ac-2", + "ac-2.1", + "ac-3", + "at-1", + "ac-6.7", + "ac-4", + "at-2" + ] + } + ] + } + ], + "merge": { + "as-is": true + }, + "modify": { + "alters": [ + { + "control-id": "ac-1", + "adds": [ + { + "position": "ending", + "parts": [ + { + "id": "ac-1_instructions", + "name": "instructions", + "prose": "A set of instructions for executors to follow 1." + }, + { + "id": "ac-1_additional_instructions", + "name": "additional_instructions", + "parts": [ + { + "id": "ac-1_additional_instructions.a", + "name": "a", + "parts": [ + { + "id": "ac-1_additional_instructions.a.some_detail_add_instr_1", + "name": "some_detail_add_instr_1" + }, + { + "id": "ac-1_additional_instructions.a.some_detail_add_instr_2", + "name": "some_detail_add_instr_2", + "prose": "More text on how to follow instructions - a2." + } + ] + }, + { + "id": "ac-1_additional_instructions.b", + "name": "b", + "parts": [ + { + "id": "ac-1_additional_instructions.b.some_detail_add_instr_1", + "name": "some_detail_add_instr_1", + "prose": "More text on how to follow instructions - b1" + }, + { + "id": "ac-1_additional_instructions.b.some_detail_add_instr_1", + "name": "some_detail_add_instr_2", + "prose": "More text on how to follow instructions - b2" + } + ] + } + ] + } + ] + } + ] + } + ] + } + } +} diff --git a/tests/trestle/core/commands/author/jinja_cmd_test.py b/tests/trestle/core/commands/author/jinja_cmd_test.py index a88cb74d9..aa4e4e416 100644 --- a/tests/trestle/core/commands/author/jinja_cmd_test.py +++ b/tests/trestle/core/commands/author/jinja_cmd_test.py @@ -25,9 +25,14 @@ from trestle.core.markdown.docs_markdown_node import DocsMarkdownNode -def setup_ssp(testdata_dir: pathlib.Path, tmp_trestle_dir: pathlib.Path, monkeypatch: MonkeyPatch): +def setup_ssp( + testdata_dir: pathlib.Path, + tmp_trestle_dir: pathlib.Path, + monkeypatch: MonkeyPatch, + profile_name: str = 'comp_prof' +): """Prepare repository for docs generation.""" - args, _ = setup_for_ssp(tmp_trestle_dir, 'comp_prof', 'my_ssp') + args, _ = setup_for_ssp(tmp_trestle_dir, profile_name, 'my_ssp') ssp_cmd = SSPGenerate() assert ssp_cmd._run(args) == 0 @@ -151,6 +156,19 @@ def test_jinja_profile_docs( assert node4.get_node_for_key('## Implementation Guidance') +def test_jinja_profile_docs_no_part_prose( + testdata_dir: pathlib.Path, tmp_trestle_dir: pathlib.Path, monkeypatch: MonkeyPatch +) -> None: + """Test Jinja Profile to multiple md files output. Test for if part to add does not have prose at all.""" + input_template = 'profile_to_docs_no_part_prose.md.jinja' + profile_name = 'comp_prof_part_none' + + setup_ssp(testdata_dir, tmp_trestle_dir, monkeypatch, profile_name) + + command_import = f'trestle author jinja -i {input_template} -o controls -p {profile_name} --docs-profile' + execute_command_and_assert(command_import, 0, monkeypatch) + + def test_jinja_profile_docs_with_group_title( testdata_dir: pathlib.Path, tmp_trestle_dir: pathlib.Path, monkeypatch: MonkeyPatch ) -> None: diff --git a/trestle/core/docs_control_writer.py b/trestle/core/docs_control_writer.py index 183014040..927d55cc0 100644 --- a/trestle/core/docs_control_writer.py +++ b/trestle/core/docs_control_writer.py @@ -217,7 +217,8 @@ def _write_part_info( if tag_pattern: self._md_file.new_line(tag_pattern.replace('[.]', tag_section_name)) self._md_file.new_paragraph() - self._md_file.new_line(part_info.prose) + prose = '' if part_info.prose is None else part_info.prose + self._md_file.new_line(prose) self._md_file.new_paragraph() for subpart_info in as_list(part_info.parts): From 760dd4b4dd6ac405df3db0c2d39d9973ab61a0f4 Mon Sep 17 00:00:00 2001 From: srmamit <100720927+srmamit@users.noreply.github.com> Date: Thu, 1 Jun 2023 12:22:48 -0400 Subject: [PATCH 06/10] fix: log warning for duplicate part ids when writing markdown from json (#1395) * fix: use empty string if part prose is None Signed-off-by: Sharma-Amit * test: add test for checking no prose in part Signed-off-by: Sharma-Amit * fix: write warning instead of exit with code 1 when duplicate parts Signed-off-by: Sharma-Amit --------- Signed-off-by: Sharma-Amit --- tests/trestle/core/commands/author/catalog_test.py | 2 +- trestle/core/markdown/control_markdown_node.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/trestle/core/commands/author/catalog_test.py b/tests/trestle/core/commands/author/catalog_test.py index 0130b628e..5c01a8ed2 100644 --- a/tests/trestle/core/commands/author/catalog_test.py +++ b/tests/trestle/core/commands/author/catalog_test.py @@ -693,7 +693,7 @@ def test_catalog_duplicate_parts_statement(tmp_trestle_dir: pathlib.Path, monkey file_utils.insert_text_in_file(md_path, '## Control Statement', control_statement_prose_with_parts) catalog_assemble = 'trestle author catalog-assemble -o my_catalog -m md_catalog' - test_utils.execute_command_and_assert(catalog_assemble, 1, monkeypatch) + test_utils.execute_command_and_assert(catalog_assemble, 0, monkeypatch) _, error = capsys.readouterr() assert 'Duplicate part id ac-2_smt.a' in error diff --git a/trestle/core/markdown/control_markdown_node.py b/trestle/core/markdown/control_markdown_node.py index 7777a9280..9b967ebb6 100644 --- a/trestle/core/markdown/control_markdown_node.py +++ b/trestle/core/markdown/control_markdown_node.py @@ -463,7 +463,7 @@ def _read_parts(self, indent: int, ii: int, lines: List[str], parent_id: str, prop = common.Property(name='label', value=id_text) part = common.Part(name=name, id=id_, prose=prose, props=[prop]) if id_ in [p.id for p in parts]: - raise TrestleError( + logger.warning( f'Duplicate part id {id_} is found in markdown ' f'{tree_context.control_id}. Please correct the part label in line {line}.' ) From 5f59a7fc7cf8b88a9f77ba4554dd493acff67114 Mon Sep 17 00:00:00 2001 From: Lou DeGenaro Date: Fri, 2 Jun 2023 08:53:31 -0400 Subject: [PATCH 07/10] feat: oscal-catalog-to-csv (#1396) * feat: oscal-catalog-to-csv Signed-off-by: degenaro * Populate testing spot checks. Signed-off-by: degenaro * fix validate Signed-off-by: degenaro * Use 'w' for output file open. Signed-off-by: degenaro * fix windows csv. Signed-off-by: degenaro * Fix sonar complaints + improved test coverage Signed-off-by: degenaro * Fix sonar complaint. Signed-off-by: degenaro * Improve test coverage. Signed-off-by: degenaro * 100% test coverage. Signed-off-by: degenaro --------- Signed-off-by: degenaro --- .../trestle.tasks.oscal_catalog_to_csv.md | 2 + mkdocs.yml | 1 + ...cal-catalog-to-csv-rev-5-by-control.config | 6 + ...l-catalog-to-csv-rev-5-by-statement.config | 5 + .../tasks/oscal_catalog_to_csv_test.py | 266 ++++++++++ trestle/tasks/cis_xlsx_to_oscal_catalog.py | 2 +- trestle/tasks/oscal_catalog_to_csv.py | 485 ++++++++++++++++++ 7 files changed, 766 insertions(+), 1 deletion(-) create mode 100644 docs/api_reference/trestle.tasks.oscal_catalog_to_csv.md create mode 100644 tests/data/tasks/oscal-catalog-to-csv/test-oscal-catalog-to-csv-rev-5-by-control.config create mode 100644 tests/data/tasks/oscal-catalog-to-csv/test-oscal-catalog-to-csv-rev-5-by-statement.config create mode 100644 tests/trestle/tasks/oscal_catalog_to_csv_test.py create mode 100644 trestle/tasks/oscal_catalog_to_csv.py diff --git a/docs/api_reference/trestle.tasks.oscal_catalog_to_csv.md b/docs/api_reference/trestle.tasks.oscal_catalog_to_csv.md new file mode 100644 index 000000000..af67ad272 --- /dev/null +++ b/docs/api_reference/trestle.tasks.oscal_catalog_to_csv.md @@ -0,0 +1,2 @@ +::: trestle.tasks.oscal_catalog_to_csv +handler: python diff --git a/mkdocs.yml b/mkdocs.yml index a7c880c10..38b112540 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -167,6 +167,7 @@ nav: - csv_to_oscal_cd: api_reference/trestle.tasks.csv_to_oscal_cd.md - ocp4_cis_profile_to_oscal_catalog: api_reference/trestle.tasks.ocp4_cis_profile_to_oscal_catalog.md - ocp4_cis_profile_to_oscal_cd: api_reference/trestle.tasks.ocp4_cis_profile_to_oscal_cd.md + - oscal_catalog_to_csv: api_reference/trestle.tasks.oscal_catalog_to_csv.md - oscal_profile_to_osco_profile: api_reference/trestle.tasks.oscal_profile_to_osco_profile.md - osco_result_to_oscal_ar: api_reference/trestle.tasks.osco_result_to_oscal_ar.md - tanium_result_to_oscal_ar: api_reference/trestle.tasks.tanium_result_to_oscal_ar.md diff --git a/tests/data/tasks/oscal-catalog-to-csv/test-oscal-catalog-to-csv-rev-5-by-control.config b/tests/data/tasks/oscal-catalog-to-csv/test-oscal-catalog-to-csv-rev-5-by-control.config new file mode 100644 index 000000000..bc1af5d56 --- /dev/null +++ b/tests/data/tasks/oscal-catalog-to-csv/test-oscal-catalog-to-csv-rev-5-by-control.config @@ -0,0 +1,6 @@ +[task.oscal-catalog-to-csv] + +input-file = nist-content/nist.gov/SP800-53/rev5/json/NIST_SP-800-53_rev5_catalog.json +output-dir = /tmp +output-name = NIST_SP-800-53_rev5_catalog.by_control.csv +level = control \ No newline at end of file diff --git a/tests/data/tasks/oscal-catalog-to-csv/test-oscal-catalog-to-csv-rev-5-by-statement.config b/tests/data/tasks/oscal-catalog-to-csv/test-oscal-catalog-to-csv-rev-5-by-statement.config new file mode 100644 index 000000000..5edc31eef --- /dev/null +++ b/tests/data/tasks/oscal-catalog-to-csv/test-oscal-catalog-to-csv-rev-5-by-statement.config @@ -0,0 +1,5 @@ +[task.oscal-catalog-to-csv] + +input-file = nist-content/nist.gov/SP800-53/rev5/json/NIST_SP-800-53_rev5_catalog.json +output-dir = /tmp +output-name = NIST_SP-800-53_rev5_catalog.by_statement.csv \ No newline at end of file diff --git a/tests/trestle/tasks/oscal_catalog_to_csv_test.py b/tests/trestle/tasks/oscal_catalog_to_csv_test.py new file mode 100644 index 000000000..3e6cffdb6 --- /dev/null +++ b/tests/trestle/tasks/oscal_catalog_to_csv_test.py @@ -0,0 +1,266 @@ +# -*- mode:python; coding:utf-8 -*- +# Copyright (c) 2023 IBM Corp. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://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. +"""oscal-catalog-to-csv task tests.""" + +import configparser +import csv +import pathlib +from typing import Dict, List + +from _pytest.monkeypatch import MonkeyPatch + +from tests import test_utils + +import trestle.tasks.oscal_catalog_to_csv as oscal_catalog_to_csv +from trestle.core.catalog.catalog_interface import CatalogInterface +from trestle.oscal.common import HowMany +from trestle.tasks.base_task import TaskOutcome + +CONFIG_BY_CONTROL = 'test-oscal-catalog-to-csv-rev-5-by-control.config' +CONFIG_BY_STATEMENT = 'test-oscal-catalog-to-csv-rev-5-by-statement.config' + +CONFIG_LIST = [f'{CONFIG_BY_CONTROL}', f'{CONFIG_BY_STATEMENT}'] + + +def monkey_exception(): + """Monkey exception.""" + raise RuntimeError('foobar') + + +def monkey_get_dependent_control_ids(self, control_id: str): + """Monkey get_dependent_control_ids.""" + return ['parent', 'parent'] + + +def _get_rows(csv_path: pathlib.Path) -> List[List[str]]: + """Get rows from csv file.""" + rows = [] + with open(csv_path, 'r', newline='') as f: + csv_reader = csv.reader(f, delimiter=',', quoting=csv.QUOTE_MINIMAL) + for row in csv_reader: + rows.append(row) + return rows + + +def _validate(config: str, section: Dict[str, str]) -> None: + """Validate.""" + odir = section['output-dir'] + oname = section['output-name'] + opth = pathlib.Path(odir) / oname + rows = _get_rows(opth) + # spot check + if config == CONFIG_BY_CONTROL: + assert len(rows) == 1190 + row = rows[0] + assert row[0] == 'Control Identifier' + assert row[1] == 'Control Title' + assert row[2] == 'Control Text' + row = rows[1] + assert row[0] == 'AC-1' + assert row[1] == 'Policy and Procedures' + assert row[ + 2 + ] == 'a. Develop, document, and disseminate to [Assignment: organization-defined personnel or roles]: 1. [Selection (one or more): organization-level; mission/business process-level; system-level] access control policy that: 2. Procedures to facilitate the implementation of the access control policy and the associated access controls; b. Designate an [Assignment: official] to manage the development, documentation, and dissemination of the access control policy and procedures; and c. Review and update the current access control: 1. Policy [Assignment: frequency] and following [Assignment: events] ; and 2. Procedures [Assignment: frequency] and following [Assignment: events].' # noqa + elif config == CONFIG_BY_STATEMENT: + assert len(rows) == 1750 + row = rows[0] + assert row[0] == 'Control Identifier' + assert row[1] == 'Control Title' + assert row[2] == 'Statement Identifier' + assert row[3] == 'Statement Text' + row = rows[1] + assert row[0] == 'AC-1' + assert row[1] == 'Policy and Procedures' + assert row[2] == 'AC-1(a)' + assert row[ + 3 + ] == 'a. Develop, document, and disseminate to [Assignment: organization-defined personnel or roles]: 1. [Selection (one or more): organization-level; mission/business process-level; system-level] access control policy that: 2. Procedures to facilitate the implementation of the access control policy and the associated access controls;' # noqa + + +def _test_init(tmp_path: pathlib.Path): + """Test init.""" + test_utils.ensure_trestle_config_dir(tmp_path) + + +def _get_config_section(tmp_path: pathlib.Path, fname: str) -> tuple: + """Get config section.""" + config = configparser.ConfigParser() + config_path = pathlib.Path(f'tests/data/tasks/oscal-catalog-to-csv/{fname}') + config.read(config_path) + section = config['task.oscal-catalog-to-csv'] + section['output-dir'] = str(tmp_path) + return (config, section) + + +def _get_config_section_init(tmp_path: pathlib.Path, fname: str) -> tuple: + """Get config section.""" + _test_init(tmp_path) + return _get_config_section(tmp_path, fname) + + +def test_print_info(tmp_path: pathlib.Path) -> None: + """Test print_info.""" + for config in CONFIG_LIST: + _, section = _get_config_section_init(tmp_path, config) + tgt = oscal_catalog_to_csv.OscalCatalogToCsv(section) + retval = tgt.print_info() + assert retval is None + + +def test_missing_section(tmp_path: pathlib.Path): + """Test missing section.""" + section = None + tgt = oscal_catalog_to_csv.OscalCatalogToCsv(section) + retval = tgt.execute() + assert retval == TaskOutcome.FAILURE + + +def test_missing_input(tmp_path: pathlib.Path): + """Test missing input.""" + for config in CONFIG_LIST: + _, section = _get_config_section_init(tmp_path, config) + section.pop('input-file') + tgt = oscal_catalog_to_csv.OscalCatalogToCsv(section) + retval = tgt.execute() + assert retval == TaskOutcome.FAILURE + + +def test_missing_output(tmp_path: pathlib.Path): + """Test missing output.""" + for config in CONFIG_LIST: + _, section = _get_config_section_init(tmp_path, config) + section.pop('output-dir') + tgt = oscal_catalog_to_csv.OscalCatalogToCsv(section) + retval = tgt.execute() + assert retval == TaskOutcome.FAILURE + + +def test_bogus_level(tmp_path: pathlib.Path): + """Test bogus level.""" + for config in CONFIG_LIST: + _, section = _get_config_section_init(tmp_path, config) + section['level'] = 'foobar' + tgt = oscal_catalog_to_csv.OscalCatalogToCsv(section) + retval = tgt.execute() + assert retval == TaskOutcome.FAILURE + + +def test_simulate(tmp_path: pathlib.Path): + """Test execute.""" + for config in CONFIG_LIST: + _, section = _get_config_section_init(tmp_path, config) + section['output-dir'] = str(tmp_path) + tgt = oscal_catalog_to_csv.OscalCatalogToCsv(section) + retval = tgt.simulate() + assert retval == TaskOutcome.SIM_SUCCESS + + +def test_execute(tmp_path: pathlib.Path): + """Test execute.""" + for config in CONFIG_LIST: + _, section = _get_config_section_init(tmp_path, config) + section['output-dir'] = str(tmp_path) + section['output-name'] = f'{config}.csv' + tgt = oscal_catalog_to_csv.OscalCatalogToCsv(section) + retval = tgt.execute() + assert retval == TaskOutcome.SUCCESS + _validate(config, section) + + +def test_no_overwrite(tmp_path: pathlib.Path): + """Test no overwrite.""" + for config in CONFIG_LIST: + _, section = _get_config_section_init(tmp_path, config) + section['output-dir'] = str(tmp_path) + tgt = oscal_catalog_to_csv.OscalCatalogToCsv(section) + retval = tgt.execute() + assert retval == TaskOutcome.SUCCESS + section['output-overwrite'] = 'false' + retval = tgt.execute() + assert retval == TaskOutcome.FAILURE + + +def test_exception(tmp_path: pathlib.Path, monkeypatch: MonkeyPatch): + """Test exception.""" + monkeypatch.setattr(oscal_catalog_to_csv.CsvHelper, 'write', monkey_exception) + for config in CONFIG_LIST: + _, section = _get_config_section_init(tmp_path, config) + section['output-dir'] = str(tmp_path) + tgt = oscal_catalog_to_csv.OscalCatalogToCsv(section) + retval = tgt.execute() + assert retval == TaskOutcome.FAILURE + + +def test_join(): + """Test join.""" + oscal_catalog_to_csv.join_str(None, None) + oscal_catalog_to_csv.join_str('x', None) + oscal_catalog_to_csv.join_str(None, 'y') + oscal_catalog_to_csv.join_str('x', 'y') + + +def test_derive_id(tmp_path: pathlib.Path): + """Test derive_id.""" + for config in CONFIG_LIST: + _, section = _get_config_section_init(tmp_path, config) + ifile = section['input-file'] + ipth = pathlib.Path(ifile) + catalog_helper = oscal_catalog_to_csv.CatalogHelper(ipth) + ids = ['ac-4.1_smt', 'ac-4.1_smt.a', 'ac-4.1_smt.a.b', 'ac-4.1_smt.a.b.c', 'ac-4.1_smt.a.b.c.d'] + for id_ in ids: + catalog_helper._derive_id(id_) + + +def test_unresolved_param(tmp_path: pathlib.Path): + """Test unresolved param.""" + for config in CONFIG_LIST: + _, section = _get_config_section_init(tmp_path, config) + ifile = section['input-file'] + ipth = pathlib.Path(ifile) + catalog_helper = oscal_catalog_to_csv.CatalogHelper(ipth) + for control in catalog_helper.get_controls(): + utext = 'foo {{ insert: param, xx-11_odp.02 }} bar' + try: + catalog_helper._resolve_parms(control, utext) + raise RuntimeError('huh?') + except RuntimeError: + break + + +def test_one_choice(tmp_path: pathlib.Path): + """Test one choice.""" + for config in CONFIG_LIST: + _, section = _get_config_section_init(tmp_path, config) + ifile = section['input-file'] + ipth = pathlib.Path(ifile) + catalog_helper = oscal_catalog_to_csv.CatalogHelper(ipth) + for control in catalog_helper.get_controls(): + if control.params: + for param in control.params: + if param.select: + param.select.how_many = HowMany.one + catalog_helper._get_parm_value(control, param.id) + return + + +def test_duplicate(tmp_path: pathlib.Path, monkeypatch: MonkeyPatch): + """Test duplicate.""" + monkeypatch.setattr(CatalogInterface, 'get_dependent_control_ids', monkey_get_dependent_control_ids) + for config in CONFIG_LIST: + _, section = _get_config_section_init(tmp_path, config) + section['output-dir'] = str(tmp_path) + tgt = oscal_catalog_to_csv.OscalCatalogToCsv(section) + retval = tgt.execute() + assert retval == TaskOutcome.FAILURE diff --git a/trestle/tasks/cis_xlsx_to_oscal_catalog.py b/trestle/tasks/cis_xlsx_to_oscal_catalog.py index 88ee87087..bd09cc4b3 100644 --- a/trestle/tasks/cis_xlsx_to_oscal_catalog.py +++ b/trestle/tasks/cis_xlsx_to_oscal_catalog.py @@ -225,7 +225,7 @@ class CisXlsxToOscalCatalog(TaskBase): def __init__(self, config_object: Optional[configparser.SectionProxy]) -> None: """ - Initialize trestle task ocp4-cis-profile-to-oscal-catalog. + Initialize trestle task. Args: config_object: Config section associated with the task. diff --git a/trestle/tasks/oscal_catalog_to_csv.py b/trestle/tasks/oscal_catalog_to_csv.py new file mode 100644 index 000000000..b68ed8457 --- /dev/null +++ b/trestle/tasks/oscal_catalog_to_csv.py @@ -0,0 +1,485 @@ +# -*- mode:python; coding:utf-8 -*- +# Copyright (c) 2023 IBM Corp. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://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. +"""OSCAL transformation tasks.""" + +# mypy: ignore-errors # noqa E800 +import configparser +import copy +import csv +import datetime +import logging +import pathlib +import re +import traceback +from typing import Iterator, List, Optional + +from trestle.core.catalog.catalog_interface import CatalogInterface +from trestle.oscal.catalog import Catalog +from trestle.oscal.catalog import Control +from trestle.oscal.common import HowMany +from trestle.oscal.common import Link +from trestle.oscal.common import Parameter +from trestle.oscal.common import Part +from trestle.tasks.base_task import TaskBase +from trestle.tasks.base_task import TaskOutcome + +logger = logging.getLogger(__name__) + +timestamp = datetime.datetime.utcnow().replace(microsecond=0).replace(tzinfo=datetime.timezone.utc).isoformat() + +recurse = True + +level_control = 'control' +level_statement = 'statement' +level_default = level_statement +level_list = [level_control, level_statement] + + +def join_str(s1: Optional[str], s2: Optional[str], sep: str = ' ') -> Optional[str]: + """Join strings.""" + if s1 is None: + rval = s2 + elif s2 is None: + rval = s1 + else: + rval = f'{s1}{sep}{s2}' + return rval + + +def convert_control_id(control_id: str) -> str: + """Convert control id.""" + rval = copy.copy(control_id) + rval = rval.upper() + if '.' in rval: + rval = rval.replace('.', '(') + rval = rval + ')' + return rval + + +def convert_smt_id(smt_id: str) -> str: + """Convert smt id.""" + parts = smt_id.split('_smt') + seg1 = convert_control_id(parts[0]) + seg2 = '' + if len(parts) == 2: + seg2 = parts[1] + if '.' in seg2: + seg2 = seg2.replace('.', '(') + seg2 = seg2 + ')' + rval = f'{seg1}{seg2}' + return rval + + +class CsvHelper: + """Csv Helper.""" + + def __init__(self, path) -> None: + """Initialize.""" + self.path = path + + def write(self, rows: List[List[str]]) -> None: + """Write csv file.""" + with open(self.path, 'w', newline='', encoding='utf-8') as output: + csv_writer = csv.writer(output, delimiter=',', quotechar='"', quoting=csv.QUOTE_MINIMAL) + for row in rows: + csv_writer.writerow(row) + + +class CatalogHelper: + """OSCAL Catalog Helper.""" + + def __init__(self, path) -> None: + """Initialize.""" + self.path = path + self.catalog = Catalog.oscal_read(path) + self.catalog_interface = CatalogInterface(self.catalog) + self._init_control_parent_map() + + def _init_control_parent_map(self, recurse=True) -> None: + """Initialize map: Child Control.id to parent Control.""" + self._control_parent_map = {} + for control in self.catalog_interface.get_all_controls_from_catalog(recurse): + parents = self.catalog_interface.get_dependent_control_ids(control.id) + for parent in parents: + # assert child has only one parent + if parent in self._control_parent_map.keys(): + raise RuntimeError('{parent} duplicate?') + self._control_parent_map[parent] = control + + def get_parent_control(self, ctl_id: str) -> Control: + """Return parent Control of child Control.id, if any.""" + return self._control_parent_map.get(ctl_id) + + def get_family_controls(self, ctl_id: str) -> List[Control]: + """Return family of controls for Control.id, if any.""" + rval = [] + search_id = ctl_id.split('.')[0] + for control in self.catalog_interface.get_all_controls_from_catalog(recurse): + if control.id.startswith(search_id): + rval.append(control) + return rval + + def get_controls(self, recurse=True) -> Iterator: + """Return controls iterator.""" + for control in self.catalog_interface.get_all_controls_from_catalog(recurse): + yield control + + def get_statement_text_for_control(self, control: Control) -> Optional[str]: + """Get statement text for control.""" + statement_text = self._withdrawn(control) + return statement_text + + def get_statement_text_for_part(self, control: Control, part: Part) -> Optional[str]: + """Get statement text for part.""" + statement_text = self._derive_text(control, part) + if part.parts: + for subpart in part.parts: + if '_smt' in subpart.id: + partial_text = self._derive_text(control, subpart) + statement_text = join_str(statement_text, partial_text) + return statement_text + + def _withdrawn(self, control: Control) -> Optional[str]: + """Check if withdrawn.""" + rval = None + for prop in control.props: + if prop.name.lower() == 'status' and prop.value.lower() == 'withdrawn': + status = self._get_status(control) + rval = join_str('Withdrawn', status, '') + rval = f'[{rval}]' + break + return rval + + def _link_generator(self, control: Control) -> Iterator[Link]: + """Link generator.""" + if control.links: + for link in control.links: + yield link + + def _get_status(self, control: Control) -> Optional[str]: + """Get status.""" + rval = None + ilist = None + for link in self._link_generator(control): + if link.rel.lower() == 'moved-to': + moved = self._href_to_control(link.href) + rval = f': Moved to {moved}.' + break + if link.rel.lower() == 'incorporated-into': + incorporated = self._href_to_control(link.href) + if ilist is None: + ilist = f'{incorporated}' + else: + ilist = f'{ilist}, {incorporated}' + if ilist: + rval = f': Incorporated into {ilist}.' + return rval + + def _href_to_control(self, href: str) -> str: + """Convert href to control.""" + rval = href.replace('#', '').upper() + return rval + + def _derive_text(self, control: Control, part: Part) -> Optional[str]: + """Derive control text.""" + rval = None + if part.prose: + id_ = self._derive_id(part.id) + text = self._resolve_parms(control, part.prose) + rval = join_str(id_, text) + return rval + + def _derive_id(self, id_: str) -> str: + """Derive control text sub-part id.""" + rval = None + id_parts = id_.split('_smt') + if id_parts[1]: + id_sub_parts = id_parts[1].split('.') + if len(id_sub_parts) == 2: + rval = f'{id_sub_parts[1]}.' + elif len(id_sub_parts) == 3: + rval = f'{id_sub_parts[2]}.' + elif len(id_sub_parts) == 4: + rval = f'({id_sub_parts[3]})' + return rval + + def _resolve_parms(self, control: Control, utext: str) -> str: + """Resolve parm.""" + rtext = self._resolve_parms_for_control(control, utext) + if '{{' in rtext: + parent_control = self.get_parent_control(control.id) + if parent_control: + rtext = self._resolve_parms_for_control(parent_control, rtext) + if '{{' in rtext: + family_controls = self.get_family_controls(control.id) + for family_control in family_controls: + rtext = self._resolve_parms_for_control(family_control, rtext) + if '{{' in rtext: + text = f'control.id: {control.id} unresolved: {rtext}' + raise RuntimeError(text) + return rtext + + def _resolve_parms_for_control(self, control: Control, utext: str) -> str: + """Resolve parms for control.""" + rtext = utext + staches: List[str] = re.findall(r'{{.*?}}', utext) + if staches: + for stach in staches: + parm_id = stach + parm_id = parm_id.replace('{{', '') + parm_id = parm_id.replace('}}', '') + parm_id = parm_id.split(',')[1].strip() + value = self._get_parm_value(control, parm_id) + if value: + rtext = rtext.replace(stach, value) + return rtext + + def _get_parm_value(self, control: Control, parm_id: str) -> str: + """Get parm value.""" + rval = None + if control.params: + for param in control.params: + if param.id != parm_id: + continue + if param.label: + rval = f'[Assignment: {param.label}]' + elif param.select: + choices = self._get_parm_choices(control, param) + if param.select.how_many == HowMany.one: + rval = f'[Selection (one): {choices}]' + else: + rval = f'[Selection (one or more): {choices}]' + break + return rval + + def _get_parm_choices(self, control: Control, param: Parameter) -> str: + """Get parm choices.""" + choices = '' + for choice in param.select.choice: + rchoice = self._resolve_parms(control, choice) + if choices: + choices += f'; {rchoice}' + else: + choices += f'{rchoice}' + return choices + + +class ContentManager(): + """Content manager.""" + + def __init__(self, catalog_helper: CatalogHelper) -> None: + """Initialize.""" + self.catalog_helper = catalog_helper + self.rows = [] + self.row_template = None + + def add(self, row: List): + """Add row.""" + n_row = copy.copy(row) + t_row = self.row_template + if t_row: + for index in range(3): + if n_row[index] == t_row[index]: + n_row[index] = None + self.rows.append(n_row) + self.row_template = row + + def get_content(self, level: str) -> List: + """Get content.""" + if level == level_control: + rval = self._get_content_by_control() + else: + rval = self._get_content_by_statement() + return rval + + def _get_content_by_statement(self) -> List: + """Get content by statement.""" + catalog_helper = self.catalog_helper + header = ['Control Identifier', 'Control Title', 'Statement Identifier', 'Statement Text'] + self.rows.append(header) + for control in catalog_helper.get_controls(): + control_id = convert_control_id(control.id) + if control.parts: + self._add_parts_by_statement(control) + else: + statement_text = catalog_helper.get_statement_text_for_control(control) + row = [control_id, control.title, '', statement_text] + self.add(row) + return self.rows + + def _add_subparts_by_statement(self, control: Control, part: Part) -> None: + """Add subparts by statement.""" + catalog_helper = self.catalog_helper + control_id = convert_control_id(control.id) + for subpart in part.parts: + if '_smt' in subpart.id: + statement_text = catalog_helper.get_statement_text_for_part(control, subpart) + row = [control_id, control.title, convert_smt_id(subpart.id), statement_text] + self.add(row) + + def _add_parts_by_statement(self, control: Control) -> None: + """Add parts by statement.""" + catalog_helper = self.catalog_helper + control_id = convert_control_id(control.id) + for part in control.parts: + if part.id: + if '_smt' not in part.id: + continue + if part.parts: + self._add_subparts_by_statement(control, part) + else: + statement_text = catalog_helper.get_statement_text_for_part(control, part) + row = [control_id, control.title, convert_smt_id(part.id), statement_text] + self.add(row) + + def _get_content_by_control(self) -> List: + """Get content by statement.""" + catalog_helper = self.catalog_helper + header = ['Control Identifier', 'Control Title', 'Control Text'] + self.rows.append(header) + for control in catalog_helper.get_controls(): + control_id = convert_control_id(control.id) + if control.parts: + self._add_parts_by_control(control) + else: + control_text = catalog_helper.get_statement_text_for_control(control) + row = [control_id, control.title, control_text] + self.add(row) + return self.rows + + def _add_subparts_by_control(self, control: Control, part: Part, control_text) -> str: + """Add subparts by control.""" + catalog_helper = self.catalog_helper + for subpart in part.parts: + if '_smt' in subpart.id: + statement_text = catalog_helper.get_statement_text_for_part(control, subpart) + control_text = join_str(control_text, statement_text) + return control_text + + def _add_parts_by_control(self, control: Control) -> None: + """Add parts by control.""" + catalog_helper = self.catalog_helper + control_id = convert_control_id(control.id) + control_text = None + for part in control.parts: + if part.id: + if '_smt' not in part.id: + continue + if part.parts: + control_text = self._add_subparts_by_control(control, part, control_text) + else: + statement_text = catalog_helper.get_statement_text_for_part(control, part) + control_text = join_str(control_text, statement_text) + row = [control_id, control.title, control_text] + self.add(row) + + +class OscalCatalogToCsv(TaskBase): + """ + Task to transform OSCAL catalog to .csv. + + Attributes: + name: Name of the task. + """ + + name = 'oscal-catalog-to-csv' + + def __init__(self, config_object: Optional[configparser.SectionProxy]) -> None: + """ + Initialize trestle task. + + Args: + config_object: Config section associated with the task. + """ + super().__init__(config_object) + + def print_info(self) -> None: + """Print the help string.""" + logger.info(f'Help information for {self.name} task.') + logger.info('') + logger.info('Purpose: Create .csv from OSCAL catalog.') + logger.info('') + logger.info('Configuration flags sit under [task.oscal-catalog-to-csv]:') + text1 = ' input-file = ' + text2 = '(required) path of file to read the catalog.' + logger.info(text1 + text2) + text1 = ' output-dir = ' + text2 = '(required) path of directory to write the generated .csv file.' + logger.info(text1 + text2) + text1 = ' output-name = ' + text2 = '(optional) name of the generated .csv file [default is name of input file with .csv suffix].' + logger.info(text1 + text2) + text1 = ' output-overwrite = ' + text2 = '(optional) true [default] or false; replace existing output when true.' + logger.info(text1 + text2) + text1 = ' level = ' + text2 = f'(optional) one of: {level_control} or {level_statement} [default].' + logger.info(text1 + text2) + + def simulate(self) -> TaskOutcome: + """Provide a simulated outcome.""" + return TaskOutcome('simulated-success') + + def execute(self) -> TaskOutcome: + """Provide an actual outcome.""" + try: + return self._execute() + except Exception: + logger.info(traceback.format_exc()) + return TaskOutcome('failure') + + def _execute(self) -> TaskOutcome: + """Wrap the execute for exception handling.""" + # config processing + if not self._config: + logger.warning('config missing') + return TaskOutcome('failure') + # input + ifile = self._config.get('input-file') + if not ifile: + logger.warning('input-file missing') + return TaskOutcome('failure') + ipth = pathlib.Path(ifile) + # overwrite + self._overwrite = self._config.getboolean('output-overwrite', True) + # output + odir = self._config.get('output-dir') + if not odir: + logger.warning('output-dir missing') + return TaskOutcome('failure') + opth = pathlib.Path(odir) + opth.mkdir(exist_ok=True, parents=True) + iname = ipth.name.split('.')[0] + oname = self._config.get('output-name', f'{iname}.csv') + opth = opth / oname + if not self._overwrite and opth.exists(): + logger.warning(f'output: {opth} already exists') + return TaskOutcome('failure') + csv_helper = CsvHelper(opth) + # level + level = self._config.get('level', level_default) + if level not in level_list: + logger.warning(f'level: {level} unknown') + return TaskOutcome('failure') + # helper + catalog_helper = CatalogHelper(ipth) + # process + content_manager = ContentManager(catalog_helper) + rows = content_manager.get_content(level) + # write + csv_helper.write(rows) + logger.info(f'output-file: {opth}') + # success + return TaskOutcome('success') From 3bd53ff370cece77fc78082dbc04304af12c6647 Mon Sep 17 00:00:00 2001 From: Jennifer Power Date: Fri, 16 Jun 2023 14:01:14 -0400 Subject: [PATCH 08/10] feat: add profile-inherit command (#1392) * test: adds testdata for profile init tests Signed-off-by: Jennifer Power * feat(cli): adds profile-seed command Adds profile-seed as author subcommand Adds profile-seed unit test Adds SSP testdata Closes #1388 Signed-off-by: Jennifer Power * chore: updates flag wording in profile.py Signed-off-by: Jennifer Power * test: adds test case for profile-seed Adds additional test case to check for ids output when all controls are filtered out Signed-off-by: Jennifer Power * test: updates description leveraged ssp testdata Signed-off-by: Jennifer Power * docs: updates author and tutorial docs with information on profile-seed command Signed-off-by: Jennifer Power * chore: updates command to profile-inherit in docs and code Signed-off-by: Jennifer Power * feat: adds excluded controls to the profile-inherit generated profile Signed-off-by: Jennifer Power * docs: adds JSON example of profile-inherit import to website docs Signed-off-by: Jennifer Power * chore: adds PR feedback on styling Signed-off-by: Jennifer Power --------- Signed-off-by: Jennifer Power --- docs/trestle_author.md | 15 + .../ssp_profile_catalog_authoring.md | 46 +++ .../ssp_profile_options.docx | Bin 23090 -> 17256 bytes .../trestle_ssp_author_options.png | Bin 44581 -> 75117 bytes tests/data/json/leveraged_ssp.json | 343 ++++++++++++++++++ tests/data/json/leveraged_ssp_readme.md | 7 + tests/data/json/simple_test_profile_less.json | 196 ++++++++++ tests/data/json/simple_test_profile_more.json | 198 ++++++++++ .../data/json/simple_test_profile_single.json | 129 +++++++ tests/test_utils.py | 23 ++ .../core/commands/author/profile_test.py | 101 +++++- trestle/core/commands/author/command.py | 3 +- trestle/core/commands/author/profile.py | 201 +++++++++- 13 files changed, 1259 insertions(+), 3 deletions(-) create mode 100644 tests/data/json/leveraged_ssp.json create mode 100644 tests/data/json/leveraged_ssp_readme.md create mode 100644 tests/data/json/simple_test_profile_less.json create mode 100644 tests/data/json/simple_test_profile_more.json create mode 100644 tests/data/json/simple_test_profile_single.json diff --git a/docs/trestle_author.md b/docs/trestle_author.md index 0b2a7bb13..6c5c9bcb8 100644 --- a/docs/trestle_author.md +++ b/docs/trestle_author.md @@ -591,6 +591,21 @@ CLI evocation: The `profile` author commands allow you to edit additions made by a profile to its imported controls that end up in the final resolved profile catalog. Only the additions may be edited or added to the generated markdown control files - and those additions can then be assembled into a new version of the original profile, with those additions. For more details on its usage please see [the profile authoring tutorial](https://ibm.github.io/compliance-trestle/tutorials/ssp_profile_catalog_authoring/ssp_profile_catalog_authoring). +### Profile generation with inheritance + +CLI evocation: + +> trestle author profile-inherit + +The `profile-inherit` sub-command takes a given parent profile and filters its imported controls based inherited controls from a given SSP. + +The leveraged SSP is evaluated based on whether provided and responsibility statements for all `by-component` fields are set for each applicable control, as well as the implementation status. +All components must have exported provided statements, no exported responsibility statements, and an implementation status of `implemented` in order for a control to be filtered from the output profile (i.e. controls delta profile). + +As with the other related author commands, if an existing destination file already exists, it is not updated if no changes would be made. + +For more details on its usage please see [the ssp-filter tutorial](https://ibm.github.io/compliance-trestle/tutorials/ssp_profile_catalog_authoring/ssp_profile_catalog_authoring). + ### SSP authoring CLI evocation: diff --git a/docs/tutorials/ssp_profile_catalog_authoring/ssp_profile_catalog_authoring.md b/docs/tutorials/ssp_profile_catalog_authoring/ssp_profile_catalog_authoring.md index 4ce1ef4e3..38a11b252 100644 --- a/docs/tutorials/ssp_profile_catalog_authoring/ssp_profile_catalog_authoring.md +++ b/docs/tutorials/ssp_profile_catalog_authoring/ssp_profile_catalog_authoring.md @@ -27,6 +27,7 @@ The author commands are: 1. `catalog-generate` converts a control Catalog to individual controls in markdown format for addition or editing of guidance prose and parameters, with parameters stored in a yaml header at the top of the markdown file. `catalog-assemble` then gathers the prose and parameters and updates the controls in the Catalog to make a new OSCAL Catalog. 1. `profile-generate` takes a given Profile and converts the controls represented by its resolved profile catalog to individual controls in markdown format, with sections corresponding to the content that the Profile adds to the Catalog, along with both the current values of parameters in the resolved profile catalog - and the values that are being modified by the given profile's SetParameters. The user may edit the content or add more, and `profile-assemble` then gathers the updated content and creates a new OSCAL Profile that includes those changes. 1. `profile-resolve` is special as an authoring tool because it does not involve markdown and instead it simply creates a JSON resolved profile catalog from a specified JSON profile in the trestle directory. There are options to specify whether or not parameters get replace in the control prose or not, along with any special brackets that might be desired to indicate the parameters embedded in the prose. +1. `profile-inherit` takes a given parent profile and filters its contents based on the inherited controls included in a given ssp to be include in the final profile. 1. `component-generate` takes a given ComponentDefinition file and represents all the controls in markdown in separate directories for each Component in the file. This allows editing of the prose on a per-component basis. `component-assemble` then assembles the markdown for all controls in all component directories into a new, or the same, ComponentDefinition file. 1. `ssp-generate` takes a given Profile and an optional list of component-definitions, and represents the individual controls as markdown files with sections that prompt for prose regarding the implementation response for items in the statement of the control, with separate response sections for each component. `ssp-assemble` then gathers the response sections and creates an OSCAL System Security Plan comprising the resolved profile catalog and the implementation responses for each component. The list of component-definitions is optional, but without them the SSP will only have one component: `This System`. Rules, parameters and status associated with the implemented requirements are stored in the SetParameters and Properties of the components in the component definitions and represented in the markdown, allowing changes to be made to the parameter values and status. These edits are then included in the assembled SSP. Note that the rules themselves may not be edited and strictly correspond to what is in the component definitions. 1. `ssp-filter` takes a given ssp and filters its contents based on the controls included in a provided profile, or in a list of components to be included in the final ssp. @@ -524,6 +525,51 @@ Similar options apply to the `jinja` authoring commands.
+trestle author profile-inherit + +The `trestle author profile-inherit` command is different from the `generate/assemble` commands because it doesn't involve markdown and instead +it takes an parent profile and ssp and creates child profile in `JSON` format. + +When utilizing a process with leveraged authorizations, use the command `trestle author profile-inherit` to create a profile with initial content using a parent profile and SSP with inheritable controls. The provided and responsibility statements for all `by-component` fields, as well as the implementation status, will be used to evaluate the leveraged SSP. +To be filtered from the output profile (i.e. controls delta profile), all components must have exported provided statements, no exported responsibility statements, and an implementation status of `implemented`. + +The filter command is invoked as: + +`trestle author profile-inherit --profile my_parent --ssp my_leveraged_ssp --output controls_delta_profile` + +Both the parent profile and the SSP must be present in the trestle workspace. This command produces a new workspace profile that imports the parent profile and filters the inherited controls from the SSP using the `exclude-controls` and `include-controls` fields in the profile import. + +
+ +Example imports generated from profile-inherit + +```json + "imports": [ + { + "href": "trestle://profiles/controls_delta/profile.json", + "include-controls": [ + { + "with-ids": [ + "ac-2" + ] + } + ], + "exclude-controls": [ + { + "with-ids": [ + "ac-1" + ] + } + ] + } + ] +``` + +
+
+ +
+ trestle author component-generate and component-assemble The `trestle author component-generate` command takes a JSON ComponentDefinition file and creates markdown for its controls in separate directories for each of the DefinedComponents in the file. This allows specifying the implementation response and status for each component separately in separate markdown files for a control. In addition, the markdown captures Rules in the control that specify descriptions and parameter values that apply to the expected responses. diff --git a/docs/tutorials/ssp_profile_catalog_authoring/ssp_profile_options.docx b/docs/tutorials/ssp_profile_catalog_authoring/ssp_profile_options.docx index 854dcd19d32ae6ac5b627af3185e6be999a66cfd..dcbf91f9cdd3a6ea80cd64784bfe52ec70bfb4cd 100644 GIT binary patch literal 17256 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 Z9S^b+ps$hv0Dyk|1Ag^5$?&}${eLeei+un9 literal 23090 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 diff --git a/docs/tutorials/ssp_profile_catalog_authoring/trestle_ssp_author_options.png b/docs/tutorials/ssp_profile_catalog_authoring/trestle_ssp_author_options.png index 91dbf3a867165a707f53561bd32ce5999aaf491f..d086127757cf5d13711499fcedafa731955989cc 100644 GIT binary patch literal 75117 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 literal 44581 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 diff --git a/tests/data/json/leveraged_ssp.json b/tests/data/json/leveraged_ssp.json new file mode 100644 index 000000000..c55392fcb --- /dev/null +++ b/tests/data/json/leveraged_ssp.json @@ -0,0 +1,343 @@ +{ + "system-security-plan": { + "uuid": "A0000000-0000-4000-8000-000000000036", + "metadata": { + "title": "Example Leveraged System Security Plan", + "last-modified": "2021-06-08T13:57:35.068496-04:00", + "version": "0.1", + "oscal-version": "1.0.0", + "roles": [ + { + "id": "admin", + "title": "Administrator" + }, + { + "id": "customer", + "title": "External Customer" + }, + { + "id": "poc-for-customers", + "title": "Internal POC for Customers" + } + ], + "parties": [ + { + "uuid": "11111111-0000-4000-9000-100000000001", + "type": "person" + } + ] + }, + "import-profile": { + "href": "trestle://profiles/simple_test_profile/profile.json" + }, + "system-characteristics": { + "system-ids": [ + { + "id": "csp_iaas_system" + } + ], + "system-name": "Leveraged IaaS System", + "description": "An example of three customers leveraging an authorized SaaS, which is running on an authorized IaaS.\n\n```\n\nCust-A Cust-B Cust-C\n | | |\n +---------+---------+\n |\n +-------------------+\n | Leveraging SaaS |\n +-------------------+\n |\n |\n +-------------------+\n | Leveraged IaaS |\n | this file |\n +-------------------+\n \n```\n\nIn this example, the IaaS SSP specifies customer responsibilities for certain controls.\n\nThe SaaS must address these for the control to be fully satisfied.\n\nThe SaaS provider may either implement these directly or pass the responsibility on to their customers. Both may be necessary.\n\nFor any given control, the Leveraged IaaS SSP must describe:\n\n1. HOW the IaaS is directly satisfying the control\n1. WHAT responsibilities are left for the Leveraging SaaS (or their customers) to implement.\n\n\nFor any given control, the Leveraging SaaS SSP must describe:\n\n1. WHAT is being inherited from the underlying IaaS\n1. HOW the SaaS is directly satisfying the control.\n1. WHAT responsibilities are left for the SaaS customers to implement. (The SaaS customers are Cust-A, B and C)\n", + "security-sensitivity-level": "low", + "system-information": { + "information-types": [ + { + "title": "System and Network Monitoring", + "description": "This IaaS system handles information pertaining to audit events.", + "categorizations": [ + { + "system": "https://doi.org/10.6028/NIST.SP.800-60v2r1", + "information-type-ids": [ + "C.3.5.8" + ] + } + ], + "confidentiality-impact": { + "base": "fips-199-moderate", + "selected": "fips-199-low", + "adjustment-justification": "This impact has been adjusted to low as an example of how to perform this type of adjustment." + }, + "integrity-impact": { + "base": "fips-199-moderate", + "selected": "fips-199-low", + "adjustment-justification": "This impact has been adjusted to low as an example of how to perform this type of adjustment." + }, + "availability-impact": { + "base": "fips-199-moderate", + "selected": "fips-199-low", + "adjustment-justification": "This impact has been adjusted to low as an example of how to perform this type of adjustment." + } + } + ] + }, + "security-impact-level": { + "security-objective-confidentiality": "fips-199-low", + "security-objective-integrity": "fips-199-low", + "security-objective-availability": "fips-199-low" + }, + "status": { + "state": "operational" + }, + "authorization-boundary": { + "description": "The hardware and software supporting the virtualized infrastructure supporting the IaaS." + }, + "remarks": "Most system-characteristics content does not support the example, and is included to meet the minimum SSP syntax requirements." + }, + "system-implementation": { + "users": [ + { + "uuid": "11111111-0000-4000-9000-200000000001", + "role-ids": [ + "admin" + ], + "authorized-privileges": [ + { + "title": "Administrator", + "functions-performed": [ + "Manages the components within the IaaS." + ] + } + ] + } + ], + "components": [ + { + "uuid": "cfbc1d9d-e772-47a4-aed5-1b902339eab2", + "type": "this-system", + "title": "This System", + "description": "The system described by this SSP.", + "status": { + "state": "operational" + } + }, + { + "uuid": "11111111-0000-4000-9001-000000000001", + "type": "this-system", + "title": "This System", + "description": "This Leveraged IaaS.\n\nThe entire system as depicted in the system authorization boundary", + "status": { + "state": "operational" + } + }, + { + "uuid": "11111111-0000-4000-9001-000000000002", + "type": "software", + "title": "Application", + "description": "An application within the IaaS, exposed to SaaS customers and their downstream customers.\n\nThis Leveraged IaaS maintains aspects of the application.\n\nThe Leveraging SaaS maintains aspects of their assigned portion of the application.\n\nThe customers of the Leveraging SaaS maintain aspects of their sub-assigned portions of the application.", + "props": [ + { + "name": "implementation-point", + "value": "system" + } + ], + "status": { + "state": "operational" + }, + "responsible-roles": [ + { + "role-id": "admin", + "party-uuids": [ + "11111111-0000-4000-9000-100000000001" + ] + } + ] + } + ] + }, + "control-implementation": { + "description": "This is a collection of control responses.", + "implemented-requirements": [ + { + "uuid": "11111111-0000-4000-9009-001000000000", + "control-id": "ac-1", + "statements": [ + { + "statement-id": "ac-1_stmt.a", + "uuid": "11111111-0000-4000-9009-001001000000", + "by-components": [ + { + "component-uuid": "11111111-0000-4000-9001-000000000001", + "uuid": "11111111-0000-4000-9009-001001001001", + "description": "Response for the \\\"This System\\\" component.\n\nOverall description of how \\\"This System\\\" satisfies AC-1, Part a.\n\nResponse for the \\\"This System\\\" component.\n\nOverall description of how \\\"This System\\\" satisfies AC-2, Part a.\n\nResponse for the \\\"This System\\\" component.\n\nOverall description of how \\\"This System\\\" satisfies AC-2, Part a.\n\nResponse for the \\\"This System\\\" component.\n\nOverall description of how \\\"This System\\\" satisfies AC-2, Part a.", + "implementation-status": { + "state": "implemented" + }, + "export": { + "description": "Optional description about what is being exported.", + "provided": [ + { + "uuid": "11111111-0000-4000-9009-001002002001", + "description": "Consumer-appropriate description of what may be inherited.\n\nIn the context of the application component in satisfaction of AC-1, part a.", + "responsible-roles": [ + { + "role-id": "poc-for-customers" + } + ] + } + ] + } + }, + { + "component-uuid": "11111111-0000-4000-9001-000000000002", + "uuid": "11111111-0000-4000-9009-001001002005", + "description": "Describes how the application satisfies AC-1, Part a.", + "implementation-status": { + "state": "implemented" + }, + "export": { + "description": "Optional description about what is being exported.", + "provided": [ + { + "uuid": "11111111-0000-4000-9009-001001002006", + "description": "Consumer-appropriate description of what may be inherited.\n\nIn the context of the application component in satisfaction of AC-1, part a.", + "responsible-roles": [ + { + "role-id": "poc-for-customers" + } + ] + } + ] + } + } + ] + } + ] + }, + { + "uuid": "11111111-0000-4000-9009-002000000000", + "control-id": "ac-2", + "set-parameters": [ + { + "param-id": "ac-2_prm_1", + "values": [ + "privileged and non-privileged" + ] + } + ], + "statements": [ + { + "statement-id": "ac-2_stmt.a", + "uuid": "11111111-0000-4000-9009-002001000000", + "by-components": [ + { + "component-uuid": "11111111-0000-4000-9001-000000000001", + "uuid": "11111111-0000-4000-9009-002001001000", + "description": "Response for the \\\"This System\\\" component.\n\nOverall description of how \\\"This System\\\" satisfies AC-2, Part a.\n\nResponse for the \\\"This System\\\" component.\n\nOverall description of how \\\"This System\\\" satisfies AC-2, Part a.\n\nResponse for the \\\"This System\\\" component.\n\nOverall description of how \\\"This System\\\" satisfies AC-2, Part a.\n\nResponse for the \\\"This System\\\" component.\n\nOverall description of how \\\"This System\\\" satisfies AC-2, Part a.", + "export": { + "description": "Optional description about what is being exported.", + "responsibilities": [ + { + "uuid": "11111111-0000-4000-9009-002001001001", + "description": "Leveraging system's responsibilities with respect to inheriting this capability.\n\nIn the context of the application component in satisfaction of AC-2, part a.", + "responsible-roles": [ + { + "role-id": "customer" + } + ] + } + ] + } + }, + { + "component-uuid": "11111111-0000-4000-9001-000000000002", + "uuid": "11111111-0000-4000-9009-002001002000", + "description": "Describes how the application satisfies AC-2, Part a.", + "export": { + "description": "Optional description about what is being exported.", + "provided": [ + { + "uuid": "11111111-0000-4000-9009-002001002001", + "description": "Consumer-appropriate description of what may be inherited.\n\nIn the context of the application component in satisfaction of AC-2, part a.", + "responsible-roles": [ + { + "role-id": "poc-for-customers" + } + ] + } + ], + "responsibilities": [ + { + "uuid": "11111111-0000-4000-9009-002001002002", + "provided-uuid": "11111111-0000-4000-9009-002001002001", + "description": "Leveraging system's responsibilities with respect to inheriting this capability.\n\nIn the context of the application component in satisfaction of AC-2, part a.", + "responsible-roles": [ + { + "role-id": "customer" + } + ] + } + ] + } + } + ], + "remarks": "a. Identifies and selects the following types of information system accounts to support organizational missions/business functions: [Assignment: privileged and non-privileged];" + } + ], + "remarks": "The organization:\n\na. Identifies and selects the following types of information system accounts to support organizational missions/business functions: [Assignment: organization-defined information system account types];\n\nb. Assigns account managers for information system accounts;\n\nc. Establishes conditions for group and role membership;\n\nd. through j. omitted" + }, + { + "uuid": "11111111-0000-4000-9009-003000000000", + "control-id": "ac-2.1", + "by-components": [ + { + "component-uuid": "11111111-0000-4000-9001-000000000001", + "uuid": "11111111-0000-4000-9009-001002001001", + "description": "Response for the \\\"This System\\\" component.\n\nOverall description of how \\\"This System\\\" satisfies AC-2, Part a.\n\nResponse for the \\\"This System\\\" component.\n\nOverall description of how \\\"This System\\\" satisfies AC-2.1.\n\nResponse for the \\\"This System\\\" component.\n\nOverall description of how \\\"This System\\\" satisfies AC-2, Part a.\n\nResponse for the \\\"This System\\\" component.\n\nOverall description of how \\\"This System\\\" satisfies AC-2, Part a.", + "implementation-status": { + "state": "planned" + }, + "export": { + "description": "Optional description about what is being exported.", + "provided": [ + { + "uuid": "11111111-0000-4000-9009-001001002001", + "description": "Consumer-appropriate description of what may be inherited.\n\nIn the context of the application component in satisfaction of AC-2.1.", + "responsible-roles": [ + { + "role-id": "poc-for-customers" + } + ] + } + ] + } + }, + { + "component-uuid": "11111111-0000-4000-9001-000000000002", + "uuid": "11111111-0000-4000-9009-001001002003", + "description": "Describes how the application satisfies AC-2, Part a.", + "implementation-status": { + "state": "implemented" + }, + "export": { + "description": "Optional description about what is being exported.", + "provided": [ + { + "uuid": "11111111-0000-4000-9009-001001002004", + "description": "Consumer-appropriate description of what may be inherited.\n\nIn the context of the application component in satisfaction of AC-2.1.", + "responsible-roles": [ + { + "role-id": "poc-for-customers" + } + ] + } + ] + } + } + ] + } + ] + }, + "back-matter": { + "resources": [ + { + "uuid": "11111111-0000-4000-9999-000000000001", + "rlinks": [ + { + "href": "./attachments/IaaS_ac_proc.docx" + } + ] + } + ] + } + } +} diff --git a/tests/data/json/leveraged_ssp_readme.md b/tests/data/json/leveraged_ssp_readme.md new file mode 100644 index 000000000..0e58eb832 --- /dev/null +++ b/tests/data/json/leveraged_ssp_readme.md @@ -0,0 +1,7 @@ +leveraged_ssp: + +> loads from simple test profile + +- ac-1 is fully implemented by the provider +- ac-2 is shared between the provider and customer +- ac-2.1 has provided export statements from the provider, but is not fully implemented. diff --git a/tests/data/json/simple_test_profile_less.json b/tests/data/json/simple_test_profile_less.json new file mode 100644 index 000000000..22b600c89 --- /dev/null +++ b/tests/data/json/simple_test_profile_less.json @@ -0,0 +1,196 @@ +{ + "profile": { + "uuid": "A0000000-0000-4000-8000-000000000052", + "metadata": { + "title": "Trestle test profile", + "last-modified": "2021-01-01T00:00:00.000+00:00", + "version": "2021-01-01", + "oscal-version": "1.0.0" + }, + "imports": [ + { + "href": "#657e15f4-bee9-45fb-a43d-44d7f7f2abfa", + "include-controls": [ + { + "with-ids": [ + "ac-1", + "ac-2" + ] + } + ] + } + ], + "merge": { + "combine": { + "method": "use-first" + }, + "as-is": true + }, + "modify": { + "set_parameters": [ + { + "param_id": "ac-1_prm_1", + "props": [ + { + "name": "display-name", + "ns": "https://ibm.github.io/compliance-trestle/schemas/oscal", + "value": "Pretty ac-1 prm 1" + } + ], + "label": "label from profile", + "values": [ + "all personnel" + ] + }, + { + "param_id": "ac-1_prm_2", + "props": [ + { + "name": "display-name", + "value": "Pretty ac-1 prm 2" + } + ], + "values": [ + "Organization-level", + "System-level" + ] + }, + { "param_id": "ac-1_prm_3", + "values": [ + "officer" + ] + }, + { "param_id": "ac-1_prm_4", + "values": [ + "weekly" + ] + }, + { "param_id": "ac-1_prm_5", + "values": [ + "all meetings" + ] + }, + { "param_id": "ac-1_prm_6", + "values": [ + "monthly" + ] + }, + { "param_id": "ac-2_prm_1", + "values": [ + "passwords" + ] + }, + { "param_id": "ac-2_prm_2", + "values": [ + "approvals" + ] + }, + { "param_id": "ac-2_prm_3", + "values": [ + "authorized personnel" + ] + }, + { "param_id": "ac-2_prm_4", + "values": [ + "guidelines" + ] + }, + { "param_id": "ac-2_prm_5", + "values": [ + "personnel" + ] + }, + { "param_id": "ac-2_prm_6", + "values": [ + "one week" + ] + }, + { "param_id": "ac-2_prm_7", + "values": [ + "one day" + ] + }, + { "param_id": "ac-2_prm_8", + "values": [ + "one hour" + ] + }, + { "param_id": "ac-2_prm_9", + "values": [ + "special needs" + ] + } + ], + "alters": [ + { + "control_id": "ac-1", + "adds": [ + { + "position": "after", + "by_id": "ac-1_smt", + "parts": [ + { + "id": "ac-1_implgdn", + "name": "implgdn", + "prose": "Do it carefully." + }, + { + "id": "ac-1_expevid", + "name": "expevid", + "prose": "Detailed logs." + } + ] + }, + { + "position": "ending", + "props": [ + { + "name": "prop_with_ns", + "value": "prop with ns", + "ns": "https://my_namespace" + }, + { + "name": "prop_with_no_ns", + "value": "prop with no ns" + } + ] + } + ] + }, + { + "control_id": "ac-2", + "adds": [ + { + "position": "after", + "by_id": "ac-2_smt", + "parts": [ + { + "id": "ac-2_implgdn", + "name": "implgdn", + "prose": "Maintain compliance" + }, + { + "id": "ac-2_expevid", + "name": "expevid", + "prose": "Daily logs." + } + ] + } + ] + } + ] + }, + "back_matter": { + "resources": [ + { + "uuid": "657e15f4-bee9-45fb-a43d-44d7f7f2abfa", + "rlinks": [ + { + "href": "trestle://catalogs/nist_cat/catalog.json" + } + ] + } + ] + } + } +} diff --git a/tests/data/json/simple_test_profile_more.json b/tests/data/json/simple_test_profile_more.json new file mode 100644 index 000000000..367ccd26f --- /dev/null +++ b/tests/data/json/simple_test_profile_more.json @@ -0,0 +1,198 @@ +{ + "profile": { + "uuid": "A0000000-0000-4000-8000-000000000051", + "metadata": { + "title": "Trestle test profile", + "last-modified": "2021-01-01T00:00:00.000+00:00", + "version": "2021-01-01", + "oscal-version": "1.0.0" + }, + "imports": [ + { + "href": "#657e15f4-bee9-45fb-a43d-44d7f7f2abfa", + "include-controls": [ + { + "with-ids": [ + "ac-1", + "ac-2", + "ac-2.1", + "ac-2.2" + ] + } + ] + } + ], + "merge": { + "combine": { + "method": "use-first" + }, + "as-is": true + }, + "modify": { + "set_parameters": [ + { + "param_id": "ac-1_prm_1", + "props": [ + { + "name": "display-name", + "ns": "https://ibm.github.io/compliance-trestle/schemas/oscal", + "value": "Pretty ac-1 prm 1" + } + ], + "label": "label from profile", + "values": [ + "all personnel" + ] + }, + { + "param_id": "ac-1_prm_2", + "props": [ + { + "name": "display-name", + "value": "Pretty ac-1 prm 2" + } + ], + "values": [ + "Organization-level", + "System-level" + ] + }, + { "param_id": "ac-1_prm_3", + "values": [ + "officer" + ] + }, + { "param_id": "ac-1_prm_4", + "values": [ + "weekly" + ] + }, + { "param_id": "ac-1_prm_5", + "values": [ + "all meetings" + ] + }, + { "param_id": "ac-1_prm_6", + "values": [ + "monthly" + ] + }, + { "param_id": "ac-2_prm_1", + "values": [ + "passwords" + ] + }, + { "param_id": "ac-2_prm_2", + "values": [ + "approvals" + ] + }, + { "param_id": "ac-2_prm_3", + "values": [ + "authorized personnel" + ] + }, + { "param_id": "ac-2_prm_4", + "values": [ + "guidelines" + ] + }, + { "param_id": "ac-2_prm_5", + "values": [ + "personnel" + ] + }, + { "param_id": "ac-2_prm_6", + "values": [ + "one week" + ] + }, + { "param_id": "ac-2_prm_7", + "values": [ + "one day" + ] + }, + { "param_id": "ac-2_prm_8", + "values": [ + "one hour" + ] + }, + { "param_id": "ac-2_prm_9", + "values": [ + "special needs" + ] + } + ], + "alters": [ + { + "control_id": "ac-1", + "adds": [ + { + "position": "after", + "by_id": "ac-1_smt", + "parts": [ + { + "id": "ac-1_implgdn", + "name": "implgdn", + "prose": "Do it carefully." + }, + { + "id": "ac-1_expevid", + "name": "expevid", + "prose": "Detailed logs." + } + ] + }, + { + "position": "ending", + "props": [ + { + "name": "prop_with_ns", + "value": "prop with ns", + "ns": "https://my_namespace" + }, + { + "name": "prop_with_no_ns", + "value": "prop with no ns" + } + ] + } + ] + }, + { + "control_id": "ac-2", + "adds": [ + { + "position": "after", + "by_id": "ac-2_smt", + "parts": [ + { + "id": "ac-2_implgdn", + "name": "implgdn", + "prose": "Maintain compliance" + }, + { + "id": "ac-2_expevid", + "name": "expevid", + "prose": "Daily logs." + } + ] + } + ] + } + ] + }, + "back_matter": { + "resources": [ + { + "uuid": "657e15f4-bee9-45fb-a43d-44d7f7f2abfa", + "rlinks": [ + { + "href": "trestle://catalogs/nist_cat/catalog.json" + } + ] + } + ] + } + } +} diff --git a/tests/data/json/simple_test_profile_single.json b/tests/data/json/simple_test_profile_single.json new file mode 100644 index 000000000..ac933b5e1 --- /dev/null +++ b/tests/data/json/simple_test_profile_single.json @@ -0,0 +1,129 @@ +{ + "profile": { + "uuid": "A0000000-0000-4000-8000-000000000052", + "metadata": { + "title": "Trestle test profile", + "last-modified": "2021-01-01T00:00:00.000+00:00", + "version": "2021-01-01", + "oscal-version": "1.0.0" + }, + "imports": [ + { + "href": "#657e15f4-bee9-45fb-a43d-44d7f7f2abfa", + "include-controls": [ + { + "with-ids": [ + "ac-1" + ] + } + ] + } + ], + "merge": { + "combine": { + "method": "use-first" + }, + "as-is": true + }, + "modify": { + "set_parameters": [ + { + "param_id": "ac-1_prm_1", + "props": [ + { + "name": "display-name", + "ns": "https://ibm.github.io/compliance-trestle/schemas/oscal", + "value": "Pretty ac-1 prm 1" + } + ], + "label": "label from profile", + "values": [ + "all personnel" + ] + }, + { + "param_id": "ac-1_prm_2", + "props": [ + { + "name": "display-name", + "value": "Pretty ac-1 prm 2" + } + ], + "values": [ + "Organization-level", + "System-level" + ] + }, + { "param_id": "ac-1_prm_3", + "values": [ + "officer" + ] + }, + { "param_id": "ac-1_prm_4", + "values": [ + "weekly" + ] + }, + { "param_id": "ac-1_prm_5", + "values": [ + "all meetings" + ] + }, + { "param_id": "ac-1_prm_6", + "values": [ + "monthly" + ] + } + ], + "alters": [ + { + "control_id": "ac-1", + "adds": [ + { + "position": "after", + "by_id": "ac-1_smt", + "parts": [ + { + "id": "ac-1_implgdn", + "name": "implgdn", + "prose": "Do it carefully." + }, + { + "id": "ac-1_expevid", + "name": "expevid", + "prose": "Detailed logs." + } + ] + }, + { + "position": "ending", + "props": [ + { + "name": "prop_with_ns", + "value": "prop with ns", + "ns": "https://my_namespace" + }, + { + "name": "prop_with_no_ns", + "value": "prop with no ns" + } + ] + } + ] + } + ] + }, + "back_matter": { + "resources": [ + { + "uuid": "657e15f4-bee9-45fb-a43d-44d7f7f2abfa", + "rlinks": [ + { + "href": "trestle://catalogs/nist_cat/catalog.json" + } + ] + } + ] + } + } +} diff --git a/tests/test_utils.py b/tests/test_utils.py index d8b251190..d167de1db 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -45,6 +45,7 @@ from trestle.oscal import common from trestle.oscal import component as comp from trestle.oscal import profile as prof +from trestle.oscal import ssp if file_utils.is_windows(): # pragma: no cover import win32api @@ -406,6 +407,28 @@ def setup_for_multi_profile(trestle_root: pathlib.Path, big_profile: bool, impor assert HrefCmd.change_import_href(trestle_root, main_profile_name, new_href, 0) == 0 +def setup_for_inherit( + tmp_trestle_dir: pathlib.Path, prof_name: str, output_name: str, ssp_name: str +) -> argparse.Namespace: + """Create the ssp and parent profile for inherit commands.""" + load_from_json(tmp_trestle_dir, 'simplified_nist_catalog', 'nist_cat', cat.Catalog) + if prof_name: + load_from_json(tmp_trestle_dir, prof_name, prof_name, prof.Profile) + if ssp_name: + load_from_json(tmp_trestle_dir, ssp_name, ssp_name, ssp.SystemSecurityPlan) + + args = argparse.Namespace( + trestle_root=tmp_trestle_dir, + profile=prof_name, + output=output_name, + ssp=ssp_name, + version=None, + verbose=0, + ) + + return args + + def load_from_json( tmp_trestle_dir: pathlib.Path, file_prefix: str, model_name: str, model_type: OscalBaseModel ) -> None: diff --git a/tests/trestle/core/commands/author/profile_test.py b/tests/trestle/core/commands/author/profile_test.py index 2d9318c53..676719473 100644 --- a/tests/trestle/core/commands/author/profile_test.py +++ b/tests/trestle/core/commands/author/profile_test.py @@ -37,7 +37,7 @@ from trestle.common.list_utils import comma_colon_sep_to_dict, comma_sep_to_list from trestle.common.model_utils import ModelUtils from trestle.core.catalog.catalog_interface import CatalogInterface -from trestle.core.commands.author.profile import ProfileAssemble, ProfileGenerate +from trestle.core.commands.author.profile import ProfileAssemble, ProfileGenerate, ProfileInherit from trestle.core.control_interface import ControlInterface from trestle.core.markdown.docs_markdown_node import DocsMarkdownNode from trestle.core.markdown.markdown_api import MarkdownAPI @@ -1016,3 +1016,102 @@ def test_profile_resolve_failures(tmp_trestle_dir: pathlib.Path, monkeypatch: Mo test_utils.execute_command_and_assert(core_command + '-lp prefix', 1, monkeypatch) test_utils.execute_command_and_assert(core_command + '-vap prefix', 1, monkeypatch) test_utils.execute_command_and_assert(core_command + '-sl -vap prefix', 1, monkeypatch) + + +def test_profile_inherit(tmp_trestle_dir: pathlib.Path): + """Test profile initialization and seeding for various use cases.""" + output_profile = 'my_profile' + excluded = prof.WithId(__root__='ac-1') + + # Test with a profile and ssp that has all controls with exported information + args = test_utils.setup_for_inherit(tmp_trestle_dir, 'simple_test_profile', output_profile, 'leveraged_ssp') + prof_inherit = ProfileInherit() + assert prof_inherit._run(args) == 0 + + result_prof, _ = ModelUtils.load_model_for_class( + tmp_trestle_dir, + output_profile, + prof.Profile, + FileContentType.JSON + ) + + assert result_prof.imports[0].href == 'trestle://profiles/simple_test_profile/profile.json' + assert len(result_prof.imports[0].include_controls[0].with_ids) == 2 + assert len(result_prof.imports[0].exclude_controls[0].with_ids) == 1 + assert result_prof.imports[0].exclude_controls[0].with_ids[0] == excluded + + # Test with a profile that has more controls than the ssp + args = test_utils.setup_for_inherit(tmp_trestle_dir, 'simple_test_profile_more', output_profile, 'leveraged_ssp') + prof_inherit = ProfileInherit() + assert prof_inherit._run(args) == 0 + + result_prof, _ = ModelUtils.load_model_for_class( + tmp_trestle_dir, + output_profile, + prof.Profile, + FileContentType.JSON + ) + + assert result_prof.imports[0].href == 'trestle://profiles/simple_test_profile_more/profile.json' + assert len(result_prof.imports[0].include_controls[0].with_ids) == 3 + assert len(result_prof.imports[0].exclude_controls[0].with_ids) == 1 + assert result_prof.imports[0].exclude_controls[0].with_ids[0] == excluded + + # Test with a profile that has less controls than the ssp + args = test_utils.setup_for_inherit(tmp_trestle_dir, 'simple_test_profile_less', output_profile, 'leveraged_ssp') + prof_inherit = ProfileInherit() + assert prof_inherit._run(args) == 0 + + result_prof, _ = ModelUtils.load_model_for_class( + tmp_trestle_dir, + output_profile, + prof.Profile, + FileContentType.JSON + ) + + assert result_prof.imports[0].href == 'trestle://profiles/simple_test_profile_less/profile.json' + assert len(result_prof.imports[0].include_controls[0].with_ids) == 1 + assert len(result_prof.imports[0].exclude_controls[0].with_ids) == 1 + assert result_prof.imports[0].exclude_controls[0].with_ids[0] == excluded + + # Test with a profile that has all controls filtered out + args = test_utils.setup_for_inherit(tmp_trestle_dir, 'simple_test_profile_single', output_profile, 'leveraged_ssp') + prof_inherit = ProfileInherit() + assert prof_inherit._run(args) == 0 + + result_prof, _ = ModelUtils.load_model_for_class( + tmp_trestle_dir, + output_profile, + prof.Profile, + FileContentType.JSON + ) + + assert result_prof.imports[0].href == 'trestle://profiles/simple_test_profile_single/profile.json' + assert len(result_prof.imports[0].include_controls[0].with_ids) == 0 + assert len(result_prof.imports[0].exclude_controls[0].with_ids) == 1 + assert result_prof.imports[0].exclude_controls[0].with_ids[0] == excluded + + # Test with version set + args = test_utils.setup_for_inherit(tmp_trestle_dir, 'simple_test_profile_less', output_profile, 'leveraged_ssp') + prof_inherit = ProfileInherit() + args.version = '1.0.0' + assert prof_inherit._run(args) == 0 + + result_prof, _ = ModelUtils.load_model_for_class( + tmp_trestle_dir, + output_profile, + prof.Profile, + FileContentType.JSON + ) + + assert result_prof.metadata.version == '1.0.0' + + # Force a failure with non-existent profile + args.profile = 'bad_prof' + prof_inherit = ProfileInherit() + assert prof_inherit._run(args) == 1 + + # Force a failure with a cyclic dependency + args.output = args.profile + prof_inherit = ProfileInherit() + assert prof_inherit._run(args) == 2 diff --git a/trestle/core/commands/author/command.py b/trestle/core/commands/author/command.py index 0c602f8eb..240e00bcf 100644 --- a/trestle/core/commands/author/command.py +++ b/trestle/core/commands/author/command.py @@ -26,7 +26,7 @@ from trestle.core.commands.author.folders import Folders from trestle.core.commands.author.headers import Headers from trestle.core.commands.author.jinja import JinjaCmd -from trestle.core.commands.author.profile import ProfileAssemble, ProfileGenerate, ProfileResolve +from trestle.core.commands.author.profile import ProfileAssemble, ProfileGenerate, ProfileInherit, ProfileResolve from trestle.core.commands.author.ssp import SSPAssemble, SSPFilter, SSPGenerate from trestle.core.commands.command_docs import CommandPlusDocs @@ -49,6 +49,7 @@ class AuthorCmd(CommandPlusDocs): JinjaCmd, ProfileAssemble, ProfileGenerate, + ProfileInherit, ProfileResolve, SSPAssemble, SSPFilter, diff --git a/trestle/core/commands/author/profile.py b/trestle/core/commands/author/profile.py index 82194cef9..17b2494c6 100644 --- a/trestle/core/commands/author/profile.py +++ b/trestle/core/commands/author/profile.py @@ -14,17 +14,20 @@ """Author commands to generate profile as markdown and assemble to json after edit.""" import argparse +import copy import logging import pathlib import shutil -from typing import Any, Dict, List, Optional +from typing import Any, Dict, List, Optional, Set from ruamel.yaml import YAML from ruamel.yaml.error import YAMLError import trestle.common.const as const import trestle.common.log as log +import trestle.core.generators as gens import trestle.oscal.profile as prof +import trestle.oscal.ssp as ssp from trestle.common import file_utils from trestle.common.err import TrestleError, TrestleNotFoundError, handle_generic_command_exception from trestle.common.list_utils import as_filtered_list, as_list, comma_sep_to_list, comma_colon_sep_to_dict, deep_set, none_if_empty # noqa E501 @@ -536,3 +539,199 @@ def resolve_profile( ModelUtils.save_top_level_model(catalog, trestle_root, catalog_name, FileContentType.JSON) return CmdReturnCodes.SUCCESS.value + + +class ProfileInherit(AuthorCommonCommand): + """Generate and populate profile in JSON from a parent profile and leveraged ssp in the trestle workspace.""" + + name = 'profile-inherit' + + def _init_arguments(self) -> None: + ssp_help_str = 'Name of the leveraged ssp model in the trestle workspace' + self.add_argument('-s', '--ssp', help=ssp_help_str, required=True, type=str) + profile_help_str = 'Name of the parent profile model in the trestle workspace' + self.add_argument('-p', '--profile', help=profile_help_str, required=True, type=str) + output_help_str = 'Name of the output generated json Profile' + self.add_argument('-o', '--output', help=output_help_str, required=True, type=str) + self.add_argument('-vn', '--version', help=const.HELP_VERSION, required=False, type=str) + + def _run(self, args: argparse.Namespace) -> int: + try: + log.set_log_level_from_args(args) + trestle_root: pathlib.Path = args.trestle_root + + if args.profile: + if args.profile == args.output: + logger.warning(f'Output profile {args.output} cannot equal parent') + return CmdReturnCodes.INCORRECT_ARGS.value + + return self.initialize_profile( + trestle_root=trestle_root, + parent_prof_name=args.profile, + output_prof_name=args.output, + leveraged_ssp_name=args.ssp, + version=args.version + ) + except Exception as e: # pragma: no cover + return handle_generic_command_exception(e, logger, 'Profile generation failed') + + @staticmethod + def _is_inherited(all_comps: List[ssp.ByComponent]) -> bool: + # Fail fast by checking for any non-compliant components. + # Must contain provided export statements, no responsibility + # statements, and be implemented. + for comp in all_comps: + if comp.export is None: + return False + + if comp.export.responsibilities is not None: + return False + + if comp.export.provided is None: + return False + + if comp.implementation_status.state != const.STATUS_IMPLEMENTED: + return False + + return True + + @staticmethod + def update_profile_import( + orig_prof_import: prof.Import, leveraged_ssp: ssp.SystemSecurityPlan, catalog_api: CatalogAPI + ) -> None: + """Add controls to different sections of a profile import based on catalog and leveraged SSP. + + Args: + orig_prof_import: The original profile import that will have the control selection updated. + leveraged_ssp: SSP input for control filtering + catalog_api: Catalog API with access to controls that need to be filtered + + Returns: + None + """ + exclude_with_ids: Set[prof.withId] = set() + components_by_id: Dict(str, List[ssp.ByComponent]) = {} + + # Create dictionary containing all by-components by control for faster searching + for implemented_requirement in leveraged_ssp.control_implementation.implemented_requirements: + by_components: List[ssp.ByComponent] = [] + + if implemented_requirement.by_components: + by_components.extend(implemented_requirement.by_components) + if implemented_requirement.statements: + for stm in implemented_requirement.statements: + if stm.by_components: + by_components.extend(stm.by_components) + components_by_id[implemented_requirement.control_id] = none_if_empty(by_components) + + # Looping by controls in the catalog because the ids in the profile should + # be a subset of the catalog and not the ssp controls. + catalog_control_ids: Set[str] = set(catalog_api._catalog_interface.get_control_ids()) + for control_id in catalog_control_ids: + + if control_id not in components_by_id: + continue + + by_comps: Optional[List[ssp.ByComponent]] = components_by_id[control_id] + if by_comps is not None and ProfileInherit._is_inherited(by_comps): + exclude_with_ids.add(control_id) + + include_with_ids: Set[prof.withId] = catalog_control_ids - exclude_with_ids + + orig_prof_import.include_controls = [prof.SelectControlById(with_ids=sorted(include_with_ids))] + orig_prof_import.exclude_controls = [prof.SelectControlById(with_ids=sorted(exclude_with_ids))] + + def initialize_profile( + self, + trestle_root: pathlib.Path, + parent_prof_name: str, + output_prof_name: str, + leveraged_ssp_name: str, + version: Optional[str], + ) -> int: + """Initialize profile with controls from a parent profile, filtering by inherited controls. + + Args: + trestle_root: Root directory of the trestle workspace + parent_prof_name: Name of the parent profile in the trestle workspace + output_prof_name: Name of the output profile json file + leveraged_ssp_name: Name of the ssp in the trestle workspace for control filtering + version: Optional profile version + + Returns: + 0 on success, 1 on error + + Notes: + The profile model will either be updated or a new json profile created. This will overwrite + any import information on an exiting profile, but will preserve control modifications and parameters. + Allowing profile updates ensure that SSP export updates can be incorporated into an existing profile. All + controls from the original profile will exists and will be grouped by included and excluded controls based + on inheritance information. + """ + try: + result_profile: prof.Profile + existing_profile: Optional[prof.Profile] = None + + existing_profile_path = ModelUtils.get_model_path_for_name_and_class( + trestle_root, output_prof_name, prof.Profile + ) + + # If a profile exists at the output path, use that as a starting point for a new profile. + # else create a new sample profile. + if existing_profile_path is not None: + existing_profile, _ = load_validate_model_name(trestle_root, + output_prof_name, + prof.Profile, + FileContentType.JSON) + result_profile = copy.deepcopy(existing_profile) + else: + result_profile = gens.generate_sample_model(prof.Profile) + + parent_prof_path = ModelUtils.get_model_path_for_name_and_class( + trestle_root, parent_prof_name, prof.Profile + ) + if parent_prof_path is None: + raise TrestleNotFoundError( + f'Profile {parent_prof_name} does not exist. An existing profile must be provided.' + ) + + local_path = f'profiles/{parent_prof_name}/profile.json' + profile_import: prof.Import = gens.generate_sample_model(prof.Import) + profile_import.href = const.TRESTLE_HREF_HEADING + local_path + + leveraged_ssp: ssp.SystemSecurityPlan + try: + leveraged_ssp, _ = load_validate_model_name( + trestle_root, + leveraged_ssp_name, + ssp.SystemSecurityPlan, + FileContentType.JSON + ) + except TrestleNotFoundError as e: + raise TrestleError(f'SSP {leveraged_ssp_name} not found: {e}') + + prof_resolver = ProfileResolver() + catalog = prof_resolver.get_resolved_profile_catalog( + trestle_root, parent_prof_path, show_value_warnings=True + ) + catalog_api = CatalogAPI(catalog=catalog) + + # Sort controls based on what controls in the SSP have exported provided information with no + # customer responsibility + ProfileInherit.update_profile_import(profile_import, leveraged_ssp, catalog_api) + + result_profile.imports[0] = profile_import + + if version: + result_profile.metadata.version = version + + if ModelUtils.models_are_equivalent(existing_profile, result_profile): # type: ignore + logger.info('Profile is no different from existing version, so no update.') + return CmdReturnCodes.SUCCESS.value + + ModelUtils.update_last_modified(result_profile) # type: ignore + ModelUtils.save_top_level_model(result_profile, trestle_root, output_prof_name, FileContentType.JSON) + + except TrestleError as e: + raise TrestleError(f'Error initializing profile {output_prof_name}: {e}') + return CmdReturnCodes.SUCCESS.value From 6849c3b01d0adfd1261b9929a7d5c1866dd38973 Mon Sep 17 00:00:00 2001 From: Lou DeGenaro Date: Mon, 26 Jun 2023 14:00:38 -0400 Subject: [PATCH 09/10] fix: python 3.7.17 issue (#1408) * fix: python 3.7.17 issue Signed-off-by: Lou DeGenaro * fix: python 3.7.17 issue Signed-off-by: Lou DeGenaro --------- Signed-off-by: Lou DeGenaro --- .github/workflows/python-test.yml | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/.github/workflows/python-test.yml b/.github/workflows/python-test.yml index 76521caf6..c50160cee 100644 --- a/.github/workflows/python-test.yml +++ b/.github/workflows/python-test.yml @@ -92,6 +92,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] + python-version: [3.8, 3.9] include: - os: ubuntu-latest path: ~/.cache/pip @@ -99,7 +100,19 @@ jobs: path: ~/Library/Caches/pip - os: windows-latest path: ~\AppData\Local\pip\Cache - python-version: [3.7, 3.8, 3.9] + # optional 3.7 test + - os: ubuntu-latest + path: ~/.cache/pip + python-version: 3.7 + # optional 3.7 test + - os: macos-latest + path: ~/Library/Caches/pip + python-version: 3.7.16 + # optional 3.7 test + - os: windows-latest + path: ~\AppData\Local\pip\Cache + python-version: 3.7 + steps: - name: Don't mess with line endings run: | @@ -122,16 +135,16 @@ jobs: run: | make develop - name: Pytest Fast - if: ${{ !(matrix.os == 'ubuntu-latest' && matrix.python-version == '3.7') }} + if: ${{ !(matrix.os == 'ubuntu-latest' && matrix.python-version == '3.8') }} run: | make test - name: Pytest Cov - if: ${{ matrix.os == 'ubuntu-latest' && matrix.python-version == '3.7' }} + if: ${{ matrix.os == 'ubuntu-latest' && matrix.python-version == '3.8' }} run: | make test-cov - name: Upload artifact - if: ${{ matrix.os == 'ubuntu-latest' && matrix.python-version == '3.7' }} + if: ${{ matrix.os == 'ubuntu-latest' && matrix.python-version == '3.8' }} uses: actions/upload-artifact@v2 with: name: coverage @@ -154,7 +167,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v2 with: - python-version: 3.7 + python-version: 3.8 - uses: actions/cache@v2 with: path: ~/.cache/pip @@ -178,7 +191,7 @@ jobs: -Dsonar.python.coverage.reportPaths=coverage.xml -Dsonar.tests=tests/ -Dsonar.sources=trestle/ - -Dsonar.python.version=3.7 + -Dsonar.python.version=3.8 -Dsonar.projectKey=compliance-trestle -Dsonar.organization=compliance-trestle -Dsonar.cpd.exclusions=trestle/oscal/*.py From 9380cc813f8b044640fecb4ee302207d3c66d29a Mon Sep 17 00:00:00 2001 From: mrgadgil <49280244+mrgadgil@users.noreply.github.com> Date: Mon, 26 Jun 2023 16:35:49 -0400 Subject: [PATCH 10/10] fix: Change the community call to use bluejeans events (#1400) Co-authored-by: AleJo2995 --- README.md | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 52e2dcec4..20b87dd1a 100644 --- a/README.md +++ b/README.md @@ -92,19 +92,29 @@ The community call will happen every 2 week(s) on Tuesday at 10.00am EST.\ Meeting information: ``` +You have been invited to Attend at the following event : Compliance Trestle Community Call -Hosted by MANJIREE GADGIL - -https://ibm.webex.com/ibm/j.php?MTID=mcf86d5904761ea884ec248c006fc3b81 -Tuesday, May 16, 2023 10:00 AM | 30 minutes | (UTC-04:00) Eastern Time (US & Canada) -Occurs every 2 week(s) on Tuesday effective 5/16/2023 from 10:00 AM to 10:30 AM, (UTC-04:00) Eastern Time (US & Canada) - -Join by phone -1-844-531-0958 United States Toll Free -1-669-234-1178 United States Toll -1-669-234-1178 United States Toll -Host access code 979 191 85 -Attendee access code 979 379 85 +Every 2nd week on Tuesday; from June 27th, 2023 at 10:00 am EST for 0.5 hour + +To join, select from the following options: + +1) Web Browser + a) https://primetime.bluejeans.com/a2m/live-event/dcwuavtj + +2) Laptop paired with room system (Best Experience) + a) Dial: meet@bjn.vc or 104.238.247.247 in the room system. + b) Go to https://primetime.bluejeans.com/a2m/live-event/dcwuavtj/room-system/ + c) Enter the pairing code displayed on your room system's screen into your browser. + +3) Room System + a) Dial: meet@bjn.vc or 104.238.247.247 in the room system. + b) Enter Meeting ID : 499830564 and Passcode : 8231 + +4) Joining via a mobile device? + a) Open this link : https://primetime.bluejeans.com/a2m/live-event/dcwuavtj + b) Download the app if you don’t have it already + c) Enter event ID : dcwuavtj + ``` ## Contributing to Trestle