Skip to content

Commit

Permalink
feat: output report to file + different formats (#71)
Browse files Browse the repository at this point in the history
close #40
  • Loading branch information
cx-monicac authored May 17, 2023
1 parent a549abe commit 48adfd8
Show file tree
Hide file tree
Showing 10 changed files with 303 additions and 46 deletions.
45 changes: 40 additions & 5 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ package cmd

import (
"fmt"
"github.com/checkmarx/2ms/config"
"os"
"path/filepath"
"strings"

"sync"
Expand All @@ -17,13 +19,17 @@ import (
"github.com/spf13/cobra"
)

const timeSleepInterval = 50

var Version = "0.0.0"

const (
tagsFlagName = "tags"
logLevelFlagName = "log-level"
timeSleepInterval = 50
tagsFlagName = "tags"
logLevelFlagName = "log-level"
reportPath = "report-path"
stdoutFormat = "stdout-format"
jsonFormat = "json"
yamlFormat = "yaml"
sarifFormat = "sarif"
)

var rootCmd = &cobra.Command{
Expand Down Expand Up @@ -76,6 +82,8 @@ func Execute() {
cobra.OnInitialize(initLog)
rootCmd.PersistentFlags().StringSlice(tagsFlagName, []string{"all"}, "select rules to be applied")
rootCmd.PersistentFlags().String(logLevelFlagName, "info", "log level (trace, debug, info, warn, error, fatal)")
rootCmd.PersistentFlags().StringSlice(reportPath, []string{""}, "path to generate report files. The output format will be determined by the file extension (.json, .yaml, .sarif)")
rootCmd.PersistentFlags().String(stdoutFormat, "yaml", "stdout output format, available formats are: json, yaml, sarif")

rootCmd.PersistentPreRun = preRun
rootCmd.PersistentPostRun = postRun
Expand Down Expand Up @@ -111,6 +119,20 @@ func validateTags(tags []string) {
}
}

func validateFormat(stdout string, reportPath []string) {
if !(strings.EqualFold(stdout, yamlFormat) || strings.EqualFold(stdout, jsonFormat) || strings.EqualFold(stdout, sarifFormat)) {
log.Fatal().Msgf(`invalid output format: %s, available formats are: json, yaml and sarif`, stdout)
}
for _, path := range reportPath {

fileExtension := filepath.Ext(path)
format := strings.TrimPrefix(fileExtension, ".")
if !(strings.EqualFold(format, yamlFormat) || strings.EqualFold(format, jsonFormat) || strings.EqualFold(format, sarifFormat)) {
log.Fatal().Msgf(`invalid report extension: %s, available extensions are: json, yaml and sarif`, format)
}
}
}

func preRun(cmd *cobra.Command, args []string) {
tags, err := cmd.Flags().GetStringSlice(tagsFlagName)
if err != nil {
Expand Down Expand Up @@ -144,13 +166,26 @@ func preRun(cmd *cobra.Command, args []string) {
func postRun(cmd *cobra.Command, args []string) {
channels.WaitGroup.Wait()

reportPath, _ := cmd.Flags().GetStringSlice(reportPath)
stdoutFormat, _ := cmd.Flags().GetString(stdoutFormat)

validateFormat(stdoutFormat, reportPath)

cfg := config.LoadConfig("2ms", Version)

// Wait for last secret to be added to report
time.Sleep(time.Millisecond * timeSleepInterval)

// -------------------------------------
// Show Report
if report.TotalItemsScanned > 0 {
report.ShowReport()
report.ShowReport(stdoutFormat, cfg)
if len(reportPath) > 0 {
err := report.WriteFile(reportPath, cfg)
if err != nil {
log.Error().Msgf("Failed to create report file with error: %s", err)
}
}
} else {
log.Error().Msg("Scan completed with empty content")
os.Exit(0)
Expand Down
10 changes: 10 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package config

type Config struct {
Name string
Version string
}

func LoadConfig(name string, version string) *Config {
return &Config{Name: name, Version: version}
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ require (
github.com/spf13/cobra v1.6.1
github.com/stretchr/testify v1.8.1
github.com/zricethezav/gitleaks/v8 v8.16.1
gopkg.in/yaml.v2 v2.4.0
)

require (
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -524,6 +524,8 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
Expand Down
15 changes: 15 additions & 0 deletions reporting/json.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package reporting

import (
"encoding/json"
"log"
)

func writeJson(report Report) string {
jsonReport, err := json.MarshalIndent(report, "", " ")
if err != nil {
log.Fatalf("failed to create Json report with error: %v", err)
}

return string(jsonReport)
}
85 changes: 48 additions & 37 deletions reporting/report.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,33 @@ package reporting

import (
"fmt"
"github.com/checkmarx/2ms/config"
"os"
"path/filepath"
"strings"
)

const (
jsonFormat = "json"
yamlFormat = "yaml"
sarifFormat = "sarif"
)

type Report struct {
Results map[string][]Secret
TotalItemsScanned int
TotalSecretsFound int
TotalItemsScanned int `json:"totalItemsScanned"`
TotalSecretsFound int `json:"totalSecretsFound"`
Results map[string][]Secret `json:"results"`
}

type Secret struct {
ID string
Description string
StartLine int
EndLine int
StartColumn int
EndColumn int
Value string
ID string `json:"id"`
Source string `json:"source"`
Description string `json:"description"`
StartLine int `json:"startLine"`
EndLine int `json:"endLine"`
StartColumn int `json:"startColumn"`
EndColumn int `json:"endColumn"`
Value string `json:"value"`
}

func Init() *Report {
Expand All @@ -28,39 +37,41 @@ func Init() *Report {
}
}

func (r *Report) ShowReport() {
fmt.Println("Summary:")
fmt.Printf("- Total items scanned: %d\n", r.TotalItemsScanned)
fmt.Printf("- Total items with secrets: %d\n", len(r.Results))
if len(r.Results) > 0 {
fmt.Printf("- Total secrets found: %d\n", r.TotalSecretsFound)
fmt.Println("Detailed Report:")
r.generateResultsReport()
}
func (r *Report) ShowReport(format string, cfg *config.Config) {
output := r.getOutput(format, cfg)

fmt.Println("Summary:")
fmt.Print(output)
}

func (r *Report) generateResultsReport() {
for source, secrets := range r.Results {
itemId := getItemId(source)
fmt.Printf("- Item ID: %s\n", itemId)
fmt.Printf(" - Item Full Path: %s\n", source)
fmt.Println(" - Secrets:")
for _, secret := range secrets {
fmt.Printf(" - Type: %s\n", secret.Description)
fmt.Printf(" - Value: %.40s\n", secret.Value)
func (r *Report) WriteFile(reportPath []string, cfg *config.Config) error {
for _, path := range reportPath {
file, err := os.Create(path)
if err != nil {
return err
}

fileExtension := filepath.Ext(path)
format := strings.TrimPrefix(fileExtension, ".")
output := r.getOutput(format, cfg)

_, err = file.WriteString(output)
if err != nil {
return err
}
}
return nil
}

func getItemId(fullPath string) string {
var itemId string
if strings.Contains(fullPath, "/") {
itemLinkStrings := strings.Split(fullPath, "/")
itemId = itemLinkStrings[len(itemLinkStrings)-1]
}
if strings.Contains(fullPath, "\\") {
itemId = filepath.Base(fullPath)
func (r *Report) getOutput(format string, cfg *config.Config) string {
var output string
switch format {
case jsonFormat:
output = writeJson(*r)
case yamlFormat:
output = writeYaml(*r)
case sarifFormat:
output = writeSarif(*r, cfg)
}
return itemId
return output
}
2 changes: 1 addition & 1 deletion reporting/report_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ JPcHeO7M6FohKgcEHX84koQDN98J/L7pFlSoU7WOl6f8BKavIdeSTPS9qQYWdQuT
-----END RSA PRIVATE KEY-----`)

results := map[string][]Secret{}
report := Report{results, 1, 1}
report := Report{len(results), 1, results}
secret := Secret{Description: "bla", StartLine: 0, StartColumn: 0, EndLine: 0, EndColumn: 0, Value: secretValue}
source := "directory\\rawStringAsFile.txt"

Expand Down
Loading

0 comments on commit 48adfd8

Please sign in to comment.