From efec34c11fb432d62928a71eb95ec0c1295251a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Insaurralde?= Date: Mon, 23 Feb 2026 15:56:34 -0300 Subject: [PATCH] test(cli): cover attestation push policy enforcement behavior MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- app/cli/cmd/attestation_push.go | 56 +++++++++++++--------- app/cli/cmd/attestation_push_test.go | 70 ++++++++++++++++++++++++++++ 2 files changed, 103 insertions(+), 23 deletions(-) create mode 100644 app/cli/cmd/attestation_push_test.go diff --git a/app/cli/cmd/attestation_push.go b/app/cli/cmd/attestation_push.go index d53d9cfe3..9b9972d8d 100644 --- a/app/cli/cmd/attestation_push.go +++ b/app/cli/cmd/attestation_push.go @@ -138,29 +138,8 @@ func newAttestationPushCmd() *cobra.Command { return fmt.Errorf("failed to render output: %w", err) } - // Block if any of the policies has been configured as a gate - for _, evaluations := range res.Status.PolicyEvaluations { - for _, eval := range evaluations { - if len(eval.Violations) > 0 && eval.Gate { - if bypassPolicyCheck { - logger.Warn().Msg(exceptionBypassPolicyCheck) - continue - } - return NewGateError(eval.Name) - } - } - } - - // Do a final check in case the operator has configured the attestation to be blocked on any policy violation - if res.Status.MustBlockOnPolicyViolations { - if bypassPolicyCheck { - logger.Warn().Msg(exceptionBypassPolicyCheck) - return nil - } - - if res.Status.HasPolicyViolations { - return ErrBlockedByPolicyViolation - } + if err := validatePolicyEnforcement(res.Status, bypassPolicyCheck); err != nil { + return err } return nil @@ -199,3 +178,34 @@ func NewGateError(policyName string) error { func (e *GateError) Error() string { return fmt.Sprintf("the policy %q is configured as a gate and has violations", e.PolicyName) } + +func validatePolicyEnforcement(status *action.AttestationStatusResult, bypassPolicyCheck bool) error { + // Block if any of the policies has been configured as a gate. + for _, evaluations := range status.PolicyEvaluations { + for _, eval := range evaluations { + if len(eval.Violations) > 0 && eval.Gate { + if bypassPolicyCheck { + logger.Warn().Msg(exceptionBypassPolicyCheck) + continue + } + + return NewGateError(eval.Name) + } + } + } + + // Do a final check in case the operator has configured the attestation + // to be blocked on any policy violation. + if status.MustBlockOnPolicyViolations { + if bypassPolicyCheck { + logger.Warn().Msg(exceptionBypassPolicyCheck) + return nil + } + + if status.HasPolicyViolations { + return ErrBlockedByPolicyViolation + } + } + + return nil +} diff --git a/app/cli/cmd/attestation_push_test.go b/app/cli/cmd/attestation_push_test.go new file mode 100644 index 000000000..101662a66 --- /dev/null +++ b/app/cli/cmd/attestation_push_test.go @@ -0,0 +1,70 @@ +// +// Copyright 2026 The Chainloop Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cmd + +import ( + "testing" + + "github.com/chainloop-dev/chainloop/app/cli/pkg/action" + "github.com/stretchr/testify/require" +) + +func TestValidatePolicyEnforcement(t *testing.T) { + t.Run("does not block when strategy is advisory and policy is not gated", func(t *testing.T) { + status := &action.AttestationStatusResult{ + PolicyEvaluations: map[string][]*action.PolicyEvaluation{ + "materials": { + { + Name: "cdx-fresh", + Gate: false, + Violations: []*action.PolicyViolation{ + {Message: "policy violation"}, + }, + }, + }, + }, + HasPolicyViolations: true, + MustBlockOnPolicyViolations: false, + } + + err := validatePolicyEnforcement(status, false) + require.NoError(t, err) + }) + + t.Run("returns gate error when strategy is advisory and policy is gated", func(t *testing.T) { + status := &action.AttestationStatusResult{ + PolicyEvaluations: map[string][]*action.PolicyEvaluation{ + "materials": { + { + Name: "cdx-fresh", + Gate: true, + Violations: []*action.PolicyViolation{ + {Message: "policy violation"}, + }, + }, + }, + }, + HasPolicyViolations: true, + MustBlockOnPolicyViolations: false, + } + + err := validatePolicyEnforcement(status, false) + require.Error(t, err) + var gateErr *GateError + require.ErrorAs(t, err, &gateErr) + require.Equal(t, "cdx-fresh", gateErr.PolicyName) + }) +}