Skip to content

Commit 5d5ddaa

Browse files
committed
add-material-kind-policy-validation
Signed-off-by: Sylwester Piskozub <sylwesterpiskozub@gmail.com>
1 parent f9c6b26 commit 5d5ddaa

2 files changed

Lines changed: 130 additions & 2 deletions

File tree

app/controlplane/pkg/biz/workflowcontract.go

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -394,9 +394,16 @@ func (uc *WorkflowContractUseCase) ValidateContractPolicies(rawSchema []byte, to
394394
}
395395
}
396396
for _, att := range c.Schema.GetPolicies().GetMaterials() {
397-
if _, err := uc.findAndValidatePolicy(att, token); err != nil {
397+
policy, err := uc.findAndValidatePolicy(att, token)
398+
if err != nil {
398399
return NewErrValidation(err)
399400
}
401+
// Validate that policies attached to materials do not have kind ATTESTATION
402+
if policy != nil {
403+
if err := validatePolicyIsNotAttestationKind(policy); err != nil {
404+
return NewErrValidation(err)
405+
}
406+
}
400407
}
401408
for _, gatt := range c.Schema.GetPolicyGroups() {
402409
if _, err := uc.findPolicyGroup(gatt, token); err != nil {
@@ -640,6 +647,25 @@ func SchemaToRawContract(contract *schemav1.CraftingSchema) (*Contract, error) {
640647
return &Contract{Raw: r, Format: unmarshal.RawFormatJSON, Schema: contract}, nil
641648
}
642649

650+
// validatePolicyIsNotAttestationKind validates that a policy does not have kind ATTESTATION.
651+
// Policies with kind ATTESTATION should only be attached at the attestation level, not to individual materials.
652+
func validatePolicyIsNotAttestationKind(policy *schemav1.Policy) error {
653+
policies := policy.GetSpec().GetPolicies()
654+
if len(policies) == 0 {
655+
// Legacy format or no policies defined
656+
return nil
657+
}
658+
659+
// Check if any policy has kind ATTESTATION - this is not allowed for material-level policies
660+
for _, policySpec := range policies {
661+
if policySpec.GetKind() == schemav1.CraftingSchema_Material_ATTESTATION {
662+
return fmt.Errorf("attestation policy %q cannot be attached to materials", policy.GetMetadata().GetName())
663+
}
664+
}
665+
666+
return nil
667+
}
668+
643669
// ContractScope represents a polymorphic relationship between a contract and a project or organization
644670
type ContractScope string
645671

app/controlplane/pkg/biz/workflowcontract_test.go

Lines changed: 103 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//
2-
// Copyright 2024 The Chainloop Authors.
2+
// Copyright 2024-2025 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.
@@ -19,6 +19,7 @@ import (
1919
"os"
2020
"testing"
2121

22+
schemav1 "github.com/chainloop-dev/chainloop/app/controlplane/api/workflowcontract/v1"
2223
"github.com/chainloop-dev/chainloop/app/controlplane/pkg/unmarshal"
2324
"github.com/stretchr/testify/assert"
2425
"github.com/stretchr/testify/require"
@@ -81,3 +82,104 @@ func TestIdentifyAndValidateRawContract(t *testing.T) {
8182
})
8283
}
8384
}
85+
86+
func TestValidatePolicyIsNotAttestationKind(t *testing.T) {
87+
testCases := []struct {
88+
name string
89+
policy *schemav1.Policy
90+
wantError bool
91+
errMsg string
92+
}{
93+
{
94+
name: "valid material-level policy with kind SBOM_SPDX_JSON",
95+
policy: &schemav1.Policy{
96+
Metadata: &schemav1.Metadata{
97+
Name: "sbom-validation",
98+
},
99+
Spec: &schemav1.PolicySpec{
100+
Policies: []*schemav1.PolicySpecV2{
101+
{
102+
Kind: schemav1.CraftingSchema_Material_SBOM_SPDX_JSON,
103+
Source: &schemav1.PolicySpecV2_Embedded{
104+
Embedded: "package main\nresult := true",
105+
},
106+
},
107+
},
108+
},
109+
},
110+
wantError: false,
111+
},
112+
{
113+
name: "invalid policy with kind ATTESTATION in materials section",
114+
policy: &schemav1.Policy{
115+
Metadata: &schemav1.Metadata{
116+
Name: "attestation-policy",
117+
},
118+
Spec: &schemav1.PolicySpec{
119+
Policies: []*schemav1.PolicySpecV2{
120+
{
121+
Kind: schemav1.CraftingSchema_Material_ATTESTATION,
122+
Source: &schemav1.PolicySpecV2_Embedded{
123+
Embedded: "package main\nresult := true",
124+
},
125+
},
126+
},
127+
},
128+
},
129+
wantError: true,
130+
errMsg: "cannot be attached to materials",
131+
},
132+
{
133+
name: "invalid policy with multiple kinds including ATTESTATION",
134+
policy: &schemav1.Policy{
135+
Metadata: &schemav1.Metadata{
136+
Name: "multi-kind-policy",
137+
},
138+
Spec: &schemav1.PolicySpec{
139+
Policies: []*schemav1.PolicySpecV2{
140+
{
141+
Kind: schemav1.CraftingSchema_Material_SBOM_SPDX_JSON,
142+
Source: &schemav1.PolicySpecV2_Embedded{
143+
Embedded: "package main\nresult := true",
144+
},
145+
},
146+
{
147+
Kind: schemav1.CraftingSchema_Material_ATTESTATION,
148+
Source: &schemav1.PolicySpecV2_Embedded{
149+
Embedded: "package main\nresult := true",
150+
},
151+
},
152+
},
153+
},
154+
},
155+
wantError: true,
156+
errMsg: "cannot be attached to materials",
157+
},
158+
{
159+
name: "legacy policy with deprecated path field - should pass",
160+
policy: &schemav1.Policy{
161+
Metadata: &schemav1.Metadata{
162+
Name: "legacy-path-policy",
163+
},
164+
Spec: &schemav1.PolicySpec{
165+
Source: &schemav1.PolicySpec_Path{
166+
Path: "file://policy.rego",
167+
},
168+
},
169+
},
170+
wantError: false,
171+
},
172+
}
173+
174+
for _, tc := range testCases {
175+
t.Run(tc.name, func(t *testing.T) {
176+
err := validatePolicyIsNotAttestationKind(tc.policy)
177+
if tc.wantError {
178+
require.Error(t, err)
179+
assert.Contains(t, err.Error(), tc.errMsg)
180+
} else {
181+
require.NoError(t, err)
182+
}
183+
})
184+
}
185+
}

0 commit comments

Comments
 (0)