Skip to content

Commit 92f6519

Browse files
authored
feat(policies): support new policy output format (chainloop-dev#1400)
Signed-off-by: Jose I. Paris <[email protected]>
1 parent 2129a62 commit 92f6519

File tree

16 files changed

+574
-227
lines changed

16 files changed

+574
-227
lines changed

app/controlplane/api/gen/frontend/attestation/v1/crafting_state.ts

Lines changed: 36 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/controlplane/api/gen/jsonschema/attestation.v1.PolicyEvaluation.jsonschema.json

Lines changed: 18 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/controlplane/api/gen/jsonschema/attestation.v1.PolicyEvaluation.schema.json

Lines changed: 18 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/attestation/crafter/api/attestation/v1/crafting_state.pb.go

Lines changed: 112 additions & 90 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/attestation/crafter/api/attestation/v1/crafting_state.proto

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,12 @@ message PolicyEvaluation {
140140
// material type, if any, of the evaluated policy
141141
workflowcontract.v1.CraftingSchema.Material.MaterialType type = 8;
142142

143+
// whether this evaluation was skipped or not (because of an invalid input, for example)
144+
bool skipped = 13;
145+
146+
// Evaluation messages, intended to communicate evaluation errors (invalid input)
147+
repeated string skip_reasons = 14;
148+
143149
message Violation {
144150
string subject = 1 [(buf.validate.field).required = true];
145151
string message = 2 [(buf.validate.field).required = true];

pkg/attestation/crafter/crafter.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -569,7 +569,7 @@ func (c *Crafter) addMaterial(ctx context.Context, m *schemaapi.CraftingSchema_M
569569
c.CraftingState.Attestation.PolicyEvaluations = append(c.CraftingState.Attestation.PolicyEvaluations, policyResults...)
570570

571571
// log policy violations
572-
policies.LogPolicyViolations(c.CraftingState.Attestation.PolicyEvaluations, c.Logger)
572+
policies.LogPolicyEvaluations(c.CraftingState.Attestation.PolicyEvaluations, c.Logger)
573573

574574
// 5 - Attach it to state
575575
if c.CraftingState.Attestation.Materials == nil {

pkg/attestation/renderer/chainloop/v02.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ type PolicyEvaluation struct {
5757
Violations []*PolicyViolation `json:"violations,omitempty"`
5858
With map[string]string `json:"with,omitempty"`
5959
Type string `json:"type"`
60+
Skipped bool `json:"skipped"`
61+
SkipReasons []string `json:"skip_reasons,omitempty"`
6062
}
6163

6264
type PolicyViolation struct {
@@ -118,7 +120,7 @@ func (r *RendererV02) Statement(ctx context.Context) (*intoto.Statement, error)
118120
}
119121
evaluations = append(evaluations, policyResults...)
120122
// log policy violations
121-
policies.LogPolicyViolations(evaluations, r.logger)
123+
policies.LogPolicyEvaluations(evaluations, r.logger)
122124

123125
// insert attestation level policy results into statement
124126
if err = addPolicyResults(statement, evaluations); err != nil {
@@ -313,6 +315,8 @@ func renderEvaluation(ev *v1.PolicyEvaluation) *PolicyEvaluation {
313315
"sha256": ev.ReferenceDigest,
314316
},
315317
},
318+
SkipReasons: ev.SkipReasons,
319+
Skipped: ev.Skipped,
316320
}
317321
}
318322

pkg/policies/engine/engine.go

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,22 +17,37 @@ package engine
1717

1818
import (
1919
"context"
20+
"fmt"
2021
)
2122

2223
type PolicyEngine interface {
2324
// Verify verifies an input against a policy
24-
Verify(ctx context.Context, policy *Policy, input []byte, args map[string]any) ([]*PolicyViolation, error)
25+
Verify(ctx context.Context, policy *Policy, input []byte, args map[string]any) (*EvaluationResult, error)
26+
}
27+
28+
type EvaluationResult struct {
29+
Violations []*PolicyViolation
30+
Skipped bool
31+
SkipReason string
2532
}
2633

2734
// PolicyViolation represents a policy failure
2835
type PolicyViolation struct {
2936
Subject, Violation string
3037
}
3138

32-
// Policy represents a loaded policy in any of the supported technologies.
39+
// Policy represents a loaded policy in any of the supported engines.
3340
type Policy struct {
3441
// the source code for this policy
3542
Source []byte `json:"module"`
3643
// The unique policy name
3744
Name string `json:"name"`
3845
}
46+
47+
type ResultFormatError struct {
48+
Field string
49+
}
50+
51+
func (e ResultFormatError) Error() string {
52+
return fmt.Sprintf("Policy result format error: %s not found or wrong format", e.Field)
53+
}

pkg/policies/engine/rego/rego.go

Lines changed: 62 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,8 @@ const (
4343
// EnvironmentModePermissive allows all operations on the compiler
4444
EnvironmentModePermissive EnvironmentMode = 1
4545
inputArgs = "args"
46-
mainRule = "violations"
47-
deprecatedMainRule = "deny"
46+
deprecatedRule = "violations"
47+
mainRule = "result"
4848
)
4949

5050
// builtinFuncNotAllowed is a list of builtin functions that are not allowed in the compiler
@@ -64,7 +64,7 @@ var allowedNetworkDomains = []string{
6464
// Force interface
6565
var _ engine.PolicyEngine = (*Rego)(nil)
6666

67-
func (r *Rego) Verify(ctx context.Context, policy *engine.Policy, input []byte, args map[string]any) ([]*engine.PolicyViolation, error) {
67+
func (r *Rego) Verify(ctx context.Context, policy *engine.Policy, input []byte, args map[string]any) (*engine.EvaluationResult, error) {
6868
policyString := string(policy.Source)
6969
parsedModule, err := ast.ParseModule(policy.Name, policyString)
7070
if err != nil {
@@ -112,29 +112,38 @@ func (r *Rego) Verify(ctx context.Context, policy *engine.Policy, input []byte,
112112
}
113113

114114
// If res is nil, it means that the rule hasn't been found
115+
// TODO: Remove when this deprecated rule is not used anymore
115116
if res == nil {
116117
// Try with the deprecated main rule
117-
if err := executeQuery(deprecatedMainRule, r.OperatingMode == EnvironmentModeRestrictive); err != nil {
118+
if err := executeQuery(deprecatedRule, r.OperatingMode == EnvironmentModeRestrictive); err != nil {
118119
return nil, err
119120
}
120121

121122
if res == nil {
122-
return nil, fmt.Errorf("failed to evaluate policy: no '%s' nor '%s' rule found", mainRule, deprecatedMainRule)
123+
return nil, fmt.Errorf("failed to evaluate policy: neither '%s' nor '%s' rule found", mainRule, deprecatedRule)
123124
}
125+
126+
return parseViolationsRule(res, policy)
124127
}
125128

129+
return parseResultRule(res, policy)
130+
}
131+
132+
// Parse deprecated list of violations.
133+
// TODO: Remove this path once `result` rule is consolidated
134+
func parseViolationsRule(res rego.ResultSet, policy *engine.Policy) (*engine.EvaluationResult, error) {
126135
violations := make([]*engine.PolicyViolation, 0)
127136
for _, exp := range res {
128137
for _, val := range exp.Expressions {
129138
ruleResults, ok := val.Value.([]interface{})
130139
if !ok {
131-
return nil, fmt.Errorf("failed to evaluate policy expression evaluation result: %s", val.Text)
140+
return nil, engine.ResultFormatError{Field: deprecatedRule}
132141
}
133142

134143
for _, result := range ruleResults {
135144
reasonStr, ok := result.(string)
136145
if !ok {
137-
return nil, fmt.Errorf("failed to evaluate rule result: %s", val.Text)
146+
return nil, engine.ResultFormatError{Field: deprecatedRule}
138147
}
139148

140149
violations = append(violations, &engine.PolicyViolation{
@@ -145,7 +154,52 @@ func (r *Rego) Verify(ctx context.Context, policy *engine.Policy, input []byte,
145154
}
146155
}
147156

148-
return violations, nil
157+
return &engine.EvaluationResult{
158+
Violations: violations,
159+
Skipped: false, // best effort
160+
SkipReason: "",
161+
}, nil
162+
}
163+
164+
// parse `result` rule
165+
func parseResultRule(res rego.ResultSet, policy *engine.Policy) (*engine.EvaluationResult, error) {
166+
result := &engine.EvaluationResult{Violations: make([]*engine.PolicyViolation, 0)}
167+
for _, exp := range res {
168+
for _, val := range exp.Expressions {
169+
ruleResult, ok := val.Value.(map[string]any)
170+
if !ok {
171+
return nil, engine.ResultFormatError{Field: mainRule}
172+
}
173+
174+
skipped, ok := ruleResult["skipped"].(bool)
175+
if !ok {
176+
return nil, engine.ResultFormatError{Field: "skipped"}
177+
}
178+
179+
reason, ok := ruleResult["skip_reason"].(string)
180+
if !ok {
181+
return nil, engine.ResultFormatError{Field: "skip_reason"}
182+
}
183+
184+
violations, ok := ruleResult["violations"].([]any)
185+
if !ok {
186+
return nil, engine.ResultFormatError{Field: "violations"}
187+
}
188+
189+
result.Skipped = skipped
190+
result.SkipReason = reason
191+
192+
for _, violation := range violations {
193+
vs, ok := violation.(string)
194+
if !ok {
195+
return nil, fmt.Errorf("failed to evaluate violation in policy evaluation result: %s", val.Text)
196+
}
197+
result.Violations = append(result.Violations, &engine.PolicyViolation{Subject: policy.Name, Violation: vs})
198+
}
199+
}
200+
}
201+
202+
return result, nil
149203
}
150204

151205
func queryRego(ctx context.Context, ruleName string, parsedModule *ast.Module, options ...func(r *rego.Rego)) (rego.ResultSet, error) {

0 commit comments

Comments
 (0)