diff --git a/.gitignore b/.gitignore index 7124e07..2eff155 100644 --- a/.gitignore +++ b/.gitignore @@ -2,5 +2,7 @@ coverage node_modules dist +http_requests +json_responses integration/**/package-lock.json unit-tests-result.json diff --git a/integration/expected_component b/integration/expected_component new file mode 100644 index 0000000..3a6fb32 --- /dev/null +++ b/integration/expected_component @@ -0,0 +1,195 @@ +[ + { + "ref": { + "name": "log4j:log4j", + "version": "1.2.17" + }, + "issues": [ + { + "id": "SNYK-JAVA-LOG4J-572732", + "source": "snyk", + "cves": [ + "CVE-2019-17571" + ], + "rawData": { + "id": "SNYK-JAVA-LOG4J-572732", + "title": "Deserialization of Untrusted Data", + "CVSSv3": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H/E:P", + "cvssScore": 9.8 + } + }, + { + "id": "SNYK-JAVA-LOG4J-572732", + "source": "snyk", + "cves": [ + "CVE-2019-17571" + ], + "rawData": { + "id": "SNYK-JAVA-LOG4J-572732", + "title": "Deserialization of Untrusted Data", + "CVSSv3": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H/E:P", + "cvssScore": 9.8 + } + }, + { + "id": "SNYK-JAVA-LOG4J-2342645", + "source": "snyk", + "cves": [ + "CVE-2022-23305" + ], + "rawData": { + "id": "SNYK-JAVA-LOG4J-2342645", + "title": "SQL Injection", + "CVSSv3": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H", + "cvssScore": 8.1 + } + }, + { + "id": "SNYK-JAVA-LOG4J-2342646", + "source": "snyk", + "cves": [ + "CVE-2022-23307" + ], + "rawData": { + "id": "SNYK-JAVA-LOG4J-2342646", + "title": "Deserialization of Untrusted Data", + "CVSSv3": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H", + "cvssScore": 8.1 + } + }, + { + "id": "SNYK-JAVA-LOG4J-2342647", + "source": "snyk", + "cves": [ + "CVE-2022-23302" + ], + "rawData": { + "id": "SNYK-JAVA-LOG4J-2342647", + "title": "Deserialization of Untrusted Data", + "CVSSv3": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H", + "cvssScore": 8.1 + } + }, + { + "id": "SNYK-JAVA-LOG4J-2342645", + "source": "snyk", + "cves": [ + "CVE-2022-23305" + ], + "rawData": { + "id": "SNYK-JAVA-LOG4J-2342645", + "title": "SQL Injection", + "CVSSv3": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H", + "cvssScore": 8.1 + } + }, + { + "id": "SNYK-JAVA-LOG4J-2342646", + "source": "snyk", + "cves": [ + "CVE-2022-23307" + ], + "rawData": { + "id": "SNYK-JAVA-LOG4J-2342646", + "title": "Deserialization of Untrusted Data", + "CVSSv3": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H", + "cvssScore": 8.1 + } + }, + { + "id": "SNYK-JAVA-LOG4J-2342647", + "source": "snyk", + "cves": [ + "CVE-2022-23302" + ], + "rawData": { + "id": "SNYK-JAVA-LOG4J-2342647", + "title": "Deserialization of Untrusted Data", + "CVSSv3": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H", + "cvssScore": 8.1 + } + }, + { + "id": "SNYK-JAVA-LOG4J-2316893", + "source": "snyk", + "cves": [ + "CVE-2021-4104" + ], + "rawData": { + "id": "SNYK-JAVA-LOG4J-2316893", + "title": "Arbitrary Code Execution", + "CVSSv3": "CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:U/C:H/I:H/A:H/E:P", + "cvssScore": 6.6 + } + }, + { + "id": "SNYK-JAVA-LOG4J-2316893", + "source": "snyk", + "cves": [ + "CVE-2021-4104" + ], + "rawData": { + "id": "SNYK-JAVA-LOG4J-2316893", + "title": "Arbitrary Code Execution", + "CVSSv3": "CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:U/C:H/I:H/A:H/E:P", + "cvssScore": 6.6 + } + }, + { + "id": "SNYK-JAVA-LOG4J-3358774", + "source": "snyk", + "cves": [ + "CVE-2023-26464" + ], + "rawData": { + "id": "SNYK-JAVA-LOG4J-3358774", + "title": "Denial of Service (DoS)", + "CVSSv3": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H", + "cvssScore": 5.9 + } + }, + { + "id": "SNYK-JAVA-LOG4J-3358774", + "source": "snyk", + "cves": [ + "CVE-2023-26464" + ], + "rawData": { + "id": "SNYK-JAVA-LOG4J-3358774", + "title": "Denial of Service (DoS)", + "CVSSv3": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H", + "cvssScore": 5.9 + } + }, + { + "id": "SNYK-JAVA-LOG4J-1300176", + "source": "snyk", + "cves": [ + "CVE-2020-9488" + ], + "rawData": { + "id": "SNYK-JAVA-LOG4J-1300176", + "title": "Man-in-the-Middle (MitM)", + "CVSSv3": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N", + "cvssScore": 3.7 + } + }, + { + "id": "SNYK-JAVA-LOG4J-1300176", + "source": "snyk", + "cves": [ + "CVE-2020-9488" + ], + "rawData": { + "id": "SNYK-JAVA-LOG4J-1300176", + "title": "Man-in-the-Middle (MitM)", + "CVSSv3": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N", + "cvssScore": 3.7 + } + } + ], + "transitive": [], + "securityRecommendations": {}, + "recommendation": null + } +] diff --git a/integration/run_it.sh b/integration/run_it.sh index 8b1eba0..90dbed4 100755 --- a/integration/run_it.sh +++ b/integration/run_it.sh @@ -31,7 +31,8 @@ echo "- SUCCESSFUL" ###### JavaScript Integration Tests ###### ########################################## echo "PREPARING JavaScript integration tests environment" -if ! npm --prefix javascript install --force --silent +rm -rf javascript/node_modules +if ! npm --prefix javascript install --silent then echo "- FAILED Installing modules for JS environment" exit $? @@ -44,17 +45,22 @@ match "expected_stack_html" "node javascript/index.js stack pom.xml true" echo "RUNNING JavaScript integration test for Stack Analysis report in Json" match "expected_stack_json" "node javascript/index.js stack pom.xml false" +echo "RUNNING JavaScript integration test for Component Analysis report" +match "expected_component" "node javascript/index.js component pom.xml '$( /dev/null 2>&1 then echo "- FAILED Compiling TS module" @@ -67,11 +73,15 @@ match "expected_stack_html" "node typescript/dist/index.js stack pom.xml true" echo "RUNNING TypeScript integration test for Stack Analysis report in Json" match "expected_stack_json" "node typescript/dist/index.js stack pom.xml false" +echo "RUNNING TypeScript integration test for Component Analysis report" +match "expected_component" "node typescript/dist/index.js component pom.xml '$( { let manifestName = args['manifest-name'] let manifestContent = args['manifest-content'] - let summary = args['summary'] let res = await crda.componentAnalysis(manifestName, manifestContent) - console.log(JSON.stringify( - summary ? res['summary'] : res, - null, - 2)) + console.log(JSON.stringify(res, null, 2)) } } diff --git a/src/providers/java_maven.js b/src/providers/java_maven.js index cd55b14..85e00e7 100644 --- a/src/providers/java_maven.js +++ b/src/providers/java_maven.js @@ -10,6 +10,10 @@ export default { isSupported, provideComponent, provideStack } /** @typedef {import('../provider').Provided} Provided */ +/** @typedef {{name: string, version: string}} Package */ + +/** @typedef {{groupId: string, artifactId: string, version: string, scope: string, ignore: boolean}} Dependency */ + /** * @type {string} ecosystem for java-maven is 'maven' * @private @@ -42,16 +46,16 @@ function provideStack(manifest) { * @param {string} data - content of pom.xml for component report * @returns {Provided} */ -function provideComponent(data) { // eslint-disable-line +function provideComponent(data) { return { ecosystem, - content: 'WIP', - contentType: 'WIP' + content: JSON.stringify(getList(data)), + contentType: 'application/json' } } /** - * Create a Dot Graph dependency tree for maven. + * Create a Dot Graph dependency tree for a manifest path. * @param {string} manifest - path for pom.xml * @returns {string} the Dot Graph content * @private @@ -74,8 +78,13 @@ function getGraph(manifest) { let tmpDepTree = path.join(tmpDir, 'mvn_deptree.txt') // build initial command let depTreeCmd = `mvn -q dependency:tree -DoutputType=dot -DoutputFile=${tmpDepTree} -f ${manifest}` - // exclude ignored dependencies - getIgnored(manifest).forEach(ignoredDep => depTreeCmd += ` -Dexcludes=${ignoredDep}`) + // exclude ignored dependencies, exclude format is groupId:artifactId:scope:version. + // version and scope are marked as '*' if not specified (we do not use scope yet) + getDependencies(manifest).forEach(dep => { + if (dep.ignore) { + depTreeCmd += ` -Dexcludes=${dep['groupId']}:${dep['artifactId']}:${dep['scope']}:${dep['version']}` + } + }) // execute dependency tree command execSync(depTreeCmd, err => { if (err) { @@ -91,13 +100,49 @@ function getGraph(manifest) { } /** - * Get a list of dependencies marked with . + * Create a dependency list for a manifest content. + * @param {string} data - content of pom.xml + * @returns {[Package]} the Dot Graph content + * @private + */ +function getList(data) { + // verify maven is accessible + execSync('mvn --version', err => { + if (err) { + throw new Error('mvn is not accessible') + } + }) + // create temp files for pom and effective pom + let tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'crda_')) + let tmpEffectivePom = path.join(tmpDir, 'effective-pom.xml') + let tmpTargetPom = path.join(tmpDir, 'target-pom.xml') + // write target pom content to temp file + fs.writeFileSync(tmpTargetPom, data) + // create effective pom and save to temp file + execSync(`mvn -q help:effective-pom -Doutput=${tmpEffectivePom} -f ${tmpTargetPom}`, err => { + if (err) { + throw new Error('failed creating maven effective pom') + } + }) + // iterate over all dependencies and create a package for every non-ignored one + /** @type [Package] */ + let packages = getDependencies(tmpEffectivePom) + .filter(dep => !dep.ignore) + .map(dep => { return {name: `${dep.groupId}:${dep.artifactId}`, version: dep.version} }) + // delete temp files and directory + fs.rmSync(tmpDir, {recursive: true, force: true}) + // return packages list + return packages +} + +/** + * Get a list of dependencies with marking of dependencies commented with . * @param {string} manifest - path for pom.xml - * @returns {string[]} an array of dependencies to be ignored, group-id:artifact-id:*:version - * (the * marks any type, if no version specified, * will be used) + * @returns {[Dependency]} an array of dependencies * @private */ -function getIgnored(manifest) { +function getDependencies(manifest) { + /** @type [Dependency] */ let ignored = [] // build xml parser with options let parser = new XMLParser({ @@ -110,10 +155,18 @@ function getIgnored(manifest) { let pomJson = parser.parse(buf.toString()) // iterate over all dependencies and chery pick dependencies with a crdaignore comment pomJson['project']['dependencies']['dependency'].forEach(dep => { + let ignore = false if (dep['#comment'] && dep['#comment'].includes('crdaignore')) { // #comment is an array or a string - ignored.push(`${dep['groupId']}:${dep['artifactId']}:*:${dep['version'] ? dep['version'] : '*'}`) + ignore = true } + ignored.push({ + groupId: dep['groupId'], + artifactId: dep['artifactId'], + version: dep['version'] ? dep['version'] : '*', + scope: '*', + ignore: ignore + }) }) - // return list of dependencies to ignore + // return list of dependencies return ignored } diff --git a/test/providers/java_maven.test.js b/test/providers/java_maven.test.js index ed96392..247c85b 100644 --- a/test/providers/java_maven.test.js +++ b/test/providers/java_maven.test.js @@ -21,6 +21,7 @@ suite('testing the java-maven data provider', () => { "pom_deps_with_no_ignore", ].forEach(testCase => { let scenario = testCase.replace('pom_deps_', '').replaceAll('_', ' ') + test(`verify maven data provided for stack analysis with scenario ${scenario}`, async () => { // load the expected graph for the scenario let expectedGraph = fs.readFileSync(`test/providers/tst_manifests/${testCase}/expected_dot_graph`,).toString() @@ -34,5 +35,21 @@ suite('testing the java-maven data provider', () => { }) // these test cases takes ~2500-2700 ms each pr >10000 in CI (for the first test-case) }).timeout(process.env.GITHUB_ACTIONS ? 25000 : 5000) + + test(`verify maven data provided for component analysis with scenario ${scenario}`, async () => { + // load the expected list for the scenario + let expectedList = fs.readFileSync(`test/providers/tst_manifests/${testCase}/component_expected_list`,).toString() + // read target manifest file + let manifestContent = fs.readFileSync(`test/providers/tst_manifests/${testCase}/pom.xml`).toString() + // invoke sut stack analysis for scenario manifest + let providedDataForStack = await javaMvnProvider.provideComponent(manifestContent) + // verify returned data matches expectation + expect(providedDataForStack).to.deep.equal({ + ecosystem: 'maven', + contentType: 'application/json', + content: expectedList + }) + // these test cases takes ~1400-2000 ms each pr >10000 in CI (for the first test-case) + }).timeout(process.env.GITHUB_ACTIONS ? 15000 : 2500) }) }) diff --git a/test/providers/tst_manifests/pom_deps_with_ignore_on_artifact/component_expected_list b/test/providers/tst_manifests/pom_deps_with_ignore_on_artifact/component_expected_list new file mode 100644 index 0000000..ab35294 --- /dev/null +++ b/test/providers/tst_manifests/pom_deps_with_ignore_on_artifact/component_expected_list @@ -0,0 +1 @@ +[{"name":"log4j:log4j","version":"1.2.17"}] \ No newline at end of file diff --git a/test/providers/tst_manifests/pom_deps_with_ignore_on_dependency/component_expected_list b/test/providers/tst_manifests/pom_deps_with_ignore_on_dependency/component_expected_list new file mode 100644 index 0000000..ab35294 --- /dev/null +++ b/test/providers/tst_manifests/pom_deps_with_ignore_on_dependency/component_expected_list @@ -0,0 +1 @@ +[{"name":"log4j:log4j","version":"1.2.17"}] \ No newline at end of file diff --git a/test/providers/tst_manifests/pom_deps_with_ignore_on_group/component_expected_list b/test/providers/tst_manifests/pom_deps_with_ignore_on_group/component_expected_list new file mode 100644 index 0000000..ab35294 --- /dev/null +++ b/test/providers/tst_manifests/pom_deps_with_ignore_on_group/component_expected_list @@ -0,0 +1 @@ +[{"name":"log4j:log4j","version":"1.2.17"}] \ No newline at end of file diff --git a/test/providers/tst_manifests/pom_deps_with_ignore_on_version/component_expected_list b/test/providers/tst_manifests/pom_deps_with_ignore_on_version/component_expected_list new file mode 100644 index 0000000..ab35294 --- /dev/null +++ b/test/providers/tst_manifests/pom_deps_with_ignore_on_version/component_expected_list @@ -0,0 +1 @@ +[{"name":"log4j:log4j","version":"1.2.17"}] \ No newline at end of file diff --git a/test/providers/tst_manifests/pom_deps_with_ignore_on_wrong/component_expected_list b/test/providers/tst_manifests/pom_deps_with_ignore_on_wrong/component_expected_list new file mode 100644 index 0000000..ab35294 --- /dev/null +++ b/test/providers/tst_manifests/pom_deps_with_ignore_on_wrong/component_expected_list @@ -0,0 +1 @@ +[{"name":"log4j:log4j","version":"1.2.17"}] \ No newline at end of file diff --git a/test/providers/tst_manifests/pom_deps_with_no_ignore/component_expected_list b/test/providers/tst_manifests/pom_deps_with_no_ignore/component_expected_list new file mode 100644 index 0000000..ab35294 --- /dev/null +++ b/test/providers/tst_manifests/pom_deps_with_no_ignore/component_expected_list @@ -0,0 +1 @@ +[{"name":"log4j:log4j","version":"1.2.17"}] \ No newline at end of file diff --git a/test/providers/tst_manifests/pom_empty/pom.xml b/test/providers/tst_manifests/pom_empty/pom.xml deleted file mode 100644 index 722e346..0000000 --- a/test/providers/tst_manifests/pom_empty/pom.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - 4.0.0 - - empty-pom-xml-for-tests - -