mirror of
https://github.com/aquasecurity/trivy.git
synced 2025-12-12 15:50:15 -08:00
264 lines
6.9 KiB
Go
264 lines
6.9 KiB
Go
package table
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"slices"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/fatih/color"
|
|
"golang.org/x/term"
|
|
|
|
"github.com/aquasecurity/tml"
|
|
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
|
|
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
|
|
"github.com/aquasecurity/trivy/pkg/types"
|
|
)
|
|
|
|
const (
|
|
severityCritical = "CRITICAL"
|
|
severityHigh = "HIGH"
|
|
severityMedium = "MEDIUM"
|
|
severityLow = "LOW"
|
|
)
|
|
|
|
type misconfigRenderer struct {
|
|
w *bytes.Buffer
|
|
severities []dbTypes.Severity
|
|
trace bool
|
|
includeNonFailures bool
|
|
width int
|
|
ansi bool
|
|
renderCause []ftypes.ConfigType
|
|
}
|
|
|
|
func NewMisconfigRenderer(buf *bytes.Buffer, severities []dbTypes.Severity, trace, includeNonFailures, ansi bool, renderCause []ftypes.ConfigType) *misconfigRenderer {
|
|
width, _, err := term.GetSize(0)
|
|
if err != nil || width == 0 {
|
|
width = 40
|
|
}
|
|
if !ansi {
|
|
tml.DisableFormatting()
|
|
}
|
|
|
|
return &misconfigRenderer{
|
|
w: buf,
|
|
severities: severities,
|
|
trace: trace,
|
|
includeNonFailures: includeNonFailures,
|
|
width: width,
|
|
ansi: ansi,
|
|
renderCause: renderCause,
|
|
}
|
|
}
|
|
|
|
func (r *misconfigRenderer) Render(result types.Result) {
|
|
// Trivy doesn't currently support showing suppressed misconfigs
|
|
// So just skip this result
|
|
if len(result.Misconfigurations) == 0 {
|
|
return
|
|
}
|
|
target := fmt.Sprintf("%s (%s)", result.Target, result.Type)
|
|
RenderTarget(r.w, target, r.ansi)
|
|
|
|
total, summaries := summarize(r.severities, r.countSeverities(result.Misconfigurations))
|
|
|
|
summary := result.MisconfSummary
|
|
r.printf("Tests: %d (SUCCESSES: %d, FAILURES: %d)\n",
|
|
summary.Successes+summary.Failures, summary.Successes, summary.Failures)
|
|
r.printf("Failures: %d (%s)\n\n", total, strings.Join(summaries, ", "))
|
|
|
|
for _, m := range result.Misconfigurations {
|
|
r.renderSingle(result.Target, result.Type, m)
|
|
}
|
|
|
|
// For debugging
|
|
if r.trace {
|
|
r.outputTrace(result.Target, result.Misconfigurations)
|
|
}
|
|
}
|
|
|
|
func (r *misconfigRenderer) countSeverities(misconfigs []types.DetectedMisconfiguration) map[string]int {
|
|
severityCount := make(map[string]int)
|
|
for _, misconf := range misconfigs {
|
|
if misconf.Status == types.MisconfStatusFailure {
|
|
severityCount[misconf.Severity]++
|
|
}
|
|
}
|
|
return severityCount
|
|
}
|
|
|
|
func (r *misconfigRenderer) printf(format string, args ...any) {
|
|
// nolint
|
|
_ = tml.Fprintf(r.w, format, args...)
|
|
}
|
|
|
|
func (r *misconfigRenderer) println(input string) {
|
|
// nolint
|
|
tml.Fprintln(r.w, input)
|
|
}
|
|
|
|
func (r *misconfigRenderer) printDoubleDivider() {
|
|
r.printf("<dim>%s\r\n", strings.Repeat("═", r.width))
|
|
}
|
|
|
|
func (r *misconfigRenderer) printSingleDivider() {
|
|
r.printf("<dim>%s\r\n", strings.Repeat("─", r.width))
|
|
}
|
|
|
|
func (r *misconfigRenderer) renderSingle(target string, typ ftypes.TargetType, misconf types.DetectedMisconfiguration) {
|
|
r.renderSummary(misconf)
|
|
r.renderCode(target, misconf)
|
|
r.renderMisconfCause(typ, misconf)
|
|
r.printf("\r\n\r\n")
|
|
}
|
|
|
|
func (r *misconfigRenderer) renderSummary(misconf types.DetectedMisconfiguration) {
|
|
// show pass/fail/exception unless we are only showing failures
|
|
if r.includeNonFailures {
|
|
switch misconf.Status {
|
|
case types.MisconfStatusPassed:
|
|
r.printf("<green><bold>%s: ", misconf.Status)
|
|
case types.MisconfStatusFailure:
|
|
r.printf("<red><bold>%s: ", misconf.Status)
|
|
case types.MisconfStatusException:
|
|
r.printf("<yellow><bold>%s: ", misconf.Status)
|
|
}
|
|
}
|
|
|
|
// ID & severity
|
|
switch misconf.Severity {
|
|
case severityCritical:
|
|
r.printf("%s <red><bold>(%s): ", misconf.AVDID, misconf.Severity)
|
|
case severityHigh:
|
|
r.printf("%s <red>(%s): ", misconf.AVDID, misconf.Severity)
|
|
case severityMedium:
|
|
r.printf("%s <yellow>(%s): ", misconf.AVDID, misconf.Severity)
|
|
case severityLow:
|
|
r.printf("%s (%s): ", misconf.AVDID, misconf.Severity)
|
|
default:
|
|
r.printf("%s <blue>(%s): ", misconf.AVDID, misconf.Severity)
|
|
}
|
|
|
|
// heading
|
|
r.printf("%s\r\n", misconf.Message)
|
|
r.printDoubleDivider()
|
|
|
|
// description
|
|
r.printf("<dim>%s\r\n", misconf.Description)
|
|
|
|
// show link if we have one
|
|
if misconf.PrimaryURL != "" {
|
|
r.printf("\r\n<dim>See %s\r\n", misconf.PrimaryURL)
|
|
}
|
|
|
|
r.printSingleDivider()
|
|
}
|
|
|
|
func (r *misconfigRenderer) renderCode(target string, misconf types.DetectedMisconfiguration) {
|
|
// highlight code if we can...
|
|
if lines := misconf.CauseMetadata.Code.Lines; len(lines) > 0 {
|
|
|
|
var lineInfo string
|
|
if misconf.CauseMetadata.StartLine > 0 {
|
|
lineInfo = tml.Sprintf("<dim>:</dim><blue>%d", misconf.CauseMetadata.StartLine)
|
|
if misconf.CauseMetadata.EndLine > misconf.CauseMetadata.StartLine {
|
|
lineInfo = tml.Sprintf("%s<blue>-%d", lineInfo, misconf.CauseMetadata.EndLine)
|
|
}
|
|
}
|
|
r.printf(" <blue>%s%s\r\n", target, lineInfo)
|
|
for i, occ := range misconf.CauseMetadata.Occurrences {
|
|
lineInfo := fmt.Sprintf("%d-%d", occ.Location.StartLine, occ.Location.EndLine)
|
|
if occ.Location.StartLine >= occ.Location.EndLine {
|
|
lineInfo = strconv.Itoa(occ.Location.StartLine)
|
|
}
|
|
|
|
r.printf(
|
|
" %s<dim>via </dim><italic>%s<dim>:%s (%s)\n",
|
|
strings.Repeat(" ", i+2),
|
|
occ.Filename,
|
|
lineInfo,
|
|
occ.Resource,
|
|
)
|
|
}
|
|
|
|
r.printSingleDivider()
|
|
for i, line := range lines {
|
|
switch {
|
|
case line.Truncated:
|
|
r.printf("<dim>%4s ", strings.Repeat(".", len(strconv.Itoa(line.Number))))
|
|
case line.IsCause:
|
|
r.printf("<red>%4d ", line.Number)
|
|
switch {
|
|
case (line.FirstCause && line.LastCause) || len(lines) == 1:
|
|
r.printf("<red>[ ")
|
|
case line.FirstCause || i == 0:
|
|
r.printf("<red>┌ ")
|
|
case line.LastCause || i == len(lines)-1:
|
|
r.printf("<red>└ ")
|
|
default:
|
|
r.printf("<red>│ ")
|
|
}
|
|
default:
|
|
r.printf("<dim>%4d ", line.Number)
|
|
}
|
|
|
|
if r.ansi {
|
|
r.printf("%s\r\n", line.Highlighted)
|
|
} else {
|
|
r.printf("%s\r\n", line.Content)
|
|
}
|
|
}
|
|
r.printSingleDivider()
|
|
}
|
|
}
|
|
|
|
func (r *misconfigRenderer) renderMisconfCause(typ ftypes.TargetType, misconf types.DetectedMisconfiguration) {
|
|
if !slices.Contains(r.renderCause, typ) {
|
|
return
|
|
}
|
|
|
|
cause := misconf.CauseMetadata.RenderedCause
|
|
if cause.Raw == "" {
|
|
return
|
|
}
|
|
|
|
content := cause.Raw
|
|
if cause.Highlighted != "" {
|
|
content = cause.Highlighted
|
|
}
|
|
|
|
r.printf("<dim>%s\r\n", "Rendered cause:")
|
|
r.printSingleDivider()
|
|
r.println(content)
|
|
r.printSingleDivider()
|
|
}
|
|
|
|
func (r *misconfigRenderer) outputTrace(target string, misconfigs []types.DetectedMisconfiguration) {
|
|
blue := color.New(color.FgBlue).SprintFunc()
|
|
green := color.New(color.FgGreen).SprintfFunc()
|
|
red := color.New(color.FgRed).SprintfFunc()
|
|
|
|
for _, misconf := range misconfigs {
|
|
if len(misconf.Traces) == 0 {
|
|
continue
|
|
}
|
|
|
|
c := green
|
|
if misconf.Status == types.MisconfStatusFailure {
|
|
c = red
|
|
}
|
|
|
|
r.println(c("\nID: %s", misconf.ID))
|
|
r.println(c("File: %s", target))
|
|
r.println(c("Namespace: %s", misconf.Namespace))
|
|
r.println(c("Query: %s", misconf.Query))
|
|
r.println(c("Message: %s", misconf.Message))
|
|
for _, t := range misconf.Traces {
|
|
r.println(blue("TRACE ") + t)
|
|
}
|
|
r.println("")
|
|
}
|
|
}
|