diff --git a/go.mod b/go.mod index bbddf9d18..eaa1eed38 100644 --- a/go.mod +++ b/go.mod @@ -38,8 +38,8 @@ require ( github.com/spf13/cobra v1.1.1 github.com/zclconf/go-cty v1.8.2 go.uber.org/zap v1.16.0 - golang.org/x/sys v0.0.0-20210611083646-a4fc73990273 - golang.org/x/tools v0.1.3 // indirect + golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 + golang.org/x/tools v0.1.4 // indirect gopkg.in/src-d/go-git.v4 v4.13.1 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b helm.sh/helm/v3 v3.4.0 diff --git a/go.sum b/go.sum index 6bc42f736..dcc81ceab 100644 --- a/go.sum +++ b/go.sum @@ -1177,10 +1177,8 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210601080250-7ecdf8ef093b h1:qh4f65QIVFjq9eBURLEYWqaEXmOyqdUyiBSgaXWccWk= golang.org/x/sys v0.0.0-20210601080250-7ecdf8ef093b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210608053332-aa57babbf139 h1:C+AwYEtBp/VQwoLntUmQ/yx3MS9vmZaKNdw5eOpoQe8= -golang.org/x/sys v0.0.0-20210608053332-aa57babbf139/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210611083646-a4fc73990273 h1:faDu4veV+8pcThn4fewv6TVlNCezafGoC1gM/mxQLbQ= -golang.org/x/sys v0.0.0-20210611083646-a4fc73990273/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 h1:RqytpXGR1iVNX7psjB3ff8y7sNFinVFvkx1c8SjBkio= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1256,10 +1254,8 @@ golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82u golang.org/x/tools v0.0.0-20201028111035-eafbe7b904eb/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/tools v0.1.2 h1:kRBLX7v7Af8W7Gdbbc908OJcdgtK8bOz9Uaj8/F1ACA= -golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.3 h1:L69ShwSZEyCsLKoAxDKeMvLDZkumEe8gXUZAjab0tX8= -golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.4 h1:cVngSRcfgyZCzys3KYOpCFa+4dqX/Oub9tAq00ttGVs= +golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1365,7 +1361,6 @@ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLks gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20141024133853-64131543e789/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/cheggaaa/pb.v1 v1.0.27/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= @@ -1383,7 +1378,6 @@ gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/src-d/go-billy.v4 v4.3.2 h1:0SQA1pRztfTFx2miS8sA97XvooFeNOmvUenF4o0EcVg= gopkg.in/src-d/go-billy.v4 v4.3.2/go.mod h1:nDjArDMp+XMs1aFAESLRjfGSgfvoYN0hDfzEk0GjC98= -gopkg.in/src-d/go-git-fixtures.v3 v3.5.0 h1:ivZFOIltbce2Mo8IjzUHAFoq/IylO9WHhNOAJK+LsJg= gopkg.in/src-d/go-git-fixtures.v3 v3.5.0/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g= gopkg.in/src-d/go-git.v4 v4.13.1 h1:SRtFyV8Kxc0UP7aCHcijOMQGPxHSmMOPrzulQWolkYE= gopkg.in/src-d/go-git.v4 v4.13.1/go.mod h1:nx5NYcxdKxq5fpltdHnPa2Exj4Sx0EclMWZQbYDu2z8= diff --git a/pkg/utils/dir.go b/pkg/utils/dir.go index b3111385f..4c609a3f3 100644 --- a/pkg/utils/dir.go +++ b/pkg/utils/dir.go @@ -1,3 +1,19 @@ +/* + 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 utils import ( @@ -30,7 +46,7 @@ func GenerateTempDir() string { func IsDirExists(dir string) bool { _, err := os.Stat(dir) if os.IsNotExist(err) { - zap.S().Debug("Directory %s does not exist.", dir) + zap.S().Errorf("directory %s does not exist.", dir) return false } return true diff --git a/pkg/utils/file.go b/pkg/utils/file.go new file mode 100644 index 000000000..e05e4b4af --- /dev/null +++ b/pkg/utils/file.go @@ -0,0 +1,37 @@ +/* + 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 utils + +import ( + "go.uber.org/zap" + "os" +) + +// GetFileMode fetches the filemode from a file path +func GetFileMode(path string) *os.FileMode { + fi, err := os.Stat(path) + if err != nil { + if os.IsNotExist(err) { + zap.S().Errorf("file %s does not exist.", path) + } else { + zap.S().Errorf("unable to fetch file info for path %s.", path) + } + return nil + } + mode := fi.Mode() + return &mode +} diff --git a/pkg/utils/json.go b/pkg/utils/json.go index 138f6da99..95d125f0b 100644 --- a/pkg/utils/json.go +++ b/pkg/utils/json.go @@ -1,3 +1,19 @@ +/* + 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 utils import ( @@ -56,19 +72,24 @@ func LoadJSON(filePath string) ([]*IacDocument, error) { // AreEqualJSON validate if two json strings are equal func AreEqualJSON(s1, s2 string) (bool, error) { + return AreEqualJSONBytes([]byte(s1), []byte(s2)) +} + +// AreEqualJSONBytes validate if two json byte arrays are equal +func AreEqualJSONBytes(b1, b2 []byte) (bool, error) { var o1 interface{} var o2 interface{} - errmsg := "error json unmarshalling string: %s. error: %v" + errmsg := "error json unmarshalling bytes: %s. error: %v" var err error - err = json.Unmarshal([]byte(s1), &o1) + err = json.Unmarshal(b1, &o1) if err != nil { - return false, fmt.Errorf(errmsg, s1, err.Error()) + return false, fmt.Errorf(errmsg, b1, err.Error()) } - err = json.Unmarshal([]byte(s2), &o2) + err = json.Unmarshal(b2, &o2) if err != nil { - return false, fmt.Errorf(errmsg, s2, err.Error()) + return false, fmt.Errorf(errmsg, b2, err.Error()) } return reflect.DeepEqual(o1, o2), nil diff --git a/pkg/writer/sarif.go b/pkg/writer/sarif.go index 4bea5b460..d25bad505 100644 --- a/pkg/writer/sarif.go +++ b/pkg/writer/sarif.go @@ -19,8 +19,10 @@ package writer import ( "fmt" "github.com/accurics/terrascan/pkg/policy" + "github.com/accurics/terrascan/pkg/utils" "github.com/accurics/terrascan/pkg/version" "github.com/owenrumney/go-sarif/sarif" + "go.uber.org/zap" "io" "path/filepath" "strings" @@ -56,7 +58,7 @@ func SarifWriter(data interface{}, writer io.Writer) error { run.AddRule(string(passedRule.RuleID)). WithDescription(passedRule.Description).WithName(passedRule.RuleName).WithProperties(m) } - resourcePath := outputData.Summary.ResourcePath + // for each result add the rule, location and result to the report for _, violation := range outputData.Violations { m := make(map[string]string) @@ -66,9 +68,10 @@ func SarifWriter(data interface{}, writer io.Writer) error { rule := run.AddRule(string(violation.RuleID)). WithDescription(violation.Description).WithName(violation.RuleName).WithProperties(m) - absFilePath := violation.File - if !filepath.IsAbs(violation.File) { - absFilePath = filepath.Join(resourcePath, violation.File) + absFilePath, err := getAbsoluteFilePath(outputData.Summary.ResourcePath, violation.File) + + if err != nil { + return err } location := sarif.NewLocation(). @@ -99,3 +102,18 @@ func getSarifLevel(severity string) string { return m[strings.ToLower(severity)] } + +func getAbsoluteFilePath(resourcePath, filePath string) (string, error) { + if !filepath.IsAbs(resourcePath) { + resourcePath, err := filepath.Abs(resourcePath) + if err != nil { + zap.S().Errorf("unable to get absolute path for %s, error: %v", resourcePath, err) + return "", err + } + } + fileMode := utils.GetFileMode(resourcePath) + if fileMode != nil && (*fileMode).IsDir() { + return filepath.Join(resourcePath, filePath), nil + } + return resourcePath, nil +} diff --git a/pkg/writer/sarif_test.go b/pkg/writer/sarif_test.go index 4996b243a..93a99970d 100644 --- a/pkg/writer/sarif_test.go +++ b/pkg/writer/sarif_test.go @@ -12,6 +12,8 @@ import ( "github.com/accurics/terrascan/pkg/version" ) +var testpath, _ = getAbsoluteFilePath(violationsInput.Summary.ResourcePath, violationsInput.Violations[0].File) + var expectedSarifOutput1 = fmt.Sprintf(`{ "version": "2.1.0", "$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json", @@ -48,7 +50,7 @@ var expectedSarifOutput1 = fmt.Sprintf(`{ { "physicalLocation": { "artifactLocation": { - "uri": "file://test/modules/m1/main.tf" + "uri": "%s" }, "region": { "startLine": 20 @@ -66,7 +68,7 @@ var expectedSarifOutput1 = fmt.Sprintf(`{ ] } ] - }`, version.GetNumeric()) + }`, version.GetNumeric(), fmt.Sprintf("file://%s", testpath)) var expectedSarifOutput2 = fmt.Sprintf(`{ "version": "2.1.0", diff --git a/pkg/writer/test/dummy b/pkg/writer/test/dummy new file mode 100644 index 000000000..e69de29bb diff --git a/test/e2e/scan/scan_k8s_files_test.go b/test/e2e/scan/scan_k8s_files_test.go index b6c49a9e9..f73d1615f 100644 --- a/test/e2e/scan/scan_k8s_files_test.go +++ b/test/e2e/scan/scan_k8s_files_test.go @@ -17,6 +17,7 @@ package scan_test import ( + "github.com/accurics/terrascan/pkg/version" "path/filepath" scanUtils "github.com/accurics/terrascan/test/e2e/scan" @@ -26,7 +27,7 @@ import ( "github.com/onsi/gomega/gbytes" ) -var _ = Describe("Scan is run for k8s files", func() { +var _ = Describe("Scan is run for k8s directories and files", func() { BeforeEach(func() { outWriter = gbytes.NewBuffer() @@ -38,16 +39,16 @@ var _ = Describe("Scan is run for k8s files", func() { errWriter = nil }) - Context("scan iac files violating k8s policies", func() { - var policyDir, iacDir string - policyDir, err1 := filepath.Abs(policyRootRelPath) - iacDir, err2 := filepath.Abs(filepath.Join(k8sIacRelPath, "kubernetes_ingress_violation")) + var policyDir, iacDir string + policyDir, err1 := filepath.Abs(policyRootRelPath) + iacDir, err2 := filepath.Abs(filepath.Join(k8sIacRelPath, "kubernetes_ingress_violation")) - It("should not error out while getting absolute path", func() { - Expect(err1).NotTo(HaveOccurred()) - Expect(err2).NotTo(HaveOccurred()) - }) + It("should not error out while getting absolute path", func() { + Expect(err1).NotTo(HaveOccurred()) + Expect(err2).NotTo(HaveOccurred()) + }) + Context("scan iac directories violating k8s policies", func() { Context("iac type k8s will be part of all iac", func() { When("k8s files are scanned but iac type is not specified", func() { It("should scan will all iac and display violations", func() { @@ -74,6 +75,15 @@ var _ = Describe("Scan is run for k8s files", func() { }) }) + When("when output type is sarif", func() { + It("should display violations in sarif format", func() { + scanArgs := []string{"-i", "k8s", "-p", policyDir, "-d", iacDir, "-o", "sarif"} + path, _ := helper.GetAbsoluteFilePathForSarif(iacDir, "config.yaml") + golden := scanUtils.GetSarifGoldenString(scanUtils.SarifTemplateK8sTLSViolation, version.GetNumeric(), path) + scanUtils.RunScanAndAssertJSONOutputString(terrascanBinaryPath, golden, helper.ExitCodeThree, true, outWriter, errWriter, scanArgs...) + }) + }) + When("when output type is json", func() { It("should display violations in json format", func() { scanArgs := []string{"-i", "k8s", "-p", policyDir, "-d", iacDir, "-o", "json"} @@ -103,4 +113,76 @@ var _ = Describe("Scan is run for k8s files", func() { }) }) }) + + Context("scan iac files violating k8s policies", func() { + iacFile := filepath.Join(iacDir, "config.yaml") + It("should not error out while getting absolute path", func() { + Expect(err1).NotTo(HaveOccurred()) + Expect(err2).NotTo(HaveOccurred()) + }) + + Context("iac type k8s will be part of all iac", func() { + When("k8s files are scanned but iac type is not specified", func() { + It("should scan will all iac and display violations", func() { + scanArgs := []string{scanUtils.ScanCommand, "-f", iacFile} + session = helper.RunCommand(terrascanBinaryPath, outWriter, errWriter, scanArgs...) + // exit code is 1 because iac file is expected to be of terraform iac type by default, not k8s yaml + helper.ValidateExitCode(session, scanUtils.ScanTimeout, helper.ExitCodeOne) + }) + }) + }) + + k8sGoldenRelPath := filepath.Join("golden", "k8s_scans", "k8s", "kubernetes_ingress_violations") + + Context("iac type is specified as k8s", func() { + It("should scan and display violations in human output format", func() { + scanArgs := []string{"-i", "k8s", "-p", policyDir, "-f", iacFile} + scanUtils.RunScanAndAssertGoldenOutputRegex(terrascanBinaryPath, filepath.Join(k8sGoldenRelPath, "kubernetes_ingress_human.txt"), helper.ExitCodeThree, false, true, outWriter, errWriter, scanArgs...) + }) + + When("-v flag is used for verbose output", func() { + It("should display verbose output for human output format", func() { + scanArgs := []string{"-i", "k8s", "-p", policyDir, "-f", iacFile, "-v"} + scanUtils.RunScanAndAssertGoldenOutputRegex(terrascanBinaryPath, filepath.Join(k8sGoldenRelPath, "kubernetes_ingress_human_verbose.txt"), helper.ExitCodeThree, false, true, outWriter, errWriter, scanArgs...) + }) + }) + + When("when output type is sarif", func() { + It("should display violations in sarif format", func() { + scanArgs := []string{"-i", "k8s", "-p", policyDir, "-f", iacFile, "-o", "sarif"} + path, _ := helper.GetAbsoluteFilePathForSarif(iacFile, "config.yaml") + golden := scanUtils.GetSarifGoldenString(scanUtils.SarifTemplateK8sTLSViolation, version.GetNumeric(), path) + scanUtils.RunScanAndAssertJSONOutputString(terrascanBinaryPath, golden, helper.ExitCodeThree, true, outWriter, errWriter, scanArgs...) + }) + }) + + When("when output type is json", func() { + It("should display violations in json format", func() { + scanArgs := []string{"-i", "k8s", "-p", policyDir, "-f", iacFile, "-o", "json"} + scanUtils.RunScanAndAssertGoldenOutputRegex(terrascanBinaryPath, filepath.Join(k8sGoldenRelPath, "kubernetes_ingress_json.txt"), helper.ExitCodeThree, false, true, outWriter, errWriter, scanArgs...) + }) + }) + + When("when output type is yaml", func() { + It("should display violations in yaml format", func() { + scanArgs := []string{"-i", "k8s", "-p", policyDir, "-f", iacFile, "-o", "yaml"} + scanUtils.RunScanAndAssertGoldenOutputRegex(terrascanBinaryPath, filepath.Join(k8sGoldenRelPath, "kubernetes_ingress_yaml.txt"), helper.ExitCodeThree, false, true, outWriter, errWriter, scanArgs...) + }) + }) + + When("when output type is xml", func() { + It("should display violations in xml format", func() { + scanArgs := []string{"-i", "k8s", "-p", policyDir, "-f", iacFile, "-o", "xml"} + scanUtils.RunScanAndAssertGoldenOutputRegex(terrascanBinaryPath, filepath.Join(k8sGoldenRelPath, "kubernetes_ingress_xml.txt"), helper.ExitCodeThree, false, true, outWriter, errWriter, scanArgs...) + }) + }) + + When("when output type is junit-xml", func() { + It("should display violations in junit-xml format", func() { + scanArgs := []string{"-i", "k8s", "-p", policyDir, "-f", iacFile, "-o", "junit-xml"} + scanUtils.RunScanAndAssertGoldenOutputRegex(terrascanBinaryPath, filepath.Join(k8sGoldenRelPath, "kubernetes_ingress_junit_xml.txt"), helper.ExitCodeThree, true, true, outWriter, errWriter, scanArgs...) + }) + }) + }) + }) }) diff --git a/test/e2e/scan/scan_tf_files_test.go b/test/e2e/scan/scan_tf_files_test.go index d2308b9a7..1eba8579e 100644 --- a/test/e2e/scan/scan_tf_files_test.go +++ b/test/e2e/scan/scan_tf_files_test.go @@ -17,6 +17,7 @@ package scan_test import ( + "github.com/accurics/terrascan/pkg/version" "path/filepath" scanUtils "github.com/accurics/terrascan/test/e2e/scan" @@ -117,6 +118,15 @@ var _ = Describe("Scan is run for terraform files", func() { }) }) + When("output type is sarif", func() { + It("should display violations in sarif format", func() { + scanArgs := []string{"-p", policyDir, "-i", "terraform", "-d", iacDir, "-o", "sarif"} + path, _ := helper.GetAbsoluteFilePathForSarif(iacDir, "main.tf") + golden := scanUtils.GetSarifGoldenString(scanUtils.SarifTemplateAWSAMIViolation, version.GetNumeric(), path) + scanUtils.RunScanAndAssertJSONOutputString(terrascanBinaryPath, golden, helper.ExitCodeThree, true, outWriter, errWriter, scanArgs...) + }) + }) + When("output type is json and no iac type is specified", func() { Context("when iac type is not specified and a directory is specified, it will be scanned will all iac providers", func() { It("should display violations in json format, and should have iac type as 'all'", func() { diff --git a/test/e2e/scan/scan_utils.go b/test/e2e/scan/scan_utils.go index 1fa41f711..e4f549a37 100644 --- a/test/e2e/scan/scan_utils.go +++ b/test/e2e/scan/scan_utils.go @@ -17,6 +17,7 @@ package scan import ( + "fmt" "io" "path/filepath" @@ -55,6 +56,15 @@ func RunScanAndAssertJSONOutput(terrascanBinaryPath, relGoldenFilePath string, e helper.CompareActualWithGoldenJSON(session, goldenFileAbsPath, isStdOut) } +// RunScanAndAssertJSONOutputString runs the scan command with supplied paramters and compares actual and golden output +func RunScanAndAssertJSONOutputString(terrascanBinaryPath, goldenString string, exitCode int, isStdOut bool, outWriter, errWriter io.Writer, args ...string) { + argList := []string{ScanCommand} + argList = append(argList, args...) + session := helper.RunCommand(terrascanBinaryPath, outWriter, errWriter, argList...) + gomega.Eventually(session, ScanTimeout).Should(gexec.Exit(exitCode)) + helper.CompareActualWithGoldenJSONString(session, goldenString, isStdOut) +} + // RunScanAndAssertYAMLOutput runs the scan command with supplied paramters and compares actual and golden output func RunScanAndAssertYAMLOutput(terrascanBinaryPath, relGoldenFilePath string, exitCode int, isJunitXML, isStdOut bool, outWriter, errWriter io.Writer, args ...string) { session, goldenFileAbsPath := RunScanCommand(terrascanBinaryPath, relGoldenFilePath, exitCode, outWriter, errWriter, args...) @@ -84,3 +94,123 @@ func RunScanCommand(terrascanBinaryPath, relGoldenFilePath string, exitCode int, gomega.Expect(err).NotTo(gomega.HaveOccurred()) return session, goldenFileAbsPath } + +// GetSarifGoldenString gives out golden sarif format string +func GetSarifGoldenString(template, terrascanVersion string, absfilepath string) string { + return fmt.Sprintf(template, terrascanVersion, fmt.Sprintf("file://%s", absfilepath)) +} + +// SarifTemplateAWSAMIViolation string +const SarifTemplateAWSAMIViolation = `{ + "version": "2.1.0", + "$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json", + "runs": [ + { + "tool": { + "driver": { + "name": "terrascan", + "version": "%s", + "informationUri": "https://github.com/accurics/terrascan", + "rules": [ + { + "id": "AC_AWS_0001", + "name": "amiNotEncrypted", + "shortDescription": { + "text": "Enable AWS AMI Encryption" + }, + "properties": { + "category": "Encryption \u0026 KeyManagement", + "severity": "MEDIUM" + } + } + ] + } + }, + "results": [ + { + "ruleId": "AC_AWS_0001", + "level": "warning", + "message": { + "text": "Enable AWS AMI Encryption" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "%s" + }, + "region": { + "startLine": 5 + } + }, + "logicalLocations": [ + { + "name": "awsAmiEncrypted", + "kind": "aws_ami" + } + ] + } + ] + } + ] + } + ] +}` + +// SarifTemplateK8sTLSViolation string template +const SarifTemplateK8sTLSViolation = `{ + "version": "2.1.0", + "$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json", + "runs": [ + { + "tool": { + "driver": { + "name": "terrascan", + "version": "%s", + "informationUri": "https://github.com/accurics/terrascan", + "rules": [ + { + "id": "AC_K8S_0001", + "name": "noHttps", + "shortDescription": { + "text": "TLS disabled can affect the confidentiality of the data in transit" + }, + "properties": { + "category": "Network Security", + "severity": "HIGH" + } + } + ] + } + }, + "results": [ + { + "ruleId": "AC_K8S_0001", + "level": "error", + "message": { + "text": "TLS disabled can affect the confidentiality of the data in transit" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "%s" + }, + "region": { + "startLine": 1 + } + }, + "logicalLocations": [ + { + "name": "ingress-demo-disallowed", + "kind": "kubernetes_ingress" + } + ] + } + ] + } + ] + } + ] +} +` diff --git a/test/helper/helper.go b/test/helper/helper.go index f486ad768..f57c8d3e0 100644 --- a/test/helper/helper.go +++ b/test/helper/helper.go @@ -20,10 +20,12 @@ import ( "bytes" "encoding/json" "encoding/xml" + "go.uber.org/zap" "io" "io/ioutil" "os" "os/exec" + "path/filepath" "reflect" "regexp" "sort" @@ -186,6 +188,24 @@ func CompareActualWithGoldenJSON(session *gexec.Session, goldenFileAbsPath strin CompareSummaryAndViolations(sessionEngineOutput, fileDataEngineOutput) } +// CompareActualWithGoldenJSONString compares actual data with golden json string passed as parameter +func CompareActualWithGoldenJSONString(session *gexec.Session, golden string, isStdOut bool) { + goldenBytes := []byte(golden) + + var sessionBytes []byte + + if isStdOut { + sessionBytes = session.Wait().Out.Contents() + } else { + sessionBytes = session.Wait().Err.Contents() + } + + sessionBytes = bytes.TrimSpace(sessionBytes) + goldenBytes = bytes.TrimSpace(goldenBytes) + + gomega.Expect(utils.AreEqualJSONBytes(sessionBytes, goldenBytes)).To(gomega.BeTrue()) +} + // CompareActualWithGoldenYAML compares actual data with contents of golden file passed as parameter func CompareActualWithGoldenYAML(session *gexec.Session, goldenFileAbsPath string, isStdOut bool) { sessionBytes, fileBytes := GetByteData(session, goldenFileAbsPath, isStdOut) @@ -352,3 +372,19 @@ func removeFileAndRoothFromViolations(v violations) { violation.PlanRoot = "" } } + +// GetAbsoluteFilePathForSarif helper for sarif path +func GetAbsoluteFilePathForSarif(resourcePath, filePath string) (string, error) { + if !filepath.IsAbs(resourcePath) { + resourcePath, err := filepath.Abs(resourcePath) + if err != nil { + zap.S().Errorf("unable to get absolute path for %s, error: %v", resourcePath, err) + return "", err + } + } + mode := utils.GetFileMode(resourcePath) + if mode != nil && (*mode).IsDir() { + return filepath.Join(resourcePath, filePath), nil + } + return resourcePath, nil +}