github.com/devseccon/trivy@v0.47.1-0.20231123133102-bd902a0bd996/pkg/report/table/misconfig.go (about)

     1  package table
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"strings"
     7  
     8  	"github.com/fatih/color"
     9  	"golang.org/x/term"
    10  
    11  	"github.com/aquasecurity/tml"
    12  	dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
    13  	"github.com/devseccon/trivy/pkg/types"
    14  )
    15  
    16  const (
    17  	severityCritical = "CRITICAL"
    18  	severityHigh     = "HIGH"
    19  	severityMedium   = "MEDIUM"
    20  	severityLow      = "LOW"
    21  )
    22  
    23  type misconfigRenderer struct {
    24  	w                  *bytes.Buffer
    25  	result             types.Result
    26  	severities         []dbTypes.Severity
    27  	trace              bool
    28  	includeNonFailures bool
    29  	width              int
    30  	ansi               bool
    31  }
    32  
    33  func NewMisconfigRenderer(result types.Result, severities []dbTypes.Severity, trace, includeNonFailures, ansi bool) *misconfigRenderer {
    34  	width, _, err := term.GetSize(0)
    35  	if err != nil || width == 0 {
    36  		width = 40
    37  	}
    38  	if !ansi {
    39  		tml.DisableFormatting()
    40  	}
    41  	return &misconfigRenderer{
    42  		w:                  bytes.NewBuffer([]byte{}),
    43  		result:             result,
    44  		severities:         severities,
    45  		trace:              trace,
    46  		includeNonFailures: includeNonFailures,
    47  		width:              width,
    48  		ansi:               ansi,
    49  	}
    50  }
    51  
    52  func (r *misconfigRenderer) Render() string {
    53  	target := fmt.Sprintf("%s (%s)", r.result.Target, r.result.Type)
    54  	RenderTarget(r.w, target, r.ansi)
    55  
    56  	total, summaries := summarize(r.severities, r.countSeverities())
    57  
    58  	summary := r.result.MisconfSummary
    59  	r.printf("Tests: %d (SUCCESSES: %d, FAILURES: %d, EXCEPTIONS: %d)\n",
    60  		summary.Successes+summary.Failures+summary.Exceptions, summary.Successes, summary.Failures, summary.Exceptions)
    61  	r.printf("Failures: %d (%s)\n\n", total, strings.Join(summaries, ", "))
    62  
    63  	for _, m := range r.result.Misconfigurations {
    64  		r.renderSingle(m)
    65  	}
    66  
    67  	// For debugging
    68  	if r.trace {
    69  		r.outputTrace()
    70  	}
    71  	return r.w.String()
    72  }
    73  
    74  func (r *misconfigRenderer) countSeverities() map[string]int {
    75  	severityCount := make(map[string]int)
    76  	for _, misconf := range r.result.Misconfigurations {
    77  		if misconf.Status == types.StatusFailure {
    78  			severityCount[misconf.Severity]++
    79  		}
    80  	}
    81  	return severityCount
    82  }
    83  
    84  func (r *misconfigRenderer) printf(format string, args ...interface{}) {
    85  	// nolint
    86  	_ = tml.Fprintf(r.w, format, args...)
    87  }
    88  
    89  func (r *misconfigRenderer) println(input string) {
    90  	// nolint
    91  	tml.Fprintln(r.w, input)
    92  }
    93  
    94  func (r *misconfigRenderer) printDoubleDivider() {
    95  	r.printf("<dim>%s\r\n", strings.Repeat("═", r.width))
    96  }
    97  
    98  func (r *misconfigRenderer) printSingleDivider() {
    99  	r.printf("<dim>%s\r\n", strings.Repeat("─", r.width))
   100  }
   101  
   102  func (r *misconfigRenderer) renderSingle(misconf types.DetectedMisconfiguration) {
   103  	r.renderSummary(misconf)
   104  	r.renderCode(misconf)
   105  	r.printf("\r\n\r\n")
   106  }
   107  
   108  func (r *misconfigRenderer) renderSummary(misconf types.DetectedMisconfiguration) {
   109  
   110  	// show pass/fail/exception unless we are only showing failures
   111  	if r.includeNonFailures {
   112  		switch misconf.Status {
   113  		case types.StatusPassed:
   114  			r.printf("<green><bold>%s: ", misconf.Status)
   115  		case types.StatusFailure:
   116  			r.printf("<red><bold>%s: ", misconf.Status)
   117  		case types.StatusException:
   118  			r.printf("<yellow><bold>%s: ", misconf.Status)
   119  		}
   120  	}
   121  
   122  	// severity
   123  	switch misconf.Severity {
   124  	case severityCritical:
   125  		r.printf("<red><bold>%s: ", misconf.Severity)
   126  	case severityHigh:
   127  		r.printf("<red>%s: ", misconf.Severity)
   128  	case severityMedium:
   129  		r.printf("<yellow>%s: ", misconf.Severity)
   130  	case severityLow:
   131  		r.printf("%s: ", misconf.Severity)
   132  	default:
   133  		r.printf("<blue>%s: ", misconf.Severity)
   134  	}
   135  
   136  	// heading
   137  	r.printf("%s\r\n", misconf.Message)
   138  	r.printDoubleDivider()
   139  
   140  	// description
   141  	r.printf("<dim>%s\r\n", misconf.Description)
   142  
   143  	// show link if we have one
   144  	if misconf.PrimaryURL != "" {
   145  		r.printf("\r\n<dim>See %s\r\n", misconf.PrimaryURL)
   146  	}
   147  
   148  	r.printSingleDivider()
   149  }
   150  
   151  func (r *misconfigRenderer) renderCode(misconf types.DetectedMisconfiguration) {
   152  	// highlight code if we can...
   153  	if lines := misconf.CauseMetadata.Code.Lines; len(lines) > 0 {
   154  
   155  		var lineInfo string
   156  		if misconf.CauseMetadata.StartLine > 0 {
   157  			lineInfo = tml.Sprintf("<dim>:</dim><blue>%d", misconf.CauseMetadata.StartLine)
   158  			if misconf.CauseMetadata.EndLine > misconf.CauseMetadata.StartLine {
   159  				lineInfo = tml.Sprintf("%s<blue>-%d", lineInfo, misconf.CauseMetadata.EndLine)
   160  			}
   161  		}
   162  		r.printf(" <blue>%s%s\r\n", r.result.Target, lineInfo)
   163  		for i, occ := range misconf.CauseMetadata.Occurrences {
   164  			lineInfo := fmt.Sprintf("%d-%d", occ.Location.StartLine, occ.Location.EndLine)
   165  			if occ.Location.StartLine >= occ.Location.EndLine {
   166  				lineInfo = fmt.Sprintf("%d", occ.Location.StartLine)
   167  			}
   168  
   169  			r.printf(
   170  				" %s<dim>via </dim><italic>%s<dim>:%s (%s)\n",
   171  				strings.Repeat(" ", i+2),
   172  				occ.Filename,
   173  				lineInfo,
   174  				occ.Resource,
   175  			)
   176  		}
   177  
   178  		r.printSingleDivider()
   179  		for i, line := range lines {
   180  			switch {
   181  			case line.Truncated:
   182  				r.printf("<dim>%4s   ", strings.Repeat(".", len(fmt.Sprintf("%d", line.Number))))
   183  			case line.IsCause:
   184  				r.printf("<red>%4d ", line.Number)
   185  				switch {
   186  				case (line.FirstCause && line.LastCause) || len(lines) == 1:
   187  					r.printf("<red>[ ")
   188  				case line.FirstCause || i == 0:
   189  					r.printf("<red>┌ ")
   190  				case line.LastCause || i == len(lines)-1:
   191  					r.printf("<red>└ ")
   192  				default:
   193  					r.printf("<red>│ ")
   194  				}
   195  			default:
   196  				r.printf("<dim>%4d   ", line.Number)
   197  			}
   198  
   199  			if r.ansi {
   200  				r.printf("%s\r\n", line.Highlighted)
   201  			} else {
   202  				r.printf("%s\r\n", line.Content)
   203  			}
   204  		}
   205  		r.printSingleDivider()
   206  	}
   207  }
   208  
   209  func (r *misconfigRenderer) outputTrace() {
   210  	blue := color.New(color.FgBlue).SprintFunc()
   211  	green := color.New(color.FgGreen).SprintfFunc()
   212  	red := color.New(color.FgRed).SprintfFunc()
   213  
   214  	for _, misconf := range r.result.Misconfigurations {
   215  		if len(misconf.Traces) == 0 {
   216  			continue
   217  		}
   218  
   219  		c := green
   220  		if misconf.Status == types.StatusFailure {
   221  			c = red
   222  		}
   223  
   224  		r.println(c("\nID: %s", misconf.ID))
   225  		r.println(c("File: %s", r.result.Target))
   226  		r.println(c("Namespace: %s", misconf.Namespace))
   227  		r.println(c("Query: %s", misconf.Query))
   228  		r.println(c("Message: %s", misconf.Message))
   229  		for _, t := range misconf.Traces {
   230  			r.println(blue("TRACE ") + t)
   231  		}
   232  		r.println("")
   233  	}
   234  }