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
11 changes: 6 additions & 5 deletions pkg/policy/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,33 +48,34 @@ func Engine(ctx context.Context, policyConfig *Params, policies []Policy) error

log.Debugf("field mapping done via extractor")

var results []Result
var policyResults []PolicyResult

log.Debugf("Evaluation of policy against SBOM begins...")

// Evaluate policies
for _, policy := range policies {
log.Debugf("Evaluating policy: ", policy.Name)

// evaluate each policy one by one against SBOM
result, err := EvaluatePolicyAgainstSBOMs(ctx, policy, doc, fieldExtractor)
if err != nil {
return fmt.Errorf("policy %s evaluation failed: %w", policy.Name, err)
}
results = append(results, result)
policyResults = append(policyResults, result)
}

// Reporting
switch strings.ToLower(policyConfig.OutputFmt) {
case "json":
if err := ReportJSON(ctx, results); err != nil {
if err := ReportJSON(ctx, policyResults); err != nil {
return fmt.Errorf("failed to write json output: %w", err)
}
case "table":
if err := ReportTable(ctx, results); err != nil {
if err := ReportTable(ctx, policyResults); err != nil {
return fmt.Errorf("failed to write yaml output: %w", err)
}
default:
if err := ReportBasic(ctx, results); err != nil {
if err := ReportBasic(ctx, policyResults); err != nil {
return fmt.Errorf("failed to write table output: %w", err)
}
}
Expand Down
59 changes: 30 additions & 29 deletions pkg/policy/evaluator.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,30 +17,29 @@ package policy
import (
"context"
"regexp"
"time"

"github.com/interlynk-io/sbomqs/pkg/logger"
"github.com/interlynk-io/sbomqs/pkg/sbom"
)

// EvaluatePolicyAgainstSBOMs evaluates a single policy against a SBOMs.
func EvaluatePolicyAgainstSBOMs(ctx context.Context, p Policy, doc sbom.Document, fieldExtractor *Extractor) (Result, error) {
func EvaluatePolicyAgainstSBOMs(ctx context.Context, policy Policy, doc sbom.Document, fieldExtractor *Extractor) (PolicyResult, error) {
log := logger.FromContext(ctx)
log.Debugf("processing policy evaluation: %s", p.Name, p.Type)
log.Debugf("processing policy evaluation: %s", policy.Name, policy.Type)

result := NewResult(p)
result.GeneratedAt = time.Now().UTC()
policyResult := NewPolicyResult(policy)

totalChecks := 0
components := doc.Components()
result.TotalChecked = len(components)
policyResult.TotalComponents = len(components)

// compile regex present in pattern rules
compiledRules, err := compilePatternRules(p)
compiledRules, err := compilePatternRules(policy)
if err != nil {
return Result{}, err
return PolicyResult{}, err
}

policyResults := make([]PolicyResult, 0, len(components)*len(compiledRules))
policyResults := make([]RuleResult, 0, len(components)*len(compiledRules))

// evaluate components against list of all rules in a single policy
for _, comp := range components {
Expand All @@ -54,6 +53,7 @@ func EvaluatePolicyAgainstSBOMs(ctx context.Context, p Policy, doc sbom.Document

// evaluate each component against list of all rules
for _, compileRule := range compiledRules {
totalChecks++
// evaluate rule

declaredRule := compileRule.Rule
Expand All @@ -66,43 +66,43 @@ func EvaluatePolicyAgainstSBOMs(ctx context.Context, p Policy, doc sbom.Document
actualValues := fieldExtractor.RetrieveValues(comp, declaredField)

// default outcome/pass reason
outcome := "pass"
result := "pass"
reason := "present"

// required rule: presence check
if RULE_TYPE(p.Type) == REQUIRED {
if RULE_TYPE(policy.Type) == REQUIRED {
ok := fieldExtractor.HasField(comp, declaredField)
if !ok {
outcome = "fail"
result = "fail"
reason = "missing field"
}

} else {
// for whitelist/blacklist do matching
matched := anyMatch(actualValues, declaredValues, patterns)

switch RULE_TYPE(p.Type) {
switch RULE_TYPE(policy.Type) {
case WHITELIST:
if !matched {
outcome = "fail"
result = "fail"
reason = "value not in whitelist"
}
case BLACKLIST:
if matched {
outcome = "fail"
result = "fail"
reason = "value in blacklist"
}
default:
// if unknown type, treat as pass (or change to fail depending on your policy)
}
}

pr := PolicyResult{
pr := RuleResult{
ComponentID: compID,
ComponentName: compName,
Field: declaredField,
Actual: actualValues,
Outcome: outcome,
DeclaredField: declaredField,
ActualValues: actualValues,
Result: result,
Reason: reason,
}

Expand All @@ -112,32 +112,33 @@ func EvaluatePolicyAgainstSBOMs(ctx context.Context, p Policy, doc sbom.Document
}

// assign results
result.PolicyResults = policyResults
policyResult.RuleResults = policyResults
policyResult.TotalChecks = totalChecks

// compute ViolationCnt (failed outcomes)
violationCount := 0
for _, pr := range policyResults {
if pr.Outcome == "fail" {
if pr.Result == "fail" {
violationCount++
}
}
result.ViolationCnt = violationCount
policyResult.ViolationCnt = violationCount

// Decide outcome
if result.ViolationCnt == 0 {
result.Result = "pass"
if policyResult.ViolationCnt == 0 {
policyResult.OverallResult = "pass"
} else {
switch p.Action {
switch policy.Action {
case "warn":
result.Result = "warn"
policyResult.OverallResult = "warn"
case "pass":
result.Result = "pass"
policyResult.OverallResult = "pass"
default:
result.Result = "fail"
policyResult.OverallResult = "fail"
}
}

return *result, nil
return *policyResult, nil
}

// anyMatch returns true if at least one of the actual values
Expand Down
3 changes: 0 additions & 3 deletions pkg/policy/policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,6 @@ type Params struct {

// Output
OutputFmt string

// Debug
debug bool
}

// PolicyFile represents the top-level YAML structure
Expand Down
Loading
Loading