@@ -20,9 +20,13 @@ import (
2020 "fmt"
2121 "time"
2222
23+ pb "github.com/chainloop-dev/chainloop/app/controlplane/api/controlplane/v1"
2324 pbc "github.com/chainloop-dev/chainloop/app/controlplane/api/workflowcontract/v1"
2425 "github.com/chainloop-dev/chainloop/pkg/attestation/crafter"
2526 v1 "github.com/chainloop-dev/chainloop/pkg/attestation/crafter/api/attestation/v1"
27+ "github.com/chainloop-dev/chainloop/pkg/attestation/renderer"
28+ "github.com/chainloop-dev/chainloop/pkg/attestation/renderer/chainloop"
29+ intoto "github.com/in-toto/attestation/go/v1"
2630)
2731
2832type AttestationStatusOpts struct {
@@ -36,20 +40,22 @@ type AttestationStatus struct {
3640 * ActionsOpts
3741 c * crafter.Crafter
3842 // Do not show information about the project version release status
39- isPushed bool
43+ isPushed bool
44+ skipPolicyEvaluation bool
4045}
4146
4247type AttestationStatusResult struct {
43- AttestationID string `json:"attestationID"`
44- InitializedAt * time.Time `json:"initializedAt"`
45- WorkflowMeta * AttestationStatusWorkflowMeta `json:"workflowMeta"`
46- Materials []AttestationStatusResultMaterial `json:"materials"`
47- EnvVars map [string ]string `json:"envVars"`
48- RunnerContext * AttestationResultRunnerContext `json:"runnerContext"`
49- DryRun bool `json:"dryRun"`
50- Annotations []* Annotation `json:"annotations"`
51- IsPushed bool `json:"isPushed"`
52- PolicyEvaluations map [string ][]* PolicyEvaluation `json:"policy_evaluations,omitempty"`
48+ AttestationID string `json:"attestationID"`
49+ InitializedAt * time.Time `json:"initializedAt"`
50+ WorkflowMeta * AttestationStatusWorkflowMeta `json:"workflowMeta"`
51+ Materials []AttestationStatusResultMaterial `json:"materials"`
52+ EnvVars map [string ]string `json:"envVars"`
53+ RunnerContext * AttestationResultRunnerContext `json:"runnerContext"`
54+ DryRun bool `json:"dryRun"`
55+ Annotations []* Annotation `json:"annotations"`
56+ IsPushed bool `json:"isPushed"`
57+ PolicyEvaluations map [string ][]* PolicyEvaluation `json:"policy_evaluations,omitempty"`
58+ HasPolicyViolations bool `json:"hasPolicyViolations"`
5359}
5460
5561type AttestationResultRunnerContext struct {
@@ -58,8 +64,8 @@ type AttestationResultRunnerContext struct {
5864}
5965
6066type AttestationStatusWorkflowMeta struct {
61- WorkflowID , Name , Team , Project , ContractRevision , Organization string
62- ProjectVersion * ProjectVersion
67+ WorkflowID , Name , Team , Project , ContractRevision , ContractName , Organization string
68+ ProjectVersion * ProjectVersion
6369}
6470
6571type AttestationStatusResultMaterial struct {
@@ -80,7 +86,19 @@ func NewAttestationStatus(cfg *AttestationStatusOpts) (*AttestationStatus, error
8086 }, nil
8187}
8288
83- func (action * AttestationStatus ) Run (ctx context.Context , attestationID string ) (* AttestationStatusResult , error ) {
89+ func WithSkipPolicyEvaluation () func (* AttestationStatus ) {
90+ return func (opts * AttestationStatus ) {
91+ opts .skipPolicyEvaluation = true
92+ }
93+ }
94+
95+ type AttestationStatusOpt func (* AttestationStatus )
96+
97+ func (action * AttestationStatus ) Run (ctx context.Context , attestationID string , opts ... AttestationStatusOpt ) (* AttestationStatusResult , error ) {
98+ for _ , opt := range opts {
99+ opt (action )
100+ }
101+
84102 c := action .c
85103
86104 if initialized , err := c .AlreadyInitialized (ctx , attestationID ); err != nil {
@@ -106,24 +124,35 @@ func (action *AttestationStatus) Run(ctx context.Context, attestationID string)
106124 Project : workflowMeta .GetProject (),
107125 Team : workflowMeta .GetTeam (),
108126 ContractRevision : workflowMeta .GetSchemaRevision (),
127+ ContractName : workflowMeta .GetContractName (),
109128 },
110129 InitializedAt : toTimePtr (att .InitializedAt .AsTime ()),
111130 DryRun : c .CraftingState .DryRun ,
112131 Annotations : pbAnnotationsToAction (c .CraftingState .InputSchema .GetAnnotations ()),
113132 IsPushed : action .isPushed ,
114133 }
115134
116- // grouped by material name
117- evaluations := make (map [string ][]* PolicyEvaluation )
118- for _ , v := range att .GetPolicyEvaluations () {
119- if existing , ok := evaluations [v .MaterialName ]; ok {
120- evaluations [v .MaterialName ] = append (existing , policyEvaluationStateToActionForStatus (v ))
121- } else {
122- evaluations [v .MaterialName ] = []* PolicyEvaluation {policyEvaluationStateToActionForStatus (v )}
135+ if ! action .skipPolicyEvaluation {
136+ // We need to render the statement to get the policy evaluations
137+ attClient := pb .NewAttestationServiceClient (action .CPConnection )
138+ renderer , err := renderer .NewAttestationRenderer (c .CraftingState , attClient , "" , "" , nil , renderer .WithLogger (action .Logger ))
139+ if err != nil {
140+ return nil , fmt .Errorf ("rendering statement: %w" , err )
123141 }
124- }
125142
126- res .PolicyEvaluations = evaluations
143+ // 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 ))
145+ if err != nil {
146+ return nil , fmt .Errorf ("rendering statement: %w" , err )
147+ }
148+
149+ res .PolicyEvaluations , err = action .getPolicyEvaluations (ctx , c , statement )
150+ if err != nil {
151+ return nil , fmt .Errorf ("getting policy evaluations: %w" , err )
152+ }
153+
154+ res .HasPolicyViolations = len (res .PolicyEvaluations ) > 0
155+ }
127156
128157 if v := workflowMeta .GetVersion (); v != nil {
129158 res .WorkflowMeta .ProjectVersion = & ProjectVersion {
@@ -157,6 +186,7 @@ func (action *AttestationStatus) Run(ctx context.Context, attestationID string)
157186 for _ , err := range errors {
158187 combinedErrs += (* err ).Error () + "\n "
159188 }
189+
160190 if len (errors ) > 0 && ! c .CraftingState .DryRun {
161191 return nil , fmt .Errorf ("error resolving env vars: %s" , combinedErrs )
162192 }
@@ -170,6 +200,37 @@ func (action *AttestationStatus) Run(ctx context.Context, attestationID string)
170200 return res , nil
171201}
172202
203+ // 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 ) {
205+ // grouped by material name
206+ evaluations := make (map [string ][]* PolicyEvaluation )
207+
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+
217+ // Add attestation-level policy evaluations
218+ attestationEvaluations , err := c .EvaluateAttestationPolicies (ctx , statement )
219+ if err != nil {
220+ return nil , fmt .Errorf ("evaluating attestation policies: %w" , err )
221+ }
222+
223+ for _ , v := range attestationEvaluations {
224+ if existing , ok := evaluations [chainloop .AttPolicyEvaluation ]; ok {
225+ evaluations [chainloop .AttPolicyEvaluation ] = append (existing , policyEvaluationStateToActionForStatus (v ))
226+ } else {
227+ evaluations [chainloop .AttPolicyEvaluation ] = []* PolicyEvaluation {policyEvaluationStateToActionForStatus (v )}
228+ }
229+ }
230+
231+ return evaluations , nil
232+ }
233+
173234// populateMaterials populates the materials in the attestation result regardless of where they are defined
174235// (contract schema or inline in the attestation)
175236func populateMaterials (craftingState * v1.CraftingState , res * AttestationStatusResult ) error {
0 commit comments