Skip to content

Commit efec34c

Browse files
test(cli): cover attestation push policy enforcement behavior
Extract policy enforcement checks in attestation push into a helper and add unit subtests for advisory/non-gated and advisory/gated violation scenarios. Signed-off-by: Matías Insaurralde <matias@chainloop.dev>
1 parent 19100b5 commit efec34c

2 files changed

Lines changed: 103 additions & 23 deletions

File tree

app/cli/cmd/attestation_push.go

Lines changed: 33 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -138,29 +138,8 @@ func newAttestationPushCmd() *cobra.Command {
138138
return fmt.Errorf("failed to render output: %w", err)
139139
}
140140

141-
// Block if any of the policies has been configured as a gate
142-
for _, evaluations := range res.Status.PolicyEvaluations {
143-
for _, eval := range evaluations {
144-
if len(eval.Violations) > 0 && eval.Gate {
145-
if bypassPolicyCheck {
146-
logger.Warn().Msg(exceptionBypassPolicyCheck)
147-
continue
148-
}
149-
return NewGateError(eval.Name)
150-
}
151-
}
152-
}
153-
154-
// Do a final check in case the operator has configured the attestation to be blocked on any policy violation
155-
if res.Status.MustBlockOnPolicyViolations {
156-
if bypassPolicyCheck {
157-
logger.Warn().Msg(exceptionBypassPolicyCheck)
158-
return nil
159-
}
160-
161-
if res.Status.HasPolicyViolations {
162-
return ErrBlockedByPolicyViolation
163-
}
141+
if err := validatePolicyEnforcement(res.Status, bypassPolicyCheck); err != nil {
142+
return err
164143
}
165144

166145
return nil
@@ -199,3 +178,34 @@ func NewGateError(policyName string) error {
199178
func (e *GateError) Error() string {
200179
return fmt.Sprintf("the policy %q is configured as a gate and has violations", e.PolicyName)
201180
}
181+
182+
func validatePolicyEnforcement(status *action.AttestationStatusResult, bypassPolicyCheck bool) error {
183+
// Block if any of the policies has been configured as a gate.
184+
for _, evaluations := range status.PolicyEvaluations {
185+
for _, eval := range evaluations {
186+
if len(eval.Violations) > 0 && eval.Gate {
187+
if bypassPolicyCheck {
188+
logger.Warn().Msg(exceptionBypassPolicyCheck)
189+
continue
190+
}
191+
192+
return NewGateError(eval.Name)
193+
}
194+
}
195+
}
196+
197+
// Do a final check in case the operator has configured the attestation
198+
// to be blocked on any policy violation.
199+
if status.MustBlockOnPolicyViolations {
200+
if bypassPolicyCheck {
201+
logger.Warn().Msg(exceptionBypassPolicyCheck)
202+
return nil
203+
}
204+
205+
if status.HasPolicyViolations {
206+
return ErrBlockedByPolicyViolation
207+
}
208+
}
209+
210+
return nil
211+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
//
2+
// Copyright 2026 The Chainloop Authors.
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
16+
package cmd
17+
18+
import (
19+
"testing"
20+
21+
"github.com/chainloop-dev/chainloop/app/cli/pkg/action"
22+
"github.com/stretchr/testify/require"
23+
)
24+
25+
func TestValidatePolicyEnforcement(t *testing.T) {
26+
t.Run("does not block when strategy is advisory and policy is not gated", func(t *testing.T) {
27+
status := &action.AttestationStatusResult{
28+
PolicyEvaluations: map[string][]*action.PolicyEvaluation{
29+
"materials": {
30+
{
31+
Name: "cdx-fresh",
32+
Gate: false,
33+
Violations: []*action.PolicyViolation{
34+
{Message: "policy violation"},
35+
},
36+
},
37+
},
38+
},
39+
HasPolicyViolations: true,
40+
MustBlockOnPolicyViolations: false,
41+
}
42+
43+
err := validatePolicyEnforcement(status, false)
44+
require.NoError(t, err)
45+
})
46+
47+
t.Run("returns gate error when strategy is advisory and policy is gated", func(t *testing.T) {
48+
status := &action.AttestationStatusResult{
49+
PolicyEvaluations: map[string][]*action.PolicyEvaluation{
50+
"materials": {
51+
{
52+
Name: "cdx-fresh",
53+
Gate: true,
54+
Violations: []*action.PolicyViolation{
55+
{Message: "policy violation"},
56+
},
57+
},
58+
},
59+
},
60+
HasPolicyViolations: true,
61+
MustBlockOnPolicyViolations: false,
62+
}
63+
64+
err := validatePolicyEnforcement(status, false)
65+
require.Error(t, err)
66+
var gateErr *GateError
67+
require.ErrorAs(t, err, &gateErr)
68+
require.Equal(t, "cdx-fresh", gateErr.PolicyName)
69+
})
70+
}

0 commit comments

Comments
 (0)