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 6 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
21 changes: 19 additions & 2 deletions pkg/cli/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,25 @@ import (
"flag"
"os"
"path/filepath"
"strings"

"github.com/accurics/terrascan/pkg/downloader"
result "github.com/accurics/terrascan/pkg/results"
"github.com/accurics/terrascan/pkg/runtime"
"github.com/accurics/terrascan/pkg/utils"
"github.com/accurics/terrascan/pkg/writer"
"go.uber.org/zap"
)

const (
humanOutputFormat = "human"
jsonOutputFormat = "json"
williepaul marked this conversation as resolved.
Show resolved Hide resolved
)

// 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 @@ -67,9 +74,19 @@ func Run(iacType, iacVersion string, cloudType []string,
outputWriter := NewOutputWriter(useColors)

if configOnly {
// human readable output doesn't support --config-only flag
// if --config-only flag is set, then display json output
if strings.EqualFold(format, humanOutputFormat) {
format = jsonOutputFormat
}
patilpankaj212 marked this conversation as resolved.
Show resolved Hide resolved
writer.Write(format, results.ResourceConfig, outputWriter)
} else {
writer.Write(format, results.Violations, outputWriter)
if strings.EqualFold(format, humanOutputFormat) {
defaultScanResult := result.NewDefaultScanResult(iacType, iacFilePath, iacDirPath, executor.GetTotalPolicyCount(), verbose, *results.Violations.ViolationStore)
writer.Write(format, defaultScanResult, outputWriter)
} else {
writer.Write(format, results.Violations, outputWriter)
}
}
williepaul marked this conversation as resolved.
Show resolved Hide resolved

if results.Violations.ViolationStore.Count.TotalCount != 0 && flag.Lookup("test.v") == nil {
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)
}
5 changes: 5 additions & 0 deletions pkg/policy/opa/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -392,3 +392,8 @@ func (e *Engine) Evaluate(engineInput policy.EngineInput) (policy.EngineOutput,
e.stats.runTime = time.Since(start)
return e.results, nil
}

// GetRuleCount will return the ruleCount value
func (e Engine) GetRuleCount() int {
return e.stats.ruleCount
}
47 changes: 47 additions & 0 deletions pkg/results/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@

package results

import (
"time"

"github.com/accurics/terrascan/pkg/utils"
)

// Violation Contains data for each violation
type Violation struct {
RuleName string `json:"rule_name" yaml:"rule_name" xml:"rule_name,attr"`
Expand Down Expand Up @@ -46,6 +52,16 @@ type ViolationStore struct {
Count ViolationStats `json:"count" yaml:"count" xml:"count"`
}

// DefaultScanResult will hold the default scan summary data
type DefaultScanResult struct {
IacType string
ResourcePath string
Timestamp string
TotalPolicies int
ShowViolationDetails bool
ViolationStore
}

// Add adds two ViolationStores
func (vs ViolationStore) Add(extra ViolationStore) ViolationStore {
// Just concatenate the slices, since order shouldn't be important
Expand All @@ -59,3 +75,34 @@ func (vs ViolationStore) Add(extra ViolationStore) ViolationStore {

return vs
}

// NewDefaultScanResult will initialize DefaultScanResult
func NewDefaultScanResult(iacType, iacFilePath, iacDirPath string, totalPolicyCount int, verbose bool, violationStore ViolationStore) *DefaultScanResult {
williepaul marked this conversation as resolved.
Show resolved Hide resolved
sr := new(DefaultScanResult)

if iacType == "" {
// the default scan type is terraform
sr.IacType = "terraform"
} else {
sr.IacType = iacType
}

if iacFilePath != "" {
// can skip the error as the file validation is already done
// while executor is initialized
filePath, _ := utils.GetAbsPath(iacFilePath)
sr.ResourcePath = filePath
} else {
// can skip the error as the directory validation is already done
// while executor is initialized
dirPath, _ := utils.GetAbsPath(iacDirPath)
sr.ResourcePath = dirPath
}
sr.ShowViolationDetails = verbose
sr.TotalPolicies = totalPolicyCount
sr.ViolationStore = violationStore
// set current time as scan time
sr.Timestamp = time.Now().UTC().String()

return sr
}
10 changes: 10 additions & 0 deletions pkg/runtime/executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,3 +131,13 @@ func (e *Executor) Execute() (results Output, err error) {
// successful
return results, nil
}

// GetTotalPolicyCount will return the total count of policies in all engines
func (e Executor) GetTotalPolicyCount() int {
williepaul marked this conversation as resolved.
Show resolved Hide resolved
policyCount := 0
for _, engine := range e.policyEngine {
opaEngine := engine.(*opa.Engine)
patilpankaj212 marked this conversation as resolved.
Show resolved Hide resolved
policyCount += opaEngine.GetRuleCount()
}
return policyCount
}
30 changes: 16 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,18 @@ 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"},
{"count", ""}: {"Bg#ccc|Fg#000", ""},
{`[rR]ule[_\s][nN]ame`, defaultValuePattern}: {"Bg#ccc|Fg#000", ""},
{"File/Folder", defaultValuePattern}: {"", "Fg#00768B|Bold"},
{"Policies Validated", defaultValuePattern}: {"Bg#ccc|Fg#000", ""},
}

func init() {
Expand Down Expand Up @@ -131,9 +133,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
83 changes: 83 additions & 0 deletions pkg/writer/humanReadable.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
williepaul marked this conversation as resolved.
Show resolved Hide resolved
Copyright (C) 2020 Accurics, Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package writer

import (
"bytes"
"io"
"text/template"

"go.uber.org/zap"
)

const (
humanReadbleFormat supportedFormat = "human"

defaultTemplate string = `
{{if (gt (len .ViolationStore.Violations) 0) }}
Violation Details -
{{- $showDetails := .ShowViolationDetails}}
{{range $index, $element := .ViolationStore.Violations}}
{{printf "%-15v" "Description"}}:{{"\t"}}{{$element.Description}}
{{printf "%-15v" "File"}}:{{"\t"}}{{$element.File}}
{{printf "%-15v" "Line"}}:{{"\t"}}{{$element.LineNumber}}
{{printf "%-15v" "Severity"}}:{{"\t"}}{{$element.Severity}}
{{if $showDetails -}}
{{printf "%-15v" "Rule Name"}}:{{"\t"}}{{$element.RuleName}}
{{printf "%-15v" "Rule ID"}}:{{"\t"}}{{$element.RuleID}}
{{printf "%-15v" "Resource Name"}}:{{"\t"}}{{if $element.ResourceName}}{{$element.ResourceName}}{{else}}""{{end}}
{{printf "%-15v" "Resource Type"}}:{{"\t"}}{{$element.ResourceType}}
{{printf "%-15v" "Category"}}:{{"\t"}}{{$element.Category}}
{{end}}
-----------------------------------------------------------------------
{{end}}
{{end}}
Scan Summary -

{{printf "%-20v" "File/Folder"}}:{{"\t"}}{{.ResourcePath}}
{{printf "%-20v" "IaC Type"}}:{{"\t"}}{{.IacType}}
{{printf "%-20v" "Scanned At"}}:{{"\t"}}{{.Timestamp}}
{{printf "%-20v" "Policies Validated"}}:{{"\t"}}{{.TotalPolicies}}
{{printf "%-20v" "Violated Policies"}}:{{"\t"}}{{.ViolationStore.Count.TotalCount}}
{{printf "%-20v" "Low"}}:{{"\t"}}{{.ViolationStore.Count.LowCount}}
{{printf "%-20v" "Medium"}}:{{"\t"}}{{.ViolationStore.Count.MediumCount}}
{{printf "%-20v" "High"}}:{{"\t"}}{{.ViolationStore.Count.HighCount}}
`
)

func init() {
RegisterWriter(humanReadbleFormat, HumanReadbleWriter)
}

// HumanReadbleWriter display scan summary in human readable format
func HumanReadbleWriter(data interface{}, writer io.Writer) error {
tmpl, err := template.New("Report").Parse(defaultTemplate)
if err != nil {
zap.S().Errorf("failed to write human readable output. error: '%v'", err)
return err
}

buffer := bytes.Buffer{}
tmpl.Execute(&buffer, data)

_, err = writer.Write(buffer.Bytes())
if err != nil {
return err
}
writer.Write([]byte{'\n'})
return nil
}
Loading