From 6f6d5d372f6bedc0801351a0cd24c114bca8e472 Mon Sep 17 00:00:00 2001 From: Assaf Attias <49212512+attiasas@users.noreply.github.com> Date: Thu, 21 Mar 2024 11:52:26 +0200 Subject: [PATCH] Move Java Dep Tree content from Core (#44) --- .github/workflows/embedded-jar-test.yml | 29 ++ buildscripts/download-jars.sh | 15 + commands/audit/sca/java/deptreemanager.go | 124 ++++++ .../audit/sca/java/deptreemanager_test.go | 66 +++ commands/audit/sca/java/gradle.go | 199 +++++++++ commands/audit/sca/java/gradle_test.go | 230 +++++++++++ commands/audit/sca/java/mvn.go | 265 ++++++++++++ commands/audit/sca/java/mvn_test.go | 380 ++++++++++++++++++ .../sca/java/resources/gradle-dep-tree.jar | Bin 0 -> 12253 bytes .../sca/java/resources/maven-dep-tree.jar | Bin 0 -> 13862 bytes .../audit/sca/java/resources/settings.xml | 19 + commands/audit/scarunner.go | 2 +- .../maven-example-with-many-types/pom.xml | 53 +++ 13 files changed, 1381 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/embedded-jar-test.yml create mode 100755 buildscripts/download-jars.sh create mode 100644 commands/audit/sca/java/deptreemanager.go create mode 100644 commands/audit/sca/java/deptreemanager_test.go create mode 100644 commands/audit/sca/java/gradle.go create mode 100644 commands/audit/sca/java/gradle_test.go create mode 100644 commands/audit/sca/java/mvn.go create mode 100644 commands/audit/sca/java/mvn_test.go create mode 100644 commands/audit/sca/java/resources/gradle-dep-tree.jar create mode 100644 commands/audit/sca/java/resources/maven-dep-tree.jar create mode 100644 commands/audit/sca/java/resources/settings.xml create mode 100644 tests/testdata/projects/package-managers/maven/maven-example-with-many-types/pom.xml diff --git a/.github/workflows/embedded-jar-test.yml b/.github/workflows/embedded-jar-test.yml new file mode 100644 index 00000000..a2ff9349 --- /dev/null +++ b/.github/workflows/embedded-jar-test.yml @@ -0,0 +1,29 @@ +# This test verifies that gradle-dep-tree.jar and maven-dep-tree.jar are kept up-to-date with the version specified in buildscripts/download-jars.js. +# It accomplishes this by downloading the JARs and executing a "git diff" command. +# In case there are any differences detected, the test will result in failure. +name: Embedded Jars Tests +on: + push: + branches: + - '**' + tags-ignore: + - '**' + pull_request: +jobs: + test: + runs-on: ubuntu-latest + env: + GOPROXY: direct + steps: + - uses: actions/checkout@v4 + + - name: Download JARs + run: buildscripts/download-jars.sh + + - name: Check Diff + run: git diff --exit-code + + - name: Log if Failure + run: echo "::warning::Please run ./buildscripts/download-jars to use compatible Maven and Gradle dependency tree JARs." + if: ${{ failure() }} + diff --git a/buildscripts/download-jars.sh b/buildscripts/download-jars.sh new file mode 100755 index 00000000..28c4d7d2 --- /dev/null +++ b/buildscripts/download-jars.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +# Please use this script to download the JAR files for maven-dep-tree and gradle-dep-tree into the directory utils/java/. +# These JARs allow us to build Maven and Gradle dependency trees efficiently and without compilation. +# Learn more about them here: +# https://github.com/jfrog/gradle-dep-tree +# 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="3.0.2" +# Changing this version also requires a change in mavenDepTreeVersion within utils/java/mvn.go. +MAVEN_DEP_TREE_VERSION="1.1.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 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/java/deptreemanager.go b/commands/audit/sca/java/deptreemanager.go new file mode 100644 index 00000000..e323fad1 --- /dev/null +++ b/commands/audit/sca/java/deptreemanager.go @@ -0,0 +1,124 @@ +package java + +import ( + "encoding/json" + "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" +) + +const ( + GavPackageTypeIdentifier = "gav://" +) + +func BuildDependencyTree(depTreeParams DepTreeParams, tech coreutils.Technology) ([]*xrayUtils.GraphNode, map[string][]string, error) { + if tech == coreutils.Maven { + return buildMavenDependencyTree(&depTreeParams) + } + return buildGradleDependencyTree(&depTreeParams) +} + +type DepTreeParams struct { + UseWrapper bool + Server *config.ServerDetails + DepsRepo string + IsMavenDepTreeInstalled bool + IsCurationCmd bool + CurationCacheFolder string +} + +type DepTreeManager struct { + server *config.ServerDetails + depsRepo string + useWrapper bool +} + +func NewDepTreeManager(params *DepTreeParams) DepTreeManager { + return DepTreeManager{useWrapper: params.UseWrapper, depsRepo: params.DepsRepo, server: params.Server} +} + +// 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"` +} + +// 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) { + modules, err := parseDepTreeFiles(outputFilePaths) + if err != nil { + return + } + uniqueDepsMap = map[string][]string{} + for _, module := range modules { + moduleTree, moduleUniqueDeps := GetModuleTreeAndDependencies(module) + depsGraph = append(depsGraph, moduleTree) + for depToAdd, depTypes := range moduleUniqueDeps { + uniqueDepsMap[depToAdd] = depTypes + } + } + return +} + +// 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) + moduleDeps := module.Nodes + for depName, dependency := range moduleDeps { + dependencyId := GavPackageTypeIdentifier + depName + var childrenList []string + for _, childName := range dependency.Children { + childId := GavPackageTypeIdentifier + childName + childrenList = append(childrenList, childId) + } + moduleTreeMap[dependencyId] = xray.DepTreeNode{ + Types: dependency.Types, + Children: childrenList, + } + } + return xray.BuildXrayDependencyTree(moduleTreeMap, GavPackageTypeIdentifier+module.Root) +} + +func parseDepTreeFiles(jsonFilePaths string) ([]*moduleDepTree, error) { + outputFilePaths := strings.Split(strings.TrimSpace(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 +} + +func getArtifactoryAuthFromServer(server *config.ServerDetails) (string, string, error) { + username, password, err := server.GetAuthenticationCredentials() + if err != nil { + return "", "", err + } + if username == "" { + return "", "", errorutils.CheckErrorf("a username is required for authenticating with Artifactory") + } + return username, password, nil +} + +func (dtm *DepTreeManager) GetDepsRepo() string { + return dtm.depsRepo +} diff --git a/commands/audit/sca/java/deptreemanager_test.go b/commands/audit/sca/java/deptreemanager_test.go new file mode 100644 index 00000000..827ccac4 --- /dev/null +++ b/commands/audit/sca/java/deptreemanager_test.go @@ -0,0 +1,66 @@ +package java + +import ( + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + "github.com/jfrog/jfrog-cli-core/v2/utils/tests" + "github.com/stretchr/testify/assert" +) + +func TestGetGradleGraphFromDepTree(t *testing.T) { + // Create and change directory to test workspace + tempDirPath, cleanUp := tests.CreateTestWorkspace(t, filepath.Join("..", "..", "..", "..", "tests", "testdata", "projects", "package-managers", "gradle", "gradle")) + defer cleanUp() + assert.NoError(t, os.Chmod(filepath.Join(tempDirPath, "gradlew"), 0700)) + expectedTree := map[string]map[string]string{ + "org.jfrog.example.gradle:shared:1.0": {}, + "org.jfrog.example.gradle:" + filepath.Base(tempDirPath) + ":1.0": {}, + "org.jfrog.example.gradle:services:1.0": {}, + "org.jfrog.example.gradle:webservice:1.0": { + "junit:junit:4.11": "", + "commons-io:commons-io:1.2": "", + "org.apache.wicket:wicket:1.3.7": "", + "org.jfrog.example.gradle:shared:1.0": "", + "org.jfrog.example.gradle:api:1.0": "", + "commons-lang:commons-lang:2.4": "", + "commons-collections:commons-collections:3.2": "", + }, + "org.jfrog.example.gradle:api:1.0": { + "org.apache.wicket:wicket:1.3.7": "", + "org.jfrog.example.gradle:shared:1.0": "", + "commons-lang:commons-lang:2.4": "", + }, + } + expectedUniqueDeps := []string{ + "junit:junit:4.11", + "org.jfrog.example.gradle:webservice:1.0", + "org.jfrog.example.gradle:api:1.0", + "org.jfrog.example.gradle:" + filepath.Base(tempDirPath) + ":1.0", + "commons-io:commons-io:1.2", + "org.apache.wicket:wicket:1.3.7", + "org.jfrog.example.gradle:shared:1.0", + "org.jfrog.example.gradle:api:1.0", + "commons-collections:commons-collections:3.2", + "commons-lang:commons-lang:2.4", + "org.hamcrest:hamcrest-core:1.3", + "org.slf4j:slf4j-api:1.4.2", + } + + manager := &gradleDepTreeManager{DepTreeManager{}} + outputFileContent, err := manager.runGradleDepTree() + assert.NoError(t, err) + depTree, uniqueDeps, err := getGraphFromDepTree(outputFileContent) + assert.NoError(t, err) + reflect.DeepEqual(uniqueDeps, expectedUniqueDeps) + + for _, dependency := range depTree { + dependencyId := strings.TrimPrefix(dependency.Id, GavPackageTypeIdentifier) + depChild, exists := expectedTree[dependencyId] + assert.True(t, exists) + assert.Equal(t, len(depChild), len(dependency.Nodes)) + } +} diff --git a/commands/audit/sca/java/gradle.go b/commands/audit/sca/java/gradle.go new file mode 100644 index 00000000..57ceff8b --- /dev/null +++ b/commands/audit/sca/java/gradle.go @@ -0,0 +1,199 @@ +package java + +import ( + _ "embed" + "errors" + "fmt" + "os" + "os/exec" + "path/filepath" + "strings" + + "github.com/jfrog/build-info-go/build" + "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" + "github.com/jfrog/jfrog-client-go/utils/errorutils" + "github.com/jfrog/jfrog-client-go/utils/io/fileutils" + "github.com/jfrog/jfrog-client-go/utils/log" + xrayUtils "github.com/jfrog/jfrog-client-go/xray/services/utils" +) + +const ( + remoteDepTreePath = "artifactory/oss-release-local" + gradlew = "gradlew" + gradleDepTreeJarFile = "gradle-dep-tree.jar" + gradleDepTreeInitFile = "gradledeptree.init" + gradleDepTreeOutputFile = "gradledeptree.out" + gradleDepTreeInitScript = `initscript { + repositories { %s + mavenCentral() + } + dependencies { + classpath files('%s') + } +} + +allprojects { + repositories { %s + } + apply plugin: com.jfrog.GradleDepTree +}` + artifactoryRepository = ` + maven { + url "%s/%s" + credentials { + username = '%s' + password = '%s' + } + }` +) + +//go:embed resources/gradle-dep-tree.jar +var gradleDepTreeJar []byte + +type gradleDepTreeManager struct { + DepTreeManager +} + +func buildGradleDependencyTree(params *DepTreeParams) (dependencyTree []*xrayUtils.GraphNode, uniqueDeps map[string][]string, err error) { + manager := &gradleDepTreeManager{DepTreeManager: NewDepTreeManager(params)} + outputFileContent, err := manager.runGradleDepTree() + if err != nil { + return + } + dependencyTree, uniqueDeps, err = getGraphFromDepTree(outputFileContent) + return +} + +func (gdt *gradleDepTreeManager) runGradleDepTree() (string, error) { + // Create the script file in the repository + depTreeDir, err := gdt.createDepTreeScriptAndGetDir() + if err != nil { + return "", err + } + defer func() { + err = errors.Join(err, fileutils.RemoveTempDir(depTreeDir)) + }() + + if gdt.useWrapper { + gdt.useWrapper, err = isGradleWrapperExist() + if err != nil { + return "", err + } + } + + output, err := gdt.execGradleDepTree(depTreeDir) + if err != nil { + return "", err + } + return string(output), nil +} + +func (gdt *gradleDepTreeManager) createDepTreeScriptAndGetDir() (tmpDir string, err error) { + tmpDir, err = fileutils.CreateTempDir() + if err != nil { + return + } + var releasesRepo string + releasesRepo, gdt.depsRepo, err = getRemoteRepos(gdt.depsRepo, gdt.server) + if err != nil { + return + } + gradleDepTreeJarPath := filepath.Join(tmpDir, gradleDepTreeJarFile) + if err = errorutils.CheckError(os.WriteFile(gradleDepTreeJarPath, gradleDepTreeJar, 0600)); err != nil { + return + } + gradleDepTreeJarPath = ioutils.DoubleWinPathSeparator(gradleDepTreeJarPath) + + depTreeInitScript := fmt.Sprintf(gradleDepTreeInitScript, releasesRepo, gradleDepTreeJarPath, gdt.depsRepo) + return tmpDir, errorutils.CheckError(os.WriteFile(filepath.Join(tmpDir, gradleDepTreeInitFile), []byte(depTreeInitScript), 0666)) +} + +// getRemoteRepos constructs the sections of Artifactory's remote repositories in the gradle-dep-tree init script. +// depsRemoteRepo - name of the remote repository that proxies the relevant registry, e.g. maven central. +// server - the Artifactory server details on which the repositories reside in. +// Returns the constructed sections. +func getRemoteRepos(depsRepo string, server *config.ServerDetails) (string, string, error) { + constructedReleasesRepo, err := constructReleasesRemoteRepo() + if err != nil { + return "", "", err + } + + constructedDepsRepo, err := getDepTreeArtifactoryRepository(depsRepo, server) + if err != nil { + return "", "", err + } + return constructedReleasesRepo, constructedDepsRepo, nil +} + +func constructReleasesRemoteRepo() (string, error) { + // Try to retrieve the serverID and remote repository that proxies https://releases.jfrog.io, from the environment variable + serverId, repoName, err := coreutils.GetServerIdAndRepo(coreutils.ReleasesRemoteEnv) + if err != nil || serverId == "" || repoName == "" { + return "", err + } + + releasesServer, err := config.GetSpecificConfig(serverId, false, true) + if err != nil { + return "", err + } + + releasesPath := fmt.Sprintf("%s/%s", repoName, remoteDepTreePath) + log.Debug("The `"+gradleDepTreeJarFile+"` will be resolved from", repoName) + return getDepTreeArtifactoryRepository(releasesPath, releasesServer) +} + +func (gdt *gradleDepTreeManager) execGradleDepTree(depTreeDir string) (outputFileContent []byte, err error) { + gradleExecPath, err := build.GetGradleExecPath(gdt.useWrapper) + if err != nil { + err = errorutils.CheckError(err) + return + } + + outputFilePath := filepath.Join(depTreeDir, gradleDepTreeOutputFile) + tasks := []string{ + "clean", + "generateDepTrees", "-I", filepath.Join(depTreeDir, gradleDepTreeInitFile), + "-q", + fmt.Sprintf("-Dcom.jfrog.depsTreeOutputFile=%s", outputFilePath), + "-Dcom.jfrog.includeAllBuildFiles=true"} + log.Info("Running gradle deps tree command:", gradleExecPath, strings.Join(tasks, " ")) + if output, err := exec.Command(gradleExecPath, tasks...).CombinedOutput(); err != nil { + return nil, errorutils.CheckErrorf("error running gradle-dep-tree: %s\n%s", err.Error(), string(output)) + } + defer func() { + err = errors.Join(err, errorutils.CheckError(os.Remove(outputFilePath))) + }() + + outputFileContent, err = os.ReadFile(outputFilePath) + err = errorutils.CheckError(err) + return +} + +func getDepTreeArtifactoryRepository(remoteRepo string, server *config.ServerDetails) (string, error) { + if remoteRepo == "" || server.IsEmpty() { + return "", nil + } + username, password, err := getArtifactoryAuthFromServer(server) + if err != nil { + return "", err + } + + log.Debug("The project dependencies will be resolved from", server.ArtifactoryUrl, "from the", remoteRepo, "repository") + return fmt.Sprintf(artifactoryRepository, + strings.TrimSuffix(server.ArtifactoryUrl, "/"), + remoteRepo, + username, + password), nil +} + +// This function assumes that the Gradle wrapper is in the root directory. +// The --project-dir option of Gradle won't work in this case. +func isGradleWrapperExist() (bool, error) { + wrapperName := gradlew + if coreutils.IsWindows() { + wrapperName += ".bat" + } + return fileutils.IsFileExists(wrapperName, false) +} diff --git a/commands/audit/sca/java/gradle_test.go b/commands/audit/sca/java/gradle_test.go new file mode 100644 index 00000000..77920f93 --- /dev/null +++ b/commands/audit/sca/java/gradle_test.go @@ -0,0 +1,230 @@ +package java + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "testing" + + "github.com/jfrog/jfrog-cli-core/v2/utils/config" + testsutils "github.com/jfrog/jfrog-cli-core/v2/utils/config/tests" + "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils" + "github.com/jfrog/jfrog-cli-core/v2/utils/ioutils" + "github.com/jfrog/jfrog-cli-core/v2/utils/tests" + + "github.com/stretchr/testify/assert" +) + +// #nosec G101 -- Dummy token for tests +// jfrog-ignore +const dummyToken = "eyJ2ZXIiOiIyIiwidHlwIjoiSldUIiwiYWxnIjoiUlMyNTYiLCJraWQiOiJIcnU2VHctZk1yOTV3dy12TDNjV3ZBVjJ3Qm9FSHpHdGlwUEFwOE1JdDljIn0.eyJzdWIiOiJqZnJ0QDAxYzNnZmZoZzJlOHc2MTQ5ZTNhMnEwdzk3XC91c2Vyc1wvYWRtaW4iLCJzY3AiOiJtZW1iZXItb2YtZ3JvdXBzOnJlYWRlcnMgYXBpOioiLCJhdWQiOiJqZnJ0QDAxYzNnZmZoZzJlOHc2MTQ5ZTNhMnEwdzk3IiwiaXNzIjoiamZydEAwMWMzZ2ZmaGcyZTh3NjE0OWUzYTJxMHc5NyIsImV4cCI6MTU1NjAzNzc2NSwiaWF0IjoxNTU2MDM0MTY1LCJqdGkiOiI1M2FlMzgyMy05NGM3LTQ0OGItOGExOC1iZGVhNDBiZjFlMjAifQ.Bp3sdvppvRxysMlLgqT48nRIHXISj9sJUCXrm7pp8evJGZW1S9hFuK1olPmcSybk2HNzdzoMcwhUmdUzAssiQkQvqd_HanRcfFbrHeg5l1fUQ397ECES-r5xK18SYtG1VR7LNTVzhJqkmRd3jzqfmIK2hKWpEgPfm8DRz3j4GGtDRxhb3oaVsT2tSSi_VfT3Ry74tzmO0GcCvmBE2oh58kUZ4QfEsalgZ8IpYHTxovsgDx_M7ujOSZx_hzpz-iy268-OkrU22PQPCfBmlbEKeEUStUO9n0pj4l1ODL31AGARyJRy46w4yzhw7Fk5P336WmDMXYs5LAX2XxPFNLvNzA" + +const expectedInitScriptWithRepos = `initscript { + repositories { + mavenCentral() + } + dependencies { + classpath files('%s') + } +} + +allprojects { + repositories { + maven { + url "https://myartifactory.com/artifactory/deps-repo" + credentials { + username = 'admin' + password = '%s' + } + } + } + apply plugin: com.jfrog.GradleDepTree +}` + +func TestGradleTreesWithoutConfig(t *testing.T) { + // Create and change directory to test workspace + tempDirPath, cleanUp := tests.CreateTestWorkspace(t, filepath.Join("..", "..", "..", "..", "tests", "testdata", "projects", "package-managers", "gradle", "gradle")) + defer cleanUp() + assert.NoError(t, os.Chmod(filepath.Join(tempDirPath, "gradlew"), 0700)) + + // Run getModulesDependencyTrees + modulesDependencyTrees, uniqueDeps, err := buildGradleDependencyTree(&DepTreeParams{}) + if assert.NoError(t, err) && assert.NotNil(t, modulesDependencyTrees) { + assert.Len(t, uniqueDeps, 12) + assert.Len(t, modulesDependencyTrees, 5) + // Check module + module := tests.GetAndAssertNode(t, modulesDependencyTrees, "org.jfrog.example.gradle:webservice:1.0") + assert.Len(t, module.Nodes, 7) + + // Check direct dependency + directDependency := tests.GetAndAssertNode(t, module.Nodes, "junit:junit:4.11") + assert.Len(t, directDependency.Nodes, 1) + + // Check transitive dependency + tests.GetAndAssertNode(t, directDependency.Nodes, "org.hamcrest:hamcrest-core:1.3") + } +} + +func TestGradleTreesWithConfig(t *testing.T) { + // Create and change directory to test workspace + tempDirPath, cleanUp := tests.CreateTestWorkspace(t, filepath.Join("..", "..", "..", "..", "tests", "testdata", "projects", "package-managers", "gradle", "gradle-example-config")) + defer cleanUp() + assert.NoError(t, os.Chmod(filepath.Join(tempDirPath, "gradlew"), 0700)) + + // Run getModulesDependencyTrees + modulesDependencyTrees, uniqueDeps, err := buildGradleDependencyTree(&DepTreeParams{UseWrapper: true}) + if assert.NoError(t, err) && assert.NotNil(t, modulesDependencyTrees) { + assert.Len(t, modulesDependencyTrees, 5) + assert.Len(t, uniqueDeps, 11) + // Check module + module := tests.GetAndAssertNode(t, modulesDependencyTrees, "org.jfrog.test.gradle.publish:api:1.0-SNAPSHOT") + assert.Len(t, module.Nodes, 4) + + // Check direct dependency + directDependency := tests.GetAndAssertNode(t, module.Nodes, "commons-lang:commons-lang:2.4") + assert.Len(t, directDependency.Nodes, 1) + + // Check transitive dependency + tests.GetAndAssertNode(t, directDependency.Nodes, "commons-io:commons-io:1.2") + } +} + +func TestIsGradleWrapperExist(t *testing.T) { + // Check Gradle wrapper doesn't exist + isWrapperExist, err := isGradleWrapperExist() + assert.False(t, isWrapperExist) + assert.NoError(t, err) + + // Check Gradle wrapper exist + _, cleanUp := tests.CreateTestWorkspace(t, filepath.Join("..", "..", "..", "..", "tests", "testdata", "projects", "package-managers", "gradle", "gradle")) + defer cleanUp() + isWrapperExist, err = isGradleWrapperExist() + assert.NoError(t, err) + assert.True(t, isWrapperExist) +} + +func TestGetDepTreeArtifactoryRepository(t *testing.T) { + tests := []struct { + name string + remoteRepo string + server *config.ServerDetails + expectedUrl string + expectedErr string + }{ + { + name: "WithAccessToken", + remoteRepo: "my-remote-repo", + server: &config.ServerDetails{ + Url: "https://myartifactory.com", + // jfrog-ignore + AccessToken: dummyToken, + }, + // jfrog-ignore + expectedUrl: "\n\t\tmaven {\n\t\t\turl \"/my-remote-repo\"\n\t\t\tcredentials {\n\t\t\t\tusername = 'admin'\n\t\t\t\tpassword = 'eyJ2ZXIiOiIyIiwidHlwIjoiSldUIiwiYWxnIjoiUlMyNTYiLCJraWQiOiJIcnU2VHctZk1yOTV3dy12TDNjV3ZBVjJ3Qm9FSHpHdGlwUEFwOE1JdDljIn0.eyJzdWIiOiJqZnJ0QDAxYzNnZmZoZzJlOHc2MTQ5ZTNhMnEwdzk3XC91c2Vyc1wvYWRtaW4iLCJzY3AiOiJtZW1iZXItb2YtZ3JvdXBzOnJlYWRlcnMgYXBpOioiLCJhdWQiOiJqZnJ0QDAxYzNnZmZoZzJlOHc2MTQ5ZTNhMnEwdzk3IiwiaXNzIjoiamZydEAwMWMzZ2ZmaGcyZTh3NjE0OWUzYTJxMHc5NyIsImV4cCI6MTU1NjAzNzc2NSwiaWF0IjoxNTU2MDM0MTY1LCJqdGkiOiI1M2FlMzgyMy05NGM3LTQ0OGItOGExOC1iZGVhNDBiZjFlMjAifQ.Bp3sdvppvRxysMlLgqT48nRIHXISj9sJUCXrm7pp8evJGZW1S9hFuK1olPmcSybk2HNzdzoMcwhUmdUzAssiQkQvqd_HanRcfFbrHeg5l1fUQ397ECES-r5xK18SYtG1VR7LNTVzhJqkmRd3jzqfmIK2hKWpEgPfm8DRz3j4GGtDRxhb3oaVsT2tSSi_VfT3Ry74tzmO0GcCvmBE2oh58kUZ4QfEsalgZ8IpYHTxovsgDx_M7ujOSZx_hzpz-iy268-OkrU22PQPCfBmlbEKeEUStUO9n0pj4l1ODL31AGARyJRy46w4yzhw7Fk5P336WmDMXYs5LAX2XxPFNLvNzA'\n\t\t\t}\n\t\t}", + expectedErr: "", + }, + { + name: "WithUsernameAndPassword", + remoteRepo: "my-remote-repo", + server: &config.ServerDetails{ + Url: "https://myartifactory.com", + User: "my-username", + Password: "my-password", + }, + expectedUrl: "\n\t\tmaven {\n\t\t\turl \"/my-remote-repo\"\n\t\t\tcredentials {\n\t\t\t\tusername = 'my-username'\n\t\t\t\tpassword = 'my-password'\n\t\t\t}\n\t\t}", + expectedErr: "", + }, + { + name: "MissingCredentials", + remoteRepo: "my-remote-repo", + server: &config.ServerDetails{ + Url: "https://myartifactory.com", + }, + expectedUrl: "", + expectedErr: "either username/password or access token must be set for https://myartifactory.com", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + url, err := getDepTreeArtifactoryRepository(test.remoteRepo, test.server) + if err != nil { + assert.Equal(t, test.expectedErr, err.Error()) + } else { + assert.Equal(t, test.expectedUrl, url) + } + }) + } +} + +func TestCreateDepTreeScript(t *testing.T) { + manager := &gradleDepTreeManager{DepTreeManager: DepTreeManager{}} + tmpDir, err := manager.createDepTreeScriptAndGetDir() + assert.NoError(t, err) + defer func() { + assert.NoError(t, os.Remove(filepath.Join(tmpDir, gradleDepTreeInitFile))) + }() + content, err := os.ReadFile(filepath.Join(tmpDir, gradleDepTreeInitFile)) + assert.NoError(t, err) + gradleDepTreeJarPath := ioutils.DoubleWinPathSeparator(filepath.Join(tmpDir, gradleDepTreeJarFile)) + assert.Equal(t, fmt.Sprintf(gradleDepTreeInitScript, "", gradleDepTreeJarPath, ""), string(content)) +} + +func TestCreateDepTreeScriptWithRepositories(t *testing.T) { + manager := &gradleDepTreeManager{DepTreeManager: DepTreeManager{}} + manager.depsRepo = "deps-repo" + manager.server = &config.ServerDetails{ + Url: "https://myartifactory.com/", + ArtifactoryUrl: "https://myartifactory.com/artifactory", + // jfrog-ignore + AccessToken: dummyToken, + } + tmpDir, err := manager.createDepTreeScriptAndGetDir() + assert.NoError(t, err) + defer func() { + assert.NoError(t, os.Remove(filepath.Join(tmpDir, gradleDepTreeInitFile))) + }() + + content, err := os.ReadFile(filepath.Join(tmpDir, gradleDepTreeInitFile)) + assert.NoError(t, err) + gradleDepTreeJarPath := ioutils.DoubleWinPathSeparator(filepath.Join(tmpDir, gradleDepTreeJarFile)) + // jfrog-ignore + assert.Equal(t, fmt.Sprintf(expectedInitScriptWithRepos, gradleDepTreeJarPath, dummyToken), string(content)) +} + +func TestConstructReleasesRemoteRepo(t *testing.T) { + cleanUp := testsutils.CreateTempEnv(t, false) + serverDetails := &config.ServerDetails{ + ServerId: "test", + ArtifactoryUrl: "https://domain.com/artifactory", + User: "user", + Password: "pass", + } + err := config.SaveServersConf([]*config.ServerDetails{serverDetails}) + assert.NoError(t, err) + defer cleanUp() + testCases := []struct { + envVar string + expectedRepo string + expectedErr error + }{ + {envVar: "", expectedRepo: "", expectedErr: nil}, + {envVar: "test/repo1", expectedRepo: "\n\t\tmaven {\n\t\t\turl \"https://domain.com/artifactory/repo1/artifactory/oss-release-local\"\n\t\t\tcredentials {\n\t\t\t\tusername = 'user'\n\t\t\t\tpassword = 'pass'\n\t\t\t}\n\t\t}", expectedErr: nil}, + {envVar: "notexist/repo1", expectedRepo: "", expectedErr: errors.New("Server ID 'notexist' does not exist.")}, + } + + for _, tc := range testCases { + // Set the environment variable for this test case + func() { + assert.NoError(t, os.Setenv(coreutils.ReleasesRemoteEnv, tc.envVar)) + defer func() { + // Reset the environment variable after each test case + assert.NoError(t, os.Unsetenv(coreutils.ReleasesRemoteEnv)) + }() + actualRepo, actualErr := constructReleasesRemoteRepo() + assert.Equal(t, tc.expectedRepo, actualRepo) + assert.Equal(t, tc.expectedErr, actualErr) + }() + } +} diff --git a/commands/audit/sca/java/mvn.go b/commands/audit/sca/java/mvn.go new file mode 100644 index 00000000..76514fb1 --- /dev/null +++ b/commands/audit/sca/java/mvn.go @@ -0,0 +1,265 @@ +package java + +import ( + _ "embed" + "errors" + "fmt" + "net/url" + "os" + "os/exec" + "path" + "path/filepath" + "strings" + + "github.com/jfrog/jfrog-cli-core/v2/utils/ioutils" + "github.com/jfrog/jfrog-client-go/utils/errorutils" + "github.com/jfrog/jfrog-client-go/utils/io/fileutils" + "github.com/jfrog/jfrog-client-go/utils/log" + xrayUtils "github.com/jfrog/jfrog-client-go/xray/services/utils" +) + +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" + settingsXmlFile = "settings.xml" +) + +var mavenConfigPath = filepath.Join(".mvn", "maven.config") + +type MavenDepTreeCmd string + +const ( + Projects MavenDepTreeCmd = "projects" + Tree MavenDepTreeCmd = "tree" +) + +//go:embed resources/settings.xml +var settingsXmlTemplate string + +//go:embed resources/maven-dep-tree.jar +var mavenDepTreeJar []byte + +type MavenDepTreeManager struct { + DepTreeManager + isInstalled bool + // this flag its curation command, it will set dedicated cache and download url. + isCurationCmd bool + // path to the curation dedicated cache + curationCacheFolder string + cmdName MavenDepTreeCmd + settingsXmlPath string +} + +func NewMavenDepTreeManager(params *DepTreeParams, cmdName MavenDepTreeCmd) *MavenDepTreeManager { + depTreeManager := NewDepTreeManager(&DepTreeParams{ + Server: params.Server, + DepsRepo: params.DepsRepo, + }) + return &MavenDepTreeManager{ + DepTreeManager: depTreeManager, + isInstalled: params.IsMavenDepTreeInstalled, + cmdName: cmdName, + isCurationCmd: params.IsCurationCmd, + curationCacheFolder: params.CurationCacheFolder, + } +} + +func buildMavenDependencyTree(params *DepTreeParams) (dependencyTree []*xrayUtils.GraphNode, uniqueDeps map[string][]string, err error) { + manager := NewMavenDepTreeManager(params, Tree) + outputFilePaths, clearMavenDepTreeRun, err := manager.RunMavenDepTree() + if err != nil { + if clearMavenDepTreeRun != nil { + err = errors.Join(err, clearMavenDepTreeRun()) + } + return + } + defer func() { + err = errors.Join(err, clearMavenDepTreeRun()) + }() + dependencyTree, uniqueDeps, err = getGraphFromDepTree(outputFilePaths) + return +} + +// Runs maven-dep-tree according to cmdName. Returns the plugin output along with a function pointer to revert the plugin side effects. +// If a non-nil clearMavenDepTreeRun pointer is returnes it means we had no error during the entire function execution +func (mdt *MavenDepTreeManager) RunMavenDepTree() (depTreeOutput string, clearMavenDepTreeRun func() error, err error) { + // depTreeExecDir is a temp directory for all the files that are required for the maven-dep-tree run + depTreeExecDir, clearMavenDepTreeRun, err := mdt.CreateTempDirWithSettingsXmlIfNeeded() + if err != nil { + return + } + if err = mdt.installMavenDepTreePlugin(depTreeExecDir); err != nil { + return + } + + depTreeOutput, err = mdt.execMavenDepTree(depTreeExecDir) + if err != nil { + return + } + return +} + +func (mdt *MavenDepTreeManager) installMavenDepTreePlugin(depTreeExecDir string) error { + if mdt.isInstalled { + return nil + } + mavenDepTreeJarPath := filepath.Join(depTreeExecDir, mavenDepTreeJarFile) + if err := errorutils.CheckError(os.WriteFile(mavenDepTreeJarPath, mavenDepTreeJar, 0666)); err != nil { + return err + } + goals := GetMavenPluginInstallationGoals(mavenDepTreeJarPath) + _, err := mdt.RunMvnCmd(goals) + return err +} + +func GetMavenPluginInstallationGoals(pluginPath string) []string { + return []string{"org.apache.maven.plugins:maven-install-plugin:3.1.1:install-file", "-Dfile=" + pluginPath, "-B"} +} + +func (mdt *MavenDepTreeManager) execMavenDepTree(depTreeExecDir string) (string, error) { + if mdt.cmdName == Tree { + return mdt.runTreeCmd(depTreeExecDir) + } + return mdt.runProjectsCmd() +} + +func (mdt *MavenDepTreeManager) runTreeCmd(depTreeExecDir string) (string, error) { + mavenDepTreePath := filepath.Join(depTreeExecDir, mavenDepTreeOutputFile) + goals := []string{"com.jfrog:maven-dep-tree:" + mavenDepTreeVersion + ":" + string(Tree), "-DdepsTreeOutputFile=" + mavenDepTreePath, "-B"} + if mdt.isCurationCmd { + goals = append(goals, "-Dmaven.repo.local="+mdt.curationCacheFolder) + } + if _, err := mdt.RunMvnCmd(goals); err != nil { + return "", err + } + + mavenDepTreeOutput, err := os.ReadFile(mavenDepTreePath) + if err != nil { + return "", errorutils.CheckError(err) + } + return string(mavenDepTreeOutput), nil +} + +func (mdt *MavenDepTreeManager) runProjectsCmd() (string, error) { + goals := []string{"com.jfrog:maven-dep-tree:" + mavenDepTreeVersion + ":" + string(Projects), "-q"} + output, err := mdt.RunMvnCmd(goals) + if err != nil { + return "", err + } + return string(output), nil +} + +func (mdt *MavenDepTreeManager) RunMvnCmd(goals []string) (cmdOutput []byte, err error) { + restoreMavenConfig, err := removeMavenConfig() + if err != nil { + return + } + + defer func() { + if restoreMavenConfig != nil { + err = errors.Join(err, restoreMavenConfig()) + } + }() + + if mdt.settingsXmlPath != "" { + goals = append(goals, "-s", mdt.settingsXmlPath) + } + + //#nosec G204 + cmdOutput, err = exec.Command("mvn", goals...).CombinedOutput() + if err != nil { + stringOutput := string(cmdOutput) + if len(cmdOutput) > 0 { + log.Info(stringOutput) + } + if msg := mdt.suspectCurationBlockedError(stringOutput); msg != "" { + err = fmt.Errorf("failed running command 'mvn %s\n\n%s", strings.Join(goals, " "), msg) + } else { + err = fmt.Errorf("failed running command 'mvn %s': %s", strings.Join(goals, " "), err.Error()) + } + } + return +} + +func (mdt *MavenDepTreeManager) GetSettingsXmlPath() string { + return mdt.settingsXmlPath +} + +func (mdt *MavenDepTreeManager) SetSettingsXmlPath(settingsXmlPath string) { + mdt.settingsXmlPath = settingsXmlPath +} + +func removeMavenConfig() (func() error, error) { + mavenConfigExists, err := fileutils.IsFileExists(mavenConfigPath, false) + if err != nil { + return nil, err + } + if !mavenConfigExists { + return nil, nil + } + restoreMavenConfig, err := ioutils.BackupFile(mavenConfigPath, "maven.config.bkp") + if err != nil { + return nil, err + } + err = os.Remove(mavenConfigPath) + if err != nil { + err = errorutils.CheckErrorf("failed to remove %s while building the maven dependencies tree. Error received:\n%s", mavenConfigPath, err.Error()) + } + return restoreMavenConfig, err +} + +// Creates a new settings.xml file configured with the provided server and repository from the current MavenDepTreeManager instance. +// The settings.xml will be written to the given path. +func (mdt *MavenDepTreeManager) createSettingsXmlWithConfiguredArtifactory(settingsXmlPath string) error { + username, password, err := getArtifactoryAuthFromServer(mdt.server) + if err != nil { + return err + } + endPoint := mdt.depsRepo + if mdt.isCurationCmd { + endPoint = path.Join("api/curation/audit", endPoint) + } + remoteRepositoryFullPath, err := url.JoinPath(mdt.server.ArtifactoryUrl, endPoint) + if err != nil { + return err + } + mdt.settingsXmlPath = filepath.Join(settingsXmlPath, settingsXmlFile) + settingsXmlContent := fmt.Sprintf(settingsXmlTemplate, username, password, remoteRepositoryFullPath) + + return errorutils.CheckError(os.WriteFile(mdt.settingsXmlPath, []byte(settingsXmlContent), 0600)) +} + +// Creates a temporary directory. +// If Artifactory resolution repo is provided, a settings.xml file with the provided server and repository will be created inside the temprary directory. +func (mdt *MavenDepTreeManager) CreateTempDirWithSettingsXmlIfNeeded() (tempDirPath string, clearMavenDepTreeRun func() error, err error) { + tempDirPath, err = fileutils.CreateTempDir() + if err != nil { + return + } + + clearMavenDepTreeRun = func() error { return fileutils.RemoveTempDir(tempDirPath) } + + // Create a settings.xml file that sets the dependency resolution from the given server and repository + if mdt.depsRepo != "" { + err = mdt.createSettingsXmlWithConfiguredArtifactory(tempDirPath) + } + if err != nil { + err = errors.Join(err, clearMavenDepTreeRun()) + clearMavenDepTreeRun = nil + } + return +} + +// In case mvn tree fails on 403 or 500 it can be related to packages blocked by curation. +// For this use case to succeed, pass through should be enabled in the curated repos +func (mdt *MavenDepTreeManager) suspectCurationBlockedError(cmdOutput string) (msgToUser string) { + if !mdt.isCurationCmd { + return + } + if strings.Contains(cmdOutput, "status code: 403") || strings.Contains(cmdOutput, "status code: 500") { + msgToUser = "Failed to get dependencies tree for maven project, Please verify pass-through enabled on the curated repos" + } + return msgToUser +} diff --git a/commands/audit/sca/java/mvn_test.go b/commands/audit/sca/java/mvn_test.go new file mode 100644 index 00000000..f211879e --- /dev/null +++ b/commands/audit/sca/java/mvn_test.go @@ -0,0 +1,380 @@ +package java + +import ( + "github.com/jfrog/build-info-go/utils" + "github.com/jfrog/jfrog-cli-core/v2/utils/config" + coreTests "github.com/jfrog/jfrog-cli-core/v2/utils/tests" + "github.com/jfrog/jfrog-client-go/utils/io/fileutils" + "github.com/jfrog/jfrog-client-go/utils/tests" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "golang.org/x/exp/maps" + "os" + "path/filepath" + "strings" + "testing" +) + +const ( + //#nosec G101 - dummy token for testing + settingsXmlWithUsernameAndPassword = ` + + + + artifactory + testUser + testPass + + + + + artifactory + https://myartifactory.com/artifactory/testRepo + * + + +` + //#nosec G101 - dummy token for testing + settingsXmlWithUsernameAndPasswordAndCurationDedicatedAPi = ` + + + + artifactory + testUser + testPass + + + + + artifactory + https://myartifactory.com/artifactory/api/curation/audit/testRepo + * + + +` + //#nosec G101 - dummy token for testing + settingsXmlWithUsernameAndToken = ` + + + + artifactory + testUser + eyJ2ZXIiOiIyIiwidHlwIjoiSldUIiwiYWxnIjoiUlMyNTYiLCJraWQiOiJIcnU2VHctZk1yOTV3dy12TDNjV3ZBVjJ3Qm9FSHpHdGlwUEFwOE1JdDljIn0.eyJzdWIiOiJqZnJ0QDAxYzNnZmZoZzJlOHc2MTQ5ZTNhMnEwdzk3XC91c2Vyc1wvYWRtaW4iLCJzY3AiOiJtZW1iZXItb2YtZ3JvdXBzOnJlYWRlcnMgYXBpOioiLCJhdWQiOiJqZnJ0QDAxYzNnZmZoZzJlOHc2MTQ5ZTNhMnEwdzk3IiwiaXNzIjoiamZydEAwMWMzZ2ZmaGcyZTh3NjE0OWUzYTJxMHc5NyIsImV4cCI6MTU1NjAzNzc2NSwiaWF0IjoxNTU2MDM0MTY1LCJqdGkiOiI1M2FlMzgyMy05NGM3LTQ0OGItOGExOC1iZGVhNDBiZjFlMjAifQ.Bp3sdvppvRxysMlLgqT48nRIHXISj9sJUCXrm7pp8evJGZW1S9hFuK1olPmcSybk2HNzdzoMcwhUmdUzAssiQkQvqd_HanRcfFbrHeg5l1fUQ397ECES-r5xK18SYtG1VR7LNTVzhJqkmRd3jzqfmIK2hKWpEgPfm8DRz3j4GGtDRxhb3oaVsT2tSSi_VfT3Ry74tzmO0GcCvmBE2oh58kUZ4QfEsalgZ8IpYHTxovsgDx_M7ujOSZx_hzpz-iy268-OkrU22PQPCfBmlbEKeEUStUO9n0pj4l1ODL31AGARyJRy46w4yzhw7Fk5P336WmDMXYs5LAX2XxPFNLvNzA + + + + + artifactory + https://myartifactory.com/artifactory/testRepo + * + + +` + //#nosec G101 - dummy token for testing + settingsXmlWithAccessToken = ` + + + + artifactory + admin + eyJ2ZXIiOiIyIiwidHlwIjoiSldUIiwiYWxnIjoiUlMyNTYiLCJraWQiOiJIcnU2VHctZk1yOTV3dy12TDNjV3ZBVjJ3Qm9FSHpHdGlwUEFwOE1JdDljIn0.eyJzdWIiOiJqZnJ0QDAxYzNnZmZoZzJlOHc2MTQ5ZTNhMnEwdzk3XC91c2Vyc1wvYWRtaW4iLCJzY3AiOiJtZW1iZXItb2YtZ3JvdXBzOnJlYWRlcnMgYXBpOioiLCJhdWQiOiJqZnJ0QDAxYzNnZmZoZzJlOHc2MTQ5ZTNhMnEwdzk3IiwiaXNzIjoiamZydEAwMWMzZ2ZmaGcyZTh3NjE0OWUzYTJxMHc5NyIsImV4cCI6MTU1NjAzNzc2NSwiaWF0IjoxNTU2MDM0MTY1LCJqdGkiOiI1M2FlMzgyMy05NGM3LTQ0OGItOGExOC1iZGVhNDBiZjFlMjAifQ.Bp3sdvppvRxysMlLgqT48nRIHXISj9sJUCXrm7pp8evJGZW1S9hFuK1olPmcSybk2HNzdzoMcwhUmdUzAssiQkQvqd_HanRcfFbrHeg5l1fUQ397ECES-r5xK18SYtG1VR7LNTVzhJqkmRd3jzqfmIK2hKWpEgPfm8DRz3j4GGtDRxhb3oaVsT2tSSi_VfT3Ry74tzmO0GcCvmBE2oh58kUZ4QfEsalgZ8IpYHTxovsgDx_M7ujOSZx_hzpz-iy268-OkrU22PQPCfBmlbEKeEUStUO9n0pj4l1ODL31AGARyJRy46w4yzhw7Fk5P336WmDMXYs5LAX2XxPFNLvNzA + + + + + artifactory + https://myartifactory.com/artifactory/testRepo + * + + +` +) + +func TestMavenTreesMultiModule(t *testing.T) { + // Create and change directory to test workspace + _, cleanUp := coreTests.CreateTestWorkspace(t, filepath.Join("..", "..", "..", "..", "tests", "testdata", "projects", "package-managers", "maven", "maven-example")) + defer cleanUp() + + expectedUniqueDeps := []string{ + GavPackageTypeIdentifier + "javax.mail:mail:1.4", + GavPackageTypeIdentifier + "org.testng:testng:5.9", + GavPackageTypeIdentifier + "javax.servlet:servlet-api:2.5", + GavPackageTypeIdentifier + "org.jfrog.test:multi:3.7-SNAPSHOT", + GavPackageTypeIdentifier + "org.jfrog.test:multi3:3.7-SNAPSHOT", + GavPackageTypeIdentifier + "org.jfrog.test:multi2:3.7-SNAPSHOT", + GavPackageTypeIdentifier + "junit:junit:3.8.1", + GavPackageTypeIdentifier + "org.jfrog.test:multi1:3.7-SNAPSHOT", + GavPackageTypeIdentifier + "commons-io:commons-io:1.4", + GavPackageTypeIdentifier + "org.apache.commons:commons-email:1.1", + GavPackageTypeIdentifier + "javax.activation:activation:1.1", + GavPackageTypeIdentifier + "hsqldb:hsqldb:1.8.0.10", + } + // Run getModulesDependencyTrees + modulesDependencyTrees, uniqueDeps, err := buildMavenDependencyTree(&DepTreeParams{}) + if assert.NoError(t, err) && assert.NotEmpty(t, modulesDependencyTrees) { + assert.ElementsMatch(t, maps.Keys(uniqueDeps), expectedUniqueDeps, "First is actual, Second is Expected") + // Check root module + multi := coreTests.GetAndAssertNode(t, modulesDependencyTrees, "org.jfrog.test:multi:3.7-SNAPSHOT") + if assert.NotNil(t, multi) { + assert.Len(t, multi.Nodes, 1) + // Check multi1 with a transitive dependency + multi1 := coreTests.GetAndAssertNode(t, modulesDependencyTrees, "org.jfrog.test:multi1:3.7-SNAPSHOT") + assert.Len(t, multi1.Nodes, 4) + commonsEmail := coreTests.GetAndAssertNode(t, multi1.Nodes, "org.apache.commons:commons-email:1.1") + assert.Len(t, commonsEmail.Nodes, 2) + + // Check multi2 and multi3 + multi2 := coreTests.GetAndAssertNode(t, modulesDependencyTrees, "org.jfrog.test:multi2:3.7-SNAPSHOT") + assert.Len(t, multi2.Nodes, 1) + multi3 := coreTests.GetAndAssertNode(t, modulesDependencyTrees, "org.jfrog.test:multi3:3.7-SNAPSHOT") + assert.Len(t, multi3.Nodes, 4) + } + } +} + +func TestMavenWrapperTrees(t *testing.T) { + // Create and change directory to test workspace + _, cleanUp := coreTests.CreateTestWorkspace(t, filepath.Join("..", "..", "..", "..", "tests", "testdata", "projects", "package-managers", "maven", "maven-example-with-wrapper")) + err := os.Chmod("mvnw", 0700) + defer cleanUp() + assert.NoError(t, err) + expectedUniqueDeps := []string{ + GavPackageTypeIdentifier + "org.jfrog.test:multi1:3.7-SNAPSHOT", + GavPackageTypeIdentifier + "org.codehaus.plexus:plexus-utils:1.5.1", + GavPackageTypeIdentifier + "org.springframework:spring-beans:2.5.6", + GavPackageTypeIdentifier + "commons-logging:commons-logging:1.1.1", + GavPackageTypeIdentifier + "org.jfrog.test:multi3:3.7-SNAPSHOT", + GavPackageTypeIdentifier + "org.apache.commons:commons-email:1.1", + GavPackageTypeIdentifier + "org.springframework:spring-aop:2.5.6", + 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 + "hsqldb:hsqldb:1.8.0.10", + GavPackageTypeIdentifier + "junit:junit:3.8.1", + GavPackageTypeIdentifier + "javax.activation:activation:1.1", + GavPackageTypeIdentifier + "javax.mail:mail:1.4", + GavPackageTypeIdentifier + "aopalliance:aopalliance:1.0", + GavPackageTypeIdentifier + "commons-io:commons-io:1.4", + GavPackageTypeIdentifier + "javax.servlet.jsp:jsp-api:2.1", + GavPackageTypeIdentifier + "javax.servlet:servlet-api:2.5", + } + + modulesDependencyTrees, uniqueDeps, err := buildMavenDependencyTree(&DepTreeParams{}) + if assert.NoError(t, err) && assert.NotEmpty(t, modulesDependencyTrees) { + assert.ElementsMatch(t, maps.Keys(uniqueDeps), expectedUniqueDeps, "First is actual, Second is Expected") + // Check root module + multi := coreTests.GetAndAssertNode(t, modulesDependencyTrees, "org.jfrog.test:multi:3.7-SNAPSHOT") + if assert.NotNil(t, multi) { + assert.Len(t, multi.Nodes, 1) + // Check multi1 with a transitive dependency + multi1 := coreTests.GetAndAssertNode(t, modulesDependencyTrees, "org.jfrog.test:multi1:3.7-SNAPSHOT") + assert.Len(t, multi1.Nodes, 7) + commonsEmail := coreTests.GetAndAssertNode(t, multi1.Nodes, "org.apache.commons:commons-email:1.1") + assert.Len(t, commonsEmail.Nodes, 2) + // Check multi2 and multi3 + multi2 := coreTests.GetAndAssertNode(t, modulesDependencyTrees, "org.jfrog.test:multi2:3.7-SNAPSHOT") + assert.Len(t, multi2.Nodes, 1) + multi3 := coreTests.GetAndAssertNode(t, modulesDependencyTrees, "org.jfrog.test:multi3:3.7-SNAPSHOT") + assert.Len(t, multi3.Nodes, 4) + } + } +} + +func TestMavenWrapperTreesTypes(t *testing.T) { + // Create and change directory to test workspace + _, cleanUp := coreTests.CreateTestWorkspace(t, filepath.Join("..", "..", "..", "..", "tests", "testdata", "projects", "package-managers", "maven", "maven-example-with-many-types")) + defer cleanUp() + tree, uniqueDeps, err := buildMavenDependencyTree(&DepTreeParams{}) + require.NoError(t, err) + // dependency of pom type + depWithPomType := uniqueDeps["gav://org.webjars:lodash:4.17.21"] + assert.NotEmpty(t, depWithPomType) + assert.Equal(t, depWithPomType[0], "pom") + existInTreePom := false + for _, node := range tree[0].Nodes { + if node.Id == "gav://org.webjars:lodash:4.17.21" { + nodeTypes := *node.Types + assert.Equal(t, nodeTypes[0], "pom") + existInTreePom = true + } + } + assert.True(t, existInTreePom) + + // dependency of jar type + depWithJarType := uniqueDeps["gav://junit:junit:4.11"] + assert.NotEmpty(t, depWithJarType) + assert.Equal(t, depWithJarType[0], "jar") + existInTreeJar := false + for _, node := range tree[0].Nodes { + if node.Id == "gav://junit:junit:4.11" { + nodeTypes := *node.Types + assert.Equal(t, nodeTypes[0], "jar") + existInTreeJar = true + } + } + assert.True(t, existInTreeJar) +} + +func TestDepTreeWithDedicatedCache(t *testing.T) { + // Create and change directory to test workspace + _, cleanUp := coreTests.CreateTestWorkspace(t, filepath.Join("..", "..", "..", "..", "tests", "testdata", "projects", "package-managers", "maven", "maven-example-with-wrapper")) + err := os.Chmod("mvnw", 0700) + defer cleanUp() + assert.NoError(t, err) + tempDir := t.TempDir() + defer assert.NoError(t, utils.RemoveTempDir(tempDir)) + manager := NewMavenDepTreeManager(&DepTreeParams{IsCurationCmd: true, CurationCacheFolder: tempDir}, Tree) + _, err = manager.runTreeCmd(tempDir) + require.NoError(t, err) + // validate one of the jars exist in the dedicated cache for curation + fileExist, err := utils.IsFileExists(filepath.Join(tempDir, "org/slf4j/slf4j-api/1.7.36/slf4j-api-1.7.36.jar"), false) + require.NoError(t, err) + assert.True(t, fileExist) +} + +func TestGetMavenPluginInstallationArgs(t *testing.T) { + args := GetMavenPluginInstallationGoals("testPlugin") + assert.Equal(t, "org.apache.maven.plugins:maven-install-plugin:3.1.1:install-file", args[0]) + assert.Equal(t, "-Dfile=testPlugin", args[1]) +} + +func TestCreateSettingsXmlWithConfiguredArtifactory(t *testing.T) { + // Test case for successful creation of settings.xml. + mdt := MavenDepTreeManager{ + DepTreeManager: DepTreeManager{ + server: &config.ServerDetails{ + ArtifactoryUrl: "https://myartifactory.com/artifactory", + User: "testUser", + Password: "testPass", + }, + depsRepo: "testRepo", + }, + } + // Create a temporary directory for testing and settings.xml creation + tempDir := t.TempDir() + err := mdt.createSettingsXmlWithConfiguredArtifactory(tempDir) + assert.NoError(t, err) + + // Verify settings.xml file creation with username and password + settingsXmlPath := filepath.Join(tempDir, "settings.xml") + actualContent, err := os.ReadFile(settingsXmlPath) + actualContent = []byte(strings.ReplaceAll(string(actualContent), "\r\n", "\n")) + assert.NoError(t, err) + assert.Equal(t, settingsXmlWithUsernameAndPassword, string(actualContent)) + + // check curation command write a dedicated api for curation. + mdt.isCurationCmd = true + err = mdt.createSettingsXmlWithConfiguredArtifactory(tempDir) + require.NoError(t, err) + actualContent, err = os.ReadFile(settingsXmlPath) + actualContent = []byte(strings.ReplaceAll(string(actualContent), "\r\n", "\n")) + assert.NoError(t, err) + assert.Equal(t, settingsXmlWithUsernameAndPasswordAndCurationDedicatedAPi, string(actualContent)) + mdt.isCurationCmd = false + + mdt.server.Password = "" + // jfrog-ignore + mdt.server.AccessToken = dummyToken + err = mdt.createSettingsXmlWithConfiguredArtifactory(tempDir) + assert.NoError(t, err) + + // Verify settings.xml file creation with username and access token + actualContent, err = os.ReadFile(settingsXmlPath) + actualContent = []byte(strings.ReplaceAll(string(actualContent), "\r\n", "\n")) + assert.NoError(t, err) + assert.Equal(t, settingsXmlWithUsernameAndToken, string(actualContent)) + + mdt.server.User = "" + err = mdt.createSettingsXmlWithConfiguredArtifactory(tempDir) + assert.NoError(t, err) + + // Verify settings.xml file creation with access token only + actualContent, err = os.ReadFile(settingsXmlPath) + actualContent = []byte(strings.ReplaceAll(string(actualContent), "\r\n", "\n")) + assert.NoError(t, err) + assert.Equal(t, settingsXmlWithAccessToken, string(actualContent)) +} + +func TestRunProjectsCmd(t *testing.T) { + // Create and change directory to test workspace + _, cleanUp := coreTests.CreateTestWorkspace(t, filepath.Join("..", "..", "..", "..", "tests", "testdata", "projects", "package-managers", "maven", "maven-example")) + defer cleanUp() + mvnDepTreeManager := NewMavenDepTreeManager(&DepTreeParams{}, Projects) + output, clearMavenDepTreeRun, err := mvnDepTreeManager.RunMavenDepTree() + assert.NoError(t, err) + assert.NotNil(t, clearMavenDepTreeRun) + + pomPathOccurrences := strings.Count(output, "pomPath") + assert.Equal(t, 4, pomPathOccurrences) + assert.NoError(t, clearMavenDepTreeRun()) +} + +func TestRemoveMavenConfig(t *testing.T) { + tmpDir := t.TempDir() + currentDir, err := os.Getwd() + assert.NoError(t, err) + restoreDir := tests.ChangeDirWithCallback(t, currentDir, tmpDir) + defer restoreDir() + + // No maven.config exists + restoreFunc, err := removeMavenConfig() + assert.Nil(t, restoreFunc) + assert.Nil(t, err) + + // Create maven.config + err = fileutils.CreateDirIfNotExist(".mvn") + assert.NoError(t, err) + file, err := os.Create(mavenConfigPath) + assert.NoError(t, err) + err = file.Close() + assert.NoError(t, err) + restoreFunc, err = removeMavenConfig() + assert.NoError(t, err) + assert.NoFileExists(t, mavenConfigPath) + err = restoreFunc() + assert.NoError(t, err) + assert.FileExists(t, mavenConfigPath) +} + +func TestMavenDepTreeManager_suspectCurationBlockedError(t *testing.T) { + errPrefix := "[ERROR] Failed to execute goal on project my-app: Could not resolve dependencies for project com.mycompany.app:my-app:jar:1.0-SNAPSHOT: Failed to " + + "collect dependencies at junit:junit:jar:3.8.1: Failed to read artifact descriptor for junit:junit:jar:3.8.1: " + + "The following artifacts could not be resolved: junit:junit:pom:3.8.1 (absent): Could not transfer artifact junit:junit:pom:3.8.1 " + + "from/to artifactory (http://test:8046/artifactory/api/curation/audit/maven-remote):" + tests := []struct { + name string + wantMsgToUser string + input string + }{ + { + name: "failed on 403", + wantMsgToUser: "Please verify pass-through enabled on the curated repos", + input: errPrefix + "status code: 403, reason phrase: Forbidden (403)", + }, + { + name: "failed on 500", + wantMsgToUser: "Please verify pass-through enabled on the curated repos", + input: errPrefix + " status code: 500, reason phrase: Internal Server Error (500)", + }, + { + name: "not 403 or 500", + wantMsgToUser: "", + input: errPrefix + " status code: 400, reason phrase: Forbidden (400)", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mdt := &MavenDepTreeManager{} + assert.Contains(t, tt.wantMsgToUser, mdt.suspectCurationBlockedError(tt.input)) + }) + } +} diff --git a/commands/audit/sca/java/resources/gradle-dep-tree.jar b/commands/audit/sca/java/resources/gradle-dep-tree.jar new file mode 100644 index 0000000000000000000000000000000000000000..b855f258b416de4c7ef9a0d487010afc882de99e GIT binary patch literal 12253 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 literal 0 HcmV?d00001 diff --git a/commands/audit/sca/java/resources/maven-dep-tree.jar b/commands/audit/sca/java/resources/maven-dep-tree.jar new file mode 100644 index 0000000000000000000000000000000000000000..f8d7ff406591d396976cc576c6cf0ce87e75042d GIT binary patch literal 13862 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 + + + + artifactory + %s + %s + + + + + artifactory + %s + * + + + \ No newline at end of file diff --git a/commands/audit/scarunner.go b/commands/audit/scarunner.go index d514d049..881f424e 100644 --- a/commands/audit/scarunner.go +++ b/commands/audit/scarunner.go @@ -13,9 +13,9 @@ import ( "github.com/jfrog/jfrog-cli-core/v2/common/project" "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/java" "github.com/jfrog/jfrog-cli-security/commands/audit/sca" _go "github.com/jfrog/jfrog-cli-security/commands/audit/sca/go" + "github.com/jfrog/jfrog-cli-security/commands/audit/sca/java" "github.com/jfrog/jfrog-cli-security/commands/audit/sca/npm" "github.com/jfrog/jfrog-cli-security/commands/audit/sca/nuget" "github.com/jfrog/jfrog-cli-security/commands/audit/sca/pnpm" 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 new file mode 100644 index 00000000..6d2096c9 --- /dev/null +++ b/tests/testdata/projects/package-managers/maven/maven-example-with-many-types/pom.xml @@ -0,0 +1,53 @@ + + 4.0.0 + + org.jfrog + cli-test + 1.0 + jar + + cli-test + http://maven.apache.org + + + UTF-8 + 1.8 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + ${java.version} + ${java.version} + + + + + + + + junit + junit + 4.11 + test + + + commons-io + commons-io + 1.2 + test + + + org.webjars + lodash + 4.17.21 + pom + + + \ No newline at end of file