Skip to content

Commit

Permalink
Fixes incorrect filepath reporting in sarif output & added e2e tests …
Browse files Browse the repository at this point in the history
…for sarif output (#863)

* fixed incorrect file path reporting with -f flag

* added tests for sarif formatted output

* fixed filepath hardcoding in unit test

* fixed tests

* fixed test

* fix gofmt

* incorporated pr reviews

* fixed go.sum

* updated golden templates with ne rule ids
  • Loading branch information
Devang Gaur authored Jun 28, 2021
1 parent a74765d commit b8fda7c
Show file tree
Hide file tree
Showing 12 changed files with 379 additions and 33 deletions.
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
14 changes: 4 additions & 10 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down Expand Up @@ -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=
Expand Down Expand Up @@ -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=
Expand All @@ -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=
Expand Down
18 changes: 17 additions & 1 deletion pkg/utils/dir.go
Original file line number Diff line number Diff line change
@@ -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 (
Expand Down Expand Up @@ -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
Expand Down
37 changes: 37 additions & 0 deletions pkg/utils/file.go
Original file line number Diff line number Diff line change
@@ -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
}
31 changes: 26 additions & 5 deletions pkg/utils/json.go
Original file line number Diff line number Diff line change
@@ -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 (
Expand Down Expand Up @@ -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
Expand Down
26 changes: 22 additions & 4 deletions pkg/writer/sarif.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
Expand All @@ -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().
Expand Down Expand Up @@ -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
}
6 changes: 4 additions & 2 deletions pkg/writer/sarif_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -48,7 +50,7 @@ var expectedSarifOutput1 = fmt.Sprintf(`{
{
"physicalLocation": {
"artifactLocation": {
"uri": "file://test/modules/m1/main.tf"
"uri": "%s"
},
"region": {
"startLine": 20
Expand All @@ -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",
Expand Down
Empty file added pkg/writer/test/dummy
Empty file.
100 changes: 91 additions & 9 deletions test/e2e/scan/scan_k8s_files_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package scan_test

import (
"github.com/accurics/terrascan/pkg/version"
"path/filepath"

scanUtils "github.com/accurics/terrascan/test/e2e/scan"
Expand All @@ -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()
Expand All @@ -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() {
Expand All @@ -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"}
Expand Down Expand Up @@ -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...)
})
})
})
})
})
Loading

0 comments on commit b8fda7c

Please sign in to comment.