Skip to content

Commit c1c719b

Browse files
authored
feat(policies): add attestation phase lifecycle control (#2765)
Signed-off-by: Miguel Martinez <[email protected]>
1 parent 4a18e52 commit c1c719b

File tree

15 files changed

+628
-134
lines changed

15 files changed

+628
-134
lines changed

app/cli/cmd/attestation_init.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//
2-
// Copyright 2024-2025 The Chainloop Authors.
2+
// Copyright 2024-2026 The Chainloop Authors.
33
//
44
// Licensed under the Apache License, Version 2.0 (the "License");
55
// you may not use this file except in compliance with the License.

app/cli/pkg/action/attestation_init.go

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//
2-
// Copyright 2024-2025 The Chainloop Authors.
2+
// Copyright 2024-2026 The Chainloop Authors.
33
//
44
// Licensed under the Apache License, Version 2.0 (the "License");
55
// you may not use this file except in compliance with the License.
@@ -27,6 +27,7 @@ import (
2727
"github.com/chainloop-dev/chainloop/app/controlplane/pkg/unmarshal"
2828
"github.com/chainloop-dev/chainloop/pkg/attestation/crafter"
2929
clientAPI "github.com/chainloop-dev/chainloop/pkg/attestation/crafter/api/attestation/v1"
30+
"github.com/chainloop-dev/chainloop/pkg/attestation/renderer"
3031
"github.com/chainloop-dev/chainloop/pkg/casclient"
3132
"github.com/chainloop-dev/chainloop/pkg/policies"
3233
"github.com/rs/zerolog"
@@ -305,6 +306,22 @@ func (action *AttestationInit) Run(ctx context.Context, opts *AttestationInitRun
305306
// Don't fail the init - this is best-effort
306307
}
307308

309+
// Evaluate attestation-level policies at init phase
310+
attClient := pb.NewAttestationServiceClient(action.CPConnection)
311+
r, err := renderer.NewAttestationRenderer(action.c.CraftingState, attClient, "", "", nil, renderer.WithLogger(action.Logger))
312+
if err != nil {
313+
return "", fmt.Errorf("creating attestation renderer: %w", err)
314+
}
315+
316+
statement, err := r.RenderStatement(ctx)
317+
if err != nil {
318+
return "", fmt.Errorf("rendering statement: %w", err)
319+
}
320+
321+
if err := action.c.EvaluateAttestationPolicies(ctx, attestationID, statement, policies.EvalPhaseInit); err != nil {
322+
return "", fmt.Errorf("evaluating attestation policies: %w", err)
323+
}
324+
308325
return attestationID, nil
309326
}
310327

app/cli/pkg/action/attestation_push.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//
2-
// Copyright 2024-2025 The Chainloop Authors.
2+
// Copyright 2024-2026 The Chainloop Authors.
33
//
44
// Licensed under the Apache License, Version 2.0 (the "License");
55
// you may not use this file except in compliance with the License.
@@ -27,6 +27,7 @@ import (
2727
"github.com/chainloop-dev/chainloop/pkg/attestation/crafter"
2828
"github.com/chainloop-dev/chainloop/pkg/attestation/renderer"
2929
"github.com/chainloop-dev/chainloop/pkg/attestation/signer"
30+
"github.com/chainloop-dev/chainloop/pkg/policies"
3031
"github.com/secure-systems-lab/go-securesystemslib/dsse"
3132
protobundle "github.com/sigstore/protobuf-specs/gen/pb-go/bundle/v1"
3233
"google.golang.org/grpc"
@@ -100,6 +101,7 @@ func (action *AttestationPush) Run(ctx context.Context, attestationID string, ru
100101
if err != nil {
101102
return nil, fmt.Errorf("creating status action: %w", err)
102103
}
104+
103105
attestationStatus, err := statusAction.Run(ctx, attestationID)
104106
if err != nil {
105107
return nil, fmt.Errorf("creating running status action: %w", err)
@@ -196,10 +198,13 @@ func (action *AttestationPush) Run(ctx context.Context, attestationID string, ru
196198
}
197199

198200
// Add attestation-level policy evaluations
199-
if err := crafter.EvaluateAttestationPolicies(ctx, attestationID, statement); err != nil {
201+
if err := crafter.EvaluateAttestationPolicies(ctx, attestationID, statement, policies.EvalPhasePush); err != nil {
200202
return nil, fmt.Errorf("evaluating attestation policies: %w", err)
201203
}
202204

205+
// Update the status result with the definitive push-phase evaluation against the final statement
206+
attestationStatus.PolicyEvaluations, attestationStatus.HasPolicyViolations = getPolicyEvaluations(crafter)
207+
203208
// render final attestation with all the evaluated policies inside
204209
envelope, bundle, err := renderer.Render(ctx)
205210
if err != nil {

app/cli/pkg/action/attestation_status.go

Lines changed: 5 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//
2-
// Copyright 2024-2025 The Chainloop Authors.
2+
// Copyright 2024-2026 The Chainloop Authors.
33
//
44
// Licensed under the Apache License, Version 2.0 (the "License");
55
// you may not use this file except in compliance with the License.
@@ -20,11 +20,9 @@ import (
2020
"fmt"
2121
"time"
2222

23-
pb "github.com/chainloop-dev/chainloop/app/controlplane/api/controlplane/v1"
2423
pbc "github.com/chainloop-dev/chainloop/app/controlplane/api/workflowcontract/v1"
2524
"github.com/chainloop-dev/chainloop/pkg/attestation/crafter"
2625
v1 "github.com/chainloop-dev/chainloop/pkg/attestation/crafter/api/attestation/v1"
27-
"github.com/chainloop-dev/chainloop/pkg/attestation/renderer"
2826
"github.com/chainloop-dev/chainloop/pkg/attestation/renderer/chainloop"
2927
)
3028

@@ -39,8 +37,7 @@ type AttestationStatus struct {
3937
*ActionsOpts
4038
c *crafter.Crafter
4139
// Do not show information about the project version release status
42-
isPushed bool
43-
skipPolicyEvaluation bool
40+
isPushed bool
4441
}
4542

4643
type AttestationStatusResult struct {
@@ -93,19 +90,7 @@ func NewAttestationStatus(cfg *AttestationStatusOpts) (*AttestationStatus, error
9390
}, nil
9491
}
9592

96-
func WithSkipPolicyEvaluation() func(*AttestationStatus) {
97-
return func(opts *AttestationStatus) {
98-
opts.skipPolicyEvaluation = true
99-
}
100-
}
101-
102-
type AttestationStatusOpt func(*AttestationStatus)
103-
104-
func (action *AttestationStatus) Run(ctx context.Context, attestationID string, opts ...AttestationStatusOpt) (*AttestationStatusResult, error) {
105-
for _, opt := range opts {
106-
opt(action)
107-
}
108-
93+
func (action *AttestationStatus) Run(ctx context.Context, attestationID string) (*AttestationStatusResult, error) {
10994
c := action.c
11095

11196
if initialized, err := c.AlreadyInitialized(ctx, attestationID); err != nil {
@@ -141,27 +126,8 @@ func (action *AttestationStatus) Run(ctx context.Context, attestationID string,
141126
TimestampAuthority: att.GetSigningOptions().GetTimestampAuthorityUrl(),
142127
}
143128

144-
if !action.skipPolicyEvaluation {
145-
// We need to render the statement to get the policy evaluations
146-
attClient := pb.NewAttestationServiceClient(action.CPConnection)
147-
renderer, err := renderer.NewAttestationRenderer(c.CraftingState, attClient, "", "", nil, renderer.WithLogger(action.Logger))
148-
if err != nil {
149-
return nil, fmt.Errorf("rendering statement: %w", err)
150-
}
151-
152-
// We do not want to evaluate policies here during render since we want to do it in a separate step
153-
statement, err := renderer.RenderStatement(ctx)
154-
if err != nil {
155-
return nil, fmt.Errorf("rendering statement: %w", err)
156-
}
157-
158-
// Add attestation-level policy evaluations
159-
if err := c.EvaluateAttestationPolicies(ctx, attestationID, statement); err != nil {
160-
return nil, fmt.Errorf("evaluating attestation policies: %w", err)
161-
}
162-
163-
res.PolicyEvaluations, res.HasPolicyViolations = getPolicyEvaluations(c)
164-
}
129+
// Read policy evaluations from crafting state (evaluation happens in init/push, not here)
130+
res.PolicyEvaluations, res.HasPolicyViolations = getPolicyEvaluations(c)
165131

166132
if v := workflowMeta.GetVersion(); v != nil {
167133
res.WorkflowMeta.ProjectVersion = &ProjectVersion{

app/controlplane/api/gen/frontend/workflowcontract/v1/crafting_schema.ts

Lines changed: 81 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/controlplane/api/gen/jsonschema/workflowcontract.v1.PolicySpecV2.jsonschema.json

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

0 commit comments

Comments
 (0)