Skip to content

Commit

Permalink
human readable output for terrascan
Browse files Browse the repository at this point in the history
  • Loading branch information
patilpankaj212 committed Dec 11, 2020
1 parent 929e377 commit 8080e17
Show file tree
Hide file tree
Showing 9 changed files with 162 additions and 6 deletions.
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)")
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
11 changes: 9 additions & 2 deletions pkg/cli/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,10 @@ 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"
Expand All @@ -31,7 +33,7 @@ import (
// 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) {

// temp dir to download the remote repo
tempDir := filepath.Join(os.TempDir(), utils.GenRandomString(6))
Expand Down Expand Up @@ -69,7 +71,12 @@ func Run(iacType, iacVersion string, cloudType []string,
if configOnly {
writer.Write(format, results.ResourceConfig, outputWriter)
} else {
writer.Write(format, results.Violations, outputWriter)
if strings.EqualFold(format, "human") {
defaultScanResult := result.NewDefaultScanResult(iacType, iacFilePath, iacDirPath, executor.GetTotalPolicyCount(), verbose, *results.Violations.ViolationStore)
writer.Write(format, defaultScanResult, outputWriter)
} else {
writer.Write(format, results.Violations, outputWriter)
}
}

if results.Violations.ViolationStore.Count.TotalCount != 0 && flag.Lookup("test.v") == nil {
Expand Down
2 changes: 1 addition & 1 deletion pkg/cli/run_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ func TestRun(t *testing.T) {

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.configOnly, false, false)
})
}
}
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
)

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 {
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 {
policyCount := 0
for _, engine := range e.policyEngine {
opaEngine := engine.(*opa.Engine)
policyCount += opaEngine.GetRuleCount()
}
return policyCount
}
83 changes: 83 additions & 0 deletions pkg/writer/humanReadable.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
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"
"fmt"
"io"
"os"
"text/template"
)

const (
humanReadbleFormat supportedFormat = "human"

defaultTemplate string = `
{{if (gt (len .ViolationStore.Violations) 0) }}
Violation Details -
{{- $showDetails := .ShowViolationDetails}}
{{range $index, $element := .ViolationStore.Violations}}
Description{{"\t"}}:{{"\t"}}{{$element.Description}}
File{{"\t\t"}}:{{"\t"}}{{$element.File}}
Line{{"\t\t"}}:{{"\t"}}{{$element.LineNumber}}
Severity{{"\t"}}:{{"\t"}}{{$element.Severity}}
{{if $showDetails -}}
Rule Name{{"\t"}}:{{"\t"}}{{$element.RuleName}}
Rule ID{{"\t\t"}}:{{"\t"}}{{$element.RuleID}}
Resource Name{{"\t"}}:{{"\t"}}{{$element.ResourceName}}
Resource Type{{"\t"}}:{{"\t"}}{{$element.ResourceType}}
Category{{"\t"}}:{{"\t"}}{{$element.Category}}
{{end}}
-----------------------------------------------------------------------
{{end}}
{{end}}
Scan Summary -
File/Folder{{"\t\t"}}:{{"\t"}}{{.ResourcePath}}
IaC Type{{"\t\t"}}:{{"\t"}}{{.IacType}}
Scanned At{{"\t\t"}}:{{"\t"}}{{.Timestamp}}
Policies Validated{{"\t"}}:{{"\t"}}{{.TotalPolicies}}
Violated Policies{{"\t"}}:{{"\t"}}{{.ViolationStore.Count.TotalCount}}
Low{{"\t\t\t"}}:{{"\t"}}{{.ViolationStore.Count.LowCount}}
Medium{{"\t\t\t"}}:{{"\t"}}{{.ViolationStore.Count.MediumCount}}
High{{"\t\t\t"}}:{{"\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 {
fmt.Println(err)
os.Exit(1)
}

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

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

0 comments on commit 8080e17

Please sign in to comment.