diff --git a/.github/workflows/python-test.yml b/.github/workflows/python-test.yml index 3d8ef5178..6eac49c70 100644 --- a/.github/workflows/python-test.yml +++ b/.github/workflows/python-test.yml @@ -151,7 +151,7 @@ jobs: - name: Upload artifact if: steps.core-version.outputs.core == 'true' - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: coverage path: coverage.xml @@ -184,7 +184,7 @@ jobs: run: | make develop - name: Get coverage - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v4 with: name: coverage - name: SonarCloud Scan diff --git a/CHANGELOG.md b/CHANGELOG.md index 27833eef6..6be554ecb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,204 @@ +## v3.4.0 (2024-08-23) + +### Chore + +* chore: Merge back version tags and changelog into develop. ([`724ac16`](https://github.com/oscal-compass/compliance-trestle/commit/724ac169389e4d80cca4c336e17fbd5bed4cedff)) + +### Documentation + +* docs: update maintainers list to reflect active maintainers (#1638) + +Signed-off-by: Jennifer Power <barnabei.jennifer@gmail.com> +Co-authored-by: mrgadgil <49280244+mrgadgil@users.noreply.github.com> ([`f8daaae`](https://github.com/oscal-compass/compliance-trestle/commit/f8daaae2e57c9a582b9a94bd5128ed55a890a3bf)) + +* docs: updates CODE_OF_CONDUCT urls in README and website (#1635) + +Signed-off-by: Jennifer Power <barnabei.jennifer@gmail.com> ([`08f387a`](https://github.com/oscal-compass/compliance-trestle/commit/08f387a074734a5ddd079d5f613220aa6b44242c)) + +* docs: adds ROADMAP.md with high level roadmap description (#1626) + +* docs: adds ROADMAP.md with high level roadmap description + +Signed-off-by: Jennifer Power <barnabei.jennifer@gmail.com> + +* chore: refines working in ROADMAP.md for clarity + +Signed-off-by: Jennifer Power <barnabei.jennifer@gmail.com> + +* docs: updates ROADMAP.md with timeline information + +Signed-off-by: Jennifer Power <barnabei.jennifer@gmail.com> + +* docs: rewords section on iterations + +Adds more clarity around what takes place in +the 12-week period. No changes to the overall plan. + +Signed-off-by: Jennifer Power <barnabei.jennifer@gmail.com> + +--------- + +Signed-off-by: Jennifer Power <barnabei.jennifer@gmail.com> ([`ed10dad`](https://github.com/oscal-compass/compliance-trestle/commit/ed10dadee72ac2bedf07c71095e598dc6f95b5bf)) + +### Feature + +* feat: add parameter aggregation support for SSP (#1668) + +Signed-off-by: Alejandro Jose Leiva Palomo <alejandro.leiva.palomo@ibm.com> ([`b2611d1`](https://github.com/oscal-compass/compliance-trestle/commit/b2611d1382c6ff1e9e1864e7fa1726dd7ad07eb5)) + +* feat: adds dependabot configuration for continous updates (#1647) + +Signed-off-by: Jennifer Power <barnabei.jennifer@gmail.com> ([`4862c4a`](https://github.com/oscal-compass/compliance-trestle/commit/4862c4ac0ec9ce06988f1b6d75ad5986acbd3b78)) + +* feat: adds implementation parts to This System component in markdown (#1536) + +* feat: adds implementation part prompts for This System + +Changes in assembly are due to changes in the markdown breaking the unit tests +because the This System component is associated with each statement + +Signed-off-by: Jennifer Power <barnabei.jennifer@gmail.com> + +* fix: removes this system comp prose and status duplication + +The process_main_component was overwriting the first prose +response to all the parts + +Signed-off-by: Jennifer Power <barnabei.jennifer@gmail.com> + +* chore: removes TODO comment for bug review + +Signed-off-by: Jennifer Power <barnabei.jennifer@gmail.com> + +* chore: updates workding in comments in control_writer.py + +Signed-off-by: Jennifer Power <barnabei.jennifer@gmail.com> + +* chore: moves part_a_text_edited into applicable unit tests + +Signed-off-by: Jennifer Power <barnabei.jennifer@gmail.com> + +* refactor: add include-all-parts to make part responses optional + +To ensure the default markdown is not overly verbose, writing all +implementation parts and the inclusion of This System is optional. + +Signed-off-by: Jennifer Power <barnabei.jennifer@gmail.com> + +* docs: updates documentation with include-all-parts description + +Signed-off-by: Jennifer Power <barnabei.jennifer@gmail.com> + +* chore: updates comments and docstring in control_writer.py updates + +The goal is to increase the usefulness of the comments + +Signed-off-by: Jennifer Power <barnabei.jennifer@gmail.com> + +* fix: updates docstring in control_writer.py to improve clarity + +Signed-off-by: Jennifer Power <barnabei.jennifer@gmail.com> + +--------- + +Signed-off-by: Jennifer Power <barnabei.jennifer@gmail.com> ([`54706af`](https://github.com/oscal-compass/compliance-trestle/commit/54706af0f9d428d10451823aa7d8d0f92a86e3eb)) + +### Fix + +* fix: cis benchmarks to catalog task, which mistakenly does not see all columns (#1657) + +* fix: allow sheet specification + +Signed-off-by: Lou DeGenaro <lou.degenaro@gmail.com> + +* fix: number of columns is too small by 1 + +Signed-off-by: Lou DeGenaro <lou.degenaro@gmail.com> + +* Fix: examine all columns + +Signed-off-by: Lou DeGenaro <lou.degenaro@gmail.com> + +--------- + +Signed-off-by: Lou DeGenaro <lou.degenaro@gmail.com> ([`6c2d3f3`](https://github.com/oscal-compass/compliance-trestle/commit/6c2d3f3bd8d6eeaf04e0a931ce39b8b52646e95a)) + +* fix: skips sonar scans for dependabot updates (#1656) + +* fix: skips sonar scans for dependabot updates + +Dependabot updates only include third party dependency updates + +Signed-off-by: Jennifer Power <barnabei.jennifer@gmail.com> + +* fix: updates workflow if statement formatting + +Signed-off-by: Jennifer Power <barnabei.jennifer@gmail.com> + +--------- + +Signed-off-by: Jennifer Power <barnabei.jennifer@gmail.com> ([`050c425`](https://github.com/oscal-compass/compliance-trestle/commit/050c425771ccb52bd263b011e37e128a1eb8205f)) + +* fix: updates invalid dependabot configuation (#1650) + +Signed-off-by: Jennifer Power <barnabei.jennifer@gmail.com> ([`e27f0cd`](https://github.com/oscal-compass/compliance-trestle/commit/e27f0cda76a89c7fe60e425916e8b85c3cb1fc30)) + +* fix: correct logo redirection for PyPi page (#1644) + +* fix: correct logo redirection for PyPi page + +Signed-off-by: Alejandro Jose Leiva Palomo <alejandro.leiva.palomo@ibm.com> + +* fix: change develop to main branch in the logo link + +Signed-off-by: Alejandro Jose Leiva Palomo <alejandro.leiva.palomo@ibm.com> + +--------- + +Signed-off-by: Alejandro Jose Leiva Palomo <alejandro.leiva.palomo@ibm.com> ([`2c4899a`](https://github.com/oscal-compass/compliance-trestle/commit/2c4899a809cb28855943f4f3e89f3e9d771aaf1e)) + +* fix: default value for optional string params should be None (#1621) + +* fix: default value for optional string params should be None + +Signed-off-by: George Vauter <gvauter@redhat.com> + +* pin setuptools to min version suppported by setuptools_scm + +Signed-off-by: George Vauter <gvauter@redhat.com> + +* fix: add include_all_parts to undo accidental deletion + +Signed-off-by: George Vauter <gvauter@redhat.com> + +--------- + +Signed-off-by: George Vauter <gvauter@redhat.com> ([`f81f567`](https://github.com/oscal-compass/compliance-trestle/commit/f81f5674ee2996532524eb014daadbbdbd33e6bb)) + +* fix: allow forks to correctly run the pipelines (#1633) + +A small set of cleanups to the pipelines. + +--------- + +Signed-off-by: Jennifer Power <barnabei.jennifer@gmail.com> +Signed-off-by: Chris Butler <chris.butler@redhat.com> +Co-authored-by: Jennifer Power <barnabei.jennifer@gmail.com> ([`af4e5a2`](https://github.com/oscal-compass/compliance-trestle/commit/af4e5a286279a0aebf70b1cb87fa97651711ada2)) + +### Unknown + +* Merge pull request #1670 from oscal-compass/develop + +chore: Trestle release ([`2420d97`](https://github.com/oscal-compass/compliance-trestle/commit/2420d9740fbaa78f8a8a4b92c54747984db70717)) + +* fix - make status and mitre column optional (#1649) + +Signed-off-by: Lou DeGenaro <lou.degenaro@gmail.com> ([`47e6936`](https://github.com/oscal-compass/compliance-trestle/commit/47e6936e47d1fa0840aef5c26f36140438f03c98)) + + ## v3.3.0 (2024-07-15) ### Chore diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1484f000e..2757a4db9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -32,6 +32,16 @@ review to indicate acceptance. A change requires LGTMs from one of the maintaine For a list of the maintainers, see the [maintainers](https://oscal-compass.github.io/compliance-trestle/maintainers/) page. +### Trestle updating and release logistics + +Contributors should make a working copy (branch or fork) from the develop branch of `trestle`. +Contributors should update the working copy with changes, then create a pull request to merge into the develop branch. +Upon approval from reviewer(s), the working copy is squashed and merged into the develop branch. +Upon a cadence established by the maintainers, the develop branch is merged into the main branch and a new release is uniquely numbered and pushed to [pypi](https://pypi.org/project/compliance-trestle/). + +`trestle` employs `semantic release` to automatically control release numbering. +Code deliveries should be tagged with prefix `fix:` for changes that are bug fixes or `feat:` for changes that are new features. See [allowed_tags](https://python-semantic-release.readthedocs.io/en/latest/commit-parsing.html#:~:text=The%20default%20configuration%20options%20for%20semantic_release.commit_parser.AngularCommitParser%20are%3A) for a list of supported tags. + ### Trestle merging and release workflow `trestle` is operating on a simple, yet opinionated, method for continuous integration. It's designed to give developers a coherent understanding of the objectives of other past developers. @@ -86,7 +96,7 @@ Software License 2.0. Using the SPDX format is the simplest approach. e.g. ```text -# Copyright (c) 2020 IBM Corp. All rights reserved. +# Copyright (c) 2024 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. @@ -181,6 +191,8 @@ Both of these repositories are submodules in the trestle project. In order to de ### Code style and formating +Python code should generally follow [PEP 8](https://peps.python.org/pep-0008/). + `trestle` uses [yapf](https://github.com/google/yapf) for code formatting and [flake8](https://flake8.pycqa.org/en/latest/) for code styling. It also uses [pre-commit](https://pre-commit.com/) hooks that are integrated into the development process and the CI. When you run `make develop` you are ensuring that the pre-commit hooks are installed and updated to their latest versions for this repository. This ensures that all delivered code has been properly formatted and passes the linter rules. See the [pre-commit configuration file](https://github.com/oscal-compass/compliance-trestle/blob/develop/.pre-commit-config.yaml) for details on `yapf` and `flake8` configurations. @@ -248,4 +260,4 @@ ______________________________________________________________________ ##### Overview of process to take OSCAL models and upgrade trestle Python code - +![](images/trestle-OSCAL-upgrade.png) diff --git a/Makefile b/Makefile index 0482664ff..362e6e68d 100644 --- a/Makefile +++ b/Makefile @@ -86,7 +86,7 @@ docs-automation:: python ./scripts/website_automation.py docs-validate:: docs-automation - mkdocs build -c -s + mkdocs build -v -c -s rm -rf site docs-serve: docs-automation @@ -117,4 +117,4 @@ pylint-test: pylint tests --rcfile=.pylintrc_tests check-for-changes: - python scripts/have_files_changed.py -u \ No newline at end of file + python scripts/have_files_changed.py -u diff --git a/README.md b/README.md index 2407cf760..e9b28a9e6 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ ![[Code Coverage](https://sonarcloud.io/dashboard?id=compliance-trestle)](https://sonarcloud.io/api/project_badges/measure?project=compliance-trestle&metric=coverage) ![[Quality gate](https://sonarcloud.io/dashboard?id=compliance-trestle)](https://sonarcloud.io/api/project_badges/measure?project=compliance-trestle&metric=alert_status) ![[Pypi](https://pypi.org/project/compliance-trestle/)](https://img.shields.io/pypi/dm/compliance-trestle) -![GitHub Actions status](https://img.shields.io/github/workflow/status/oscal-compass/compliance-trestle/Trestle%20PR%20pipeline?event=push) +![GitHub Actions status](https://github.com/oscal-compass/compliance-trestle/actions/workflows/python-test.yml/badge.svg?branch=develop) Trestle is an ensemble of tools that enable the creation, validation, and governance of documentation artifacts for compliance needs. It leverages NIST's [OSCAL](https://pages.nist.gov/OSCAL/) as a standard data format for interchange between tools and people, and provides an opinionated approach to OSCAL adoption. @@ -112,7 +112,7 @@ If you would like to see the detailed LICENSE click [here](LICENSE). Consult [contributors](https://github.com/oscal-compass/compliance-trestle/graphs/contributors) for a list of authors and [maintainers](MAINTAINERS.md) for the core team. ```text -# Copyright (c) 2020 IBM Corp. All rights reserved. +# Copyright (c) 2024 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. @@ -125,5 +125,17 @@ Consult [contributors](https://github.com/oscal-compass/compliance-trestle/graph # 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. - ``` + +______________________________________________________________________ + +We are a Cloud Native Computing Foundation sandbox project. + + + + + + +The Linux Foundation® (TLF) has registered trademarks and uses trademarks. For a list of TLF trademarks, see [Trademark Usage](https://www.linuxfoundation.org/legal/trademark-usage)". + +*Trestle was originally created by IBM.* diff --git a/docs/cli.md b/docs/cli.md index f7ef55b9e..bd44ac92e 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -113,11 +113,11 @@ Users can query the contents of files using `trestle describe`, and probe the co OSCAL models are rich and contain multiple nested data structures. Given this, a mechanism is required to address _elements_ /_attributes_ within an oscal object. -This accessing method is called 'element path' and is similar to _jsonPath_. Commands provide element path by a `-e` argument where available, e.g. trestle split -f catalog.json -e 'catalog.metadata.\*'. This path is used whenever specifying an attribute or model, rather than exposing trestle's underlying object model name. Users can refer to [NIST's json outline](https://pages.nist.gov/OSCAL/reference/latest/complete/json-outline/) to understand object names in trestle. +This accessing method is called 'element path' and is similar to _jsonPath_. Commands provide element path by a `-e` argument where available, e.g. trestle split -f catalog.json -e 'catalog.metadata.\*'. This path is used whenever specifying an attribute or model, rather than exposing trestle's underlying object model name. Users can refer to [NIST's json outline](https://pages.nist.gov/OSCAL-Reference/models/latest/complete/json-outline/) to understand object names in trestle. ### Rules for element path -1. Element path is an expression of the attribute names, [in json form](https://pages.nist.gov/OSCAL/reference/latest/complete/json-outline/) , concatenated by a period (`.`). +1. Element path is an expression of the attribute names, [in json form](https://pages.nist.gov/OSCAL-Reference/models/latest/complete/json-outline/) , concatenated by a period (`.`). 1. E.g. The metadata in a catalog is referred to as `catalog.metadata` 1. Element paths are relative to the file. 1. e.g. For `metadata.json` roles would be referred to as `metadata.roles`, from the catalog file that would be `catalog.metadata.roles` diff --git a/docs/contributing/images/trestle-OSCAL-upgrade.png b/docs/contributing/images/trestle-OSCAL-upgrade.png new file mode 100644 index 000000000..63e73799f Binary files /dev/null and b/docs/contributing/images/trestle-OSCAL-upgrade.png differ diff --git a/docs/contributing/trestle_oscal_object_model.md b/docs/contributing/trestle_oscal_object_model.md index 71083d6d2..a77b47a4f 100644 --- a/docs/contributing/trestle_oscal_object_model.md +++ b/docs/contributing/trestle_oscal_object_model.md @@ -9,7 +9,7 @@ This functionality, which is built on [pydantic](https://pydantic-docs.helpmanua ## Mapping and variance with OSCAL names. -The underlying object model that trestle relies on is the json schema published by NIST [here](https://github.com/usnistgov/OSCAL/tree/main/json/schema). In understanding these models the [model reference page](https://pages.nist.gov/OSCAL/reference/1.0.0/) is an indispensable source. +The underlying object model that trestle relies on is the json schema published by NIST [here](https://github.com/usnistgov/OSCAL/releases/latest). In understanding these models the [model reference page](https://pages.nist.gov/OSCAL-Reference/models/) is an indispensable source. When generating the python data class based models we have tried to be as faithful as we can to the naming convention provided by OSCAL. This is the hierarchy of rules that we have used: diff --git a/docs/index.md b/docs/index.md index bee864d33..3759cecaf 100644 --- a/docs/index.md +++ b/docs/index.md @@ -6,7 +6,7 @@ ![[Code Coverage](https://sonarcloud.io/dashboard?id=compliance-trestle)](https://sonarcloud.io/api/project_badges/measure?project=compliance-trestle&metric=coverage) ![[Quality gate](https://sonarcloud.io/dashboard?id=compliance-trestle)](https://sonarcloud.io/api/project_badges/measure?project=compliance-trestle&metric=alert_status) ![[Pypi](https://pypi.org/project/compliance-trestle/)](https://img.shields.io/pypi/dm/compliance-trestle) -![GitHub Actions status](https://img.shields.io/github/workflow/status/oscal-compass/compliance-trestle/Trestle%20PR%20pipeline?event=push) +![GitHub Actions status](https://github.com/oscal-compass/compliance-trestle/actions/workflows/python-test.yml/badge.svg?branch=develop) Trestle is an ensemble of tools that enable the creation, validation, and governance of documentation artifacts for compliance needs. It leverages NIST's [OSCAL](https://pages.nist.gov/OSCAL/documentation/) as a standard data format for interchange between tools and people, and provides an opinionated approach to OSCAL adoption. @@ -65,7 +65,7 @@ natively supports only `json` and `yaml` formats at this time. Future roadmap anticipates that support for xml [import](https://github.com/oscal-compass/compliance-trestle/issues/177) and [upstream references](https://github.com/oscal-compass/compliance-trestle/issues/178) will be enabled. However, it is expected that full support will remain only for `json` and `yaml`. -Users needing to import XML OSCAL artifacts are recommended to look at NIST's XML to json conversion page [here](https://github.com/usnistgov/OSCAL/tree/master/json#oscal-xml-to-json-converters). +Users needing to import XML OSCAL artifacts are recommended to look at NIST's OSCAL converters page [here](https://github.com/usnistgov/OSCAL/blob/main/build/README.md#converters). ## Python codebase, easy installation via pip @@ -77,15 +77,15 @@ Compliance trestle is currently stable and is based on NIST OSCAL version 1.0.4, ## Contributing to Trestle -Our project welcomes external contributions. Please consult [contributing](contributing/mkdocs_contributing/) to get started. +Our project welcomes external contributions. Please consult [contributing](contributing/mkdocs_contributing.md) to get started. ## License & Authors -If you would like to see the detailed LICENSE click [here](license/). -Consult [contributors](https://github.com/oscal-compass/compliance-trestle/graphs/contributors) for a list of authors and [maintainers](maintainers/) for the core team. +If you would like to see the detailed LICENSE click [here](license.md). +Consult [contributors](https://github.com/oscal-compass/compliance-trestle/graphs/contributors) for a list of authors and [maintainers](maintainers.md) for the core team. ```text -# Copyright (c) 2020 IBM Corp. All rights reserved. +# Copyright (c) 2024 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. @@ -98,5 +98,17 @@ Consult [contributors](https://github.com/oscal-compass/compliance-trestle/graph # 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. - ``` + +______________________________________________________________________ + +We are a Cloud Native Computing Foundation sandbox project. + + + + + + +The Linux Foundation® (TLF) has registered trademarks and uses trademarks. For a list of TLF trademarks, see [Trademark Usage](https://www.linuxfoundation.org/legal/trademark-usage)". + +*Trestle was originally created by IBM.* diff --git a/docs/plugins/compliance-trestle-fedramp.md b/docs/plugins/compliance-trestle-fedramp.md index 8b5069bb9..843039eef 100644 --- a/docs/plugins/compliance-trestle-fedramp.md +++ b/docs/plugins/compliance-trestle-fedramp.md @@ -4,7 +4,7 @@ This plugin provides functionality for validating an SSP for FedRAMP compliance. ## `trestle fedramp-validate` -This command allows users to validate existing OSCAL SSP file (in JSON or YAML format) for FedRAMP compliance. For example, `trestle fedramp-validate -f /local_dir/ssp.json -o report/` will validate `ssp.json` file for fedramp complaince and store the validation reports in `report` folder. +This command allows users to validate existing OSCAL SSP file (in JSON or YAML format) for FedRAMP compliance. For example, `trestle fedramp-validate -f /local_dir/ssp.json -o report/` will validate `ssp.json` file for fedramp compliance and store the validation reports in `report` folder. The following options are supported: @@ -12,3 +12,15 @@ The following options are supported: - `-o or --output`: specifies the name of the output directory where the validation reports will be stored. It may be an absolute or relative path. The output directory should already exist. This is also a required option. The validation reports are created in XML and HTML format and provide details on which part of the SSP are not complaint as per FedRAMP specification. + +## `trestle fedramp-transform` + +This command allows users to extract information from an OSCAL SSP and transform it into a Word document based on the FedRAMP SSP Appendix A Template. The templates for the High, Moderate, and Low baseline security control requirements were retrieved from this [location](https://www.fedramp.gov/documents-templates/) and are bundled with the application. The `Control Summary Information` tables are populated for each control based on the OSCAL SSP. + +For example, `trestle fedramp-transform -n ssp-name -l 'high' -o my_ssp.docx` will transform the OSCAL SSP file `ssp-name` into a Word document `my_ssp.docx` based on the SSP Appendix A - High FedRAMP Security Controls template. + +The following options are supported: + +- `-n or --ssp-name`: The name of the OSCAL SSP imported into trestle workspace. This is a required option. +- `-l or --level`: The baseline level corresponding to the template. This is high, moderate, low. This is a required option. +- `-o or --output-file`: The output location for the populated Word document. This is also a required option. diff --git a/docs/tutorials/continuous-compliance/continuous-compliance.md b/docs/tutorials/continuous-compliance/continuous-compliance.md index 0bd7e0d3e..a8f5b0c8c 100644 --- a/docs/tutorials/continuous-compliance/continuous-compliance.md +++ b/docs/tutorials/continuous-compliance/continuous-compliance.md @@ -17,7 +17,7 @@ Moreover, assuring continuous compliance across multiple cloud vendors can compl Common sense dictates that standardization would simplify matters. The National Institute of Standards and Technologies (NIST) is developing the Open Security Controls Assessment Language ([OSCAL](https://pages.nist.gov/OSCAL)). -The compliance-[trestle](https://oscal-compass.github.io/compliance-trestle/) open source github project is an effort to employ [OSCAL](https://pages.nist.gov/OSCAL) for compliance standardization and automation. Of great utility is the [trestle](https://oscal-compass.github.io/compliance-trestle/) oscal module that facilitates transformation of data to/from Python object representations in accordance with the [OSCAL](https://pages.nist.gov/OSCAL) schemas. +The compliance-[trestle](../../index.md) open source github project is an effort to employ [OSCAL](https://pages.nist.gov/OSCAL) for compliance standardization and automation. Of great utility is the [trestle](../../index.md) oscal module that facilitates transformation of data to/from Python object representations in accordance with the [OSCAL](https://pages.nist.gov/OSCAL) schemas. #### Simple Continuous Compliance Architecture @@ -25,7 +25,7 @@ The compliance-[trestle](https://oscal-compass.github.io/compliance-trestle/) op Cloud Services can often be configured to monitor (and sometimes enforce) policies. Examples include OpenShift Compliance Operator and Tanium. However, the compliance reporting “raw” data produced is unique to each. -Two steps are needed to ascertain your compliance posture. Step 1 is to transform available compliance “raw” data into standardized form ([OSCAL](https://pages.nist.gov/OSCAL)). Step 2 is to examine the [OSCAL](https://pages.nist.gov/OSCAL) data and assemble a compliance posture for the controls and components of interest. And [trestle](https://oscal-compass.github.io/compliance-trestle/) is the go-to solution. +Two steps are needed to ascertain your compliance posture. Step 1 is to transform available compliance “raw” data into standardized form ([OSCAL](https://pages.nist.gov/OSCAL)). Step 2 is to examine the [OSCAL](https://pages.nist.gov/OSCAL) data and assemble a compliance posture for the controls and components of interest. And [trestle](../../index.md) is the go-to solution. #### Step 1 – Transformation @@ -33,14 +33,14 @@ The bad news is that a transformer to [OSCAL](https://pages.nist.gov/OSCAL) is n However, there is plenty of good news: -- a transformer for your Cloud Service type may already exist, such as: [Tanium to OSCAL](https://github.com/oscal-compass/compliance-trestle/blob/main/trestle/tasks/tanium-result-to-oscal-ar.py), [OpenShift Compliance Operator to OSCAL](https://github.com/oscal-compass/compliance-trestle/blob/main/trestle/tasks/xccdf_result_to_oscal_ar.py) +- a transformer for your Cloud Service type may already exist, such as: [Tanium to OSCAL](https://github.com/oscal-compass/compliance-trestle/blob/main/trestle/tasks/tanium_result_to_oscal_ar.py), [OpenShift Compliance Operator to OSCAL](https://github.com/oscal-compass/compliance-trestle/blob/main/trestle/tasks/xccdf_result_to_oscal_ar.py) - once a transformer for a Cloud Service type has been written, it can be open-sourced/re-used -- writing a transformer is fairly easy: just a few lines of Python code using [trestle](https://oscal-compass.github.io/compliance-trestle/) as a foundation +- writing a transformer is fairly easy: just a few lines of Python code using [trestle](../../index.md) as a foundation In the case of Tanium, the [OSCAL](https://pages.nist.gov/OSCAL) compliance data document is a *System Assessment Results* fragment with *Findings* and *Observations*, while in the case of OpenShift Compliance Operator there are *Observations* only. -Tutorials are available to show you: how to [run a transformer](https://oscal-compass.github.io/compliance-trestle/tutorials/task.tanuim-to-oscal/transformation/), how to [write a transformer](https://oscal-compass.github.io/compliance-trestle/tutorials/task.transformer-construction/transformer-construction/). +Tutorials are available to show you: how to [run a transformer](../task.tanium-result-to-oscal-ar/transformation.md), how to [write a transformer](../task.transformer-construction/transformer-construction.md). #### Step 2 – Reporting -Coming soon is a [trestle](https://oscal-compass.github.io/compliance-trestle/) tool to assemble the [OSCAL](https://pages.nist.gov/OSCAL) fragments documents together using [OSCAL](https://pages.nist.gov/OSCAL) compliance configuration data (*System Assessment Plan* and *System Security Plan*) into a complete *System Assessment Results*. +Coming soon is a [trestle](../../index.md) tool to assemble the [OSCAL](https://pages.nist.gov/OSCAL) fragments documents together using [OSCAL](https://pages.nist.gov/OSCAL) compliance configuration data (*System Assessment Plan* and *System Security Plan*) into a complete *System Assessment Results*. 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 9a1d2acbc..0286b5d58 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 @@ -793,8 +793,7 @@ The markdown header lists all the rules that apply to this control, along with t The values for rule parameters are specified using the normal `SetParameter` mechanism in the ControlImplementation, but it's important to note that there are two different types of `SetParameter`: Those that apply to the normal parameters of the control, and those that apply strictly to the rules. -Note that markdown for a control is only created if there are rules associated with the control, and within the markdown the only parts written out that -prompt for responses are parts that have rules assigned. Thus the output markdown directory may be highly pruned of both controls and groups of controls if only some controls have rules associated. +Note that markdown is created for all of the controls in the `Component` control implementation. However, when processing control parts, the part is only written out if there are associated rules or pre-filled implementation descriptions. In addition, the rules should be regarded as read-only from the editing perspective, and you cannot change the rules associated with a control or its parts. But you may edit the rule parameter values as described in the comments of the markdown file under `x-trestle-comp-def-rules-param-vals`. You may also edit the prose and implementation status associated with a statement part at the bottom of the markdown file. diff --git a/docs/tutorials/task.ocp4-cis-profile-to-oscal-catalog/transformation.md b/docs/tutorials/task.ocp4-cis-profile-to-oscal-catalog/transformation.md index 5b36df015..00a669a2e 100644 --- a/docs/tutorials/task.ocp4-cis-profile-to-oscal-catalog/transformation.md +++ b/docs/tutorials/task.ocp4-cis-profile-to-oscal-catalog/transformation.md @@ -1,6 +1,6 @@ # Tutorial: Setup for and use of ComplianceAsCode profile to OSCAL Catalog transformer -Here are step by step instructions for setup and transformation of [ComplianceAsCode](https://github.com/ComplianceAsCode/content) profile data files into [NIST](https://www.nist.gov/) standard [OSCAL](https://pages.nist.gov/OSCAL/) [Catalog](https://pages.nist.gov/OSCAL/reference/latest/catalog/json-outline/) using the [compliance-trestle](https://oscal-compass.github.io/compliance-trestle/) tool. +Here are step by step instructions for setup and transformation of [ComplianceAsCode](https://github.com/ComplianceAsCode/content) profile data files into [NIST](https://www.nist.gov/) standard [OSCAL](https://pages.nist.gov/OSCAL/) [Catalog](https://pages.nist.gov/OSCAL-Reference/models/latest/catalog/json-outline/) using the [compliance-trestle](../../index.md) tool. ## *Objective* @@ -12,7 +12,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](../../python_trestle_setup.md) to install trestle in a virtual environment. ## *Step 2: Transform profile data (CIS benchmarks)* diff --git a/docs/tutorials/task.ocp4-cis-profile-to-oscal-cd/transformation.md b/docs/tutorials/task.ocp4-cis-profile-to-oscal-cd/transformation.md index 23184aea1..58dcef079 100644 --- a/docs/tutorials/task.ocp4-cis-profile-to-oscal-cd/transformation.md +++ b/docs/tutorials/task.ocp4-cis-profile-to-oscal-cd/transformation.md @@ -1,6 +1,6 @@ # Tutorial: Setup for and use of ComplianceAsCode profile to OSCAL Component Definition transformer -Here are step by step instructions for setup and transformation of [ComplianceAsCode](https://github.com/ComplianceAsCode/content) profile data files into [NIST](https://www.nist.gov/) standard [OSCAL](https://pages.nist.gov/OSCAL/) [Component Definition](https://pages.nist.gov/OSCAL/reference/latest/component-definition/json-outline/) using the [compliance-trestle](https://oscal-compass.github.io/compliance-trestle/) tool. +Here are step by step instructions for setup and transformation of [ComplianceAsCode](https://github.com/ComplianceAsCode/content) profile data files into [NIST](https://www.nist.gov/) standard [OSCAL](https://pages.nist.gov/OSCAL/) [Component Definition](https://pages.nist.gov/OSCAL-Reference/models/v1.1.2/complete/json-reference/#/component-definition) using the [compliance-trestle](https://oscal-compass.github.io/compliance-trestle/) tool. ## *Objective* diff --git a/docs/tutorials/task.transformer-construction/transformer-construction.md b/docs/tutorials/task.transformer-construction/transformer-construction.md index b6924437c..e5573cd5e 100644 --- a/docs/tutorials/task.transformer-construction/transformer-construction.md +++ b/docs/tutorials/task.transformer-construction/transformer-construction.md @@ -18,7 +18,7 @@ The objective here is to transform your compliance data into valid OSCAL, in par Examples of existing transformers included with trestle are for the OpenShift Compliance Operator [OSCO](https://github.com/oscal-compass/compliance-trestle/blob/develop/trestle/tasks/xccdf_result_to_oscal_ar.py) and -[Tanium](https://github.com/oscal-compass/compliance-trestle/blob/develop/trestle/tasks/tanium-result-to-oscal-ar.py). +[Tanium](https://github.com/oscal-compass/compliance-trestle/blob/develop/trestle/tasks/tanium_result_to_oscal_ar.py). ## *Overview* @@ -407,7 +407,7 @@ There are 2 transformers in trestle. The [xccdf-result-to-oscal-ar](https://github.com/oscal-compass/compliance-trestle/blob/develop/trestle/tasks/xccdf_result_to_oscal_ar.py) transformer emits OSCAL Observations, the simplest partial result. -The [tanium-result-to-oscal-ar](https://github.com/oscal-compass/compliance-trestle/blob/develop/trestle/tasks/tanium-result-to-oscal-ar.py) +The [tanium-result-to-oscal-ar](https://github.com/oscal-compass/compliance-trestle/blob/develop/trestle/tasks/tanium_result_to_oscal_ar.py) transformer emits OSCAL Findings, a more complex partial result. Table of approximate lines of code. diff --git a/images/trestle-OSCAL-upgrade.png b/images/trestle-OSCAL-upgrade.png index eae3bb6ef..63e73799f 100644 Binary files a/images/trestle-OSCAL-upgrade.png and b/images/trestle-OSCAL-upgrade.png differ diff --git a/mkdocs.yml b/mkdocs.yml index 41838da52..9a7d7bf0c 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -3,6 +3,10 @@ extra: analytics: property: G-XT3KGMHSY8 provider: google + consent: + title: Cookie consent + description: >- + We use cookies to recognize your repeated visits and preferences, as well as to measure the effectiveness of our documentation and whether users find what they're searching for. With your consent, you're helping us to make our documentation better. markdown_extensions: - admonition - markdown_include.include @@ -44,6 +48,7 @@ nav: - Trestle's object model: contributing/trestle_oscal_object_model.md - Developer Certificate of Originality: contributing/DCO.md - Trestle plugin mechanism: contributing/plugins.md + - GitHub actions setup: contributing/github_actions_setup.md - Known limitations: errors.md - Demos: demonstrations-content.md - Plugins: @@ -193,11 +198,25 @@ nav: - transformer_singleton: api_reference/trestle/transforms/transformer_singleton.md plugins: - awesome-pages +# warning don't use `macros` - yet - search +- autorefs +- htmlproofer: + enabled: true + validate_rendered_template: false + validate_external_urls: true + raise_error_after_finish: false + raise_error_excludes: + # This is to remove some false positives for links which work. + # Anchors are validated again by core mkdocs + 404: ['#trestle.*'] - mkdocstrings: + default_handler: python handlers: python: options: + show_inheritance_diagram: true + inherited_members: false group_by_category: true show_category_heading: true show_if_no_docstring: true @@ -207,8 +226,6 @@ plugins: - '!^__json' - '!^__config__' new_path_syntax: true - watch: - - trestle repo_name: oscal-compass/compliance-trestle repo_url: https://github.com/oscal-compass/compliance-trestle site_description: Documentation for compliance-trestle package. @@ -223,3 +240,8 @@ theme: accent: purple primary: teal scheme: slate +validation: + omitted_files: warn + absolute_links: warn # Or 'relative_to_docs' - new in MkDocs 1.6 + unrecognized_links: warn + anchors: warn # New in MkDocs 1.6 diff --git a/setup.cfg b/setup.cfg index 116f70029..c1b26c678 100644 --- a/setup.cfg +++ b/setup.cfg @@ -79,8 +79,9 @@ dev = types-requests types-setuptools # # Docs website - mkdocs==1.5.0 - mkdocstrings[python-legacy]==0.19.0 + mkdocs>=1.6.0 + mkdocstrings[python]>=0.25.2 + mkdocs-htmlproofer-plugin mkdocs-material markdown-include pymdown-extensions diff --git a/tests/data/json/comp_def_c.json b/tests/data/json/comp_def_c.json new file mode 100644 index 000000000..f56d434d1 --- /dev/null +++ b/tests/data/json/comp_def_c.json @@ -0,0 +1,121 @@ +{ + "component-definition": { + "uuid": "2652b814-2a6b-4b6d-a0ae-8bc7a007209f", + "metadata": { + "title": "comp def c", + "last-modified": "2021-07-19T14:03:03+00:00", + "version": "0.21.0", + "oscal-version": "1.0.2", + "roles": [ + { + "id": "prepared-by", + "title": "Indicates the organization that created this content." + }, + { + "id": "prepared-for", + "title": "Indicates the organization for which this content was created.." + }, + { + "id": "content-approver", + "title": "Indicates the organization responsible for all content represented in the \"document\"." + } + ], + "parties": [ + { + "uuid": "ce1f379a-fcdd-485a-a7b7-6f02c0763dd2", + "type": "organization", + "name": "ACME", + "remarks": "ACME company" + }, + { + "uuid": "481856b6-16e4-4993-a3ed-2fb242ce235b", + "type": "organization", + "name": "Customer", + "remarks": "Customer for the Component Definition" + }, + { + "uuid": "2dc8b17f-daca-44a1-8a1d-c290120ea5e2", + "type": "organization", + "name": "ISV", + "remarks": "ISV for the Component Definition" + } + ], + "responsible-parties": [ + { + "role-id": "prepared-by", + "party-uuids": [ + "ce1f379a-fcdd-485a-a7b7-6f02c0763dd2" + ] + }, + { + "role-id": "prepared-for", + "party-uuids": [ + "481856b6-16e4-4993-a3ed-2fb242ce235b", + "2dc8b17f-daca-44a1-8a1d-c290120ea5e2" + ] + }, + { + "role-id": "content-approver", + "party-uuids": [ + "ce1f379a-fcdd-485a-a7b7-6f02c0763dd2" + ] + } + ] + }, + "components": [ + { + "uuid": "8220b305-0271-45f9-8a21-40ab6f197f70", + "type": "Service", + "title": "comp_cc", + "description": "comp cc", + "control-implementations": [ + { + "uuid": "76e89b67-3d6b-463d-90df-ec56a46c6069", + "source": "trestle://profiles/comp_prof_aa/profile.json", + "description": "trestle comp prof cc", + "implemented-requirements": [ + { + "uuid": "ca5ea4c5-ba51-4b1d-932a-5606891b7500", + "control-id": "ac-1", + "description": "imp req prose for ac-1 from comp cc", + "responsible-roles": [ + { + "role-id": "prepared-by", + "party-uuids": [ + "ce1f379a-fcdd-485a-a7b7-6f02c0763dd2" + ] + }, + { + "role-id": "prepared-for", + "party-uuids": [ + "481856b6-16e4-4993-a3ed-2fb242ce235b", + "2dc8b17f-daca-44a1-8a1d-c290120ea5e2" + ] + }, + { + "role-id": "content-approver", + "party-uuids": [ + "ce1f379a-fcdd-485a-a7b7-6f02c0763dd2" + ] + } + ], + "statements": [ + { + "statement-id": "ac-1_smt.a", + "uuid": "2652b814-2a6b-4b6d-a0ae-8bc7a0072200", + "description": "statement prose for part a. from comp cc" + } + ] + }, + { + "uuid": "ca5ea4c5-ba51-4b1d-932a-5606891b7599", + "control-id": "ac-3", + "description": "imp req prose for ac-3 from comp cc" + } + ] + } + ] + } + ] + } +} \ No newline at end of file diff --git a/tests/data/json/profile_aggregation.json b/tests/data/json/profile_aggregation.json new file mode 100644 index 000000000..f6e6459a4 --- /dev/null +++ b/tests/data/json/profile_aggregation.json @@ -0,0 +1,106 @@ +{ + "profile": { + "uuid": "83be30e4-6c88-40e0-9927-286ff0e43d59", + "metadata": { + "title": "SI-7 profile", + "last-modified": "2024-06-24T19:35:43.973109+00:00", + "version": "0.0.1", + "oscal-version": "1.1.2" + }, + "imports": [ + { + "href": "https://raw.githubusercontent.com/usnistgov/oscal-content/main/nist.gov/SP800-53/rev5/json/NIST_SP-800-53_rev5_catalog.json", + "include-controls": [ + { + "with-ids": [ + "si-7" + ] + } + ] + } + ], + "modify": { + "set-parameters": [ + { + "param-id": "si-07_odp.01", + "props": [ + { + "name": "param-value-origin", + "value": "OCISO" + } + ], + "values": [ + "software" + ] + }, + { + "param-id": "si-07_odp.02", + "props": [ + { + "name": "param-value-origin", + "value": "OCISO" + } + ], + "values": [ + "firmware" + ] + }, + { + "param-id": "si-07_odp.03", + "props": [ + { + "name": "param-value-origin", + "value": "OCISO" + } + ], + "values": [ + "information" + ] + }, + { + "param-id": "si-07_odp.04", + "props": [ + { + "name": "param-value-origin", + "value": "OCISO" + } + ], + "values": [ + "notify the System Owner, ISSO, ISSM, and the Incident Response team" + ] + }, + { + "param-id": "si-07_odp.05", + "props": [ + { + "name": "param-value-origin", + "value": "OCISO" + } + ], + "values": [ + "notify the System Owner, ISSO, ISSM, and the Incident Response team" + ] + }, + { + "param-id": "si-07_odp.06", + "props": [ + { + "name": "param-value-origin", + "value": "OCISO" + } + ], + "values": [ + "notify the System Owner, ISSO, ISSM, and the Incident Response team" + ] + }, + { + "param-id": "si-7_prm_1" + }, + { + "param-id": "si-7_prm_2" + } + ], + "alters": [] + } + } +} diff --git a/tests/trestle/core/commands/author/component_test.py b/tests/trestle/core/commands/author/component_test.py index 508b14f71..0160371c4 100644 --- a/tests/trestle/core/commands/author/component_test.py +++ b/tests/trestle/core/commands/author/component_test.py @@ -202,3 +202,64 @@ def test_component_generate_more_than_one_param(tmp_trestle_dir: pathlib.Path, m # now assemble component generated assemble_cmd = f'trestle author component-assemble -m {md_path} -n {comp_name} -o {assem_name}' test_utils.execute_command_and_assert(assemble_cmd, CmdReturnCodes.SUCCESS.value, monkeypatch) + + +def test_component_workflow_no_rules(tmp_trestle_dir: pathlib.Path, monkeypatch: MonkeyPatch) -> None: + """Test component generate and assemble with no rules set.""" + comp_name = test_utils.setup_component_generate(tmp_trestle_dir, 'comp_def_c') + ac1_path = tmp_trestle_dir / 'md_comp/comp_cc/comp_prof_aa/ac/ac-1.md' + + orig_component, _ = model_utils.ModelUtils.load_model_for_class( + tmp_trestle_dir, comp_name, comp.ComponentDefinition + ) + + generate_cmd = f'trestle author component-generate -n {comp_name} -o {md_path}' + test_utils.execute_command_and_assert(generate_cmd, CmdReturnCodes.SUCCESS.value, monkeypatch) + + # Check that the example md file looks correct + _, tree = MarkdownProcessor().process_markdown(ac1_path) + + imp_req_md = """## What is the solution and how is it implemented? + + + + + +imp req prose for ac-1 from comp cc + +### Implementation Status: planned + +______________________________________________________________________ +""" # noqa E501 + + node = tree.get_node_for_key('## What is the solution and how is it implemented?') + assert node.content.raw_text == imp_req_md + + part_a_md = """## Implementation for part a. + +statement prose for part a. from comp cc + +### Implementation Status: planned + +______________________________________________________________________""" + + node = tree.get_node_for_key('## Implementation for part a.') + assert node.content.raw_text == part_a_md + + test_utils.substitute_text_in_file( + ac1_path, '### Implementation Status: planned', f'### Implementation Status: {const.STATUS_IMPLEMENTED}' + ) + # Check that the changes make it into the JSON + assem_name = 'assem_comp' + assemble_cmd = f'trestle author component-assemble -m {md_path} -n {comp_name} -o {assem_name}' + test_utils.execute_command_and_assert(assemble_cmd, CmdReturnCodes.SUCCESS.value, monkeypatch) + assem_component, _ = model_utils.ModelUtils.load_model_for_class( + tmp_trestle_dir, assem_name, comp.ComponentDefinition + ) + + # Check the ac-1 implementation status and that the model changed + assert not model_utils.ModelUtils.models_are_equivalent(orig_component, assem_component) # type: ignore + imp_reqs = assem_component.components[0].control_implementations[0].implemented_requirements # type: ignore + imp_req = next((i_req for i_req in imp_reqs if i_req.control_id == 'ac-1'), None) + assert imp_req.description == 'imp req prose for ac-1 from comp cc' + assert ControlInterface.get_status_from_props(imp_req).state == const.STATUS_IMPLEMENTED # type: ignore diff --git a/tests/trestle/core/commands/author/ssp_test.py b/tests/trestle/core/commands/author/ssp_test.py index b4fe2c5e3..ec4df8d7e 100644 --- a/tests/trestle/core/commands/author/ssp_test.py +++ b/tests/trestle/core/commands/author/ssp_test.py @@ -1303,3 +1303,75 @@ def test_ssp_generate_no_cds_include_all_parts(tmp_trestle_dir: pathlib.Path) -> node = tree.get_node_for_key('## Implementation for part a.') assert node.content.raw_text == part_a_text_no_comp + + +def test_ssp_generate_aggregates_no_cds(tmp_trestle_dir: pathlib.Path) -> None: + """Test the ssp generator with no comp defs does aggregate values from aggregated parameters.""" + args, _ = setup_for_ssp(tmp_trestle_dir, 'profile_aggregation', ssp_name) + + args.compdefs = None + ssp_cmd = SSPGenerate() + assert ssp_cmd._run(args) == 0 + md_dir = tmp_trestle_dir / ssp_name + si_7 = md_dir / 'si-7.md' + assert si_7.exists() + + md_api = MarkdownAPI() + header, tree = md_api.processor.process_markdown(si_7) + si_7_odp_01 = header['x-trestle-set-params']['si-07_odp.01'] + si_7_odp_01['ssp-values'] = ['changed value in the ssp markdown'] + + md_api.write_markdown_with_header(si_7, header, tree.content.raw_text) + + # now assemble the edited controls into json ssp + ssp_assemble = SSPAssemble() + assemble_args = argparse.Namespace( + trestle_root=tmp_trestle_dir, + markdown=ssp_name, + output=ssp_name, + verbose=0, + regenerate=False, + version='', + name=None, + compdefs=None + ) + assert ssp_assemble._run(assemble_args) == 0 + + # Verify the correct information is in the assembled ssp + ssp, _ = ModelUtils.load_model_for_class(tmp_trestle_dir, ssp_name, ossp.SystemSecurityPlan, FileContentType.JSON) + imp_reqs = ssp.control_implementation.implemented_requirements + si_7_imp_req = next((i_req for i_req in imp_reqs if i_req.control_id == 'si-7'), None) + si_07_odp_01 = next((param for param in si_7_imp_req.set_parameters if param.param_id == 'si-07_odp.01'), None) + changed_value_in_ssp = next( + (val for val in si_07_odp_01.values if val == 'changed value in the ssp markdown'), None + ) + assert changed_value_in_ssp is not None + + # regenerate the SSP again + ssp_cmd = SSPGenerate() + assert ssp_cmd._run(args) == 0 + md_dir = tmp_trestle_dir / ssp_name + si_7 = md_dir / 'si-7.md' + assert si_7.exists() + + md_api = MarkdownAPI() + header, tree = md_api.processor.process_markdown(si_7) + si_7_odp_01 = header['x-trestle-set-params']['si-07_odp.01'] + assert 'changed value in the ssp markdown' in si_7_odp_01['ssp-values'] + + +def test_ssp_generate_aggregates_no_param_value_orig(tmp_trestle_dir: pathlib.Path) -> None: + """Test the ssp generator aggregate parameters have no parame-value-origin.""" + args, _ = setup_for_ssp(tmp_trestle_dir, 'profile_aggregation', ssp_name) + + args.compdefs = None + ssp_cmd = SSPGenerate() + assert ssp_cmd._run(args) == 0 + md_dir = tmp_trestle_dir / ssp_name + si_7 = md_dir / 'si-7.md' + assert si_7.exists() + + md_api = MarkdownAPI() + header, _ = md_api.processor.process_markdown(si_7) + si_7_prm_1 = header['x-trestle-set-params']['si-7_prm_1'] + assert const.PARAM_VALUE_ORIGIN not in si_7_prm_1.keys() diff --git a/trestle/__init__.py b/trestle/__init__.py index 0de089d7f..ef4973693 100644 --- a/trestle/__init__.py +++ b/trestle/__init__.py @@ -23,4 +23,4 @@ opinionated approach to OSCAL adoption. """ -__version__ = '3.3.0' +__version__ = '3.4.0' diff --git a/trestle/common/model_utils.py b/trestle/common/model_utils.py index d24991abd..47e901b7c 100644 --- a/trestle/common/model_utils.py +++ b/trestle/common/model_utils.py @@ -68,7 +68,7 @@ def load_distributed( Return a tuple of Model Type (e.g. class 'trestle.oscal.catalog.Catalog'), Model Alias (e.g. 'catalog.metadata') and Instance of the Model. If the model is decomposed/split/distributed, the instance of the model contains - the decomposed models loaded recursively. + the decomposed models loaded recursively. Note: This does not validate the model. You must either validate the model separately or use the load_validate @@ -171,7 +171,8 @@ def load_model_for_class( If you need to load an existing model but its content type may not be known, use this method. But the file content type should be specified if it is somehow known. - Note: This does not validate the model. If you want to validate the model use the load_validate utilities. + Note: + This does not validate the model. If you want to validate the model use the load_validate utilities. """ root_model_path = ModelUtils._root_path_for_top_level_model( trestle_root, model_name, model_class diff --git a/trestle/core/catalog/catalog_writer.py b/trestle/core/catalog/catalog_writer.py index 4f7065338..13cb1e584 100644 --- a/trestle/core/catalog/catalog_writer.py +++ b/trestle/core/catalog/catalog_writer.py @@ -165,9 +165,11 @@ def _construct_set_parameters_dict( # adds it to prof-param-value-origin if prof_param_value_origin != '' and prof_param_value_origin is not None: if context.purpose == ContextPurpose.PROFILE: - new_dict[const.PROFILE_PARAM_VALUE_ORIGIN] = prof_param_value_origin + if const.AGGREGATES not in [prop.name for prop in as_list(param.props)]: + new_dict[const.PROFILE_PARAM_VALUE_ORIGIN] = prof_param_value_origin else: - new_dict[const.PROFILE_PARAM_VALUE_ORIGIN] = const.REPLACE_ME_PLACEHOLDER + if const.AGGREGATES not in [prop.name for prop in as_list(param.props)]: + new_dict[const.PROFILE_PARAM_VALUE_ORIGIN] = const.REPLACE_ME_PLACEHOLDER # then insert the original, incoming values as values if param_id in control_param_dict: orig_param = control_param_dict[param_id] @@ -180,6 +182,8 @@ def _construct_set_parameters_dict( new_dict.pop(const.VALUES) if new_dict[const.GUIDELINES] is None: new_dict.pop(const.GUIDELINES) + if const.AGGREGATES in [prop.name for prop in as_list(orig_param.props)]: + new_dict.pop(const.PROFILE_PARAM_VALUE_ORIGIN) else: # if the profile doesnt change this param at all, show it in the header with values tmp_dict = ModelUtils.parameter_to_dict(param_dict, True) @@ -351,31 +355,24 @@ def _update_values(set_param: comp.SetParameter, control_param_dict) -> None: ci_set_params = ControlInterface.get_set_params_from_item(control_imp) for imp_req in as_list(control_imp.implemented_requirements): control_part_id_map = part_id_map.get(imp_req.control_id, {}) - control_rules, statement_rules, _ = ControlInterface.get_rule_list_for_imp_req(imp_req) - if control_rules or statement_rules: - if control_rules: - status = ControlInterface.get_status_from_props(imp_req) - comp_info = ComponentImpInfo(imp_req.description, control_rules, [], status) - self._catalog_interface.add_comp_info(imp_req.control_id, context.comp_name, '', comp_info) - set_params = copy.deepcopy(ci_set_params) - set_params.update(ControlInterface.get_set_params_from_item(imp_req)) - for set_param in set_params.values(): - self._catalog_interface.add_comp_set_param(imp_req.control_id, context.comp_name, set_param) - for statement in as_list(imp_req.statements): - rule_list, _ = ControlInterface.get_rule_list_for_item(statement) - if rule_list: - status = ControlInterface.get_status_from_props(statement) - if statement.statement_id not in control_part_id_map: - label = statement.statement_id - logger.warning( - f'No statement label found for statement id {label}. Defaulting to {label}.' - ) - else: - label = control_part_id_map[statement.statement_id] - comp_info = ComponentImpInfo(statement.description, rule_list, [], status) - self._catalog_interface.add_comp_info( - imp_req.control_id, context.comp_name, label, comp_info - ) + status = ControlInterface.get_status_from_props(imp_req) + control_rules, _ = ControlInterface.get_rule_list_for_item(imp_req) + comp_info = ComponentImpInfo(imp_req.description, control_rules, [], status) + self._catalog_interface.add_comp_info(imp_req.control_id, context.comp_name, '', comp_info) + set_params = copy.deepcopy(ci_set_params) + set_params.update(ControlInterface.get_set_params_from_item(imp_req)) + for set_param in set_params.values(): + self._catalog_interface.add_comp_set_param(imp_req.control_id, context.comp_name, set_param) + for statement in as_list(imp_req.statements): + status = ControlInterface.get_status_from_props(statement) + if statement.statement_id not in control_part_id_map: + label = statement.statement_id + logger.warning(f'No statement label found for statement id {label}. Defaulting to {label}.') + else: + label = control_part_id_map[statement.statement_id] + rule_list, _ = ControlInterface.get_rule_list_for_item(statement) + comp_info = ComponentImpInfo(statement.description, rule_list, [], status) + self._catalog_interface.add_comp_info(imp_req.control_id, context.comp_name, label, comp_info) catalog_merger = CatalogMerger(self._catalog_interface) diff --git a/trestle/core/control_interface.py b/trestle/core/control_interface.py index cd7cc1622..4e92c2bfc 100644 --- a/trestle/core/control_interface.py +++ b/trestle/core/control_interface.py @@ -670,6 +670,24 @@ def _param_selection_as_str(param: common.Parameter, verbose: bool = False, brac return choices_str return '' + @staticmethod + def _param_as_aggregated_value( + param: common.Parameter, + param_dict: Dict[str, common.Parameter], + verbose: bool = False, + brackets: bool = False + ) -> str: + """Convert parameter aggregation to str.""" + # review is an aggregated parameter + if const.AGGREGATES in [prop.name for prop in as_list(param.props)]: + aggregated_values = '' + for prop in as_list(param.props): + if prop.value not in param_dict: + continue + aggregated_values += ', '.join(as_list(param_dict[prop.value].values)) + ', ' + return aggregated_values[:-2] + return '' + @staticmethod def _param_label_choices_as_str(param: common.Parameter, verbose: bool = False, brackets: bool = False) -> str: """Convert param label or choices to string, using choices if present.""" @@ -681,6 +699,7 @@ def _param_label_choices_as_str(param: common.Parameter, verbose: bool = False, @staticmethod def _param_values_assignment_str( param: common.Parameter, + param_dict: Dict[str, common.Parameter], value_assigned_prefix: Optional[str] = None, value_not_assigned_prefix: Optional[str] = None ) -> str: @@ -692,6 +711,9 @@ def _param_values_assignment_str( # otherwise use param selection if present if not param_str: param_str = ControlInterface._param_selection_as_str(param, True, False) + # otherwise use param aggregated values if present + if not param_str: + param_str = ControlInterface._param_as_aggregated_value(param, param_dict, True, False) # finally use label and param_id as fallbacks if not param_str: param_str = param.label if param.label else param.id @@ -722,7 +744,8 @@ def param_to_str( brackets: bool = False, params_format: Optional[str] = None, value_assigned_prefix: Optional[str] = None, - value_not_assigned_prefix: Optional[str] = None + value_not_assigned_prefix: Optional[str] = None, + param_dict: Dict[str, common.Parameter] = None ) -> Optional[str]: """ Convert parameter to string based on best available representation. @@ -755,7 +778,7 @@ def param_to_str( param_str = '' elif param_rep == ParameterRep.ASSIGNMENT_FORM: param_str = ControlInterface._param_values_assignment_str( - param, value_assigned_prefix, value_not_assigned_prefix + param, param_dict, value_assigned_prefix, value_not_assigned_prefix ) if not param_str: param_str = '' @@ -854,11 +877,20 @@ def _replace_params( elif param_dict[param_ids[i]] is not None: param = param_dict[param_ids[i]] param_str = ControlInterface.param_to_str( - param, param_rep, False, False, params_format, value_assigned_prefix, value_not_assigned_prefix + param, + param_rep, + False, + False, + params_format, + value_assigned_prefix, + value_not_assigned_prefix, + param_dict ) text = text.replace(staches[i], param_str, 1).strip() if show_value_warnings and param_rep != ParameterRep.LABEL_OR_CHOICES and not param.values: - logger.warning(f'Parameter {param_id} has no values and was referenced by prose.') + # verifies the current parameter is not an aggregated parameter to throw a warning + if const.AGGREGATES not in [prop.name for prop in as_list(param.props)]: + logger.warning(f'Parameter {param_id} has no values and was referenced by prose.') elif show_value_warnings: logger.warning(f'Control prose references param {param_ids[i]} with no specified value.') # there may be staches remaining that we can't replace if not in param_dict diff --git a/trestle/core/control_reader.py b/trestle/core/control_reader.py index c1881c488..884910ac5 100644 --- a/trestle/core/control_reader.py +++ b/trestle/core/control_reader.py @@ -283,9 +283,6 @@ def read_implemented_requirement(control_file: pathlib.Path, imp_req.statements = [] comp_dict = md_comp_dict[comp_name] for label, comp_info in comp_dict.items(): - # only assemble responses with associated rules - if not comp_info.rules: - continue # if no label it applies to the imp_req itself rather than a statement if not label: imp_req.description = ControlReader._handle_empty_prose(comp_info.prose, control_id) diff --git a/trestle/core/control_writer.py b/trestle/core/control_writer.py index 481e23931..62703bd5f 100644 --- a/trestle/core/control_writer.py +++ b/trestle/core/control_writer.py @@ -129,7 +129,7 @@ def _insert_comp_info( level = 3 if context.purpose == ContextPurpose.COMPONENT else 4 if part_label in comp_info: info = comp_info[part_label] - if context.purpose in [ContextPurpose.COMPONENT, ContextPurpose.SSP] and not info.rules: + if context.purpose in [ContextPurpose.COMPONENT, ContextPurpose.SSP] and not self._include_component(info): return self._md_file.new_paragraph() if info.prose: @@ -266,23 +266,35 @@ def _add_implementation_response_prompts( self._insert_comp_info(part_label, dic, context) self._md_file.new_hr() + def _include_component(self, comp_info: ComponentImpInfo) -> bool: + """ + Check if a component has the required Markdown fields. + + Notes: This is a simple function to centralize logic to check + when a component meets the requirement to get written to Markdown. + """ + if comp_info.rules or comp_info.prose: + return True + return False + def _skip_part(self, context: ControlContext, part_label: str, comp_dict: CompDict) -> bool: """ Check if a part should be skipped based on rules and context. Notes: The default logic is to conditionally add control parts based - on whether the component has rules associated with that part. This can be + on whether the component has rules or existing prose associated with that part. This can be changed using the control context for SSP markdown. """ if context.purpose == ContextPurpose.SSP and context.include_all_parts: return False else: - no_applied_rules = True + skip_item = True for _, dic in comp_dict.items(): - if part_label in dic and dic[part_label].rules: - no_applied_rules = False - break - return no_applied_rules + if part_label in dic: + if self._include_component(dic[part_label]): + skip_item = False + break + return skip_item def _dump_subpart_infos(self, level: int, part: Dict[str, Any]) -> None: name = part['name']