Skip to content

Commit 70b0f08

Browse files
authored
refactor: run attestation-level policy evaluations in crafting layer (#1689)
Signed-off-by: Miguel Martinez <[email protected]>
1 parent c646bf2 commit 70b0f08

File tree

5 files changed

+60
-129
lines changed

5 files changed

+60
-129
lines changed

app/cli/internal/action/attestation_push.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,19 @@ func (action *AttestationPush) Run(ctx context.Context, attestationID string, ru
160160
return nil, err
161161
}
162162

163+
// execute policy evaluations
164+
// We do not want to evaluate policies here during render since we want to do it in a separate step
165+
statement, err := renderer.RenderStatement(ctx)
166+
if err != nil {
167+
return nil, fmt.Errorf("rendering statement: %w", err)
168+
}
169+
170+
// Add attestation-level policy evaluations
171+
if err := crafter.EvaluateAttestationPolicies(ctx, attestationID, statement); err != nil {
172+
return nil, fmt.Errorf("evaluating attestation policies: %w", err)
173+
}
174+
175+
// render final attestation with all the evaluated policies inside
163176
envelope, err := renderer.Render(ctx)
164177
if err != nil {
165178
return nil, err

app/cli/internal/action/attestation_status.go

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -141,12 +141,12 @@ func (action *AttestationStatus) Run(ctx context.Context, attestationID string,
141141
}
142142

143143
// We do not want to evaluate policies here during render since we want to do it in a separate step
144-
statement, err := renderer.RenderStatement(ctx, chainloop.WithSkipPolicyEvaluation(true))
144+
statement, err := renderer.RenderStatement(ctx)
145145
if err != nil {
146146
return nil, fmt.Errorf("rendering statement: %w", err)
147147
}
148148

149-
res.PolicyEvaluations, err = action.getPolicyEvaluations(ctx, c, statement)
149+
res.PolicyEvaluations, err = action.getPolicyEvaluations(ctx, c, attestationID, statement)
150150
if err != nil {
151151
return nil, fmt.Errorf("getting policy evaluations: %w", err)
152152
}
@@ -201,30 +201,26 @@ func (action *AttestationStatus) Run(ctx context.Context, attestationID string,
201201
}
202202

203203
// getPolicyEvaluations retrieves both material-level and attestation-level policy evaluations
204-
func (action *AttestationStatus) getPolicyEvaluations(ctx context.Context, c *crafter.Crafter, statement *intoto.Statement) (map[string][]*PolicyEvaluation, error) {
204+
func (action *AttestationStatus) getPolicyEvaluations(ctx context.Context, c *crafter.Crafter, attestationID string, statement *intoto.Statement) (map[string][]*PolicyEvaluation, error) {
205205
// grouped by material name
206206
evaluations := make(map[string][]*PolicyEvaluation)
207207

208-
// Add material-level policy evaluations
209-
for _, v := range c.CraftingState.Attestation.GetPolicyEvaluations() {
210-
if existing, ok := evaluations[v.MaterialName]; ok {
211-
evaluations[v.MaterialName] = append(existing, policyEvaluationStateToActionForStatus(v))
212-
} else {
213-
evaluations[v.MaterialName] = []*PolicyEvaluation{policyEvaluationStateToActionForStatus(v)}
214-
}
215-
}
216-
217208
// Add attestation-level policy evaluations
218-
attestationEvaluations, err := c.EvaluateAttestationPolicies(ctx, statement)
219-
if err != nil {
209+
if err := c.EvaluateAttestationPolicies(ctx, attestationID, statement); err != nil {
220210
return nil, fmt.Errorf("evaluating attestation policies: %w", err)
221211
}
222212

223-
for _, v := range attestationEvaluations {
224-
if existing, ok := evaluations[chainloop.AttPolicyEvaluation]; ok {
225-
evaluations[chainloop.AttPolicyEvaluation] = append(existing, policyEvaluationStateToActionForStatus(v))
213+
// map evaluations
214+
for _, v := range c.CraftingState.Attestation.GetPolicyEvaluations() {
215+
keyName := v.MaterialName
216+
if keyName == "" {
217+
keyName = chainloop.AttPolicyEvaluation
218+
}
219+
220+
if existing, ok := evaluations[keyName]; ok {
221+
evaluations[keyName] = append(existing, policyEvaluationStateToActionForStatus(v))
226222
} else {
227-
evaluations[chainloop.AttPolicyEvaluation] = []*PolicyEvaluation{policyEvaluationStateToActionForStatus(v)}
223+
evaluations[keyName] = []*PolicyEvaluation{policyEvaluationStateToActionForStatus(v)}
228224
}
229225
}
230226

pkg/attestation/crafter/crafter.go

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -593,23 +593,38 @@ func (c *Crafter) addMaterial(ctx context.Context, m *schemaapi.CraftingSchema_M
593593
return nil
594594
}
595595

596-
func (c *Crafter) EvaluateAttestationPolicies(ctx context.Context, statement *intoto.Statement) ([]*api.PolicyEvaluation, error) {
596+
// EvaluateAttestationPolicies evaluates the attestation-level policies and stores them in the attestation state
597+
func (c *Crafter) EvaluateAttestationPolicies(ctx context.Context, attestationID string, statement *intoto.Statement) error {
597598
// evaluate attestation-level policies
598599
pv := policies.NewPolicyVerifier(c.CraftingState.InputSchema, c.attClient, c.Logger)
599-
policyResults, err := pv.VerifyStatement(ctx, statement)
600+
policyEvaluations, err := pv.VerifyStatement(ctx, statement)
600601
if err != nil {
601-
return nil, fmt.Errorf("evaluating policies in statement: %w", err)
602+
return fmt.Errorf("evaluating policies in statement: %w", err)
602603
}
603604

604605
pgv := policies.NewPolicyGroupVerifier(c.CraftingState.InputSchema, c.attClient, c.Logger)
605606
policyGroupResults, err := pgv.VerifyStatement(ctx, statement)
606607
if err != nil {
607-
return nil, fmt.Errorf("evaluating policy groups in statement: %w", err)
608+
return fmt.Errorf("evaluating policy groups in statement: %w", err)
608609
}
609610

610-
policyResults = append(policyResults, policyGroupResults...)
611+
policyEvaluations = append(policyEvaluations, policyGroupResults...)
611612

612-
return policyResults, nil
613+
// Since we are going to override the state, we want to keep the existing material-type policy evaluations
614+
for _, ev := range c.CraftingState.Attestation.PolicyEvaluations {
615+
// We can not use kind = ATTESTATION since that's a valid material kind
616+
if ev.MaterialName != "" {
617+
policyEvaluations = append(policyEvaluations, ev)
618+
}
619+
}
620+
621+
c.CraftingState.Attestation.PolicyEvaluations = policyEvaluations
622+
623+
if err := c.stateManager.Write(ctx, attestationID, c.CraftingState); err != nil {
624+
return fmt.Errorf("failed to persist crafting state: %w", err)
625+
}
626+
627+
return nil
613628
}
614629

615630
func (c *Crafter) ValidateAttestation() error {

pkg/attestation/renderer/chainloop/v02.go

Lines changed: 9 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ import (
2727
pb "github.com/chainloop-dev/chainloop/app/controlplane/api/controlplane/v1"
2828
schemaapi "github.com/chainloop-dev/chainloop/app/controlplane/api/workflowcontract/v1"
2929
v1 "github.com/chainloop-dev/chainloop/pkg/attestation/crafter/api/attestation/v1"
30-
"github.com/chainloop-dev/chainloop/pkg/policies"
3130
crv1 "github.com/google/go-containerregistry/pkg/v1"
3231
intoto "github.com/in-toto/attestation/go/v1"
3332
"github.com/rs/zerolog"
@@ -86,25 +85,7 @@ func NewChainloopRendererV02(att *v1.Attestation, schema *schemaapi.CraftingSche
8685
}
8786
}
8887

89-
type RenderOptions struct {
90-
evaluatePolicies bool
91-
}
92-
93-
type RenderOpt func(*RenderOptions)
94-
95-
func WithSkipPolicyEvaluation(skip bool) RenderOpt {
96-
return func(o *RenderOptions) {
97-
o.evaluatePolicies = !skip
98-
}
99-
}
100-
101-
func (r *RendererV02) Statement(ctx context.Context, opts ...RenderOpt) (*intoto.Statement, error) {
102-
var evaluations []*v1.PolicyEvaluation
103-
options := &RenderOptions{evaluatePolicies: true}
104-
for _, opt := range opts {
105-
opt(options)
106-
}
107-
88+
func (r *RendererV02) Statement(_ context.Context) (*intoto.Statement, error) {
10889
subject, err := r.subject()
10990
if err != nil {
11091
return nil, fmt.Errorf("error creating subject: %w", err)
@@ -122,88 +103,9 @@ func (r *RendererV02) Statement(ctx context.Context, opts ...RenderOpt) (*intoto
122103
Predicate: predicate,
123104
}
124105

125-
if options.evaluatePolicies {
126-
// Validate policy groups
127-
pgv := policies.NewPolicyGroupVerifier(r.schema, r.attClient, r.logger)
128-
policyGroupResults, err := pgv.VerifyStatement(ctx, statement)
129-
if err != nil {
130-
return nil, fmt.Errorf("error applying policy groups to statement: %w", err)
131-
}
132-
evaluations = append(evaluations, policyGroupResults...)
133-
134-
// validate attestation-level policies
135-
pv := policies.NewPolicyVerifier(r.schema, r.attClient, r.logger)
136-
policyResults, err := pv.VerifyStatement(ctx, statement)
137-
if err != nil {
138-
return nil, fmt.Errorf("applying policies to statement: %w", err)
139-
}
140-
evaluations = append(evaluations, policyResults...)
141-
// log policy violations
142-
policies.LogPolicyEvaluations(evaluations, r.logger)
143-
144-
// insert attestation level policy results into statement
145-
if err = addPolicyResults(statement, evaluations); err != nil {
146-
return nil, fmt.Errorf("adding policy results to statement: %w", err)
147-
}
148-
}
149-
150106
return statement, nil
151107
}
152108

153-
// addPolicyResults adds policy evaluation results to the statement. It does it by deserializing the predicate from a structpb.Struct,
154-
// filling PolicyEvaluations, and serializing it again to a structpb.Struct object, using JSON as an intermediate representation.
155-
// Note that this is needed because intoto predicates are generic structpb.Struct
156-
func addPolicyResults(statement *intoto.Statement, policyResults []*v1.PolicyEvaluation) error {
157-
if len(policyResults) == 0 {
158-
return nil
159-
}
160-
161-
predicate := statement.Predicate
162-
// marshall to json
163-
jsonPredicate, err := protojson.Marshal(predicate)
164-
if err != nil {
165-
return fmt.Errorf("marshalling predicate: %w", err)
166-
}
167-
168-
// unmarshall to our typed predicate object
169-
var p ProvenancePredicateV02
170-
err = json.Unmarshal(jsonPredicate, &p)
171-
if err != nil {
172-
return fmt.Errorf("unmarshalling predicate: %w", err)
173-
}
174-
175-
// insert policy evaluations for attestation
176-
if p.PolicyEvaluations == nil {
177-
p.PolicyEvaluations = make(map[string][]*PolicyEvaluation)
178-
}
179-
attEvaluations := make([]*PolicyEvaluation, 0, len(policyResults))
180-
for _, ev := range policyResults {
181-
renderedEv, err := renderEvaluation(ev)
182-
if err != nil {
183-
return fmt.Errorf("rendering evaluation: %w", err)
184-
}
185-
attEvaluations = append(attEvaluations, renderedEv)
186-
}
187-
p.PolicyEvaluations[AttPolicyEvaluation] = attEvaluations
188-
189-
// marshall back to JSON
190-
jsonPredicate, err = json.Marshal(p)
191-
if err != nil {
192-
return fmt.Errorf("marshalling predicate: %w", err)
193-
}
194-
195-
// finally unmarshal from JSON to structpb.Struct.
196-
var finalPredicate structpb.Struct
197-
err = protojson.Unmarshal(jsonPredicate, &finalPredicate)
198-
if err != nil {
199-
return fmt.Errorf("unmarshalling predicate: %w", err)
200-
}
201-
202-
statement.Predicate = &finalPredicate
203-
204-
return nil
205-
}
206-
207109
func commitAnnotations(c *v1.Commit) (*structpb.Struct, error) {
208110
annotationsRaw := map[string]interface{}{
209111
subjectGitAnnotationWhen: c.GetDate().AsTime().Format(time.RFC3339),
@@ -285,7 +187,7 @@ func (r *RendererV02) predicate() (*structpb.Struct, error) {
285187
return nil, fmt.Errorf("error normalizing materials: %w", err)
286188
}
287189

288-
policies, err := policyEvaluationsFromMaterials(r.att)
190+
policies, err := mappedPolicyEvaluations(r.att)
289191
if err != nil {
290192
return nil, fmt.Errorf("error rendering policy evaluations: %w", err)
291193
}
@@ -313,14 +215,19 @@ func (r *RendererV02) predicate() (*structpb.Struct, error) {
313215
}
314216

315217
// collect all policy evaluations grouped by material
316-
func policyEvaluationsFromMaterials(att *v1.Attestation) (map[string][]*PolicyEvaluation, error) {
218+
func mappedPolicyEvaluations(att *v1.Attestation) (map[string][]*PolicyEvaluation, error) {
317219
result := map[string][]*PolicyEvaluation{}
318220
for _, p := range att.GetPolicyEvaluations() {
221+
keyName := p.MaterialName
222+
if keyName == "" {
223+
keyName = AttPolicyEvaluation
224+
}
225+
319226
ev, err := renderEvaluation(p)
320227
if err != nil {
321228
return nil, err
322229
}
323-
result[p.MaterialName] = append(result[p.MaterialName], ev)
230+
result[keyName] = append(result[keyName], ev)
324231
}
325232

326233
return result, nil

pkg/attestation/renderer/renderer.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ type AttestationRenderer struct {
5454
}
5555

5656
type r interface {
57-
Statement(ctx context.Context, opts ...chainloop.RenderOpt) (*intoto.Statement, error)
57+
Statement(ctx context.Context) (*intoto.Statement, error)
5858
}
5959

6060
type Opt func(*AttestationRenderer)
@@ -95,8 +95,8 @@ func NewAttestationRenderer(state *crafter.VersionedCraftingState, attClient pb.
9595
}
9696

9797
// Render the in-toto statement skipping validations, dsse envelope wrapping nor signing
98-
func (ab *AttestationRenderer) RenderStatement(ctx context.Context, opts ...chainloop.RenderOpt) (*intoto.Statement, error) {
99-
statement, err := ab.renderer.Statement(ctx, opts...)
98+
func (ab *AttestationRenderer) RenderStatement(ctx context.Context) (*intoto.Statement, error) {
99+
statement, err := ab.renderer.Statement(ctx)
100100
if err != nil {
101101
return nil, fmt.Errorf("generating in-toto statement: %w", err)
102102
}

0 commit comments

Comments
 (0)