core/pkg/opaprocessor/PROCESS_FLOW.md
This document traces the OPA/Rego rule-evaluation pipeline in core/pkg/opaprocessor/. It is intended as an onboarding guide for contributors who want to understand how Kubescape turns scanned Kubernetes objects into misconfiguration results.
Controls.PolicyRule objects plus metadata.data and input values passed to every rule (cloud provider, control inputs).reporthandling.ParseRegoResult from an OPA resultSet.graph TD
A[OPAProcessor.ProcessRulesListener] --> B[convertFrameworksToPolicies]
A --> C[ConvertFrameworksToSummaryDetails]
A --> D[OPAProcessor.Process]
D --> E[processControl]
E --> F[processRule]
F --> G[getAllSupportedObjects]
G --> H[RegoResourcesAggregator]
H --> I[enumerateData]
I --> J[runOPAOnSingleRule]
J --> K{RuleLanguage}
K -->|Rego| L[runRegoOnK8s]
L --> M[getCompiledRule]
L --> N[regoEval]
N --> O[ParseRegoResult]
K -->|CEL| P[runCELOnK8s stub]
O --> Q[build failed & passed ResourceAssociatedRule maps]
D --> R[BuildScanCoverage/ComputeCoverageScore]
R --> S[updateResults]
S --> T[markTimedOutControlsSkipped]
T --> U[scorewrapper.Calculate]
U --> V[reweightComplianceScores]
OPAProcessor and NewOPAProcessorprocessorhandler.go defines OPAProcessor and NewOPAProcessor. The struct holds the compiled-module cache (compiledModules), per-control timeout state (ControlTimeout, TimedOutControls), namespace filters, and the OPASessionObj that everything is recorded into.
ProcessRulesListenerThe public entry point in processorhandler.go:
cautils.Policies via convertFrameworksToPolicies.ConvertFrameworksToSummaryDetails.Process to run the controls.BuildScanCoverage and ComputeCoverageScore after Process returns.updateResults to apply exceptions and update summaries.markTimedOutControlsSkipped to mark controls that timed out.scorewrapper.Calculate to compute the posture score.reweightComplianceScores to reweight compliance scores.ProcessThe main control loop in processorhandler.go. For every Control in the policy set:
context.WithTimeout when ControlTimeout is configured; if the deadline is exceeded, the control is marked via markControlTimedOut and recorded as not evaluated.processControl for the actual rule evaluation.resourcesAssociatedControl map into opap.ResourcesResult.processControlIterates over the rules in a control and calls processRule for each. If a rule returns a non-empty ResourceAssociatedRule map, it builds a ResourceAssociatedControl and sets its status from the overall control definition.
processRuleThis is where per-rule, per-namespace work happens:
getAllSupportedObjects selects Kubernetes and external resources that match the rule's Match / DynamicMatch constraints.RegoResourcesAggregator assembles the objects the rule will see as input.enumerateData optionally narrows the list using a rule's ResourceEnumerator (a Rego snippet that filters the input set).runOPAOnSingleRule dispatches to runRegoOnK8s or the CEL stub based on rule.RuleLanguage.RuleResponse objects, the function performs a two-pass merge:
failedIDs and creates ResourceAssociatedRule entries for failed resources.StatusPassed.ResourceAssociatedRule.runRegoOnK8s and regoEvalrunRegoOnK8s in processorhandler.go:
cosign.verify, cosign.has_signature, image.parse_normalized_name) once via sync.Once.getRuleData.getCompiledRule, which caches the *ast.Compiler by rule name + source.storage.Store from ruleRegoDependenciesData.regoEval to run OPA with the compiled module, the store, and the K8s objects as input.resultSet into []reporthandling.RuleResponse.regoEval uses rego.New with a fixed query data.armo_builtins, ast.RegoV0, and rego.Input(inputObj).
updateResultsprocessorhandlerutils.go:
AllResources (removeData).ResourcesResult entry.inputworkloadinterface.IMetadata objects are collected into []workloadinterface.IMetadata per namespace.RegoResourcesAggregator optionally aggregates/filters the []workloadinterface.IMetadata list (e.g. subject/role aggregation for RBAC rules) while keeping it as []workloadinterface.IMetadata.workloadinterface.ListMetaToMap converts the []workloadinterface.IMetadata into the raw []map[string]any slice.regoEval passes this slice to OPA as rego.Input(inputObj).rego.Eval returns a rego.ResultSet.reporthandling.ParseRegoResult converts it to []reporthandling.RuleResponse.processRule maps each failed resource to a *resourcesresults.ResourceAssociatedRule with StatusFailed, paths, and related objects.StatusPassed.processControl wraps rule results in resourcesresults.ResourceAssociatedControl.updateResults applies exceptions and pushes the final data into opap.Report.runOPAOnSingleRule currently dispatches to runCELOnK8s, which is a stub that returns an error. The CEL evaluator under core/pkg/opaprocessor/cel/ is intended to become the second rule language. The rest of processorhandler.go is already structured to treat RuleResponse as a language-agnostic result.
The package already has useful tests that exercise this flow:
processorhandler_test.go — TestProcessRule, TestProcessResourcesResultprocessorhandler_timeout_test.go — TestProcess_ControlTimeoutprocessorhandler_clusterscope_test.go — TestProcessRule_ClusterScopedPathsAcrossNamespacesGood follow-up contributions include:
markResourcesSkipped error paths.getCompiledRule cache behavior.regoEval with a tiny inline Rego module.core/pkg/policyhandler/ — where frameworks and controls are loaded before evaluation.github.com/kubescape/regolibrary — the actual Rego rules (not in this repo).github.com/kubescape/opa-utils/reporthandling — PolicyRule, RuleResponse, and RegoResourcesAggregator.