From 9729941823bbd5ce7ec05e3d6839d4012ba0d56f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Str=C3=B6mberg?= Date: Mon, 7 Oct 2024 08:15:36 -0400 Subject: [PATCH] scan: wolfictl inspired output presentation (#492) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * pip: add known good list * scan: wolfictl inspired output presentation * shorten empty Scanning commands --------- Signed-off-by: Thomas StrΓΆmberg --- cmd/mal/mal.go | 10 +++++----- pkg/action/scan.go | 1 + pkg/malcontent/malcontent.go | 1 + pkg/render/json.go | 2 ++ pkg/render/markdown.go | 2 ++ pkg/render/simple.go | 2 ++ pkg/render/strings.go | 19 +++++++++++++++++++ pkg/render/terminal.go | 28 ++++++++++++++++++---------- pkg/render/terminal_brief.go | 23 +++++------------------ pkg/render/yaml.go | 2 ++ 10 files changed, 57 insertions(+), 33 deletions(-) diff --git a/cmd/mal/mal.go b/cmd/mal/mal.go index e7be11c15..7aa4acd2d 100644 --- a/cmd/mal/mal.go +++ b/cmd/mal/mal.go @@ -384,7 +384,7 @@ func main() { processPaths, err := action.GetAllProcessPaths(ctx) if err != nil { returnCode = ExitActionFailed - return err + return fmt.Errorf("process paths: %w", err) } for _, p := range processPaths { mc.ScanPaths = append(mc.ScanPaths, p.Path) @@ -394,7 +394,7 @@ func main() { res, err = action.Scan(ctx, mc) if err != nil { returnCode = ExitActionFailed - return err + return fmt.Errorf("scan: %w", err) } err = renderer.Full(ctx, res) @@ -461,7 +461,7 @@ func main() { processPaths, err := action.GetAllProcessPaths(ctx) if err != nil { returnCode = ExitActionFailed - return err + return fmt.Errorf("process paths: %w", err) } for _, p := range processPaths { mc.ScanPaths = append(mc.ScanPaths, p.Path) @@ -471,7 +471,7 @@ func main() { res, err = action.Scan(ctx, mc) if err != nil { returnCode = ExitActionFailed - return err + return fmt.Errorf("scan: %w", err) } err = renderer.Full(ctx, res) @@ -481,7 +481,7 @@ func main() { } if res.Files.Len() > 0 { - fmt.Fprintf(os.Stderr, "\ntip: For detailed analysis, run: mal analyze \n") + fmt.Fprintf(os.Stderr, "\nπŸ’‘ For detailed analysis, try \"mal analyze \"\n") } return nil diff --git a/pkg/action/scan.go b/pkg/action/scan.go index 5dc0df53e..6a6345daa 100644 --- a/pkg/action/scan.go +++ b/pkg/action/scan.go @@ -222,6 +222,7 @@ func recursiveScan(ctx context.Context, c malcontent.Config) (*malcontent.Report var scanPathFindings sync.Map for _, scanPath := range c.ScanPaths { + c.Renderer.Scanning(ctx, scanPath) logger.Debug("recursive scan", slog.Any("scanPath", scanPath)) imageURI := "" ociExtractPath := "" diff --git a/pkg/malcontent/malcontent.go b/pkg/malcontent/malcontent.go index 665d91707..129ca92e7 100644 --- a/pkg/malcontent/malcontent.go +++ b/pkg/malcontent/malcontent.go @@ -13,6 +13,7 @@ import ( // Renderer is a common interface for Renderers. type Renderer interface { + Scanning(context.Context, string) File(context.Context, *FileReport) error Full(context.Context, *Report) error } diff --git a/pkg/render/json.go b/pkg/render/json.go index 398f99de2..f37fbb794 100644 --- a/pkg/render/json.go +++ b/pkg/render/json.go @@ -20,6 +20,8 @@ func NewJSON(w io.Writer) JSON { return JSON{w: w} } +func (r JSON) Scanning(_ context.Context, _ string) {} + func (r JSON) File(_ context.Context, _ *malcontent.FileReport) error { return nil } diff --git a/pkg/render/markdown.go b/pkg/render/markdown.go index e41a54ea8..3ce2680bd 100644 --- a/pkg/render/markdown.go +++ b/pkg/render/markdown.go @@ -41,6 +41,8 @@ func matchFragmentLink(s string) string { return fmt.Sprintf("[%s](https://github.com/search?q=%s&type=code)", s, url.QueryEscape(s)) } +func (r Markdown) Scanning(_ context.Context, _ string) {} + func (r Markdown) File(ctx context.Context, fr *malcontent.FileReport) error { if len(fr.Behaviors) > 0 { markdownTable(ctx, fr, r.w, tableConfig{Title: fmt.Sprintf("## %s [%s]", fr.Path, mdRisk(fr.RiskScore, fr.RiskLevel))}) diff --git a/pkg/render/simple.go b/pkg/render/simple.go index 5bb1beaf1..a3ccf631b 100644 --- a/pkg/render/simple.go +++ b/pkg/render/simple.go @@ -20,6 +20,8 @@ func NewSimple(w io.Writer) Simple { return Simple{w: w} } +func (r Simple) Scanning(_ context.Context, _ string) {} + func (r Simple) File(_ context.Context, fr *malcontent.FileReport) error { if fr.Skipped != "" { return nil diff --git a/pkg/render/strings.go b/pkg/render/strings.go index 5a9865e25..09a2cb628 100644 --- a/pkg/render/strings.go +++ b/pkg/render/strings.go @@ -43,6 +43,21 @@ var riskLevels = map[int]string{ 4: "CRITICAL", // critical: certainly malware } +func briefRiskColor(level string) string { + switch level { + case "LOW": + return color.HiGreenString("LOW") + case "MEDIUM", "MED": + return color.HiYellowString("MED") + case "HIGH": + return color.HiRedString("HIGH") + case "CRITICAL", "CRIT": + return color.HiMagentaString("CRIT") + default: + return color.WhiteString(level) + } +} + type StringMatches struct { w io.Writer } @@ -58,6 +73,10 @@ type Match struct { Strings []string } +func (r StringMatches) Scanning(_ context.Context, path string) { + fmt.Fprintf(r.w, "πŸ”Ž Scanning %q\n", path) +} + func (r StringMatches) File(_ context.Context, fr *malcontent.FileReport) error { if len(fr.Behaviors) == 0 { return nil diff --git a/pkg/render/terminal.go b/pkg/render/terminal.go index 23027d684..ca5b42718 100644 --- a/pkg/render/terminal.go +++ b/pkg/render/terminal.go @@ -57,25 +57,29 @@ func NewTerminal(w io.Writer) Terminal { } func decorativeRisk(score int, level string) string { - return fmt.Sprintf("%s %s", riskEmoji(score), riskColor(level)) + return fmt.Sprintf("%s %s", riskEmoji(score), riskColor(level, level)) } func darkBrackets(s string) string { return fmt.Sprintf("%s%s%s", color.HiBlackString("["), s, color.HiBlackString("]")) } -func riskColor(level string) string { +func riskInColor(level string) string { + return riskColor(level, level) +} + +func riskColor(level string, text string) string { switch level { case "LOW": - return color.HiGreenString(level) + return color.HiGreenString(text) case "MEDIUM", "MED": - return color.HiYellowString(level) + return color.HiYellowString(text) case "HIGH": - return color.HiRedString(level) + return color.HiRedString(text) case "CRITICAL", "CRIT": - return color.HiMagentaString(level) + return color.HiMagentaString(text) default: - return color.WhiteString(level) + return color.WhiteString(text) } } @@ -90,6 +94,10 @@ func ShortRisk(s string) string { return short } +func (r Terminal) Scanning(_ context.Context, path string) { + fmt.Fprintf(r.w, "πŸ”Ž Scanning %q\n", path) +} + func (r Terminal) File(ctx context.Context, fr *malcontent.FileReport) error { if len(fr.Behaviors) > 0 { renderTable(ctx, fr, r.w, @@ -279,12 +287,12 @@ func renderTable(ctx context.Context, fr *malcontent.FileReport, w io.Writer, rc } evidence := strings.Join(abbreviatedEv, "\n") - risk := riskColor(ShortRisk(k.Behavior.RiskLevel)) + risk := riskInColor(ShortRisk(k.Behavior.RiskLevel)) if k.Behavior.DiffAdded || rc.DiffAdded { if rc.SkipAdded { continue } - risk = fmt.Sprintf("%s%s", color.HiWhiteString("+"), riskColor(ShortRisk(k.Behavior.RiskLevel))) + risk = fmt.Sprintf("%s%s", color.HiWhiteString("+"), riskInColor(ShortRisk(k.Behavior.RiskLevel))) } wKey := wrapKey(k.Key, keyWidth) @@ -294,7 +302,7 @@ func renderTable(ctx context.Context, fr *malcontent.FileReport, w io.Writer, rc if rc.SkipRemoved { continue } - risk = fmt.Sprintf("%s%s", color.WhiteString("-"), riskColor(ShortRisk(k.Behavior.RiskLevel))) + risk = fmt.Sprintf("%s%s", color.WhiteString("-"), riskInColor(ShortRisk(k.Behavior.RiskLevel))) evidence = darkenText(evidence) } diff --git a/pkg/render/terminal_brief.go b/pkg/render/terminal_brief.go index d60897a62..78d662ea9 100644 --- a/pkg/render/terminal_brief.go +++ b/pkg/render/terminal_brief.go @@ -15,7 +15,6 @@ import ( "context" "fmt" "io" - "strings" "github.com/chainguard-dev/malcontent/pkg/malcontent" "github.com/fatih/color" @@ -29,19 +28,8 @@ func NewTerminalBrief(w io.Writer) TerminalBrief { return TerminalBrief{w: w} } -func briefRiskColor(level string) string { - switch level { - case "LOW": - return color.HiGreenString("LOW") - case "MEDIUM", "MED": - return color.HiYellowString("MED") - case "HIGH": - return color.HiRedString("HIGH") - case "CRITICAL", "CRIT": - return color.HiMagentaString("CRIT") - default: - return color.WhiteString(level) - } +func (r TerminalBrief) Scanning(_ context.Context, path string) { + fmt.Fprintf(r.w, "πŸ”Ž Scanning %q\n", path) } func (r TerminalBrief) File(_ context.Context, fr *malcontent.FileReport) error { @@ -49,13 +37,12 @@ func (r TerminalBrief) File(_ context.Context, fr *malcontent.FileReport) error return nil } - reasons := []string{} + fmt.Fprintf(r.w, "β”œβ”€β”€ πŸ“„ %s %s%s%s\n", fr.Path, color.HiBlackString("["), riskInColor(fr.RiskLevel), color.HiBlackString("]")) + for _, b := range fr.Behaviors { - reasons = append(reasons, fmt.Sprintf("%s %s%s%s\n", color.HiYellowString(b.ID), color.HiBlackString("("), b.Description, color.HiBlackString(")"))) + fmt.Fprintf(r.w, "β”‚ %s %s: %s\n", riskEmoji(fr.RiskScore), riskColor(fr.RiskLevel, b.ID), b.Description) } - fmt.Fprintf(r.w, "%s%s%s %s: \n%s%s\n", color.HiBlackString("["), briefRiskColor(fr.RiskLevel), color.HiBlackString("]"), color.HiGreenString(fr.Path), - color.HiBlackString("- "), strings.Join(reasons, color.HiBlackString("- "))) return nil } diff --git a/pkg/render/yaml.go b/pkg/render/yaml.go index 0d188c3bd..a85afa012 100644 --- a/pkg/render/yaml.go +++ b/pkg/render/yaml.go @@ -20,6 +20,8 @@ func NewYAML(w io.Writer) YAML { return YAML{w: w} } +func (r YAML) Scanning(_ context.Context, _ string) {} + func (r YAML) File(_ context.Context, _ *malcontent.FileReport) error { return nil }