From 4f85833d258436f0f7488ff2d9ca1c8b25788a2a Mon Sep 17 00:00:00 2001 From: "Jose I. Paris" Date: Tue, 23 Dec 2025 00:29:03 +0100 Subject: [PATCH 1/3] store gate property in attestation Signed-off-by: Jose I. Paris --- pkg/attestation/renderer/chainloop/v02.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/attestation/renderer/chainloop/v02.go b/pkg/attestation/renderer/chainloop/v02.go index 5e79c6380..8b4692879 100644 --- a/pkg/attestation/renderer/chainloop/v02.go +++ b/pkg/attestation/renderer/chainloop/v02.go @@ -86,6 +86,7 @@ type PolicyEvaluation struct { SkipReasons []string `json:"skipReasons,omitempty"` GroupReference *intoto.ResourceDescriptor `json:"groupReference,omitempty"` Requirements []string `json:"requirements,omitempty"` + Gate bool `json:"gate,omitempty"` } type PolicyViolation struct { @@ -310,6 +311,7 @@ func renderEvaluation(ev *v1.PolicyEvaluation) (*PolicyEvaluation, error) { Skipped: ev.Skipped, GroupReference: groupRef, Requirements: ev.Requirements, + Gate: ev.Gate, }, nil } From 9a2368f6b401bc60f909d7451bb923fb8aa17f66 Mon Sep 17 00:00:00 2001 From: "Jose I. Paris" Date: Tue, 23 Dec 2025 01:30:04 +0100 Subject: [PATCH 2/3] mark as blocked Signed-off-by: Jose I. Paris --- pkg/attestation/renderer/chainloop/v02.go | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/pkg/attestation/renderer/chainloop/v02.go b/pkg/attestation/renderer/chainloop/v02.go index 8b4692879..9c88949a0 100644 --- a/pkg/attestation/renderer/chainloop/v02.go +++ b/pkg/attestation/renderer/chainloop/v02.go @@ -222,6 +222,20 @@ func (r *RendererV02) predicate() (*structpb.Struct, error) { policyCheckBlockingStrategy = PolicyViolationBlockingStrategyEnforced } + // Determine if the attestation is blocked + // An attestation is blocked when: + // - any of the policies marked as gate has violations + // - or if there are any policy violations and the attestation is configured to be blocked on violations + // In all cases, if the bypass flag is set, the attestation is not blocked + gated := false + for _, eval := range r.att.PolicyEvaluations { + if len(eval.Violations) > 0 && eval.Gate { + gated = true + break + } + } + blocked := !r.att.GetBypassPolicyCheck() && (gated || hasViolations && r.att.GetBlockOnPolicyViolation()) + p := ProvenancePredicateV02{ ProvenancePredicateCommon: predicateCommon(r.builder, r.att), Materials: normalizedMaterials, @@ -229,7 +243,7 @@ func (r *RendererV02) predicate() (*structpb.Struct, error) { PolicyHasViolations: hasViolations, PolicyCheckBlockingStrategy: policyCheckBlockingStrategy, PolicyBlockBypassEnabled: r.att.GetBypassPolicyCheck(), - PolicyAttBlocked: hasViolations && r.att.GetBlockOnPolicyViolation() && !r.att.GetBypassPolicyCheck(), + PolicyAttBlocked: blocked, SigningCA: r.att.GetSigningOptions().GetSigningCa(), SigningTSA: r.att.GetSigningOptions().GetTimestampAuthorityUrl(), } From 31df86b398cce254a3371f9268ce2635481473d4 Mon Sep 17 00:00:00 2001 From: "Jose I. Paris" Date: Tue, 23 Dec 2025 01:45:11 +0100 Subject: [PATCH 3/3] expose gate property Signed-off-by: Jose I. Paris --- app/cli/cmd/workflow_workflow_run_describe.go | 4 +++- app/cli/pkg/action/workflow_run_describe.go | 1 + .../api/controlplane/v1/response_messages.pb.go | 13 +++++++++++-- .../api/controlplane/v1/response_messages.proto | 1 + .../frontend/controlplane/v1/response_messages.ts | 15 +++++++++++++++ ...ntrolplane.v1.PolicyEvaluation.jsonschema.json | 3 +++ .../controlplane.v1.PolicyEvaluation.schema.json | 3 +++ app/controlplane/internal/service/attestation.go | 1 + 8 files changed, 38 insertions(+), 3 deletions(-) diff --git a/app/cli/cmd/workflow_workflow_run_describe.go b/app/cli/cmd/workflow_workflow_run_describe.go index 750978465..f4839d5de 100644 --- a/app/cli/cmd/workflow_workflow_run_describe.go +++ b/app/cli/cmd/workflow_workflow_run_describe.go @@ -162,8 +162,10 @@ func workflowRunDescribeTableOutput(run *action.WorkflowRunItemFull) error { } gt.AppendRow(table.Row{"Policies violation strategy", att.PolicyEvaluationStatus.Strategy}) - if att.PolicyEvaluationStatus.Strategy == action.PolicyViolationBlockingStrategyEnforced { + if att.PolicyEvaluationStatus.Blocked { gt.AppendRow(table.Row{"Run Blocked", att.PolicyEvaluationStatus.Blocked}) + } + if att.PolicyEvaluationStatus.Strategy == action.PolicyViolationBlockingStrategyEnforced { gt.AppendRow(table.Row{"Policy enforcement bypassed", att.PolicyEvaluationStatus.Bypassed}) } diff --git a/app/cli/pkg/action/workflow_run_describe.go b/app/cli/pkg/action/workflow_run_describe.go index 630e3e2b2..71c50863e 100644 --- a/app/cli/pkg/action/workflow_run_describe.go +++ b/app/cli/pkg/action/workflow_run_describe.go @@ -296,6 +296,7 @@ func policyEvaluationPBToAction(in *pb.PolicyEvaluation) *PolicyEvaluation { Violations: violations, Skipped: in.Skipped, SkipReasons: in.SkipReasons, + Gate: in.Gate, } } diff --git a/app/controlplane/api/controlplane/v1/response_messages.pb.go b/app/controlplane/api/controlplane/v1/response_messages.pb.go index 26c2cd420..86ceb210d 100644 --- a/app/controlplane/api/controlplane/v1/response_messages.pb.go +++ b/app/controlplane/api/controlplane/v1/response_messages.pb.go @@ -1079,6 +1079,7 @@ type PolicyEvaluation struct { SkipReasons []string `protobuf:"bytes,13,rep,name=skip_reasons,json=skipReasons,proto3" json:"skip_reasons,omitempty"` Requirements []string `protobuf:"bytes,14,rep,name=requirements,proto3" json:"requirements,omitempty"` GroupReference *PolicyReference `protobuf:"bytes,15,opt,name=group_reference,json=groupReference,proto3" json:"group_reference,omitempty"` + Gate bool `protobuf:"varint,16,opt,name=gate,proto3" json:"gate,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -1212,6 +1213,13 @@ func (x *PolicyEvaluation) GetGroupReference() *PolicyReference { return nil } +func (x *PolicyEvaluation) GetGate() bool { + if x != nil { + return x.Gate + } + return false +} + type PolicyViolation struct { state protoimpl.MessageState `protogen:"open.v1"` Subject string `protobuf:"bytes,1,opt,name=subject,proto3" json:"subject,omitempty"` @@ -2664,7 +2672,7 @@ const file_controlplane_v1_response_messages_proto_rawDesc = "" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"X\n" + "\x11PolicyEvaluations\x12C\n" + - "\vevaluations\x18\x01 \x03(\v2!.controlplane.v1.PolicyEvaluationR\vevaluations\"\xfe\x05\n" + + "\vevaluations\x18\x01 \x03(\v2!.controlplane.v1.PolicyEvaluationR\vevaluations\"\x92\x06\n" + "\x10PolicyEvaluation\x12\x12\n" + "\x04name\x18\x01 \x01(\tR\x04name\x12#\n" + "\rmaterial_name\x18\x02 \x01(\tR\fmaterialName\x12\x16\n" + @@ -2682,7 +2690,8 @@ const file_controlplane_v1_response_messages_proto_rawDesc = "" + "\askipped\x18\f \x01(\bR\askipped\x12!\n" + "\fskip_reasons\x18\r \x03(\tR\vskipReasons\x12\"\n" + "\frequirements\x18\x0e \x03(\tR\frequirements\x12I\n" + - "\x0fgroup_reference\x18\x0f \x01(\v2 .controlplane.v1.PolicyReferenceR\x0egroupReference\x1a>\n" + + "\x0fgroup_reference\x18\x0f \x01(\v2 .controlplane.v1.PolicyReferenceR\x0egroupReference\x12\x12\n" + + "\x04gate\x18\x10 \x01(\bR\x04gate\x1a>\n" + "\x10AnnotationsEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\x1a7\n" + diff --git a/app/controlplane/api/controlplane/v1/response_messages.proto b/app/controlplane/api/controlplane/v1/response_messages.proto index cb12e2ce8..94eac83e7 100644 --- a/app/controlplane/api/controlplane/v1/response_messages.proto +++ b/app/controlplane/api/controlplane/v1/response_messages.proto @@ -161,6 +161,7 @@ message PolicyEvaluation { repeated string skip_reasons = 13; repeated string requirements = 14; PolicyReference group_reference = 15; + bool gate = 16; } message PolicyViolation { diff --git a/app/controlplane/api/gen/frontend/controlplane/v1/response_messages.ts b/app/controlplane/api/gen/frontend/controlplane/v1/response_messages.ts index 7fe2b2932..a3d45ae85 100644 --- a/app/controlplane/api/gen/frontend/controlplane/v1/response_messages.ts +++ b/app/controlplane/api/gen/frontend/controlplane/v1/response_messages.ts @@ -441,6 +441,7 @@ export interface PolicyEvaluation { skipReasons: string[]; requirements: string[]; groupReference?: PolicyReference; + gate: boolean; } export interface PolicyEvaluation_AnnotationsEntry { @@ -2203,6 +2204,7 @@ function createBasePolicyEvaluation(): PolicyEvaluation { skipReasons: [], requirements: [], groupReference: undefined, + gate: false, }; } @@ -2250,6 +2252,9 @@ export const PolicyEvaluation = { if (message.groupReference !== undefined) { PolicyReference.encode(message.groupReference, writer.uint32(122).fork()).ldelim(); } + if (message.gate === true) { + writer.uint32(128).bool(message.gate); + } return writer; }, @@ -2364,6 +2369,13 @@ export const PolicyEvaluation = { message.groupReference = PolicyReference.decode(reader, reader.uint32()); continue; + case 16: + if (tag !== 128) { + break; + } + + message.gate = reader.bool(); + continue; } if ((tag & 7) === 4 || tag === 0) { break; @@ -2401,6 +2413,7 @@ export const PolicyEvaluation = { skipReasons: Array.isArray(object?.skipReasons) ? object.skipReasons.map((e: any) => String(e)) : [], requirements: Array.isArray(object?.requirements) ? object.requirements.map((e: any) => String(e)) : [], groupReference: isSet(object.groupReference) ? PolicyReference.fromJSON(object.groupReference) : undefined, + gate: isSet(object.gate) ? Boolean(object.gate) : false, }; }, @@ -2448,6 +2461,7 @@ export const PolicyEvaluation = { } message.groupReference !== undefined && (obj.groupReference = message.groupReference ? PolicyReference.toJSON(message.groupReference) : undefined); + message.gate !== undefined && (obj.gate = message.gate); return obj; }, @@ -2488,6 +2502,7 @@ export const PolicyEvaluation = { message.groupReference = (object.groupReference !== undefined && object.groupReference !== null) ? PolicyReference.fromPartial(object.groupReference) : undefined; + message.gate = object.gate ?? false; return message; }, }; diff --git a/app/controlplane/api/gen/jsonschema/controlplane.v1.PolicyEvaluation.jsonschema.json b/app/controlplane/api/gen/jsonschema/controlplane.v1.PolicyEvaluation.jsonschema.json index 8ba147876..2b84ce035 100644 --- a/app/controlplane/api/gen/jsonschema/controlplane.v1.PolicyEvaluation.jsonschema.json +++ b/app/controlplane/api/gen/jsonschema/controlplane.v1.PolicyEvaluation.jsonschema.json @@ -35,6 +35,9 @@ "description": { "type": "string" }, + "gate": { + "type": "boolean" + }, "groupReference": { "$ref": "controlplane.v1.PolicyReference.jsonschema.json" }, diff --git a/app/controlplane/api/gen/jsonschema/controlplane.v1.PolicyEvaluation.schema.json b/app/controlplane/api/gen/jsonschema/controlplane.v1.PolicyEvaluation.schema.json index d584793ec..0397ee3cb 100644 --- a/app/controlplane/api/gen/jsonschema/controlplane.v1.PolicyEvaluation.schema.json +++ b/app/controlplane/api/gen/jsonschema/controlplane.v1.PolicyEvaluation.schema.json @@ -35,6 +35,9 @@ "description": { "type": "string" }, + "gate": { + "type": "boolean" + }, "group_reference": { "$ref": "controlplane.v1.PolicyReference.schema.json" }, diff --git a/app/controlplane/internal/service/attestation.go b/app/controlplane/internal/service/attestation.go index b78b6c8cd..85ea8c898 100644 --- a/app/controlplane/internal/service/attestation.go +++ b/app/controlplane/internal/service/attestation.go @@ -592,6 +592,7 @@ func extractPolicyEvaluations(in map[string][]*chainloop.PolicyEvaluation) map[s Skipped: ev.Skipped, SkipReasons: ev.SkipReasons, Requirements: ev.Requirements, + Gate: ev.Gate, } if ev.PolicyReference != nil {