From 93275766a7558c5113bedfaa53858c4b0448fee5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 17 Dec 2024 11:11:31 -0500 Subject: [PATCH 1/5] build(deps): bump paramiko from 3.4.0 to 3.5.0 (#1705) Bumps [paramiko](https://github.com/paramiko/paramiko) from 3.4.0 to 3.5.0. - [Commits](https://github.com/paramiko/paramiko/compare/3.4.0...3.5.0) --- updated-dependencies: - dependency-name: paramiko dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 4d32d3ba8..6d28c5506 100644 --- a/setup.cfg +++ b/setup.cfg @@ -29,7 +29,7 @@ install_requires = attrs ilcli cryptography==43.0.3 - paramiko==3.4.0 + paramiko==3.5.0 ruamel.yaml furl pydantic[email]>=2.0.0 From 9d5ebf3eec7fc95b721a659206905d11d6ffd724 Mon Sep 17 00:00:00 2001 From: Chris Butler Date: Thu, 19 Dec 2024 23:09:10 +1100 Subject: [PATCH 2/5] fix: activate mike (#1776) Signed-off-by: Chris Butler Co-authored-by: Jennifer Power --- mkdocs.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mkdocs.yml b/mkdocs.yml index 5f808ec26..92d6975f1 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,5 +1,7 @@ edit_uri: '' extra: + version: + provider: mike analytics: property: G-XT3KGMHSY8 provider: google From 8171357bf01030f8d96d079be90100b0a37f8087 Mon Sep 17 00:00:00 2001 From: Lou DeGenaro Date: Thu, 16 Jan 2025 08:23:07 -0500 Subject: [PATCH 3/5] feat: task cis-xlsx-to-oscal-cd (#1764) * task cis-xlsx-to-oscal-cd Signed-off-by: Lou DeGenaro * docs Signed-off-by: Lou DeGenaro * signatures & trace Signed-off-by: Lou DeGenaro * tmpdir Signed-off-by: Lou DeGenaro * hack Signed-off-by: Lou DeGenaro * give up. Signed-off-by: Lou DeGenaro * Fix failing macos pipeline (thanks Vikas!) Signed-off-by: Lou DeGenaro * SortHelper Signed-off-by: Lou DeGenaro * Add debugging try/except Signed-off-by: Lou DeGenaro * 2nd try Signed-off-by: Lou DeGenaro * add utf-8 encoding to open Signed-off-by: Lou DeGenaro * Fix sonar code smells Signed-off-by: Lou DeGenaro * Fix sonar code smells Signed-off-by: Lou DeGenaro * Fix sonar code smells Signed-off-by: Lou DeGenaro * Fix sonar code smells Signed-off-by: Lou DeGenaro * Fix sonar code smells Signed-off-by: Lou DeGenaro * Fix sonar code smells Signed-off-by: Lou DeGenaro * Fix sonar code smells Signed-off-by: Lou DeGenaro * Fix sonar code smells Signed-off-by: Lou DeGenaro * improve test spot checking Signed-off-by: Lou DeGenaro * tests: bad_confg and bad_overwrite Signed-off-by: Lou DeGenaro * simplify & improve test coverage Signed-off-by: Lou DeGenaro * simplify & improve test coverage Signed-off-by: Lou DeGenaro * simplify & improve test coverage Signed-off-by: Lou DeGenaro * simplify & improve test coverage Signed-off-by: Lou DeGenaro * simplify & improve test coverage Signed-off-by: Lou DeGenaro * simplify & improve test coverage Signed-off-by: Lou DeGenaro * simplify & improve test coverage Signed-off-by: Lou DeGenaro * Simplify & improve test coverage Signed-off-by: Lou DeGenaro * Simplify & improve test coverage Signed-off-by: Lou DeGenaro * 100% test coverage! Signed-off-by: Lou DeGenaro * Fix license. Signed-off-by: Lou DeGenaro --------- Signed-off-by: Lou DeGenaro --- .../API/trestle/tasks/cis_xlsx_to_oscal_cd.md | 7 + ...S_IBM_Db2_11_Benchmark_v1.1.0.snippet.xlsx | Bin 0 -> 27400 bytes ..._Benchmark_v1.1.0.snippet_bad_control.xlsx | Bin 0 -> 27723 bytes ..._11_Benchmark_v1.1.0.snippet_combined.xlsx | Bin 0 -> 44663 bytes ...Db2_11_Benchmark_v1.1.0.snippet_merge.xlsx | Bin 0 -> 27520 bytes ...nchmark_v1.1.0.snippet_missing_column.xlsx | Bin 0 -> 27397 bytes ...st-cis-xlsx-to-oscal-cd.db2.snippet.config | 18 + tests/trestle/core/utils_test.py | 8 + .../tasks/cis_xlsx_to_oscal_cd_test.py | 242 +++++ trestle/common/str_utils.py | 12 + trestle/tasks/cis_xlsx_to_oscal_cd.py | 965 ++++++++++++++++++ trestle/tasks/csv_to_oscal_cd.py | 2 +- 12 files changed, 1253 insertions(+), 1 deletion(-) create mode 100644 docs/reference/API/trestle/tasks/cis_xlsx_to_oscal_cd.md create mode 100644 tests/data/tasks/cis-xlsx-to-oscal-cd/CIS_IBM_Db2_11_Benchmark_v1.1.0.snippet.xlsx create mode 100644 tests/data/tasks/cis-xlsx-to-oscal-cd/CIS_IBM_Db2_11_Benchmark_v1.1.0.snippet_bad_control.xlsx create mode 100644 tests/data/tasks/cis-xlsx-to-oscal-cd/CIS_IBM_Db2_11_Benchmark_v1.1.0.snippet_combined.xlsx create mode 100644 tests/data/tasks/cis-xlsx-to-oscal-cd/CIS_IBM_Db2_11_Benchmark_v1.1.0.snippet_merge.xlsx create mode 100644 tests/data/tasks/cis-xlsx-to-oscal-cd/CIS_IBM_Db2_11_Benchmark_v1.1.0.snippet_missing_column.xlsx create mode 100644 tests/data/tasks/cis-xlsx-to-oscal-cd/test-cis-xlsx-to-oscal-cd.db2.snippet.config create mode 100644 tests/trestle/tasks/cis_xlsx_to_oscal_cd_test.py create mode 100644 trestle/tasks/cis_xlsx_to_oscal_cd.py diff --git a/docs/reference/API/trestle/tasks/cis_xlsx_to_oscal_cd.md b/docs/reference/API/trestle/tasks/cis_xlsx_to_oscal_cd.md new file mode 100644 index 000000000..a57dcd57c --- /dev/null +++ b/docs/reference/API/trestle/tasks/cis_xlsx_to_oscal_cd.md @@ -0,0 +1,7 @@ +--- +title: trestle.tasks.cis_xlsx_to_oscal_cd +description: Documentation for trestle.tasks.cis_xlsx_to_oscal_cd module +--- + +::: trestle.tasks.cis_xlsx_to_oscal_cd +handler: python diff --git a/tests/data/tasks/cis-xlsx-to-oscal-cd/CIS_IBM_Db2_11_Benchmark_v1.1.0.snippet.xlsx b/tests/data/tasks/cis-xlsx-to-oscal-cd/CIS_IBM_Db2_11_Benchmark_v1.1.0.snippet.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..73086a4939172080caa6dd3832a2f45816041b3e GIT binary patch literal 27400 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 literal 0 HcmV?d00001 diff --git a/tests/data/tasks/cis-xlsx-to-oscal-cd/CIS_IBM_Db2_11_Benchmark_v1.1.0.snippet_bad_control.xlsx b/tests/data/tasks/cis-xlsx-to-oscal-cd/CIS_IBM_Db2_11_Benchmark_v1.1.0.snippet_bad_control.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..789d8335618e6f84884b091ebe58b1a0801ecfd6 GIT binary patch literal 27723 zcmaI6Q;;Y?ldapfZM%E7ZQHhO+s1C&wr$(CZQE!6Gxy$@`*5Zzq9Pya>C23jE2BzY z3K#?h00II6Ak3Xu6X1U==zp=EqlvW>9qoV4>V$sTL3)_bTdzo}SC(yli2RaHY4J`% zX1qQUTkPhD!l9st7hz;1nCOPV7zba!AH(7uXT8`i6SrOI7z6pM;WPkz*D0uV*nohFh?B~H^v|1@RMMT ztG`GNW~v2iejr`JGB#E$wi5U_p1e#4YD9^Iu49tYh2Ev?T}ZGdp->(>CO++xySGVj zm@xF>GKi^!CpTkgi>)z)-$|3_X=%tE?I(`*hEHJAT}Z+7X3kzR#6IHEH@T>|OnQ=0 znmJ#SC&=-cML{($nVbX5O_28xPNY;?tUKeF?jO^hRe+ zRb>XsUXj`cuIB?QcPh3b35qP_A@0)=OvKe2u2(cKzc6h17)Yw8qcDt?WFmJUYWYOV z9ub04qj7 zA7*z-keC6OO+c;FC()V^gs4;i2V&1^E~2il+FXrgJj0sJ>J19}hJMbWq&-$ps3pBzPJtRJiAt!#^%_WxkvtZ$8G@za z0@h`KjngJ&a7BH+l#71>1x9{4!Dxmfuut$v98NQ0Gj**)-ehL>lpe8@z{cYFcS}4g zT?FlZw9-=OF~i$LAoC;;O4|>Wv<|$76dO(`GP(@nL#l5F9D1eQ-k576JWh-i*Sp|^ zkDDCpgRyDy*PJNnU;ujmCSzdti=5k9r)wK5so1deV!w4#9+O2>r$b?mS4(OM)yS@k zrlD`1M_3P}g3g+g2OIm@e(K8Bc=cFaKD_8CPX}RJL3y+%J$ri~sp_@Y_jXV#UCP(L zx&()~FhV}$`^UZ%_8Z>u~Q%RsH zs#8>q3`AcL##}sDRbYV%fh}h~7A}yyMdFCpPwW z`X$ceah7uXVFp@k#BAb=>V0K%@o&(}m$%kzm7&Brcuw;5t{`0Hkl+2*f&GHAq01}%@6L)Wu|MdP>0-1OY z#{>T*@aKOO8rXjcbhoB+a`vz`ar#g4t~KXl3RzKnZq*2OI^+1)@+BKGOazxxEt|Qh zRVqIA{DrC*SQ%$*8}#(Bfdl?#i%Kv_Y|vu?zJ6?Zd3)Pr#odRw#8lhGC5IQfIoYyZ^s0MZ1)7(WIZPSB)Gle^+{E)!0J z9Lcjp0f;(<7C8Vq2D43QH%q4YCl_o|=_czOuZ=e$Kg1IbKdban2hJu3HKHcP)sY_XYZj|gSeazVVXbb*2`}4FZGs00Pm%fM3_lv~sovqtki8PB7-3r;Rsw&b9Vrh{ z&yjd9HV%nTH0UFxy
AQ@zV05xW=hjd}?S)^Qe4ZQZUft&vARDa=3FZm+ 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μWQ~0VU1(+5zQX?4m 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 literal 0 HcmV?d00001 diff --git a/tests/data/tasks/cis-xlsx-to-oscal-cd/CIS_IBM_Db2_11_Benchmark_v1.1.0.snippet_combined.xlsx b/tests/data/tasks/cis-xlsx-to-oscal-cd/CIS_IBM_Db2_11_Benchmark_v1.1.0.snippet_combined.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..b90484a27f54fdcb485db241e1d7217e3ab09145 GIT binary patch literal 44663 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 literal 0 HcmV?d00001 diff --git a/tests/data/tasks/cis-xlsx-to-oscal-cd/CIS_IBM_Db2_11_Benchmark_v1.1.0.snippet_merge.xlsx b/tests/data/tasks/cis-xlsx-to-oscal-cd/CIS_IBM_Db2_11_Benchmark_v1.1.0.snippet_merge.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..3201c276831cc93b7855468e15ee1d6f1da0cbfd GIT binary patch literal 27520 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 UAtB$sHU|7-3?GIVcDK3z1!Y7|2mk;8 literal 0 HcmV?d00001 diff --git a/tests/data/tasks/cis-xlsx-to-oscal-cd/CIS_IBM_Db2_11_Benchmark_v1.1.0.snippet_missing_column.xlsx b/tests/data/tasks/cis-xlsx-to-oscal-cd/CIS_IBM_Db2_11_Benchmark_v1.1.0.snippet_missing_column.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..f997fee3540c41c20d5c92576e568dbaf9f50f74 GIT binary patch literal 27397 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 p4xi)9fCa8eMU^&n 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 literal 0 HcmV?d00001 diff --git a/tests/data/tasks/cis-xlsx-to-oscal-cd/test-cis-xlsx-to-oscal-cd.db2.snippet.config b/tests/data/tasks/cis-xlsx-to-oscal-cd/test-cis-xlsx-to-oscal-cd.db2.snippet.config new file mode 100644 index 000000000..e9bd631b5 --- /dev/null +++ b/tests/data/tasks/cis-xlsx-to-oscal-cd/test-cis-xlsx-to-oscal-cd.db2.snippet.config @@ -0,0 +1,18 @@ +[task.cis-xlsx-to-oscal-cd] + +benchmark-file = tests/data/tasks/cis-xlsx-to-oscal-cd/CIS_IBM_Db2_11_Benchmark_v1.1.0.snippet.xlsx +benchmark-title = CIS IBM Db2 11 Benchmark +benchmark-version = 1.1.0 + +namespace = https://oscal-compass/compliance-trestle/schemas/oscal/cd + +component-name = IBM Db2 11 +component-description = IBM Db2 11 +component-type = software + +profile-version = v8 +profile-source = catalogs/CIS_controls_v8/catalog.json +profile-description = CIS catalog v8 + +output-dir = tests/data/tasks/cis-xlsx-to-oscal-cd/output +output-overwrite = true diff --git a/tests/trestle/core/utils_test.py b/tests/trestle/core/utils_test.py index ab6a097ed..16f39ece4 100644 --- a/tests/trestle/core/utils_test.py +++ b/tests/trestle/core/utils_test.py @@ -41,6 +41,7 @@ from trestle.common.err import TrestleError from trestle.common.model_utils import ModelUtils from trestle.common.str_utils import AliasMode +from trestle.common.str_utils import as_bool def load_good_catalog() -> catalog.Catalog: @@ -352,3 +353,10 @@ def test_prune_empty_dirs(tmp_path: pathlib.Path) -> None: assert not (tmp_path / 'sub1/sub11/sub111').exists() assert foo_path.exists() assert bar_path.exists() + + +def test_as_bool(tmp_path: pathlib.Path) -> None: + """Test as_bool function.""" + assert as_bool('true') + assert not as_bool('false') + assert not as_bool(None) diff --git a/tests/trestle/tasks/cis_xlsx_to_oscal_cd_test.py b/tests/trestle/tasks/cis_xlsx_to_oscal_cd_test.py new file mode 100644 index 000000000..07a68212c --- /dev/null +++ b/tests/trestle/tasks/cis_xlsx_to_oscal_cd_test.py @@ -0,0 +1,242 @@ +# Copyright (c) 2025 The OSCAL Compass Authors. 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. +"""cis-xlsx-to-oscal-cd task tests.""" + +import configparser +import os +import pathlib +from typing import Dict +from unittest.mock import patch + +import trestle.tasks.cis_xlsx_to_oscal_cd as cis_xlsx_to_oscal_cd +from trestle.oscal.component import ComponentDefinition +from trestle.tasks.base_task import TaskOutcome + +db2_config = 'tests/data/tasks/cis-xlsx-to-oscal-cd/test-cis-xlsx-to-oscal-cd.db2.snippet.config' + + +def _get_section(tmp_path: pathlib.Path, file_: str) -> Dict: + """Get section.""" + config = configparser.ConfigParser() + config_path = pathlib.Path(file_) + config.read(config_path) + section = config['task.cis-xlsx-to-oscal-cd'] + section['output-dir'] = str(tmp_path) + return section + + +def test_cis_xlsx_to_oscal_cd_compare(tmp_path: pathlib.Path): + """Test compare.""" + x = cis_xlsx_to_oscal_cd.SortHelper.compare('A', 'B') + assert x == -1 + x = cis_xlsx_to_oscal_cd.SortHelper.compare('0.0', '1.0') + assert x == -1 + x = cis_xlsx_to_oscal_cd.SortHelper.compare('1.0', '0.0') + assert x == 1 + x = cis_xlsx_to_oscal_cd.SortHelper.compare('1.1', '1.1') + assert x == 0 + + +def test_cis_xlsx_to_oscal_cd_print_info(tmp_path: pathlib.Path): + """Test print_info call.""" + section = _get_section(tmp_path, db2_config) + tgt = cis_xlsx_to_oscal_cd.CisXlsxToOscalCd(section) + retval = tgt.print_info() + assert retval is None + + +def test_cis_xlsx_to_oscal_cd_simulate(tmp_path: pathlib.Path): + """Test simulate call.""" + section = _get_section(tmp_path, db2_config) + tgt = cis_xlsx_to_oscal_cd.CisXlsxToOscalCd(section) + retval = tgt.simulate() + assert retval == TaskOutcome.SIM_SUCCESS + assert len(os.listdir(str(tmp_path))) == 0 + + +def test_cis_xlsx_to_oscal_cd_execute(tmp_path: pathlib.Path): + """Test execute call - db2.""" + section = _get_section(tmp_path, db2_config) + tgt = cis_xlsx_to_oscal_cd.CisXlsxToOscalCd(section) + retval = tgt.execute() + assert retval == TaskOutcome.SUCCESS + _validate_db2(tmp_path) + + +def test_cis_xlsx_to_oscal_cd_execute_combined(tmp_path: pathlib.Path): + """Test execute call - db2.""" + section = _get_section(tmp_path, db2_config) + section['benchmark-file' + ] = 'tests/data/tasks/cis-xlsx-to-oscal-cd/CIS_IBM_Db2_11_Benchmark_v1.1.0.snippet_combined.xlsx' + tgt = cis_xlsx_to_oscal_cd.CisXlsxToOscalCd(section) + retval = tgt.execute() + assert retval == TaskOutcome.SUCCESS + _validate_db2(tmp_path) + + +def test_cis_xlsx_to_oscal_cd_execute_missing_column(tmp_path: pathlib.Path): + """Test execute call - missing column.""" + section = _get_section(tmp_path, db2_config) + section['benchmark-file' + ] = 'tests/data/tasks/cis-xlsx-to-oscal-cd/CIS_IBM_Db2_11_Benchmark_v1.1.0.snippet_missing_column.xlsx' + tgt = cis_xlsx_to_oscal_cd.CisXlsxToOscalCd(section) + retval = tgt.execute() + assert retval == TaskOutcome.FAILURE + + +def test_cis_xlsx_to_oscal_cd_execute_bad_config(tmp_path: pathlib.Path): + """Test execute call - bad config.""" + section = _get_section(tmp_path, db2_config) + del section['benchmark-file'] + tgt = cis_xlsx_to_oscal_cd.CisXlsxToOscalCd(section) + retval = tgt.execute() + assert retval == TaskOutcome.FAILURE + + +def test_cis_xlsx_to_oscal_cd_execute_bad_overwrite(tmp_path: pathlib.Path): + """Test execute call - bad overwrite.""" + section = _get_section(tmp_path, db2_config) + tgt = cis_xlsx_to_oscal_cd.CisXlsxToOscalCd(section) + retval = tgt.execute() + assert retval == TaskOutcome.SUCCESS + section['output-overwrite'] = 'false' + tgt = cis_xlsx_to_oscal_cd.CisXlsxToOscalCd(section) + retval = tgt.execute() + assert retval == TaskOutcome.FAILURE + + +def test_cis_xlsx_to_oscal_cd_execute_merge(tmp_path: pathlib.Path): + """Test execute call - merge.""" + section = _get_section(tmp_path, db2_config) + section['benchmark-file' + ] = 'tests/data/tasks/cis-xlsx-to-oscal-cd/CIS_IBM_Db2_11_Benchmark_v1.1.0.snippet_merge.xlsx' + tgt = cis_xlsx_to_oscal_cd.CisXlsxToOscalCd(section) + retval = tgt.execute() + assert retval == TaskOutcome.SUCCESS + _validate_db2(tmp_path) + + +def test_cis_xlsx_to_oscal_cd_execute_rule_prefix(tmp_path: pathlib.Path): + """Test execute call - rule prefix.""" + section = _get_section(tmp_path, db2_config) + section['benchmark-rule-prefix'] = 'CIS' + tgt = cis_xlsx_to_oscal_cd.CisXlsxToOscalCd(section) + retval = tgt.execute() + assert retval == TaskOutcome.SUCCESS + _validate_db2(tmp_path) + + +def test_cis_xlsx_to_oscal_cd_execute_control_prefix(tmp_path: pathlib.Path): + """Test execute call - control prefix.""" + section = _get_section(tmp_path, db2_config) + section['benchmark-control-prefix'] = 'cisc' + tgt = cis_xlsx_to_oscal_cd.CisXlsxToOscalCd(section) + retval = tgt.execute() + assert retval == TaskOutcome.SUCCESS + _validate_db2(tmp_path) + + +def test_cis_xlsx_to_oscal_cd_execute_control_bad(tmp_path: pathlib.Path): + """Test execute call - control bad.""" + section = _get_section(tmp_path, db2_config) + section['benchmark-file' + ] = 'tests/data/tasks/cis-xlsx-to-oscal-cd/CIS_IBM_Db2_11_Benchmark_v1.1.0.snippet_bad_control.xlsx' + tgt = cis_xlsx_to_oscal_cd.CisXlsxToOscalCd(section) + retval = tgt.execute() + assert retval == TaskOutcome.FAILURE + + +def test_cis_xlsx_to_oscal_cd_execute_columns_exclude(tmp_path: pathlib.Path): + """Test execute call - control prefix.""" + section = _get_section(tmp_path, db2_config) + section['columns-exclude'] = '"Recommendation #", "Profile", "Description"' + tgt = cis_xlsx_to_oscal_cd.CisXlsxToOscalCd(section) + retval = tgt.execute() + assert retval == TaskOutcome.SUCCESS + _validate_db2(tmp_path) + + +def test_cis_xlsx_to_oscal_cd_execute_config_missing(tmp_path: pathlib.Path): + """Test execute call - config_missing.""" + section = None + tgt = cis_xlsx_to_oscal_cd.CisXlsxToOscalCd(section) + retval = tgt.execute() + assert retval == TaskOutcome.FAILURE + + +def test_cis_xlsx_to_oscal_cd_execute_count_mismatch(tmp_path: pathlib.Path): + """Test execute call - count mismatch.""" + with patch('trestle.tasks.cis_xlsx_to_oscal_cd.CombineHelper._populate_combined_map') as mock_original_function: + mock_original_function.return_value = -5 + section = _get_section(tmp_path, db2_config) + tgt = cis_xlsx_to_oscal_cd.CisXlsxToOscalCd(section) + retval = tgt.execute() + assert retval == TaskOutcome.FAILURE + + +def test_cis_xlsx_to_oscal_cd_execute_csv_row_mgr(tmp_path: pathlib.Path): + """Test execute call - csv row mgr.""" + row_names = ['row1', 'row2', 'row3'] + # create new row mgr + csv_row_mgr = cis_xlsx_to_oscal_cd.CsvRowMgr(row_names) + # test valid case + try: + csv_row_mgr.put('row1', '') + except RuntimeError: + assert 0 == 1 + # test invalid case + try: + csv_row_mgr.put('rowX', '') + assert 0 == 1 + except RuntimeError: + pass + + +def _validate_db2(tmp_path: pathlib.Path): + """Validate produced OSCAL for db2 cd.""" + # read catalog + file_path = tmp_path / 'component-definition.json' + component_definition = ComponentDefinition.oscal_read(file_path) + # spot check + assert len(component_definition.components) == 1 + assert component_definition.metadata.title == 'CIS IBM Db2 11 Benchmark' + assert component_definition.metadata.version == '1.1.0' + component = component_definition.components[0] + assert component.type == 'software' + assert len(component.props) == 552 + prop = component.props[0] + assert prop.name == 'Rule_Id' + assert prop.ns == 'https://oscal-compass/compliance-trestle/schemas/oscal/cd' + assert prop.value == 'CIS-1.1.1' + assert prop.remarks == 'rule_set_00' + assert len(component.control_implementations) == 1 + prop = component.props[551] + assert prop.name == 'Group_Description_Level_1' + assert prop.ns == 'https://oscal-compass/compliance-trestle/schemas/oscal/cd' + assert prop.value.startswith('This section provides guidance on various database configuration parameters.') + assert prop.remarks == 'rule_set_22' + assert len(component.control_implementations) == 1 + control_implementation = component.control_implementations[0] + assert len(control_implementation.implemented_requirements) == 6 + assert control_implementation.source == 'catalogs/CIS_controls_v8/catalog.json' + assert control_implementation.description == 'CIS catalog v8' + implemented_requirement = control_implementation.implemented_requirements[0] + assert implemented_requirement.control_id == 'cisc-7.4' + assert len(implemented_requirement.props) == 1 + prop = implemented_requirement.props[0] + assert prop.name == 'Rule_Id' + assert prop.ns == 'https://oscal-compass/compliance-trestle/schemas/oscal/cd' + assert prop.value == 'CIS-1.1.1' + implemented_requirement = control_implementation.implemented_requirements[5] + assert implemented_requirement.control_id == 'cisc-3.10' diff --git a/trestle/common/str_utils.py b/trestle/common/str_utils.py index f247801a8..88f98ad53 100644 --- a/trestle/common/str_utils.py +++ b/trestle/common/str_utils.py @@ -123,6 +123,18 @@ def as_string(string_or_none: Optional[str]) -> str: return string_or_none if string_or_none else '' +def as_bool(string_or_none: Optional[str]) -> bool: + """Convert string to boolean.""" + if string_or_none: + if string_or_none.lower() in ['false']: + rval = False + else: + rval = True + else: + rval = False + return rval + + def string_from_root(item_with_root: Optional[Any]) -> str: """Convert root to string if present.""" return as_string(item_with_root.__root__) if item_with_root else '' diff --git a/trestle/tasks/cis_xlsx_to_oscal_cd.py b/trestle/tasks/cis_xlsx_to_oscal_cd.py new file mode 100644 index 000000000..a7011eda3 --- /dev/null +++ b/trestle/tasks/cis_xlsx_to_oscal_cd.py @@ -0,0 +1,965 @@ +# Copyright (c) 2025 The OSCAL Compass Authors. 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.""" +import configparser +import csv +import datetime +import logging +import pathlib +import tempfile +import traceback +from configparser import SectionProxy +from functools import cmp_to_key +from typing import Dict, Iterator, List, Optional + +from openpyxl import load_workbook +from openpyxl.workbook.workbook import Workbook + +from trestle.common.str_utils import as_bool +from trestle.tasks.base_task import TaskBase +from trestle.tasks.base_task import TaskOutcome +from trestle.tasks.csv_to_oscal_cd import CsvToOscalComponentDefinition + +logger = logging.getLogger(__name__) + +timestamp = datetime.datetime.now(datetime.timezone.utc).replace(microsecond=0).isoformat() + +default_benchmark_control_prefix = 'cisc-' +default_benchmark_rule_prefix = 'CIS-' +output_file = 'component-definition.json' + +head_recommendation_no = 'Recommendation #' +head_section_no = 'Section #' + + +class CisXlsxToOscalCd(TaskBase): + """ + Task to transform CIS .xlsx to OSCAL component definition. + + Attributes: + name: Name of the task. + """ + + name = 'cis-xlsx-to-oscal-cd' + + def __init__(self, config_object: Optional[configparser.SectionProxy]) -> None: + """ + Initialize trestle task. + + Args: + config_object: Config section associated with the task. + """ + self.config_object = config_object + super().__init__(config_object) + self._default_benchmark_sheet_name = 'Combined Profiles' + self._default_component_type = 'software' + self._default_output_overwrite = 'True' + # + self._example_benchmark_file = 'data/CIS_IBM_Db2_11_Benchmark_v1.1.0.xlsx' + self._example_benchmark_title = 'CIS IBM Db2 11 Benchmark' + self._example_benchmark_version = '1.1.0' + self._example_oscal_cd_dir = 'data/component-definitions/CIS_IBM_Db2_11_Benchmark_v1.1.0' + self._example_namespace = 'https://oscal-compass/compliance-trestle/schemas/oscal/cd' + self._example_profile_version = 'v8' + self._example_profile_source = 'catalogs/CIS_controls_v8/catalog.json' + self._example_profile_description = 'CIS catalog v8' + self._example_component_name = 'Db2 11' + + def print_info(self) -> None: + """Print the help string.""" + logger.info(f'Help information for {self.name} task.') + logger.info('') + logger.info('Purpose: Create component definition from standard CIS benchmark') + logger.info('') + logger.info('Configuration flags sit under [task.cis-xlsx-to-oscal-cd]:') + text1 = ' benchmark-file = ' + text2 = f'(required) path of file to read the CIS benchmark .xlsx, e.g., "{self._example_benchmark_file}".' + logger.info(text1 + text2) + text1 = ' benchmark-title = ' + text2 = f'(required) title of the CIS benchmark, e.g., "{self._example_benchmark_title}".' + logger.info(text1 + text2) + text1 = ' benchmark-version = ' + text2 = f'(required) version of the CIS benchmark .xlsx, e.g., "{self._example_benchmark_version}".' + logger.info(text1 + text2) + text1 = ' benchmark-control-prefix = ' + text2 = f'(optional) benchmark control prefix, default = "{default_benchmark_control_prefix}".' + logger.info(text1 + text2) + text1 = ' benchmark-rule-prefix = ' + text2 = f'(optional) benchmark rule prefix, default = "{default_benchmark_rule_prefix}".' + logger.info(text1 + text2) + text1 = ' benchmark-sheet-name = ' + text2 = f'(optional) benchmark sheet name, default = "{self._default_benchmark_sheet_name}".' + logger.info(text1 + text2) + text1 = ' component-name = ' + text2 = f'(required) component name, e.g., "{self._example_component_name}".' + logger.info(text1 + text2) + text1 = ' component-description = ' + text2 = f'(required) component description, e.g., "{self._example_component_name}".' + logger.info(text1 + text2) + text1 = ' component-type = ' + text2 = f'(required) component type, e.g., "{self._default_component_type}".' + logger.info(text1 + text2) + text1 = ' namespace = ' + text2 = f'(required) namespace, e.g., "{self._example_namespace}".' + logger.info(text1 + text2) + text1 = ' output-dir = ' + text2 = f'(required) path of folder to write the OSCAL {output_file}, e.g., "{self._example_oscal_cd_dir}".' + logger.info(text1 + text2) + text1 = ' output-overwrite = ' + text2 = f'(optional) output overwrite, default = "{self._default_output_overwrite}".' + logger.info(text1 + text2) + text1 = ' profile-version = ' + text2 = f'(required) profile version, e.g., "{self._example_profile_version}".' + logger.info(text1 + text2) + text1 = ' profile-source = ' + text2 = f'(required) profile source, e.g., "{self._example_profile_source}".' + logger.info(text1 + text2) + text1 = ' profile-description = ' + text2 = f'(required) profile description, e.g., "{self._example_profile_description}".' + 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.""" + if not self.config_object: + logger.warning('config missing') + return TaskOutcome('failure') + # required + try: + self._benchmark_file = self.config_object['benchmark-file'] + self._oscal_cd_dir = self.config_object['output-dir'] + self._namespace = self.config_object['namespace'] + self._profile_version = self.config_object['profile-version'] + except KeyError as e: + logger.info(f'key {e.args[0]} missing') + return TaskOutcome('failure') + # output + self._oscal_cd_path = pathlib.Path(self._oscal_cd_dir) + # insure output dir exists + self._oscal_cd_path.mkdir(exist_ok=True, parents=True) + # calculate output file name & check writability + oname = 'component-definition.json' + ofile = self._oscal_cd_path / oname + overwrite = self.config_object.get('output-overwrite', self._default_output_overwrite) + overwrite = as_bool(overwrite) + if not overwrite and pathlib.Path(ofile).exists(): + logger.warning(f'output: {ofile} already exists') + return TaskOutcome('failure') + with self._get_tempdir() as tmpdir: + # step 1 - add combined sheet, if needed + combine_helper = CombineHelper(self.config_object, tmpdir) + combine_helper.run() + # step 2 - create trestle ready csv file from xlsx file + xlsx_to_csv_helper = XlsxToCsvHelper(self.config_object, tmpdir) + xlsx_to_csv_helper.run() + # step 3 - create OSCAL json file from csv file + csv_to_json_helper = CsvToJsonHelper(self.config_object, tmpdir) + task_outcome = csv_to_json_helper.run() + return task_outcome + + def _get_tempdir(self) -> tempfile.TemporaryDirectory(): + """Get tmpdir.""" + return tempfile.TemporaryDirectory() + + +class SortHelper: + """SortHelper.""" + + @staticmethod + def compare(item1: str, item2: str) -> int: + """Compare.""" + # get parts + parts1 = ''.split('.') + if item1 is not None: + parts1 = str(item1).split('.') + parts2 = ''.split('.') + if item2 is not None: + parts2 = str(item2).split('.') + # normalize parts length + while len(parts1) < len(parts2): + parts1.append('0') + while len(parts2) < len(parts1): + parts2.append('0') + # comparison + rval = 0 + for i in range(len(parts1)): + try: + v1 = int(parts1[i]) + except Exception: + rval = -1 + break + try: + v2 = int(parts2[i]) + except Exception: + rval = 1 + break + if v1 < v2: + rval = -1 + break + if v1 > v2: + rval = 1 + break + text = f'compare rval: {rval} item1: {item1} item2: {item2}' + logger.debug(f'{text}') + return rval + + +class SheetHelper: + """SheetHelper.""" + + def __init__(self, wb: Workbook, sn: str) -> None: + """Initialize.""" + self.wb = wb + self.sn = sn + self.ws = self.wb[self.sn] + + def get_sn(self) -> int: + """Get sheet name.""" + return self.sn + + def get_max_col(self) -> int: + """Get max column.""" + return self.ws.max_column + + def row_generator(self) -> Iterator[int]: + """Generate rows until max reached.""" + row = 1 + while row <= self.ws.max_row: + yield row + row += 1 + + def get_cell_value(self, row: int, col: int) -> str: + """Get cell value for given row and column name.""" + cell = self.ws.cell(row, col) + return cell.value + + def put_cell_value(self, row: int, col: int, value: str) -> None: + """Get cell value for given row and column name.""" + cell = self.ws.cell(row, col) + cell.value = value + + @staticmethod + def get_sheetname_prefixes() -> List[str]: + """Get sheetnames prefixes.""" + rval = ['Level 1', 'Level 2'] + return rval + + @staticmethod + def get_sheetname() -> str: + """Get sheetname output.""" + rval = 'Combined Profiles' + return rval + + +class ColHelper: + """Col Helper.""" + + @staticmethod + def get_section() -> int: + """Get section col no.""" + return 1 + + @staticmethod + def get_recommendation() -> int: + """Get recommendation col no.""" + return 2 + + +class Int: + """Int.""" + + def __init__(self, value=0): + """Initialize.""" + self.value = value + + def inc_value(self) -> None: + """Increment.""" + self.value += 1 + + def get_value(self) -> int: + """Get.""" + return self.value + + +class CombineHelper: + """Combine helper.""" + + tgt_col_profile = 3 + + def __init__(self, config: SectionProxy, tmpdir: str) -> None: + """Initialize.""" + benchmark_file = config['benchmark-file'] + self.ipath = pathlib.Path(benchmark_file) + self.opath = pathlib.Path(tmpdir) / self.ipath.name + self.wb = load_workbook(self.ipath) + self.ws_map = {} + self.combined_map = {} + + def run(self) -> None: + """Run.""" + self._add_sheet_combined_profiles() + self._save() + + def _gather_sheets(self) -> None: + """Gather sheets.""" + for sn in self.wb.sheetnames: + for pn in self.sheetnames_prefixes: + if sn.startswith(pn): + self.ws_map[sn] = SheetHelper(self.wb, sn) + logger.debug(f'input sheet {sn} to be combined.') + break + + def _validate_columns_count(self) -> None: + """Validate columns count.""" + columns = -1 + for sn in self.ws_map.keys(): + sheet_helper = self.ws_map[sn] + if columns < 0: + columns = sheet_helper.get_max_col() + if columns != sheet_helper.get_max_col(): + raise RuntimeError(f'{sn} unexpected columns count {sheet_helper.get_max_col()} for sheet {sn}') + + def _populate_combined_map(self) -> int: + """Populate combined map.""" + src_col_section_no = ColHelper.get_section() + src_col_recommendation_no = ColHelper.get_recommendation() + rec_count_sheets = 0 + # populate combined map + for sn in self.ws_map.keys(): + sheet_helper = SheetHelper(self.wb, sn) + # process all rows from individual sheet + rec_count_sheets += self._process_sheet(sheet_helper, src_col_section_no, src_col_recommendation_no) + return rec_count_sheets + + def _process_sheet(self, sheet_helper: SheetHelper, src_col_section_no: int, src_col_recommendation_no: int) -> int: + """Process sheet.""" + rec_count = 0 + for row in sheet_helper.row_generator(): + # section + section_no = sheet_helper.get_cell_value(row, src_col_section_no) + if section_no not in self.combined_map.keys(): + self.combined_map[section_no] = {} + # recommendation + recommendation_no = sheet_helper.get_cell_value(row, src_col_recommendation_no) + if recommendation_no not in self.combined_map[section_no].keys(): + self.combined_map[section_no][recommendation_no] = {} + # combine head or data + if row == 1: + self._combine_head(sheet_helper, row, section_no, recommendation_no, CombineHelper.tgt_col_profile) + else: + self._combine_data(sheet_helper, row, section_no, recommendation_no, CombineHelper.tgt_col_profile) + if recommendation_no: + rec_count += 1 + return rec_count + + def _handle_head_row( + self, combined_helper: SheetHelper, row: Int, kvset: Dict, section_no: str, recommendation_no: str + ) -> None: + """Handle head row.""" + for col in kvset.keys(): + value = self.combined_map[section_no][recommendation_no][col] + if col == CombineHelper.tgt_col_profile: + value = value[0] + combined_helper.put_cell_value(row.get_value(), col, value) + row.inc_value() + + def _handle_data_row_control( + self, + combined_helper: SheetHelper, + row: Int, + kvset: Dict, + section_no: str, + recommendation_no: str, + rec_count_merged: Int + ) -> None: + """Handle data row control.""" + profiles = kvset[CombineHelper.tgt_col_profile] + for profile in profiles: + for col in kvset.keys(): + value = self.combined_map[section_no][recommendation_no][col] + if col == CombineHelper.tgt_col_profile: + value = profile + combined_helper.put_cell_value(row.get_value(), col, value) + row.inc_value() + rec_count_merged.inc_value() + + def _handle_data_row_non_control( + self, combined_helper: SheetHelper, row: Int, kvset: Dict, section_no: str, recommendation_no: str + ) -> None: + """Handle data row non-control.""" + for col in kvset.keys(): + value = self.combined_map[section_no][recommendation_no][col] + if col == CombineHelper.tgt_col_profile: + value = None + combined_helper.put_cell_value(row.get_value(), col, value) + row.inc_value() + + def _handle_data_row( + self, + combined_helper: SheetHelper, + row: Int, + kvset: Dict, + section_no: str, + recommendation_no: str, + rec_count_merged: Int + ) -> None: + """Handle data row.""" + if recommendation_no: + self._handle_data_row_control(combined_helper, row, kvset, section_no, recommendation_no, rec_count_merged) + else: + self._handle_data_row_non_control(combined_helper, row, kvset, section_no, recommendation_no) + + def _populate_combined_sheet(self, combined_helper: SheetHelper) -> int: + """Populate combined sheet.""" + rec_count_merged = Int(0) + row = Int(1) + keys1 = list(self.combined_map.keys()) + keys1.sort(key=cmp_to_key(SortHelper.compare)) + for section_no in keys1: + section = self.combined_map[section_no] + keys2 = list(section.keys()) + keys2.sort(key=cmp_to_key(SortHelper.compare)) + for recommendation_no in keys2: + kvset = self.combined_map[section_no][recommendation_no] + if row.get_value() == 1: + self._handle_head_row(combined_helper, row, kvset, section_no, recommendation_no) + else: + self._handle_data_row(combined_helper, row, kvset, section_no, recommendation_no, rec_count_merged) + return rec_count_merged.get_value() + + def _add_sheet_combined_profiles(self) -> None: + """Add sheet combined profiles.""" + # output sheet + self.sheetname_output = SheetHelper.get_sheetname() + exists = self.sheetname_output in self.wb.sheetnames + if exists: + logger.debug(f'output sheet {self.sheetname_output} exists.') + return + # input sheets + self.sheetnames_prefixes = SheetHelper.get_sheetname_prefixes() + # sheets + self._gather_sheets() + # validate + self._validate_columns_count() + # key columns mappings + rec_count_sheets = self._populate_combined_map() + # add combined sheet + sn = self.sheetname_output + self.wb.create_sheet(sn) + combined_helper = SheetHelper(self.wb, sn) + self.ws_map[sn] = combined_helper + # populate combined sheet + rec_count_merged = self._populate_combined_sheet(combined_helper) + # correctness check + if rec_count_sheets != rec_count_merged: + raise RuntimeError(f'recommendation counts original: {rec_count_sheets} merged: {rec_count_merged}') + + def _combine_head( + self, sheet_helper: SheetHelper, row: int, section_no: str, recommendation_no: str, tgt_col_profile: int + ) -> None: + """Combine head.""" + if not len(self.combined_map[section_no][recommendation_no].keys()): + self.combined_map[section_no][recommendation_no][tgt_col_profile] = ['profile'] + for col in range(1, sheet_helper.get_max_col() + 1): + if col < tgt_col_profile: + tcol = col + else: + tcol = col + 1 + value = sheet_helper.get_cell_value(row, col) + self.combined_map[section_no][recommendation_no][tcol] = value + + def _combine_data( + self, sheet_helper: SheetHelper, row: int, section_no: str, recommendation_no: str, tgt_col_profile: int + ) -> None: + """Combine data.""" + if not len(self.combined_map[section_no][recommendation_no].keys()): + self.combined_map[section_no][recommendation_no][tgt_col_profile] = [] + sn = sheet_helper.get_sn() + self.combined_map[section_no][recommendation_no][tgt_col_profile].append(sn) + for col in range(1, sheet_helper.get_max_col() + 1): + if col < tgt_col_profile: + tcol = col + else: + tcol = col + 1 + self.combined_map[section_no][recommendation_no][tcol] = sheet_helper.get_cell_value(row, col) + + def _save(self) -> None: + """Save.""" + self.wb.save(self.opath) + logger.debug(f'{self.opath} saved') + + +class PropertyHelper: + """Property Helper.""" + + @staticmethod + def normalize_name(name: str) -> str: + """Normalize name.""" + rval = name.replace('(', '').replace(')', '').replace('#', '').strip() + rval = rval.replace(' ', '_') + rval = rval.replace('__', '_') + return rval + + +class CsvHelper: + """Csv Helper.""" + + def __init__(self, path: pathlib.Path) -> None: + """Initialize.""" + self.path = path + self.path.parent.mkdir(parents=True, exist_ok=True) + self.rows = [] + + def add_row(self, row: List[str]) -> None: + """Add row.""" + self.rows.append(row) + + def delete_last_row(self) -> None: + """Delete last row.""" + self.rows = self.rows[:-1] + + def write(self) -> 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 self.rows: + csv_writer.writerow(row) + + def save(self) -> None: + """Save csv file.""" + self.write() + logger.debug(f'{self.path} saved') + + @staticmethod + def columns() -> Dict: + """Columns.""" + return { + 'Component_Title': 'A human readable name for the component.', # noqa + 'Component_Description': 'A description of the component including information about its function.', # noqa + 'Component_Type': 'A category describing the purpose of the component. ALLOWED VALUES interconnection:software:hardware:service:physical:process-procedure:plan:guidance:standard:validation:', # noqa + 'Profile': 'List of CIS profiles', # noqa + 'Rule_Id': 'A textual label that uniquely identifies a policy (desired state) that can be used to reference it elsewhere in this or other documents.', # noqa + 'Rule_Description': 'A description of the policy (desired state) including information about its purpose and scope.', # noqa + 'Profile_Source': 'A URL reference to the source catalog or profile for which this component is implementing controls for. A profile designates a selection and configuration of controls from one or more catalogs.', # noqa + 'Profile_Description': 'A description of the profile.', # noqa + 'Control_Id_List': 'A list of textual labels that uniquely identify the controls or statements that the component implements.', # noqa + 'Namespace': 'A namespace qualifying the property\'s name. This allows different organizations to associate distinct semantics with the same name. Used in conjunction with "class" as the ontology concept.', # noqa + } + + +class CsvRowMgr: + """Csv row manager.""" + + def __init__(self, row_names: List) -> None: + """Initialize.""" + self.row_names = row_names + self.map = {} + + def put(self, key: str, val: str) -> None: + """Put.""" + if key not in self.row_names: + raise RuntimeError(f'{key} not found') + self.map[key] = val + + def get(self) -> List[str]: + """Get.""" + row = [] + for name in self.row_names: + if name in self.map.keys(): + row.append(self.map[name]) + else: + row.append('') + return row + + +class CisControlsHelper: + """Cis controls helper.""" + + def __init__(self, cis_controls: str) -> None: + """Initialize.""" + self.cis_controls = cis_controls + + def ctl_generator(self) -> Iterator[Dict]: + """Generate ctls until finished.""" + parts = self.cis_controls.split(';TITLE:') + # restore TITLE: + for n in range(len(parts)): + parts[n] = f'TITLE:{parts[n]}' + # process triples TITLE, CONTROL, DESCRIPTION + for part in parts: + if part: + s1 = part.split('DESCRIPTION:') + description = s1[1].strip() + s2 = s1[0].split('CONTROL:') + control = s2[1].strip() + control_version = control.split()[0] + control_id = control.split()[1] + s3 = s2[0].split('TITLE:') + title = s3[1].strip() + ctl = { + 'description': description, + 'control-version': control_version, + 'control-id': control_id, + 'title': title + } + yield ctl + + def get_ctl_list(self, ctl_pfx: str, profile_version: List[str]) -> List[str]: + """Get control list.""" + ctl_list = [] + if self.cis_controls: + for ctl in self.ctl_generator(): + if ctl['control-version'] not in profile_version: + continue + ctl_id = ctl['control-id'] + try: + float(ctl_id) + except Exception: + text = f'missing or invalid control-id: "{ctl_id}"' + raise RuntimeError(text) + ctl_list.append(f'{ctl_pfx}{ctl_id}') + return ctl_list + + +class XlsxToCsvHelper: + """Xlsx to csv helper.""" + + def __init__(self, config_object: SectionProxy, tmpdir: str) -> None: + """Initialize.""" + self.config_object = config_object + benchmark_file = self.config_object['benchmark-file'] + self.ipath = pathlib.Path(benchmark_file) + self.xpath = pathlib.Path(tmpdir) / self.ipath.name + path = pathlib.Path(tmpdir) / self.xpath.name + self.opath = path.with_suffix('.csv') + self.wb = load_workbook(self.xpath) + # worksheet + self.ws = self.wb[SheetHelper.get_sheetname()] + self._create_maps() + # excluded columns + default_columns_exclude = [f'"{head_recommendation_no}"', '"Profile"', '"Description"'] + columns_exclude = self.config_object.get('columns-exclude') + if columns_exclude: + columns_exclude = columns_exclude.strip().split(',') + else: + columns_exclude = default_columns_exclude + self._columns_exclude = [] + for col in columns_exclude: + name = PropertyHelper.normalize_name(col.replace('"', '')) + self._columns_exclude.append(name) + # benchmark control prefix + self._benchmark_control_prefix = self.config_object.get( + 'benchmark-control-prefix', default_benchmark_control_prefix + ) + if not self._benchmark_control_prefix.endswith('-'): + self._benchmark_control_prefix = f'{self._benchmark_control_prefix}-' + # benchmark rule prefix + self._benchmark_rule_prefix = self.config_object.get('benchmark-rule-prefix', default_benchmark_rule_prefix) + if not self._benchmark_rule_prefix.endswith('-'): + self._benchmark_rule_prefix = f'{self._benchmark_rule_prefix}-' + + def _create_maps(self) -> Dict: + """Create maps.""" + self._map_col_key_to_number = {} + self._map_name_to_col_key = {} + row = 1 + cols = self.ws.max_column + 1 + for col in range(row, cols): + cell = self.ws.cell(row, col) + if cell.value: + key = self._name_to_key(cell.value) + self._map_col_key_to_number[key] = col + self._map_name_to_col_key[cell.value] = key + + def _name_to_key(self, name: str) -> str: + """Name to key.""" + rval = name.lower() + return rval + + def _sanitize(self, value: str) -> str: + """Sanitize value.""" + rval = value + if value: + rval = value.replace('\n', ' ') + return rval + + def _get_map(self) -> Dict: + """Get map.""" + return self._map_name_to_col_key + + def get_all_columns(self) -> List: + """Get all columns.""" + return self._get_map().keys() + + def row_generator(self) -> Iterator[int]: + """Generate rows until max reached.""" + row = 2 + while row <= self.ws.max_row: + yield row + row += 1 + + def get(self, row: int, name: str) -> str: + """Get cell value for given row and column name.""" + key = self._name_to_key(name) + col = self._map_col_key_to_number[key] + cell = self.ws.cell(row, col) + return self._sanitize(cell.value) + + def is_same_rule(self, row_a: int, row_b: str) -> str: + """Is same rule.""" + rule_a = self.get(row_a, head_recommendation_no) + rule_b = self.get(row_b, head_recommendation_no) + rval = rule_a == rule_b + logger.debug(f'{rval} {rule_a} {rule_b}') + return rval + + def is_excluded_column(self, column: str) -> bool: + """Is excluded column.""" + for item in self._columns_exclude: + if item.lower() == column.lower(): + return True + return False + + def merge_row(self, prev_row: int, curr_row: int) -> bool: + """Merge row.""" + rval = False + if prev_row and self.is_same_rule(prev_row, curr_row): + prof2 = self.get(prev_row, 'Profile') + prof1 = self.get(curr_row, 'Profile') + prof = f'"{prof1}; {prof2}"' + self.csv_helper.delete_last_row() + # col Profile + self.csv_row_mgr.put('Profile', prof) + # add body row + row_body = self.csv_row_mgr.get() + self.csv_helper.add_row(row_body) + rval = True + return rval + + def _is_column(self, c1: str, c2: str) -> bool: + """Is column.""" + if c1.lower() == c2.lower(): + rval = True + else: + rval = False + return rval + + def heading_row_1(self) -> None: + """Heading row 1.""" + self.row_names = [] + for col_name in CsvHelper.columns().keys(): + name = PropertyHelper.normalize_name(col_name) + self.row_names.append(name) + + def heading_row_2(self) -> None: + """Heading row 2.""" + self.row_descs = [] + for col_desc in CsvHelper.columns().values(): + desc = col_desc + self.row_descs.append(desc) + # additional user columns + for col in self.get_all_columns(): + name = PropertyHelper.normalize_name(col) + if self.is_excluded_column(col): + continue + self.row_names.append(name) + self.row_descs.append(name) + # additional non-rule columns + for col in self.non_rule_helper.get_all_columns(): + name = PropertyHelper.normalize_name(col) + self.row_names.append(name) + self.row_descs.append(name) + + def _get_ctl_list(self, prev_row: int, curr_row: int) -> List[str]: + """Get_ctl_list.""" + ctl_list = [] + # if merged row, list is empty + if not self.merge_row(prev_row, curr_row): + # if non-rule row, list is empty + rec_no = self.get(curr_row, head_recommendation_no) + if rec_no is not None: + # get list + cis_controls = self.get(curr_row, 'CIS Controls') + cis_control_helper = CisControlsHelper(cis_controls) + ctl_list = cis_control_helper.get_ctl_list( + self._benchmark_control_prefix, self.config_object['profile-version'] + ) + return ctl_list + + def run(self) -> None: + """Run.""" + self.csv_helper = CsvHelper(self.opath) + self.non_rule_helper = NonRuleHelper(self.config_object, self) + # heading row 1 - names + self.heading_row_1() + # heading row 2 - descriptions + self.heading_row_2() + # add heading rows + self.csv_helper.add_row(self.row_names) + self.csv_helper.add_row(self.row_descs) + # body + user_columns = [] + for col in self.get_all_columns(): + if self.is_excluded_column(col): + continue + user_columns.append(col) + # process each data row of CIS Benchmark file + prev_row = None + for curr_row in self.row_generator(): + ctl_list = self._get_ctl_list(prev_row, curr_row) + if not ctl_list: + continue + # create new row + self.csv_row_mgr = CsvRowMgr(self.row_names) + # col Component_Title + self.csv_row_mgr.put('Component_Title', self.config_object['component-name']) + # col Component_Description + self.csv_row_mgr.put('Component_Description', self.config_object['component-description']) + # col Component_Type + self.csv_row_mgr.put('Component_Type', self.config_object['component-type']) + # col Rule_ID + rec_no = self.get(curr_row, head_recommendation_no) + rule_id = f'{self._benchmark_rule_prefix}{rec_no}' + self.csv_row_mgr.put('Rule_Id', rule_id) + # col Rule_Description + rule_desc = self.get(curr_row, 'Description') + self.csv_row_mgr.put('Rule_Description', rule_desc) + # col Profile_Source + self.csv_row_mgr.put('Profile_Source', self.config_object['profile-source']) + # col Profile_Description + self.csv_row_mgr.put('Profile_Description', self.config_object['profile-description']) + # col Control_Id_List + ctl_list = ' '.join(ctl_list) + self.csv_row_mgr.put('Control_Id_List', ctl_list) + # col Namespace + self.csv_row_mgr.put('Namespace', self.config_object['namespace']) + # col Profile + prof = self.get(curr_row, 'Profile') + self.csv_row_mgr.put('Profile', prof) + # col user + for col in user_columns: + val = self.get(curr_row, col) + if val: + if self._is_column(col, 'cis controls'): + val = f'"{val}"' + name = PropertyHelper.normalize_name(col) + self.csv_row_mgr.put(name, val) + # col group levels + self.non_rule_helper.add_group_levels(rec_no, self.csv_row_mgr) + # add body row + row_body = self.csv_row_mgr.get() + self.csv_helper.add_row(row_body) + # previous row + prev_row = curr_row + # write body + self.csv_helper.save() + + +class NonRuleHelper: + """Non-rule Helper.""" + + def __init__(self, config: SectionProxy, xlsx_helper: XlsxToCsvHelper) -> None: + """Initialize.""" + self.col_prefix = 'Group' + self.col_level = 'Level' + self.config = config + self.xlsx_helper = xlsx_helper + default_columns_carry_forward = [head_section_no, 'Title', 'Description'] + self.columns_carry_forward = self.config.get('columns-carry-forward', default_columns_carry_forward) + self.col_list = [] + self.sec_map = {} + if self.columns_carry_forward: + self._init_col_list() + self._init_sec_map() + + def _init_col_list(self) -> None: + """Init col list.""" + biggest = -1 + for row in self.xlsx_helper.row_generator(): + rec_no = self.xlsx_helper.get(row, head_recommendation_no) + if rec_no: + continue + sec_no = self.xlsx_helper.get(row, head_section_no) + count = sec_no.count('.') + if count > biggest: + biggest = count + for i in range(biggest + 1): + for col_name in self.columns_carry_forward: + name = f'{self.col_prefix}_{col_name}_{self.col_level}_{i}' + name = PropertyHelper.normalize_name(name) + self.col_list.append(name) + + def _init_sec_map(self) -> None: + """Init sec map.""" + for row in self.xlsx_helper.row_generator(): + rec_no = self.xlsx_helper.get(row, head_recommendation_no) + if rec_no: + continue + sec_no = self.xlsx_helper.get(row, head_section_no) + _map = {} + for name in self.columns_carry_forward: + _map[name] = self.xlsx_helper.get(row, name) + self.sec_map[sec_no] = _map + + def get_all_columns(self) -> List: + """Get all columns.""" + return self.col_list + + def add_group_levels(self, rec_no: str, csv_row_mgr: CsvRowMgr) -> None: + """Add group levels.""" + parts = rec_no.split('.')[:-1] + for i, part in enumerate(parts): + if not i: + sec_no = f'{part}' + else: + sec_no = f'{sec_no}.{part}' + if sec_no in self.sec_map.keys(): + _map = self.sec_map[sec_no] + for key in _map.keys(): + val = _map[key] + name = f'{self.col_prefix}_{key}_{self.col_level}_{i}' + name = PropertyHelper.normalize_name(name) + csv_row_mgr.put(name, val) + + +class CsvToJsonHelper: + """Csv to json helper.""" + + def __init__(self, config_object: Optional[configparser.SectionProxy], tmpdir: str) -> None: + """Initialize trestle task.""" + self.config_object = config_object + benchmark_file = self.config_object['benchmark-file'] + self.ipath = pathlib.Path(benchmark_file) + self.xpath = pathlib.Path(tmpdir) / self.ipath.name + path = pathlib.Path(tmpdir) / self.xpath.name + self.opath = path.with_suffix('.csv') + self.config_object['csv-file'] = str(self.opath) + self.csv_to_oscal_cd = CsvToOscalComponentDefinition(self.config_object) + self.config_object['title'] = self.config_object['benchmark-title'] + self.config_object['version'] = self.config_object['benchmark-version'] + + def run(self) -> None: + """Run.""" + return self.csv_to_oscal_cd.execute() diff --git a/trestle/tasks/csv_to_oscal_cd.py b/trestle/tasks/csv_to_oscal_cd.py index c8bf32dff..358dece29 100644 --- a/trestle/tasks/csv_to_oscal_cd.py +++ b/trestle/tasks/csv_to_oscal_cd.py @@ -1576,7 +1576,7 @@ class _CsvMgr(): def __init__(self, csv_path: pathlib.Path) -> None: """Initialize.""" self._csv = [] - with open(csv_path, 'r', newline='') as f: + with open(csv_path, 'r', newline='', encoding='utf8') as f: csv_reader = csv.reader(f, delimiter=',', quoting=csv.QUOTE_MINIMAL) for row in csv_reader: self._csv.append(row) From bdb64444971275aed458dee85e1222fc4f77fd48 Mon Sep 17 00:00:00 2001 From: Lou DeGenaro Date: Fri, 17 Jan 2025 06:45:40 -0500 Subject: [PATCH 4/5] fix: release info in docs (#1794) * Fix release info in docs. Signed-off-by: Lou DeGenaro * add trestle v0 and generalize current version to 3.x.x Signed-off-by: Lou DeGenaro * specify 0.3.7 explicitly Signed-off-by: Lou DeGenaro * fix: version is 0.37.x Signed-off-by: Lou DeGenaro --------- Signed-off-by: Lou DeGenaro --- docs/index.md | 60 +++++++++++++++++++++++++++++++++++++++---- docs/tutorials/cli.md | 6 ++--- 2 files changed, 58 insertions(+), 8 deletions(-) diff --git a/docs/index.md b/docs/index.md index b76cd62bc..abb987ad3 100644 --- a/docs/index.md +++ b/docs/index.md @@ -37,11 +37,61 @@ Trestle provides tooling to help orchestrate the compliance process across a num ## Important Note: -The current version of trestle supports NIST OSCAL 1.0.0-4. There was a breaking change in OSCAL moving from -version 1.0.0 to 1.0.2 mainly due to `prop` becoming `props` in AssessmentResults. As a result, the current development path of trestle requires OSCAL 1.0.4, but for those who require OSCAL 1.0.0 please use trestle version 0.37.x. That version is stable but will not have any features added, and we encourage users to move to OSCAL 1.0.4. +The current version of trestle 3.x supports NIST OSCAL 1.1.2. +Below shows trestle versions correspondence with OSCAL versions: -OSCAL version 1.0.0 files are still handled on import but any AssessmentResults must conform to the OSCAL 1.0.4 schema, with -props instead of prop. And all files created by trestle will be output as OSCAL version 1.0.4. +``` +trestle 3.x => OSCAL 1.1.2 +trestle 2.x => OSCAL 1.0.4 +trestle 1.x => OSCAL 1.0.2 +trestle 0.37.x => OSCAL 1.0.0 +``` + +Visit [pypi](https://pypi.org/project/compliance-trestle/#history) for trestle release history and downloads. + +## Notes for install of current and older versions of trestle + +#### Install of trestle 3.x + +Use python 3.11. + +``` +python3.11 -m venv venv.trestle +source venv.trestle/bin/activate +pip install compliance-trestle==3.6.0 +trestle version +Trestle version v3.6.0 based on OSCAL version 1.1.2 +``` + +#### Install of trestle 2.x + +Use python 3.9. + +``` +python3.9 -m venv venv.trestle +source venv.trestle/bin/activate +pip install compliance-trestle==2.6.0 +trestle version +Trestle version v2.6.0 based on OSCAL version 1.0.4 +``` + +#### Install of trestle 1.x + +Use python 3.9. + +Due to dependency updates since the release of trestle 1.2.0, perform the following in your venv: + +``` +python3.9 -m venv venv.trestle +source venv.trestle/bin/activate +pip install compliance-trestle==1.2.0 +pip uninstall pydantic +pip uninstall pydantic_core +pip install pydantic==1.10.2 +pip install requests +trestle version +Trestle version v1.2.0 based on OSCAL version 1.0.2 +``` ## Why Trestle @@ -79,7 +129,7 @@ Trestle runs on most all python platforms (e.g. Linux, Mac, Windows) and is avai ## Development status -Compliance trestle is currently stable and is based on NIST OSCAL version 1.0.4, with active development continuing. +Compliance trestle is currently stable and is based on NIST OSCAL version 1.1.2, with active development continuing. ## Contributing to Trestle diff --git a/docs/tutorials/cli.md b/docs/tutorials/cli.md index ae91b4662..150e0fab2 100644 --- a/docs/tutorials/cli.md +++ b/docs/tutorials/cli.md @@ -148,7 +148,7 @@ This command will return the current version of Trestle and OSCAL it is using. Running `trestle version` will return: -> Trestle version v2.0.0 based on OSCAL version 1.0.4 +> Trestle version v3.x.x based on OSCAL version 1.1.2 It can also be used to retrieve the metadata version of the OSCAL object: @@ -165,7 +165,7 @@ It can also be used to retrieve the metadata version of the OSCAL object: "version": "0.1.10", <<< this version here - "oscal-version": "1.0.4" + "oscal-version": "1.1.2" }, ... @@ -176,7 +176,7 @@ It can also be used to retrieve the metadata version of the OSCAL object: Running `trestle version -n nist -t catalog` will return: -> Version of OSCAL object of nist catalog is: 1.0.0 +> Version of OSCAL object of nist catalog is: 1.1.2 ## `trestle init` From b261eadc99da57bba3fd5df038b66efd7ba1156e Mon Sep 17 00:00:00 2001 From: Jennifer Power Date: Wed, 22 Jan 2025 07:34:09 -0500 Subject: [PATCH 5/5] fix: updates ssp add-props to replace existing properties (#1801) * fix: updates ssp add-props to replace existing properties Signed-off-by: Jennifer Power * docs: fixes broken links Signed-off-by: Jennifer Power --------- Signed-off-by: Jennifer Power --- CONTRIBUTING.md | 8 ++++---- README.md | 6 +++--- .../OCP4_CIS_profile_to_oscal_cd.md | 2 +- .../Transformers_and_Tasks/csv_to_oscal_cd.md | 2 +- .../ssp_profile_catalog_authoring.md | 2 +- .../tutorials/Trestle_authoring/trestle_author.md | 10 +++++----- tests/trestle/core/commands/author/ssp_test.py | 15 ++++++++++----- trestle/core/catalog/catalog_reader.py | 4 ++-- trestle/core/control_interface.py | 12 ++++++++++++ 9 files changed, 39 insertions(+), 22 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 049d30103..8ec997d7c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,7 +7,7 @@ To contribute code or documentation, please submit a [pull request](https://gith A good way to familiarize yourself with the codebase and contribution process is to look for and tackle low-hanging fruit in the [issue tracker](https://github.com/oscal-compass/compliance-trestle/issues). -Before embarking on a more ambitious contribution, please quickly [get in touch](https://oscal-compass.github.io/compliance-trestle/maintainers/) with us. +Before embarking on a more ambitious contribution, please quickly [get in touch](https://oscal-compass.github.io/compliance-trestle/latest/contributing/maintainers/) with us. **Note: We appreciate your effort, and want to avoid a situation where a contribution requires extensive rework (by you or by us), sits in backlog for a long time, or @@ -32,7 +32,7 @@ review to indicate acceptance. A change requires LGTMs from at least two reviewers. One of the reviewers must be a [`CODEOWNER`](https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners). -For a list of the maintainers (also codeowners), see the [maintainers](https://oscal-compass.github.io/compliance-trestle/maintainers/) page. +For a list of the maintainers (also codeowners), see the [maintainers](https://oscal-compass.github.io/compliance-trestle/latest/contributing/maintainers/) ### Trestle updating, testing and release logistics @@ -88,7 +88,7 @@ The devops process does not _strictly_ enforce typing, however, the expectation commits with a focus on quality over quantity (e.g. don't add `Any` everywhere just to meet coverage requirements). Python typing of functions is an active work in progress. -`mkbuild` is used to generate the [trestle documenation site](https://oscal-compass.github.io/compliance-trestle). The `mkbuild` +`mkbuild` is used to generate the [trestle documenation site](https://oscal-compass.github.io/compliance-trestle/latest). The `mkbuild` website includes an API reference section generated from the code. Docstrings within the code are expected to follow [google style docstrings](https://www.sphinx-doc.org/en/master/usage/extensions/example_google.html). @@ -116,7 +116,7 @@ e.g. We have tried to make it as easy as possible to make contributions. This applies to how we handle the legal aspects of contribution. We use the -same approach - the [Developer's Certificate of Origin 1.1 (DCO)](https://oscal-compass.github.io/compliance-trestle/contributing/DCO/) - that the Linux® Kernel [community](https://developercertificate.org/) +same approach - the [Developer's Certificate of Origin 1.1 (DCO)](https://oscal-compass.github.io/compliance-trestle/latest/contributing/DCO/) - that the Linux® Kernel [community](https://developercertificate.org/) uses to manage code contributions. We simply ask that when submitting a patch for review, the developer diff --git a/README.md b/README.md index 731f01d18..be8b71a48 100644 --- a/README.md +++ b/README.md @@ -64,11 +64,11 @@ Users needing to import XML OSCAL artifacts are recommended to look at NIST's XM Trestle runs on almost all Python platforms (e.g. Linux, Mac, Windows), is available on PyPi and can be easily installed via pip. It is under active development and new releases are made available regularly.\ To install run: `pip install compliance-trestle`\ -See [Install trestle in a python virtual environment](https://oscal-compass.github.io/compliance-trestle/python_trestle_setup/) for the full installation guide. +See [Install trestle in a python virtual environment](https://oscal-compass.github.io/compliance-trestle/latest/installation/) for the full installation guide. ## Complete documentation and tutorials -Complete documentation, tutorials, and background on compliance can be found [here](https://oscal-compass.github.io/compliance-trestle). +Complete documentation, tutorials, and background on compliance can be found [here](https://oscal-compass.github.io/compliance-trestle/latest). ## Agile Authoring @@ -101,7 +101,7 @@ Please refer to the community [README](https://github.com/oscal-compass/communit ## Contributing to Trestle -Our project welcomes external contributions. Please consult [contributing](https://oscal-compass.github.io/compliance-trestle/contributing/mkdocs_contributing/) to get started. +Our project welcomes external contributions. Please consult [contributing](https://oscal-compass.github.io/compliance-trestle/latest/contributing/mkdocs_contributing/) to get started. ## Code of Conduct diff --git a/docs/tutorials/Transformers_and_Tasks/OCP4_CIS_profile_to_oscal_cd.md b/docs/tutorials/Transformers_and_Tasks/OCP4_CIS_profile_to_oscal_cd.md index 119647497..ceb12f7f9 100644 --- a/docs/tutorials/Transformers_and_Tasks/OCP4_CIS_profile_to_oscal_cd.md +++ b/docs/tutorials/Transformers_and_Tasks/OCP4_CIS_profile_to_oscal_cd.md @@ -17,7 +17,7 @@ The second is a one-command transformation from `.profile` to `OSCAL.json`. ## Step 1: Install trestle in a Python virtual environment -Follow the instructions [here](https://oscal-compass.github.io/compliance-trestle/python_trestle_setup/) to install trestle in a virtual environment. +Follow the instructions [here](https://oscal-compass.github.io/compliance-trestle/latest/installation/) to install trestle in a virtual environment. ## Step 2: Transform profile data (CIS benchmarks) diff --git a/docs/tutorials/Transformers_and_Tasks/csv_to_oscal_cd.md b/docs/tutorials/Transformers_and_Tasks/csv_to_oscal_cd.md index 13aa4898d..90c16d220 100644 --- a/docs/tutorials/Transformers_and_Tasks/csv_to_oscal_cd.md +++ b/docs/tutorials/Transformers_and_Tasks/csv_to_oscal_cd.md @@ -169,7 +169,7 @@ The below table represents the expectations of trestle task `csv-to-oscal-cd` fo ## *Step 1: Install trestle in a Python virtual environment* -Follow the instructions [here](https://oscal-compass.github.io/compliance-trestle/python_trestle_setup/) to install trestle in a virtual environment. +Follow the instructions [here](https://oscal-compass.github.io/compliance-trestle/latest/installation/) to install trestle in a virtual environment. ## *Step 2: Transform profile data (CIS benchmarks)* diff --git a/docs/tutorials/Trestle_authoring/ssp_profile_catalog_authoring.md b/docs/tutorials/Trestle_authoring/ssp_profile_catalog_authoring.md index 3a8881692..ff3185d37 100644 --- a/docs/tutorials/Trestle_authoring/ssp_profile_catalog_authoring.md +++ b/docs/tutorials/Trestle_authoring/ssp_profile_catalog_authoring.md @@ -321,7 +321,7 @@ Access control policy and procedures address the controls in the AC family that - + ## Control Implementation Guidance diff --git a/docs/tutorials/Trestle_authoring/trestle_author.md b/docs/tutorials/Trestle_authoring/trestle_author.md index 9ddffa333..3340861f1 100644 --- a/docs/tutorials/Trestle_authoring/trestle_author.md +++ b/docs/tutorials/Trestle_authoring/trestle_author.md @@ -690,7 +690,7 @@ CLI evocation: > trestle author catalog-assemble -The `catalog` author commands allow you to convert a control catalog to markdown and edit its control statement, then assemble markdown back into an OSCAL catalog with the modifications to the statement. Items in the statement may be edited or added. For more details on its usage please see [the catalog authoring tutorial](https://oscal-compass.github.io/compliance-trestle/tutorials/ssp_profile_catalog_authoring/ssp_profile_catalog_authoring). +The `catalog` author commands allow you to convert a control catalog to markdown and edit its control statement, then assemble markdown back into an OSCAL catalog with the modifications to the statement. Items in the statement may be edited or added. For more details on its usage please see [the catalog authoring tutorial](https://oscal-compass.github.io/compliance-trestle/latest/tutorials/Trestle_authoring/ssp_profile_catalog_authoring/). ### Profile authoring @@ -704,7 +704,7 @@ CLI evocation: > trestle author profile-assemble -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://oscal-compass.github.io/compliance-trestle/tutorials/ssp_profile_catalog_authoring/ssp_profile_catalog_authoring). +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://oscal-compass.github.io/compliance-trestle/latest/tutorials/Trestle_authoring/ssp_profile_catalog_authoring/). ### Profile generation with inheritance @@ -719,7 +719,7 @@ All components must have exported provided statements, no exported responsibilit 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://oscal-compass.github.io/compliance-trestle/tutorials/ssp_profile_catalog_authoring/ssp_profile_catalog_authoring). +For more details on its usage please see [the ssp-filter tutorial](https://oscal-compass.github.io/compliance-trestle/latest/tutorials/Trestle_authoring/ssp_profile_catalog_authoring/). ### SSP authoring @@ -735,7 +735,7 @@ CLI evocation: The `ssp-generate` sub-command creates a partial SSP (System Security Plan) from a profile and optional yaml header file. `ssp-assemble` can then assemble the markdown files into a single json SSP file. -For more details on its usage please see [the ssp authoring tutorial](https://oscal-compass.github.io/compliance-trestle/tutorials/ssp_profile_catalog_authoring/ssp_profile_catalog_authoring). +For more details on its usage please see [the ssp authoring tutorial](https://oscal-compass.github.io/compliance-trestle/latest/tutorials/Trestle_authoring/ssp_profile_catalog_authoring/). ### SSP Content Filtering @@ -757,6 +757,6 @@ You may filter by a combination of a profile, list of component names, implement 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://oscal-compass.github.io/compliance-trestle/tutorials/ssp_profile_catalog_authoring/ssp_profile_catalog_authoring). +For more details on its usage please see [the ssp-filter tutorial](https://oscal-compass.github.io/compliance-trestle/latest/tutorials/Trestle_authoring/ssp_profile_catalog_authoring/). diff --git a/tests/trestle/core/commands/author/ssp_test.py b/tests/trestle/core/commands/author/ssp_test.py index ec4df8d7e..0df5ce34f 100644 --- a/tests/trestle/core/commands/author/ssp_test.py +++ b/tests/trestle/core/commands/author/ssp_test.py @@ -1160,14 +1160,19 @@ def test_ssp_gen_and_assemble_add_props(tmp_trestle_dir: pathlib.Path) -> None: impl_reqs = assem_ssp.control_implementation.implemented_requirements impl_req = next((i_req for i_req in impl_reqs if i_req.control_id == 'ac-1'), None) assert len(impl_req.props) == 1 - assert impl_req.props[0].name == 'prop_with_ns' - assert impl_req.props[0].value == 'prop with ns' - assert impl_req.props[0].ns == 'https://my_new_namespace' + assert impl_req.props[0].name == 'prop_with_ns' # type: ignore + assert impl_req.props[0].value == 'prop with ns' # type: ignore + assert impl_req.props[0].ns == 'https://my_new_namespace' # type: ignore smt_a = next((smt for smt in impl_req.statements if smt.statement_id == 'ac-1_smt.a'), None) assert len(smt_a.props) == 1 - assert smt_a.props[0].name == 'smt_prop' - assert smt_a.props[0].value == 'smt prop' + assert smt_a.props[0].name == 'smt_prop' # type: ignore + assert smt_a.props[0].value == 'smt prop' # type: ignore + + # Run again and check that there is no change + assert ssp_assemble._run(args) == 0 + assem_ssp_2, _ = ModelUtils.load_model_for_class(tmp_trestle_dir, ssp_name, ossp.SystemSecurityPlan) + assert assem_ssp_2.metadata.last_modified == assem_ssp.metadata.last_modified def test_ssp_gen_and_assemble_implementation_parts(tmp_trestle_dir: pathlib.Path, monkeypatch: MonkeyPatch) -> None: diff --git a/trestle/core/catalog/catalog_reader.py b/trestle/core/catalog/catalog_reader.py index 4ed76d132..2e951be00 100644 --- a/trestle/core/catalog/catalog_reader.py +++ b/trestle/core/catalog/catalog_reader.py @@ -349,7 +349,7 @@ def _add_props_to_imp_req( # add the props at control level if props: imp_req.props = as_list(imp_req.props) - imp_req.props.extend(props) + ControlInterface.reconcile_props(imp_req, props) # add the props at the part level for label, part_id in control_part_id_map.items(): @@ -359,7 +359,7 @@ def _add_props_to_imp_req( for statement in as_list(imp_req.statements): if statement.statement_id == part_id: statement.props = as_list(statement.props) - statement.props.extend(props) + ControlInterface.reconcile_props(statement, props) @staticmethod def _update_ssp_with_md_header( diff --git a/trestle/core/control_interface.py b/trestle/core/control_interface.py index ab3c4c95e..69611ae06 100644 --- a/trestle/core/control_interface.py +++ b/trestle/core/control_interface.py @@ -1121,6 +1121,18 @@ def insert_status_in_props(item: TypeWithProps, status: common.ImplementationSta prop = ControlInterface._status_as_prop(status) ControlInterface._replace_prop(item, prop) + @staticmethod + def reconcile_props(item: TypeWithProps, props: List[common.Property]) -> None: + """Add properties to an item with properties while replacing existing.""" + names = [prop.name for prop in as_list(item.props)] + item.props = as_list(item.props) + for prop in props: + if prop.name in names: + index = names.index(prop.name) + item.props[index] = prop + else: + item.props.append(prop) + @staticmethod def _copy_status_in_props(dest: TypeWithProps, src: TypeWithProps) -> None: """Copy status in props from one object to another."""