diff --git a/src/cmd/generate/generate.go b/src/cmd/generate/generate.go index d44c3b9b..609eaea5 100644 --- a/src/cmd/generate/generate.go +++ b/src/cmd/generate/generate.go @@ -106,7 +106,7 @@ var generateComponentCmd = &cobra.Command{ // Create a component definition from the catalog given required context comp, err := oscal.ComponentFromCatalog(source, catalog, title, componentOpts.Requirements, remarks) if err != nil { - message.Fatalf(fmt.Errorf("error creating component"), "error creating component") + 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 diff --git a/src/pkg/common/oscal/component.go b/src/pkg/common/oscal/component.go index 0a13976e..c7cda27f 100644 --- a/src/pkg/common/oscal/component.go +++ b/src/pkg/common/oscal/component.go @@ -243,7 +243,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) { // store all of the implemented requirements - implmentedRequirements := make([]oscalTypes_1_1_2.ImplementedRequirementControlImplementation, 0) + implementedRequirements := make([]oscalTypes_1_1_2.ImplementedRequirementControlImplementation, 0) var componentDefinition = &oscalTypes_1_1_2.ComponentDefinition{} if len(targetControls) == 0 { @@ -255,40 +255,32 @@ func ComponentFromCatalog(source string, catalog *oscalTypes_1_1_2.Catalog, comp controlMap[control] = false } - if catalog.Groups == nil { - return componentDefinition, fmt.Errorf("catalog Groups is nil - no catalog provided") - } + // A catalog has groups and controls + // A group has controls and groups (note the nesting of groups) + // A control has controls (note the nesting) + // Given the nesting of groups/controls we will need to recursively search groups/controls - // Iterate through all possible controls -> improve the efficiency of this in the future - for _, group := range *catalog.Groups { - if group.Controls == nil { - message.Debugf("group %s has no controls", group.ID) - continue + // Begin Recursive group search + if catalog.Groups != nil { + newReqs, err := searchGroups(catalog.Groups, controlMap, targetRemarks) + if err != nil { + return componentDefinition, err } - for _, control := range *group.Controls { - if _, ok := controlMap[control.ID]; ok { - newRequirement, err := ControlToImplementedRequirement(&control, targetRemarks) - if err != nil { - return componentDefinition, err - } - implmentedRequirements = append(implmentedRequirements, newRequirement) - controlMap[control.ID] = true - } + implementedRequirements = append(implementedRequirements, newReqs...) + } - if control.Controls != nil { - for _, subControl := range *control.Controls { - if _, ok := controlMap[subControl.ID]; ok { - newRequirement, err := ControlToImplementedRequirement(&subControl, targetRemarks) - if err != nil { - return componentDefinition, err - } - implmentedRequirements = append(implmentedRequirements, newRequirement) - controlMap[subControl.ID] = true - continue - } - } - } + // Begin recursive control search + if catalog.Controls != nil { + newReqs, err := searchControls(catalog.Controls, controlMap, targetRemarks) + if err != nil { + return componentDefinition, err } + implementedRequirements = append(implementedRequirements, newReqs...) + } + + // TODO: rework this - a catalog does not require groups + if catalog.Groups == nil { + return componentDefinition, fmt.Errorf("catalog Groups is nil - no catalog provided") } for id, found := range controlMap { @@ -297,6 +289,10 @@ func ComponentFromCatalog(source string, catalog *oscalTypes_1_1_2.Catalog, comp } } + if len(implementedRequirements) == 0 { + return componentDefinition, fmt.Errorf("no controls were identified in the catalog from the requirements list: %v\n", targetControls) + } + componentDefinition.Components = &[]oscalTypes_1_1_2.DefinedComponent{ { UUID: uuid.NewUUID(), @@ -307,7 +303,7 @@ func ComponentFromCatalog(source string, catalog *oscalTypes_1_1_2.Catalog, comp { UUID: uuid.NewUUIDWithSource(source), Source: source, - ImplementedRequirements: implmentedRequirements, + ImplementedRequirements: implementedRequirements, Description: "Control Implementation Description", }, }, @@ -330,6 +326,54 @@ func ComponentFromCatalog(source string, catalog *oscalTypes_1_1_2.Catalog, comp } +func searchGroups(groups *[]oscalTypes_1_1_2.Group, controlMap map[string]bool, remarks []string) ([]oscalTypes_1_1_2.ImplementedRequirementControlImplementation, error) { + + implementedRequirements := make([]oscalTypes_1_1_2.ImplementedRequirementControlImplementation, 0) + + for _, group := range *groups { + if group.Groups != nil { + newReqs, err := searchGroups(group.Groups, controlMap, remarks) + if err != nil { + return implementedRequirements, err + } + implementedRequirements = append(implementedRequirements, newReqs...) + } + if group.Controls != nil { + newReqs, err := searchControls(group.Controls, controlMap, remarks) + if err != nil { + return implementedRequirements, err + } + implementedRequirements = append(implementedRequirements, newReqs...) + } + } + return implementedRequirements, nil +} + +func searchControls(controls *[]oscalTypes_1_1_2.Control, controlMap map[string]bool, remarks []string) ([]oscalTypes_1_1_2.ImplementedRequirementControlImplementation, error) { + + implementedRequirements := make([]oscalTypes_1_1_2.ImplementedRequirementControlImplementation, 0) + + for _, control := range *controls { + if _, ok := controlMap[control.ID]; ok { + newRequirement, err := ControlToImplementedRequirement(&control, remarks) + if err != nil { + return implementedRequirements, err + } + implementedRequirements = append(implementedRequirements, newRequirement) + controlMap[control.ID] = true + } + + if control.Controls != nil { + newReqs, err := searchControls(control.Controls, controlMap, remarks) + if err != nil { + return implementedRequirements, err + } + implementedRequirements = append(implementedRequirements, newReqs...) + } + } + return implementedRequirements, nil +} + // Consume a control - Identify statements - iterate through parts in order to create a description func ControlToImplementedRequirement(control *oscalTypes_1_1_2.Control, targetRemarks []string) (implementedRequirement oscalTypes_1_1_2.ImplementedRequirementControlImplementation, err error) { var controlDescription string @@ -531,29 +575,34 @@ func addPart(part *[]oscalTypes_1_1_2.Part, paramMap map[string]parameter, level var result, label string - for _, part := range *part { - // need to get the label first - unsure if there will ever be more than one? - for _, prop := range *part.Props { - if prop.Name == "label" { - label = prop.Value + if part != nil { + for _, part := range *part { + // need to get the label first - unsure if there will ever be more than one? + if part.Props != nil { + for _, prop := range *part.Props { + if prop.Name == "label" { + label = prop.Value + } + } } - } - var tabs string - for range level { - tabs += "\t" - } - prose := part.Prose - if prose == "" { - result += fmt.Sprintf("%s%s\n", tabs, label) - } else if strings.Contains(prose, "{{ insert: param,") { - result += fmt.Sprintf("%s%s %s\n", tabs, label, replaceParams(prose, paramMap)) - } else { - result += fmt.Sprintf("%s%s %s\n", tabs, label, prose) - } - if part.Parts != nil { - result += addPart(part.Parts, paramMap, level+1) - } + var tabs string + for range level { + tabs += "\t" + } + prose := part.Prose + if prose == "" { + result += fmt.Sprintf("%s%s\n", tabs, label) + } else if strings.Contains(prose, "{{ insert: param,") { + result += fmt.Sprintf("%s%s %s\n", tabs, label, replaceParams(prose, paramMap)) + } else { + result += fmt.Sprintf("%s%s %s\n", tabs, label, prose) + } + if part.Parts != nil { + result += addPart(part.Parts, paramMap, level+1) + } + + } } return result