Skip to content

Commit

Permalink
fix(generate): proposed transition of generation annotation to props (#…
Browse files Browse the repository at this point in the history
…574)

* fix(generate): generation annotation in a prop

* fix(generate): introduce framework flag to generate

* chore(docs): add generation ns docs
  • Loading branch information
brandtkeller authored Aug 9, 2024
1 parent 8a92a8c commit b7a936d
Show file tree
Hide file tree
Showing 6 changed files with 77 additions and 9 deletions.
14 changes: 14 additions & 0 deletions docs/cli-commands/generation/component.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,20 @@ There are optional flags that can be added to the command to generate a componen
- The title of the component `--component`; `Software Title`
- The output file of the component `-o` or `--output`; `oscal-component.yaml`

### Reproducibility

The `lula generate` commands are meant to be reproducible and will auto-merge models based on filename. The intent for this generation is to make it easy to update a given model with automation and only inject human intervention as needed. An artifact generated with `lula generate` can be merged with a pre-existing artifact of the same model type.

For component-definitions, see each individual `control-implementation` props for the `generation` prop. It should look like the following:
```yaml
props:
- name: generation
ns: https://docs.lula.dev/ns
value: lula generate component --catalog-source https://raw.githubusercontent.com/usnistgov/oscal-content/master/nist.gov/SP800-53/rev5/json/NIST_SP-800-53_rev5_catalog.json --component 'Component Title' --requirements ac-1,ac-3,ac-3.2,ac-4 --remarks assessment-objective
```
This `value` should mirror any required inputs in order to reproduce a given control implementation in a component.

### Existing Data

The ability to retain data that is put into OSCAL artifacts is of utmost importance to this generation process and also a large feature of continued maintenance of these artifacts. Lula supports the ability to merge newly generated component definition templates into existing component definitions automatically.
Expand Down
3 changes: 2 additions & 1 deletion docs/ns/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ As indicated on the [Extending OSCAL models](https://pages.nist.gov/OSCAL/learn/
## Current Support
- [threshold](./threshold.md)
- [target](./target.md)
- [framework](./framework.md)
- [framework](./framework.md)
- [generation](./generation.md)
10 changes: 10 additions & 0 deletions docs/ns/generation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Generation

The `generation` prop is an identifier for the purposes of tracking imperative reproducibility of a given artifact or subset of an artifact. In the example below, the `lula generate component` command annotates how a given `control-implementation` - and associated `component` were generated.

```yaml
props:
- name: generation
ns: https://docs.lula.dev/ns
value: lula generate component --catalog-source https://raw.githubusercontent.com/usnistgov/oscal-content/master/nist.gov/SP800-53/rev5/json/NIST_SP-800-53_rev5_catalog.json --component 'Component Title' --requirements ac-1,ac-3,ac-3.2,ac-4 --remarks assessment-objective
```
12 changes: 7 additions & 5 deletions src/cmd/generate/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ type componentFlags struct {
Component string // --component
Requirements []string // -r --requirements
Remarks []string // --remarks
Framework string // --framework
}

var opts = &flags{}
Expand Down Expand Up @@ -91,6 +92,10 @@ var generateComponentCmd = &cobra.Command{
// Used to reproduce the command for documentation
command := fmt.Sprintf("%s --catalog-source %s --component '%s' --requirements %s --remarks %s", cmd.CommandPath(), source, title, strings.Join(componentOpts.Requirements, ","), strings.Join(remarks, ","))

if componentOpts.Framework != "" {
command += fmt.Sprintf(" --framework %s", componentOpts.Framework)
}

// Fetch the catalog source
data, err := network.Fetch(source)
if err != nil {
Expand All @@ -104,15 +109,11 @@ var generateComponentCmd = &cobra.Command{
}

// Create a component definition from the catalog given required context
comp, err := oscal.ComponentFromCatalog(source, catalog, title, componentOpts.Requirements, remarks)
comp, err := oscal.ComponentFromCatalog(command, source, catalog, title, componentOpts.Requirements, remarks, componentOpts.Framework)
if err != nil {
message.Fatalf(err, fmt.Sprintf("error creating component - %s\n", err.Error()))
}

// Add the command to the remarks - this will always overwrite the existing remarks content
components := *comp.Components
components[0].Remarks = fmt.Sprintf("This component was generated using the following command:\n\t %s", command)

var model = oscalTypes_1_1_2.OscalModels{
ComponentDefinition: comp,
}
Expand Down Expand Up @@ -202,4 +203,5 @@ func generateComponentFlags() {
componentFlags.StringVar(&componentOpts.Component, "component", "", "Component Title")
componentFlags.StringSliceVarP(&componentOpts.Requirements, "requirements", "r", []string{}, "List of requirements to capture")
componentFlags.StringSliceVar(&componentOpts.Remarks, "remarks", []string{}, "Target for remarks population (default = statement)")
componentFlags.StringVar(&componentOpts.Framework, "framework", "", "Control-Implementation collection that these controls belong to")
}
43 changes: 42 additions & 1 deletion src/pkg/common/oscal/component.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,18 @@ func mergeComponents(original *oscalTypes_1_1_2.DefinedComponent, latest *oscalT
tempItems = append(tempItems, item)
}

// merge Props without duplicating
// note: this assumes uniqueness of named prop
if latest.Props != nil {
if original.Props == nil {
original.Props = latest.Props
} else {
for _, prop := range *latest.Props {
UpdateProps(prop.Name, prop.Ns, prop.Value, original.Props)
}
}
}

original.ControlImplementations = &tempItems
return original
}
Expand Down Expand Up @@ -184,6 +196,17 @@ func mergeControlImplementations(original *oscalTypes_1_1_2.ControlImplementatio
for _, item := range originalMap {
tempItems = append(tempItems, item)
}
// merge Props without duplicating
// note: this assumes uniqueness of named prop
if latest.Props != nil {
if original.Props == nil {
original.Props = latest.Props
} else {
for _, prop := range *latest.Props {
UpdateProps(prop.Name, prop.Ns, prop.Value, original.Props)
}
}
}
original.ImplementedRequirements = tempItems
return original
}
Expand Down Expand Up @@ -241,7 +264,7 @@ func mergeLinks(orig []oscalTypes_1_1_2.Link, latest []oscalTypes_1_1_2.Link) *[
}

// Creates a component-definition from a catalog and identified (or all) controls. Allows for specification of what the content of the remarks section should contain.
func ComponentFromCatalog(source string, catalog *oscalTypes_1_1_2.Catalog, componentTitle string, targetControls []string, targetRemarks []string) (*oscalTypes_1_1_2.ComponentDefinition, error) {
func ComponentFromCatalog(command string, source string, catalog *oscalTypes_1_1_2.Catalog, componentTitle string, targetControls []string, targetRemarks []string, framework string) (*oscalTypes_1_1_2.ComponentDefinition, error) {
// store all of the implemented requirements
implementedRequirements := make([]oscalTypes_1_1_2.ImplementedRequirementControlImplementation, 0)
var componentDefinition = &oscalTypes_1_1_2.ComponentDefinition{}
Expand Down Expand Up @@ -289,6 +312,23 @@ func ComponentFromCatalog(source string, catalog *oscalTypes_1_1_2.Catalog, comp
}
}

props := []oscalTypes_1_1_2.Property{
{
Name: "generation",
Ns: "https://docs.lula.dev/ns",
Value: command,
},
}

if framework != "" {
prop := oscalTypes_1_1_2.Property{
Name: "framework",
Ns: "https://docs.lula.dev/ns",
Value: framework,
}
props = append(props, prop)
}

if len(implementedRequirements) == 0 {
return componentDefinition, fmt.Errorf("no controls were identified in the catalog from the requirements list: %v\n", targetControls)
}
Expand All @@ -305,6 +345,7 @@ func ComponentFromCatalog(source string, catalog *oscalTypes_1_1_2.Catalog, comp
Source: source,
ImplementedRequirements: implementedRequirements,
Description: "Control Implementation Description",
Props: &props,
},
},
},
Expand Down
4 changes: 2 additions & 2 deletions src/pkg/common/oscal/component_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ func TestComponentFromCatalog(t *testing.T) {

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := oscal.ComponentFromCatalog(tt.source, &tt.data, tt.title, tt.requirements, tt.remarks)
got, err := oscal.ComponentFromCatalog("Mock Command", tt.source, &tt.data, tt.title, tt.requirements, tt.remarks, "impact")
if (err != nil) != tt.wantErr {
t.Errorf("ComponentFromCatalog() error = %v, wantErr %v", err, tt.wantErr)
return
Expand Down Expand Up @@ -299,7 +299,7 @@ func TestMergeComponentDefinitions(t *testing.T) {
existingImplementedRequirementsMap[req.ControlId] = true
}

generated, _ := oscal.ComponentFromCatalog(tt.source, catalog, tt.title, tt.requirements, tt.remarks)
generated, _ := oscal.ComponentFromCatalog("Mock Command", tt.source, catalog, tt.title, tt.requirements, tt.remarks, "impact")

merged, err := oscal.MergeComponentDefinitions(validComponent, generated)
if (err != nil) != tt.wantErr {
Expand Down

0 comments on commit b7a936d

Please sign in to comment.