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 }