From e738946c45b18556f8e8dac09aac99baeb4d241d Mon Sep 17 00:00:00 2001 From: Prashant Balachandran Date: Thu, 15 Apr 2021 19:29:57 +0530 Subject: [PATCH] changes for sarif format changing version of testify removed stretchr from go.sum correcting dependencies in go.mod changing function call to support older version of go --- analyses/stackanalyses/controller.go | 11 +- analyses/verbose/helper.go | 1 - analyses/verbose/sarif_helper.go | 153 +++++++++++++++++++++++++++ cmd/analyse.go | 4 +- go.mod | 4 +- go.sum | 5 + 6 files changed, 173 insertions(+), 5 deletions(-) create mode 100644 analyses/verbose/sarif_helper.go diff --git a/analyses/stackanalyses/controller.go b/analyses/stackanalyses/controller.go index d9d308c..9f3037c 100644 --- a/analyses/stackanalyses/controller.go +++ b/analyses/stackanalyses/controller.go @@ -40,7 +40,8 @@ const ( ) //StackAnalyses is main controller function for analyse command. This function is responsible for all communications between cmd and custom packages. -func StackAnalyses(ctx context.Context, requestParams driver.RequestType, jsonOut bool, verboseOut bool) (bool, error) { + +func StackAnalyses(ctx context.Context, requestParams driver.RequestType, jsonOut bool, verboseOut bool, sarifFmt bool) (bool, error) { log.Debug().Msgf("Executing StackAnalyses.") var hasVul bool matcher, err := GetMatcher(requestParams.RawManifestFile) @@ -60,7 +61,13 @@ func StackAnalyses(ctx context.Context, requestParams driver.RequestType, jsonOu verboseEligible := getResponse.RegistrationStatus == RegisteredStatus showVerboseMsg := verboseOut && !verboseEligible - if verboseOut && verboseEligible { + if verboseEligible && sarifFmt { + hasVul, err = verbose.ProcessSarif(getResponse, mc.fileStats.RawFilePath) + if err != nil { + log.Fatal().Err(err) + return hasVul, err + } + } else if verboseOut && verboseEligible { hasVul = verbose.ProcessVerbose(ctx, getResponse, jsonOut) } else { hasVul = summary.ProcessSummary(ctx, getResponse, jsonOut, showVerboseMsg) diff --git a/analyses/verbose/helper.go b/analyses/verbose/helper.go index 5b1be0c..39caa75 100644 --- a/analyses/verbose/helper.go +++ b/analyses/verbose/helper.go @@ -9,7 +9,6 @@ import ( "sort" "github.com/fabric8-analytics/cli-tools/utils" - "github.com/fatih/color" "github.com/rs/zerolog/log" diff --git a/analyses/verbose/sarif_helper.go b/analyses/verbose/sarif_helper.go new file mode 100644 index 0000000..c28ef0b --- /dev/null +++ b/analyses/verbose/sarif_helper.go @@ -0,0 +1,153 @@ +package verbose + +import ( + "errors" + "fmt" + "github.com/fabric8-analytics/cli-tools/analyses/driver" + "github.com/owenrumney/go-sarif/models" + "github.com/owenrumney/go-sarif/sarif" + "github.com/rs/zerolog/log" + "io/ioutil" + "os" + "regexp" + "strings" +) + +type RegexDependencyLocator struct { + FileContent string + Ecosystem string + EndIndices []int + DependencyNodeIndex []int +} + +func ProcessSarif(analysedResult *driver.GetResponseType, manifestFilePath string) (bool, error) { + var hasVuln bool + report, err := sarif.New(sarif.Version210) + + if err != nil { + log.Fatal().Msg("Error forming SARIF respose") + return false, errors.New("unable to create SARIF file") + } + + run := report.AddRun("CRDA", "https://github.com/fabric8-analytics") + + regexDependencyLocator := RegexDependencyLocator{} + if len(analysedResult.AnalysedDeps) == 0 { + log.Fatal().Msg("Dependencies have not been analysed") + return false, errors.New("dependencies have not been analysed") + } + err = regexDependencyLocator.SetUp(manifestFilePath, analysedResult.AnalysedDeps[0].Ecosystem) + + if err != nil { + return false, errors.New("unable to setup dependency locator") + } + + manifestParts := strings.Split(manifestFilePath, string(os.PathSeparator)) + manifest := manifestParts[len(manifestParts) - 1] + for _, dep := range analysedResult.AnalysedDeps { + line, column := regexDependencyLocator.LocateDependency(dep.Name) + for _, publicVuln := range dep.PublicVulnerabilities { + addVulnToReport(run, publicVuln, manifest, line, column) + hasVuln = true + } + for _, privateVuln := range dep.PrivateVulnerabilities { + addVulnToReport(run, privateVuln, manifest, line, column) + hasVuln = true + } + } + + report.Write(os.Stdout) + return hasVuln, nil +} + +func addVulnToReport(run *models.Run, vuln driver.VulnerabilitiesType, manifestFilePath string, line int, column int) { + rule := run.AddRule(vuln.ID). + WithHelpURI(vuln.URL).WithDescription(vuln.Title) + + run.AddResult(rule.ID). + WithMessage(vuln.Title). + WithLevel(vuln.Severity). + WithLocationDetails(manifestFilePath, line, column) +} + + +func (r *RegexDependencyLocator) SetUp(manifestFilePath string, ecosystem string) error{ + content, err := ioutil.ReadFile(manifestFilePath) + if err != nil { + log.Fatal().Msg("Unable to load manifest File " + manifestFilePath) + return fmt.Errorf("unable to load manifest file %s" ,manifestFilePath) + } + + r.FileContent = string(content) + r.Ecosystem = ecosystem + newLineRegex, _ := regexp.Compile("\n") + + lines := newLineRegex.FindAllStringIndex(r.FileContent, -1) + r.EndIndices = make([]int, len(lines)) + // Finding the end index for each end of line + for i, line := range lines { + r.EndIndices[i] = line[1] + } + + dependenciesRegex, _ := regexp.Compile(getDependencyNodeRegex(r.Ecosystem)) + // Find the index for the start of the dependency node ( in case of pom.xml, + // dependencies: in case of package.json) + r.DependencyNodeIndex = dependenciesRegex.FindStringIndex(r.FileContent) + + return nil + +} + +func (r *RegexDependencyLocator) LocateDependency(dependency string) (int, int){ + // In case of maven the dependency consists groupId and artifactID + // Picking up the artifact ID as the dependency + if r.Ecosystem == "maven" { + dependencyParts := strings.Split(dependency, ":") + dependency = dependencyParts[len(dependencyParts) - 1] + } + // Adding the actual dependency in to the dependency regex + dependencyRegexStr := strings.Replace(getDependencyRegex(r.Ecosystem), "?", dependency, 1) + dependencyRegex, _ := regexp.Compile(dependencyRegexStr) + dependencyIndex := dependencyRegex.FindStringIndex(r.FileContent) + + var lineNum int + var column int + + // Check if the dependency index is within the dependency node + if r.DependencyNodeIndex[0] < dependencyIndex[0] && dependencyIndex[0] < r.DependencyNodeIndex[1] { + for i, val := range r.EndIndices { + // Getting the line num and column number of the dependency + if val <= dependencyIndex[0] && dependencyIndex[0] < r.EndIndices[i+1] { + lineNum = i + 2 + column = dependencyIndex[0] - val + 2 + break + } + } + } + return lineNum, column +} + +func getDependencyNodeRegex(ecosystem string) string{ + switch ecosystem { + case "npm": + return "\"dependencies\"[\\s]*:[\\s\n]*{[\\s\na-z\"\\d.,\\-:^]*}" + case "maven": + return "[\\s]*[\\s\\n]*[<>!\\s\\w\\/${}\"\\d.,\\-:^]*<\\/dependencies[\\s\\n]*>" + default: + return "[\\s\\S]*" + + } +} + +func getDependencyRegex(ecosystem string) string{ + switch ecosystem{ + case "npm": + return "\"?\"[\\s]*:[\\s\n]*\"[\\d\\.^\\-\\w]*\"" + case "maven": + return "?[\\s\\n]*<\\/artifactId[\\s\\n]*>" + default: + return "?[\\s]*==" + + } +} + diff --git a/cmd/analyse.go b/cmd/analyse.go index 4b14bfe..1f642b3 100644 --- a/cmd/analyse.go +++ b/cmd/analyse.go @@ -15,6 +15,7 @@ import ( var jsonOut bool var verboseOut bool +var sarifOut bool // analyseCmd represents the analyse command var analyseCmd = &cobra.Command{ @@ -31,6 +32,7 @@ func init() { rootCmd.AddCommand(analyseCmd) analyseCmd.Flags().BoolVarP(&jsonOut, "json", "j", false, "Set output format to JSON.") analyseCmd.Flags().BoolVarP(&verboseOut, "verbose", "v", false, "Detailed Analyses Report.") + analyseCmd.Flags().BoolVarP(&sarifOut, "sarif", "s", false, "Report in Sarif format.") } // destructor deletes intermediary files used to have stack analyses @@ -81,7 +83,7 @@ func runAnalyse(cmd *cobra.Command, args []string) error { fmt.Println("Analysing your Dependency Stack! Please wait...") } name := sa.GetManifestName(manifestPath) - hasVul, err := sa.StackAnalyses(cmd.Context(), requestParams, jsonOut, verboseOut) + hasVul, err := sa.StackAnalyses(cmd.Context(), requestParams, jsonOut, verboseOut, sarifOut) telemetry.SetManifest(cmd.Context(), name) if err != nil { telemetry.SetExitCode(cmd.Context(), 1) diff --git a/go.mod b/go.mod index 43c8d04..8f5af33 100644 --- a/go.mod +++ b/go.mod @@ -11,13 +11,15 @@ require ( github.com/mitchellh/go-homedir v1.1.0 github.com/onsi/ginkgo v1.16.1 github.com/onsi/gomega v1.11.0 + github.com/owenrumney/go-sarif v0.0.8 github.com/pborman/uuid v1.2.1 github.com/rs/zerolog v1.20.0 github.com/segmentio/analytics-go v3.0.1+incompatible github.com/segmentio/backo-go v0.0.0-20200129164019-23eae7c10bd3 // indirect github.com/spf13/cobra v1.1.1 github.com/spf13/viper v1.7.1 - github.com/stretchr/testify v1.6.1 + github.com/stretchr/testify v1.7.0 github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c // indirect gopkg.in/yaml.v2 v2.4.0 + ) diff --git a/go.sum b/go.sum index 78d72ea..05d2519 100644 --- a/go.sum +++ b/go.sum @@ -181,6 +181,8 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.11.0 h1:+CqWgvj0OZycCaqclBD1pxKHAU+tOkHmQIWvDHq2aug= github.com/onsi/gomega v1.11.0/go.mod h1:azGKhqFUon9Vuj0YmTfLSmx0FUwqXYSTl5re8lQLTUg= +github.com/owenrumney/go-sarif v0.0.8 h1:rxNjz+/hm8s4+NUtq1mnvY5NbAr6RqppTzlT+QH2c28= +github.com/owenrumney/go-sarif v0.0.8/go.mod h1:fcYnVdYRfXWB943S0WaZrjAMuoTEj7vqS7JpOMf6CG0= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pborman/uuid v1.2.1 h1:+ZZIw58t/ozdjRaXh/3awHfmWRbzYxJoAdNJxe/3pvw= github.com/pborman/uuid v1.2.1/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= @@ -242,6 +244,8 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV 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 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= @@ -362,6 +366,7 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 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 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=