diff --git a/app/cli/cmd/attestation_status.go b/app/cli/cmd/attestation_status.go index 9a80252dd..957fcefcb 100644 --- a/app/cli/cmd/attestation_status.go +++ b/app/cli/cmd/attestation_status.go @@ -193,6 +193,12 @@ func materialsTable(status *action.AttestationStatusResult, full bool) error { } } + evs := status.PolicyEvaluations[m.Name] + if len(evs) > 0 { + mt.AppendRow(table.Row{"Policies", "------"}) + policiesTable(evs, mt) + } + mt.AppendSeparator() } mt.Render() diff --git a/app/cli/cmd/workflow_workflow_run_describe.go b/app/cli/cmd/workflow_workflow_run_describe.go index b2d787a2e..0b09d49c9 100644 --- a/app/cli/cmd/workflow_workflow_run_describe.go +++ b/app/cli/cmd/workflow_workflow_run_describe.go @@ -82,7 +82,7 @@ func newWorkflowWorkflowRunDescribeCmd() *cobra.Command { } cmd.Flags().StringVar(&runID, "id", "", "workflow Run ID") - cmd.Flags().StringVar(&attestationDigest, "digest", "", "content digest of the attestation") + cmd.Flags().StringVarP(&attestationDigest, "digest", "d", "", "content digest of the attestation") cmd.Flags().BoolVar(&verifyAttestation, "verify", false, "verify the attestation") cmd.Flags().StringVar(&publicKey, "key", "", fmt.Sprintf("public key used to verify the attestation. Note: You can also use env variable %s", signingKeyEnvVarName)) @@ -156,6 +156,12 @@ func workflowRunDescribeTableOutput(run *action.WorkflowRunItemFull) error { gt.AppendRow(table.Row{"", fmt.Sprintf("%s: %s", a.Name, a.Value)}) } } + + evs := att.PolicyEvaluations[chainloop.AttPolicyEvaluation] + if len(evs) > 0 { + gt.AppendRow(table.Row{"Policies", "------"}) + policiesTable(evs, gt) + } gt.Render() predicateV1Table(att) @@ -183,7 +189,9 @@ func predicateV1Table(att *action.WorkflowRunAttestationItem) { if m.Tag != "" { v = fmt.Sprintf("%s:%s", v, m.Tag) } - mt.AppendRow(table.Row{"Value", wrap.String(v, 100)}) + if v != "" { + mt.AppendRow(table.Row{"Value", wrap.String(v, 100)}) + } } if m.Hash != "" { @@ -198,8 +206,7 @@ func predicateV1Table(att *action.WorkflowRunAttestationItem) { } evs := att.PolicyEvaluations[m.Name] if len(evs) > 0 { - mt.AppendSeparator() - mt.AppendRow(table.Row{"Policies"}) + mt.AppendRow(table.Row{"Policies", "------"}) policiesTable(evs, mt) } mt.AppendSeparator() @@ -220,29 +227,32 @@ func predicateV1Table(att *action.WorkflowRunAttestationItem) { } mt.Render() } - - evs := att.PolicyEvaluations[chainloop.AttPolicyEvaluation] - if len(evs) > 0 { - mt := newTableWriter() - mt.SetTitle("Attestation policies") - policiesTable(evs, mt) - mt.Render() - } } func policiesTable(evs []*action.PolicyEvaluation, mt table.Writer) { for _, ev := range evs { - mt.AppendSeparator() - mt.AppendRow(table.Row{"Policy", ev.Name}) + color := text.Colors{text.FgHiRed} var violations []string + var prefix = "" if len(ev.Violations) > 0 { for _, v := range ev.Violations { violations = append(violations, v.Message) } + // For multiple violations, we want to indent the list + if len(violations) > 1 { + prefix = "\n - " + } } else { - violations = append(violations, "None") + violations = append(violations, "Ok") + color = text.Colors{text.FgHiGreen} } - mt.AppendRow(table.Row{"Violations", fmt.Sprint(strings.Join(violations, "\n"))}) + + // Color the violations text before joining + for i, v := range violations { + violations[i] = color.Sprint(v) + } + + mt.AppendRow(table.Row{"", fmt.Sprintf("%s: %s", ev.Name, prefix+strings.Join(violations, prefix))}) } } diff --git a/app/cli/internal/action/attestation_status.go b/app/cli/internal/action/attestation_status.go index f1dac43c3..f294251ce 100644 --- a/app/cli/internal/action/attestation_status.go +++ b/app/cli/internal/action/attestation_status.go @@ -40,15 +40,16 @@ type AttestationStatus struct { } type AttestationStatusResult struct { - AttestationID string `json:"attestationID"` - InitializedAt *time.Time `json:"initializedAt"` - WorkflowMeta *AttestationStatusWorkflowMeta `json:"workflowMeta"` - Materials []AttestationStatusResultMaterial `json:"materials"` - EnvVars map[string]string `json:"envVars"` - RunnerContext *AttestationResultRunnerContext `json:"runnerContext"` - DryRun bool `json:"dryRun"` - Annotations []*Annotation `json:"annotations"` - IsPushed bool `json:"isPushed"` + AttestationID string `json:"attestationID"` + InitializedAt *time.Time `json:"initializedAt"` + WorkflowMeta *AttestationStatusWorkflowMeta `json:"workflowMeta"` + Materials []AttestationStatusResultMaterial `json:"materials"` + EnvVars map[string]string `json:"envVars"` + RunnerContext *AttestationResultRunnerContext `json:"runnerContext"` + DryRun bool `json:"dryRun"` + Annotations []*Annotation `json:"annotations"` + IsPushed bool `json:"isPushed"` + PolicyEvaluations map[string][]*PolicyEvaluation `json:"policy_evaluations,omitempty"` } type AttestationResultRunnerContext struct { @@ -112,6 +113,18 @@ func (action *AttestationStatus) Run(ctx context.Context, attestationID string) IsPushed: action.isPushed, } + // grouped by material name + evaluations := make(map[string][]*PolicyEvaluation) + for _, v := range att.GetPolicyEvaluations() { + if existing, ok := evaluations[v.MaterialName]; ok { + evaluations[v.MaterialName] = append(existing, policyEvaluationStateToActionForStatus(v)) + } else { + evaluations[v.MaterialName] = []*PolicyEvaluation{policyEvaluationStateToActionForStatus(v)} + } + } + + res.PolicyEvaluations = evaluations + if v := workflowMeta.GetVersion(); v != nil { res.WorkflowMeta.ProjectVersion = &ProjectVersion{ Version: v.GetVersion(), @@ -277,3 +290,31 @@ func setMaterialValue(w *v1.Attestation_Material, o *AttestationStatusResultMate return nil } + +func policyEvaluationStateToActionForStatus(in *v1.PolicyEvaluation) *PolicyEvaluation { + var pr *PolicyReference + if in.PolicyReference != nil { + pr = &PolicyReference{ + Name: in.PolicyReference.Name, + } + } + + violations := make([]*PolicyViolation, 0, len(in.Violations)) + for _, v := range in.Violations { + violations = append(violations, &PolicyViolation{ + Subject: v.Subject, + Message: v.Message, + }) + } + + return &PolicyEvaluation{ + Name: in.Name, + MaterialName: in.MaterialName, + Description: in.Description, + Annotations: in.Annotations, + PolicyReference: pr, + Violations: violations, + Skipped: in.Skipped, + SkipReasons: in.SkipReasons, + } +}