diff --git a/app/cli/cmd/workflow_workflow_run_describe.go b/app/cli/cmd/workflow_workflow_run_describe.go index f4839d5de..d2b183d7f 100644 --- a/app/cli/cmd/workflow_workflow_run_describe.go +++ b/app/cli/cmd/workflow_workflow_run_describe.go @@ -165,6 +165,9 @@ func workflowRunDescribeTableOutput(run *action.WorkflowRunItemFull) error { if att.PolicyEvaluationStatus.Blocked { gt.AppendRow(table.Row{"Run Blocked", att.PolicyEvaluationStatus.Blocked}) } + if att.PolicyEvaluationStatus.HasGatedViolations { + gt.AppendRow(table.Row{"Run Gated", text.Colors{text.FgHiRed}.Sprint(att.PolicyEvaluationStatus.HasGatedViolations)}) + } 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 71c50863e..a488c6c71 100644 --- a/app/cli/pkg/action/workflow_run_describe.go +++ b/app/cli/pkg/action/workflow_run_describe.go @@ -64,10 +64,11 @@ type WorkflowRunAttestationItem struct { } type PolicyEvaluationStatus struct { - Strategy string `json:"strategy"` - Bypassed bool `json:"bypassed"` - Blocked bool `json:"blocked"` - HasViolations bool `json:"has_violations"` + Strategy string `json:"strategy"` + Bypassed bool `json:"bypassed"` + Blocked bool `json:"blocked"` + HasViolations bool `json:"has_violations"` + HasGatedViolations bool `json:"has_gated_violations"` } type Material struct { @@ -236,10 +237,11 @@ func (action *WorkflowRunDescribe) Run(ctx context.Context, opts *WorkflowRunDes Digest: att.DigestInCasBackend, PolicyEvaluations: evaluations, PolicyEvaluationStatus: &PolicyEvaluationStatus{ - Strategy: policyEvaluationStatus.Strategy, - Bypassed: policyEvaluationStatus.Bypassed, - Blocked: policyEvaluationStatus.Blocked, - HasViolations: policyEvaluationStatus.HasViolations, + Strategy: policyEvaluationStatus.Strategy, + Bypassed: policyEvaluationStatus.Bypassed, + Blocked: policyEvaluationStatus.Blocked, + HasViolations: policyEvaluationStatus.HasViolations, + HasGatedViolations: policyEvaluationStatus.HasGatedViolations, }, } diff --git a/app/controlplane/api/controlplane/v1/response_messages.pb.go b/app/controlplane/api/controlplane/v1/response_messages.pb.go index a970876ac..1ee65fdab 100644 --- a/app/controlplane/api/controlplane/v1/response_messages.pb.go +++ b/app/controlplane/api/controlplane/v1/response_messages.pb.go @@ -2260,13 +2260,14 @@ func (x *APITokenItem) GetLastUsedAt() *timestamppb.Timestamp { } type AttestationItem_PolicyEvaluationStatus struct { - state protoimpl.MessageState `protogen:"open.v1"` - Strategy string `protobuf:"bytes,1,opt,name=strategy,proto3" json:"strategy,omitempty"` - Bypassed bool `protobuf:"varint,2,opt,name=bypassed,proto3" json:"bypassed,omitempty"` - Blocked bool `protobuf:"varint,3,opt,name=blocked,proto3" json:"blocked,omitempty"` - HasViolations bool `protobuf:"varint,4,opt,name=has_violations,json=hasViolations,proto3" json:"has_violations,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Strategy string `protobuf:"bytes,1,opt,name=strategy,proto3" json:"strategy,omitempty"` + Bypassed bool `protobuf:"varint,2,opt,name=bypassed,proto3" json:"bypassed,omitempty"` + Blocked bool `protobuf:"varint,3,opt,name=blocked,proto3" json:"blocked,omitempty"` + HasViolations bool `protobuf:"varint,4,opt,name=has_violations,json=hasViolations,proto3" json:"has_violations,omitempty"` + HasGatedViolations bool `protobuf:"varint,5,opt,name=has_gated_violations,json=hasGatedViolations,proto3" json:"has_gated_violations,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *AttestationItem_PolicyEvaluationStatus) Reset() { @@ -2327,6 +2328,13 @@ func (x *AttestationItem_PolicyEvaluationStatus) GetHasViolations() bool { return false } +func (x *AttestationItem_PolicyEvaluationStatus) GetHasGatedViolations() bool { + if x != nil { + return x.HasGatedViolations + } + return false +} + type AttestationItem_EnvVariable struct { state protoimpl.MessageState `protogen:"open.v1"` Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` @@ -2641,7 +2649,7 @@ const file_controlplane_v1_response_messages_proto_rawDesc = "" + "\n" + "created_at\x18\x04 \x01(\v2\x1a.google.protobuf.TimestampR\tcreatedAt\x12;\n" + "\vreleased_at\x18\x05 \x01(\v2\x1a.google.protobuf.TimestampR\n" + - "releasedAt\"\xb1\n" + + "releasedAt\"\xe3\n" + "\n" + "\x0fAttestationItem\x12\x1e\n" + "\benvelope\x18\x03 \x01(\fB\x02\x18\x01R\benvelope\x12\x16\n" + @@ -2658,12 +2666,13 @@ const file_controlplane_v1_response_messages_proto_rawDesc = "" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\x1ah\n" + "\x16PolicyEvaluationsEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x128\n" + - "\x05value\x18\x02 \x01(\v2\".controlplane.v1.PolicyEvaluationsR\x05value:\x028\x01\x1a\x91\x01\n" + + "\x05value\x18\x02 \x01(\v2\".controlplane.v1.PolicyEvaluationsR\x05value:\x028\x01\x1a\xc3\x01\n" + "\x16PolicyEvaluationStatus\x12\x1a\n" + "\bstrategy\x18\x01 \x01(\tR\bstrategy\x12\x1a\n" + "\bbypassed\x18\x02 \x01(\bR\bbypassed\x12\x18\n" + "\ablocked\x18\x03 \x01(\bR\ablocked\x12%\n" + - "\x0ehas_violations\x18\x04 \x01(\bR\rhasViolations\x1a7\n" + + "\x0ehas_violations\x18\x04 \x01(\bR\rhasViolations\x120\n" + + "\x14has_gated_violations\x18\x05 \x01(\bR\x12hasGatedViolations\x1a7\n" + "\vEnvVariable\x12\x12\n" + "\x04name\x18\x01 \x01(\tR\x04name\x12\x14\n" + "\x05value\x18\x02 \x01(\tR\x05value\x1a\xf9\x02\n" + diff --git a/app/controlplane/api/controlplane/v1/response_messages.proto b/app/controlplane/api/controlplane/v1/response_messages.proto index 782360f61..902792268 100644 --- a/app/controlplane/api/controlplane/v1/response_messages.proto +++ b/app/controlplane/api/controlplane/v1/response_messages.proto @@ -116,6 +116,7 @@ message AttestationItem { bool bypassed = 2; bool blocked = 3; bool has_violations = 4; + bool has_gated_violations = 5; } message EnvVariable { 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 492eb6fb4..e31106e26 100644 --- a/app/controlplane/api/gen/frontend/controlplane/v1/response_messages.ts +++ b/app/controlplane/api/gen/frontend/controlplane/v1/response_messages.ts @@ -391,6 +391,7 @@ export interface AttestationItem_PolicyEvaluationStatus { bypassed: boolean; blocked: boolean; hasViolations: boolean; + hasGatedViolations: boolean; } export interface AttestationItem_EnvVariable { @@ -1690,7 +1691,7 @@ export const AttestationItem_PolicyEvaluationsEntry = { }; function createBaseAttestationItem_PolicyEvaluationStatus(): AttestationItem_PolicyEvaluationStatus { - return { strategy: "", bypassed: false, blocked: false, hasViolations: false }; + return { strategy: "", bypassed: false, blocked: false, hasViolations: false, hasGatedViolations: false }; } export const AttestationItem_PolicyEvaluationStatus = { @@ -1707,6 +1708,9 @@ export const AttestationItem_PolicyEvaluationStatus = { if (message.hasViolations === true) { writer.uint32(32).bool(message.hasViolations); } + if (message.hasGatedViolations === true) { + writer.uint32(40).bool(message.hasGatedViolations); + } return writer; }, @@ -1745,6 +1749,13 @@ export const AttestationItem_PolicyEvaluationStatus = { message.hasViolations = reader.bool(); continue; + case 5: + if (tag !== 40) { + break; + } + + message.hasGatedViolations = reader.bool(); + continue; } if ((tag & 7) === 4 || tag === 0) { break; @@ -1760,6 +1771,7 @@ export const AttestationItem_PolicyEvaluationStatus = { bypassed: isSet(object.bypassed) ? Boolean(object.bypassed) : false, blocked: isSet(object.blocked) ? Boolean(object.blocked) : false, hasViolations: isSet(object.hasViolations) ? Boolean(object.hasViolations) : false, + hasGatedViolations: isSet(object.hasGatedViolations) ? Boolean(object.hasGatedViolations) : false, }; }, @@ -1769,6 +1781,7 @@ export const AttestationItem_PolicyEvaluationStatus = { message.bypassed !== undefined && (obj.bypassed = message.bypassed); message.blocked !== undefined && (obj.blocked = message.blocked); message.hasViolations !== undefined && (obj.hasViolations = message.hasViolations); + message.hasGatedViolations !== undefined && (obj.hasGatedViolations = message.hasGatedViolations); return obj; }, @@ -1786,6 +1799,7 @@ export const AttestationItem_PolicyEvaluationStatus = { message.bypassed = object.bypassed ?? false; message.blocked = object.blocked ?? false; message.hasViolations = object.hasViolations ?? false; + message.hasGatedViolations = object.hasGatedViolations ?? false; return message; }, }; diff --git a/app/controlplane/api/gen/jsonschema/controlplane.v1.AttestationItem.PolicyEvaluationStatus.jsonschema.json b/app/controlplane/api/gen/jsonschema/controlplane.v1.AttestationItem.PolicyEvaluationStatus.jsonschema.json index b77af9fcd..4d330a558 100644 --- a/app/controlplane/api/gen/jsonschema/controlplane.v1.AttestationItem.PolicyEvaluationStatus.jsonschema.json +++ b/app/controlplane/api/gen/jsonschema/controlplane.v1.AttestationItem.PolicyEvaluationStatus.jsonschema.json @@ -3,6 +3,9 @@ "$schema": "https://json-schema.org/draft/2020-12/schema", "additionalProperties": false, "patternProperties": { + "^(has_gated_violations)$": { + "type": "boolean" + }, "^(has_violations)$": { "type": "boolean" } @@ -14,6 +17,9 @@ "bypassed": { "type": "boolean" }, + "hasGatedViolations": { + "type": "boolean" + }, "hasViolations": { "type": "boolean" }, diff --git a/app/controlplane/api/gen/jsonschema/controlplane.v1.AttestationItem.PolicyEvaluationStatus.schema.json b/app/controlplane/api/gen/jsonschema/controlplane.v1.AttestationItem.PolicyEvaluationStatus.schema.json index 09b53f198..5ad5d5e91 100644 --- a/app/controlplane/api/gen/jsonschema/controlplane.v1.AttestationItem.PolicyEvaluationStatus.schema.json +++ b/app/controlplane/api/gen/jsonschema/controlplane.v1.AttestationItem.PolicyEvaluationStatus.schema.json @@ -3,6 +3,9 @@ "$schema": "https://json-schema.org/draft/2020-12/schema", "additionalProperties": false, "patternProperties": { + "^(hasGatedViolations)$": { + "type": "boolean" + }, "^(hasViolations)$": { "type": "boolean" } @@ -14,6 +17,9 @@ "bypassed": { "type": "boolean" }, + "has_gated_violations": { + "type": "boolean" + }, "has_violations": { "type": "boolean" }, diff --git a/app/controlplane/internal/service/attestation.go b/app/controlplane/internal/service/attestation.go index 6d188b70e..4ca1a7423 100644 --- a/app/controlplane/internal/service/attestation.go +++ b/app/controlplane/internal/service/attestation.go @@ -553,10 +553,11 @@ func bizAttestationToPb(att *biz.Attestation) (*cpAPI.AttestationItem, error) { Annotations: predicate.GetAnnotations(), PolicyEvaluations: extractPolicyEvaluations(predicate.GetPolicyEvaluations()), PolicyEvaluationStatus: &cpAPI.AttestationItem_PolicyEvaluationStatus{ - Strategy: string(policyEvaluationStatus.Strategy), - Bypassed: policyEvaluationStatus.Bypassed, - Blocked: policyEvaluationStatus.Blocked, - HasViolations: policyEvaluationStatus.HasViolations, + Strategy: string(policyEvaluationStatus.Strategy), + Bypassed: policyEvaluationStatus.Bypassed, + Blocked: policyEvaluationStatus.Blocked, + HasViolations: policyEvaluationStatus.HasViolations, + HasGatedViolations: policyEvaluationStatus.HasGatedViolations, }, Bundle: att.Bundle, }, nil diff --git a/app/controlplane/pkg/biz/referrer.go b/app/controlplane/pkg/biz/referrer.go index c89a73213..f82829df1 100644 --- a/app/controlplane/pkg/biz/referrer.go +++ b/app/controlplane/pkg/biz/referrer.go @@ -300,12 +300,14 @@ func extractReferrers(att *dsse.Envelope, digest cr_v1.Hash, repo ReferrerRepo) // We add both annotations and workflow metadata attestationReferrer.Annotations = predicate.GetAnnotations() hasViolations := predicate.GetPolicyEvaluationStatus().HasViolations + hasGatedViolations := predicate.GetPolicyEvaluationStatus().HasGatedViolations attestationReferrer.Metadata = map[string]string{ // workflow name, team and project "name": predicate.GetMetadata().Name, "team": predicate.GetMetadata().Team, "project": predicate.GetMetadata().Project, "hasPolicyViolations": fmt.Sprintf("%t", hasViolations), + "hasGatedPolicyViolations": fmt.Sprintf("%t", hasGatedViolations), "projectVersion": predicate.GetMetadata().ProjectVersion, "projectVersionPrerelease": fmt.Sprintf("%t", predicate.GetMetadata().ProjectVersionPrerelease), "organization": predicate.GetMetadata().Organization, diff --git a/app/controlplane/pkg/biz/referrer_integration_test.go b/app/controlplane/pkg/biz/referrer_integration_test.go index e1f30a65f..e3f48fb86 100644 --- a/app/controlplane/pkg/biz/referrer_integration_test.go +++ b/app/controlplane/pkg/biz/referrer_integration_test.go @@ -257,6 +257,7 @@ func (s *referrerIntegrationTestSuite) TestExtractAndPersists() { "project": "test", "team": "my-team", "organization": "my-org", + "hasGatedPolicyViolations": "false", "hasPolicyViolations": "false", "projectVersion": "", "projectVersionPrerelease": "false", diff --git a/app/controlplane/pkg/biz/referrer_test.go b/app/controlplane/pkg/biz/referrer_test.go index 7f659a2d5..50772bdf9 100644 --- a/app/controlplane/pkg/biz/referrer_test.go +++ b/app/controlplane/pkg/biz/referrer_test.go @@ -103,6 +103,7 @@ func (s *referrerTestSuite) TestExtractReferrers() { "project": "foo", "projectVersion": "", "projectVersionPrerelease": "false", + "hasGatedPolicyViolations": "false", "hasPolicyViolations": "false", }, Annotations: map[string]string{ @@ -157,6 +158,7 @@ func (s *referrerTestSuite) TestExtractReferrers() { "project": "bar", "projectVersion": "", "projectVersionPrerelease": "false", + "hasGatedPolicyViolations": "false", "hasPolicyViolations": "false", }, Annotations: map[string]string{ @@ -193,6 +195,7 @@ func (s *referrerTestSuite) TestExtractReferrers() { "project": "foo", "projectVersion": "", "projectVersionPrerelease": "false", + "hasGatedPolicyViolations": "false", "hasPolicyViolations": "false", }, Annotations: map[string]string{ @@ -257,6 +260,7 @@ func (s *referrerTestSuite) TestExtractReferrers() { "project": "test", "projectVersion": "", "projectVersionPrerelease": "false", + "hasGatedPolicyViolations": "false", "hasPolicyViolations": "false", }, References: []*Referrer{ diff --git a/pkg/attestation/renderer/chainloop/chainloop.go b/pkg/attestation/renderer/chainloop/chainloop.go index 82167b475..634461309 100644 --- a/pkg/attestation/renderer/chainloop/chainloop.go +++ b/pkg/attestation/renderer/chainloop/chainloop.go @@ -54,6 +54,8 @@ type PolicyEvaluationStatus struct { Blocked bool // Whether the attestation has policy violations HasViolations bool + // Whether the attestation has gated policy violations + HasGatedViolations bool } type NormalizedMaterial struct { diff --git a/pkg/attestation/renderer/chainloop/v02.go b/pkg/attestation/renderer/chainloop/v02.go index 372bff3dd..db6a08329 100644 --- a/pkg/attestation/renderer/chainloop/v02.go +++ b/pkg/attestation/renderer/chainloop/v02.go @@ -48,6 +48,8 @@ type ProvenancePredicateV02 struct { // Whether the attestation has policy violations PolicyHasViolations bool `json:"policyHasViolations"` + // Whether the attestation has policy violations in gated policies + PolicyHasGatedViolations bool `json:"policyHasGatedViolations,omitempty"` // Whether we want to block the attestation on policy violations PolicyCheckBlockingStrategy PolicyViolationBlockingStrategy `json:"policyCheckBlockingStrategy"` // Whether the policy check was bypassed @@ -248,6 +250,7 @@ func (r *RendererV02) predicate() (*structpb.Struct, error) { Materials: normalizedMaterials, PolicyEvaluations: policies, PolicyHasViolations: hasViolations, + PolicyHasGatedViolations: gated, PolicyCheckBlockingStrategy: policyCheckBlockingStrategy, PolicyBlockBypassEnabled: r.att.GetBypassPolicyCheck(), PolicyAttBlocked: blocked, @@ -413,10 +416,11 @@ func (p *ProvenancePredicateV02) GetPolicyEvaluations() map[string][]*PolicyEval func (p *ProvenancePredicateV02) GetPolicyEvaluationStatus() *PolicyEvaluationStatus { return &PolicyEvaluationStatus{ - Strategy: p.PolicyCheckBlockingStrategy, - Bypassed: p.PolicyBlockBypassEnabled, - Blocked: p.PolicyAttBlocked, - HasViolations: p.PolicyHasViolations, + Strategy: p.PolicyCheckBlockingStrategy, + Bypassed: p.PolicyBlockBypassEnabled, + Blocked: p.PolicyAttBlocked, + HasViolations: p.PolicyHasViolations, + HasGatedViolations: p.PolicyHasGatedViolations, } }