From 5738c2855c63732e2056db47ef3f7634532eed8a Mon Sep 17 00:00:00 2001 From: Asaf Ambar Date: Wed, 22 May 2024 12:17:35 +0300 Subject: [PATCH 1/3] Add classifier to maven graph (#61) --- buildscripts/download-jars.sh | 2 +- commands/audit/sca/common.go | 3 +- commands/audit/sca/common_test.go | 17 ++-- commands/audit/sca/java/deptreemanager.go | 25 +++--- commands/audit/sca/java/gradle.go | 3 +- commands/audit/sca/java/mvn.go | 5 +- commands/audit/sca/java/mvn_test.go | 16 +++- .../sca/java/resources/maven-dep-tree.jar | Bin 13862 -> 14114 bytes commands/audit/sca/npm/npm.go | 5 +- commands/audit/sca/nuget/nuget.go | 5 +- commands/audit/sca/pnpm/pnpm.go | 13 ++- commands/audit/sca/yarn/yarn.go | 7 +- commands/audit/scarunner.go | 14 ++-- commands/curation/curationaudit.go | 16 ++-- go.mod | 36 ++++----- go.sum | 74 +++++++++--------- .../maven-example-with-many-types/pom.xml | 8 ++ utils/xrayutils.go | 59 ++++++++++++++ 18 files changed, 195 insertions(+), 113 deletions(-) create mode 100644 utils/xrayutils.go diff --git a/buildscripts/download-jars.sh b/buildscripts/download-jars.sh index 28c4d7d2..fce3b1ea 100755 --- a/buildscripts/download-jars.sh +++ b/buildscripts/download-jars.sh @@ -9,7 +9,7 @@ # Once you have updated the versions mentioned below, please execute this script from the root directory of the jfrog-cli-core to ensure the JAR files are updated. GRADLE_DEP_TREE_VERSION="3.0.2" # Changing this version also requires a change in mavenDepTreeVersion within utils/java/mvn.go. -MAVEN_DEP_TREE_VERSION="1.1.0" +MAVEN_DEP_TREE_VERSION="1.1.1" curl -fL https://releases.jfrog.io/artifactory/oss-release-local/com/jfrog/gradle-dep-tree/${GRADLE_DEP_TREE_VERSION}/gradle-dep-tree-${GRADLE_DEP_TREE_VERSION}.jar -o commands/audit/sca/java/resources/gradle-dep-tree.jar curl -fL https://releases.jfrog.io/artifactory/oss-release-local/com/jfrog/maven-dep-tree/${MAVEN_DEP_TREE_VERSION}/maven-dep-tree-${MAVEN_DEP_TREE_VERSION}.jar -o commands/audit/sca/java/resources/maven-dep-tree.jar diff --git a/commands/audit/sca/common.go b/commands/audit/sca/common.go index 566ac82c..0613e7d7 100644 --- a/commands/audit/sca/common.go +++ b/commands/audit/sca/common.go @@ -178,7 +178,8 @@ func SuspectCurationBlockedError(isCurationCmd bool, tech coreutils.Technology, } switch tech { case coreutils.Maven: - if strings.Contains(cmdOutput, "status code: 403") || strings.Contains(cmdOutput, "status code: 500") { + if strings.Contains(cmdOutput, "status code: 403") || strings.Contains(strings.ToLower(cmdOutput), "403 forbidden") || + strings.Contains(cmdOutput, "status code: 500") { msgToUser = fmt.Sprintf(curationErrorMsgToUserTemplate, coreutils.Maven) } case coreutils.Pip: diff --git a/commands/audit/sca/common_test.go b/commands/audit/sca/common_test.go index a76361b4..8cda89e8 100644 --- a/commands/audit/sca/common_test.go +++ b/commands/audit/sca/common_test.go @@ -9,7 +9,6 @@ import ( "golang.org/x/exp/maps" "github.com/jfrog/jfrog-cli-core/v2/utils/tests" - coreXray "github.com/jfrog/jfrog-cli-core/v2/utils/xray" "github.com/jfrog/jfrog-cli-security/utils" "github.com/jfrog/jfrog-client-go/xray/services" xrayUtils "github.com/jfrog/jfrog-client-go/xray/services/utils" @@ -61,13 +60,13 @@ func TestGetExcludePattern(t *testing.T) { } func TestBuildXrayDependencyTree(t *testing.T) { - treeHelper := make(map[string]coreXray.DepTreeNode) - rootDep := coreXray.DepTreeNode{Children: []string{"topDep1", "topDep2", "topDep3"}} - topDep1 := coreXray.DepTreeNode{Children: []string{"midDep1", "midDep2"}} - topDep2 := coreXray.DepTreeNode{Children: []string{"midDep2", "midDep3"}} - midDep1 := coreXray.DepTreeNode{Children: []string{"bottomDep1"}} - midDep2 := coreXray.DepTreeNode{Children: []string{"bottomDep2", "bottomDep3"}} - bottomDep3 := coreXray.DepTreeNode{Children: []string{"leafDep"}} + treeHelper := make(map[string]utils.DepTreeNode) + rootDep := utils.DepTreeNode{Children: []string{"topDep1", "topDep2", "topDep3"}} + topDep1 := utils.DepTreeNode{Children: []string{"midDep1", "midDep2"}} + topDep2 := utils.DepTreeNode{Children: []string{"midDep2", "midDep3"}} + midDep1 := utils.DepTreeNode{Children: []string{"bottomDep1"}} + midDep2 := utils.DepTreeNode{Children: []string{"bottomDep2", "bottomDep3"}} + bottomDep3 := utils.DepTreeNode{Children: []string{"leafDep"}} treeHelper["rootDep"] = rootDep treeHelper["topDep1"] = topDep1 treeHelper["topDep2"] = topDep2 @@ -116,7 +115,7 @@ func TestBuildXrayDependencyTree(t *testing.T) { topDep2Node.Parent = rootNode topDep3Node.Parent = rootNode - tree, uniqueDeps := coreXray.BuildXrayDependencyTree(treeHelper, "rootDep") + tree, uniqueDeps := utils.BuildXrayDependencyTree(treeHelper, "rootDep") assert.ElementsMatch(t, expectedUniqueDeps, maps.Keys(uniqueDeps)) assert.True(t, tests.CompareTree(tree, rootNode)) diff --git a/commands/audit/sca/java/deptreemanager.go b/commands/audit/sca/java/deptreemanager.go index e323fad1..fe577b37 100644 --- a/commands/audit/sca/java/deptreemanager.go +++ b/commands/audit/sca/java/deptreemanager.go @@ -2,12 +2,12 @@ package java import ( "encoding/json" + "github.com/jfrog/jfrog-cli-security/utils" "os" "strings" "github.com/jfrog/jfrog-cli-core/v2/utils/config" "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" - "github.com/jfrog/jfrog-cli-core/v2/utils/xray" "github.com/jfrog/jfrog-client-go/utils/errorutils" xrayUtils "github.com/jfrog/jfrog-client-go/xray/services/utils" ) @@ -16,7 +16,7 @@ const ( GavPackageTypeIdentifier = "gav://" ) -func BuildDependencyTree(depTreeParams DepTreeParams, tech coreutils.Technology) ([]*xrayUtils.GraphNode, map[string][]string, error) { +func BuildDependencyTree(depTreeParams DepTreeParams, tech coreutils.Technology) ([]*xrayUtils.GraphNode, map[string]*utils.DepTreeNode, error) { if tech == coreutils.Maven { return buildMavenDependencyTree(&depTreeParams) } @@ -44,18 +44,18 @@ func NewDepTreeManager(params *DepTreeParams) DepTreeManager { // The structure of a dependency tree of a module in a Gradle/Maven project, as created by the gradle-dep-tree and maven-dep-tree plugins. type moduleDepTree struct { - Root string `json:"root"` - Nodes map[string]xray.DepTreeNode `json:"nodes"` + Root string `json:"root"` + Nodes map[string]utils.DepTreeNode `json:"nodes"` } // Reads the output files of the gradle-dep-tree and maven-dep-tree plugins and returns them as a slice of GraphNodes. // It takes the output of the plugin's run (which is a byte representation of a list of paths of the output files, separated by newlines) as input. -func getGraphFromDepTree(outputFilePaths string) (depsGraph []*xrayUtils.GraphNode, uniqueDepsMap map[string][]string, err error) { +func getGraphFromDepTree(outputFilePaths string) (depsGraph []*xrayUtils.GraphNode, uniqueDepsMap map[string]*utils.DepTreeNode, err error) { modules, err := parseDepTreeFiles(outputFilePaths) if err != nil { return } - uniqueDepsMap = map[string][]string{} + uniqueDepsMap = map[string]*utils.DepTreeNode{} for _, module := range modules { moduleTree, moduleUniqueDeps := GetModuleTreeAndDependencies(module) depsGraph = append(depsGraph, moduleTree) @@ -67,8 +67,8 @@ func getGraphFromDepTree(outputFilePaths string) (depsGraph []*xrayUtils.GraphNo } // Returns a dependency tree and a flat list of the module's dependencies for the given module -func GetModuleTreeAndDependencies(module *moduleDepTree) (*xrayUtils.GraphNode, map[string][]string) { - moduleTreeMap := make(map[string]xray.DepTreeNode) +func GetModuleTreeAndDependencies(module *moduleDepTree) (*xrayUtils.GraphNode, map[string]*utils.DepTreeNode) { + moduleTreeMap := make(map[string]utils.DepTreeNode) moduleDeps := module.Nodes for depName, dependency := range moduleDeps { dependencyId := GavPackageTypeIdentifier + depName @@ -77,12 +77,13 @@ func GetModuleTreeAndDependencies(module *moduleDepTree) (*xrayUtils.GraphNode, childId := GavPackageTypeIdentifier + childName childrenList = append(childrenList, childId) } - moduleTreeMap[dependencyId] = xray.DepTreeNode{ - Types: dependency.Types, - Children: childrenList, + moduleTreeMap[dependencyId] = utils.DepTreeNode{ + Classifier: dependency.Classifier, + Types: dependency.Types, + Children: childrenList, } } - return xray.BuildXrayDependencyTree(moduleTreeMap, GavPackageTypeIdentifier+module.Root) + return utils.BuildXrayDependencyTree(moduleTreeMap, GavPackageTypeIdentifier+module.Root) } func parseDepTreeFiles(jsonFilePaths string) ([]*moduleDepTree, error) { diff --git a/commands/audit/sca/java/gradle.go b/commands/audit/sca/java/gradle.go index 57ceff8b..dad131e0 100644 --- a/commands/audit/sca/java/gradle.go +++ b/commands/audit/sca/java/gradle.go @@ -4,6 +4,7 @@ import ( _ "embed" "errors" "fmt" + "github.com/jfrog/jfrog-cli-security/utils" "os" "os/exec" "path/filepath" @@ -56,7 +57,7 @@ type gradleDepTreeManager struct { DepTreeManager } -func buildGradleDependencyTree(params *DepTreeParams) (dependencyTree []*xrayUtils.GraphNode, uniqueDeps map[string][]string, err error) { +func buildGradleDependencyTree(params *DepTreeParams) (dependencyTree []*xrayUtils.GraphNode, uniqueDeps map[string]*utils.DepTreeNode, err error) { manager := &gradleDepTreeManager{DepTreeManager: NewDepTreeManager(params)} outputFileContent, err := manager.runGradleDepTree() if err != nil { diff --git a/commands/audit/sca/java/mvn.go b/commands/audit/sca/java/mvn.go index 0b616985..c7263a6d 100644 --- a/commands/audit/sca/java/mvn.go +++ b/commands/audit/sca/java/mvn.go @@ -6,6 +6,7 @@ import ( "fmt" "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" "github.com/jfrog/jfrog-cli-security/commands/audit/sca" + "github.com/jfrog/jfrog-cli-security/utils" "net/url" "os" "os/exec" @@ -24,7 +25,7 @@ const ( mavenDepTreeJarFile = "maven-dep-tree.jar" mavenDepTreeOutputFile = "mavendeptree.out" // Changing this version also requires a change in MAVEN_DEP_TREE_VERSION within buildscripts/download_jars.sh - mavenDepTreeVersion = "1.1.0" + mavenDepTreeVersion = "1.1.1" settingsXmlFile = "settings.xml" ) @@ -68,7 +69,7 @@ func NewMavenDepTreeManager(params *DepTreeParams, cmdName MavenDepTreeCmd) *Mav } } -func buildMavenDependencyTree(params *DepTreeParams) (dependencyTree []*xrayUtils.GraphNode, uniqueDeps map[string][]string, err error) { +func buildMavenDependencyTree(params *DepTreeParams) (dependencyTree []*xrayUtils.GraphNode, uniqueDeps map[string]*utils.DepTreeNode, err error) { manager := NewMavenDepTreeManager(params, Tree) outputFilePaths, clearMavenDepTreeRun, err := manager.RunMavenDepTree() if err != nil { diff --git a/commands/audit/sca/java/mvn_test.go b/commands/audit/sca/java/mvn_test.go index 3c614bc6..94a413a8 100644 --- a/commands/audit/sca/java/mvn_test.go +++ b/commands/audit/sca/java/mvn_test.go @@ -105,7 +105,7 @@ func TestMavenTreesMultiModule(t *testing.T) { expectedUniqueDeps := []string{ GavPackageTypeIdentifier + "javax.mail:mail:1.4", - GavPackageTypeIdentifier + "org.testng:testng:5.9", + GavPackageTypeIdentifier + "org.testng:testng:5.9-jdk15", GavPackageTypeIdentifier + "javax.servlet:servlet-api:2.5", GavPackageTypeIdentifier + "org.jfrog.test:multi:3.7-SNAPSHOT", GavPackageTypeIdentifier + "org.jfrog.test:multi3:3.7-SNAPSHOT", @@ -157,7 +157,7 @@ func TestMavenWrapperTrees(t *testing.T) { GavPackageTypeIdentifier + "org.springframework:spring-core:2.5.6", GavPackageTypeIdentifier + "org.jfrog.test:multi:3.7-SNAPSHOT", GavPackageTypeIdentifier + "org.jfrog.test:multi2:3.7-SNAPSHOT", - GavPackageTypeIdentifier + "org.testng:testng:5.9", + GavPackageTypeIdentifier + "org.testng:testng:5.9-jdk15", GavPackageTypeIdentifier + "hsqldb:hsqldb:1.8.0.10", GavPackageTypeIdentifier + "junit:junit:3.8.1", GavPackageTypeIdentifier + "javax.activation:activation:1.1", @@ -198,7 +198,8 @@ func TestMavenWrapperTreesTypes(t *testing.T) { // dependency of pom type depWithPomType := uniqueDeps["gav://org.webjars:lodash:4.17.21"] assert.NotEmpty(t, depWithPomType) - assert.Equal(t, depWithPomType[0], "pom") + types := *depWithPomType.Types + assert.Equal(t, types[0], "pom") existInTreePom := false for _, node := range tree[0].Nodes { if node.Id == "gav://org.webjars:lodash:4.17.21" { @@ -212,7 +213,8 @@ func TestMavenWrapperTreesTypes(t *testing.T) { // dependency of jar type depWithJarType := uniqueDeps["gav://junit:junit:4.11"] assert.NotEmpty(t, depWithJarType) - assert.Equal(t, depWithJarType[0], "jar") + types = *depWithJarType.Types + assert.Equal(t, types[0], "jar") existInTreeJar := false for _, node := range tree[0].Nodes { if node.Id == "gav://junit:junit:4.11" { @@ -221,6 +223,12 @@ func TestMavenWrapperTreesTypes(t *testing.T) { existInTreeJar = true } } + // dependency with classifier + depWithJarClassifier1 := uniqueDeps["gav://commons-io:commons-io:1.2-flavor1"] + assert.NotEmpty(t, depWithJarClassifier1) + depWithJarClassifier2 := uniqueDeps["gav://commons-io:commons-io:1.2-flavor2"] + assert.NotEmpty(t, depWithJarClassifier2) + assert.True(t, existInTreeJar) } diff --git a/commands/audit/sca/java/resources/maven-dep-tree.jar b/commands/audit/sca/java/resources/maven-dep-tree.jar index f8d7ff406591d396976cc576c6cf0ce87e75042d..d1863b87031e9885c2a7f1399e68f1d5efd17b15 100644 GIT binary patch delta 8711 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 aWAFIq6#jP{gu>8lp8qCup&If2)AL_N0-fFf diff --git a/commands/audit/sca/npm/npm.go b/commands/audit/sca/npm/npm.go index 00478a7a..20bc531d 100644 --- a/commands/audit/sca/npm/npm.go +++ b/commands/audit/sca/npm/npm.go @@ -7,7 +7,6 @@ import ( buildinfo "github.com/jfrog/build-info-go/entities" "github.com/jfrog/jfrog-cli-core/v2/artifactory/commands/npm" "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" - coreXray "github.com/jfrog/jfrog-cli-core/v2/utils/xray" "github.com/jfrog/jfrog-cli-security/utils" "github.com/jfrog/jfrog-client-go/utils/log" xrayUtils "github.com/jfrog/jfrog-client-go/xray/services/utils" @@ -105,7 +104,7 @@ func addIgnoreScriptsFlag(npmArgs []string) []string { // Parse the dependencies into an Xray dependency tree format func parseNpmDependenciesList(dependencies []buildinfo.Dependency, packageInfo *biutils.PackageInfo) (*xrayUtils.GraphNode, []string) { - treeMap := make(map[string]coreXray.DepTreeNode) + treeMap := make(map[string]utils.DepTreeNode) for _, dependency := range dependencies { dependencyId := utils.NpmPackageTypeIdentifier + dependency.Id for _, requestedByNode := range dependency.RequestedBy { @@ -119,7 +118,7 @@ func parseNpmDependenciesList(dependencies []buildinfo.Dependency, packageInfo * treeMap[parent] = depTreeNode } } - graph, nodeMapTypes := coreXray.BuildXrayDependencyTree(treeMap, utils.NpmPackageTypeIdentifier+packageInfo.BuildInfoModuleId()) + graph, nodeMapTypes := utils.BuildXrayDependencyTree(treeMap, utils.NpmPackageTypeIdentifier+packageInfo.BuildInfoModuleId()) return graph, maps.Keys(nodeMapTypes) } diff --git a/commands/audit/sca/nuget/nuget.go b/commands/audit/sca/nuget/nuget.go index c3da7bc8..ffec7824 100644 --- a/commands/audit/sca/nuget/nuget.go +++ b/commands/audit/sca/nuget/nuget.go @@ -16,7 +16,6 @@ import ( "github.com/jfrog/gofrog/datastructures" "github.com/jfrog/jfrog-cli-core/v2/artifactory/commands/dotnet" "github.com/jfrog/jfrog-cli-core/v2/utils/config" - coreXray "github.com/jfrog/jfrog-cli-core/v2/utils/xray" "github.com/jfrog/jfrog-cli-security/commands/audit/sca" "github.com/jfrog/jfrog-cli-security/utils" "github.com/jfrog/jfrog-client-go/utils/errorutils" @@ -219,7 +218,7 @@ func runDotnetRestore(wd string, params utils.AuditParams, toolType bidotnet.Too func parseNugetDependencyTree(buildInfo *entities.BuildInfo) (nodes []*xrayUtils.GraphNode, allUniqueDeps []string) { uniqueDepsSet := datastructures.MakeSet[string]() for _, module := range buildInfo.Modules { - treeMap := make(map[string]coreXray.DepTreeNode) + treeMap := make(map[string]utils.DepTreeNode) for _, dependency := range module.Dependencies { dependencyId := nugetPackageTypeIdentifier + dependency.Id parent := nugetPackageTypeIdentifier + dependency.RequestedBy[0][0] @@ -231,7 +230,7 @@ func parseNugetDependencyTree(buildInfo *entities.BuildInfo) (nodes []*xrayUtils } treeMap[parent] = depTreeNode } - dependencyTree, uniqueDeps := coreXray.BuildXrayDependencyTree(treeMap, nugetPackageTypeIdentifier+module.Id) + dependencyTree, uniqueDeps := utils.BuildXrayDependencyTree(treeMap, nugetPackageTypeIdentifier+module.Id) nodes = append(nodes, dependencyTree) for _, uniqueDep := range maps.Keys(uniqueDeps) { uniqueDepsSet.Add(uniqueDep) diff --git a/commands/audit/sca/pnpm/pnpm.go b/commands/audit/sca/pnpm/pnpm.go index 39dedce5..5ae20b2e 100644 --- a/commands/audit/sca/pnpm/pnpm.go +++ b/commands/audit/sca/pnpm/pnpm.go @@ -20,7 +20,6 @@ import ( "github.com/jfrog/jfrog-client-go/utils/log" biutils "github.com/jfrog/build-info-go/utils" - coreXray "github.com/jfrog/jfrog-cli-core/v2/utils/xray" xrayUtils "github.com/jfrog/jfrog-client-go/xray/services/utils" ) @@ -143,7 +142,7 @@ func parsePnpmLSContent(projectInfo []pnpmLsProject) (dependencyTrees []*xrayUti uniqueDepsSet := datastructures.MakeSet[string]() for _, project := range projectInfo { // Parse the dependencies into Xray dependency tree format - dependencyTree, uniqueProjectDeps := coreXray.BuildXrayDependencyTree(createProjectDependenciesTree(project), getDependencyId(project.Name, project.Version)) + dependencyTree, uniqueProjectDeps := utils.BuildXrayDependencyTree(createProjectDependenciesTree(project), getDependencyId(project.Name, project.Version)) // Add results dependencyTrees = append(dependencyTrees, dependencyTree) uniqueDepsSet.AddElements(maps.Keys(uniqueProjectDeps)...) @@ -152,8 +151,8 @@ func parsePnpmLSContent(projectInfo []pnpmLsProject) (dependencyTrees []*xrayUti return } -func createProjectDependenciesTree(project pnpmLsProject) map[string]coreXray.DepTreeNode { - treeMap := make(map[string]coreXray.DepTreeNode) +func createProjectDependenciesTree(project pnpmLsProject) map[string]utils.DepTreeNode { + treeMap := make(map[string]utils.DepTreeNode) directDependencies := []string{} // Handle production-dependencies for depName, dependency := range project.Dependencies { @@ -168,7 +167,7 @@ func createProjectDependenciesTree(project pnpmLsProject) map[string]coreXray.De appendTransitiveDependencies(directDependency, dependency.Dependencies, treeMap) } if len(directDependencies) > 0 { - treeMap[getDependencyId(project.Name, project.Version)] = coreXray.DepTreeNode{Children: directDependencies} + treeMap[getDependencyId(project.Name, project.Version)] = utils.DepTreeNode{Children: directDependencies} } return treeMap } @@ -178,13 +177,13 @@ func getDependencyId(depName, version string) string { return utils.NpmPackageTypeIdentifier + depName + ":" + version } -func appendTransitiveDependencies(parent string, dependencies map[string]pnpmLsDependency, result map[string]coreXray.DepTreeNode) { +func appendTransitiveDependencies(parent string, dependencies map[string]pnpmLsDependency, result map[string]utils.DepTreeNode) { for depName, dependency := range dependencies { dependencyId := getDependencyId(depName, dependency.Version) if node, ok := result[parent]; ok { node.Children = appendUniqueChild(node.Children, dependencyId) } else { - result[parent] = coreXray.DepTreeNode{Children: []string{dependencyId}} + result[parent] = utils.DepTreeNode{Children: []string{dependencyId}} } appendTransitiveDependencies(dependencyId, dependency.Dependencies, result) } diff --git a/commands/audit/sca/yarn/yarn.go b/commands/audit/sca/yarn/yarn.go index c70e623c..6a1fe54d 100644 --- a/commands/audit/sca/yarn/yarn.go +++ b/commands/audit/sca/yarn/yarn.go @@ -13,7 +13,6 @@ import ( "github.com/jfrog/jfrog-cli-core/v2/utils/config" "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" "github.com/jfrog/jfrog-cli-core/v2/utils/ioutils" - coreXray "github.com/jfrog/jfrog-cli-core/v2/utils/xray" "github.com/jfrog/jfrog-cli-security/utils" "github.com/jfrog/jfrog-client-go/utils/errorutils" "github.com/jfrog/jfrog-client-go/utils/io/fileutils" @@ -200,7 +199,7 @@ func runYarnInstallAccordingToVersion(curWd, yarnExecPath string, installCommand // Parse the dependencies into a Xray dependency tree format func parseYarnDependenciesMap(dependencies map[string]*biutils.YarnDependency, rootXrayId string) (*xrayUtils.GraphNode, []string) { - treeMap := make(map[string]coreXray.DepTreeNode) + treeMap := make(map[string]utils.DepTreeNode) for _, dependency := range dependencies { xrayDepId := getXrayDependencyId(dependency) var subDeps []string @@ -208,10 +207,10 @@ func parseYarnDependenciesMap(dependencies map[string]*biutils.YarnDependency, r subDeps = append(subDeps, getXrayDependencyId(dependencies[biutils.GetYarnDependencyKeyFromLocator(subDepPtr.Locator)])) } if len(subDeps) > 0 { - treeMap[xrayDepId] = coreXray.DepTreeNode{Children: subDeps} + treeMap[xrayDepId] = utils.DepTreeNode{Children: subDeps} } } - graph, uniqDeps := coreXray.BuildXrayDependencyTree(treeMap, rootXrayId) + graph, uniqDeps := utils.BuildXrayDependencyTree(treeMap, rootXrayId) return graph, maps.Keys(uniqDeps) } diff --git a/commands/audit/scarunner.go b/commands/audit/scarunner.go index 2472701c..56bab257 100644 --- a/commands/audit/scarunner.go +++ b/commands/audit/scarunner.go @@ -210,7 +210,7 @@ func GetTechDependencyTree(params xrayutils.AuditParams, tech coreutils.Technolo return } var uniqueDeps []string - var uniqDepsWithTypes map[string][]string + var uniqDepsWithTypes map[string]*xrayutils.DepTreeNode startTime := time.Now() switch tech { @@ -345,14 +345,18 @@ func SetResolutionRepoIfExists(params xrayutils.AuditParams, tech coreutils.Tech return } -func createFlatTreeWithTypes(uniqueDeps map[string][]string) (*xrayCmdUtils.GraphNode, error) { +func createFlatTreeWithTypes(uniqueDeps map[string]*xrayutils.DepTreeNode) (*xrayCmdUtils.GraphNode, error) { if err := logDeps(uniqueDeps); err != nil { return nil, err } var uniqueNodes []*xrayCmdUtils.GraphNode - for uniqueDep, types := range uniqueDeps { - p := types - uniqueNodes = append(uniqueNodes, &xrayCmdUtils.GraphNode{Id: uniqueDep, Types: &p}) + for uniqueDep, nodeAttr := range uniqueDeps { + node := &xrayCmdUtils.GraphNode{Id: uniqueDep} + if nodeAttr != nil { + node.Types = nodeAttr.Types + node.Classifier = nodeAttr.Classifier + } + uniqueNodes = append(uniqueNodes, node) } return &xrayCmdUtils.GraphNode{Id: "root", Nodes: uniqueNodes}, nil } diff --git a/commands/curation/curationaudit.go b/commands/curation/curationaudit.go index 1b4e78d5..e31b20ea 100644 --- a/commands/curation/curationaudit.go +++ b/commands/curation/curationaudit.go @@ -619,7 +619,7 @@ func getUrlNameAndVersionByTech(tech coreutils.Technology, node *xrayUtils.Graph case coreutils.Npm: return getNpmNameScopeAndVersion(node.Id, artiUrl, repo, coreutils.Npm.String()) case coreutils.Maven: - return getMavenNameScopeAndVersion(node.Id, artiUrl, repo, node.Types) + return getMavenNameScopeAndVersion(node.Id, artiUrl, repo, node) case coreutils.Pip: downloadUrls, name, version = getPythonNameVersion(node.Id, downloadUrlsMap) @@ -648,18 +648,22 @@ func getPythonNameVersion(id string, downloadUrlsMap map[string]string) (downloa // input- id: gav://org.apache.tomcat.embed:tomcat-embed-jasper:8.0.33 // input - repo: libs-release -// output - downloadUrl: /libs-release/org/apache/tomcat/embed/tomcat-embed-jasper/8.0.33/tomcat-embed-jasper-8.0.33.jar -func getMavenNameScopeAndVersion(id, artiUrl, repo string, types *[]string) (downloadUrls []string, name, scope, version string) { +// output - downloadUrl: /libs-release/org/apache/tomcat/embed/tomcat-embed-jasper/8.0.33/tomcat-embed-jasper-8.0.33-jdk15.jar +func getMavenNameScopeAndVersion(id, artiUrl, repo string, node *xrayUtils.GraphNode) (downloadUrls []string, name, scope, version string) { id = strings.TrimPrefix(id, "gav://") allParts := strings.Split(id, ":") if len(allParts) < 3 { return } nameVersion := allParts[1] + "-" + allParts[2] + versionDir := allParts[2] + if node != nil && node.Classifier != nil && *node.Classifier != "" { + versionDir = strings.TrimSuffix(versionDir, "-"+*node.Classifier) + } packagePath := strings.Join(strings.Split(allParts[0], "."), "/") + "/" + - allParts[1] + "/" + allParts[2] + "/" + nameVersion - if types != nil { - for _, fileType := range *types { + allParts[1] + "/" + versionDir + "/" + nameVersion + if node.Types != nil { + for _, fileType := range *node.Types { // curation service supports maven only for jar and war file types. if fileType == "jar" || fileType == "war" { downloadUrls = append(downloadUrls, strings.TrimSuffix(artiUrl, "/")+"/"+repo+"/"+packagePath+"."+fileType) diff --git a/go.mod b/go.mod index b8af635c..07dc412b 100644 --- a/go.mod +++ b/go.mod @@ -8,13 +8,13 @@ require ( github.com/jfrog/gofrog v1.7.1 github.com/jfrog/jfrog-apps-config v1.0.1 github.com/jfrog/jfrog-cli-core/v2 v2.51.0 - github.com/jfrog/jfrog-client-go v1.40.1 + github.com/jfrog/jfrog-client-go v1.40.2 github.com/magiconair/properties v1.8.7 github.com/owenrumney/go-sarif/v2 v2.3.0 github.com/stretchr/testify v1.9.0 - golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 - golang.org/x/sync v0.6.0 - golang.org/x/text v0.14.0 + golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 + golang.org/x/sync v0.7.0 + golang.org/x/text v0.15.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -31,7 +31,7 @@ require ( github.com/c-bata/go-prompt v0.2.5 // indirect github.com/chzyer/readline v1.5.1 // indirect github.com/cloudflare/circl v1.3.7 // indirect - github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect github.com/cyphar/filepath-securejoin v0.2.4 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect @@ -40,7 +40,7 @@ require ( github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/go-billy/v5 v5.5.0 // indirect - github.com/go-git/go-git/v5 v5.11.0 // indirect + github.com/go-git/go-git/v5 v5.12.0 // indirect github.com/gocarina/gocsv v0.0.0-20231116093920-b87c2d0e983a // indirect github.com/golang-jwt/jwt/v4 v4.5.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect @@ -48,7 +48,7 @@ require ( github.com/google/uuid v1.6.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect - github.com/jedib0t/go-pretty/v6 v6.5.6 // indirect + github.com/jedib0t/go-pretty/v6 v6.5.9 // indirect github.com/jfrog/archiver/v3 v3.6.0 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect github.com/klauspost/compress v1.17.4 // indirect @@ -72,8 +72,8 @@ require ( github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect - github.com/sergi/go-diff v1.1.0 // indirect - github.com/skeema/knownhosts v1.2.1 // indirect + github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect + github.com/skeema/knownhosts v1.2.2 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.6.0 // indirect @@ -81,25 +81,25 @@ require ( github.com/spf13/viper v1.18.2 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/ulikunitz/xz v0.5.11 // indirect - github.com/urfave/cli v1.22.14 // indirect + github.com/urfave/cli v1.22.15 // indirect github.com/vbauerster/mpb/v7 v7.5.3 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.9.0 // indirect - golang.org/x/crypto v0.21.0 // indirect - golang.org/x/mod v0.16.0 // indirect - golang.org/x/net v0.22.0 // indirect - golang.org/x/sys v0.18.0 // indirect - golang.org/x/term v0.18.0 // indirect - golang.org/x/tools v0.19.0 // indirect + golang.org/x/crypto v0.23.0 // indirect + golang.org/x/mod v0.17.0 // indirect + golang.org/x/net v0.25.0 // indirect + golang.org/x/sys v0.20.0 // indirect + golang.org/x/term v0.20.0 // indirect + golang.org/x/tools v0.21.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect ) -// replace github.com/jfrog/jfrog-cli-core/v2 => github.com/jfrog/jfrog-cli-core/v2 dev +replace github.com/jfrog/jfrog-cli-core/v2 => github.com/jfrog/jfrog-cli-core/v2 v2.31.1-0.20240522073234-a62576f8d17f -// replace github.com/jfrog/jfrog-client-go => github.com/jfrog/jfrog-client-go dev +replace github.com/jfrog/jfrog-client-go => github.com/jfrog/jfrog-client-go v1.28.1-0.20240509124610-4b9c587c0377 // replace github.com/jfrog/build-info-go => github.com/jfrog/build-info-go dev diff --git a/go.sum b/go.sum index 7edec7cd..88fd2692 100644 --- a/go.sum +++ b/go.sum @@ -39,8 +39,8 @@ github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38 github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= -github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= -github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -60,16 +60,16 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= -github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY= -github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4= +github.com/gliderlabs/ssh v0.3.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE= +github.com/gliderlabs/ssh v0.3.7/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU= github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= -github.com/go-git/go-git/v5 v5.11.0 h1:XIZc1p+8YzypNr34itUfSvYJcv+eYdTnTvOZ2vD3cA4= -github.com/go-git/go-git/v5 v5.11.0/go.mod h1:6GFcX2P3NM7FPBfpePbpLd21XxsgdAt+lKqXmCUiUCY= +github.com/go-git/go-git/v5 v5.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZtys= +github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY= github.com/gocarina/gocsv v0.0.0-20231116093920-b87c2d0e983a h1:RYfmiM0zluBJOiPDJseKLEN4BapJ42uSi9SZBQ2YyiA= github.com/gocarina/gocsv v0.0.0-20231116093920-b87c2d0e983a/go.mod h1:5YoVOkjYAQumqlV356Hj3xeYh4BdZuLE0/nRkf2NKkI= github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= @@ -92,8 +92,8 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= -github.com/jedib0t/go-pretty/v6 v6.5.6 h1:nKXVLqPfAwY7sWcYXdNZZZ2fjqDpAtj9UeWupgfUxSg= -github.com/jedib0t/go-pretty/v6 v6.5.6/go.mod h1:5LQIxa52oJ/DlDSLv0HEkWOFMDGoWkJb9ss5KqPpJBg= +github.com/jedib0t/go-pretty/v6 v6.5.9 h1:ACteMBRrrmm1gMsXe9PSTOClQ63IXDUt03H5U+UV8OU= +github.com/jedib0t/go-pretty/v6 v6.5.9/go.mod h1:zbn98qrYlh95FIhwwsbIip0LYpwSG8SUOScs+v9/t0E= github.com/jfrog/archiver/v3 v3.6.0 h1:OVZ50vudkIQmKMgA8mmFF9S0gA47lcag22N13iV3F1w= github.com/jfrog/archiver/v3 v3.6.0/go.mod h1:fCAof46C3rAXgZurS8kNRNdSVMKBbZs+bNNhPYxLldI= github.com/jfrog/build-info-go v1.9.26 h1:1Ddc6+Ecvhc+UMnKhRVG1jGM6fYNwA49207azTBGBc8= @@ -102,10 +102,10 @@ github.com/jfrog/gofrog v1.7.1 h1:ME1Meg4hukAT/7X6HUQCVSe4DNjMZACCP8aCY37EW/w= github.com/jfrog/gofrog v1.7.1/go.mod h1:X7bjfWoQDN0Z4FQGbE91j3gbPP7Urwzm4Z8tkvrlbRI= github.com/jfrog/jfrog-apps-config v1.0.1 h1:mtv6k7g8A8BVhlHGlSveapqf4mJfonwvXYLipdsOFMY= github.com/jfrog/jfrog-apps-config v1.0.1/go.mod h1:8AIIr1oY9JuH5dylz2S6f8Ym2MaadPLR6noCBO4C22w= -github.com/jfrog/jfrog-cli-core/v2 v2.51.0 h1:nESbCpSTPZx1av0W9tdmWLxKaPSL1SaZinbZGtYNeFI= -github.com/jfrog/jfrog-cli-core/v2 v2.51.0/go.mod h1:064wSSHVI3ZIVi/a94yJqzs+ACM+9JK/u9tQ1sfTK6A= -github.com/jfrog/jfrog-client-go v1.40.1 h1:ISSSV7/IUS8R+QCPfH2lVKLburbv2Xn07fvNyDc17rI= -github.com/jfrog/jfrog-client-go v1.40.1/go.mod h1:FprEW0Sqhj6ZSFTFk9NCni+ovFAYMA3zCBmNX4hGXgQ= +github.com/jfrog/jfrog-cli-core/v2 v2.31.1-0.20240522073234-a62576f8d17f h1:fBuzVIQnShcLhDJeNsodveWQFfF6Y4q3kS1wewliYJM= +github.com/jfrog/jfrog-cli-core/v2 v2.31.1-0.20240522073234-a62576f8d17f/go.mod h1:hB5R+BgmCbOoz5HZyyqEdv408rL26ej7ZSHfXGpLqmw= +github.com/jfrog/jfrog-client-go v1.28.1-0.20240509124610-4b9c587c0377 h1:fDneRi89u4hmD0qIf/X+HLT3IavqFeQevRtrZhyJZ5k= +github.com/jfrog/jfrog-client-go v1.28.1-0.20240509124610-4b9c587c0377/go.mod h1:m3hIn12eFWk5nJH1swPRtFrjXbiiCscOpX+v/vCdmNI= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= @@ -181,11 +181,11 @@ github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6ke github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= -github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= -github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/skeema/knownhosts v1.2.1 h1:SHWdIUa82uGZz+F+47k8SY4QhhI291cXCpopT1lK2AQ= -github.com/skeema/knownhosts v1.2.1/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo= +github.com/skeema/knownhosts v1.2.2 h1:Iug2P4fLmDw9f41PB6thxUkNUkJzB5i+1/exaj40L3A= +github.com/skeema/knownhosts v1.2.2/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= @@ -199,6 +199,7 @@ github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMV github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -215,8 +216,8 @@ github.com/terminalstatic/go-xsd-validate v0.1.5/go.mod h1:18lsvYFofBflqCrvo1ump github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8= github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= -github.com/urfave/cli v1.22.14 h1:ebbhrRiGK2i4naQJr+1Xj92HXZCrK7MsyTS/ob3HnAk= -github.com/urfave/cli v1.22.14/go.mod h1:X0eDS6pD6Exaclxm99NJ3FiCDRED7vIHpx2mDOHLvkA= +github.com/urfave/cli v1.22.15 h1:nuqt+pdC/KqswQKhETJjo7pvn/k4xMUxgW6liI7XpnM= +github.com/urfave/cli v1.22.15/go.mod h1:wSan1hmo5zeyLGBjRJbzRTNk8gwoYa2B9n4q9dmRIc0= github.com/vbauerster/mpb/v7 v7.5.3 h1:BkGfmb6nMrrBQDFECR/Q7RkKCw7ylMetCb4079CGs4w= github.com/vbauerster/mpb/v7 v7.5.3/go.mod h1:i+h4QY6lmLvBNK2ah1fSreiw3ajskRlBp9AhY/PnuOE= github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4= @@ -244,14 +245,14 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= -golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= -golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= -golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 h1:aAcj0Da7eBAtrTp03QXWvm88pSyOt+UgdZw2BFZ+lEw= -golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8/go.mod h1:CQ1k9gNrJ50XIzaKCRR2hssIjF07kZFEiieALBM/ARQ= +golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM= +golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= -golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -261,14 +262,14 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= -golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= -golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= -golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -295,15 +296,15 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= -golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= -golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= +golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/text v0.3.0/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/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -313,14 +314,14 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw= -golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc= +golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw= +golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= @@ -334,7 +335,6 @@ gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/tests/testdata/projects/package-managers/maven/maven-example-with-many-types/pom.xml b/tests/testdata/projects/package-managers/maven/maven-example-with-many-types/pom.xml index 6d2096c9..5fc62ef7 100644 --- a/tests/testdata/projects/package-managers/maven/maven-example-with-many-types/pom.xml +++ b/tests/testdata/projects/package-managers/maven/maven-example-with-many-types/pom.xml @@ -42,6 +42,14 @@ commons-io 1.2 test + flavor1 + + + commons-io + commons-io + 1.2 + test + flavor2 org.webjars diff --git a/utils/xrayutils.go b/utils/xrayutils.go new file mode 100644 index 00000000..a43e1e7e --- /dev/null +++ b/utils/xrayutils.go @@ -0,0 +1,59 @@ +package utils + +import ( + xrayUtils "github.com/jfrog/jfrog-client-go/xray/services/utils" +) + +const maxUniqueAppearances = 10 + +type DepTreeNode struct { + Classifier *string `json:"classifier"` + Types *[]string `json:"types"` + Children []string `json:"children"` +} + +func toNodeTypesMap(depMap map[string]DepTreeNode) map[string]*DepTreeNode { + mapOfTypes := map[string]*DepTreeNode{} + for nodId, value := range depMap { + mapOfTypes[nodId] = nil + if value.Types != nil || value.Classifier != nil { + mapOfTypes[nodId] = &DepTreeNode{ + Classifier: value.Classifier, + Types: value.Types, + } + } + } + return mapOfTypes +} + +func BuildXrayDependencyTree(treeHelper map[string]DepTreeNode, nodeId string) (*xrayUtils.GraphNode, map[string]*DepTreeNode) { + rootNode := &xrayUtils.GraphNode{ + Id: nodeId, + Nodes: []*xrayUtils.GraphNode{}, + } + dependencyAppearances := map[string]int8{} + populateXrayDependencyTree(rootNode, treeHelper, dependencyAppearances) + return rootNode, toNodeTypesMap(treeHelper) +} + +func populateXrayDependencyTree(currNode *xrayUtils.GraphNode, treeHelper map[string]DepTreeNode, dependencyAppearances map[string]int8) { + dependencyAppearances[currNode.Id]++ + if _, ok := treeHelper[currNode.Id]; !ok { + treeHelper[currNode.Id] = DepTreeNode{} + } + // Recursively create & append all node's dependencies. + for _, childDepId := range treeHelper[currNode.Id].Children { + childNode := &xrayUtils.GraphNode{ + Id: childDepId, + Nodes: []*xrayUtils.GraphNode{}, + Parent: currNode, + Types: treeHelper[childDepId].Types, + Classifier: treeHelper[childDepId].Classifier, + } + if dependencyAppearances[childDepId] >= maxUniqueAppearances || childNode.NodeHasLoop() { + continue + } + currNode.Nodes = append(currNode.Nodes, childNode) + populateXrayDependencyTree(childNode, treeHelper, dependencyAppearances) + } +} From 3e9fe2dc5e8114b48532dc48b02632fb80d9325c Mon Sep 17 00:00:00 2001 From: Assaf Attias <49212512+attiasas@users.noreply.github.com> Date: Wed, 22 May 2024 15:13:07 +0300 Subject: [PATCH 2/3] Add summary format (#63) --- commands/audit/scarunner.go | 15 +- commands/audit/scarunner_test.go | 56 +-- commands/scan/buildscan.go | 8 +- commands/scan/scan.go | 26 +- formats/summary.go | 111 +++++ formats/summary_test.go | 104 +++++ go.mod | 2 +- go.sum | 4 +- .../other/jobSummary/multi_command_job.md | 16 + .../testdata/other/jobSummary/single_issue.md | 1 + .../other/jobSummary/single_no_issue.md | 3 + utils/analyticsmetrics_test.go | 26 +- utils/analyzermanager.go | 15 + utils/results.go | 83 ++-- utils/results_test.go | 160 +++++++ utils/resultstable.go | 10 +- utils/resultstable_test.go | 2 +- utils/sarifutils.go | 32 +- utils/securityJobSummary.go | 420 ++++++++++++++++++ utils/securityJobSummary_test.go | 141 ++++++ 20 files changed, 1137 insertions(+), 98 deletions(-) create mode 100644 formats/summary.go create mode 100644 formats/summary_test.go create mode 100644 tests/testdata/other/jobSummary/multi_command_job.md create mode 100644 tests/testdata/other/jobSummary/single_issue.md create mode 100644 tests/testdata/other/jobSummary/single_no_issue.md create mode 100644 utils/results_test.go create mode 100644 utils/securityJobSummary.go create mode 100644 utils/securityJobSummary_test.go diff --git a/commands/audit/scarunner.go b/commands/audit/scarunner.go index 56bab257..a8edf387 100644 --- a/commands/audit/scarunner.go +++ b/commands/audit/scarunner.go @@ -4,11 +4,12 @@ import ( "encoding/json" "errors" "fmt" - "github.com/jfrog/build-info-go/utils/pythonutils" - "github.com/jfrog/jfrog-client-go/utils/io/fileutils" "os" "time" + "github.com/jfrog/build-info-go/utils/pythonutils" + "github.com/jfrog/jfrog-client-go/utils/io/fileutils" + "github.com/jfrog/gofrog/datastructures" "github.com/jfrog/jfrog-cli-core/v2/common/project" "github.com/jfrog/jfrog-cli-core/v2/utils/config" @@ -58,9 +59,9 @@ func runScaScan(params *AuditParams, results *xrayutils.Results) (err error) { }() for _, scan := range scans { // Run the scan - log.Info("Running SCA scan for", scan.Technology, "vulnerable dependencies in", scan.WorkingDirectory, "directory...") + log.Info("Running SCA scan for", scan.Technology, "vulnerable dependencies in", scan.Target, "directory...") if wdScanErr := executeScaScan(serverDetails, params, scan); wdScanErr != nil { - err = errors.Join(err, fmt.Errorf("audit command in '%s' failed:\n%s", scan.WorkingDirectory, wdScanErr.Error())) + err = errors.Join(err, fmt.Errorf("audit command in '%s' failed:\n%s", scan.Target, wdScanErr.Error())) continue } // Add the scan to the results @@ -87,11 +88,11 @@ func getScaScansToPreform(params *AuditParams) (scansToPreform []*xrayutils.ScaS } if len(workingDirs) == 0 { // Requested technology (from params) descriptors/indicators was not found, scan only requested directory for this technology. - scansToPreform = append(scansToPreform, &xrayutils.ScaScanResult{WorkingDirectory: requestedDirectory, Technology: tech}) + scansToPreform = append(scansToPreform, &xrayutils.ScaScanResult{Target: requestedDirectory, Technology: tech}) } for workingDir, descriptors := range workingDirs { // Add scan for each detected working directory. - scansToPreform = append(scansToPreform, &xrayutils.ScaScanResult{WorkingDirectory: workingDir, Technology: tech, Descriptors: descriptors}) + scansToPreform = append(scansToPreform, &xrayutils.ScaScanResult{Target: workingDir, Technology: tech, Descriptors: descriptors}) } } } @@ -110,7 +111,7 @@ func getRequestedDescriptors(params *AuditParams) map[coreutils.Technology][]str // This method will change the working directory to the scan's working directory. func executeScaScan(serverDetails *config.ServerDetails, params *AuditParams, scan *xrayutils.ScaScanResult) (err error) { // Get the dependency tree for the technology in the working directory. - if err = os.Chdir(scan.WorkingDirectory); err != nil { + if err = os.Chdir(scan.Target); err != nil { return errorutils.CheckError(err) } treeResult, techErr := GetTechDependencyTree(params.AuditBasicParams, scan.Technology) diff --git a/commands/audit/scarunner_test.go b/commands/audit/scarunner_test.go index 68162094..897b0426 100644 --- a/commands/audit/scarunner_test.go +++ b/commands/audit/scarunner_test.go @@ -137,8 +137,8 @@ func TestGetScaScansToPreform(t *testing.T) { }, expected: []*xrayutils.ScaScanResult{ { - Technology: coreutils.Maven, - WorkingDirectory: filepath.Join(dir, "dir", "maven"), + Technology: coreutils.Maven, + Target: filepath.Join(dir, "dir", "maven"), Descriptors: []string{ filepath.Join(dir, "dir", "maven", "pom.xml"), filepath.Join(dir, "dir", "maven", "maven-sub", "pom.xml"), @@ -146,14 +146,14 @@ func TestGetScaScansToPreform(t *testing.T) { }, }, { - Technology: coreutils.Npm, - WorkingDirectory: filepath.Join(dir, "dir", "npm"), - Descriptors: []string{filepath.Join(dir, "dir", "npm", "package.json")}, + Technology: coreutils.Npm, + Target: filepath.Join(dir, "dir", "npm"), + Descriptors: []string{filepath.Join(dir, "dir", "npm", "package.json")}, }, { - Technology: coreutils.Go, - WorkingDirectory: filepath.Join(dir, "dir", "go"), - Descriptors: []string{filepath.Join(dir, "dir", "go", "go.mod")}, + Technology: coreutils.Go, + Target: filepath.Join(dir, "dir", "go"), + Descriptors: []string{filepath.Join(dir, "dir", "go", "go.mod")}, }, }, }, @@ -167,8 +167,8 @@ func TestGetScaScansToPreform(t *testing.T) { }, expected: []*xrayutils.ScaScanResult{ { - Technology: coreutils.Maven, - WorkingDirectory: filepath.Join(dir, "dir", "maven"), + Technology: coreutils.Maven, + Target: filepath.Join(dir, "dir", "maven"), Descriptors: []string{ filepath.Join(dir, "dir", "maven", "pom.xml"), filepath.Join(dir, "dir", "maven", "maven-sub", "pom.xml"), @@ -176,34 +176,34 @@ func TestGetScaScansToPreform(t *testing.T) { }, }, { - Technology: coreutils.Npm, - WorkingDirectory: filepath.Join(dir, "dir", "npm"), - Descriptors: []string{filepath.Join(dir, "dir", "npm", "package.json")}, + Technology: coreutils.Npm, + Target: filepath.Join(dir, "dir", "npm"), + Descriptors: []string{filepath.Join(dir, "dir", "npm", "package.json")}, }, { - Technology: coreutils.Go, - WorkingDirectory: filepath.Join(dir, "dir", "go"), - Descriptors: []string{filepath.Join(dir, "dir", "go", "go.mod")}, + Technology: coreutils.Go, + Target: filepath.Join(dir, "dir", "go"), + Descriptors: []string{filepath.Join(dir, "dir", "go", "go.mod")}, }, { - Technology: coreutils.Yarn, - WorkingDirectory: filepath.Join(dir, "yarn"), - Descriptors: []string{filepath.Join(dir, "yarn", "package.json")}, + Technology: coreutils.Yarn, + Target: filepath.Join(dir, "yarn"), + Descriptors: []string{filepath.Join(dir, "yarn", "package.json")}, }, { - Technology: coreutils.Pip, - WorkingDirectory: filepath.Join(dir, "yarn", "Pip"), - Descriptors: []string{filepath.Join(dir, "yarn", "Pip", "requirements.txt")}, + Technology: coreutils.Pip, + Target: filepath.Join(dir, "yarn", "Pip"), + Descriptors: []string{filepath.Join(dir, "yarn", "Pip", "requirements.txt")}, }, { - Technology: coreutils.Pipenv, - WorkingDirectory: filepath.Join(dir, "yarn", "Pipenv"), - Descriptors: []string{filepath.Join(dir, "yarn", "Pipenv", "Pipfile")}, + Technology: coreutils.Pipenv, + Target: filepath.Join(dir, "yarn", "Pipenv"), + Descriptors: []string{filepath.Join(dir, "yarn", "Pipenv", "Pipfile")}, }, { - Technology: coreutils.Nuget, - WorkingDirectory: filepath.Join(dir, "Nuget"), - Descriptors: []string{filepath.Join(dir, "Nuget", "project.sln"), filepath.Join(dir, "Nuget", "Nuget-sub", "project.csproj")}, + Technology: coreutils.Nuget, + Target: filepath.Join(dir, "Nuget"), + Descriptors: []string{filepath.Join(dir, "Nuget", "project.sln"), filepath.Join(dir, "Nuget", "Nuget-sub", "project.csproj")}, }, }, }, diff --git a/commands/scan/buildscan.go b/commands/scan/buildscan.go index bbd18d0b..2bcbd66d 100644 --- a/commands/scan/buildscan.go +++ b/commands/scan/buildscan.go @@ -2,6 +2,7 @@ package scan import ( "errors" + "fmt" "github.com/jfrog/jfrog-cli-core/v2/common/build" outputFormat "github.com/jfrog/jfrog-cli-core/v2/common/format" @@ -130,7 +131,7 @@ func (bsc *BuildScanCommand) runBuildScanAndPrintResults(xrayManager *xray.XrayS scanResults := xrutils.NewAuditResults() scanResults.XrayVersion = xrayVersion - scanResults.ScaResults = []xrutils.ScaScanResult{{XrayResults: scanResponse}} + scanResults.ScaResults = []xrutils.ScaScanResult{{Target: fmt.Sprintf("%s (%s)", params.BuildName, params.BuildNumber), XrayResults: scanResponse}} resultsPrinter := xrutils.NewResultsWriter(scanResults). SetOutputFormat(bsc.outputFormat). @@ -143,7 +144,9 @@ func (bsc *BuildScanCommand) runBuildScanAndPrintResults(xrayManager *xray.XrayS if bsc.outputFormat != outputFormat.Table { // Print the violations and/or vulnerabilities as part of one JSON. - err = resultsPrinter.PrintScanResults() + if err = resultsPrinter.PrintScanResults(); err != nil { + return + } } else { // Print two different tables for violations and vulnerabilities (if needed) @@ -160,6 +163,7 @@ func (bsc *BuildScanCommand) runBuildScanAndPrintResults(xrayManager *xray.XrayS } } } + err = utils.RecordSecurityCommandOutput(utils.ScanCommandSummaryResult{Results: scanResults.GetSummary(), Section: utils.Build}) return } diff --git a/commands/scan/scan.go b/commands/scan/scan.go index c8035d84..c707fd22 100644 --- a/commands/scan/scan.go +++ b/commands/scan/scan.go @@ -34,6 +34,11 @@ import ( type FileContext func(string) parallel.TaskFunc type indexFileHandlerFunc func(file string) +type ScanInfo struct { + Target string + Result *services.ScanResponse +} + const ( BypassArchiveLimitsMinXrayVersion = "3.59.0" indexingCommand = "graph" @@ -213,7 +218,7 @@ func (scanCmd *ScanCommand) Run() (err error) { } // resultsArr is a two-dimensional array. Each array in it contains a list of ScanResponses that were requested and collected by a specific thread. - resultsArr := make([][]*services.ScanResponse, threads) + resultsArr := make([][]*ScanInfo, threads) fileProducerConsumer := parallel.NewRunner(scanCmd.threads, 20000, false) fileProducerErrors := make([][]formats.SimpleJsonError, threads) indexedFileProducerConsumer := parallel.NewRunner(scanCmd.threads, 20000, false) @@ -225,10 +230,10 @@ func (scanCmd *ScanCommand) Run() (err error) { scanCmd.performScanTasks(fileProducerConsumer, indexedFileProducerConsumer) // Handle results - flatResults := []services.ScanResponse{} + flatResults := []xrutils.ScaScanResult{} for _, arr := range resultsArr { for _, res := range arr { - flatResults = append(flatResults, *res) + flatResults = append(flatResults, xrutils.ScaScanResult{Target: res.Target, XrayResults: []services.ScanResponse{*res.Result}}) } } if scanCmd.progress != nil { @@ -248,7 +253,7 @@ func (scanCmd *ScanCommand) Run() (err error) { scanResults := xrutils.NewAuditResults() scanResults.XrayVersion = xrayVersion - scanResults.ScaResults = []xrutils.ScaScanResult{{XrayResults: flatResults}} + scanResults.ScaResults = flatResults if err = xrutils.NewResultsWriter(scanResults). SetOutputFormat(scanCmd.outputFormat). @@ -264,10 +269,15 @@ func (scanCmd *ScanCommand) Run() (err error) { if err != nil { return err } + + if err = utils.RecordSecurityCommandOutput(utils.ScanCommandSummaryResult{Results: scanResults.GetSummary(), Section: utils.Binary}); err != nil { + return err + } + // If includeVulnerabilities is false it means that context was provided, so we need to check for build violations. // If user provided --fail=false, don't fail the build. if scanCmd.fail && !scanCmd.includeVulnerabilities { - if xrutils.CheckIfFailBuild(flatResults) { + if xrutils.CheckIfFailBuild(scanResults.GetScaScansXrayResults()) { return xrutils.NewFailBuildError() } } @@ -286,7 +296,7 @@ func (scanCmd *ScanCommand) CommandName() string { return "xr_scan" } -func (scanCmd *ScanCommand) prepareScanTasks(fileProducer, indexedFileProducer parallel.Runner, resultsArr [][]*services.ScanResponse, fileErrors, indexedFileErrors [][]formats.SimpleJsonError, fileCollectingErrorsQueue *clientutils.ErrorsQueue, xrayVersion string) { +func (scanCmd *ScanCommand) prepareScanTasks(fileProducer, indexedFileProducer parallel.Runner, resultsArr [][]*ScanInfo, fileErrors, indexedFileErrors [][]formats.SimpleJsonError, fileCollectingErrorsQueue *clientutils.ErrorsQueue, xrayVersion string) { go func() { defer fileProducer.Done() // Iterate over file-spec groups and produce indexing tasks. @@ -305,7 +315,7 @@ func (scanCmd *ScanCommand) prepareScanTasks(fileProducer, indexedFileProducer p }() } -func (scanCmd *ScanCommand) createIndexerHandlerFunc(file *spec.File, indexedFileProducer parallel.Runner, resultsArr [][]*services.ScanResponse, fileErrors, indexedFileErrors [][]formats.SimpleJsonError, xrayVersion string) FileContext { +func (scanCmd *ScanCommand) createIndexerHandlerFunc(file *spec.File, indexedFileProducer parallel.Runner, resultsArr [][]*ScanInfo, fileErrors, indexedFileErrors [][]formats.SimpleJsonError, xrayVersion string) FileContext { return func(filePath string) parallel.TaskFunc { return func(threadId int) (err error) { logMsgPrefix := clientutils.GetLogMsgPrefix(threadId, false) @@ -355,7 +365,7 @@ func (scanCmd *ScanCommand) createIndexerHandlerFunc(file *spec.File, indexedFil indexedFileErrors[threadId] = append(indexedFileErrors[threadId], formats.SimpleJsonError{FilePath: filePath, ErrorMessage: err.Error()}) return } - resultsArr[threadId] = append(resultsArr[threadId], scanResults) + resultsArr[threadId] = append(resultsArr[threadId], &ScanInfo{Target: filePath, Result: scanResults}) return } diff --git a/formats/summary.go b/formats/summary.go new file mode 100644 index 00000000..e43c8a5c --- /dev/null +++ b/formats/summary.go @@ -0,0 +1,111 @@ +package formats + +type SummaryResults struct { + Scans []ScanSummaryResult `json:"scans"` +} + +func (sr SummaryResults) GetTotalIssueCount() (total int) { + for _, scan := range sr.Scans { + total += scan.GetTotalIssueCount() + } + return +} + +type ScanSummaryResult struct { + Target string `json:"target,omitempty"` + ScaScanResults *ScaSummaryCount `json:"sca,omitempty"` + IacScanResults *SummaryCount `json:"iac,omitempty"` + SecretsScanResults *SummaryCount `json:"secrets,omitempty"` + SastScanResults *SummaryCount `json:"sast,omitempty"` +} + +type SummarySubScanType string + +const ( + ScaScan SummarySubScanType = "SCA" + IacScan SummarySubScanType = "IAC" + SecretsScan SummarySubScanType = "Secrets" + SastScan SummarySubScanType = "SAST" +) + +// Severity -> Count +type SummaryCount map[string]int + +func (sc SummaryCount) GetTotal() int { + total := 0 + for _, count := range sc { + total += count + } + return total +} + +// Severity -> Applicable status -> Count +type ScaSummaryCount map[string]SummaryCount + +func (sc ScaSummaryCount) GetTotal() (total int) { + for _, count := range sc { + total += count.GetTotal() + } + return +} + +func (sc ScaSummaryCount) GetSeverityCountsWithoutStatus() (severityCounts SummaryCount) { + severityCounts = SummaryCount{} + for severity, statusCounts := range sc { + for _, count := range statusCounts { + severityCounts[severity] += count + } + } + return +} + +func (s *ScanSummaryResult) HasIssues() bool { + return s.GetTotalIssueCount() > 0 +} + +func (s *ScanSummaryResult) GetTotalIssueCount() (total int) { + if s.ScaScanResults != nil { + total += s.ScaScanResults.GetTotal() + } + if s.IacScanResults != nil { + total += s.IacScanResults.GetTotal() + } + if s.SecretsScanResults != nil { + total += s.SecretsScanResults.GetTotal() + } + if s.SastScanResults != nil { + total += s.SastScanResults.GetTotal() + } + return +} + +func (s *ScanSummaryResult) GetSubScansWithIssues() []SummarySubScanType { + subScans := []SummarySubScanType{} + if s.SecretsScanResults != nil && s.SecretsScanResults.GetTotal() > 0 { + subScans = append(subScans, SecretsScan) + } + if s.SastScanResults != nil && s.SastScanResults.GetTotal() > 0 { + subScans = append(subScans, SastScan) + } + if s.IacScanResults != nil && s.IacScanResults.GetTotal() > 0 { + subScans = append(subScans, IacScan) + } + if s.ScaScanResults != nil && s.ScaScanResults.GetTotal() > 0 { + subScans = append(subScans, ScaScan) + } + return subScans +} + +func (s *ScanSummaryResult) GetSubScanTotalIssueCount(subScanType SummarySubScanType) (count int) { + switch subScanType { + case ScaScan: + count = s.ScaScanResults.GetTotal() + case IacScan: + count = s.IacScanResults.GetTotal() + case SecretsScan: + count = s.SecretsScanResults.GetTotal() + case SastScan: + count = s.SastScanResults.GetTotal() + } + return +} diff --git a/formats/summary_test.go b/formats/summary_test.go new file mode 100644 index 00000000..7a216662 --- /dev/null +++ b/formats/summary_test.go @@ -0,0 +1,104 @@ +package formats + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestSummaryCount(t *testing.T) { + testCases := []struct { + name string + count SummaryCount + expected int + }{ + {"Empty", SummaryCount{}, 0}, + {"Single", SummaryCount{"High": 1}, 1}, + {"Multiple", SummaryCount{"High": 1, "Medium": 2, "Low": 3}, 6}, + } + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + assert.Equal(t, testCase.expected, testCase.count.GetTotal()) + }) + } +} + +func TestScaSummaryCount(t *testing.T) { + testCases := []struct { + name string + count ScaSummaryCount + expected int + expectedSeverityCountsWithoutStatus SummaryCount + }{ + {"Empty", ScaSummaryCount{}, 0, SummaryCount{}}, + {"Single-NoStatus", ScaSummaryCount{"High": SummaryCount{"": 1}}, 1, SummaryCount{"High": 1}}, + {"Single-Status", ScaSummaryCount{"High": SummaryCount{"Applicable": 1}}, 1, SummaryCount{"High": 1}}, + { + "Multiple-NoStatus", + ScaSummaryCount{"High": SummaryCount{"": 1}, "Medium": SummaryCount{"": 2}, "Low": SummaryCount{"": 3}}, + 6, + SummaryCount{"High": 1, "Medium": 2, "Low": 3}, + }, + { + "Multiple-Status", + ScaSummaryCount{"High": SummaryCount{"Applicable": 1}, "Medium": SummaryCount{"": 2}, "Low": SummaryCount{"Applicable": 3, "Not Applicable": 3}}, + 9, + SummaryCount{"High": 1, "Medium": 2, "Low": 6}, + }, + } + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + assert.Equal(t, testCase.expected, testCase.count.GetTotal()) + assert.Equal(t, testCase.expectedSeverityCountsWithoutStatus, testCase.count.GetSeverityCountsWithoutStatus()) + }) + } +} + +func TestScanSummaryResult(t *testing.T) { + testCases := []struct { + name string + result *ScanSummaryResult + expectedTotalIssueCount int + expectedSubScansWithIssues []SummarySubScanType + expectedSubScansIssuesCount map[SummarySubScanType]int + }{ + { + "Empty", + &ScanSummaryResult{}, + 0, + []SummarySubScanType{}, + map[SummarySubScanType]int{}, + }, + { + "Single", + &ScanSummaryResult{ + ScaScanResults: &ScaSummaryCount{"High": SummaryCount{"Applicable": 1}}, + }, + 1, + []SummarySubScanType{ScaScan}, + map[SummarySubScanType]int{ScaScan: 1}, + }, + { + "Multiple", + &ScanSummaryResult{ + ScaScanResults: &ScaSummaryCount{"High": SummaryCount{"Applicable": 1}}, + SastScanResults: &SummaryCount{"High": 1}, + }, + 2, + []SummarySubScanType{SastScan, ScaScan}, + map[SummarySubScanType]int{SastScan: 1, ScaScan: 1}, + }, + } + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + assert.Equal(t, testCase.expectedTotalIssueCount > 0, testCase.result.HasIssues()) + assert.Equal(t, testCase.expectedTotalIssueCount, testCase.result.GetTotalIssueCount()) + if assert.Equal(t, testCase.expectedSubScansWithIssues, testCase.result.GetSubScansWithIssues()) { + for subScan, expectedCount := range testCase.expectedSubScansIssuesCount { + assert.Equal(t, expectedCount, testCase.result.GetSubScanTotalIssueCount(subScan)) + } + } + }) + } + +} diff --git a/go.mod b/go.mod index 07dc412b..72deb875 100644 --- a/go.mod +++ b/go.mod @@ -98,7 +98,7 @@ require ( gopkg.in/warnings.v0 v0.1.2 // indirect ) -replace github.com/jfrog/jfrog-cli-core/v2 => github.com/jfrog/jfrog-cli-core/v2 v2.31.1-0.20240522073234-a62576f8d17f +replace github.com/jfrog/jfrog-cli-core/v2 => github.com/jfrog/jfrog-cli-core/v2 v2.31.1-0.20240522101456-44e989e0a442 replace github.com/jfrog/jfrog-client-go => github.com/jfrog/jfrog-client-go v1.28.1-0.20240509124610-4b9c587c0377 diff --git a/go.sum b/go.sum index 88fd2692..40100f77 100644 --- a/go.sum +++ b/go.sum @@ -102,8 +102,8 @@ github.com/jfrog/gofrog v1.7.1 h1:ME1Meg4hukAT/7X6HUQCVSe4DNjMZACCP8aCY37EW/w= github.com/jfrog/gofrog v1.7.1/go.mod h1:X7bjfWoQDN0Z4FQGbE91j3gbPP7Urwzm4Z8tkvrlbRI= github.com/jfrog/jfrog-apps-config v1.0.1 h1:mtv6k7g8A8BVhlHGlSveapqf4mJfonwvXYLipdsOFMY= github.com/jfrog/jfrog-apps-config v1.0.1/go.mod h1:8AIIr1oY9JuH5dylz2S6f8Ym2MaadPLR6noCBO4C22w= -github.com/jfrog/jfrog-cli-core/v2 v2.31.1-0.20240522073234-a62576f8d17f h1:fBuzVIQnShcLhDJeNsodveWQFfF6Y4q3kS1wewliYJM= -github.com/jfrog/jfrog-cli-core/v2 v2.31.1-0.20240522073234-a62576f8d17f/go.mod h1:hB5R+BgmCbOoz5HZyyqEdv408rL26ej7ZSHfXGpLqmw= +github.com/jfrog/jfrog-cli-core/v2 v2.31.1-0.20240522101456-44e989e0a442 h1:Oq43c8phFFMwZImYP2Er1+1Ewd2Bu7q7MfkRp8Ti4EA= +github.com/jfrog/jfrog-cli-core/v2 v2.31.1-0.20240522101456-44e989e0a442/go.mod h1:hB5R+BgmCbOoz5HZyyqEdv408rL26ej7ZSHfXGpLqmw= github.com/jfrog/jfrog-client-go v1.28.1-0.20240509124610-4b9c587c0377 h1:fDneRi89u4hmD0qIf/X+HLT3IavqFeQevRtrZhyJZ5k= github.com/jfrog/jfrog-client-go v1.28.1-0.20240509124610-4b9c587c0377/go.mod h1:m3hIn12eFWk5nJH1swPRtFrjXbiiCscOpX+v/vCdmNI= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= diff --git a/tests/testdata/other/jobSummary/multi_command_job.md b/tests/testdata/other/jobSummary/multi_command_job.md new file mode 100644 index 00000000..e26061cf --- /dev/null +++ b/tests/testdata/other/jobSummary/multi_command_job.md @@ -0,0 +1,16 @@ +#### Builds +| Status | Id | Details | +|--------|----|---------| +| ✅ | build-name (build-number) | | +| ❌ | build-name (build-number) |
Vulnerabilities found 3
└── 3 SCA 🔴 2 High
🟡 1 Low
| +#### Artifacts +| Status | Id | Details | +|--------|----|---------| +| ❌ | /binary-name |
Vulnerabilities found 3
└── 3 Secrets 🔴 2 High
🟡 1 Low
| +| ✅ | other-root/dir/binary-name2 | | +#### Modules +| Status | Id | Details | +|--------|----|---------| +| ❌ | /application1 |
Vulnerabilities found 14
├── 1 SAST 🟡 1 Low
├── 5 IAC 🟠 5 Medium
└── 8 SCA ❗️ 3 Critical (2 Not Applicable)
🔴 4 High (1 Applicable, 1 Not Applicable)
🟡 1 Low
| +| ❌ | /application2 |
Vulnerabilities found 1
└── 1 SCA 🔴 1 High (1 Not Applicable)
| +| ✅ | /dir/application3 | | \ No newline at end of file diff --git a/tests/testdata/other/jobSummary/single_issue.md b/tests/testdata/other/jobSummary/single_issue.md new file mode 100644 index 00000000..a9df2a5b --- /dev/null +++ b/tests/testdata/other/jobSummary/single_issue.md @@ -0,0 +1 @@ +
❌ Vulnerabilities found 3
└── 3 Secrets 🔴 2 High
🟡 1 Low
\ No newline at end of file diff --git a/tests/testdata/other/jobSummary/single_no_issue.md b/tests/testdata/other/jobSummary/single_no_issue.md new file mode 100644 index 00000000..37677dd0 --- /dev/null +++ b/tests/testdata/other/jobSummary/single_no_issue.md @@ -0,0 +1,3 @@ +``` +✅ No vulnerabilities were found +``` \ No newline at end of file diff --git a/utils/analyticsmetrics_test.go b/utils/analyticsmetrics_test.go index 12f0bfb0..6b9fcf36 100644 --- a/utils/analyticsmetrics_test.go +++ b/utils/analyticsmetrics_test.go @@ -2,15 +2,16 @@ package utils import ( "errors" + "os" + "testing" + "time" + "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" "github.com/jfrog/jfrog-client-go/utils/tests" "github.com/jfrog/jfrog-client-go/xray/services" xscservices "github.com/jfrog/jfrog-client-go/xsc/services" "github.com/owenrumney/go-sarif/v2/sarif" "github.com/stretchr/testify/assert" - "os" - "testing" - "time" ) const ( @@ -73,15 +74,24 @@ func TestAddGeneralEvent(t *testing.T) { func TestAnalyticsMetricsService_createAuditResultsFromXscAnalyticsBasicGeneralEvent(t *testing.T) { usageCallback := tests.SetEnvWithCallbackAndAssert(t, coreutils.ReportUsage, "true") defer usageCallback() - vulnerabilities := []services.Vulnerability{{IssueId: "CVE-123", Components: map[string]services.Component{"issueId_2_direct_dependency": {}}}} + vulnerabilities := []services.Vulnerability{{IssueId: "XRAY-ID", Cves: []services.Cve{{Id: "CVE-123"}}, Components: map[string]services.Component{"issueId_2_direct_dependency": {}}}} scaResults := []ScaScanResult{{XrayResults: []services.ScanResponse{{Vulnerabilities: vulnerabilities}}}} auditResults := Results{ ScaResults: scaResults, ExtendedScanResults: &ExtendedScanResults{ - ApplicabilityScanResults: []*sarif.Run{{}, {}}, - SecretsScanResults: []*sarif.Run{{}, {}}, - IacScanResults: []*sarif.Run{{}, {}}, - SastScanResults: []*sarif.Run{{}, {}}, + ApplicabilityScanResults: []*sarif.Run{CreateRunWithDummyResults(CreateDummyPassingResult("applic_CVE-123"))}, + SecretsScanResults: []*sarif.Run{ + CreateRunWithDummyResults(CreateResultWithLocations("", "", "note", CreateLocation("", 0, 0, 0, 0, ""))), + CreateRunWithDummyResults(CreateResultWithLocations("", "", "note", CreateLocation("", 1, 1, 1, 1, ""))), + }, + IacScanResults: []*sarif.Run{ + CreateRunWithDummyResults(CreateResultWithLocations("", "", "note", CreateLocation("", 0, 0, 0, 0, ""))), + CreateRunWithDummyResults(CreateResultWithLocations("", "", "note", CreateLocation("", 1, 1, 1, 1, ""))), + }, + SastScanResults: []*sarif.Run{ + CreateRunWithDummyResults(CreateResultWithLocations("", "", "note", CreateLocation("", 0, 0, 0, 0, ""))), + CreateRunWithDummyResults(CreateResultWithLocations("", "", "note", CreateLocation("", 1, 1, 1, 1, ""))), + }, }, } testStruct := []struct { diff --git a/utils/analyzermanager.go b/utils/analyzermanager.go index 464520b9..f825337c 100644 --- a/utils/analyzermanager.go +++ b/utils/analyzermanager.go @@ -55,6 +55,21 @@ func (as ApplicabilityStatus) String() string { return string(as) } +func convertToApplicabilityStatus(status string) ApplicabilityStatus { + switch status { + case Applicable.String(): + return Applicable + case NotApplicable.String(): + return NotApplicable + case ApplicabilityUndetermined.String(): + return ApplicabilityUndetermined + case NotCovered.String(): + return NotCovered + default: + return NotScanned + } +} + type JasScanType string const ( diff --git a/utils/results.go b/utils/results.go index 3a1d8668..43870ac4 100644 --- a/utils/results.go +++ b/utils/results.go @@ -3,6 +3,7 @@ package utils import ( "github.com/jfrog/gofrog/datastructures" "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" + "github.com/jfrog/jfrog-cli-security/formats" "github.com/jfrog/jfrog-client-go/xray/services" "github.com/owenrumney/go-sarif/v2/sarif" ) @@ -59,6 +60,15 @@ func (r *Results) IsScaIssuesFound() bool { return false } +func (r *Results) getScaScanResultByTarget(target string) *ScaScanResult { + for _, scan := range r.ScaResults { + if scan.Target == target { + return &scan + } + } + return nil +} + func (r *Results) IsIssuesFound() bool { if r.IsScaIssuesFound() { return true @@ -71,44 +81,48 @@ func (r *Results) IsIssuesFound() bool { // Counts the total number of unique findings in the provided results. // A unique SCA finding is identified by a unique pair of vulnerability's/violation's issueId and component id or by a result returned from one of JAS scans. -func (r *Results) CountScanResultsFindings() int { - var totalFindings int - totalFindings += getScaResultsUniqueFindingsAmount(&r.ScaResults) - - if r.ExtendedScanResults != nil { - totalFindings += len(r.ExtendedScanResults.SastScanResults) - totalFindings += len(r.ExtendedScanResults.IacScanResults) - totalFindings += len(r.ExtendedScanResults.SecretsScanResults) - } - - return totalFindings +func (r *Results) CountScanResultsFindings() (total int) { + return formats.SummaryResults{Scans: r.getScanSummaryByTargets()}.GetTotalIssueCount() } -func getScaResultsUniqueFindingsAmount(scaScanResults *[]ScaScanResult) int { - uniqueXrayFindings := datastructures.MakeSet[string]() - - for _, scaResult := range *scaScanResults { - for _, xrayResult := range scaResult.XrayResults { - // XrayResults may contain Vulnerabilities OR Violations, but not both. Therefore, only one of them will be counted - for _, vulnerability := range xrayResult.Vulnerabilities { - for compId := range vulnerability.Components { - uniqueXrayFindings.Add(vulnerability.IssueId + compId) - } - } +func (r *Results) GetSummary() (summary formats.SummaryResults) { + if len(r.ScaResults) <= 1 { + summary.Scans = r.getScanSummaryByTargets() + return + } + for _, scaScan := range r.ScaResults { + summary.Scans = append(summary.Scans, r.getScanSummaryByTargets(scaScan.Target)...) + } + return +} - for _, violation := range xrayResult.Violations { - for compId := range violation.Components { - uniqueXrayFindings.Add(violation.IssueId + compId) - } - } +// Returns a summary for the provided targets. If no targets are provided, a summary for all targets is returned. +func (r *Results) getScanSummaryByTargets(targets ...string) (summaries []formats.ScanSummaryResult) { + if len(targets) == 0 { + // No filter, one scan summary for all targets + summaries = append(summaries, getScanSummary(r.ExtendedScanResults, r.ScaResults...)) + return + } + for _, target := range targets { + // Get target sca results + targetScaResults := []ScaScanResult{} + if targetScaResult := r.getScaScanResultByTarget(target); targetScaResult != nil { + targetScaResults = append(targetScaResults, *targetScaResult) + } + // Get target extended results + targetExtendedResults := r.ExtendedScanResults + if targetExtendedResults != nil { + targetExtendedResults = targetExtendedResults.GetResultsForTarget(target) } + summaries = append(summaries, getScanSummary(targetExtendedResults, targetScaResults...)) } - return uniqueXrayFindings.Size() + return } type ScaScanResult struct { - Technology coreutils.Technology `json:"Technology"` - WorkingDirectory string `json:"WorkingDirectory"` + // Could be working directory (audit), file path (binary scan) or build name+number (build scan) + Target string `json:"Target"` + Technology coreutils.Technology `json:"Technology,omitempty"` XrayResults []services.ScanResponse `json:"XrayResults,omitempty"` Descriptors []string `json:"Descriptors,omitempty"` IsMultipleRootProject *bool `json:"IsMultipleRootProject,omitempty"` @@ -137,3 +151,12 @@ func (e *ExtendedScanResults) IsIssuesFound() bool { GetResultsLocationCount(e.IacScanResults...) > 0 || GetResultsLocationCount(e.SastScanResults...) > 0 } + +func (e *ExtendedScanResults) GetResultsForTarget(target string) (result *ExtendedScanResults) { + return &ExtendedScanResults{ + ApplicabilityScanResults: GetRunsByWorkingDirectory(target, e.ApplicabilityScanResults...), + SecretsScanResults: GetRunsByWorkingDirectory(target, e.SecretsScanResults...), + IacScanResults: GetRunsByWorkingDirectory(target, e.IacScanResults...), + SastScanResults: GetRunsByWorkingDirectory(target, e.SastScanResults...), + } +} diff --git a/utils/results_test.go b/utils/results_test.go new file mode 100644 index 00000000..40fde1e3 --- /dev/null +++ b/utils/results_test.go @@ -0,0 +1,160 @@ +package utils + +import ( + "testing" + + "github.com/jfrog/jfrog-cli-security/formats" + "github.com/jfrog/jfrog-client-go/xray/services" + "github.com/owenrumney/go-sarif/v2/sarif" + "github.com/stretchr/testify/assert" +) + +func TestGetScaScanResultByTarget(t *testing.T) { + target1 := &ScaScanResult{Target: "target1"} + target2 := &ScaScanResult{Target: "target2"} + testCases := []struct { + name string + results Results + target string + expected *ScaScanResult + }{ + { + name: "Sca scan result by target", + results: Results{ + ScaResults: []ScaScanResult{ + *target1, + *target2, + }, + }, + target: "target1", + expected: target1, + }, + { + name: "Sca scan result by target not found", + results: Results{ + ScaResults: []ScaScanResult{ + *target1, + *target2, + }, + }, + target: "target3", + expected: nil, + }, + } + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + result := testCase.results.getScaScanResultByTarget(testCase.target) + assert.Equal(t, testCase.expected, result) + }) + } +} + +func TestGetSummary(t *testing.T) { + dummyScaVulnerabilities := []services.Vulnerability{ + {IssueId: "XRAY-1", Severity: "Critical", Cves: []services.Cve{{Id: "CVE-1"}}, Components: map[string]services.Component{"issueId_direct_dependency": {}}}, + {IssueId: "XRAY-2", Severity: "High", Cves: []services.Cve{{Id: "CVE-2"}}, Components: map[string]services.Component{"issueId_direct_dependency": {}}}, + } + dummyExtendedScanResults := &ExtendedScanResults{ + ApplicabilityScanResults: []*sarif.Run{ + CreateRunWithDummyResults(CreateDummyPassingResult("applic_CVE-2")).WithInvocations([]*sarif.Invocation{ + sarif.NewInvocation().WithWorkingDirectory(sarif.NewSimpleArtifactLocation("target1")), + }), + }, + SecretsScanResults: []*sarif.Run{ + CreateRunWithDummyResults(CreateResultWithLocations("", "", "note", CreateLocation("target1/file", 0, 0, 0, 0, "snippet"))).WithInvocations([]*sarif.Invocation{ + sarif.NewInvocation().WithWorkingDirectory(sarif.NewSimpleArtifactLocation("target1")), + }), + CreateRunWithDummyResults(CreateResultWithLocations("", "", "note", CreateLocation("target2/file", 0, 0, 0, 0, "snippet"))).WithInvocations([]*sarif.Invocation{ + sarif.NewInvocation().WithWorkingDirectory(sarif.NewSimpleArtifactLocation("target2")), + }), + }, + SastScanResults: []*sarif.Run{ + CreateRunWithDummyResults(CreateResultWithLocations("", "", "note", CreateLocation("target1/file2", 0, 0, 0, 0, "snippet"))).WithInvocations([]*sarif.Invocation{ + sarif.NewInvocation().WithWorkingDirectory(sarif.NewSimpleArtifactLocation("target1")), + }), + }, + } + + testCases := []struct { + name string + results Results + expected formats.SummaryResults + findingCount int + }{ + { + name: "Empty results", + results: Results{ScaResults: []ScaScanResult{}}, + expected: formats.SummaryResults{Scans: []formats.ScanSummaryResult{{}}}, + findingCount: 0, + }, + { + name: "One module result", + results: Results{ + ScaResults: []ScaScanResult{{ + Target: "target1", + XrayResults: []services.ScanResponse{{Vulnerabilities: dummyScaVulnerabilities}}, + }}, + ExtendedScanResults: dummyExtendedScanResults, + }, + expected: formats.SummaryResults{ + Scans: []formats.ScanSummaryResult{ + { + Target: "target1", + ScaScanResults: &formats.ScaSummaryCount{ + "Critical": formats.SummaryCount{"Undetermined": 1}, + "High": formats.SummaryCount{"Not Applicable": 1}, + }, + SecretsScanResults: &formats.SummaryCount{"Low": 2}, + SastScanResults: &formats.SummaryCount{"Low": 1}, + }, + }, + }, + findingCount: 5, + }, + { + name: "Multiple module results", + results: Results{ + ScaResults: []ScaScanResult{ + { + Target: "target1", + XrayResults: []services.ScanResponse{{Vulnerabilities: dummyScaVulnerabilities}}, + }, + { + Target: "target2", + XrayResults: []services.ScanResponse{{Vulnerabilities: dummyScaVulnerabilities}}, + }, + }, + ExtendedScanResults: dummyExtendedScanResults, + }, + expected: formats.SummaryResults{ + Scans: []formats.ScanSummaryResult{ + { + Target: "target1", + ScaScanResults: &formats.ScaSummaryCount{ + "Critical": formats.SummaryCount{"Undetermined": 1}, + "High": formats.SummaryCount{"Not Applicable": 1}, + }, + SecretsScanResults: &formats.SummaryCount{"Low": 1}, + SastScanResults: &formats.SummaryCount{"Low": 1}, + }, + { + Target: "target2", + ScaScanResults: &formats.ScaSummaryCount{ + "Critical": formats.SummaryCount{"": 1}, + "High": formats.SummaryCount{"": 1}, + }, + SecretsScanResults: &formats.SummaryCount{"Low": 1}, + }, + }, + }, + findingCount: 5, + }, + } + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + result := testCase.results.GetSummary() + assert.Equal(t, testCase.expected, result) + assert.Equal(t, testCase.findingCount, testCase.results.CountScanResultsFindings()) + }) + } +} diff --git a/utils/resultstable.go b/utils/resultstable.go index b8da7325..1badb738 100644 --- a/utils/resultstable.go +++ b/utils/resultstable.go @@ -93,7 +93,7 @@ func prepareViolations(violations []services.Violation, results *Results, multip cves := convertCves(violation.Cves) if results.ExtendedScanResults.EntitledForJas { for i := range cves { - cves[i].Applicability = getCveApplicabilityField(cves[i], results.ExtendedScanResults.ApplicabilityScanResults, violation.Components) + cves[i].Applicability = getCveApplicabilityField(cves[i].Id, results.ExtendedScanResults.ApplicabilityScanResults, violation.Components) } } applicabilityStatus := getApplicableCveStatus(results.ExtendedScanResults.EntitledForJas, results.ExtendedScanResults.ApplicabilityScanResults, cves) @@ -218,7 +218,7 @@ func prepareVulnerabilities(vulnerabilities []services.Vulnerability, results *R cves := convertCves(vulnerability.Cves) if results.ExtendedScanResults.EntitledForJas { for i := range cves { - cves[i].Applicability = getCveApplicabilityField(cves[i], results.ExtendedScanResults.ApplicabilityScanResults, vulnerability.Components) + cves[i].Applicability = getCveApplicabilityField(cves[i].Id, results.ExtendedScanResults.ApplicabilityScanResults, vulnerability.Components) } } applicabilityStatus := getApplicableCveStatus(results.ExtendedScanResults.EntitledForJas, results.ExtendedScanResults.ApplicabilityScanResults, cves) @@ -951,7 +951,7 @@ func getApplicableCveStatus(entitledForJas bool, applicabilityScanResults []*sar return getFinalApplicabilityStatus(applicableStatuses) } -func getCveApplicabilityField(cve formats.CveRow, applicabilityScanResults []*sarif.Run, components map[string]services.Component) *formats.Applicability { +func getCveApplicabilityField(cveId string, applicabilityScanResults []*sarif.Run, components map[string]services.Component) *formats.Applicability { if len(applicabilityScanResults) == 0 { return nil } @@ -960,14 +960,14 @@ func getCveApplicabilityField(cve formats.CveRow, applicabilityScanResults []*sa resultFound := false var applicabilityStatuses []ApplicabilityStatus for _, applicabilityRun := range applicabilityScanResults { - if rule, _ := applicabilityRun.GetRuleById(CveToApplicabilityRuleId(cve.Id)); rule != nil { + if rule, _ := applicabilityRun.GetRuleById(CveToApplicabilityRuleId(cveId)); rule != nil { applicability.ScannerDescription = GetRuleFullDescription(rule) status := getApplicabilityStatusFromRule(rule) if status != "" { applicabilityStatuses = append(applicabilityStatuses, status) } } - result, _ := applicabilityRun.GetResultByRuleId(CveToApplicabilityRuleId(cve.Id)) + result, _ := applicabilityRun.GetResultByRuleId(CveToApplicabilityRuleId(cveId)) if result == nil { continue } diff --git a/utils/resultstable_test.go b/utils/resultstable_test.go index 74202b57..af83a690 100644 --- a/utils/resultstable_test.go +++ b/utils/resultstable_test.go @@ -573,7 +573,7 @@ func TestGetApplicableCveValue(t *testing.T) { for _, testCase := range testCases { cves := convertCves(testCase.cves) for i := range cves { - cves[i].Applicability = getCveApplicabilityField(cves[i], testCase.scanResults.ApplicabilityScanResults, nil) + cves[i].Applicability = getCveApplicabilityField(cves[i].Id, testCase.scanResults.ApplicabilityScanResults, nil) } applicableValue := getApplicableCveStatus(testCase.scanResults.EntitledForJas, testCase.scanResults.ApplicabilityScanResults, cves) assert.Equal(t, testCase.expectedResult, applicableValue) diff --git a/utils/sarifutils.go b/utils/sarifutils.go index 0da57714..2996a384 100644 --- a/utils/sarifutils.go +++ b/utils/sarifutils.go @@ -97,12 +97,18 @@ func isSameLocation(location *sarif.Location, other *sarif.Location) bool { if location == other { return true } - return GetLocationFileName(location) == GetLocationFileName(other) && - GetLocationSnippet(location) == GetLocationSnippet(other) && - GetLocationStartLine(location) == GetLocationStartLine(other) && - GetLocationStartColumn(location) == GetLocationStartColumn(other) && - GetLocationEndLine(location) == GetLocationEndLine(other) && - GetLocationEndColumn(location) == GetLocationEndColumn(other) + return GetLocationId(location) == GetLocationId(other) +} + +func GetLocationId(location *sarif.Location) string { + return fmt.Sprintf("%s:%s:%d:%d:%d:%d", + GetLocationFileName(location), + GetLocationSnippet(location), + GetLocationStartLine(location), + GetLocationStartColumn(location), + GetLocationEndLine(location), + GetLocationEndColumn(location), + ) } func GetResultsLocationCount(runs ...*sarif.Run) (count int) { @@ -114,6 +120,20 @@ func GetResultsLocationCount(runs ...*sarif.Run) (count int) { return } +func GetRunsByWorkingDirectory(workingDirectory string, runs ...*sarif.Run) (filteredRuns []*sarif.Run) { + for _, run := range runs { + for _, invocation := range run.Invocations { + runWorkingDir := GetInvocationWorkingDirectory(invocation) + if runWorkingDir == workingDirectory { + filteredRuns = append(filteredRuns, run) + break + } + } + } + return + +} + func GetResultMsgText(result *sarif.Result) string { if result.Message.Text != nil { return *result.Message.Text diff --git a/utils/securityJobSummary.go b/utils/securityJobSummary.go new file mode 100644 index 00000000..a8c3d245 --- /dev/null +++ b/utils/securityJobSummary.go @@ -0,0 +1,420 @@ +package utils + +import ( + "fmt" + "path/filepath" + "strings" + + "github.com/jfrog/gofrog/datastructures" + "github.com/jfrog/jfrog-cli-core/v2/commandsummary" + "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" + "github.com/jfrog/jfrog-cli-security/formats" + "github.com/jfrog/jfrog-client-go/xray/services" + "github.com/owenrumney/go-sarif/v2/sarif" + "golang.org/x/exp/maps" +) + +const ( + Build SecuritySummarySection = "Builds" + Binary SecuritySummarySection = "Artifacts" + Modules SecuritySummarySection = "Modules" +) + +type SecuritySummarySection string + +type ScanCommandSummaryResult struct { + Section SecuritySummarySection `json:"section"` + Results formats.SummaryResults `json:"results"` +} + +type SecurityCommandsSummary struct { + BuildScanCommands []formats.SummaryResults `json:"buildScanCommands"` + ScanCommands []formats.SummaryResults `json:"scanCommands"` + AuditCommands []formats.SummaryResults `json:"auditCommands"` +} + +// Manage the job summary for security commands +func SecurityCommandsJobSummary() (js *commandsummary.CommandSummary, err error) { + return commandsummary.New(&SecurityCommandsSummary{}, "security") +} + +// Record the security command output +func RecordSecurityCommandOutput(content ScanCommandSummaryResult) (err error) { + if !commandsummary.ShouldRecordSummary() { + return + } + manager, err := SecurityCommandsJobSummary() + if err != nil || manager == nil { + return + } + return manager.Record(content) +} + +func (scs *SecurityCommandsSummary) GenerateMarkdownFromFiles(dataFilePaths []string) (markdown string, err error) { + if err = loadContentFromFiles(dataFilePaths, scs); err != nil { + return "", fmt.Errorf("failed while creating security markdown: %w", err) + } + return ConvertSummaryToString(*scs) +} + +func loadContentFromFiles(dataFilePaths []string, scs *SecurityCommandsSummary) (err error) { + for _, dataFilePath := range dataFilePaths { + // Load file content + var cmdResults ScanCommandSummaryResult + if err = commandsummary.UnmarshalFromFilePath(dataFilePath, &cmdResults); err != nil { + return fmt.Errorf("failed while Unmarshal '%s': %w", dataFilePath, err) + } + // Append the new data + switch cmdResults.Section { + case Build: + scs.BuildScanCommands = append(scs.BuildScanCommands, cmdResults.Results) + case Binary: + scs.ScanCommands = append(scs.ScanCommands, cmdResults.Results) + case Modules: + scs.AuditCommands = append(scs.AuditCommands, cmdResults.Results) + } + } + return +} + +func (scs *SecurityCommandsSummary) GetOrderedSectionsWithContent() (sections []SecuritySummarySection) { + if len(scs.BuildScanCommands) > 0 { + sections = append(sections, Build) + } + if len(scs.ScanCommands) > 0 { + sections = append(sections, Binary) + } + if len(scs.AuditCommands) > 0 { + sections = append(sections, Modules) + } + return + +} + +func (scs *SecurityCommandsSummary) GetSectionSummaries(section SecuritySummarySection) (summaries []formats.SummaryResults) { + switch section { + case Build: + summaries = scs.BuildScanCommands + case Binary: + summaries = scs.ScanCommands + case Modules: + summaries = scs.AuditCommands + } + return +} + +func ConvertSummaryToString(results SecurityCommandsSummary) (summary string, err error) { + sectionsWithContent := results.GetOrderedSectionsWithContent() + addSectionTitle := len(sectionsWithContent) > 1 + var sectionSummary string + for i, section := range sectionsWithContent { + if sectionSummary, err = GetSummaryString(results.GetSectionSummaries(section)...); err != nil { + return + } + if addSectionTitle { + if i > 0 { + summary += "\n" + } + summary += fmt.Sprintf("#### %s\n", section) + } + summary += sectionSummary + } + return +} + +func GetSummaryString(summaries ...formats.SummaryResults) (str string, err error) { + parsed := 0 + singleScan := isSingleCommandAndScan(summaries...) + wd, err := coreutils.GetWorkingDirectory() + if err != nil { + return + } + if !singleScan { + str += "| Status | Id | Details |\n|--------|----|---------|\n" + } + for i := range summaries { + if !singleScan { + updateSummaryNamesToRelativePath(&summaries[i], wd) + } + for _, scan := range summaries[i].Scans { + if parsed > 0 { + str += "\n" + } + str += GetScanSummaryString(scan, singleScan) + parsed++ + } + } + return +} + +func isSingleCommandAndScan(summaries ...formats.SummaryResults) bool { + if len(summaries) != 1 { + return false + } + if len(summaries[0].Scans) != 1 { + return false + } + // One command and one scan + return true +} + +func GetScanSummaryString(summary formats.ScanSummaryResult, singleData bool) (content string) { + // single data -> no table + hasIssues := summary.HasIssues() + if !hasIssues { + if singleData { + return "```\n✅ No vulnerabilities were found\n```" + } + return fmt.Sprintf("| ✅ | %s | |", summary.Target) + } + issueDetails := getDetailsString(summary) + if singleData { + return fmt.Sprintf("
❌ %s
", issueDetails) + } + return fmt.Sprintf("| ❌ | %s |
%s
|", summary.Target, issueDetails) +} + +func getDetailsString(summary formats.ScanSummaryResult) (content string) { + content = fmt.Sprintf("Vulnerabilities found %d", summary.GetTotalIssueCount()) + // Display sub scans with issues + subScansWithIssues := summary.GetSubScansWithIssues() + for i, subScanType := range subScansWithIssues { + content += fmt.Sprintf("
%s", getListItemPrefix(i, len(subScansWithIssues))) + subScanPrefix := fmt.Sprintf("%d ", summary.GetSubScanTotalIssueCount(subScanType)) + switch subScanType { + case formats.ScaScan: + subScanPrefix += "SCA " + case formats.IacScan: + subScanPrefix += "IAC " + case formats.SecretsScan: + subScanPrefix += "Secrets " + case formats.SastScan: + subScanPrefix += "SAST " + } + content += subScanPrefix + getSubScanSummaryCountsString(summary, subScanType, getPrefixPadding(subScanPrefix)) + } + return +} + +func getPrefixPadding(prefix string) int { + // 4 spaces for the list item prefix (len not equal to actual length) + return 4 + len(prefix) +} + +func getListItemPrefix(index, total int) (content string) { + if index == total-1 { + content += "└── " + return + } + content += "├── " + return +} + +func getSubScanSummaryCountsString(summary formats.ScanSummaryResult, subScanType formats.SummarySubScanType, padding int) (content string) { + switch subScanType { + case formats.ScaScan: + content += GetScaSummaryCountString(*summary.ScaScanResults, padding) + case formats.IacScan: + content += GetSeveritySummaryCountString(*summary.IacScanResults, padding) + case formats.SecretsScan: + content += GetSeveritySummaryCountString(*summary.SecretsScanResults, padding) + case formats.SastScan: + content += GetSeveritySummaryCountString(*summary.SastScanResults, padding) + } + return +} + +func hasApplicableDataToDisplayInSummary(summary formats.ScaSummaryCount) bool { + for _, statuses := range summary { + sorted := getSummarySortedKeysToDisplay(maps.Keys(statuses)...) + for _, status := range sorted { + if _, ok := statuses[status]; ok && statuses[status] > 0 { + return true + } + } + } + return false +} + +func GetScaSummaryCountString(summary formats.ScaSummaryCount, padding int) (content string) { + severityCount := len(summary) + if severityCount == 0 { + return + } + if !hasApplicableDataToDisplayInSummary(summary) { + return GetSeveritySummaryCountString(summary.GetSeverityCountsWithoutStatus(), padding) + } + // Display contextual-analysis details + keys := getSummarySortedKeysToDisplay(maps.Keys(summary)...) + for i, severity := range keys { + if i > 0 { + content += "
" + strings.Repeat(" ", padding) + } + statusCounts := summary[severity] + content += fmt.Sprintf("%s%s", + fmt.Sprintf(summaryContentToFormatString[severity], statusCounts.GetTotal()), + GetSummaryContentString(statusCounts, ", ", true), + ) + } + return +} + +var summaryContentToFormatString = map[string]string{ + "Critical": `❗️ %d Critical`, + "High": `🔴 %d High`, + "Medium": `🟠 %d Medium`, + "Low": `🟡 %d Low`, + "Unknown": `⚪️ %d Unknown`, + string(Applicable): "%d " + string(Applicable), + string(NotApplicable): "%d " + string(NotApplicable), +} + +func getSummarySortedKeysToDisplay(keys ...string) (sorted []string) { + if len(keys) == 0 { + return + } + keysSet := datastructures.MakeSetFromElements(keys...) + allowedSorted := []string{ + "Critical", "High", "Medium", "Low", "Unknown", + string(Applicable), string(NotApplicable), + } + for _, key := range allowedSorted { + if keysSet.Exists(key) { + sorted = append(sorted, key) + } + } + return +} + +func GetSeveritySummaryCountString(summary formats.SummaryCount, padding int) (content string) { + return GetSummaryContentString(summary, "
"+strings.Repeat(" ", padding), false) +} + +func GetSummaryContentString(summary formats.SummaryCount, delimiter string, wrap bool) (content string) { + // sort and filter + keys := getSummarySortedKeysToDisplay(maps.Keys(summary)...) + if len(keys) == 0 { + return + } + for i, key := range keys { + if i > 0 { + content += delimiter + } + content += fmt.Sprintf(summaryContentToFormatString[key], summary[key]) + } + if wrap { + content = fmt.Sprintf(" (%s)", content) + } + return +} + +func updateSummaryNamesToRelativePath(summary *formats.SummaryResults, wd string) { + for i, scan := range summary.Scans { + if scan.Target == "" { + continue + } + if !strings.HasPrefix(scan.Target, wd) { + continue + } + if scan.Target == wd { + summary.Scans[i].Target = filepath.Base(wd) + } + summary.Scans[i].Target = strings.TrimPrefix(scan.Target, wd) + } +} + +func getScanSummary(extendedScanResults *ExtendedScanResults, scaResults ...ScaScanResult) (summary formats.ScanSummaryResult) { + if len(scaResults) == 1 { + summary.Target = scaResults[0].Target + } + if extendedScanResults == nil { + summary.ScaScanResults = getScaSummaryResults(&scaResults) + return + } + summary.ScaScanResults = getScaSummaryResults(&scaResults, extendedScanResults.ApplicabilityScanResults...) + summary.IacScanResults = getJASSummaryCount(extendedScanResults.IacScanResults...) + summary.SecretsScanResults = getJASSummaryCount(extendedScanResults.SecretsScanResults...) + summary.SastScanResults = getJASSummaryCount(extendedScanResults.SastScanResults...) + return +} + +type SeverityWithApplicable struct { + SeverityInfo *TableSeverity + ApplicabilityStatus ApplicabilityStatus +} + +func getCveId(cve services.Cve, defaultIssueId string) string { + if cve.Id == "" { + return defaultIssueId + } + return cve.Id +} + +func getUniqueVulnerabilitiesInfo(cves []services.Cve, issueId, severity string, components map[string]services.Component, applicableRuns ...*sarif.Run) (uniqueFindings map[string]SeverityWithApplicable) { + uniqueFindings = map[string]SeverityWithApplicable{} + for _, cve := range cves { + cveId := getCveId(cve, issueId) + for compId := range components { + applicableStatus := NotScanned + if applicableInfo := getCveApplicabilityField(cveId, applicableRuns, components); applicableInfo != nil { + applicableStatus = convertToApplicabilityStatus(applicableInfo.Status) + } + uniqueFindings[cveId+compId] = SeverityWithApplicable{SeverityInfo: GetSeverity(severity, applicableStatus), ApplicabilityStatus: applicableStatus} + } + } + return +} + +func getScaSummaryResults(scaScanResults *[]ScaScanResult, applicableRuns ...*sarif.Run) *formats.ScaSummaryCount { + uniqueFindings := map[string]SeverityWithApplicable{} + if len(*scaScanResults) == 0 { + return nil + } + // Aggregate unique findings + for _, scaResult := range *scaScanResults { + for _, xrayResult := range scaResult.XrayResults { + for _, vulnerability := range xrayResult.Vulnerabilities { + vulUniqueFindings := getUniqueVulnerabilitiesInfo(vulnerability.Cves, vulnerability.IssueId, vulnerability.Severity, vulnerability.Components, applicableRuns...) + for key, value := range vulUniqueFindings { + uniqueFindings[key] = value + } + } + for _, violation := range xrayResult.Violations { + vioUniqueFindings := getUniqueVulnerabilitiesInfo(violation.Cves, violation.IssueId, violation.Severity, violation.Components, applicableRuns...) + for key, value := range vioUniqueFindings { + uniqueFindings[key] = value + } + } + } + } + // Create summary + summary := formats.ScaSummaryCount{} + for _, severityWithApplicable := range uniqueFindings { + severity := severityWithApplicable.SeverityInfo.Severity + status := severityWithApplicable.ApplicabilityStatus.String() + if _, ok := summary[severity]; !ok { + summary[severity] = formats.SummaryCount{} + } + summary[severity][status]++ + } + return &summary +} + +func getJASSummaryCount(runs ...*sarif.Run) *formats.SummaryCount { + if len(runs) == 0 { + return nil + } + count := formats.SummaryCount{} + issueToSeverity := map[string]string{} + for _, run := range runs { + for _, result := range run.Results { + for _, location := range result.Locations { + issueToSeverity[GetLocationId(location)] = GetResultSeverity(result) + } + } + } + for _, severity := range issueToSeverity { + count[severity]++ + } + return &count +} diff --git a/utils/securityJobSummary_test.go b/utils/securityJobSummary_test.go new file mode 100644 index 00000000..7edb73c2 --- /dev/null +++ b/utils/securityJobSummary_test.go @@ -0,0 +1,141 @@ +package utils + +import ( + "os" + "path/filepath" + "strings" + "testing" + + "github.com/jfrog/jfrog-cli-security/formats" + "github.com/stretchr/testify/assert" +) + +var ( + summaryExpectedContentDir = filepath.Join("..", "tests", "testdata", "other", "jobSummary") +) + +func TestConvertSummaryToString(t *testing.T) { + wd, err := os.Getwd() + assert.NoError(t, err) + + testCases := []struct { + name string + summary SecurityCommandsSummary + expectedContentPath string + }{ + { + name: "One Section - No Issues", + summary: getDummySecurityCommandsSummary( + ScanCommandSummaryResult{ + Section: Binary, + Results: formats.SummaryResults{Scans: []formats.ScanSummaryResult{{Target: filepath.Join(wd, "binary-name")}}}, + }, + ), + expectedContentPath: filepath.Join(summaryExpectedContentDir, "single_no_issue.md"), + }, + { + name: "One Section - With Issues", + summary: getDummySecurityCommandsSummary( + ScanCommandSummaryResult{ + Section: Build, + Results: formats.SummaryResults{Scans: []formats.ScanSummaryResult{{ + Target: "build-name (build-number)", + SecretsScanResults: &formats.SummaryCount{"Low": 1, "High": 2}, + }}}, + }, + ), + expectedContentPath: filepath.Join(summaryExpectedContentDir, "single_issue.md"), + }, + { + name: "Multiple Sections", + summary: getDummySecurityCommandsSummary( + ScanCommandSummaryResult{ + Section: Build, + Results: formats.SummaryResults{Scans: []formats.ScanSummaryResult{{Target: "build-name (build-number)"}}}, + }, + ScanCommandSummaryResult{ + Section: Build, + Results: formats.SummaryResults{Scans: []formats.ScanSummaryResult{{ + Target: "build-name (build-number)", + ScaScanResults: &formats.ScaSummaryCount{"Low": formats.SummaryCount{"": 1}, "High": formats.SummaryCount{"": 2}}, + }}}, + }, + ScanCommandSummaryResult{ + Section: Binary, + Results: formats.SummaryResults{Scans: []formats.ScanSummaryResult{ + { + Target: filepath.Join(wd, "binary-name"), + ScaScanResults: &formats.ScaSummaryCount{}, + SecretsScanResults: &formats.SummaryCount{"Low": 1, "High": 2}, + }, + { + Target: filepath.Join("other-root", "dir", "binary-name2"), + ScaScanResults: &formats.ScaSummaryCount{}, + }, + }}, + }, + ScanCommandSummaryResult{ + Section: Modules, + Results: formats.SummaryResults{Scans: []formats.ScanSummaryResult{ + { + Target: filepath.Join(wd, "application1"), + SastScanResults: &formats.SummaryCount{"Low": 1}, + IacScanResults: &formats.SummaryCount{"Medium": 5}, + ScaScanResults: &formats.ScaSummaryCount{ + "Critical": formats.SummaryCount{"Undetermined": 1, "Not Applicable": 2}, + "High": formats.SummaryCount{"Applicable": 1, "Not Applicable": 1, "Not Covered": 2}, + "Low": formats.SummaryCount{"Undetermined": 1}, + }, + }, + { + Target: filepath.Join(wd, "application2"), + ScaScanResults: &formats.ScaSummaryCount{ + "High": formats.SummaryCount{"Not Applicable": 1}, + }, + }, + { + Target: filepath.Join(wd, "dir", "application3"), + }, + }}, + }, + ), + expectedContentPath: filepath.Join(summaryExpectedContentDir, "multi_command_job.md"), + }, + } + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + // Read expected content from file + expectedContent := getOutputFromFile(t, testCase.expectedContentPath) + summary, err := ConvertSummaryToString(testCase.summary) + assert.NoError(t, err) + assert.Equal(t, expectedContent, summary) + }) + } +} + +func getOutputFromFile(t *testing.T, path string) string { + content, err := os.ReadFile(path) + assert.NoError(t, err) + return strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(string(content), "\r\n", "\n"), "/", string(filepath.Separator)), "<"+string(filepath.Separator), " Date: Wed, 22 May 2024 18:14:37 +0300 Subject: [PATCH 3/3] Update dependencies (#65) --- go.mod | 8 ++++---- go.sum | 12 ++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/go.mod b/go.mod index 72deb875..cb06e0c9 100644 --- a/go.mod +++ b/go.mod @@ -4,10 +4,10 @@ go 1.21 require ( github.com/gookit/color v1.5.4 - github.com/jfrog/build-info-go v1.9.26 + github.com/jfrog/build-info-go v1.9.27 github.com/jfrog/gofrog v1.7.1 github.com/jfrog/jfrog-apps-config v1.0.1 - github.com/jfrog/jfrog-cli-core/v2 v2.51.0 + github.com/jfrog/jfrog-cli-core/v2 v2.53.0 github.com/jfrog/jfrog-client-go v1.40.2 github.com/magiconair/properties v1.8.7 github.com/owenrumney/go-sarif/v2 v2.3.0 @@ -98,8 +98,8 @@ require ( gopkg.in/warnings.v0 v0.1.2 // indirect ) -replace github.com/jfrog/jfrog-cli-core/v2 => github.com/jfrog/jfrog-cli-core/v2 v2.31.1-0.20240522101456-44e989e0a442 +// replace github.com/jfrog/jfrog-cli-core/v2 => github.com/jfrog/jfrog-cli-core/v2 dev -replace github.com/jfrog/jfrog-client-go => github.com/jfrog/jfrog-client-go v1.28.1-0.20240509124610-4b9c587c0377 +// replace github.com/jfrog/jfrog-client-go => github.com/jfrog/jfrog-client-go dev // replace github.com/jfrog/build-info-go => github.com/jfrog/build-info-go dev diff --git a/go.sum b/go.sum index 40100f77..30ff2df8 100644 --- a/go.sum +++ b/go.sum @@ -96,16 +96,16 @@ github.com/jedib0t/go-pretty/v6 v6.5.9 h1:ACteMBRrrmm1gMsXe9PSTOClQ63IXDUt03H5U+ github.com/jedib0t/go-pretty/v6 v6.5.9/go.mod h1:zbn98qrYlh95FIhwwsbIip0LYpwSG8SUOScs+v9/t0E= github.com/jfrog/archiver/v3 v3.6.0 h1:OVZ50vudkIQmKMgA8mmFF9S0gA47lcag22N13iV3F1w= github.com/jfrog/archiver/v3 v3.6.0/go.mod h1:fCAof46C3rAXgZurS8kNRNdSVMKBbZs+bNNhPYxLldI= -github.com/jfrog/build-info-go v1.9.26 h1:1Ddc6+Ecvhc+UMnKhRVG1jGM6fYNwA49207azTBGBc8= -github.com/jfrog/build-info-go v1.9.26/go.mod h1:8T7/ajM9aGshvgpwCtXwIFpyF/R6CEn4W+/FLryNXWw= +github.com/jfrog/build-info-go v1.9.27 h1:7RWJcajqtNNbGHuYkgOLUIG7mmRKF0yxC7mvYAbdVlU= +github.com/jfrog/build-info-go v1.9.27/go.mod h1:8T7/ajM9aGshvgpwCtXwIFpyF/R6CEn4W+/FLryNXWw= github.com/jfrog/gofrog v1.7.1 h1:ME1Meg4hukAT/7X6HUQCVSe4DNjMZACCP8aCY37EW/w= github.com/jfrog/gofrog v1.7.1/go.mod h1:X7bjfWoQDN0Z4FQGbE91j3gbPP7Urwzm4Z8tkvrlbRI= github.com/jfrog/jfrog-apps-config v1.0.1 h1:mtv6k7g8A8BVhlHGlSveapqf4mJfonwvXYLipdsOFMY= github.com/jfrog/jfrog-apps-config v1.0.1/go.mod h1:8AIIr1oY9JuH5dylz2S6f8Ym2MaadPLR6noCBO4C22w= -github.com/jfrog/jfrog-cli-core/v2 v2.31.1-0.20240522101456-44e989e0a442 h1:Oq43c8phFFMwZImYP2Er1+1Ewd2Bu7q7MfkRp8Ti4EA= -github.com/jfrog/jfrog-cli-core/v2 v2.31.1-0.20240522101456-44e989e0a442/go.mod h1:hB5R+BgmCbOoz5HZyyqEdv408rL26ej7ZSHfXGpLqmw= -github.com/jfrog/jfrog-client-go v1.28.1-0.20240509124610-4b9c587c0377 h1:fDneRi89u4hmD0qIf/X+HLT3IavqFeQevRtrZhyJZ5k= -github.com/jfrog/jfrog-client-go v1.28.1-0.20240509124610-4b9c587c0377/go.mod h1:m3hIn12eFWk5nJH1swPRtFrjXbiiCscOpX+v/vCdmNI= +github.com/jfrog/jfrog-cli-core/v2 v2.53.0 h1:qdZ1Svb1hGyRx2QviJtarhcA8eet8QtYU054nKzlhDg= +github.com/jfrog/jfrog-cli-core/v2 v2.53.0/go.mod h1:l101ZcbHy/FLieCx1xDtjONgkqsoLDNaqVT7b4KJ5OQ= +github.com/jfrog/jfrog-client-go v1.40.2 h1:zdCWPPT11r0bMGnAXGhZPb3RrIINhiTFCceQABhguZ4= +github.com/jfrog/jfrog-client-go v1.40.2/go.mod h1:m3hIn12eFWk5nJH1swPRtFrjXbiiCscOpX+v/vCdmNI= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=