Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 10 additions & 8 deletions pkg/policies/engine/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,12 @@ type CommonEngineOptions struct {
EnablePrint bool
ControlPlaneConnection *grpc.ClientConn
// ProjectName / ProjectVersionName carry the project + version this engine
// instance is evaluating policies for. They are surfaced to chainloop.* built-ins
// via the per-evaluation context.Context (see builtins.WithProjectContext) so a
// built-in like chainloop.findings can scope its query without the rego author
// having to pass the values explicitly. Either may be empty (e.g. local dev
// eval without flags) — built-ins must degrade gracefully in that case.
// instance is evaluating policies for. The rego engine merges them into
// input.chainloop_metadata.project_name / .project_version_name at evaluation
// time so policy authors can read them from input and forward them as operands
// to chainloop.* built-ins (e.g. chainloop.effective_assessments). Either may
// be empty (e.g. local dev eval without flags); rego authors should treat them
// as optional.
ProjectName string
ProjectVersionName string
}
Expand Down Expand Up @@ -115,9 +116,10 @@ func WithGRPCConn(conn *grpc.ClientConn) Option {
}

// WithProjectContext sets the project name and version that this engine
// instance is evaluating policies for. The values are propagated to chainloop.*
// built-ins through the per-evaluation context so they can scope queries
// (e.g. chainloop.findings) without the rego author passing them explicitly.
// instance is evaluating policies for. The rego engine exposes them on the
// per-evaluation input as input.chainloop_metadata.project_name /
// input.chainloop_metadata.project_version_name so policy authors can pass them
// as operands to chainloop.* built-ins. Either value may be empty.
func WithProjectContext(name, version string) Option {
return func(opts *Options) {
opts.ProjectName = name
Expand Down
44 changes: 0 additions & 44 deletions pkg/policies/engine/rego/builtins/context.go

This file was deleted.

64 changes: 0 additions & 64 deletions pkg/policies/engine/rego/builtins/context_test.go

This file was deleted.

55 changes: 37 additions & 18 deletions pkg/policies/engine/rego/rego.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,25 +88,42 @@ func (p *regoOutputHook) Print(_ print.Context, msg string) error { //nolint:for
// Force interface
var _ engine.PolicyEngine = (*Engine)(nil)

// withProjectContext attaches the engine's per-evaluation project name / version
// to ctx so chainloop.* built-ins can read them from bctx.Context. Skipped when
// the engine was created without project context (e.g. local dev eval).
func (r *Engine) withProjectContext(ctx context.Context) context.Context {
// chainloop_metadata input keys. These form a contract with rego authors who
// read e.g. `input.chainloop_metadata.project_name` and forward it as an
// operand to chainloop.* built-ins; treat them as stable.
const (
chainloopMetadataKey = "chainloop_metadata"
chainloopProjectNameKey = "project_name"
chainloopProjectVersionNameKey = "project_version_name"
)

// injectProjectMetadata merges the engine's project name / version (when set)
// into input.chainloop_metadata. Existing keys (e.g. the intoto descriptor
// populated by Attestation_Material.GetEvaluableContent) are preserved.
func (r *Engine) injectProjectMetadata(inputMap map[string]interface{}) map[string]interface{} {
if r.CommonEngineOptions == nil {
return ctx
return inputMap
}
if r.ProjectName == "" && r.ProjectVersionName == "" {
return ctx
return inputMap
}

cm, _ := inputMap[chainloopMetadataKey].(map[string]interface{})
if cm == nil {
cm = make(map[string]interface{})
}
if r.ProjectName != "" {
cm[chainloopProjectNameKey] = r.ProjectName
}
if r.ProjectVersionName != "" {
cm[chainloopProjectVersionNameKey] = r.ProjectVersionName
}
return builtins.WithProjectContext(ctx, builtins.ProjectContext{
Name: r.ProjectName,
Version: r.ProjectVersionName,
})
inputMap[chainloopMetadataKey] = cm

return inputMap
}

func (r *Engine) Verify(ctx context.Context, policy *engine.Policy, input []byte, args map[string]any) (*engine.EvaluationResult, error) {
ctx = r.withProjectContext(ctx)

policyString := string(policy.Source)
parsedModule, err := ast.ParseModule(policy.Name, policyString)
if err != nil {
Expand All @@ -128,6 +145,10 @@ func (r *Engine) Verify(ctx context.Context, policy *engine.Policy, input []byte
decodedInput = inputMap
}

if inputMap, ok := decodedInput.(map[string]interface{}); ok {
decodedInput = r.injectProjectMetadata(inputMap)
}

// put arguments embedded in the input object
if args != nil {
inputMap, ok := decodedInput.(map[string]interface{})
Expand Down Expand Up @@ -341,8 +362,6 @@ func getRuleName(packagePath ast.Ref, rule string) string {
// MatchesParameters evaluates the matches_parameters rule in a rego policy.
// The function creates an input object with policy parameters and expected parameters.
func (r *Engine) MatchesParameters(ctx context.Context, policy *engine.Policy, evaluationParams, expectedParams map[string]string) (bool, error) {
ctx = r.withProjectContext(ctx)

policyString := string(policy.Source)
parsedModule, err := ast.ParseModule(policy.Name, policyString)
if err != nil {
Expand All @@ -361,9 +380,10 @@ func (r *Engine) MatchesParameters(ctx context.Context, policy *engine.Policy, e
} else {
inputMap[expectedArgs] = expectedParams
}
decodedInput := r.injectProjectMetadata(inputMap)

// Evaluate matches_parameters rule
matchesParameters, found, err := r.evaluateMatchingRule(ctx, getRuleName(parsedModule.Package.Path, matchesParametersRule), parsedModule, inputMap)
matchesParameters, found, err := r.evaluateMatchingRule(ctx, getRuleName(parsedModule.Package.Path, matchesParametersRule), parsedModule, decodedInput)
if err != nil {
return false, err
}
Expand All @@ -378,8 +398,6 @@ func (r *Engine) MatchesParameters(ctx context.Context, policy *engine.Policy, e
// MatchesEvaluation evaluates the matches_evaluation rule in a rego policy.
// Creates an input object with expected parameters and policy violations.
func (r *Engine) MatchesEvaluation(ctx context.Context, policy *engine.Policy, violations []string, expectedParams map[string]string) (bool, error) {
ctx = r.withProjectContext(ctx)

policyString := string(policy.Source)
parsedModule, err := ast.ParseModule(policy.Name, policyString)
if err != nil {
Expand All @@ -398,9 +416,10 @@ func (r *Engine) MatchesEvaluation(ctx context.Context, policy *engine.Policy, v
} else {
inputMap[violationsResult] = violations
}
decodedInput := r.injectProjectMetadata(inputMap)

// Evaluate matches_evaluation rule
matchesEvaluation, found, err := r.evaluateMatchingRule(ctx, getRuleName(parsedModule.Package.Path, matchesEvaluationRule), parsedModule, inputMap)
matchesEvaluation, found, err := r.evaluateMatchingRule(ctx, getRuleName(parsedModule.Package.Path, matchesEvaluationRule), parsedModule, decodedInput)
if err != nil {
return false, err
}
Expand Down
Loading
Loading