Skip to content

trestle CLI Overview and OSCAL Usecases¤

The trestle CLI has three primary use cases:

  • Serve as tooling to generate and manipulate OSCAL files directly by an end user. The objective is to reduce the complexity of creating and editing workflows. Example commands are: trestle import, trestle create, trestle split, trestle merge.
  • Act as an automation tool that, by design, can be an integral part of a CI/CD pipeline e.g. trestle validate, trestle tasks.
  • Allow governance of markdown documents so they conform to specific style or structure requirements.

To support each of these use cases trestle creates an opinionated directory structure to manage governed documents.

Opinionated directory structure¤

Trestle relies on an opinionated directory structure (trestle workspace), similar to git, go, or auditree, to manage the workflow. Most trestle commands are restricted to working within an initialized directory tree.

The directory structure setup by trestle has three major elements:

  • A .trestle hidden folder.
  • A dist folder.
  • Folders for each of the top level OSCAL models.

The outline of the schema is below:

.
├── .trestle
├── dist
│   ├── catalogs
│   ├── profiles
│   ├── component-definitions
│   ├── system-security-plans
│   ├── assessment-plans
│   ├── assessment-results
│   └── plan-of-action-and-milestones
├── catalogs
├── profiles
├── component-definitions
├── system-security-plans
├── assessment-plans
├── assessment-results
└── plan-of-action-and-milestones

.trestle directory is a special directory containing various trestle artefacts to help run various other commands. Examples include configuration files, caches and templates.

dist directory will contain the assembled version of the top level models located on the source model directories.

The bulk of the folder structure is used to represent each of the top level schemas or top level models such as catalogs and profiles. For each of these directories the following root structure is maintained:

├── .trestle
└── TOP_LEVEL_MODEL_PLURAL
    └── NAME_OF_MODEL_INSTANCE
        └── TOP_LEVEL_MODEL_NAME.{json,yaml,yml}

which appears, for a catalog a user decides is titled nist-800-53, as:

├── .trestle
└── catalogs
    └── nist-800-53
        └── catalog.json

In most of the places in the documentation we use json format for specifying model files, but they are equally applicable to yaml format also. The default format is json, and yaml is supported on best effort basis. Within one model directory the two different formats should not be mixed.

Support for subdivided document structures¤

The files constructed by OSCAL can run into tens of thousands of lines of yaml or formatted json. At this size the files become completely unmanageable for users. To combat this, trestle can trestle split a file into many smaller files and later merge those split files together.

Directory structures such as the one below can represent OSCAL document structures. Users are strongly encourage to rely on split and merge to code these structures.

Users can query the contents of files using trestle describe, and probe the contents more deeply using it in combination with element paths.

.
├── .trestle
├── dist 
│   └── catalogs
│       └── nist800-53.json 
└── catalogs
    └── nist800-53
        ├── catalog.json
        └── catalog
            ├── metadata.json
            ├── metadata
            │   ├── revision-history
            │   │   ├── 00000__revision-history.json
            │   │   ├── 00001__revision-history.json
            │   │   └── 00002__revision-history.json   
            │   └── responsible-parties
            │       ├── creator__responsible-party.json
            │       └── contact__responsible-party.json       
            └── groups
                ├── 00000__group.json        
                ├── 00000__group
                │   └── controls
                │       ├── 00000__control.json
                │       └── 00001__control.json
                ├── 00001__group.json 
                └── 00001__group
                    └── controls
                        ├── 00000__control.json
                        └── 00001__control.json
...

Specifying attributes / elements within trestle commands.¤

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 to understand object names in trestle.

Rules for element path¤

  1. Element path is an expression of the attribute names, in json form , concatenated by a period (.).
  2. E.g. The metadata in a catalog is referred to as catalog.metadata
  3. Element paths are relative to the file.
  4. e.g. For metadata.json roles would be referred to as metadata.roles, from the catalog file that would be catalog.metadata.roles
  5. Arrays can be handled by a wild card * or a numerical index for a specific index.
  6. catalog.groups.* to refer to each group in a catalog
  7. catalog.groups.*.controls.* to refer to 'for each control under a top level group'
  8. For NIST 800-53 catalog.groups.0.controls.0.
  9. On *nix platforms if using the wildcard the element path argument should be wrapped in quotes to prevent problems with the shell interpreting the wild card before trestle can
  10. When dealing with an array based object, the array syntax may be skipped when passing a model
  11. e.g. a control could be catalog.controls.control or catalog.groups.controls.control
  12. This syntax is required as OSCAL, across the schema, has conflicting element definitions.

A note for software developers using trestle.¤

Trestle provides utilities for converting from element path to trestle's python object model. The (slightly simplified) model is:

  1. Class attributes are converted from dash-case to dash_case (aka snake_case)
  2. Class names are converted from dash-case to DashCase (aka CamelCase)

trestle version¤

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

It can also be used to retrieve the metadata version of the OSCAL object:

"catalog": {

    "uuid": "fa3f44a8-25cd-4f6a-8175-7afe647df7ed",

    "metadata": {

      "title": "Catalog1",

      "last-modified": "2023-01-11T17:04:02.840910+00:00",

      "version": "0.1.10", <<< this version here

      "oscal-version": "1.0.4"

    },
...
  • -t or --type - a type of the OSCAL object (can be either catalog, profile, component-definition, system-security-plan, etc)
  • -n or --name - a name of the OSCAL object

Running trestle version -n nist -t catalog will return:

Version of OSCAL object of nist catalog is: 1.0.0

trestle init¤

This command will create (initialize) a trestle workspace in the current directory with the necessary directory structure and trestle artefacts. This command has multiple modes that it can run in:

  • --full
  • --local
  • --govdocs

By default trestle init will run in the --full mode.

--full mode is meant to be used when full functionality of Trestle is used i.e. managing OSCAL models locally, govern documents or using Trestle for API purposes. dist repository will be used when trestle assemble command is used. Running trestle init --full will create the directory structure below for different artefacts:

.
├── .trestle
├── dist
│   ├── catalogs
│   ├── profiles
│   ├── component-definitions
│   ├── system-security-plans
│   ├── assessment-plans
│   ├── assessment-results
│   └── plan-of-action-and-milestones
├── catalogs
├── profiles
├── component-definitions
├── system-security-plans
├── assessment-plans
├── assessment-results
└── plan-of-action-and-milestones

--local mode is meant to be used when Trestle is used to only manage OSCAL models locally. Running trestle init --local will create the directory structure below for different artefacts:

.
├── .trestle
├── catalogs
├── profiles
├── component-definitions
├── system-security-plans
├── assessment-plans
├── assessment-results
└── plan-of-action-and-milestones

--govdocs mode is meant to be used when Trestle is only used to govern documents. Running trestle init --govdocs will create the directory structure below:

.
├── .trestle

.trestle directory is a special directory containing various trestle artefacts to help run various other commands.

dist directory will contain the merged or assembled version of the top level models located on the source model directories which are: catalogs, profiles, component-definitions, system-security-plans, assessment-plans, assessment-results and plan-of-action-and-milestones.

Notice that trestle is a highly opinionated tool and, therefore, the names of the files and directories that are created by any of the trestle commands and subcommands MUST NOT be changed manually.

trestle create¤

This command will create a bare-bones sample file for one of the top level OSCAL models, and it can also create new elements within an existing file. For example, trestle create -t catalog -o nist800-53 will create a sample catalog file, catalog.json in the catalog subdirectory, nist800-53 as shown below:

.
├── .trestle
└── catalogs
    └── nist800-53
        └── catalog.json
...

The -t specifies the type of the model to create, which can be one of catalog, profile, component-definition, system-security-plan, assessment-plan, assessment-results, plan-of-action-and-milestones. Each type will be created in its corresponding directory, such as catalogs, profiles, etc.

The following additional options are supported:

  • -o or --output: specifies the name/alias of a model. It is used as the prefix for the output filename under the dist directory and for naming the source subdirectories under catalogs, profiles, component-definitions, system-security-plans, assessment-plans, assessment-results or plan-of-action-and-milestones.

The user can edit the parts of the generated OSCAL model by modifying the sample content in those directories.

Passing -iof or --include-optional-fields will make trestle create generate a richer model containing all optional fields until finding recursion in the model (e.g controls within control).

In addition, trestle create can create new components within an existing file by specifying the existing file name and the corresponding element path to create within that file.

For example,

$TRESTLE_BASEDIR/catalogs/nist800-53$ trestle create -f ./catalog.json -e catalog.metadata.roles

will add the following property under the metadata property for a catalog that will be written to the appropriate file under catalogs/nist800-53 directory:

{
  "roles": [
    {
      "id": "REPLACE_ME",
      "title": "REPLACE_ME"
    }
  ]
}

Default values for mandatory datatypes will be like below. All UUID's will be populated by default whether or not they are mandatory.

  - DateTime: <Current date-time>
  - Boolean: false
  - Integer: 0
  - String: REPLACE_ME
  - Float/Double: 0.00
  - Id field: Auto generated UUID

Again, passing -iof or --include-optional-fields will make trestle create generate a richer version of the element being created, by including optional fields.

trestle import¤

This command allows users to import existing OSCAL files so that they can be managed using trestle. For example trestle import -f /local_dir/existing_catalog.json -o my_existing_catalog will import existing_catalog.json into a new folder under catalogs as shown below:

.
├── .trestle
└── catalogs
    └── my_existing_catalog
        └── catalog.json
...

The following options are supported:

  • -f or --file: specifies the path of an existing OSCAL file or URL to a remote file.
  • -o or --output: specifies the name/alias of a model. It is used as the prefix for the output filename under the dist directory and for naming the source subdirectories under catalogs, profiles, component-definitions, system-security-plans, assessment-plans, assessment-results or plan-of-action-and-milestones.

The --file option may be an absolute or relative path, and it may be a URL. For details on allowed formats please see the documentation for the href command. The file must be imported from outside the current trestle directory or an error will result.

The import subcommand can determine the type of the model that is to be imported by the contents of the file. But the file name must end with an allowed json or yaml extension: .json, .yaml, .yml

During the import process the file must pass the validate test described below for the command, validate. If the file does not pass validation a warning will be given describing the nature of the problem and the import will fail.

Once a file has been imported it can be split into a rich tree of sub-components as shown at the top of this document. But the file must be imported first.

trestle replicate¤

This command allows users to replicate a certain OSCAL model (file and directory structure). For example trestle replicate catalog -n cat1 -o cat11 will replicate the Catalog cat1 into cat11 directory. It can also regenerate all the UUIDs as required.

trestle split¤

This command allows users to further decompose a trestle model into additional subcomponents.

The following options are currently supported:

  • -f or --file: this is optional and specifies the file path of the json/yaml file containing the elements that will be split.
  • -e or --elements: specifies the model subcomponent element(s) (JSON/YAML property path) that is/are going to be split. Multiple elements can be specified at once using a comma-separated value, e.g -e 'catalog.metadata,catalog.groups'. Make sure to include the quotes that enclose the comma-separated paths.

If the element is of JSON/YAML type array list and you want trestle to create a separate subcomponent file per array item, the element needs to be suffixed with .*, e.g. -e 'catalog.groups.*'. If the suffix is not specified, split will place all array items in only one separate subcomponent file, e.g. 'groups.json'. Again, make sure to include the quotes around the elements.

If you just want to split a file into all its constituent parts and the file does not contain a simple list of objects, you can still use * and the file will be split into all its non-trivial elements. Thus if you split a catalog with -e catalog.* the result will be a new directory, catalog, containing files representing the large items, back-matter.json, groups.json and metadata.json, but there will still be a catalog.json file containing just the catalog's uuid. Small items such as strings and dates cannot be split off and will remain in the original model file that is being split.

Here are some examples. Starting with a single catalog file, my_catalog/catalog.json, if you do trestle split -f catalog.json -e 'catalog.*' you end up with:

catalogs
 ┗ my_catalog
 ┃ ┣ catalog
 ┃ ┃ ┣ back-matter.json
 ┃ ┃ ┣ groups.json
 ┃ ┃ ┗ metadata.json
 ┃ ┗ catalog.json

If you then split roles out of metadata as a single file containing a list of roles, trestle split -f catalog/metadata.json -e 'metadata.roles' you would end up with:

catalogs
 ┗ my_catalog
 ┃ ┣ catalog
 ┃ ┃ ┣ metadata
 ┃ ┃ ┃ ┗ roles.json
 ┃ ┃ ┣ back-matter.json
 ┃ ┃ ┣ groups.json
 ┃ ┃ ┗ metadata.json
 ┃ ┗ catalog.json

If instead you had specified -e 'metadata.roles.*' you would get:

my_catalog
 ┣ catalog
 ┃ ┣ metadata
 ┃ ┃ ┗ roles
 ┃ ┃ ┃ ┣ 00000__role.json
 ┃ ┃ ┃ ┗ 00001__role.json
 ┃ ┣ back-matter.json
 ┃ ┣ groups.json
 ┃ ┗ metadata.json
 ┗ catalog.json

You can see there is no roles.json file anymore and instead there is a subdirectory, roles containing a list of files, one for each role.

If the -f or --file option is not specified, the file to split will be determined from the elements specified, in the context of the current working directory. The current directory must be within a specific model (e.g. catalog or profile), and the element paths must either be absolute (e.g. catalog.metadata.roles) or relative to the current working directory. For example, if you are in catalogs/mycat/catalog/groups and you want to split the file 00000__group.json, you must use -f to specify the filename, and the element path can either be absolute, as catalog.group.*, or you can set the current working directory to where the file is and use element path group.*. This makes it easier to specify splits when deep in a directory structure.

Every subdirectory in a trestle directory model should have a corresponding .json or .yaml file with the same name, except when that subdirectory corresponds to a list of items, such as catalog.groups. When those subcomponents are split/expanded each file or subdirectory under them represents an item of the collection. Because of that, if a corresponding groups.json | groups.yaml file were to exist, its contents would just be an empty representation of that collection and the user would need to be careful never to edit that file. Therefore, we decided not to create that corresponding file in those cases. Following the same logic, another exception is when all the fields from a .json | .yaml file are split, leaving the original file as an empty object. In that case, the file would be deleted as well.

To inspect a file to see what elements can be split from it, use the describe command described below. It is also useful for inspection of files created by the split operation.

trestle merge¤

The trestle merge command is the reversal of trestle split. This command allows users to reverse the decomposition of a trestle model by aggregating subcomponents scattered across multiple files or directories into the parent JSON/YAML file. To merge a model, you have to first change working directory to the root model component directory that you want to merge a sub-component model into. The following option is required:

  • -e or --elements: specifies the properties (JSON/YAML path) that will be merged, relative to the current working directory. This must contain at least 2 elements, where the last element is the model/sub-component to be merged into the second from last component.

For example, in the command trestle merge -e 'catalog.metadata', executed in the same directory where catalog.json or the split catalog directory exists, the property metadata from metadata.json would be moved/merged into catalog.json. If the metadata model has already been split into smaller sub-component models previously, those smaller sub-components are first recusively merged into metadata, before merging metadata subcomponent into catalog. To specify merging every sub-component split from a component, .* can be used. For example, trestle merge -e 'catalog.*' command, issued from the directory where catalog.json orcatalog directory exists, will merge every single sub-component of that catalog back into the catalog.json.

trestle describe¤

This command lets users inspect model files to explore contents using an optional element path. The command can work well in concert with split to show what each file contains, and probe within the contents to determine sub-components that can be extracted as separate files.

Unlike split, describe only describes the contents of a single item, so the element path may not contain wildcards (*) or commas.

For example, if a catalog file has been imported to catalogs/my_catalog/catalog.json then the commmand, trestle describe -f catalog.json might yield:

#Model file catalog.json is of type catalog.Catalog and contains
uuid: 613fca2d-704a-42e7-8e2b-b206fb92b456
metadata: common.Metadata
params: None
controls: None
groups: list of 20 items of type catalog.Group
back_matter: common.BackMatter

Note that contents are listed even when they are empty (and therefore optional) so the full potential contents can be seen. Also note that if an item corresponds to a list of elements, the number and type of elements is provided. Finally, if an item is a simple string such as id, uuid or title, the string is shown directly up to a maximum of 100 characters. If the string is clipped it will be indicated by [truncated] at the end of the string.

An element path can be specified to probe the contents, as in trestle describe -f catalog.json -e 'catalog.metadata.roles'. A possible response is:

Model file catalog.json at element path catalog.metadata.roles is a list of 2 items of type common.Role

You can also query individual elements, and elements of an element, e.g. trestle describe -f catalog.json -e 'catalog.groups.5.controls.3'

# Model file catalog.json at element path catalog.groups.5.controls.3 is of type catalog.Control and contains:
id: cp-4
class_: SP800-53
title: Contingency Plan Testing
params: list of 2 items of type common.Parameter
props: list of 2 items of type common.Property
links: list of 14 items of type common.Link
parts: list of 2 items of type common.Part
controls: list of 5 items of type catalog.Control

(Note that the numbering starts at 0, so the .3 corresponds to the 4th element.)

In all output from describe the type of the item shown corresponds to the python file and class of the corresponding OSCAL model in trestle.

If you split items off a model so they end up in a subdirectory, the original file is referred to as a "stripped" model, with parts of it stripped off and only some elements remaining. For example, if you do trestle split -f catalog.json -e 'catalog.metadata' it will split off metadata from the original catalog.json file and place it in catalog/metadata.json. If you then do trestle describe -f catalog.json on the new file, it will say something like:

# Model file catalog.json is of type stripped.Catalog and contains:
uuid: 613fca2d-704a-42e7-8e2b-b206fb92b456
params: None
controls: None
groups: list of 20 items of type catalog.Group
back_matter: common.BackMatter

Note that the type of the file is now stripped.Catalog and it no longer contains metadata. Even though metadata is no longer in the original .json file, trestle is still aware it is present in the model since it is properly placed as its own file in the subdirectory, catalog.

trestle partial-object-validate¤

OSCAL objects may be extremely large. Some systems may only be able to produce partial OSCAL objects. For example, the tanium-result-to-oscal-ar task produces the results attribute of an assessment-results object.

trestle partial-object-validate allows the validation of any sub-element/attribute using element path.

Using the example above trestle partial-object-validate -f results.json -e assessment-results.results.

The file is not required to be in the trestle directory or required to be a specific file name.

Example valid element-paths¤

All element paths must be absolute e.g.: catalog.metadata catalog catalog.groups catalog.groups.group.controls.control.controls.control Remembering in the end you only care about the end type. So in this scenario catalog.groups.group.controls.control.controls.control is equivalent to catalog.controls.control.

trestle href¤

This command changes the href of an Import in a profile and is needed when generating an SSP (system security plan) with the author tool, ssp-generate. The Imports in a profile are used to load associated catalogs of controls and profiles, and must be available at the corresponding href uri. If an imported catalog is in the trestle directory then the href should be changed with a command of the form:

trestle href -n my_profile -hr trestle://catalogs/my_catalog/catalog.json

Similarly, if the item imported is a profile, a corresponding href should point to a json file in the profiles directory.

Note that catalogs or profiles in the trestle directory are indicated by the trestle:// prefix, followed by the path from the top level models directory to the actual catalog file. The profile itself, which is having its imports modified, is just indicated by its name with the -n option.

If the profile has more than one import, you can display the corresponding hrefs with:

trestle href -n my_profile

This will give a numbered list of the hrefs. You can then change them individually by providing the corresponding item number:

trestle href -n my_profile -i 1 -hr trestle://catalogs/my_catalog/catalog.json

This will change the href indexed as 1 when the list was displayed. The href's are indexed starting from 0.

The trestle href command can also be used to change the value back to the intended one prior to distribution of the profile.

The provided href can be of form trestle://, https://, sftp://, or file:///. If file:/// is used, the path provided must be absolute - and on Windows it must include the drive letter followed by a slash. The only time a relative path is allowed is with the trestle:// heading.

A username and password may be embedded in the url for https://, and a CA certificate path will be searched from environment variables REQUESTS_CA_BUNDLE and CURL_CA_BUNDLE in that order.

Authorization for sftp:// access relies on the user's private key being either active via ssh-agent or supplied via the environment variable SSH_KEY. In the latter case it must not require a passphrase prompt.

trestle assemble¤

This command assembles all contents (files and directories) representing a specific model into a single OSCAL file located under dist folder. For example,

$TRESTLE_BASEDIR$ trestle assemble catalog -n nist800-53

will traverse the catalogs/nist800-53 directory and its children and combine all data into a OSCAL file that will be written to dist/catalogs/nist800-53.json. Note that the parts of catalog nist800-53 can be written in either YAML/JSON (e.g. based on the file extension), however, the output will be generated as YAML/JSON as desired. Trestle will infer the content type from the file extension and create the model representation appropriately in memory and then output in the desired format. Trestle assemble will also validate content as it assembles the files and make sure the contents are syntactically correct.

trestle remove¤

The trestle remove command is the reversal of trestle create -f filename.json -e element_path, as it will remove the corresponding element from the specified file.

trestle validate¤

Trestle validate checks the integrity of one or more OSCAL files in a number of ways.

validate returns a non-zero return code if there is any validation problem detected in a file.

The current list of validation modes that get checked internally are:

Mode Purpose
duplicates Detect whether disallowed duplicate uuid's are present
oscal_version Confirm that the oscal version of the file is supported
refs Confirm that all references in responsible parties are found in roles
catalog Confirm all parameter ids in a catalog are unique
links Confirm referenced resources are 1:1 with resources in backmatter

You can validate a single model file by specifying its full path:

trestle validate -f catalogs/my_cat/catalog.json

or by specifying its model name and type:

trestle validate -t catalog -n my_cat

In addition to validating a single file you can validate all files of a given type with the -t option and no file name:

trestle validate -t catalog

And you can validate all models with the -a option:

trestle validate -a

Note that when you Import a file it will perform a full validation on it first, and if it does not pass validation the file cannot be imported.

By default validate will display warning messages and a message indicating the file is valid, but you can suppress those messages with the -q --quiet option.

The links validator is special because it always returns success that the file is valid - but it will list any inconsistencies it finds between the references to links, and corresponding links in the backmatter.

trestle tasks¤

Open Shift Compliance Operator and Tanium are supported as 3rd party tools.

trestle task xccdf-result-to-oscal-ar¤

The trestle task xccdf-result-to-oscal-ar command facilitates transformation of XCCDF results, e.g. OpenShift Compliance Operator (OSCO) scan results, .yaml files into OSCAL partial results .json files. Specify required config parameters to indicate the location of the input and the output. Specify optional config parameters to indicate the name of the oscal-metadata.yaml file, if any, and whether overwriting of existing output is permitted.

Example command invocation:

$TRESTLE_BASEDIR$ trestle task xccdf-result-to-oscal-ar -c /home/user/task.config

Example config:

/home/user/task.config

[task.xccdf-result-to-oscal-ar]

input-dir =  /home/user/git/evidence/xccdf/input
output-dir = /home/user/git/evidence/oscal/output
oscal-metadata = oscal-metadata.yaml
output-overwrite = true

input

Example input directory contents listing:

/home/user/git/evidence/xccdf/input

-rw-rw-r--. 1 user user  3832 Feb  2 09:36 oscal-metadata.yaml
-rw-rw-r--. 1 user user 49132 Feb  2 06:12 ssg-ocp4-ds-cis-111.222.333.444-pod.yaml
-rw-rw-r--. 1 user user 52747 Feb  2 06:41 ssg-ocp4-ds-cis-111.222.333.555-pod.yaml

Example input OSCO scan result file contents (snippet):

ssg-ocp4-ds-cis-111.222.333.444-pod.yaml

display sample
apiVersion: v1
data:
  exit-code: '2'
  results: |
    <?xml version="1.0" encoding="UTF-8"?>
    <TestResult xmlns="https://checklists.nist.gov/xccdf/1.2" 
                id="xccdf_org.open-scap_testresult_xccdf_org.ssgproject.content_profile_cis"
                start-time="2020-08-03T02:26:26+00:00" end-time="2020-08-03T02:26:26+00:00"
                version="0.1.52"
                test-system="cpe:/a:redhat:openscap:1.3.3">
              <benchmark href="/content/ssg-ocp4-ds.xml" id="xccdf_org.ssgproject.content_benchmark_OCP-4"/>
              <title>OSCAP Scan Result</title>
              <profile idref="xccdf_org.ssgproject.content_profile_cis"/>
              <target>kube-br7qsa3d0vceu2so1a90-roksopensca-default-0000026b.iks.mycorp</target>
              <target-facts>
                <fact name="urn:xccdf:fact:identifier" type="string">chroot:///host</fact>
                <fact name="urn:xccdf:fact:scanner:name" type="string">OpenSCAP</fact>
                <fact name="urn:xccdf:fact:scanner:version" type="string">1.3.3</fact>
              </target-facts>
              <target-id-ref system="https://scap.nist.gov/schema/asset-identification/1.1" name="asset0" href=""/>
              <platform idref="cpe:/a:redhat:openshift_container_platform:4.1"/>
              <platform idref="cpe:/a:machine"/>
              <set-value idref="xccdf_org.ssgproject.content_value_ocp_data_root">/kubernetes-api-resources</set-value>
              <set-value idref="xccdf_org.ssgproject.content_value_var_kube_authorization_mode">Webhook</set-value>
              <set-value idref="xccdf_org.ssgproject.content_value_var_streaming_connection_timeouts">5m</set-value>
              <rule-result idref="xccdf_org.ssgproject.content_rule_ocp_idp_no_htpasswd" time="2020-08-03T02:26:26+00:00" severity="medium" weight="1.000000">
                <result>notselected</result>
                <ident system="https://nvd.nist.gov/cce/index.cfm">CCE-84209-6</ident>
              </rule-result>
              <rule-result idref="xccdf_org.ssgproject.content_rule_accounts_restrict_service_account_tokens" time="2020-08-03T02:26:26+00:00" severity="medium" weight="1.000000">
                <result>notchecked</result>
                <message severity="info">No candidate or applicable check found.</message>
              </rule-result>
              <rule-result idref="xccdf_org.ssgproject.content_rule_accounts_unique_service_account" time="2020-08-03T02:26:26+00:00" severity="medium" weight="1.000000">
                <result>notchecked</result>
                <message severity="info">No candidate or applicable check found.</message>
              </rule-result>

              ...

           </TestResult>
kind: ConfigMap
metadata:
  annotations:
    compliance-remediations/processed: ''
    compliance.openshift.io/scan-error-msg: ''
    compliance.openshift.io/scan-result: NON-COMPLIANT
    openscap-scan-result/node: 111.222.333.444
  creationTimestamp: '2020-08-03T02:26:34Z'
  labels:
    compliance-scan: ssg-ocp4-ds-cis
  name: ssg-ocp4-ds-cis-111.222.333.444-pod
  namespace: openshift-compliance
  resourceVersion: '22693328'
  selfLink: 
    /api/v1/namespaces/openshift-compliance/configmaps/ssg-ocp4-ds-cis-111.222.333.444-pod
  uid: 1da3ea81-0a25-4512-ad86-7ac360246b5d


Example input OSCAL metadata file contents:

oscal-metadata.yaml

display sample
ssg-ocp4-ds-cis-111.222.333.444-pod:
  locker: https://github.mycorp.com/degenaro/evidence-locker
  namespace: xccdf
  benchmark: CIS Kubernetes Benchmark
  subject-references:
    component:
      uuid-ref: 56666738-0f9a-4e38-9aac-c0fad00a5821
      type: component
      title: Red Hat OpenShift Kubernetes
    inventory-item:
      uuid-ref: 46aADFAC-A1fd-4Cf0-a6aA-d1AfAb3e0d3e
      type: inventory-item
      title: Pod
      properties:
        target: kube-br7qsa3d0vceu2so1a90-roksopensca-default-0000026b.iks.mycorp
        target-ip: 111.222.333.444
        cluster-name: ROKS-OpenSCAP-1
        cluster-type: openshift
        cluster-region: us-south

ssg-rhel7-ds-cis-111.222.333.444-pod:
  locker: https://github.mycorp.com/degenaro/evidence-locker
  namespace: xccdf
  benchmark: CIS Kubernetes Benchmark
  subject-references:
    component:
      uuid-ref: 89cfe7a7-ce6b-4699-aa7b-2f5739c72001
      type: component
      title: RedHat Enterprise Linux 7.8
    inventory-item:
      uuid-ref: 46aADFAC-A1fd-4Cf0-a6aA-d1AfAb3e0d3e
      type: inventory-item
      title: VM
      properties:
        target: kube-br7qsa3d0vceu2so1a90-roksopensca-default-0000026b.iks.mycorp
        target-ip: 111.222.333.444
        cluster-name: ROKS-OpenSCAP-1
        cluster-type: openshift
        cluster-region: us-south

metadata format

The oscal-metadata.yaml file comprises one or more mappings. Below is shown the format of a single mapping. The items in angle brackets are to be replaced with desired values for augmenting the produced OSCAL.

The mapping whose name matches the [metadata][name] in the evidence for the corresponding embedded XML, if any, will be used for augmenting the produced OSCAL.

name:
  locker: <locker>
  namespace: <namespace>
  benchmark: <benchmark>
  subject-references:
    component:
      uuid-ref: <uuid-ref-component>
      type: <component-type>
      title: <component-title>
    inventory-item:
      uuid-ref: <uuid-ref-inventory-item>
      type: <inventory-item-type>
      title: <inventory-item-title>
      properties:
        target: <target>
        cluster-name: <cluster-name>
        cluster-type: <cluster-type>
        cluster-region: <cluster-region>

output

Example output directory contents listing:

/home/user/git/evidence/oscal/output

-rw-rw-r--. 1 user user 49132 Feb  3 10:59 ssg-ocp4-ds-cis-111.222.333.444-pod.json
-rw-rw-r--. 1 user user 52747 Feb  3 10:59 ssg-ocp4-ds-cis-111.222.333.555-pod.json

Example output OSCAL Observations file contents (snippet):

ssg-ocp4-ds-cis-111.222.333.444-pod.json

display sample
{
  "observations": [
    {
      "uuid": "56666738-0f9a-4e38-9aac-c0fad00a5821",
      "title": "xccdf_org.ssgproject.content_rule_ocp_idp_no_htpasswd",
      "description": "xccdf_org.ssgproject.content_rule_ocp_idp_no_htpasswd",
      "props": [
        {
          "name": "benchmark",
          "ns": "dns://osco",
          "class": "source",
          "value": "CIS Kubernetes Benchmark"
        }
      ],
      "methods": [
        "TEST-AUTOMATED"
      ],
      "subjects": [
        {
          "uuid-ref": "56666738-0f9a-4e38-9aac-c0fad00a5821",
          "type": "component",
          "title": "Red Hat OpenShift Kubernetes"
        },
        {
          "uuid-ref": "46aADFAC-A1fd-4Cf0-a6aA-d1AfAb3e0d3e",
          "type": "inventory-item",
          "title": "Pod",
          "props": [
            {
              "name": "target",
              "ns": "dns://osco",
              "class": "inventory-item",
              "value": "kube-br7qsa3d0vceu2so1a90-roksopensca-default-0000026b.iks.mycorp"
            },
            {
              "name": "target-ip",
              "ns": "dns://osco",
              "class": "inventory-item",
              "value": "111.222.333.444"
            },
            {
              "name": "cluster-name",
              "ns": "dns://osco",
              "class": "inventory-item",
              "value": "ROKS-OpenSCAP-1"
            },
            {
              "name": "cluster-type",
              "ns": "dns://osco",
              "class": "inventory-item",
              "value": "openshift"
            },
            {
              "name": "cluster-region",
              "ns": "dns://osco",
              "class": "inventory-item",
              "value": "us-south"
            }
          ]
        }
      ],
      "relevant-evidence": [
        {
          "href": "https://github.mycorp.com/degenaro/evidence-locker",
          "description": "Evidence location.",
          "props": [
            {
              "name": "rule",
              "ns": "dns://xccdf",
              "class": "id",
              "value": "xccdf_org.ssgproject.content_rule_ocp_idp_no_htpasswd"
            },
            {
              "name": "time",
              "ns": "dns://xccdf",
              "class": "timestamp",
              "value": "2020-08-03T02:26:26+00:00"
            },
            {
              "name": "result",
              "ns": "dns://xccdf",
              "class": "result",
              "value": "notselected"
            }
          ]
        }
      ]
    }
  ]
}

trestle task tanium-result-to-oscal-ar¤

The trestle task tanium-result-to-oscal-ar command facilitates transformation of Tanuim reports, each input file comprising individual lines consumable as json, into OSCAL partial results .json files. Specify required config parameters to indicate the location of the input and the output. Specify optional config parameter output-overwrite to indicate whether overwriting of existing output is permitted. Specify optional config parameter timestamp as ISO 8601 formated string (e.g., 2021-02-24T19:31:13+00:00) to override the timestamp attached to each Observation.

Example command invocation:

$TRESTLE_BASEDIR$ trestle task tanium-result-to-oscal-ar -c /home/user/task.config

Example config:

/home/user/task.config

[task.tanium-result-to-oscal-ar]

input-dir =  /home/user/git/compliance/tanium/input
output-dir = /home/user/git/compliance/oscal/output
output-overwrite = true

input

Example input directory contents listing:

/home/user/git/compliance/tanium/input

-rw-rw-r--. 1 degenaro degenaro 1830 Mar  7 08:23 Tanium.comply-nist-results

Tanium.comply-nist-results

display sample
{
  "IP Address": "fe80::3cd5:564b:940e:49ab",
  "Computer Name": "cmp-wn-2106.demo.tanium.local",
  "Comply - JovalCM Results[c2dc8749]": [
    {
      "Benchmark": "CIS Microsoft Windows 10 Enterprise Release 1803 Benchmark",
      "Benchmark Version": "1.5.0.1",
      "Profile": "Windows 10 - NIST 800-53",
      "ID": "xccdf_org.cisecurity.benchmarks_rule_1.1.1_L1_Ensure_Enforce_password_history_is_set_to_24_or_more_passwords",
      "Result": "pass",
      "Custom ID": "800-53: IA-5",
      "Version": "version: 1"
    }
  ],
  "Count": "1",
  "Age": "600"
}
{
  "IP Address": "10.8.69.11",
  "Computer Name": "",
  "Comply - JovalCM Results[c2dc8749]": [
    {
      "Benchmark": "CIS Microsoft Windows 10 Enterprise Release 1803 Benchmark",
      "Benchmark Version": "1.5.0.1",
      "Profile": "Windows 10 - NIST 800-53",
      "ID": "xccdf_org.cisecurity.benchmarks_rule_1.1.2_L1_Ensure_Maximum_password_age_is_set_to_60_or_fewer_days_but_not_0",
      "Result": "pass",
      "Custom ID": "800-53: IA-5",
      "Version": "version: 1"
    }
  ],
  "Count": "1",
  "Age": "600"
}
{
  "IP Address": "10.8.69.11",
  "Computer Name": "cmp-wn-2106.demo.tanium.local",
  "Comply - JovalCM Results[c2dc8749]": [
    {
      "Benchmark": "CIS Microsoft Windows 10 Enterprise Release 1803 Benchmark",
      "Benchmark Version": "1.5.0.1",
      "Profile": "Windows 10 - NIST 800-53",
      "ID": "xccdf_org.cisecurity.benchmarks_rule_1.1.3_L1_Ensure_Minimum_password_age_is_set_to_1_or_more_days",
      "Result": "fail",
      "Custom ID": "800-53: IA-5",
      "Version": "version: 1"
    }
  ],
  "Count": "1",
  "Age": "600"
}
{
  "IP Address": "10.8.69.11",
  "Computer Name": "cmp-wn-2106.demo.tanium.local",
  "Comply - JovalCM Results[c2dc8749]": [
    {
      "Benchmark": "CIS Microsoft Windows 10 Enterprise Release 1803 Benchmark",
      "Benchmark Version": "1.5.0.1",
      "Profile": "Windows 10 - NIST 800-53",
      "ID": "xccdf_org.cisecurity.benchmarks_rule_1.1.4_L1_Ensure_Minimum_password_length_is_set_to_14_or_more_characters",
      "Result": "pass",
      "Custom ID": "800-53: IA-5",
      "Version": "version: 1"
    }
  ],
  "Count": "1",
  "Age": "600"
}

output

Example output directory contents listing:

/home/user/git/compliance/oscal/output

-rw-rw-r--. 1 degenaro degenaro 6479 Mar  7 08:25 Tanium.oscal.json

Tanium.oscal.json

display sample
{
  "results": [
    {
      "uuid": "0ed0791e-5454-4d07-919f-15a0d806a5a8",
      "title": "Tanium",
      "description": "Tanium",
      "start": "2021-04-13T00:16:20.000+00:00",
      "local-definitions": {
        "inventory-items": [
          {
            "uuid": "da8b87f6-2068-415f-94bb-e14e31b4f5c2",
            "description": "inventory",
            "props": [
              {
                "name": "computer-name",
                "ns": "dns://tanium",
                "value": "cmp-wn-2106.demo.tanium.local",
                "class": " inventory-item"
              },
              {
                "name": "computer-ip",
                "ns": "dns://tanium",
                "value": "fe80::3cd5:564b:940e:49ab",
                "class": " inventory-item"
              },
              {
                "name": "profile",
                "ns": "dns://tanium",
                "value": "Windows 10",
                "class": " inventory-item"
              }
            ]
          },
          {
            "uuid": "f3ab87b2-70c1-4332-991e-c003d4314c0b",
            "description": "inventory",
            "props": [
              {
                "name": "computer-name",
                "ns": "dns://tanium",
                "value": "",
                "class": " inventory-item"
              },
              {
                "name": "computer-ip",
                "ns": "dns://tanium",
                "value": "10.8.69.11",
                "class": " inventory-item"
              },
              {
                "name": "profile",
                "ns": "dns://tanium",
                "value": "Windows 10",
                "class": " inventory-item"
              }
            ]
          }
        ]
      },
      "reviewed-controls": {
        "control-selections": [
          {}
        ]
      },
      "observations": [
        {
          "uuid": "b3250b66-fe6f-4ac0-be99-cb4ff093dc31",
          "description": "xccdf_org.cisecurity.benchmarks_rule_1.1.1_L1_Ensure_Enforce_password_history_is_set_to_24_or_more_passwords",
          "props": [
            {
              "name": "benchmark",
              "ns": "dns://tanium",
              "value": "CIS Microsoft Windows 10 Enterprise Release 1803 Benchmark",
              "class": "source"
            },
            {
              "name": "rule",
              "ns": "dns://xccdf",
              "value": "xccdf_org.cisecurity.benchmarks_rule_1.1.1_L1_Ensure_Enforce_password_history_is_set_to_24_or_more_passwords",
              "class": "id"
            },
            {
              "name": "result",
              "ns": "dns://xccdf",
              "value": "pass",
              "class": "result"
            }
          ],
          "methods": [
            "TEST-AUTOMATED"
          ],
          "subjects": [
            {
              "uuid-ref": "da8b87f6-2068-415f-94bb-e14e31b4f5c2",
              "type": "inventory-item"
            }
          ],
          "collected": "2021-04-13T00:16:20.000+00:00"
        },
        {
          "uuid": "5ae9c133-c32d-44c5-b52e-5af4513cb94a",
          "description": "xccdf_org.cisecurity.benchmarks_rule_1.1.2_L1_Ensure_Maximum_password_age_is_set_to_60_or_fewer_days_but_not_0",
          "props": [
            {
              "name": "benchmark",
              "ns": "dns://tanium",
              "value": "CIS Microsoft Windows 10 Enterprise Release 1803 Benchmark",
              "class": "source"
            },
            {
              "name": "rule",
              "ns": "dns://xccdf",
              "value": "xccdf_org.cisecurity.benchmarks_rule_1.1.2_L1_Ensure_Maximum_password_age_is_set_to_60_or_fewer_days_but_not_0",
              "class": "id"
            },
            {
              "name": "result",
              "ns": "dns://xccdf",
              "value": "pass",
              "class": "result"
            }
          ],
          "methods": [
            "TEST-AUTOMATED"
          ],
          "subjects": [
            {
              "uuid-ref": "f3ab87b2-70c1-4332-991e-c003d4314c0b",
              "type": "inventory-item"
            }
          ],
          "collected": "2021-04-13T00:16:20.000+00:00"
        },
        {
          "uuid": "8d021edc-176e-4373-a3c4-a19e954c1e4d",
          "description": "xccdf_org.cisecurity.benchmarks_rule_1.1.3_L1_Ensure_Minimum_password_age_is_set_to_1_or_more_days",
          "props": [
            {
              "name": "benchmark",
              "ns": "dns://tanium",
              "value": "CIS Microsoft Windows 10 Enterprise Release 1803 Benchmark",
              "class": "source"
            },
            {
              "name": "rule",
              "ns": "dns://xccdf",
              "value": "xccdf_org.cisecurity.benchmarks_rule_1.1.3_L1_Ensure_Minimum_password_age_is_set_to_1_or_more_days",
              "class": "id"
            },
            {
              "name": "result",
              "ns": "dns://xccdf",
              "value": "fail",
              "class": "result"
            }
          ],
          "methods": [
            "TEST-AUTOMATED"
          ],
          "subjects": [
            {
              "uuid-ref": "f3ab87b2-70c1-4332-991e-c003d4314c0b",
              "type": "inventory-item"
            }
          ],
          "collected": "2021-04-13T00:16:20.000+00:00"
        },
        {
          "uuid": "36aa7551-d047-4f4a-9853-6ac63cfc9e48",
          "description": "xccdf_org.cisecurity.benchmarks_rule_1.1.4_L1_Ensure_Minimum_password_length_is_set_to_14_or_more_characters",
          "props": [
            {
              "name": "benchmark",
              "ns": "dns://tanium",
              "value": "CIS Microsoft Windows 10 Enterprise Release 1803 Benchmark",
              "class": "source"
            },
            {
              "name": "rule",
              "ns": "dns://xccdf",
              "value": "xccdf_org.cisecurity.benchmarks_rule_1.1.4_L1_Ensure_Minimum_password_length_is_set_to_14_or_more_characters",
              "class": "id"
            },
            {
              "name": "result",
              "ns": "dns://xccdf",
              "value": "pass",
              "class": "result"
            }
          ],
          "methods": [
            "TEST-AUTOMATED"
          ],
          "subjects": [
            {
              "uuid-ref": "f3ab87b2-70c1-4332-991e-c003d4314c0b",
              "type": "inventory-item"
            }
          ],
          "collected": "2021-04-13T00:16:20.000+00:00"
        }
      ],
      "findings": [
        {
          "uuid": "ba4e264f-0aee-4ead-9ee3-6161c5cc4ecb",
          "title": "800-53: IA-5",
          "description": "800-53: IA-5",
          "target": {
            "type": "objective-id",
            "id-ref": "800-53: IA-5",
            "props": [
              {
                "name": "profile",
                "ns": "dns://tanium",
                "value": "NIST 800-53",
                "class": "source"
              },
              {
                "name": "id-ref",
                "ns": "dns://tanium",
                "value": "800-53: IA-5",
                "class": "source"
              },
              {
                "name": "result",
                "ns": "dns://xccdf",
                "value": "FAIL",
                "class": "STRVALUE"
              }
            ],
            "status": "not-satisfied"
          },
          "related-observations": [
            {
              "observation-uuid": "b3250b66-fe6f-4ac0-be99-cb4ff093dc31"
            },
            {
              "observation-uuid": "5ae9c133-c32d-44c5-b52e-5af4513cb94a"
            },
            {
              "observation-uuid": "8d021edc-176e-4373-a3c4-a19e954c1e4d"
            },
            {
              "observation-uuid": "36aa7551-d047-4f4a-9853-6ac63cfc9e48"
            }
          ]
        }
      ]
    }
  ]
}

trestle task xlsx-to-oscal-cd¤

The trestle task xlsx-to-oscal-cd command facilitates transformation of an excel spreadsheet into an OSCAL component-definition.json file. Specify in the config:

  • location of catalog file
  • location of spreadsheet file
  • work sheet name in the spreadsheet file
  • output directory to write the component-definition.json file
  • whether or not to overwrite an existing component-definition.json file
  • the organization name
  • the organization remarks
  • the namespace
  • comma separated mappings from name to class
  • the catalog URL
  • the catalog title

Example command invocation:

$TRESTLE_BASEDIR$ trestle task xlsx-to-oscal-cd -c /home/user/task-xlsx-to-oscal-cd.config

Example config:

/home/user/task-xlsx-to-oscal-cd.config

[task.xlsx-to-oscal-cd]

catalog-file = nist-content/nist.gov/SP800-53/rev4/json/NIST_SP-800-53_rev4_catalog.json
spread-sheet-file = /home/user/compliance/data/spread-sheet/good.xlsx
work-sheet-name = example_best_practices_controls
output-dir = /home/user/compliance/data/tasks/xlsx/output
output-overwrite = true

org-name = International Business Machines
org-remarks = IBM
namespace = https://oscal-compass.github.io/compliance-trestle/schemas/oscal/cd/ibm-cloud
property-name-to-class = goal_name_id:scc_goal_name_id, goal_version:scc_goal_version
catalog-url = https://github.com/usnistgov/oscal-content/blob/master/nist.gov/SP800-53/rev4/json/NIST_SP-800-53_rev4_catalog.json
catalog-title = NIST Special Publication 800-53 Revision 4

catalog-file

Example catalog-file:

nist-content/nist.gov/SP800-53/rev4/json/NIST_SP-800-53_rev4_catalog.json

spread-sheet-file

Example spread-sheet-file:

/home/user/compliance/data/spread-sheet/good.xlsx

output

Example component-definition.json:

/home/user/compliance/data/tasks/xlsx/output/component-definition.json

spreadsheet to component definition mapping¤

display mapping table
spreadsheet column name component definition path comments
ControlId
  • implemented_requirement.property[name='goal_name_id'].value
  • only used if column 'goal_name_id' is empty
ControlText
  • implemented_requirement.property[name='goal_name_id'].remarks
  • transformation code replaces "Check whether" with "Ensure" in text
Nist Mappings
  • implemented_requirement.description
  • heading may span multiple columns
  • one value expected per column
  • each entry is separated into control + statements (if any)
ResourceTitle
  • component.title
  • component.description
  • component.control-implementation.description + {text}
goal_name_id
  • implemented_requirement.property[name='goal_name_id'].value
Version
  • implemented_requirement.property[name='goal_version'].value
  • Value from spreadsheet is not currently used.
  • Value '1.0' is hard coded.
Parameter [optional parameter]
  • implemented_requirement.set_parameter.param_id
  • The expected text is in two parts separated by '\n'.
  • The text following the '\n' is the value used.
Values [alternatives]
  • implemented_requirement.set_parameter.values
  • The expected text is of the following format:
  • v0, [v1, v2...]
  • The value v0 is used.

trestle task xlsx-to-oscal-profile¤

The trestle task xlsx-to-oscal-profile command facilitates transformation of an excel spreadsheet into an OSCAL profile.json file. Specify in the config:

  • the href URL of the spreadsheet
  • file system location of spreadsheet file
  • work sheet name in the spreadsheet file
  • output directory to write the profile.json file
  • whether or not to overwrite an existing profile.json file
  • the profile title

Example command invocation:

$TRESTLE_BASEDIR$ trestle task xlsx-to-oscal-profile -c /home/user/task-xlsx-to-oscal-profile.config

Example config:

/home/user/task-xlsx-to-oscal-profile.config

[task.xlsx-to-oscal-profile]

spread-sheet-url = https://github.mycorp.com/spread-sheets/good.xlsx
spread-sheet-file = /home/user/compliance/data/spread-sheet/good.xlsx
work-sheet-name = example_best_practices_controls
output-dir = /home/user/compliance/data/tasks/xlsx/output
output-overwrite = true

profile-title = IBM Best Practices SCC GOALS

spread-sheet-file

Example spread-sheet-file:

/home/user/compliance/data/spread-sheet/good.xlsx

output

Example profile.json:

/home/user/compliance/data/tasks/xlsx/output/profile.json