Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bash output improvements #431

Merged
merged 17 commits into from
Dec 16, 2020
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pkg/cli/register.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ func setDefaultCommandIfNonePresent() {
func Execute() {
rootCmd.PersistentFlags().StringVarP(&LogLevel, "log-level", "l", "info", "log level (debug, info, warn, error, panic, fatal)")
rootCmd.PersistentFlags().StringVarP(&LogType, "log-type", "x", "console", "log output type (console, json)")
rootCmd.PersistentFlags().StringVarP(&OutputType, "output", "o", "yaml", "output type (json, yaml, xml)")
rootCmd.PersistentFlags().StringVarP(&OutputType, "output", "o", "human", "output type (human, json, yaml, xml)")
patilpankaj212 marked this conversation as resolved.
Show resolved Hide resolved
rootCmd.PersistentFlags().StringVarP(&ConfigFile, "config-path", "c", "", "config file path")

// Function to execute before processing commands
Expand Down
2 changes: 1 addition & 1 deletion pkg/cli/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ var (
LogLevel string
// LogType Logging output type (console, json)
LogType string
// OutputType Violation output type (text, json, yaml, xml)
// OutputType Violation output type (human, json, yaml, xml)
OutputType string
// ConfigFile Config file path
ConfigFile string
Expand Down
19 changes: 17 additions & 2 deletions pkg/cli/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"flag"
"os"
"path/filepath"
"strings"

"github.com/accurics/terrascan/pkg/downloader"
"github.com/accurics/terrascan/pkg/runtime"
Expand All @@ -28,10 +29,14 @@ import (
"go.uber.org/zap"
)

const (
humanOutputFormat = "human"
)

// Run executes terrascan in CLI mode
func Run(iacType, iacVersion string, cloudType []string,
iacFilePath, iacDirPath, configFile string, policyPath []string,
format, remoteType, remoteURL string, configOnly, useColors bool) {
format, remoteType, remoteURL string, configOnly, useColors, verbose bool) {
patilpankaj212 marked this conversation as resolved.
Show resolved Hide resolved

// temp dir to download the remote repo
tempDir := filepath.Join(os.TempDir(), utils.GenRandomString(6))
Expand Down Expand Up @@ -64,15 +69,25 @@ func Run(iacType, iacVersion string, cloudType []string,
return
}

// add verbose flag to the scan summary
results.Violations.ViolationStore.Summary.ShowViolationDetails = verbose

outputWriter := NewOutputWriter(useColors)

if configOnly {
// human readable output doesn't support --config-only flag
// if --config-only flag is set, then exit with an error
// asking the user to use yaml or json output format
if strings.EqualFold(format, humanOutputFormat) {
zap.S().Error("please use yaml or json output format when using --config-only flag")
return
}
writer.Write(format, results.ResourceConfig, outputWriter)
} else {
writer.Write(format, results.Violations, outputWriter)
}

if results.Violations.ViolationStore.Count.TotalCount != 0 && flag.Lookup("test.v") == nil {
if results.Violations.ViolationStore.Summary.ViolatedPolicies != 0 && flag.Lookup("test.v") == nil {
os.RemoveAll(tempDir)
os.Exit(3)
}
Expand Down
17 changes: 16 additions & 1 deletion pkg/cli/run_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,12 @@ func TestRun(t *testing.T) {
iacType string
iacVersion string
cloudType []string
format string
iacFilePath string
iacDirPath string
configFile string
configOnly bool
verbose bool
stdOut string
want string
wantErr error
Expand All @@ -56,11 +58,24 @@ func TestRun(t *testing.T) {
iacFilePath: "testdata/run-test/config-only.yaml",
configOnly: true,
},
{
name: "config-only flag true with human readable format",
cloudType: []string{"terraform"},
iacFilePath: "testdata/run-test/config-only.tf",
configOnly: true,
format: "human",
},
{
name: "config-only flag false with human readable format",
cloudType: []string{"k8s"},
iacFilePath: "testdata/run-test/config-only.yaml",
format: "human",
},
}

for _, tt := range table {
t.Run(tt.name, func(t *testing.T) {
Run(tt.iacType, tt.iacVersion, tt.cloudType, tt.iacFilePath, tt.iacDirPath, tt.configFile, []string{}, "", "", "", tt.configOnly, false)
Run(tt.iacType, tt.iacVersion, tt.cloudType, tt.iacFilePath, tt.iacDirPath, tt.configFile, []string{}, tt.format, "", "", tt.configOnly, false, tt.verbose)
})
}
}
6 changes: 5 additions & 1 deletion pkg/cli/scan.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ var (
// UseColors indicates whether to use color output
UseColors bool
useColors string // used for flag processing

// Verbose indicates whether to display all fields in default human readlbe output
Verbose bool
williepaul marked this conversation as resolved.
Show resolved Hide resolved
)

var scanCmd = &cobra.Command{
Expand Down Expand Up @@ -100,7 +103,7 @@ Detect compliance and security violations across Infrastructure as Code to mitig
func scan(cmd *cobra.Command, args []string) {
zap.S().Debug("running terrascan in cli mode")
Run(IacType, IacVersion, PolicyType, IacFilePath, IacDirPath, ConfigFile,
PolicyPath, OutputType, RemoteType, RemoteURL, ConfigOnly, UseColors)
PolicyPath, OutputType, RemoteType, RemoteURL, ConfigOnly, UseColors, Verbose)
}

func init() {
Expand All @@ -115,5 +118,6 @@ func init() {
scanCmd.Flags().BoolVarP(&ConfigOnly, "config-only", "", false, "will output resource config (should only be used for debugging purposes)")
// flag passes a string, but we normalize to bool in PreRun
scanCmd.Flags().StringVar(&useColors, "use-colors", "auto", "color output (auto, t, f)")
scanCmd.Flags().BoolVarP(&Verbose, "verbose", "v", false, "will show violations with details (applicable for default output)")
RegisterCommand(rootCmd, scanCmd)
}
11 changes: 7 additions & 4 deletions pkg/policy/opa/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -303,16 +303,16 @@ func (e *Engine) reportViolation(regoData *RegoData, resource *output.ResourceCo

severity := regoData.Metadata.Severity
if strings.ToLower(severity) == "high" {
e.results.ViolationStore.Count.HighCount++
e.results.ViolationStore.Summary.HighCount++
} else if strings.ToLower(severity) == "medium" {
e.results.ViolationStore.Count.MediumCount++
e.results.ViolationStore.Summary.MediumCount++
} else if strings.ToLower(severity) == "low" {
e.results.ViolationStore.Count.LowCount++
e.results.ViolationStore.Summary.LowCount++
} else {
zap.S().Warn("invalid severity found in rule definition",
zap.String("rule id", violation.RuleID), zap.String("severity", severity))
}
e.results.ViolationStore.Count.TotalCount++
e.results.ViolationStore.Summary.ViolatedPolicies++

e.results.ViolationStore.AddResult(&violation)
}
Expand Down Expand Up @@ -390,5 +390,8 @@ func (e *Engine) Evaluate(engineInput policy.EngineInput) (policy.EngineOutput,
}

e.stats.runTime = time.Since(start)

// add the rule count of the policy engine to result summary
e.results.ViolationStore.Summary.TotalPolicies += e.stats.ruleCount
return e.results, nil
}
2 changes: 1 addition & 1 deletion pkg/policy/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,6 @@ func (me EngineOutput) AsViolationStore() results.ViolationStore {
}
return results.ViolationStore{
Violations: me.Violations,
Count: me.Count,
Summary: me.Summary,
}
}
48 changes: 33 additions & 15 deletions pkg/results/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@

package results

import (
"time"
)

// Violation Contains data for each violation
type Violation struct {
RuleName string `json:"rule_name" yaml:"rule_name" xml:"rule_name,attr"`
Expand All @@ -32,30 +36,44 @@ type Violation struct {
LineNumber int `json:"line" yaml:"line" xml:"line,attr"`
}

// ViolationStats Contains stats related to the violation data
type ViolationStats struct {
LowCount int `json:"low" yaml:"low" xml:"low,attr"`
MediumCount int `json:"medium" yaml:"medium" xml:"medium,attr"`
HighCount int `json:"high" yaml:"high" xml:"high,attr"`
TotalCount int `json:"total" yaml:"total" xml:"total,attr"`
}

// ViolationStore Storage area for violation data
type ViolationStore struct {
Violations []*Violation `json:"violations" yaml:"violations" xml:"violations>violation"`
Count ViolationStats `json:"count" yaml:"count" xml:"count"`
Violations []*Violation `json:"violations" yaml:"violations" xml:"violations>violation"`
Summary ScanSummary `json:"scan_summary" yaml:"scan_summary" xml:"scan_summary"`
}

// ScanSummary will hold the default scan summary data
type ScanSummary struct {
ResourcePath string `json:"file/folder" yaml:"file/folder" xml:"file/folder,attr"`
IacType string `json:"iac_type" yaml:"iac_type" xml:"iac_type,attr"`
Timestamp string `json:"scanned_at" yaml:"scanned_at" xml:"scanned_at,attr"`
ShowViolationDetails bool `json:"-" yaml:"-" xml:"-"`
TotalPolicies int `json:"policies_validated" yaml:"policies_validated" xml:"policies_validated,attr"`
ViolatedPolicies int `json:"violated_policies" yaml:"violated_policies" xml:"violated_policies,attr"`
LowCount int `json:"low" yaml:"low" xml:"low,attr"`
MediumCount int `json:"medium" yaml:"medium" xml:"medium,attr"`
HighCount int `json:"high" yaml:"high" xml:"high,attr"`
}

// Add adds two ViolationStores
func (vs ViolationStore) Add(extra ViolationStore) ViolationStore {
// Just concatenate the slices, since order shouldn't be important
vs.Violations = append(vs.Violations, extra.Violations...)

// Add the counts
vs.Count.LowCount += extra.Count.LowCount
vs.Count.MediumCount += extra.Count.MediumCount
vs.Count.HighCount += extra.Count.HighCount
vs.Count.TotalCount += extra.Count.TotalCount
// Add the scan summary
vs.Summary.LowCount += extra.Summary.LowCount
vs.Summary.MediumCount += extra.Summary.MediumCount
vs.Summary.HighCount += extra.Summary.HighCount
vs.Summary.ViolatedPolicies += extra.Summary.ViolatedPolicies
vs.Summary.TotalPolicies += extra.Summary.TotalPolicies

return vs
}

// AddSummary will update the summary with remaining details
func (vs *ViolationStore) AddSummary(iacType, iacResourcePath string) {

vs.Summary.IacType = iacType
vs.Summary.ResourcePath = iacResourcePath
vs.Summary.Timestamp = time.Now().UTC().String()
}
8 changes: 8 additions & 0 deletions pkg/runtime/executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,14 @@ func (e *Executor) Execute() (results Output, err error) {

results.Violations = policy.EngineOutputFromViolationStore(&violations)

resourcePath := e.filePath
if resourcePath == "" {
resourcePath = e.dirPath
}

// add other summary details after policies are evaluated
results.Violations.ViolationStore.AddSummary(e.iacType, resourcePath)

// send notifications, if configured
if err = e.SendNotifications(results); err != nil {
return results, err
Expand Down
29 changes: 15 additions & 14 deletions pkg/termcolor/colorpatterns.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ package termcolor
import (
"encoding/json"
"fmt"
"go.uber.org/zap"
"io/ioutil"
"os"
"regexp"

"go.uber.org/zap"
)

var (
Expand Down Expand Up @@ -53,17 +54,17 @@ type colorPatternSerialized struct {
**/

var defaultColorPatterns = map[FieldSpec]FieldStyle{
{"description", defaultValuePattern}: {"", "Fg#0c0"},
{"severity", defaultValuePattern}: {"", "?HIGH=Fg#f00?MEDIUM=Fg#c84?LOW=Fg#cc0"},
{"resource_name", defaultValuePattern}: {"", "Fg#0ff|Bold"},
{"resource_type", defaultValuePattern}: {"", "Fg#0cc"},
{"file", defaultValuePattern}: {"", "Fg#fff|Bold"},
{"low", `\d+`}: {"Fg#cc0", "Fg#cc0"},
{"medium", `\d+`}: {"Fg#c84", "Fg#c84"},
{"high", `\d+`}: {"Fg#f00", "Fg#f00"},

{"count", ""}: {"Bg#ccc|Fg#000", ""},
{"rule_name", defaultValuePattern}: {"Bg#ccc|Fg#000", ""},
{"[dD]escription", defaultValuePattern}: {"", "Fg#0c0"},
{"[sS]everity", defaultValuePattern}: {"", "?HIGH=Fg#f00?MEDIUM=Fg#c84?LOW=Fg#cc0"},
{`[rR]esource[_\s][nN]ame`, defaultValuePattern}: {"", "Fg#0ff|Bold"},
{`[rR]esource[_\s][tT]ype`, defaultValuePattern}: {"", "Fg#0cc"},
{"[fF]ile", defaultValuePattern}: {"", "Fg#00768B|Bold"},
{"[lL]ow", `\d+`}: {"Fg#cc0", "Fg#cc0"},
{"[mM]edium", `\d+`}: {"Fg#c84", "Fg#c84"},
{"[hH]igh", `\d+`}: {"Fg#f00", "Fg#f00"},
{`[rR]ule[_\s][nN]ame`, defaultValuePattern}: {"Bg#ccc|Fg#000", ""},
{"[fF]ile/[fF]older", defaultValuePattern}: {"", "Fg#00768B|Bold"},
{`[pP]olicies[_\s][vV]alidated`, defaultValuePattern}: {"Bg#ccc|Fg#000", ""},
}

func init() {
Expand Down Expand Up @@ -131,9 +132,9 @@ func GetColorPatterns() map[*regexp.Regexp]FieldStyle {

/* rePtn should process a whole line and have 5 subgroups */
if len(ptn.ValuePattern) == 0 {
rePtn = fmt.Sprintf(`^([-\s]*"?)(%s)("?:\s*?)()(.*?)\s*$`, ptn.KeyPattern)
rePtn = fmt.Sprintf(`^([-\s]*"?)(%s)("?\s*:\s*?)()(.*?)\s*$`, ptn.KeyPattern)
} else {
rePtn = fmt.Sprintf(`^([-\s]*"?)(%s)("?: "?)(%s)("?,?)\s*$`, ptn.KeyPattern, ptn.ValuePattern)
rePtn = fmt.Sprintf(`^([-\s]*"?)(%s)("?\s*:\s*"?)(%s)("?,?)\s*$`, ptn.KeyPattern, ptn.ValuePattern)
}
ColorPatterns[regexp.MustCompile("(?m)"+rePtn)] = fmts
}
Expand Down
8 changes: 4 additions & 4 deletions pkg/termcolor/writer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,8 @@ func TestYAMLFileIsColorized(t *testing.T) {
verifyLineWithStringIsColorized("file", yamlData.String(), t)
}

func TestYAMLCountIsColorized(t *testing.T) {
verifyLineWithStringIsColorized("count", yamlData.String(), t)
func TestYAMLPoliciesValidatedIsColorized(t *testing.T) {
verifyLineWithStringIsColorized("policies_validated", yamlData.String(), t)
}
func TestYAMLCountLowIsColorized(t *testing.T) {
verifyLineWithStringIsColorized("low", yamlData.String(), t)
Expand Down Expand Up @@ -162,8 +162,8 @@ func TestJSONFileIsColorized(t *testing.T) {
verifyLineWithStringIsColorized("file", jsonData.String(), t)
}

func TestJSONCountIsColorized(t *testing.T) {
verifyLineWithStringIsColorized("count", jsonData.String(), t)
func TestJSONPoliciesValidatedIsColorized(t *testing.T) {
verifyLineWithStringIsColorized("policies_validated", jsonData.String(), t)
}
func TestJSONCountLowIsColorized(t *testing.T) {
verifyLineWithStringIsColorized("low", jsonData.String(), t)
Expand Down
Loading