diff --git a/app/controlplane/api/gen/frontend/workflowcontract/v1/crafting_schema.ts b/app/controlplane/api/gen/frontend/workflowcontract/v1/crafting_schema.ts index 6c6eb8aab..7c4334fd8 100644 --- a/app/controlplane/api/gen/frontend/workflowcontract/v1/crafting_schema.ts +++ b/app/controlplane/api/gen/frontend/workflowcontract/v1/crafting_schema.ts @@ -493,7 +493,15 @@ export interface PolicyAttachment { * - false: policy violations are non-blocking for this policy * - unset: inherit organization-level default behavior */ - gate?: boolean | undefined; + gate?: + | boolean + | undefined; + /** + * Controls at which attestation phases this policy is evaluated. + * Empty means evaluate at all phases (INIT and PUSH) for backwards compatibility. + * Only applicable to attestation-level policies. + */ + attestationPhases: AttestationPhase[]; } export interface PolicyAttachment_WithEntry { @@ -586,9 +594,9 @@ export interface PolicySpecV2 { /** if set, it will match any material supported by Chainloop */ kind: CraftingSchema_Material_MaterialType; /** - * Controls at which attestation phases this policy is evaluated. - * Empty means evaluate at all phases (INIT and PUSH) for backwards compatibility. - * Only applicable when kind is ATTESTATION. + * Deprecated: attestation_phases has been moved to PolicyAttachment. + * + * @deprecated */ attestationPhases: AttestationPhase[]; } @@ -1420,6 +1428,7 @@ function createBasePolicyAttachment(): PolicyAttachment { with: {}, requirements: [], gate: undefined, + attestationPhases: [], }; } @@ -1446,6 +1455,11 @@ export const PolicyAttachment = { if (message.gate !== undefined) { writer.uint32(56).bool(message.gate); } + writer.uint32(66).fork(); + for (const v of message.attestationPhases) { + writer.int32(v); + } + writer.ldelim(); return writer; }, @@ -1508,6 +1522,23 @@ export const PolicyAttachment = { message.gate = reader.bool(); continue; + case 8: + if (tag === 64) { + message.attestationPhases.push(reader.int32() as any); + + continue; + } + + if (tag === 66) { + const end2 = reader.uint32() + reader.pos; + while (reader.pos < end2) { + message.attestationPhases.push(reader.int32() as any); + } + + continue; + } + + break; } if ((tag & 7) === 4 || tag === 0) { break; @@ -1531,6 +1562,9 @@ export const PolicyAttachment = { : {}, requirements: Array.isArray(object?.requirements) ? object.requirements.map((e: any) => String(e)) : [], gate: isSet(object.gate) ? Boolean(object.gate) : undefined, + attestationPhases: Array.isArray(object?.attestationPhases) + ? object.attestationPhases.map((e: any) => attestationPhaseFromJSON(e)) + : [], }; }, @@ -1553,6 +1587,11 @@ export const PolicyAttachment = { obj.requirements = []; } message.gate !== undefined && (obj.gate = message.gate); + if (message.attestationPhases) { + obj.attestationPhases = message.attestationPhases.map((e) => attestationPhaseToJSON(e)); + } else { + obj.attestationPhases = []; + } return obj; }, @@ -1578,6 +1617,7 @@ export const PolicyAttachment = { }, {}); message.requirements = object.requirements?.map((e) => e) || []; message.gate = object.gate ?? undefined; + message.attestationPhases = object.attestationPhases?.map((e) => e) || []; return message; }, }; diff --git a/app/controlplane/api/gen/jsonschema/workflowcontract.v1.PolicyAttachment.jsonschema.json b/app/controlplane/api/gen/jsonschema/workflowcontract.v1.PolicyAttachment.jsonschema.json index 1ab915cdb..958d6e2a6 100644 --- a/app/controlplane/api/gen/jsonschema/workflowcontract.v1.PolicyAttachment.jsonschema.json +++ b/app/controlplane/api/gen/jsonschema/workflowcontract.v1.PolicyAttachment.jsonschema.json @@ -3,7 +3,53 @@ "$schema": "https://json-schema.org/draft/2020-12/schema", "additionalProperties": false, "description": "A policy to be applied to this contract", + "patternProperties": { + "^(attestation_phases)$": { + "description": "Controls at which attestation phases this policy is evaluated.\n Empty means evaluate at all phases (INIT and PUSH) for backwards compatibility.\n Only applicable to attestation-level policies.", + "items": { + "anyOf": [ + { + "enum": [ + "ATTESTATION_PHASE_UNSPECIFIED", + "INIT", + "PUSH" + ], + "title": "Attestation Phase", + "type": "string" + }, + { + "maximum": 2147483647, + "minimum": -2147483648, + "type": "integer" + } + ] + }, + "type": "array" + } + }, "properties": { + "attestationPhases": { + "description": "Controls at which attestation phases this policy is evaluated.\n Empty means evaluate at all phases (INIT and PUSH) for backwards compatibility.\n Only applicable to attestation-level policies.", + "items": { + "anyOf": [ + { + "enum": [ + "ATTESTATION_PHASE_UNSPECIFIED", + "INIT", + "PUSH" + ], + "title": "Attestation Phase", + "type": "string" + }, + { + "maximum": 2147483647, + "minimum": -2147483648, + "type": "integer" + } + ] + }, + "type": "array" + }, "disabled": { "description": "set to true to disable this rule", "type": "boolean" diff --git a/app/controlplane/api/gen/jsonschema/workflowcontract.v1.PolicyAttachment.schema.json b/app/controlplane/api/gen/jsonschema/workflowcontract.v1.PolicyAttachment.schema.json index a7764cefe..001386e0b 100644 --- a/app/controlplane/api/gen/jsonschema/workflowcontract.v1.PolicyAttachment.schema.json +++ b/app/controlplane/api/gen/jsonschema/workflowcontract.v1.PolicyAttachment.schema.json @@ -3,7 +3,53 @@ "$schema": "https://json-schema.org/draft/2020-12/schema", "additionalProperties": false, "description": "A policy to be applied to this contract", + "patternProperties": { + "^(attestationPhases)$": { + "description": "Controls at which attestation phases this policy is evaluated.\n Empty means evaluate at all phases (INIT and PUSH) for backwards compatibility.\n Only applicable to attestation-level policies.", + "items": { + "anyOf": [ + { + "enum": [ + "ATTESTATION_PHASE_UNSPECIFIED", + "INIT", + "PUSH" + ], + "title": "Attestation Phase", + "type": "string" + }, + { + "maximum": 2147483647, + "minimum": -2147483648, + "type": "integer" + } + ] + }, + "type": "array" + } + }, "properties": { + "attestation_phases": { + "description": "Controls at which attestation phases this policy is evaluated.\n Empty means evaluate at all phases (INIT and PUSH) for backwards compatibility.\n Only applicable to attestation-level policies.", + "items": { + "anyOf": [ + { + "enum": [ + "ATTESTATION_PHASE_UNSPECIFIED", + "INIT", + "PUSH" + ], + "title": "Attestation Phase", + "type": "string" + }, + { + "maximum": 2147483647, + "minimum": -2147483648, + "type": "integer" + } + ] + }, + "type": "array" + }, "disabled": { "description": "set to true to disable this rule", "type": "boolean" diff --git a/app/controlplane/api/gen/jsonschema/workflowcontract.v1.PolicySpecV2.jsonschema.json b/app/controlplane/api/gen/jsonschema/workflowcontract.v1.PolicySpecV2.jsonschema.json index 7749248d9..e5cc93bac 100644 --- a/app/controlplane/api/gen/jsonschema/workflowcontract.v1.PolicySpecV2.jsonschema.json +++ b/app/controlplane/api/gen/jsonschema/workflowcontract.v1.PolicySpecV2.jsonschema.json @@ -4,7 +4,7 @@ "additionalProperties": false, "patternProperties": { "^(attestation_phases)$": { - "description": "Controls at which attestation phases this policy is evaluated.\n Empty means evaluate at all phases (INIT and PUSH) for backwards compatibility.\n Only applicable when kind is ATTESTATION.", + "description": "Deprecated: attestation_phases has been moved to PolicyAttachment.", "items": { "anyOf": [ { @@ -28,7 +28,7 @@ }, "properties": { "attestationPhases": { - "description": "Controls at which attestation phases this policy is evaluated.\n Empty means evaluate at all phases (INIT and PUSH) for backwards compatibility.\n Only applicable when kind is ATTESTATION.", + "description": "Deprecated: attestation_phases has been moved to PolicyAttachment.", "items": { "anyOf": [ { diff --git a/app/controlplane/api/gen/jsonschema/workflowcontract.v1.PolicySpecV2.schema.json b/app/controlplane/api/gen/jsonschema/workflowcontract.v1.PolicySpecV2.schema.json index f1d5f5fa2..d743f77bf 100644 --- a/app/controlplane/api/gen/jsonschema/workflowcontract.v1.PolicySpecV2.schema.json +++ b/app/controlplane/api/gen/jsonschema/workflowcontract.v1.PolicySpecV2.schema.json @@ -4,7 +4,7 @@ "additionalProperties": false, "patternProperties": { "^(attestationPhases)$": { - "description": "Controls at which attestation phases this policy is evaluated.\n Empty means evaluate at all phases (INIT and PUSH) for backwards compatibility.\n Only applicable when kind is ATTESTATION.", + "description": "Deprecated: attestation_phases has been moved to PolicyAttachment.", "items": { "anyOf": [ { @@ -28,7 +28,7 @@ }, "properties": { "attestation_phases": { - "description": "Controls at which attestation phases this policy is evaluated.\n Empty means evaluate at all phases (INIT and PUSH) for backwards compatibility.\n Only applicable when kind is ATTESTATION.", + "description": "Deprecated: attestation_phases has been moved to PolicyAttachment.", "items": { "anyOf": [ { diff --git a/app/controlplane/api/workflowcontract/v1/crafting_schema.pb.go b/app/controlplane/api/workflowcontract/v1/crafting_schema.pb.go index 06cfb540b..4228063bc 100644 --- a/app/controlplane/api/workflowcontract/v1/crafting_schema.pb.go +++ b/app/controlplane/api/workflowcontract/v1/crafting_schema.pb.go @@ -717,9 +717,13 @@ type PolicyAttachment struct { // - true: policy violations are blocking for this policy // - false: policy violations are non-blocking for this policy // - unset: inherit organization-level default behavior - Gate *bool `protobuf:"varint,7,opt,name=gate,proto3,oneof" json:"gate,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Gate *bool `protobuf:"varint,7,opt,name=gate,proto3,oneof" json:"gate,omitempty"` + // Controls at which attestation phases this policy is evaluated. + // Empty means evaluate at all phases (INIT and PUSH) for backwards compatibility. + // Only applicable to attestation-level policies. + AttestationPhases []AttestationPhase `protobuf:"varint,8,rep,packed,name=attestation_phases,json=attestationPhases,proto3,enum=workflowcontract.v1.AttestationPhase" json:"attestation_phases,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *PolicyAttachment) Reset() { @@ -812,6 +816,13 @@ func (x *PolicyAttachment) GetGate() bool { return false } +func (x *PolicyAttachment) GetAttestationPhases() []AttestationPhase { + if x != nil { + return x.AttestationPhases + } + return nil +} + type isPolicyAttachment_Policy interface { isPolicyAttachment_Policy() } @@ -1175,9 +1186,9 @@ type PolicySpecV2 struct { Source isPolicySpecV2_Source `protobuf_oneof:"source"` // if set, it will match any material supported by Chainloop Kind CraftingSchema_Material_MaterialType `protobuf:"varint,3,opt,name=kind,proto3,enum=workflowcontract.v1.CraftingSchema_Material_MaterialType" json:"kind,omitempty"` - // Controls at which attestation phases this policy is evaluated. - // Empty means evaluate at all phases (INIT and PUSH) for backwards compatibility. - // Only applicable when kind is ATTESTATION. + // Deprecated: attestation_phases has been moved to PolicyAttachment. + // + // Deprecated: Marked as deprecated in workflowcontract/v1/crafting_schema.proto. AttestationPhases []AttestationPhase `protobuf:"varint,5,rep,packed,name=attestation_phases,json=attestationPhases,proto3,enum=workflowcontract.v1.AttestationPhase" json:"attestation_phases,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache @@ -1255,6 +1266,7 @@ func (x *PolicySpecV2) GetKind() CraftingSchema_Material_MaterialType { return CraftingSchema_Material_MATERIAL_TYPE_UNSPECIFIED } +// Deprecated: Marked as deprecated in workflowcontract/v1/crafting_schema.proto. func (x *PolicySpecV2) GetAttestationPhases() []AttestationPhase { if x != nil { return x.AttestationPhases @@ -1990,7 +2002,7 @@ const file_workflowcontract_v1_crafting_schema_proto_rawDesc = "" + "\x05value\x18\x02 \x01(\tR\x05value\"\x98\x01\n" + "\bPolicies\x12C\n" + "\tmaterials\x18\x01 \x03(\v2%.workflowcontract.v1.PolicyAttachmentR\tmaterials\x12G\n" + - "\vattestation\x18\x02 \x03(\v2%.workflowcontract.v1.PolicyAttachmentR\vattestation\"\x98\x04\n" + + "\vattestation\x18\x02 \x03(\v2%.workflowcontract.v1.PolicyAttachmentR\vattestation\"\xee\x04\n" + "\x10PolicyAttachment\x12\x1b\n" + "\x03ref\x18\x01 \x01(\tB\a\xbaH\x04r\x02\x10\x01H\x00R\x03ref\x129\n" + "\bembedded\x18\x02 \x01(\v2\x1b.workflowcontract.v1.PolicyH\x00R\bembedded\x12R\n" + @@ -1998,7 +2010,8 @@ const file_workflowcontract_v1_crafting_schema_proto_rawDesc = "" + "\bdisabled\x18\x04 \x01(\bR\bdisabled\x12C\n" + "\x04with\x18\x05 \x03(\v2/.workflowcontract.v1.PolicyAttachment.WithEntryR\x04with\x12c\n" + "\frequirements\x18\x06 \x03(\tB?\xbaH<\x92\x019\"7r523^([a-z0-9-]+\\/)?([^\\s\\/]+\\/)([^\\s@\\/]+)(@[^\\s@]+)?$R\frequirements\x12\x17\n" + - "\x04gate\x18\a \x01(\bH\x01R\x04gate\x88\x01\x01\x1a7\n" + + "\x04gate\x18\a \x01(\bH\x01R\x04gate\x88\x01\x01\x12T\n" + + "\x12attestation_phases\x18\b \x03(\x0e2%.workflowcontract.v1.AttestationPhaseR\x11attestationPhases\x1a7\n" + "\tWithEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\x1a&\n" + @@ -2042,13 +2055,13 @@ const file_workflowcontract_v1_crafting_schema_proto_rawDesc = "" + "\x14name.go_map_variable\x12:must contain only lowercase letters, numbers, and hyphens.\x1a'this.matches('^[a-zA-Z][a-zA-Z0-9_]*$')R\x04name\x12 \n" + "\vdescription\x18\x02 \x01(\tR\vdescription\x12\x1a\n" + "\brequired\x18\x03 \x01(\bR\brequired\x12\x18\n" + - "\adefault\x18\x04 \x01(\tR\adefault\"\x9a\x02\n" + + "\adefault\x18\x04 \x01(\tR\adefault\"\x9e\x02\n" + "\fPolicySpecV2\x12\x18\n" + "\x04path\x18\x01 \x01(\tB\x02\x18\x01H\x00R\x04path\x12\x1c\n" + "\bembedded\x18\x02 \x01(\tH\x00R\bembedded\x12\x12\n" + "\x03ref\x18\x04 \x01(\tH\x00R\x03ref\x12W\n" + - "\x04kind\x18\x03 \x01(\x0e29.workflowcontract.v1.CraftingSchema.Material.MaterialTypeB\b\xbaH\x05\x82\x01\x02 \x03R\x04kind\x12T\n" + - "\x12attestation_phases\x18\x05 \x03(\x0e2%.workflowcontract.v1.AttestationPhaseR\x11attestationPhasesB\x0f\n" + + "\x04kind\x18\x03 \x01(\x0e29.workflowcontract.v1.CraftingSchema.Material.MaterialTypeB\b\xbaH\x05\x82\x01\x02 \x03R\x04kind\x12X\n" + + "\x12attestation_phases\x18\x05 \x03(\x0e2%.workflowcontract.v1.AttestationPhaseB\x02\x18\x01R\x11attestationPhasesB\x0f\n" + "\x06source\x12\x05\xbaH\x02\b\x01\"h\n" + "\tAutoMatch\x12\x18\n" + "\x04path\x18\x01 \x01(\tB\x02\x18\x01H\x00R\x04path\x12\x1c\n" + @@ -2147,32 +2160,33 @@ var file_workflowcontract_v1_crafting_schema_proto_depIdxs = []int32{ 9, // 14: workflowcontract.v1.PolicyAttachment.embedded:type_name -> workflowcontract.v1.Policy 20, // 15: workflowcontract.v1.PolicyAttachment.selector:type_name -> workflowcontract.v1.PolicyAttachment.MaterialSelector 19, // 16: workflowcontract.v1.PolicyAttachment.with:type_name -> workflowcontract.v1.PolicyAttachment.WithEntry - 10, // 17: workflowcontract.v1.Policy.metadata:type_name -> workflowcontract.v1.Metadata - 11, // 18: workflowcontract.v1.Policy.spec:type_name -> workflowcontract.v1.PolicySpec - 21, // 19: workflowcontract.v1.Metadata.annotations:type_name -> workflowcontract.v1.Metadata.AnnotationsEntry - 2, // 20: workflowcontract.v1.PolicySpec.type:type_name -> workflowcontract.v1.CraftingSchema.Material.MaterialType - 13, // 21: workflowcontract.v1.PolicySpec.policies:type_name -> workflowcontract.v1.PolicySpecV2 - 12, // 22: workflowcontract.v1.PolicySpec.inputs:type_name -> workflowcontract.v1.PolicyInput - 14, // 23: workflowcontract.v1.PolicySpec.auto_match:type_name -> workflowcontract.v1.AutoMatch - 2, // 24: workflowcontract.v1.PolicySpecV2.kind:type_name -> workflowcontract.v1.CraftingSchema.Material.MaterialType - 0, // 25: workflowcontract.v1.PolicySpecV2.attestation_phases:type_name -> workflowcontract.v1.AttestationPhase - 22, // 26: workflowcontract.v1.PolicyGroupAttachment.with:type_name -> workflowcontract.v1.PolicyGroupAttachment.WithEntry - 10, // 27: workflowcontract.v1.PolicyGroup.metadata:type_name -> workflowcontract.v1.Metadata - 23, // 28: workflowcontract.v1.PolicyGroup.spec:type_name -> workflowcontract.v1.PolicyGroup.PolicyGroupSpec - 1, // 29: workflowcontract.v1.CraftingSchema.Runner.type:type_name -> workflowcontract.v1.CraftingSchema.Runner.RunnerType - 2, // 30: workflowcontract.v1.CraftingSchema.Material.type:type_name -> workflowcontract.v1.CraftingSchema.Material.MaterialType - 6, // 31: workflowcontract.v1.CraftingSchema.Material.annotations:type_name -> workflowcontract.v1.Annotation - 24, // 32: workflowcontract.v1.PolicyGroup.PolicyGroupSpec.policies:type_name -> workflowcontract.v1.PolicyGroup.PolicyGroupPolicies - 12, // 33: workflowcontract.v1.PolicyGroup.PolicyGroupSpec.inputs:type_name -> workflowcontract.v1.PolicyInput - 25, // 34: workflowcontract.v1.PolicyGroup.PolicyGroupPolicies.materials:type_name -> workflowcontract.v1.PolicyGroup.Material - 8, // 35: workflowcontract.v1.PolicyGroup.PolicyGroupPolicies.attestation:type_name -> workflowcontract.v1.PolicyAttachment - 2, // 36: workflowcontract.v1.PolicyGroup.Material.type:type_name -> workflowcontract.v1.CraftingSchema.Material.MaterialType - 8, // 37: workflowcontract.v1.PolicyGroup.Material.policies:type_name -> workflowcontract.v1.PolicyAttachment - 38, // [38:38] is the sub-list for method output_type - 38, // [38:38] is the sub-list for method input_type - 38, // [38:38] is the sub-list for extension type_name - 38, // [38:38] is the sub-list for extension extendee - 0, // [0:38] is the sub-list for field type_name + 0, // 17: workflowcontract.v1.PolicyAttachment.attestation_phases:type_name -> workflowcontract.v1.AttestationPhase + 10, // 18: workflowcontract.v1.Policy.metadata:type_name -> workflowcontract.v1.Metadata + 11, // 19: workflowcontract.v1.Policy.spec:type_name -> workflowcontract.v1.PolicySpec + 21, // 20: workflowcontract.v1.Metadata.annotations:type_name -> workflowcontract.v1.Metadata.AnnotationsEntry + 2, // 21: workflowcontract.v1.PolicySpec.type:type_name -> workflowcontract.v1.CraftingSchema.Material.MaterialType + 13, // 22: workflowcontract.v1.PolicySpec.policies:type_name -> workflowcontract.v1.PolicySpecV2 + 12, // 23: workflowcontract.v1.PolicySpec.inputs:type_name -> workflowcontract.v1.PolicyInput + 14, // 24: workflowcontract.v1.PolicySpec.auto_match:type_name -> workflowcontract.v1.AutoMatch + 2, // 25: workflowcontract.v1.PolicySpecV2.kind:type_name -> workflowcontract.v1.CraftingSchema.Material.MaterialType + 0, // 26: workflowcontract.v1.PolicySpecV2.attestation_phases:type_name -> workflowcontract.v1.AttestationPhase + 22, // 27: workflowcontract.v1.PolicyGroupAttachment.with:type_name -> workflowcontract.v1.PolicyGroupAttachment.WithEntry + 10, // 28: workflowcontract.v1.PolicyGroup.metadata:type_name -> workflowcontract.v1.Metadata + 23, // 29: workflowcontract.v1.PolicyGroup.spec:type_name -> workflowcontract.v1.PolicyGroup.PolicyGroupSpec + 1, // 30: workflowcontract.v1.CraftingSchema.Runner.type:type_name -> workflowcontract.v1.CraftingSchema.Runner.RunnerType + 2, // 31: workflowcontract.v1.CraftingSchema.Material.type:type_name -> workflowcontract.v1.CraftingSchema.Material.MaterialType + 6, // 32: workflowcontract.v1.CraftingSchema.Material.annotations:type_name -> workflowcontract.v1.Annotation + 24, // 33: workflowcontract.v1.PolicyGroup.PolicyGroupSpec.policies:type_name -> workflowcontract.v1.PolicyGroup.PolicyGroupPolicies + 12, // 34: workflowcontract.v1.PolicyGroup.PolicyGroupSpec.inputs:type_name -> workflowcontract.v1.PolicyInput + 25, // 35: workflowcontract.v1.PolicyGroup.PolicyGroupPolicies.materials:type_name -> workflowcontract.v1.PolicyGroup.Material + 8, // 36: workflowcontract.v1.PolicyGroup.PolicyGroupPolicies.attestation:type_name -> workflowcontract.v1.PolicyAttachment + 2, // 37: workflowcontract.v1.PolicyGroup.Material.type:type_name -> workflowcontract.v1.CraftingSchema.Material.MaterialType + 8, // 38: workflowcontract.v1.PolicyGroup.Material.policies:type_name -> workflowcontract.v1.PolicyAttachment + 39, // [39:39] is the sub-list for method output_type + 39, // [39:39] is the sub-list for method input_type + 39, // [39:39] is the sub-list for extension type_name + 39, // [39:39] is the sub-list for extension extendee + 0, // [0:39] is the sub-list for field type_name } func init() { file_workflowcontract_v1_crafting_schema_proto_init() } diff --git a/app/controlplane/api/workflowcontract/v1/crafting_schema.proto b/app/controlplane/api/workflowcontract/v1/crafting_schema.proto index 6e2e1f25e..968018503 100644 --- a/app/controlplane/api/workflowcontract/v1/crafting_schema.proto +++ b/app/controlplane/api/workflowcontract/v1/crafting_schema.proto @@ -242,6 +242,11 @@ message PolicyAttachment { // - unset: inherit organization-level default behavior optional bool gate = 7; + // Controls at which attestation phases this policy is evaluated. + // Empty means evaluate at all phases (INIT and PUSH) for backwards compatibility. + // Only applicable to attestation-level policies. + repeated AttestationPhase attestation_phases = 8; + message MaterialSelector { // material name string name = 1; @@ -351,10 +356,8 @@ message PolicySpecV2 { not_in: [3] }]; - // Controls at which attestation phases this policy is evaluated. - // Empty means evaluate at all phases (INIT and PUSH) for backwards compatibility. - // Only applicable when kind is ATTESTATION. - repeated AttestationPhase attestation_phases = 5; + // Deprecated: attestation_phases has been moved to PolicyAttachment. + repeated AttestationPhase attestation_phases = 5 [deprecated = true]; } // Auto-matching policy specification diff --git a/pkg/policies/policies.go b/pkg/policies/policies.go index 486cf58ee..df2e481bf 100644 --- a/pkg/policies/policies.go +++ b/pkg/policies/policies.go @@ -194,6 +194,23 @@ type evalOpts struct { bindings map[string]string } +// Deprecated: attestationPhasesFromSpec extracts attestation phases from the PolicySpecV2 +// entries. This is used as a fallback when the attachment doesn't specify phases. +func attestationPhasesFromSpec(spec *v1.PolicySpec, kind v1.CraftingSchema_Material_MaterialType) []v1.AttestationPhase { + if spec.GetSource() != nil { + return nil + } + + var phases []v1.AttestationPhase + for _, s := range spec.GetPolicies() { + if s.GetKind() == v1.CraftingSchema_Material_MATERIAL_TYPE_UNSPECIFIED || s.GetKind() == kind { + phases = append(phases, s.GetAttestationPhases()...) //nolint:staticcheck // deprecated field used as fallback + } + } + + return phases +} + // shouldEvaluateAtPhase checks if a policy should be evaluated at the given phase. // If no phases are specified, the policy runs at all phases (backwards compatible). // If phase is EvalPhaseUnspecified (e.g. material evaluation), the policy always runs. @@ -215,25 +232,6 @@ func shouldEvaluateAtPhase(phases []v1.AttestationPhase, phase EvalPhase) bool { return slices.Contains(phases, target) } -// attestationPhasesForKind returns the aggregated attestation phases from -// PolicySpecV2 entries that match the given kind. For legacy policies (using -// spec.type + spec.path), returns nil (evaluate at all phases). -func attestationPhasesForKind(spec *v1.PolicySpec, kind v1.CraftingSchema_Material_MaterialType) []v1.AttestationPhase { - // Legacy single-kind policies don't have per-kind phase config - if spec.GetSource() != nil { - return nil - } - - var phases []v1.AttestationPhase - for _, s := range spec.GetPolicies() { - if s.GetKind() == v1.CraftingSchema_Material_MATERIAL_TYPE_UNSPECIFIED || s.GetKind() == kind { - phases = append(phases, s.GetAttestationPhases()...) - } - } - - return phases -} - func (pv *PolicyVerifier) evaluatePolicyAttachment(ctx context.Context, attachment *v1.PolicyAttachment, material []byte, opts *evalOpts) (*v12.PolicyEvaluation, error) { // load the policy policy policy, ref, err := pv.loadPolicySpec(ctx, attachment) @@ -242,8 +240,11 @@ func (pv *PolicyVerifier) evaluatePolicyAttachment(ctx context.Context, attachme } // Skip policies not configured for the current attestation phase. - // Phases are defined per-kind in PolicySpecV2 entries. - phases := attestationPhasesForKind(policy.GetSpec(), opts.kind) + // Phases on the attachment take precedence; fall back to the policy spec level. + phases := attachment.GetAttestationPhases() + if len(phases) == 0 { + phases = attestationPhasesFromSpec(policy.GetSpec(), opts.kind) + } if !shouldEvaluateAtPhase(phases, pv.evalPhase) { pv.logger.Debug().Str("policy", policy.GetMetadata().GetName()).Msg("skipping policy not configured for current attestation phase") return nil, nil diff --git a/pkg/policies/policies_test.go b/pkg/policies/policies_test.go index b0b8a046a..aed575a4b 100644 --- a/pkg/policies/policies_test.go +++ b/pkg/policies/policies_test.go @@ -1252,10 +1252,11 @@ func (s *testSuite) TestMultiKindAWithIgnore() { func (s *testSuite) TestAttestationPhaseFiltering() { cases := []struct { - name string - policyRef string - phase EvalPhase - npolicies int + name string + policyRef string + attachmentPhases []v12.AttestationPhase + phase EvalPhase + npolicies int }{ { name: "no phases specified, runs at push", @@ -1270,17 +1271,54 @@ func (s *testSuite) TestAttestationPhaseFiltering() { npolicies: 1, }, { - name: "push-only policy, runs at push", + name: "push-only attachment, runs at push", + policyRef: "file://testdata/workflow.yaml", + attachmentPhases: []v12.AttestationPhase{v12.AttestationPhase_PUSH}, + phase: EvalPhasePush, + npolicies: 1, + }, + { + name: "push-only attachment, skipped at init", + policyRef: "file://testdata/workflow.yaml", + attachmentPhases: []v12.AttestationPhase{v12.AttestationPhase_PUSH}, + phase: EvalPhaseInit, + npolicies: 0, + }, + { + name: "init-only attachment, runs at init", + policyRef: "file://testdata/workflow.yaml", + attachmentPhases: []v12.AttestationPhase{v12.AttestationPhase_INIT}, + phase: EvalPhaseInit, + npolicies: 1, + }, + { + name: "init-only attachment, skipped at push", + policyRef: "file://testdata/workflow.yaml", + attachmentPhases: []v12.AttestationPhase{v12.AttestationPhase_INIT}, + phase: EvalPhasePush, + npolicies: 0, + }, + // Fallback: phases from deprecated PolicySpecV2 field + { + name: "spec-level push-only fallback, runs at push", policyRef: "file://testdata/workflow_push_only.yaml", phase: EvalPhasePush, npolicies: 1, }, { - name: "push-only policy, skipped at init", + name: "spec-level push-only fallback, skipped at init", policyRef: "file://testdata/workflow_push_only.yaml", phase: EvalPhaseInit, npolicies: 0, }, + // Attachment phases take precedence over spec-level phases + { + name: "attachment overrides spec-level phases", + policyRef: "file://testdata/workflow_push_only.yaml", + attachmentPhases: []v12.AttestationPhase{v12.AttestationPhase_INIT}, + phase: EvalPhaseInit, + npolicies: 1, + }, } for _, tc := range cases { @@ -1288,7 +1326,10 @@ func (s *testSuite) TestAttestationPhaseFiltering() { schema := &v12.CraftingSchema{ Policies: &v12.Policies{ Attestation: []*v12.PolicyAttachment{ - {Policy: &v12.PolicyAttachment_Ref{Ref: tc.policyRef}}, + { + Policy: &v12.PolicyAttachment_Ref{Ref: tc.policyRef}, + AttestationPhases: tc.attachmentPhases, + }, }, }, } diff --git a/pkg/policies/testdata/policy_group_push_only.yaml b/pkg/policies/testdata/policy_group_push_only.yaml index 10462947a..dd169b856 100644 --- a/pkg/policies/testdata/policy_group_push_only.yaml +++ b/pkg/policies/testdata/policy_group_push_only.yaml @@ -7,3 +7,5 @@ spec: policies: attestation: - ref: file://testdata/workflow_push_only.yaml + attestation_phases: + - PUSH