-
Notifications
You must be signed in to change notification settings - Fork 295
/
helpers.go
341 lines (303 loc) · 10.6 KB
/
helpers.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
package helpers
import (
"encoding/json"
"errors"
"fmt"
"io"
"math"
"os"
"path/filepath"
"strings"
"sync"
"github.com/BurntSushi/toml"
"github.com/Checkmarx/kics/internal/metrics"
"github.com/Checkmarx/kics/pkg/model"
"github.com/Checkmarx/kics/pkg/report"
"github.com/gookit/color"
"github.com/hashicorp/hcl"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"gopkg.in/yaml.v3"
)
var reportGenerators = map[string]func(path, filename string, body interface{}) error{
"json": report.PrintJSONReport,
"sarif": report.PrintSarifReport,
"html": report.PrintHTMLReport,
}
// ProgressBar represents a Progress
// Writer is the writer output for progress bar
type ProgressBar struct {
Writer io.Writer
label string
space int
total float64
currentProgress float64
progress chan float64
}
// Printer wil print console output with colors
// Medium is for medium sevevity results
// High is for high sevevity results
// Low is for low sevevity results
// Info is for info sevevity results
// Success is for successful prints
// Line is the color to print the line with the vulnerability
// minVersion is a bool that if true will print the results output in a minimum version
type Printer struct {
Medium color.RGBColor
High color.RGBColor
Low color.RGBColor
Info color.RGBColor
Success color.RGBColor
Line color.RGBColor
minimal bool
}
// NewProgressBar initializes a new ProgressBar
// label is a string print before the progress bar
// total is the progress bar target (a.k.a 100%)
// space is the number of '=' characters on each side of the bar
// progress is a channel updating the current executed elements
func NewProgressBar(label string, space int, total float64, progress chan float64) ProgressBar {
return ProgressBar{
Writer: os.Stdout,
label: label,
space: space,
total: total,
progress: progress,
}
}
// Start starts to print a progress bar on console
// wg is a wait group to report when progress is done
func (p *ProgressBar) Start(wg *sync.WaitGroup) {
defer wg.Done()
if p.Writer != io.Discard {
var firstHalfPercentage, secondHalfPercentage string
const hundredPercent = 100
formmatingString := "\r" + p.label + "[%s %4.1f%% %s]"
for {
newProgress, ok := <-p.progress
p.currentProgress += newProgress
if !ok || p.currentProgress >= p.total {
fmt.Fprintf(p.Writer, formmatingString, strings.Repeat("=", p.space), 100.0, strings.Repeat("=", p.space))
break
}
percentage := p.currentProgress / p.total * hundredPercent
convertedPercentage := int(math.Round(float64(p.space+p.space) / hundredPercent * math.Round(percentage)))
if percentage >= hundredPercent/2 {
firstHalfPercentage = strings.Repeat("=", p.space)
secondHalfPercentage = strings.Repeat("=", convertedPercentage-p.space) +
strings.Repeat(" ", 2*p.space-convertedPercentage)
} else {
secondHalfPercentage = strings.Repeat(" ", p.space)
firstHalfPercentage = strings.Repeat("=", convertedPercentage) +
strings.Repeat(" ", p.space-convertedPercentage)
}
fmt.Fprintf(p.Writer, formmatingString, firstHalfPercentage, percentage, secondHalfPercentage)
}
}
fmt.Println()
}
// WordWrap Wraps text at the specified number of words
func WordWrap(s, identation string, limit int) string {
if strings.TrimSpace(s) == "" {
return s
}
wordSlice := strings.Fields(s)
var result string
for len(wordSlice) >= 1 {
result = result + identation + strings.Join(wordSlice[:limit], " ") + "\r\n"
wordSlice = wordSlice[limit:]
if len(wordSlice) < limit {
limit = len(wordSlice)
}
}
return result
}
// PrintResult prints on output the summary results
func PrintResult(summary *model.Summary, failedQueries map[string]error, printer *Printer) error {
log.Debug().Msg("helpers.PrintResult()")
fmt.Printf("Files scanned: %d\n", summary.ScannedFiles)
fmt.Printf("Parsed files: %d\n", summary.ParsedFiles)
fmt.Printf("Queries loaded: %d\n", summary.TotalQueries)
fmt.Printf("Queries failed to execute: %d\n\n", summary.FailedToExecuteQueries)
for queryName, err := range failedQueries {
fmt.Printf("\t- %s:\n", queryName)
fmt.Printf("%s", WordWrap(err.Error(), "\t\t", 5))
}
fmt.Printf("------------------------------------\n\n")
for index := range summary.Queries {
idx := len(summary.Queries) - index - 1
fmt.Printf(
"%s, Severity: %s, Results: %d\n",
printer.PrintBySev(summary.Queries[idx].QueryName, string(summary.Queries[idx].Severity)),
printer.PrintBySev(string(summary.Queries[idx].Severity), string(summary.Queries[idx].Severity)),
len(summary.Queries[idx].Files),
)
if !printer.minimal {
fmt.Printf("Description: %s\n", summary.Queries[idx].Description)
fmt.Printf("Platform: %s\n\n", summary.Queries[idx].Platform)
}
printFiles(&summary.Queries[idx], printer)
}
fmt.Printf("\nResults Summary:\n")
printSeverityCounter(model.SeverityHigh, summary.SeveritySummary.SeverityCounters[model.SeverityHigh], printer.High)
printSeverityCounter(model.SeverityMedium, summary.SeveritySummary.SeverityCounters[model.SeverityMedium], printer.Medium)
printSeverityCounter(model.SeverityLow, summary.SeveritySummary.SeverityCounters[model.SeverityLow], printer.Low)
printSeverityCounter(model.SeverityInfo, summary.SeveritySummary.SeverityCounters[model.SeverityInfo], printer.Info)
fmt.Printf("TOTAL: %d\n\n", summary.SeveritySummary.TotalCounter)
log.Info().Msgf("Files scanned: %d", summary.ScannedFiles)
log.Info().Msgf("Parsed files: %d", summary.ParsedFiles)
log.Info().Msgf("Queries loaded: %d", summary.TotalQueries)
log.Info().Msgf("Queries failed to execute: %d", summary.FailedToExecuteQueries)
log.Info().Msg("Inspector stopped")
return nil
}
func printSeverityCounter(severity string, counter int, printColor color.RGBColor) {
fmt.Printf("%s: %d\n", printColor.Sprint(severity), counter)
}
func printFiles(query *model.VulnerableQuery, printer *Printer) {
for fileIdx := range query.Files {
fmt.Printf("\t%s %s:%s\n", printer.PrintBySev(fmt.Sprintf("[%d]:", fileIdx+1), string(query.Severity)),
query.Files[fileIdx].FileName, printer.Success.Sprint(query.Files[fileIdx].Line))
if !printer.minimal {
fmt.Println()
for _, line := range query.Files[fileIdx].VulnLines {
if line.Position == query.Files[fileIdx].Line {
printer.Line.Printf("\t\t%03d: %s\n", line.Position, line.Line)
} else {
fmt.Printf("\t\t%03d: %s\n", line.Position, line.Line)
}
}
fmt.Print("\n\n")
}
}
}
// CustomConsoleWriter creates an output to print log in a files
func CustomConsoleWriter(fileLogger *zerolog.ConsoleWriter) zerolog.ConsoleWriter {
fileLogger.FormatLevel = func(i interface{}) string {
return strings.ToUpper(fmt.Sprintf("| %-6s|", i))
}
fileLogger.FormatFieldName = func(i interface{}) string {
return fmt.Sprintf("%s:", i)
}
fileLogger.FormatErrFieldName = func(i interface{}) string {
return "ERROR:"
}
fileLogger.FormatFieldValue = func(i interface{}) string {
return fmt.Sprintf("%s", i)
}
return *fileLogger
}
// FileAnalyzer determines the type of extension in the passed config file by its content
func FileAnalyzer(path string) (string, error) {
ostat, err := os.Open(filepath.Clean(path))
if err != nil {
return "", err
}
rc, err := io.ReadAll(ostat)
if err != nil {
return "", err
}
var temp map[string]interface{}
if err := json.Unmarshal(rc, &temp); err == nil {
return "json", nil
}
if err := yaml.Unmarshal(rc, &temp); err == nil {
return "yaml", nil
}
if _, err := toml.Decode(string(rc), &temp); err == nil {
return "toml", nil
}
if c, err := hcl.Parse(string(rc)); err == nil {
if err = hcl.DecodeObject(&temp, c); err == nil {
return "hcl", nil
}
}
return "", errors.New("invalid configuration file format")
}
// GenerateReport execute each report function to generate report
func GenerateReport(path, filename string, body interface{}, formats []string) error {
log.Debug().Msgf("helpers.GenerateReport()")
metrics.Metric.Start("generate_report")
var err error = nil
for _, format := range formats {
if err = reportGenerators[format](path, filename, body); err != nil {
log.Error().Msgf("Failed to generate %s report", format)
break
}
}
metrics.Metric.Stop()
return err
}
// GetExecutableDirectory - returns the path to the directory containing KICS executable
func GetExecutableDirectory() string {
log.Debug().Msg("helpers.GetExecutableDirectory()")
path, err := os.Executable()
if err != nil {
log.Err(err)
}
return filepath.Dir(path)
}
// GetDefaultQueryPath - returns the default query path
func GetDefaultQueryPath(queriesPath string) (string, error) {
log.Debug().Msg("helpers.GetDefaultQueryPath()")
executableDirPath := GetExecutableDirectory()
queriesDirectory := filepath.Join(executableDirPath, queriesPath)
if _, err := os.Stat(queriesDirectory); os.IsNotExist(err) {
currentWorkDir, err := os.Getwd()
if err != nil {
return "", err
}
queriesDirectory = filepath.Join(currentWorkDir, queriesPath)
if _, err := os.Stat(queriesDirectory); os.IsNotExist(err) {
return "", err
}
}
log.Debug().Msgf("Queries found in %s", queriesDirectory)
return queriesDirectory, nil
}
// ListReportFormats return a slice with all supported report formats
func ListReportFormats() []string {
supportedFormats := make([]string, 0, len(reportGenerators))
for reportFormats := range reportGenerators {
supportedFormats = append(supportedFormats, reportFormats)
}
return supportedFormats
}
// ValidateReportFormats returns an error if output format is not supported
func ValidateReportFormats(formats []string) error {
log.Debug().Msg("helpers.ValidateReportFormats()")
for _, format := range formats {
if _, ok := reportGenerators[format]; !ok {
return fmt.Errorf(
fmt.Sprintf("Report format not supported: %s\nSupportted formats:\n %s\n", format, strings.Join(ListReportFormats(), "\n")),
)
}
}
return nil
}
// NewPrinter initializes a new Printer
func NewPrinter(minimal bool) *Printer {
return &Printer{
Medium: color.HEX("#ff7213"),
High: color.HEX("#bb2124"),
Low: color.HEX("#edd57e"),
Success: color.HEX("#22bb33"),
Info: color.HEX("#5bc0de"),
Line: color.HEX("#f0ad4e"),
minimal: minimal,
}
}
// PrintBySev will print the output with the specific severity color given the severity of the result
func (p *Printer) PrintBySev(content, sev string) string {
switch strings.ToUpper(sev) {
case model.SeverityHigh:
return p.High.Sprintf(content)
case model.SeverityMedium:
return p.Medium.Sprintf(content)
case model.SeverityLow:
return p.Low.Sprintf(content)
case model.SeverityInfo:
return p.Info.Sprintf(content)
}
return content
}