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
1 change: 1 addition & 0 deletions pkg/policies/engine/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ type EvaluationResult struct {
Violations []*PolicyViolation
Skipped bool
SkipReason string
Ignore bool
}

// PolicyViolation represents a policy failure
Expand Down
7 changes: 7 additions & 0 deletions pkg/policies/engine/rego/rego.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ func parseViolationsRule(res rego.ResultSet, policy *engine.Policy) (*engine.Eva
Violations: violations,
Skipped: false, // best effort
SkipReason: "",
Ignore: false, // Assume old rules should not be ignored
}, nil
}

Expand All @@ -189,13 +190,19 @@ func parseResultRule(res rego.ResultSet, policy *engine.Policy) (*engine.Evaluat
reason = val
}

var ignore bool
if val, ok := ruleResult["ignore"].(bool); ok {
ignore = val
}

violations, ok := ruleResult["violations"].([]any)
if !ok {
return nil, engine.ResultFormatError{Field: "violations"}
}

result.Skipped = skipped
result.SkipReason = reason
result.Ignore = ignore

for _, violation := range violations {
vs, ok := violation.(string)
Expand Down
29 changes: 29 additions & 0 deletions pkg/policies/engine/rego/rego_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,13 +193,15 @@ func TestRego_ResultFormat(t *testing.T) {
require.NoError(t, err)
assert.True(t, result.Skipped)
assert.Equal(t, "invalid input", result.SkipReason)
assert.False(t, result.Ignore)
})

t.Run("valid input, no violations", func(t *testing.T) {
result, err := r.Verify(context.TODO(), policy, []byte("{\"specVersion\": \"1.5\"}"), nil)
require.NoError(t, err)
assert.False(t, result.Skipped)
assert.Len(t, result.Violations, 0)
assert.False(t, result.Ignore)
})

t.Run("valid input, violations", func(t *testing.T) {
Expand All @@ -208,6 +210,33 @@ func TestRego_ResultFormat(t *testing.T) {
assert.False(t, result.Skipped)
assert.Len(t, result.Violations, 1)
assert.Equal(t, "wrong CycloneDX version. Expected 1.5, but it was 1.4", result.Violations[0].Violation)
assert.False(t, result.Ignore)
})

t.Run("valid input, not ignore", func(t *testing.T) {
result, err := r.Verify(context.TODO(), policy, []byte("{\"specVersion\": \"1.0\"}"), nil)
require.NoError(t, err)
assert.False(t, result.Skipped)
assert.Len(t, result.Violations, 1)
assert.Equal(t, "wrong CycloneDX version. Expected 1.5, but it was 1.0", result.Violations[0].Violation)
assert.True(t, result.Ignore)
})
}

func TestRego_ResultFormatWithoutIgnoreValue(t *testing.T) {
regoContent, err := os.ReadFile("testfiles/result_format_without_ignore.rego")
require.NoError(t, err)

r := &Rego{}
policy := &engine.Policy{
Name: "result-output",
Source: regoContent,
}

t.Run("by default return always ignore to false", func(t *testing.T) {
result, err := r.Verify(context.TODO(), policy, []byte("{\"foo\": \"bar\"}"), nil)
require.NoError(t, err)
assert.False(t, result.Ignore)
})
}

Expand Down
7 changes: 7 additions & 0 deletions pkg/policies/engine/rego/testfiles/result_format.rego
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ result := {
"skipped": skipped,
"violations": violations,
"skip_reason": skip_reason,
"ignore": ignore,
}

default skip_reason := ""
Expand All @@ -17,8 +18,14 @@ skip_reason := m if {

default skipped := true

default ignore := false

skipped := false if valid_input

ignore := true if {
input.specVersion == "1.0"
}

violations contains msg if {
valid_input
input.specVersion != "1.5"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package main

import rego.v1

result := {
"skipped": skipped,
"violations": violations,
"skip_reason": skip_reason,
}

default skip_reason := ""

skip_reason := m if {
not valid_input
m := "invalid input"
}

default skipped := true

skipped := false if valid_input

violations contains msg if {
valid_input
input.specVersion != "1.5"
msg := sprintf("wrong CycloneDX version. Expected 1.5, but it was %s", [input.specVersion])
}

valid_input if {
input.specVersion
}
18 changes: 16 additions & 2 deletions pkg/policies/policies.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,9 @@ func (pv *PolicyVerifier) VerifyMaterial(ctx context.Context, material *v12.Atte
return nil, NewPolicyError(err)
}

result = append(result, ev)
if ev != nil {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would mean that there is no policy evaluation result for the attachment. This will happen when ALL execution paths of the multipolicy have been either not chosen (because the material type doesn't match), or ignored (because of this new feature), right?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exactly, the same that will happen with a multikind policy where all of the policies are ignored, no policy evaluation result.

We need to check the side implications.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it will be fine, since the consequence is that the policy evaluation is not added to the attestation, which is what we wanted. I'll double check other side effects.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks Jose

result = append(result, ev)
}
}

return result, nil
Expand Down Expand Up @@ -149,6 +151,11 @@ func (pv *PolicyVerifier) evaluatePolicyAttachment(ctx context.Context, attachme
return nil, NewPolicyError(err)
}

// Skip if the script explicitly instructs us to ignore it, effectively preventing it from being added to the evaluation results
if r.Ignore {
continue
}

// Gather merged results
evalResults = append(evalResults, r)

Expand All @@ -161,6 +168,11 @@ func (pv *PolicyVerifier) evaluatePolicyAttachment(ctx context.Context, attachme
sources = append(sources, base64.StdEncoding.EncodeToString(script.Source))
}

if len(sources) == 0 {
pv.logger.Debug().Msgf("policy %s explicitly ignored by definition", policy.Metadata.Name)
return nil, nil
}

var evaluationSources []string
if ref != nil && !IsProviderScheme(ref.URI) {
evaluationSources = sources
Expand Down Expand Up @@ -263,7 +275,9 @@ func (pv *PolicyVerifier) VerifyStatement(ctx context.Context, statement *intoto
return nil, NewPolicyError(err)
}

result = append(result, ev)
if ev != nil {
result = append(result, ev)
}
}

return result, nil
Expand Down
109 changes: 109 additions & 0 deletions pkg/policies/policies_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -956,27 +956,37 @@ func (s *testSuite) TestNewResultFormat() {
expectErr bool
expectViolations int
expectSkipped bool
expectIgnore bool
expectReasons []string
}{
{
name: "result.violations",
policy: "file://testdata/policy_result_format.yaml",
material: "{\"specVersion\": \"1.4\"}",
expectViolations: 1,
expectIgnore: false,
},
{
name: "skip",
policy: "file://testdata/policy_result_format.yaml",
material: "{\"invalid\": \"1.4\"}",
expectSkipped: true,
expectReasons: []string{"invalid input"},
expectIgnore: false,
},
{
name: "skip multiple",
policy: "file://testdata/policy_result_skipped.yaml",
material: "{}",
expectSkipped: true,
expectReasons: []string{"this one is skipped", "this is also skipped"},
expectIgnore: false,
},
{
name: "ignore",
policy: "file://testdata/policy_with_ignore.yaml",
material: "{\"specVersion\": \"1.0\"}",
expectIgnore: true,
},
}

Expand Down Expand Up @@ -1014,6 +1024,12 @@ func (s *testSuite) TestNewResultFormat() {
return
}

if tc.expectIgnore {
s.Nil(err)
s.Equal([]*v1.PolicyEvaluation{}, res)
return
}

s.Require().NoError(err)
s.Len(res, 1)
s.Len(res[0].Violations, tc.expectViolations)
Expand Down Expand Up @@ -1089,6 +1105,99 @@ func (s *testSuite) TestContainerMaterial() {
}
}

func (s *testSuite) TestMultiKindAWithIgnore() {
cases := []struct {
name string
policy string
material string
expectErr bool
expectedEvaluations int
expectSkipped bool
expectReasons []string
expecteIgnore bool
}{
{
name: "scripts should not be ignored and skipped",
policy: "file://testdata/policy_multi_kind_with_ignore.yaml",
material: "{\"specVersion\": \"1.4\"}",
expectedEvaluations: 1,
expectSkipped: true,
expectReasons: []string{"this on is skipped"},
},
{
name: "scripts should be ignored",
policy: "file://testdata/policy_multi_kind_with_ignore.yaml",
material: "{\"specVersion\": \"1.0\"}",
expectedEvaluations: 0,
expecteIgnore: true,
},
{
name: "all scripts should not be ignored",
policy: "file://testdata/policy_multi_kind_with_ignore.yaml",
material: "{\"specVersion\": \"1.4\"}",
expectedEvaluations: 1,
expectSkipped: false,
},
}

for _, tc := range cases {
s.Run(tc.name, func() {
schema := &v12.CraftingSchema{
Materials: []*v12.CraftingSchema_Material{
{
Name: "sbom",
Type: v12.CraftingSchema_Material_SBOM_CYCLONEDX_JSON,
},
},
Policies: &v12.Policies{
Materials: []*v12.PolicyAttachment{
{
Policy: &v12.PolicyAttachment_Ref{Ref: tc.policy},
},
},
},
}

material := &v1.Attestation_Material{
M: &v1.Attestation_Material_Artifact_{Artifact: &v1.Attestation_Material_Artifact{
Content: []byte(tc.material),
}},
MaterialType: v12.CraftingSchema_Material_SBOM_CYCLONEDX_JSON,
InlineCas: true,
}

if tc.expecteIgnore {
material.MaterialType = v12.CraftingSchema_Material_SARIF
}

if tc.name == "all scripts should not be ignored" {
material.MaterialType = v12.CraftingSchema_Material_OPENVEX
}

verifier := NewPolicyVerifier(schema, nil, &s.logger)
res, err := verifier.VerifyMaterial(context.TODO(), material, "")

if tc.expectErr {
s.Error(err)
return
}

if tc.expecteIgnore {
s.Nil(err)
s.Len(res, tc.expectedEvaluations)
return
}

s.Require().NoError(err)
s.Len(res, tc.expectedEvaluations)
s.Equal(tc.expectSkipped, res[0].Skipped)
if len(res[0].SkipReasons) > 0 {
s.Equal(tc.expectReasons, res[0].SkipReasons)
}
})
}
}

type testSuite struct {
suite.Suite

Expand Down
10 changes: 10 additions & 0 deletions pkg/policies/policy_groups.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,11 @@ func (pgv *PolicyGroupVerifier) VerifyMaterial(ctx context.Context, material *ap
return nil, NewPolicyError(err)
}

if ev == nil {
// no evaluation, skip
continue
}

// Assign group reference to this evaluation
ev.GroupReference = &api.PolicyEvaluation_Reference{
Name: group.GetMetadata().GetName(),
Expand Down Expand Up @@ -133,6 +138,11 @@ func (pgv *PolicyGroupVerifier) VerifyStatement(ctx context.Context, statement *
return nil, NewPolicyError(err)
}

if ev == nil {
// no evaluation, skip
continue
}

// Assign group reference to this evaluation
ev.GroupReference = &api.PolicyEvaluation_Reference{
Name: group.GetMetadata().GetName(),
Expand Down
Loading
Loading