diff --git a/component/builder.go b/component/builder.go index 8a0d181..62ba4b0 100644 --- a/component/builder.go +++ b/component/builder.go @@ -81,30 +81,62 @@ func (c *DefinitionBuilder) AddTargetComponent(targetComponent, componentType st } func (c *DefinitionBuilder) AddValidationComponent(evaluationPlan layer4.EvaluationPlan) *DefinitionBuilder { - var componentProps []oscalTypes.Property - var groupNumber = 00 + executorsById := make(map[string]layer4.AssessmentExecutor) + for _, executor := range evaluationPlan.Executors { + executorsById[executor.Id] = executor + } + + proceduresByExecutor := make(map[string][]executorProcedure) for _, plan := range evaluationPlan.Plans { for _, assessment := range plan.Assessments { for _, procedure := range assessment.Procedures { - checkProps := makeCheck(assessment.Requirement.EntryId, procedure, groupNumber) - groupNumber += 1 - componentProps = append(componentProps, checkProps...) + for _, executorRef := range procedure.Executors { + executor, ok := executorsById[executorRef.Id] + if !ok { + continue + } + if executor.Type == layer4.Manual { + continue // Skip manual executors + } + proceduresByExecutor[executor.Id] = append(proceduresByExecutor[executor.Id], executorProcedure{ + requirement: assessment.Requirement, + procedure: procedure, + }) + } } - } } - component := oscalTypes.DefinedComponent{ - UUID: uuid.NewUUID(), - Type: "validation", - Title: evaluationPlan.Metadata.Author.Name, - Props: utils.NilIfEmpty(&componentProps), + // Create a validation component for each executor + for executorId, procs := range proceduresByExecutor { + executor := executorsById[executorId] + var componentProps []oscalTypes.Property + var groupNumber = 00 + + for _, execProc := range procs { + checkProps := makeCheck(execProc.requirement.EntryId, execProc.procedure, groupNumber) + groupNumber += 1 + componentProps = append(componentProps, checkProps...) + } + + component := oscalTypes.DefinedComponent{ + UUID: uuid.NewUUID(), + Type: "validation", + Title: executor.Id, + Props: utils.NilIfEmpty(&componentProps), + } + c.validationComponent = append(c.validationComponent, component) } - c.validationComponent = append(c.validationComponent, component) + return c } +type executorProcedure struct { + requirement layer4.Mapping + procedure layer4.AssessmentProcedure +} + // AddParameterModifiers takes parameter modifications for a given Layer 2 reference and creates OSCAL set-parameters // on the associated control set implementations. This will only take effect if the Layer 2 Catalogs has been added // through AddTargetComponent. diff --git a/component/builder_test.go b/component/builder_test.go index 3b3882e..de8499e 100644 --- a/component/builder_test.go +++ b/component/builder_test.go @@ -31,6 +31,13 @@ func TestDefinitionBuilder_Build(t *testing.T) { Name: "myvalidator", }, }, + Executors: []layer4.AssessmentExecutor{ + { + Id: "automated-executor", + Name: "Automated Executor", + Type: layer4.Automated, + }, + }, Plans: []layer4.AssessmentPlan{ { Control: layer4.Mapping{ @@ -46,6 +53,11 @@ func TestDefinitionBuilder_Build(t *testing.T) { Id: "my-check-id", Name: "My Check", Description: "My Check", + Executors: []layer4.ExecutorMapping{ + { + Id: "automated-executor", + }, + }, }, }, }, @@ -61,6 +73,8 @@ func TestDefinitionBuilder_Build(t *testing.T) { components := *componentDefinition.Components require.Len(t, *components[0].Props, 5) require.Len(t, *components[1].Props, 3) + require.Equal(t, "automated-executor", components[1].Title) + require.Equal(t, "validation", components[1].Type) ci := *components[0].ControlImplementations require.Len(t, ci, 1) @@ -84,3 +98,67 @@ func TestDefinitionBuilder_Build(t *testing.T) { require.Len(t, ci, 1) require.Equal(t, []oscalTypes.SetParameter{{ParamId: "main_branch_min_approvals", Values: []string{"2"}}}, *ci[0].SetParameters) } + +func TestDefinitionBuilder_AddValidationComponent_MultipleExecutors(t *testing.T) { + plan := layer4.EvaluationPlan{ + Metadata: layer4.Metadata{ + Author: layer4.Author{ + Name: "myvalidator", + }, + }, + Executors: []layer4.AssessmentExecutor{ + { + Id: "executor-1", + Name: "Executor One", + Type: layer4.Automated, + }, + { + Id: "executor-2", + Name: "Executor Two", + Type: layer4.Automated, + }, + }, + Plans: []layer4.AssessmentPlan{ + { + Control: layer4.Mapping{ + EntryId: "OSPS-QA-07", + }, + Assessments: []layer4.Assessment{ + { + Requirement: layer4.Mapping{ + EntryId: "OSPS-QA-07.01", + }, + Procedures: []layer4.AssessmentProcedure{ + { + Id: "check-1", + Name: "Check One", + Description: "First check", + Executors: []layer4.ExecutorMapping{ + {Id: "executor-1"}, + }, + }, + { + Id: "check-2", + Name: "Check Two", + Description: "Second check", + Executors: []layer4.ExecutorMapping{ + {Id: "executor-2"}, + }, + }, + }, + }, + }, + }, + }, + } + + builder := NewDefinitionBuilder("ComponentDefinition", "v0.1.0") + componentDefinition := builder.AddValidationComponent(plan).Build() + require.Len(t, *componentDefinition.Components, 2) + + components := *componentDefinition.Components + require.Equal(t, "executor-1", components[0].Title) + require.Equal(t, "executor-2", components[1].Title) + require.Equal(t, "validation", components[0].Type) + require.Equal(t, "validation", components[1].Type) +} diff --git a/go.mod b/go.mod index de59523..7e6d04c 100644 --- a/go.mod +++ b/go.mod @@ -22,3 +22,5 @@ require ( github.com/goccy/go-yaml v1.18.0 github.com/stretchr/testify v1.11.1 ) + +replace github.com/ossf/gemara => github.com/jpower432/sci v0.0.0-20251107221736-76054b25e204 diff --git a/go.sum b/go.sum index 2329999..cc709f9 100644 --- a/go.sum +++ b/go.sum @@ -11,14 +11,14 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/jpower432/sci v0.0.0-20251107221736-76054b25e204 h1:p9UxM4mFFiZCst87Q6+KfkC4XzGk1sFYgexpRyW2lz0= +github.com/jpower432/sci v0.0.0-20251107221736-76054b25e204/go.mod h1:rY4YvaWvOSJthTE2jHudjwcCRIQ31Y7GpEc3pyJPIPM= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/oscal-compass/oscal-sdk-go v0.0.8 h1:9nVh/WLj8gqLKEDFBgc+XT4VUSwKROG5KYuF6exOtmM= github.com/oscal-compass/oscal-sdk-go v0.0.8/go.mod h1:+8uyfcY5YkpB2CUXEeVZnWmSwi8Po5MV5yflLPDjRKE= -github.com/ossf/gemara v0.12.1 h1:Cyiytndw3HnyrctXE/iV4OzZURwypie2lmI7bf1bLAs= -github.com/ossf/gemara v0.12.1/go.mod h1:rY4YvaWvOSJthTE2jHudjwcCRIQ31Y7GpEc3pyJPIPM= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ=