From fafdda439bfc6b220e35f6fd487127d42a34665f Mon Sep 17 00:00:00 2001 From: Devang Gaur Date: Mon, 31 May 2021 15:31:29 +0530 Subject: [PATCH] add support for sarif format violation reports (#806) * added support for sarif formatted violation reports * updated sarif.level to violation.severity mapping * accomodate for --show-passed flag * added unit test * added logical location * added tool driver version property in sarif output --- go.mod | 9 +- go.sum | 22 ++- pkg/cli/register.go | 2 +- pkg/utils/json.go | 23 +++ pkg/version/version.go | 9 +- pkg/writer/sarif.go | 101 +++++++++++ pkg/writer/sarif_test.go | 159 ++++++++++++++++++ test/e2e/help/golden/help_command.txt | 2 +- test/e2e/help/golden/help_flag.txt | 2 +- test/e2e/help/golden/help_init.txt | 2 +- test/e2e/help/golden/help_scan.txt | 2 +- test/e2e/help/golden/help_server.txt | 2 +- .../help/golden/help_unsupported_command.txt | 2 +- test/e2e/help/golden/help_version.txt | 2 +- test/e2e/help/golden/no_command.txt | 2 +- 15 files changed, 321 insertions(+), 20 deletions(-) create mode 100644 pkg/writer/sarif.go create mode 100644 pkg/writer/sarif_test.go diff --git a/go.mod b/go.mod index 21e6e98b4..efbd478bf 100644 --- a/go.mod +++ b/go.mod @@ -28,18 +28,19 @@ require ( github.com/onsi/ginkgo v1.12.1 github.com/onsi/gomega v1.10.5 github.com/open-policy-agent/opa v0.22.0 + github.com/owenrumney/go-sarif v1.0.4 github.com/pelletier/go-toml v1.8.1 github.com/pkg/errors v0.9.1 github.com/spf13/afero v1.5.1 github.com/spf13/cobra v1.1.1 - github.com/zclconf/go-cty v1.7.1 + github.com/zclconf/go-cty v1.8.2 go.uber.org/zap v1.16.0 - golang.org/x/sys v0.0.0-20210511113859-b0526f3d8744 - golang.org/x/tools v0.1.1 // indirect + golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea + golang.org/x/tools v0.1.2 // 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 - honnef.co/go/tools v0.1.4 // indirect + honnef.co/go/tools v0.2.0 // indirect k8s.io/api v0.19.2 k8s.io/apimachinery v0.19.2 k8s.io/client-go v10.0.0+incompatible diff --git a/go.sum b/go.sum index d1a44be32..03eed2346 100644 --- a/go.sum +++ b/go.sum @@ -127,6 +127,8 @@ github.com/apparentlymart/go-textseg v1.0.0 h1:rRmlIsPEEhUTIKQb7T++Nz/A5Q6C9IuX2 github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk= github.com/apparentlymart/go-textseg/v12 v12.0.0 h1:bNEQyAGak9tojivJNkoqWErVCQbjdL7GzRt3F8NvfJ0= github.com/apparentlymart/go-textseg/v12 v12.0.0/go.mod h1:S/4uRK2UtaQttw1GenVJEynmyUenKwP++x/+DdGV/Ec= +github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw= +github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= github.com/apparentlymart/go-userdirs v0.0.0-20200915174352-b0c018a67c13/go.mod h1:7kfpUbyCdGJ9fDRCp3fopPQi5+cKNHgTE4ZuNrO71Cw= github.com/apparentlymart/go-versions v1.0.1 h1:ECIpSn0adcYNsBfSRwdDdz9fWlL+S/6EUd9+irwkBgU= github.com/apparentlymart/go-versions v1.0.1/go.mod h1:YF5j7IQtrOAOnsGkniupEA5bfCjzd7i14yu0shZavyM= @@ -743,6 +745,8 @@ github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxS github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/owenrumney/go-sarif v1.0.4 h1:0LFC5eHP6amc/9ajM1jDiE52UfXFcl/oozay+X3KgV4= +github.com/owenrumney/go-sarif v1.0.4/go.mod h1:DXUGbHwQcCMvqcvZbxh8l/7diHsJVztOKZgmPt88RNI= github.com/packer-community/winrmcp v0.0.0-20180921211025-c76d91c1e7db/go.mod h1:f6Izs6JvFTdnRbziASagjZ2vmf55NSIkC/weStxCHqk= github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= @@ -873,8 +877,9 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/svanharmelen/jsonapi v0.0.0-20180618144545-0c0828c3f16d/go.mod h1:BSTlc8jOjh0niykqEGVXOLXdi9o0r0kR8tCYiMvjFgw= github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= @@ -926,8 +931,9 @@ github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go github.com/zclconf/go-cty v1.0.0/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s= github.com/zclconf/go-cty v1.1.0/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s= github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8= -github.com/zclconf/go-cty v1.7.1 h1:AvsC01GMhMLFL8CgEYdHGM+yLnnDOwhPAYcgTkeF0Gw= github.com/zclconf/go-cty v1.7.1/go.mod h1:VDR4+I79ubFBGm1uJac1226K5yANQFHeauxPBoP54+o= +github.com/zclconf/go-cty v1.8.2 h1:u+xZfBKgpycDnTNjPhGiTEYZS5qS/Sb5MqSfm7vzcjg= +github.com/zclconf/go-cty v1.8.2/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= github.com/zclconf/go-cty-yaml v1.0.2/go.mod h1:IP3Ylp0wQpYm50IHK8OZWKMu6sPJIUgKa8XhiVHura0= github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= @@ -1151,13 +1157,16 @@ 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-20210511113859-b0526f3d8744 h1:yhBbb4IRs2HS9PPlAg6DMC6mUOKexJBNsLf4Z+6En1Q= golang.org/x/sys v0.0.0-20210511113859-b0526f3d8744/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea h1:+WiDlPBBaO+h9vPNZi8uJ3k4BkKQB7Iow3aqwHVA5hI= +golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea/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= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1226,6 +1235,8 @@ golang.org/x/tools v0.0.0-20201028111035-eafbe7b904eb/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.1 h1:wGiQel/hW0NnEkJUk8lbzkX2gFJU6PFxf1v5OlCfuOs= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.2 h1:kRBLX7v7Af8W7Gdbbc908OJcdgtK8bOz9Uaj8/F1ACA= +golang.org/x/tools v0.1.2/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= @@ -1331,7 +1342,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= @@ -1349,7 +1359,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= @@ -1382,8 +1391,9 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.1.4 h1:SadWOkti5uVN1FAMgxn165+Mw00fuQKyk4Gyn/inxNQ= honnef.co/go/tools v0.1.4/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= +honnef.co/go/tools v0.2.0 h1:ws8AfbgTX3oIczLPNPCu5166oBg9ST2vNs0rcht+mDE= +honnef.co/go/tools v0.2.0/go.mod h1:lPVVZ2BS5TfnjLyizF7o7hv7j9/L+8cZY2hLyjP9cGY= k8s.io/api v0.19.0 h1:XyrFIJqTYZJ2DU7FBE/bSPz7b1HvbVBuBf07oeo6eTc= k8s.io/api v0.19.0/go.mod h1:I1K45XlvTrDjmj5LoM5LuP/KYrhWbjUKT/SoPG0qTjw= k8s.io/apiextensions-apiserver v0.19.2 h1:oG84UwiDsVDu7dlsGQs5GySmQHCzMhknfhFExJMz9tA= diff --git a/pkg/cli/register.go b/pkg/cli/register.go index e3b791dee..8c9724cac 100644 --- a/pkg/cli/register.go +++ b/pkg/cli/register.go @@ -37,7 +37,7 @@ func RegisterCommand(baseCommand *cobra.Command, command *cobra.Command) { 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", "human", "output type (human, json, yaml, xml, junit-xml)") + rootCmd.PersistentFlags().StringVarP(&OutputType, "output", "o", "human", "output type (human, json, yaml, xml, junit-xml, sarif)") rootCmd.PersistentFlags().StringVarP(&ConfigFile, "config-path", "c", "", "config file path") // Function to execute before processing commands diff --git a/pkg/utils/json.go b/pkg/utils/json.go index 8083aebed..138f6da99 100644 --- a/pkg/utils/json.go +++ b/pkg/utils/json.go @@ -2,8 +2,11 @@ package utils import ( "bufio" + "encoding/json" + "fmt" "io/ioutil" "os" + "reflect" ) const ( @@ -50,3 +53,23 @@ func LoadJSON(filePath string) ([]*IacDocument, error) { return iacDocumentList, nil } + +// AreEqualJSON validate if two json strings are equal +func AreEqualJSON(s1, s2 string) (bool, error) { + var o1 interface{} + var o2 interface{} + + errmsg := "error json unmarshalling string: %s. error: %v" + + var err error + err = json.Unmarshal([]byte(s1), &o1) + if err != nil { + return false, fmt.Errorf(errmsg, s1, err.Error()) + } + err = json.Unmarshal([]byte(s2), &o2) + if err != nil { + return false, fmt.Errorf(errmsg, s2, err.Error()) + } + + return reflect.DeepEqual(o1, o2), nil +} diff --git a/pkg/version/version.go b/pkg/version/version.go index b118c307b..03027cc06 100644 --- a/pkg/version/version.go +++ b/pkg/version/version.go @@ -16,10 +16,17 @@ package version +import "fmt" + // Terrascan The Terrascan version -const Terrascan = "v1.6.0" +const Terrascan = "1.6.0" // Get returns the terrascan version func Get() string { + return fmt.Sprintf("v%s", Terrascan) +} + +// GetNumeric returns the numeric terrascan version +func GetNumeric() string { return Terrascan } diff --git a/pkg/writer/sarif.go b/pkg/writer/sarif.go new file mode 100644 index 000000000..4bea5b460 --- /dev/null +++ b/pkg/writer/sarif.go @@ -0,0 +1,101 @@ +/* + 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 ( + "fmt" + "github.com/accurics/terrascan/pkg/policy" + "github.com/accurics/terrascan/pkg/version" + "github.com/owenrumney/go-sarif/sarif" + "io" + "path/filepath" + "strings" +) + +const ( + sarifFormat supportedFormat = "sarif" +) + +func init() { + RegisterWriter(sarifFormat, SarifWriter) +} + +// SarifWriter writes sarif formatted violation results report +func SarifWriter(data interface{}, writer io.Writer) error { + outputData := data.(policy.EngineOutput) + report, err := sarif.New(sarif.Version210) + if err != nil { + return err + } + + run := sarif.NewRun("terrascan", "https://github.com/accurics/terrascan") + run.Tool.Driver.WithVersion(version.GetNumeric()) + + // add a run to the report + report.AddRun(run) + + for _, passedRule := range outputData.PassedRules { + m := make(map[string]string) + m["category"] = passedRule.Category + m["severity"] = passedRule.Severity + + 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) + m["category"] = violation.Category + m["severity"] = violation.Severity + + 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) + } + + location := sarif.NewLocation(). + WithPhysicalLocation(sarif.NewPhysicalLocation(). + WithArtifactLocation(sarif.NewSimpleArtifactLocation(fmt.Sprintf("file://%s", absFilePath))). + WithRegion(sarif.NewRegion().WithStartLine(violation.LineNumber))) + + if len(violation.ResourceType) > 0 && len(violation.ResourceName) > 0 { + location.LogicalLocations = append(location.LogicalLocations, sarif.NewLogicalLocation(). + WithKind(violation.ResourceType).WithName(violation.ResourceName)) + } + + run.AddResult(rule.ID). + WithMessage(sarif.NewTextMessage(violation.Description)). + WithLevel(getSarifLevel(violation.Severity)). + WithLocation(location) + } + + // print the report to anything that implements `io.Writer` + return report.PrettyWrite(writer) +} + +func getSarifLevel(severity string) string { + m := make(map[string]string, 3) + m["low"] = "note" + m["medium"] = "warning" + m["high"] = "error" + + return m[strings.ToLower(severity)] +} diff --git a/pkg/writer/sarif_test.go b/pkg/writer/sarif_test.go new file mode 100644 index 000000000..c89e43f25 --- /dev/null +++ b/pkg/writer/sarif_test.go @@ -0,0 +1,159 @@ +package writer + +import ( + "bytes" + "github.com/accurics/terrascan/pkg/policy" + "github.com/accurics/terrascan/pkg/results" + "github.com/accurics/terrascan/pkg/utils" + "strings" + "testing" +) + +const expectedSarifOutput1 = `{ + "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": "1.6.0", + "informationUri": "https://github.com/accurics/terrascan", + "rules": [ + { + "id": "AWS.S3Bucket.DS.High.1043", + "name": "s3EnforceUserACL", + "shortDescription": { + "text": "S3 bucket Access is allowed to all AWS Account Users." + }, + "properties": { + "category": "S3", + "severity": "HIGH" + } + } + ] + } + }, + "results": [ + { + "ruleId": "AWS.S3Bucket.DS.High.1043", + "level": "error", + "message": { + "text": "S3 bucket Access is allowed to all AWS Account Users." + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "file://test/modules/m1/main.tf" + }, + "region": { + "startLine": 20 + } + }, + "logicalLocations": [ + { + "name": "bucket", + "kind": "aws_s3_bucket" + } + ] + } + ] + } + ] + } + ] + }` + +const expectedSarifOutput2 = `{ + "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": "1.6.0", + "informationUri": "https://github.com/accurics/terrascan" + } + }, + "results": [] + } + ] + }` + +const expectedSarifOutput3 = `{ + "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": "1.6.0", + "informationUri": "https://github.com/accurics/terrascan", + "rules": [ + { + "id": "AWS.S3Bucket.DS.High.1043", + "name": "s3EnforceUserACL", + "shortDescription": { + "text": "S3 bucket Access is allowed to all AWS Account Users." + }, + "properties": { + "category": "S3", + "severity": "HIGH" + } + } + ] + } + }, + "results": [] + } + ] + }` + +func TestSarifWriter(t *testing.T) { + + type funcInput interface{} + tests := []struct { + name string + input funcInput + expectedError bool + expectedOutput string + }{ + { + name: "Human Readable Writer: Violations", + input: violationsInput, + expectedOutput: expectedSarifOutput1, + }, + { + name: "Human Readable Writer: No Violations", + input: policy.EngineOutput{ + ViolationStore: &results.ViolationStore{ + Summary: summaryWithNoViolations, + }, + }, + expectedOutput: expectedSarifOutput2, + }, + { + name: "Human Readable Writer: With PassedRules", + input: outputWithPassedRules, + expectedOutput: expectedSarifOutput3, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + writer := &bytes.Buffer{} + if err := SarifWriter(tt.input, writer); (err != nil) != tt.expectedError { + t.Errorf("HumanReadbleWriter() error = gotErr: %v, wantErr: %v", err, tt.expectedError) + } + outputBytes := writer.Bytes() + gotOutput := string(bytes.TrimSpace(outputBytes)) + + if equal, _ := utils.AreEqualJSON(strings.TrimSpace(gotOutput), strings.TrimSpace(tt.expectedOutput)); !equal { + t.Errorf("HumanReadbleWriter() = got: %v, want: %v", gotOutput, tt.expectedOutput) + } + }) + } +} diff --git a/test/e2e/help/golden/help_command.txt b/test/e2e/help/golden/help_command.txt index 8bd075abf..78492c802 100644 --- a/test/e2e/help/golden/help_command.txt +++ b/test/e2e/help/golden/help_command.txt @@ -18,6 +18,6 @@ Flags: -h, --help help for terrascan -l, --log-level string log level (debug, info, warn, error, panic, fatal) (default "info") -x, --log-type string log output type (console, json) (default "console") - -o, --output string output type (human, json, yaml, xml, junit-xml) (default "human") + -o, --output string output type (human, json, yaml, xml, junit-xml, sarif) (default "human") Use "terrascan [command] --help" for more information about a command. diff --git a/test/e2e/help/golden/help_flag.txt b/test/e2e/help/golden/help_flag.txt index 9015c2520..8916859d9 100644 --- a/test/e2e/help/golden/help_flag.txt +++ b/test/e2e/help/golden/help_flag.txt @@ -16,6 +16,6 @@ Flags: -c, --config-path string config file path -l, --log-level string log level (debug, info, warn, error, panic, fatal) (default "info") -x, --log-type string log output type (console, json) (default "console") - -o, --output string output type (human, json, yaml, xml, junit-xml) (default "human") + -o, --output string output type (human, json, yaml, xml, junit-xml, sarif) (default "human") Use "terrascan [command] --help" for more information about a command. diff --git a/test/e2e/help/golden/help_init.txt b/test/e2e/help/golden/help_init.txt index 0a2d2a485..d853c544a 100644 --- a/test/e2e/help/golden/help_init.txt +++ b/test/e2e/help/golden/help_init.txt @@ -12,4 +12,4 @@ Global Flags: -c, --config-path string config file path -l, --log-level string log level (debug, info, warn, error, panic, fatal) (default "info") -x, --log-type string log output type (console, json) (default "console") - -o, --output string output type (human, json, yaml, xml, junit-xml) (default "human") + -o, --output string output type (human, json, yaml, xml, junit-xml, sarif) (default "human") diff --git a/test/e2e/help/golden/help_scan.txt b/test/e2e/help/golden/help_scan.txt index 1575302a7..66e4e56bc 100644 --- a/test/e2e/help/golden/help_scan.txt +++ b/test/e2e/help/golden/help_scan.txt @@ -29,4 +29,4 @@ Global Flags: -c, --config-path string config file path -l, --log-level string log level (debug, info, warn, error, panic, fatal) (default "info") -x, --log-type string log output type (console, json) (default "console") - -o, --output string output type (human, json, yaml, xml, junit-xml) (default "human") \ No newline at end of file + -o, --output string output type (human, json, yaml, xml, junit-xml, sarif) (default "human") diff --git a/test/e2e/help/golden/help_server.txt b/test/e2e/help/golden/help_server.txt index 303a60ee4..02146225d 100644 --- a/test/e2e/help/golden/help_server.txt +++ b/test/e2e/help/golden/help_server.txt @@ -15,4 +15,4 @@ Global Flags: -c, --config-path string config file path -l, --log-level string log level (debug, info, warn, error, panic, fatal) (default "info") -x, --log-type string log output type (console, json) (default "console") - -o, --output string output type (human, json, yaml, xml, junit-xml) (default "human") \ No newline at end of file + -o, --output string output type (human, json, yaml, xml, junit-xml, sarif) (default "human") diff --git a/test/e2e/help/golden/help_unsupported_command.txt b/test/e2e/help/golden/help_unsupported_command.txt index 26f55db5a..0a52fb382 100644 --- a/test/e2e/help/golden/help_unsupported_command.txt +++ b/test/e2e/help/golden/help_unsupported_command.txt @@ -13,6 +13,6 @@ Flags: -c, --config-path string config file path -l, --log-level string log level (debug, info, warn, error, panic, fatal) (default "info") -x, --log-type string log output type (console, json) (default "console") - -o, --output string output type (human, json, yaml, xml, junit-xml) (default "human") + -o, --output string output type (human, json, yaml, xml, junit-xml, sarif) (default "human") Use "terrascan [command] --help" for more information about a command. diff --git a/test/e2e/help/golden/help_version.txt b/test/e2e/help/golden/help_version.txt index 6c8ab2a26..bd13423cb 100644 --- a/test/e2e/help/golden/help_version.txt +++ b/test/e2e/help/golden/help_version.txt @@ -12,4 +12,4 @@ Global Flags: -c, --config-path string config file path -l, --log-level string log level (debug, info, warn, error, panic, fatal) (default "info") -x, --log-type string log output type (console, json) (default "console") - -o, --output string output type (human, json, yaml, xml, junit-xml) (default "human") + -o, --output string output type (human, json, yaml, xml, junit-xml, sarif) (default "human") diff --git a/test/e2e/help/golden/no_command.txt b/test/e2e/help/golden/no_command.txt index 8bd075abf..78492c802 100644 --- a/test/e2e/help/golden/no_command.txt +++ b/test/e2e/help/golden/no_command.txt @@ -18,6 +18,6 @@ Flags: -h, --help help for terrascan -l, --log-level string log level (debug, info, warn, error, panic, fatal) (default "info") -x, --log-type string log output type (console, json) (default "console") - -o, --output string output type (human, json, yaml, xml, junit-xml) (default "human") + -o, --output string output type (human, json, yaml, xml, junit-xml, sarif) (default "human") Use "terrascan [command] --help" for more information about a command.