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

Fixes incorrect filepath reporting in sarif output & added e2e tests for sarif output #863

Merged
merged 9 commits into from
Jun 28, 2021
Merged
Show file tree
Hide file tree
Changes from all 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
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing copyright header?


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)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method may panic. os.Stat() returns nil file info on an error and we are just logging the error and not returning.

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) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to use AreEqualJSON already present in the file?
If not, I think we can refactor AreEqualJSON func.

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