From 86ed1c71299d52313bb955ce1882be257cf535db Mon Sep 17 00:00:00 2001 From: Jennifer Power Date: Tue, 25 Feb 2025 13:17:13 -0500 Subject: [PATCH 1/3] test: add test to reproduce bug in rules.Store When a validation component is indexed with the same title as an existing validation component, the rule data is not merged, it is overwritten. Signed-off-by: Jennifer Power --- rules/memory_test.go | 33 ++++++- testdata/component-definition-test2.json | 113 +++++++++++++++++++++++ 2 files changed, 142 insertions(+), 4 deletions(-) create mode 100644 testdata/component-definition-test2.json diff --git a/rules/memory_test.go b/rules/memory_test.go index 3d1de48..3bf0c88 100644 --- a/rules/memory_test.go +++ b/rules/memory_test.go @@ -158,6 +158,7 @@ func TestMemoryStore_FindByComponent(t *testing.T) { validator1RuleSet, err := testMemory.FindByComponent(testCtx, "Validator") require.NoError(t, err) + require.Len(t, validator1RuleSet, 1) require.Contains(t, validator1RuleSet, expectedKeyFileRule) validator2RuleSet, err := testMemory.FindByComponent(testCtx, "Validator2") @@ -166,24 +167,48 @@ func TestMemoryStore_FindByComponent(t *testing.T) { _, err = testMemory.FindByComponent(testCtx, "not_a_component") require.EqualError(t, err, "failed to find rules for component \"not_a_component\"") + + // Add a new target component for ="Validator" + testDataPath := "../testdata/component-definition-test2.json" + loadComponents(t, testMemory, testDataPath) + + expectedExampleRule := extensions.RuleSet{ + Rule: extensions.Rule{ + ID: "example_rule_1", + Description: "Example rule 1 description", + }, + Checks: []extensions.Check{ + { + ID: "example_check_1", + Description: "Example check 1 description", + }, + }, + } + + validator1RuleSet, err = testMemory.FindByComponent(testCtx, "Validator") + require.NoError(t, err) + require.Len(t, validator1RuleSet, 2) + require.Contains(t, validator1RuleSet, expectedExampleRule, expectedKeyFileRule) } func prepMemoryStore(t *testing.T) *MemoryStore { testDataPath := "../testdata/component-definition-test.json" + testMemory := NewMemoryStore() + loadComponents(t, testMemory, testDataPath) + return testMemory +} +func loadComponents(t *testing.T, store *MemoryStore, testDataPath string) { file, err := os.Open(testDataPath) require.NoError(t, err) definition, err := generators.NewComponentDefinition(file) require.NoError(t, err) - testMemory := NewMemoryStore() var comps []components.Component for _, cp := range *definition.Components { adapters := components.NewDefinedComponentAdapter(cp) comps = append(comps, adapters) } - err = testMemory.IndexAll(comps) + err = store.IndexAll(comps) require.NoError(t, err) - - return testMemory } diff --git a/testdata/component-definition-test2.json b/testdata/component-definition-test2.json new file mode 100644 index 0000000..b7a2393 --- /dev/null +++ b/testdata/component-definition-test2.json @@ -0,0 +1,113 @@ +{ + "component-definition": { + "uuid": "c14d8812-7098-4a9b-8f89-cba41b6ff0d8", + "metadata": { + "title": "Component definition for TestOS", + "last-modified": "2023-02-21T06:53:42+00:00", + "version": "1.1", + "oscal-version": "1.1.2" + }, + "components": [ + { + "uuid": "c8106bc8-5174-4e86-91a4-52f2fe0ed027", + "type": "service", + "title": "TestOS", + "description": "TestOS", + "props": [ + { + "name": "Rule_Id", + "ns": "https://oscal-compass.github.io/compliance-trestle/schemas/oscal/cd", + "value": "example_rule_1", + "remarks": "rule_set_00" + }, + { + "name": "Rule_Description", + "ns": "https://oscal-compass.github.io/compliance-trestle/schemas/oscal/cd", + "value": "Example rule 1", + "remarks": "rule_set_00" + }, + { + "name": "Parameter_Id", + "ns": "https://oscal-compass.github.io/compliance-trestle/schemas/oscal/cd", + "value": "file_name", + "remarks": "rule_set_00" + }, + { + "name": "Parameter_Description", + "ns": "https://oscal-compass.github.io/compliance-trestle/schemas/oscal/cd", + "value": "A parameter for a file name", + "remarks": "rule_set_00" + } + ], + "control-implementations": [ + { + "uuid": "f79d6290-8efa-4ea7-b931-27b8435cf707", + "source": "profiles/cis/profile.json", + "description": "CIS Profile", + "set-parameters": [ + { + "param-id": "file_name", + "values": [ + "file_name_override" + ] + } + ], + "implemented-requirements": [ + { + "uuid": "a1b5b713-52c7-46fb-ab57-ebac7f576b23", + "control-id": "CIS-2.1", + "description": "", + "props": [ + { + "name": "Rule_Id", + "ns": "https://oscal-compass.github.io/compliance-trestle/schemas/oscal/cd", + "value": "example_rule_1" + } + ], + "statements": [ + { + "statement-id": "CIS-2.1_smt", + "uuid": "cb9219b1-e51c-4680-abb0-616a43bbfbb2", + "description": "" + } + ] + } + ] + } + ] + }, + { + "uuid": "701c70f1-482b-42b0-a419-9870158cd9e2", + "type": "validation", + "title": "Validator", + "description": "An example validation component", + "props": [ + { + "name": "Rule_Id", + "ns": "https://oscal-compass.github.io/compliance-trestle/schemas/oscal/cd", + "value": "example_rule_1", + "remarks": "rule_set_09" + }, + { + "name": "Rule_Description", + "ns": "https://oscal-compass.github.io/compliance-trestle/schemas/oscal/cd", + "value": "Example rule 1", + "remarks": "rule_set_10" + }, + { + "name": "Check_Id", + "ns": "https://oscal-compass.github.io/compliance-trestle/schemas/oscal/cd", + "value": "example_check_1", + "remarks": "rule_set_09" + }, + { + "name": "Check_Description", + "ns": "https://oscal-compass.github.io/compliance-trestle/schemas/oscal/cd", + "value": "Example check 1 description", + "remarks": "rule_set_09" + } + ] + } + ] + } +} From 9179a9c9c6e89be1807b9ca1bec981ad6baec29d Mon Sep 17 00:00:00 2001 From: Jennifer Power Date: Tue, 25 Feb 2025 13:56:01 -0500 Subject: [PATCH 2/3] fix: updates rules.Store memory to merged extracted rules and checks Signed-off-by: Jennifer Power --- rules/memory.go | 51 ++++++++++++++++++------ testdata/component-definition-test2.json | 24 +---------- 2 files changed, 41 insertions(+), 34 deletions(-) diff --git a/rules/memory.go b/rules/memory.go index 35fef3b..c5f269d 100644 --- a/rules/memory.go +++ b/rules/memory.go @@ -64,24 +64,52 @@ func (m *MemoryStore) IndexAll(components []components.Component) error { return fmt.Errorf("failed to index components: %w", ErrComponentsNotFound) } for _, component := range components { - extractedRules := m.indexComponent(component) + + // Catalog information here at the component in the MemoryStore at the + // component level + + componentTitle := component.Title() + extractedRules, extractedChecks := m.indexComponent(component) if len(extractedRules) != 0 { + existingRules, ok := m.rulesByComponent[componentTitle] + if ok { + for rule := range existingRules { + extractedRules.Add(rule) + } + } m.rulesByComponent[component.Title()] = extractedRules } + existingRules, ok := m.rulesByComponent[componentTitle] + if ok { + for rule := range existingRules { + extractedRules.Add(rule) + } + } + + if len(extractedChecks) != 0 { + existingChecks, ok := m.checksByValidationComponent[componentTitle] + if ok { + for check := range existingChecks { + extractedChecks.Add(check) + } + } + m.checksByValidationComponent[componentTitle] = extractedChecks + } } return nil } -func (m *MemoryStore) indexComponent(component components.Component) set.Set[string] { +// indexComponent returns extracted rules and checks (respectively) from a given component. +func (m *MemoryStore) indexComponent(component components.Component) (set.Set[string], set.Set[string]) { + // Catalog all registered rules for all components and check implementations by validation component for filtering in + // `rules.FindByComponent`. rules := set.New[string]() + checks := set.New[string]() + if len(component.Props()) == 0 { - return rules + return rules, checks } - // Catalog all registered check implementations by validation component for filtering in - // `rules.FindByComponent`. - checkIds := set.New[string]() - // Each rule set is linked by a group id in the property remarks byRemarks := groupPropsByRemarks(component.Props()) for _, propSet := range byRemarks { @@ -95,7 +123,8 @@ func (m *MemoryStore) indexComponent(component components.Component) set.Set[str ruleSet = extensions.RuleSet{} } - // A check may or may not be registered. + // A check may or may not be registered depending on + // component.Type(). placeholderCheck := extensions.Check{} for prop := range propSet { @@ -130,15 +159,13 @@ func (m *MemoryStore) indexComponent(component components.Component) set.Set[str if placeholderCheck.ID != "" { ruleSet.Checks = append(ruleSet.Checks, placeholderCheck) m.byCheck[placeholderCheck.ID] = ruleSet.Rule.ID + checks.Add(placeholderCheck.ID) } rules.Add(ruleSet.Rule.ID) m.nodes[ruleSet.Rule.ID] = ruleSet } - if len(checkIds) != 0 { - m.checksByValidationComponent[component.Title()] = checkIds - } - return rules + return rules, checks } func (m *MemoryStore) GetByRuleID(_ context.Context, ruleId string) (extensions.RuleSet, error) { diff --git a/testdata/component-definition-test2.json b/testdata/component-definition-test2.json index b7a2393..852f6be 100644 --- a/testdata/component-definition-test2.json +++ b/testdata/component-definition-test2.json @@ -23,19 +23,7 @@ { "name": "Rule_Description", "ns": "https://oscal-compass.github.io/compliance-trestle/schemas/oscal/cd", - "value": "Example rule 1", - "remarks": "rule_set_00" - }, - { - "name": "Parameter_Id", - "ns": "https://oscal-compass.github.io/compliance-trestle/schemas/oscal/cd", - "value": "file_name", - "remarks": "rule_set_00" - }, - { - "name": "Parameter_Description", - "ns": "https://oscal-compass.github.io/compliance-trestle/schemas/oscal/cd", - "value": "A parameter for a file name", + "value": "Example rule 1 description", "remarks": "rule_set_00" } ], @@ -44,14 +32,6 @@ "uuid": "f79d6290-8efa-4ea7-b931-27b8435cf707", "source": "profiles/cis/profile.json", "description": "CIS Profile", - "set-parameters": [ - { - "param-id": "file_name", - "values": [ - "file_name_override" - ] - } - ], "implemented-requirements": [ { "uuid": "a1b5b713-52c7-46fb-ab57-ebac7f576b23", @@ -91,7 +71,7 @@ { "name": "Rule_Description", "ns": "https://oscal-compass.github.io/compliance-trestle/schemas/oscal/cd", - "value": "Example rule 1", + "value": "Example rule 1 description", "remarks": "rule_set_10" }, { From 04663293635123e398dc25352007b5de050101d3 Mon Sep 17 00:00:00 2001 From: Jennifer Power Date: Fri, 28 Feb 2025 10:57:14 -0500 Subject: [PATCH 3/3] fix: removes "=" from comment in memory_test.go Signed-off-by: Jennifer Power --- rules/memory_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rules/memory_test.go b/rules/memory_test.go index 3bf0c88..a6c0185 100644 --- a/rules/memory_test.go +++ b/rules/memory_test.go @@ -168,7 +168,7 @@ func TestMemoryStore_FindByComponent(t *testing.T) { _, err = testMemory.FindByComponent(testCtx, "not_a_component") require.EqualError(t, err, "failed to find rules for component \"not_a_component\"") - // Add a new target component for ="Validator" + // Add a new target component for "Validator" testDataPath := "../testdata/component-definition-test2.json" loadComponents(t, testMemory, testDataPath)