Files
trivy/pkg/report/table/misconfig.go
Matthieu MOREL 3ab459e3b6 fix: more revive rules (#8814)
Signed-off-by: Matthieu MOREL <matthieu.morel35@gmail.com>
2025-05-05 05:50:37 +00:00

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("")
}
}