Skip to content

Commit c163bf7

Browse files
authored
feat(policies): gated policy attachments (#2622)
Signed-off-by: Jose I. Paris <[email protected]>
1 parent d08a596 commit c163bf7

17 files changed

+445
-335
lines changed

app/cli/cmd/attestation_push.go

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,8 +122,20 @@ func newAttestationPushCmd() *cobra.Command {
122122
return fmt.Errorf("failed to render output: %w", err)
123123
}
124124

125-
// We do a final check to see if the attestation has policy violations
126-
// and fail the command if needed
125+
// Block if any of the policies has been configured as a gate
126+
for _, evaluations := range res.Status.PolicyEvaluations {
127+
for _, eval := range evaluations {
128+
if len(eval.Violations) > 0 && eval.Gate {
129+
if bypassPolicyCheck {
130+
logger.Warn().Msg(exceptionBypassPolicyCheck)
131+
continue
132+
}
133+
return NewGateError(eval.Name)
134+
}
135+
}
136+
}
137+
138+
// Do a final check in case the operator has configured the attestation to be blocked on any policy violation
127139
if res.Status.MustBlockOnPolicyViolations {
128140
if bypassPolicyCheck {
129141
logger.Warn().Msg(exceptionBypassPolicyCheck)
@@ -158,3 +170,15 @@ var (
158170
ErrBlockedByPolicyViolation = fmt.Errorf("the operator requires all policies to pass before continuing, please fix them and try again or temporarily bypass the policy check using --%s", exceptionFlagName)
159171
exceptionBypassPolicyCheck = fmt.Sprintf("Attention: You have opted to bypass the policy enforcement check and an operator has been notified of this exception.\nPlease make sure you are back on track with the policy evaluations and remove the --%s as soon as possible.", exceptionFlagName)
160172
)
173+
174+
type GateError struct {
175+
PolicyName string
176+
}
177+
178+
func NewGateError(policyName string) error {
179+
return &GateError{PolicyName: policyName}
180+
}
181+
182+
func (e *GateError) Error() string {
183+
return fmt.Sprintf("the policy %q is configured as a gate and has violations", e.PolicyName)
184+
}

app/cli/cmd/workflow_workflow_run_describe.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,11 @@ func policiesTable(evs []*action.PolicyEvaluation, mt table.Writer) {
268268
msg = strings.Join(violations, prefix)
269269
}
270270

271-
mt.AppendRow(table.Row{"", fmt.Sprintf("%s: %s", ev.Name, msg)})
271+
name := ev.Name
272+
if ev.Gate {
273+
name = fmt.Sprintf("%s (gate)", ev.Name)
274+
}
275+
mt.AppendRow(table.Row{"", fmt.Sprintf("%s: %s", name, msg)})
272276
}
273277
}
274278

app/cli/main.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ func errorInfo(err error, logger zerolog.Logger) (string, int) {
7878
}
7979
}
8080

81+
var gateErr *cmd.GateError
82+
8183
// Make overrides
8284
switch {
8385
case v1.IsCasBackendErrorReasonRequired(err):
@@ -101,6 +103,9 @@ func errorInfo(err error, logger zerolog.Logger) (string, int) {
101103
case errors.Is(err, cmd.ErrBlockedByPolicyViolation):
102104
// default exit code for policy violations
103105
exitCode = 3
106+
case errors.As(err, &gateErr):
107+
// exit code for gate errors
108+
exitCode = 4
104109
}
105110

106111
return msg, exitCode

app/cli/pkg/action/attestation_add.go

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -173,11 +173,7 @@ func (action *AttestationAdd) GetPolicyEvaluations(ctx context.Context, attestat
173173
return nil, err
174174
}
175175

176-
policyEvaluations, _, err := getPolicyEvaluations(crafter)
177-
178-
if err != nil {
179-
return nil, fmt.Errorf("getting policy evaluations: %w", err)
180-
}
176+
policyEvaluations, _ := getPolicyEvaluations(crafter)
181177

182178
return policyEvaluations, nil
183179
}

app/cli/pkg/action/attestation_status.go

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -158,10 +158,7 @@ func (action *AttestationStatus) Run(ctx context.Context, attestationID string,
158158
return nil, fmt.Errorf("evaluating attestation policies: %w", err)
159159
}
160160

161-
res.PolicyEvaluations, res.HasPolicyViolations, err = getPolicyEvaluations(c)
162-
if err != nil {
163-
return nil, fmt.Errorf("getting policy evaluations: %w", err)
164-
}
161+
res.PolicyEvaluations, res.HasPolicyViolations = getPolicyEvaluations(c)
165162
}
166163

167164
if v := workflowMeta.GetVersion(); v != nil {
@@ -211,7 +208,7 @@ func (action *AttestationStatus) Run(ctx context.Context, attestationID string,
211208
}
212209

213210
// getPolicyEvaluations retrieves both material-level and attestation-level policy evaluations and returns if it has violations
214-
func getPolicyEvaluations(c *crafter.Crafter) (map[string][]*PolicyEvaluation, bool, error) {
211+
func getPolicyEvaluations(c *crafter.Crafter) (map[string][]*PolicyEvaluation, bool) {
215212
// grouped by material name
216213
evaluations := make(map[string][]*PolicyEvaluation)
217214
var hasViolations bool
@@ -234,7 +231,7 @@ func getPolicyEvaluations(c *crafter.Crafter) (map[string][]*PolicyEvaluation, b
234231
}
235232
}
236233

237-
return evaluations, hasViolations, nil
234+
return evaluations, hasViolations
238235
}
239236

240237
// populateMaterials populates the materials in the attestation result regardless of where they are defined
@@ -415,5 +412,6 @@ func policyEvaluationStateToActionForStatus(in *v1.PolicyEvaluation) *PolicyEval
415412
Violations: violations,
416413
Skipped: in.Skipped,
417414
SkipReasons: in.SkipReasons,
415+
Gate: in.Gate,
418416
}
419417
}

app/cli/pkg/action/workflow_run_describe.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ type PolicyEvaluation struct {
104104
Type string `json:"type"`
105105
Skipped bool `json:"skipped"`
106106
SkipReasons []string `json:"skip_reasons,omitempty"`
107+
Gate bool `json:"gate,omitempty"`
107108
}
108109

109110
type PolicyViolation struct {

app/controlplane/api/gen/frontend/attestation/v1/crafting_state.ts

Lines changed: 16 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/controlplane/api/gen/frontend/workflowcontract/v1/crafting_schema.ts

Lines changed: 24 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/controlplane/api/gen/jsonschema/attestation.v1.PolicyEvaluation.jsonschema.json

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/controlplane/api/gen/jsonschema/attestation.v1.PolicyEvaluation.schema.json

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)