diff --git a/docs/getting-started.md b/docs/getting-started.md index 47f1467b8..8398e706e 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -102,6 +102,7 @@ Usage: terrascan scan [flags] Flags: + --config-only will output resource config (should only be used for debugging purposes) -h, --help help for scan -d, --iac-dir string path to a directory containing one or more IaC files (default ".") -f, --iac-file string path to a single IaC file @@ -121,6 +122,54 @@ By default Terrascan will output YAML. This can be changed to JSON or XML by usi Terrascan will exit 3 if any issues are found. +### CLI Output types +#### Violations +Terrascan's default output is a list of violations present in the scanned IaC. +``` Bash +$ terrascan scan -t aws +results: + violations: + - rule_name: scanOnPushDisabled + description: Unscanned images may contain vulnerabilities + rule_id: AWS.ECR.DataSecurity.High.0578 + severity: MEDIUM + category: Data Security + resource_name: scanOnPushDisabled + resource_type: aws_ecr_repository + file: ecr.tf + line: 1 + count: + low: 0 + medium: 1 + high: 0 + total: 1 +``` +##### Resource Config +Terrascan while scanning the IaC, loads all the IaC files, creates a list of resource configs and then processes this list to report violations. For debugging purposes, it possible to print this resource configs list as an output by providing the `--config-only` flag to the `terrascan scan` command. +``` Bash +$ terrascan scan -t aws --config-only +aws_ecr_repository: +- id: aws_ecr_repository.scanOnPushDisabled + name: scanOnPushDisabled + source: ecr.tf + line: 1 + type: aws_ecr_repository + config: + image_scanning_configuration: + - scan_on_push: + value: {} + image_tag_mutability: MUTABLE + name: test +- id: aws_ecr_repository.scanOnPushNoSet + name: scanOnPushNoSet + source: ecr.tf + line: 10 + type: aws_ecr_repository + config: + image_tag_mutability: MUTABLE + name: test +``` + ### Server mode Server mode will execute Terrascan's API server. This is useful when using Terrascan to enforce policies in a centralized way. By default the server will be started listening in port 9010 and supports the following routes: diff --git a/pkg/cli/run.go b/pkg/cli/run.go index 656e86111..b60666373 100644 --- a/pkg/cli/run.go +++ b/pkg/cli/run.go @@ -25,7 +25,7 @@ import ( // Run executes terrascan in CLI mode func Run(iacType, iacVersion, cloudType, iacFilePath, iacDirPath, configFile, - policyPath, format string) { + policyPath, format string, configOnly bool) { // create a new runtime executor for processing IaC executor, err := runtime.NewExecutor(iacType, iacVersion, cloudType, iacFilePath, @@ -35,13 +35,16 @@ func Run(iacType, iacVersion, cloudType, iacFilePath, iacDirPath, configFile, } // executor output - violations, err := executor.Execute() + results, err := executor.Execute() if err != nil { return } - writer.Write(format, violations, os.Stdout) - - if violations.ViolationStore.Count.TotalCount != 0 { + if configOnly { + writer.Write(format, results.ResourceConfig, os.Stdout) + } else { + writer.Write(format, results.Violations, os.Stdout) + } + if results.Violations.ViolationStore.Count.TotalCount != 0 { os.Exit(3) } } diff --git a/pkg/cli/scan.go b/pkg/cli/scan.go index 4eff25af2..03e80c501 100644 --- a/pkg/cli/scan.go +++ b/pkg/cli/scan.go @@ -34,6 +34,8 @@ var ( IacFilePath string // IacDirPath Path to a directory containing one or more IaC files IacDirPath string + //ConfigOnly will output resource config (should only be used for debugging purposes) + ConfigOnly bool ) var scanCmd = &cobra.Command{ @@ -51,7 +53,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) + Run(IacType, IacVersion, PolicyType, IacFilePath, IacDirPath, ConfigFile, PolicyPath, OutputType, ConfigOnly) } func init() { @@ -61,6 +63,7 @@ func init() { scanCmd.Flags().StringVarP(&IacFilePath, "iac-file", "f", "", "path to a single IaC file") scanCmd.Flags().StringVarP(&IacDirPath, "iac-dir", "d", ".", "path to a directory containing one or more IaC files") scanCmd.Flags().StringVarP(&PolicyPath, "policy-path", "p", "", "policy path directory") + scanCmd.Flags().BoolVarP(&ConfigOnly, "config-only", "", false, "will output resource config (should only be used for debugging purposes)") scanCmd.MarkFlagRequired("policy-type") RegisterCommand(rootCmd, scanCmd) } diff --git a/pkg/runtime/executor.go b/pkg/runtime/executor.go index e0c3c7757..beb00798e 100644 --- a/pkg/runtime/executor.go +++ b/pkg/runtime/executor.go @@ -20,7 +20,6 @@ import ( "go.uber.org/zap" iacProvider "github.com/accurics/terrascan/pkg/iac-providers" - "github.com/accurics/terrascan/pkg/iac-providers/output" "github.com/accurics/terrascan/pkg/notifications" "github.com/accurics/terrascan/pkg/policy" opa "github.com/accurics/terrascan/pkg/policy/opa" @@ -95,21 +94,20 @@ func (e *Executor) Init() error { } // Execute validates the inputs, processes the IaC, creates json output -func (e *Executor) Execute() (results policy.EngineOutput, err error) { +func (e *Executor) Execute() (results Output, err error) { // create results output from Iac - var normalized output.AllResourceConfigs if e.filePath != "" { - normalized, err = e.iacProvider.LoadIacFile(e.filePath) + results.ResourceConfig, err = e.iacProvider.LoadIacFile(e.filePath) } else { - normalized, err = e.iacProvider.LoadIacDir(e.dirPath) + results.ResourceConfig, err = e.iacProvider.LoadIacDir(e.dirPath) } if err != nil { return results, err } // evaluate policies - results, err = e.policyEngine.Evaluate(policy.EngineInput{InputData: &normalized}) + results.Violations, err = e.policyEngine.Evaluate(policy.EngineInput{InputData: &results.ResourceConfig}) if err != nil { return results, err } diff --git a/pkg/runtime/types.go b/pkg/runtime/types.go new file mode 100644 index 000000000..a2e129691 --- /dev/null +++ b/pkg/runtime/types.go @@ -0,0 +1,28 @@ +/* + 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 runtime + +import ( + "github.com/accurics/terrascan/pkg/iac-providers/output" + "github.com/accurics/terrascan/pkg/policy" +) + +// Output is the runtime engine output +type Output struct { + ResourceConfig output.AllResourceConfigs + Violations policy.EngineOutput +} diff --git a/pkg/writer/json.go b/pkg/writer/json.go index 4df22391b..6fbae649f 100644 --- a/pkg/writer/json.go +++ b/pkg/writer/json.go @@ -19,8 +19,6 @@ package writer import ( "encoding/json" "io" - - "github.com/accurics/terrascan/pkg/policy" ) const ( @@ -32,7 +30,7 @@ func init() { } // JSONWriter prints data in JSON format -func JSONWriter(data policy.EngineOutput, writer io.Writer) error { +func JSONWriter(data interface{}, writer io.Writer) error { j, _ := json.MarshalIndent(data, "", " ") writer.Write(j) writer.Write([]byte{'\n'}) diff --git a/pkg/writer/register.go b/pkg/writer/register.go index 55fe7b173..0beb611a6 100644 --- a/pkg/writer/register.go +++ b/pkg/writer/register.go @@ -18,17 +18,15 @@ package writer import ( "io" - - "github.com/accurics/terrascan/pkg/policy" ) // supportedFormat data type for supported formats type supportedFormat string // writerMap stores mapping of supported writer formats with respective functions -var writerMap = make(map[supportedFormat](func(policy.EngineOutput, io.Writer) error)) +var writerMap = make(map[supportedFormat](func(interface{}, io.Writer) error)) // RegisterWriter registers a writer for terrascan -func RegisterWriter(format supportedFormat, writerFunc func(policy.EngineOutput, io.Writer) error) { +func RegisterWriter(format supportedFormat, writerFunc func(interface{}, io.Writer) error) { writerMap[format] = writerFunc } diff --git a/pkg/writer/writer.go b/pkg/writer/writer.go index 3773fee7f..a4ef7f964 100644 --- a/pkg/writer/writer.go +++ b/pkg/writer/writer.go @@ -20,7 +20,6 @@ import ( "fmt" "io" - "github.com/accurics/terrascan/pkg/policy" "go.uber.org/zap" ) @@ -29,7 +28,7 @@ var ( ) // Write method writes in the given format using the respective writer func -func Write(format string, data policy.EngineOutput, writer io.Writer) error { +func Write(format string, data interface{}, writer io.Writer) error { writerFunc, present := writerMap[supportedFormat(format)] if !present { diff --git a/pkg/writer/xml.go b/pkg/writer/xml.go index c86e451bb..8d80f950a 100644 --- a/pkg/writer/xml.go +++ b/pkg/writer/xml.go @@ -20,7 +20,6 @@ import ( "encoding/xml" "io" - "github.com/accurics/terrascan/pkg/policy" "go.uber.org/zap" ) @@ -33,7 +32,7 @@ func init() { } // XMLWriter prints data in XML format -func XMLWriter(data policy.EngineOutput, writer io.Writer) error { +func XMLWriter(data interface{}, writer io.Writer) error { j, err := xml.MarshalIndent(data, "", " ") if err != nil { zap.S().Errorf("failed to write XML output. error: '%v'", err) diff --git a/pkg/writer/yaml.go b/pkg/writer/yaml.go index 77346109d..2d7f8d7ca 100644 --- a/pkg/writer/yaml.go +++ b/pkg/writer/yaml.go @@ -19,7 +19,6 @@ package writer import ( "io" - "github.com/accurics/terrascan/pkg/policy" "gopkg.in/yaml.v2" ) @@ -32,7 +31,7 @@ func init() { } // YAMLWriter prints data in YAML format -func YAMLWriter(data policy.EngineOutput, writer io.Writer) error { +func YAMLWriter(data interface{}, writer io.Writer) error { j, _ := yaml.Marshal(data) writer.Write(j) writer.Write([]byte{'\n'})