From 672e9035c32af7d1b6800b3cd3654e25c68ab3b5 Mon Sep 17 00:00:00 2001 From: Asaf Gabai <77976014+asafgabai@users.noreply.github.com> Date: Mon, 2 Oct 2023 11:32:29 +0300 Subject: [PATCH] Build flat dependency trees while auditing Gradle projects (#976) * Upgraded to the new gradle-dep-tree version, to make the scan faster and more memory-efficient. --- buildscripts/download-jars.sh | 2 +- .../audit/sca/java/gradle-dep-tree.jar | Bin 10000 -> 11855 bytes xray/commands/audit/sca/java/gradle.go | 91 +----------------- xray/commands/audit/sca/java/gradle_test.go | 89 ++--------------- xray/commands/audit/sca/java/javautils.go | 75 ++++++++++++++- .../commands/audit/sca/java/javautils_test.go | 68 +++++++++++++ 6 files changed, 151 insertions(+), 174 deletions(-) create mode 100644 xray/commands/audit/sca/java/javautils_test.go diff --git a/buildscripts/download-jars.sh b/buildscripts/download-jars.sh index e5e6a5510..3e9f10a89 100755 --- a/buildscripts/download-jars.sh +++ b/buildscripts/download-jars.sh @@ -7,7 +7,7 @@ # https://github.com/jfrog/maven-dep-tree # 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="2.2.0" +GRADLE_DEP_TREE_VERSION="3.0.0" MAVEN_DEP_TREE_VERSION="1.0.0" 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 xray/commands/audit/sca/java/gradle-dep-tree.jar diff --git a/xray/commands/audit/sca/java/gradle-dep-tree.jar b/xray/commands/audit/sca/java/gradle-dep-tree.jar index 2762a71ba29b1c6f99fa94a823dd7aaa97c22e4e..532445bc1a337a6c2055c4b31d255a80f891ecaa 100644 GIT binary patch delta 10035 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 delta 8219 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 diff --git a/xray/commands/audit/sca/java/gradle.go b/xray/commands/audit/sca/java/gradle.go index d140efffd..933788192 100644 --- a/xray/commands/audit/sca/java/gradle.go +++ b/xray/commands/audit/sca/java/gradle.go @@ -2,11 +2,8 @@ package java import ( _ "embed" - "encoding/base64" - "encoding/json" "errors" "fmt" - "github.com/jfrog/gofrog/datastructures" "os" "os/exec" "path/filepath" @@ -58,60 +55,12 @@ allprojects { var gradleDepTreeJar []byte type depTreeManager struct { - dependenciesTree server *config.ServerDetails releasesRepo string depsRepo string useWrapper bool } -// dependenciesTree represents a map between dependencies to their children dependencies in multiple projects. -type dependenciesTree struct { - tree map[string][]dependenciesPaths -} - -// dependenciesPaths represents a map between dependencies to their children dependencies in a single project. -type dependenciesPaths struct { - Paths map[string]dependenciesPaths `json:"children"` -} - -// The gradle-dep-tree generates a JSON representation for the dependencies for each gradle build file in the project. -// parseDepTreeFiles iterates over those JSONs, and append them to the map of dependencies in dependenciesTree struct. -func (dtp *depTreeManager) parseDepTreeFiles(jsonFiles []byte) error { - outputFiles := strings.Split(strings.TrimSpace(string(jsonFiles)), "\n") - for _, path := range outputFiles { - tree, err := os.ReadFile(strings.TrimSpace(path)) - if err != nil { - return errorutils.CheckError(err) - } - - encodedFileName := path[strings.LastIndex(path, string(os.PathSeparator))+1:] - decodedFileName, err := base64.StdEncoding.DecodeString(encodedFileName) - if err != nil { - return errorutils.CheckError(err) - } - - if err = dtp.appendDependenciesPaths(tree, string(decodedFileName)); err != nil { - return errorutils.CheckError(err) - } - } - return nil -} - -func (dtp *depTreeManager) appendDependenciesPaths(jsonDepTree []byte, fileName string) error { - var deps dependenciesPaths - if err := json.Unmarshal(jsonDepTree, &deps); err != nil { - return errorutils.CheckError(err) - } - if dtp.tree == nil { - dtp.tree = make(map[string][]dependenciesPaths) - } - if len(deps.Paths) > 0 { - dtp.tree[fileName] = append(dtp.tree[fileName], deps) - } - return nil -} - func buildGradleDependencyTree(params *DependencyTreeParams) (dependencyTree []*xrayUtils.GraphNode, uniqueDeps []string, err error) { manager := &depTreeManager{useWrapper: params.UseWrapper} if params.IgnoreConfigFile { @@ -130,7 +79,7 @@ func buildGradleDependencyTree(params *DependencyTreeParams) (dependencyTree []* if err != nil { return } - dependencyTree, uniqueDeps, err = manager.getGraphFromDepTree(outputFileContent) + dependencyTree, uniqueDeps, err = getGraphFromDepTree(outputFileContent) return } @@ -163,7 +112,7 @@ func (dtp *depTreeManager) createDepTreeScriptAndGetDir() (tmpDir string, err er if err != nil { return } - gradleDepTreeJarPath := filepath.Join(tmpDir, string(gradleDepTreeJarFile)) + gradleDepTreeJarPath := filepath.Join(tmpDir, gradleDepTreeJarFile) if err = errorutils.CheckError(os.WriteFile(gradleDepTreeJarPath, gradleDepTreeJar, 0666)); err != nil { return } @@ -237,42 +186,6 @@ func (dtp *depTreeManager) execGradleDepTree(depTreeDir string) (outputFileConte return } -// Assuming we ran gradle-dep-tree, getGraphFromDepTree receives the content of the depTreeOutputFile as input -func (dtp *depTreeManager) getGraphFromDepTree(outputFileContent []byte) ([]*xrayUtils.GraphNode, []string, error) { - if err := dtp.parseDepTreeFiles(outputFileContent); err != nil { - return nil, nil, err - } - var depsGraph []*xrayUtils.GraphNode - uniqueDepsSet := datastructures.MakeSet[string]() - for dependency, children := range dtp.tree { - directDependency := &xrayUtils.GraphNode{ - Id: GavPackageTypeIdentifier + dependency, - Nodes: []*xrayUtils.GraphNode{}, - } - for _, childPath := range children { - populateGradleDependencyTree(directDependency, childPath, uniqueDepsSet) - } - depsGraph = append(depsGraph, directDependency) - } - return depsGraph, uniqueDepsSet.ToSlice(), nil -} - -func populateGradleDependencyTree(currNode *xrayUtils.GraphNode, currNodeChildren dependenciesPaths, uniqueDepsSet *datastructures.Set[string]) { - uniqueDepsSet.Add(currNode.Id) - for gav, children := range currNodeChildren.Paths { - childNode := &xrayUtils.GraphNode{ - Id: GavPackageTypeIdentifier + gav, - Nodes: []*xrayUtils.GraphNode{}, - Parent: currNode, - } - if currNode.NodeHasLoop() { - return - } - populateGradleDependencyTree(childNode, children, uniqueDepsSet) - currNode.Nodes = append(currNode.Nodes, childNode) - } -} - func getDepTreeArtifactoryRepository(remoteRepo string, server *config.ServerDetails) (string, error) { if remoteRepo == "" || server.IsEmpty() { return "", nil diff --git a/xray/commands/audit/sca/java/gradle_test.go b/xray/commands/audit/sca/java/gradle_test.go index e9d09b742..27c4aaaa0 100644 --- a/xray/commands/audit/sca/java/gradle_test.go +++ b/xray/commands/audit/sca/java/gradle_test.go @@ -47,10 +47,10 @@ func TestGradleTreesWithoutConfig(t *testing.T) { // Run getModulesDependencyTrees modulesDependencyTrees, uniqueDeps, err := buildGradleDependencyTree(&DependencyTreeParams{}) if assert.NoError(t, err) && assert.NotNil(t, modulesDependencyTrees) { - assert.Len(t, uniqueDeps, 11) - assert.Len(t, modulesDependencyTrees, 2) + assert.Len(t, uniqueDeps, 9) + assert.Len(t, modulesDependencyTrees, 5) // Check module - module := sca.GetAndAssertNode(t, modulesDependencyTrees, "webservice") + module := sca.GetAndAssertNode(t, modulesDependencyTrees, "org.jfrog.example.gradle:webservice:1.0") assert.Len(t, module.Nodes, 7) // Check direct dependency @@ -71,10 +71,10 @@ func TestGradleTreesWithConfig(t *testing.T) { // Run getModulesDependencyTrees modulesDependencyTrees, uniqueDeps, err := buildGradleDependencyTree(&DependencyTreeParams{UseWrapper: true}) if assert.NoError(t, err) && assert.NotNil(t, modulesDependencyTrees) { - assert.Len(t, modulesDependencyTrees, 3) - assert.Len(t, uniqueDeps, 11) + assert.Len(t, modulesDependencyTrees, 5) + assert.Len(t, uniqueDeps, 8) // Check module - module := sca.GetAndAssertNode(t, modulesDependencyTrees, "api") + module := sca.GetAndAssertNode(t, modulesDependencyTrees, "org.jfrog.test.gradle.publish:api:1.0-SNAPSHOT") assert.Len(t, module.Nodes, 4) // Check direct dependency @@ -86,22 +86,6 @@ func TestGradleTreesWithConfig(t *testing.T) { } } -func TestGradleTreesExcludeTestDeps(t *testing.T) { - // Create and change directory to test workspace - tempDirPath, cleanUp := sca.CreateTestWorkspace(t, "gradle-example-ci-server") - defer cleanUp() - assert.NoError(t, os.Chmod(filepath.Join(tempDirPath, "gradlew"), 0700)) - - // Run getModulesDependencyTrees - modulesDependencyTrees, uniqueDeps, err := buildGradleDependencyTree(&DependencyTreeParams{UseWrapper: true}) - if assert.NoError(t, err) && assert.NotNil(t, modulesDependencyTrees) { - assert.Len(t, modulesDependencyTrees, 2) - assert.Len(t, uniqueDeps, 11) - // Check direct dependency - assert.Nil(t, sca.GetModule(modulesDependencyTrees, "services")) - } -} - func TestIsGradleWrapperExist(t *testing.T) { // Check Gradle wrapper doesn't exist isWrapperExist, err := isGradleWrapperExist() @@ -168,67 +152,6 @@ func TestGetDepTreeArtifactoryRepository(t *testing.T) { } } -func TestGetGraphFromDepTree(t *testing.T) { - // Create and change directory to test workspace - tempDirPath, cleanUp := sca.CreateTestWorkspace(t, "gradle-example-ci-server") - defer func() { - cleanUp() - }() - assert.NoError(t, os.Chmod(filepath.Join(tempDirPath, "gradlew"), 0700)) - testCase := struct { - name string - expectedTree map[string]map[string]string - expectedUniqueDeps []string - }{ - name: "ValidOutputFileContent", - expectedTree: map[string]map[string]string{ - GavPackageTypeIdentifier + "shared": {}, - GavPackageTypeIdentifier + filepath.Base(tempDirPath): {}, - GavPackageTypeIdentifier + "services": {}, - GavPackageTypeIdentifier + "webservice": { - GavPackageTypeIdentifier + "junit:junit:4.11": "", - GavPackageTypeIdentifier + "commons-io:commons-io:1.2": "", - GavPackageTypeIdentifier + "org.apache.wicket:wicket:1.3.7": "", - GavPackageTypeIdentifier + "org.jfrog.example.gradle:shared:1.0": "", - GavPackageTypeIdentifier + "org.jfrog.example.gradle:api:1.0": "", - GavPackageTypeIdentifier + "commons-lang:commons-lang:2.4": "", - GavPackageTypeIdentifier + "commons-collections:commons-collections:3.2": "", - }, - GavPackageTypeIdentifier + "api": { - GavPackageTypeIdentifier + "org.apache.wicket:wicket:1.3.7": "", - GavPackageTypeIdentifier + "org.jfrog.example.gradle:shared:1.0": "", - GavPackageTypeIdentifier + "commons-lang:commons-lang:2.4": "", - }, - }, - expectedUniqueDeps: []string{ - GavPackageTypeIdentifier + "webservice", - GavPackageTypeIdentifier + "junit:junit:4.11", - GavPackageTypeIdentifier + "commons-io:commons-io:1.2", - GavPackageTypeIdentifier + "org.apache.wicket:wicket:1.3.7", - GavPackageTypeIdentifier + "org.jfrog.example.gradle:shared:1.0", - GavPackageTypeIdentifier + "org.jfrog.example.gradle:api:1.0", - GavPackageTypeIdentifier + "commons-collections:commons-collections:3.2", - GavPackageTypeIdentifier + "api", - GavPackageTypeIdentifier + "commons-lang:commons-lang:2.4", - GavPackageTypeIdentifier + "org.hamcrest:hamcrest-core:1.3", - GavPackageTypeIdentifier + "org.slf4j:slf4j-api:1.4.2", - }, - } - - manager := &depTreeManager{} - outputFileContent, err := manager.runGradleDepTree() - assert.NoError(t, err) - depTree, uniqueDeps, err := (&depTreeManager{}).getGraphFromDepTree(outputFileContent) - assert.NoError(t, err) - assert.ElementsMatch(t, uniqueDeps, testCase.expectedUniqueDeps, "First is actual, Second is Expected") - - for _, dependency := range depTree { - depChild, exists := testCase.expectedTree[dependency.Id] - assert.True(t, exists) - assert.Equal(t, len(depChild), len(dependency.Nodes)) - } -} - func TestCreateDepTreeScript(t *testing.T) { manager := &depTreeManager{} tmpDir, err := manager.createDepTreeScriptAndGetDir() diff --git a/xray/commands/audit/sca/java/javautils.go b/xray/commands/audit/sca/java/javautils.go index 9653de43f..37009cb5b 100644 --- a/xray/commands/audit/sca/java/javautils.go +++ b/xray/commands/audit/sca/java/javautils.go @@ -1,12 +1,15 @@ package java import ( + "encoding/json" "github.com/jfrog/gofrog/datastructures" "github.com/jfrog/jfrog-cli-core/v2/utils/config" "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" xrayutils "github.com/jfrog/jfrog-cli-core/v2/xray/utils" xrayUtils "github.com/jfrog/jfrog-client-go/xray/services/utils" + "os" "strconv" + "strings" "time" buildinfo "github.com/jfrog/build-info-go/entities" @@ -58,7 +61,7 @@ func createGavDependencyTree(buildConfig *artifactoryUtils.BuildConfiguration) ( if len(generatedBuildsInfos) == 0 { return nil, nil, errorutils.CheckErrorf("Couldn't find build " + buildName + "/" + buildNumber) } - modules := []*xrayUtils.GraphNode{} + var modules []*xrayUtils.GraphNode uniqueDepsSet := datastructures.MakeSet[string]() for _, module := range generatedBuildsInfos[0].Modules { modules = append(modules, addModuleTree(module, uniqueDepsSet)) @@ -173,3 +176,73 @@ func (dm *dependencyMultimap) putChild(parent string, child *buildinfo.Dependenc func (dm *dependencyMultimap) getChildren(parent string) map[string]*buildinfo.Dependency { return dm.multimap[parent] } + +// 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]depTreeNode `json:"nodes"` +} + +type depTreeNode struct { + Children []string `json:"children"` +} + +// getGraphFromDepTree 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(depTreeOutput []byte) (depsGraph []*xrayUtils.GraphNode, uniqueDeps []string, err error) { + modules, err := parseDepTreeFiles(depTreeOutput) + if err != nil { + return + } + uniqueDepsSet := datastructures.MakeSet[string]() + for _, moduleTree := range modules { + directDependency := &xrayUtils.GraphNode{ + Id: GavPackageTypeIdentifier + moduleTree.Root, + Nodes: []*xrayUtils.GraphNode{}, + } + populateDependencyTree(directDependency, moduleTree.Root, moduleTree, uniqueDepsSet) + depsGraph = append(depsGraph, directDependency) + } + uniqueDeps = uniqueDepsSet.ToSlice() + return +} + +func populateDependencyTree(currNode *xrayUtils.GraphNode, currNodeId string, moduleTree *moduleDepTree, uniqueDepsSet *datastructures.Set[string]) { + if currNode.NodeHasLoop() { + return + } + for _, childId := range moduleTree.Nodes[currNodeId].Children { + childGav := GavPackageTypeIdentifier + childId + childNode := &xrayUtils.GraphNode{ + Id: childGav, + Nodes: []*xrayUtils.GraphNode{}, + Parent: currNode, + } + uniqueDepsSet.Add(childGav) + populateDependencyTree(childNode, childId, moduleTree, uniqueDepsSet) + currNode.Nodes = append(currNode.Nodes, childNode) + } +} + +func parseDepTreeFiles(jsonFilePaths []byte) ([]*moduleDepTree, error) { + outputFilePaths := strings.Split(strings.TrimSpace(string(jsonFilePaths)), "\n") + var modules []*moduleDepTree + for _, path := range outputFilePaths { + results, err := parseDepTreeFile(path) + if err != nil { + return nil, err + } + modules = append(modules, results) + } + return modules, nil +} + +func parseDepTreeFile(path string) (results *moduleDepTree, err error) { + depTreeJson, err := os.ReadFile(strings.TrimSpace(path)) + if errorutils.CheckError(err) != nil { + return + } + results = &moduleDepTree{} + err = errorutils.CheckError(json.Unmarshal(depTreeJson, &results)) + return +} diff --git a/xray/commands/audit/sca/java/javautils_test.go b/xray/commands/audit/sca/java/javautils_test.go new file mode 100644 index 000000000..325a59408 --- /dev/null +++ b/xray/commands/audit/sca/java/javautils_test.go @@ -0,0 +1,68 @@ +package java + +import ( + "github.com/jfrog/jfrog-cli-core/v2/xray/commands/audit/sca" + "github.com/stretchr/testify/assert" + "os" + "path/filepath" + "testing" +) + +func TestGetGraphFromDepTree(t *testing.T) { + // Create and change directory to test workspace + tempDirPath, cleanUp := sca.CreateTestWorkspace(t, "gradle-example-ci-server") + defer func() { + cleanUp() + }() + assert.NoError(t, os.Chmod(filepath.Join(tempDirPath, "gradlew"), 0700)) + testCase := struct { + name string + expectedTree map[string]map[string]string + expectedUniqueDeps []string + }{ + name: "ValidOutputFileContent", + expectedTree: map[string]map[string]string{ + GavPackageTypeIdentifier + "org.jfrog.example.gradle:shared:1.0": {}, + GavPackageTypeIdentifier + "org.jfrog.example.gradle:" + filepath.Base(tempDirPath) + ":1.0": {}, + GavPackageTypeIdentifier + "org.jfrog.example.gradle:services:1.0": {}, + GavPackageTypeIdentifier + "org.jfrog.example.gradle:webservice:1.0": { + GavPackageTypeIdentifier + "junit:junit:4.11": "", + GavPackageTypeIdentifier + "commons-io:commons-io:1.2": "", + GavPackageTypeIdentifier + "org.apache.wicket:wicket:1.3.7": "", + GavPackageTypeIdentifier + "org.jfrog.example.gradle:shared:1.0": "", + GavPackageTypeIdentifier + "org.jfrog.example.gradle:api:1.0": "", + GavPackageTypeIdentifier + "commons-lang:commons-lang:2.4": "", + GavPackageTypeIdentifier + "commons-collections:commons-collections:3.2": "", + }, + GavPackageTypeIdentifier + "org.jfrog.example.gradle:api:1.0": { + GavPackageTypeIdentifier + "org.apache.wicket:wicket:1.3.7": "", + GavPackageTypeIdentifier + "org.jfrog.example.gradle:shared:1.0": "", + GavPackageTypeIdentifier + "commons-lang:commons-lang:2.4": "", + }, + }, + expectedUniqueDeps: []string{ + GavPackageTypeIdentifier + "junit:junit:4.11", + GavPackageTypeIdentifier + "commons-io:commons-io:1.2", + GavPackageTypeIdentifier + "org.apache.wicket:wicket:1.3.7", + GavPackageTypeIdentifier + "org.jfrog.example.gradle:shared:1.0", + GavPackageTypeIdentifier + "org.jfrog.example.gradle:api:1.0", + GavPackageTypeIdentifier + "commons-collections:commons-collections:3.2", + GavPackageTypeIdentifier + "commons-lang:commons-lang:2.4", + GavPackageTypeIdentifier + "org.hamcrest:hamcrest-core:1.3", + GavPackageTypeIdentifier + "org.slf4j:slf4j-api:1.4.2", + }, + } + + manager := &depTreeManager{} + outputFileContent, err := manager.runGradleDepTree() + assert.NoError(t, err) + depTree, uniqueDeps, err := getGraphFromDepTree(outputFileContent) + assert.NoError(t, err) + assert.ElementsMatch(t, uniqueDeps, testCase.expectedUniqueDeps, "First is actual, Second is Expected") + + for _, dependency := range depTree { + depChild, exists := testCase.expectedTree[dependency.Id] + assert.True(t, exists) + assert.Equal(t, len(depChild), len(dependency.Nodes)) + } +}