fix(compliance): fix Control Reference for E8 and other profiles#19946
fix(compliance): fix Control Reference for E8 and other profiles#19946
Conversation
Add a new column after "Profile(version)" that shows whether the profile is a regular "Profile" or a "Tailored Profile". The value is derived from the profile's OperatorKind enum stored in the database. UNSPECIFIED kind (should not occur in practice) renders as "Data Not Available" rather than defaulting to "Profile".
GetControlsForScanResults mapped profile names to benchmark shortnames via a hardcoded regex, then filtered controls by that shortname. Profiles like ocp4-e8 mapped to "E8", but no rule carries controls with standard "E8" — those rules have controls under CIS-OCP, NIST-800-53, etc. This caused "Data Not Available" in the Control Reference column for affected profiles. Remove the benchmark filter: query all controls for the given rule names directly. Controls are already scoped to rules via CO annotations, so filtering by benchmark is redundant and lossy. Also fix deprecated GetByQuery calls (SA1019) in the same file.
|
Skipping CI for Draft Pull Request. |
📝 WalkthroughSummary by CodeRabbit
WalkthroughThe PR simplifies the compliance rules datastore API by introducing a new Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes 🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #19946 +/- ##
==========================================
- Coverage 49.56% 49.56% -0.01%
==========================================
Files 2764 2764
Lines 208351 208405 +54
==========================================
+ Hits 103269 103289 +20
- Misses 97430 97464 +34
Partials 7652 7652
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
🚀 Build Images ReadyImages are ready for commit 9cefe9a. To use with deploy scripts: export MAIN_IMAGE_TAG=4.11.x-623-g9cefe9a6a5 |
There was a problem hiding this comment.
Hey - I've left some high level feedback:
- Now that controls are no longer filtered by benchmark,
GetControlsForScanResults’sprofileNameparameter is unused and explicitly ignored; consider removing this parameter and updating call sites to simplify the API and avoid confusion about its role. - If
GetControlsByRulesAndBenchmarksis no longer used after switching all callers toGetControlsByRules, consider removing the older method from the datastore interface, implementation, and mocks to reduce dead code and keep the datastore surface minimal.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- Now that controls are no longer filtered by benchmark, `GetControlsForScanResults`’s `profileName` parameter is unused and explicitly ignored; consider removing this parameter and updating call sites to simplify the API and avoid confusion about its role.
- If `GetControlsByRulesAndBenchmarks` is no longer used after switching all callers to `GetControlsByRules`, consider removing the older method from the datastore interface, implementation, and mocks to reduce dead code and keep the datastore surface minimal.Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (3)
central/complianceoperator/v2/rules/datastore/datastore.go (1)
32-36: Deprecate the benchmark-filtered API to prevent regression-prone usage.With both methods available, future callers can accidentally reintroduce the lossy benchmark filter. Please mark
GetControlsByRulesAndBenchmarksas deprecated (or remove it once no callers remain) and steer usage toGetControlsByRules.As per coding guidelines, "Focus on major issues impacting performance, readability, maintainability and security. Avoid nitpicks and avoid verbosity."Suggested interface annotation
- // GetControlsByRulesAndBenchmarks returns controls by a list of rule names group by control, standard and rule name. + // Deprecated: GetControlsByRulesAndBenchmarks applies benchmark filtering and may drop valid controls. + // Prefer GetControlsByRules. + // GetControlsByRulesAndBenchmarks returns controls by a list of rule names group by control, standard and rule name. GetControlsByRulesAndBenchmarks(ctx context.Context, ruleNames []string, benchmarkNames []string) ([]*ControlResult, error)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@central/complianceoperator/v2/rules/datastore/datastore.go` around lines 32 - 36, Mark the benchmark-filtered API GetControlsByRulesAndBenchmarks as deprecated and steer usage to GetControlsByRules: add a clear deprecation comment/docstring above the method declaration in the interface (mention it is deprecated and to use GetControlsByRules instead), optionally add a // Deprecated: ... line so static analyzers pick it up, and scan/update callers to replace uses of GetControlsByRulesAndBenchmarks with GetControlsByRules (or remove the method entirely once no callers remain); keep the function signature intact for now to avoid breakage but add the deprecation note and an issue/todo to remove it later.central/complianceoperator/v2/report/manager/results/results_aggregator.go (1)
125-131: Replace the 4-value tuple fromgetProfileInfowith a typed return object.Line 147 now returns
(string, string, string, error), which is easy to mis-order at call sites (Line 125). A small struct improves safety and readability as this path evolves.As per coding guidelines, "Focus on major issues impacting performance, readability, maintainability and security. Avoid nitpicks and avoid verbosity."Refactor sketch
+type profileInfo struct { + displayName string + profileName string + profileType string +} - -func (g *Aggregator) getProfileInfo(...) (string, string, string, error) { +func (g *Aggregator) getProfileInfo(...) (*profileInfo, error) { ... - return profileInfo, profiles[0].GetName(), profileType, nil + return &profileInfo{ + displayName: profileInfo, + profileName: profiles[0].GetName(), + profileType: profileType, + }, nil } - profileInfo, profileName, profileType, err := g.getProfileInfo(...) + pInfo, err := g.getProfileInfo(...) if err != nil { ... } - row.Profile = profileInfo - row.ProfileType = profileType + row.Profile = pInfo.displayName + row.ProfileType = pInfo.profileType - controlsInfo, err := g.getControlsInfo(ctx, checkResult, profileName) + controlsInfo, err := g.getControlsInfo(ctx, checkResult, pInfo.profileName)Also applies to: 147-161
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@central/complianceoperator/v2/report/manager/results/results_aggregator.go` around lines 125 - 131, Change getProfileInfo to return a typed struct instead of three separate strings and an error (e.g., type ProfileMeta struct { Name string; Info string; Type string }), update its signature to (ProfileMeta, error), and update all call sites including the usage in results_aggregator.go where you currently do profileInfo, profileName, profileType, err := g.getProfileInfo(...) to instead receive a single value (meta, err := g.getProfileInfo(...)) and then set row.Profile = meta.Info and row.ProfileType = meta.Type (and any use of profileName -> meta.Name); also update other callers referenced around lines 147-161 to use the new struct fields. Ensure imports and any tests are adjusted to the new type.central/complianceoperator/v2/report/manager/results/results_aggregator_test.go (1)
356-357: Assert the rule-name slice passed intoGetControlsByRules.Using
gomock.Any()here means this suite no longer checks that aggregation is actually scoping controls by the resolved rule names. That’s the regression boundary this PR is fixing, so the mock should verify the expected slice instead of only the call count.Example tightening
if tcase.expectedControls != nil { - s.ruleDS.EXPECT().GetControlsByRules(gomock.Any(), gomock.Any()).Times(1).Return(tcase.expectedControls()) + expRules, _ := tcase.expectedRules() + expRuleNames := []string{expRules[0].GetName()} + s.ruleDS.EXPECT(). + GetControlsByRules(gomock.Any(), gomock.Eq(expRuleNames)). + Times(1). + Return(tcase.expectedControls()) }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@central/complianceoperator/v2/report/manager/results/results_aggregator_test.go` around lines 356 - 357, The test currently uses gomock.Any() for the rule-name slice when setting the ruleDS.EXPECT().GetControlsByRules call, which lets the test miss verifying that aggregation scopes by resolved rule names; change the second gomock.Any() to assert the exact slice of rule names from the testcase (e.g. gomock.Eq(tcase.resolvedRuleNames) or the field that holds the expected rule-name slice on tcase) so ruleDS.EXPECT().GetControlsByRules(gomock.Any(), gomock.Eq(tcase.resolvedRuleNames)).Times(1).Return(tcase.expectedControls()) and import/adjust any test fields as needed to provide that expected slice.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@central/complianceoperator/v2/rules/datastore/datastore_impl.go`:
- Around line 97-123: GetControlsByRules currently builds and runs a query even
when the input ruleNames slice is empty; add an early return at the start of
datastoreImpl.GetControlsByRules that checks if len(ruleNames) == 0 and returns
an empty []*ControlResult and nil error to prevent unbounded or backend-specific
behavior and avoid hitting Postgres unnecessarily.
---
Nitpick comments:
In
`@central/complianceoperator/v2/report/manager/results/results_aggregator_test.go`:
- Around line 356-357: The test currently uses gomock.Any() for the rule-name
slice when setting the ruleDS.EXPECT().GetControlsByRules call, which lets the
test miss verifying that aggregation scopes by resolved rule names; change the
second gomock.Any() to assert the exact slice of rule names from the testcase
(e.g. gomock.Eq(tcase.resolvedRuleNames) or the field that holds the expected
rule-name slice on tcase) so ruleDS.EXPECT().GetControlsByRules(gomock.Any(),
gomock.Eq(tcase.resolvedRuleNames)).Times(1).Return(tcase.expectedControls())
and import/adjust any test fields as needed to provide that expected slice.
In `@central/complianceoperator/v2/report/manager/results/results_aggregator.go`:
- Around line 125-131: Change getProfileInfo to return a typed struct instead of
three separate strings and an error (e.g., type ProfileMeta struct { Name
string; Info string; Type string }), update its signature to (ProfileMeta,
error), and update all call sites including the usage in results_aggregator.go
where you currently do profileInfo, profileName, profileType, err :=
g.getProfileInfo(...) to instead receive a single value (meta, err :=
g.getProfileInfo(...)) and then set row.Profile = meta.Info and row.ProfileType
= meta.Type (and any use of profileName -> meta.Name); also update other callers
referenced around lines 147-161 to use the new struct fields. Ensure imports and
any tests are adjusted to the new type.
In `@central/complianceoperator/v2/rules/datastore/datastore.go`:
- Around line 32-36: Mark the benchmark-filtered API
GetControlsByRulesAndBenchmarks as deprecated and steer usage to
GetControlsByRules: add a clear deprecation comment/docstring above the method
declaration in the interface (mention it is deprecated and to use
GetControlsByRules instead), optionally add a // Deprecated: ... line so static
analyzers pick it up, and scan/update callers to replace uses of
GetControlsByRulesAndBenchmarks with GetControlsByRules (or remove the method
entirely once no callers remain); keep the function signature intact for now to
avoid breakage but add the deprecation note and an issue/todo to remove it
later.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Central YAML (base), Organization UI (inherited)
Review profile: CHILL
Plan: Pro
Run ID: 692ab720-85a6-4de7-bf45-d0fa0ca8af69
📒 Files selected for processing (11)
central/complianceoperator/v2/checkresults/service/service_impl_test.gocentral/complianceoperator/v2/checkresults/stats/service/service_impl_test.gocentral/complianceoperator/v2/checkresults/utils/utils.gocentral/complianceoperator/v2/report/manager/format/formatter.gocentral/complianceoperator/v2/report/manager/format/formatter_test.gocentral/complianceoperator/v2/report/manager/results/results_aggregator.gocentral/complianceoperator/v2/report/manager/results/results_aggregator_test.gocentral/complianceoperator/v2/report/result.gocentral/complianceoperator/v2/rules/datastore/datastore.gocentral/complianceoperator/v2/rules/datastore/datastore_impl.gocentral/complianceoperator/v2/rules/datastore/mocks/datastore.go
| func (d *datastoreImpl) GetControlsByRules(ctx context.Context, ruleNames []string) ([]*ControlResult, error) { | ||
| builder := search.NewQueryBuilder() | ||
| builder.AddSelectFields( | ||
| search.NewQuerySelect(search.ComplianceOperatorControl), | ||
| search.NewQuerySelect(search.ComplianceOperatorStandard), | ||
| search.NewQuerySelect(search.ComplianceOperatorRuleName), | ||
| ) | ||
|
|
||
| builder.AddExactMatches(search.ComplianceOperatorRuleName, ruleNames...) | ||
|
|
||
| builder.AddGroupBy( | ||
| search.ComplianceOperatorRuleName, | ||
| search.ComplianceOperatorControl, | ||
| search.ComplianceOperatorStandard, | ||
| ) | ||
|
|
||
| query := builder.ProtoQuery() | ||
| var results []*ControlResult | ||
| err := pgSearch.RunSelectRequestForSchemaFn[ControlResult](ctx, d.db, postgresSchema.ComplianceOperatorRuleV2Schema, query, func(r *ControlResult) error { | ||
| results = append(results, r) | ||
| return nil | ||
| }) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| return results, nil |
There was a problem hiding this comment.
Short-circuit empty ruleNames before hitting Postgres.
This query now relies entirely on ComplianceOperatorRuleName for scoping. If ruleNames is empty, the method can fall through to an unbounded grouped select or backend-specific empty-match behavior. Returning early here makes the contract explicit and avoids an expensive wrong-result path.
Suggested fix
func (d *datastoreImpl) GetControlsByRules(ctx context.Context, ruleNames []string) ([]*ControlResult, error) {
+ if len(ruleNames) == 0 {
+ return nil, nil
+ }
+
builder := search.NewQueryBuilder()
builder.AddSelectFields(
search.NewQuerySelect(search.ComplianceOperatorControl),
search.NewQuerySelect(search.ComplianceOperatorStandard),
search.NewQuerySelect(search.ComplianceOperatorRuleName),📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| func (d *datastoreImpl) GetControlsByRules(ctx context.Context, ruleNames []string) ([]*ControlResult, error) { | |
| builder := search.NewQueryBuilder() | |
| builder.AddSelectFields( | |
| search.NewQuerySelect(search.ComplianceOperatorControl), | |
| search.NewQuerySelect(search.ComplianceOperatorStandard), | |
| search.NewQuerySelect(search.ComplianceOperatorRuleName), | |
| ) | |
| builder.AddExactMatches(search.ComplianceOperatorRuleName, ruleNames...) | |
| builder.AddGroupBy( | |
| search.ComplianceOperatorRuleName, | |
| search.ComplianceOperatorControl, | |
| search.ComplianceOperatorStandard, | |
| ) | |
| query := builder.ProtoQuery() | |
| var results []*ControlResult | |
| err := pgSearch.RunSelectRequestForSchemaFn[ControlResult](ctx, d.db, postgresSchema.ComplianceOperatorRuleV2Schema, query, func(r *ControlResult) error { | |
| results = append(results, r) | |
| return nil | |
| }) | |
| if err != nil { | |
| return nil, err | |
| } | |
| return results, nil | |
| func (d *datastoreImpl) GetControlsByRules(ctx context.Context, ruleNames []string) ([]*ControlResult, error) { | |
| if len(ruleNames) == 0 { | |
| return nil, nil | |
| } | |
| builder := search.NewQueryBuilder() | |
| builder.AddSelectFields( | |
| search.NewQuerySelect(search.ComplianceOperatorControl), | |
| search.NewQuerySelect(search.ComplianceOperatorStandard), | |
| search.NewQuerySelect(search.ComplianceOperatorRuleName), | |
| ) | |
| builder.AddExactMatches(search.ComplianceOperatorRuleName, ruleNames...) | |
| builder.AddGroupBy( | |
| search.ComplianceOperatorRuleName, | |
| search.ComplianceOperatorControl, | |
| search.ComplianceOperatorStandard, | |
| ) | |
| query := builder.ProtoQuery() | |
| var results []*ControlResult | |
| err := pgSearch.RunSelectRequestForSchemaFn[ControlResult](ctx, d.db, postgresSchema.ComplianceOperatorRuleV2Schema, query, func(r *ControlResult) error { | |
| results = append(results, r) | |
| return nil | |
| }) | |
| if err != nil { | |
| return nil, err | |
| } | |
| return results, nil | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@central/complianceoperator/v2/rules/datastore/datastore_impl.go` around lines
97 - 123, GetControlsByRules currently builds and runs a query even when the
input ruleNames slice is empty; add an early return at the start of
datastoreImpl.GetControlsByRules that checks if len(ruleNames) == 0 and returns
an empty []*ControlResult and nil error to prevent unbounded or backend-specific
behavior and avoid hitting Postgres unnecessarily.
Description
Fix "Data Not Available" in the Control Reference column of compliance CSV reports (and API responses) for profiles whose benchmark shortname doesn't match any
standardvalue in rule controls.Root cause:
GetControlsForScanResultsmapped profile names to benchmark shortnames via a hardcoded regex inbenchmark.go, then filtered controls by that shortname. For example,ocp4-e8mapped toE8, but the Compliance Operator never usesE8as a standard in its rule annotations — those rules have controls underCIS-OCP,NIST-800-53, etc. The queryWHERE standard = 'E8'always returned 0 rows.Fix: Remove the benchmark filter entirely. Query all controls for the given rule names directly via a new
GetControlsByRulesmethod. Controls are already scoped to rules via CO annotations, so filtering by benchmark is redundant and lossy.Affected profiles (confirmed):
ocp4-e8(E8), likelyocp4-pci-dss-4-0(PCI-DSS vs PCI-DSS-4-0 mismatch).Also fixes pre-existing
SA1019staticcheck warnings (deprecatedGetByQuery→GetByQueryFn) in the same file.User-facing documentation
Testing and quality
Automated testing
How I validated my change
E8standard exists incompliance_operator_rule_v2_controlsocp4-e8rules have controls underCIS-OCP,NIST-800-53,BSI,PCI-DSS, etc.ocp4-e8profile to confirm controls appear in CSV