Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: missing common maven transitive dependencies #45

Merged
merged 6 commits into from
Sep 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 79 additions & 35 deletions src/providers/java_maven.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
import { getCustomPath } from "../tools.js";
import os from 'node:os'
import path from 'node:path'
import Sbom from '../sbom.js'

Check warning on line 7 in src/providers/java_maven.js

View workflow job for this annotation

GitHub Actions / Lint and test project (18)

Imports should be sorted alphabetically

Check warning on line 7 in src/providers/java_maven.js

View workflow job for this annotation

GitHub Actions / Lint and test project (latest)

Imports should be sorted alphabetically
import {PackageURL} from 'packageurl-js'

Check warning on line 8 in src/providers/java_maven.js

View workflow job for this annotation

GitHub Actions / Lint and test project (18)

Imports should be sorted alphabetically

Check warning on line 8 in src/providers/java_maven.js

View workflow job for this annotation

GitHub Actions / Lint and test project (latest)

Imports should be sorted alphabetically
import {EOL} from 'os'

Check warning on line 9 in src/providers/java_maven.js

View workflow job for this annotation

GitHub Actions / Lint and test project (18)

Imports should be sorted alphabetically

Check warning on line 9 in src/providers/java_maven.js

View workflow job for this annotation

GitHub Actions / Lint and test project (latest)

Imports should be sorted alphabetically

export default { isSupported, provideComponent, provideStack }

Expand Down Expand Up @@ -61,47 +61,91 @@
}

/**
* convert a dog graph dependency into a Package URL Object.
* @param {string} root one dependency from one line of a dot graph
* @return {PackageURL} returns package URL of the artifact
*
* @param {String} dotGraphList Text graph String of the pom.xml manifest
* @param {[String]} ignoredDeps List of ignored dependencies to be omitted from sbom
* @return {String} formatted sbom Json String with all dependencies
* @private
*/
function dotGraphToPurl(root) {
let parts = root.split(":")
let group = parts[0].replaceAll("\"","")
let name = parts[1]
let version = parts[3].replaceAll("\"","")
return new PackageURL('maven',group,name,version,undefined,undefined);
function createSbomFileFromTextFormat(dotGraphList, ignoredDeps) {
let lines = dotGraphList.split(EOL);
// get root component
let root = lines[0];
let rootPurl = parseDep(root);
let sbom = new Sbom();
sbom.addRoot(rootPurl);
parseDependencyTree(root, 0, lines.slice(1), sbom);
return sbom.filterIgnoredDepsIncludingVersion(ignoredDeps).getAsJsonString();
}

const DEP_REGEX = /(?:([-a-zA-Z0-9._]+):([-a-zA-Z0-9._]+):[-a-zA-Z0-9._]+:([-a-zA-Z0-9._]+):[-a-zA-Z]+)/
const ROOT_REGEX = /(?:([-a-zA-Z0-9._]+):([-a-zA-Z0-9._]+):[-a-zA-Z0-9._]+:([-a-zA-Z0-9._]+))/
const CONFLICT_REGEX = /.*- omitted for conflict with (\S+)\)/

/**
* Recursively populates the SBOM instance with the parsed graph
* @param {string} src - Source dependency to start the calculations from
* @param {number} srcDepth - Current depth in the graph for the given source
* @param {Array} lines - Array containing the text files being parsed
* @param {Sbom} sbom - The SBOM where the dependencies are being added
* @private
*/
function parseDependencyTree(src, srcDepth, lines, sbom) {
if(lines.length === 0) {
return;
}
if((lines.length === 1 && lines[0].trim() === "")) {
return;
}
let index = 0;
let target = lines[index];
let targetDepth = getDepth(target);
while(targetDepth > srcDepth && index < lines.length) {
if(targetDepth === srcDepth + 1) {
let from = parseDep(src);
let to = parseDep(target);
sbom.addDependency(sbom.purlToComponent(from), to)
} else {
parseDependencyTree(lines[index-1], getDepth(lines[index-1]), lines.slice(index), sbom)
}
target = lines[++index];
targetDepth = getDepth(target);
}
}

/**
*
* @param {String} dotGraphList Dot Graph tree String of the pom.xml manifest
* @param {[String]} ignoredDeps List of ignored dependencies to be omitted from sbom
* @return {String} formatted sbom Json String with all dependencies
* Calculates how deep in the graph is the given line
* @param {string} line - line to calculate the depth from
* @returns {number} The calculated depth
* @private
*/
function createSbomFileFromDotGraphFormat(dotGraphList, ignoredDeps) {
// get root component
let lines = dotGraphList.replaceAll(";","").split(EOL);
let root = lines[0].split("\"")[1];
let rootPurl = dotGraphToPurl(root);
lines.splice(0,1);
let sbom = new Sbom()
sbom.addRoot(rootPurl)
lines.forEach(pair => {
if(pair.trim() !== "}") {
let thePair = pair.split("->")
if(thePair.length === 2) {
let from = dotGraphToPurl(thePair[0].trim())
let to = dotGraphToPurl(thePair[1].trim())
sbom.addDependency(sbom.purlToComponent(from), to)
}
}
})
return sbom.filterIgnoredDepsIncludingVersion(ignoredDeps).getAsJsonString()
function getDepth(line) {
if(line === undefined) {
return -1;
}
return ((line.indexOf('-') - 1) / 3) + 1;
}

/**
* Create a PackageURL from any line in a Text Graph dependency tree for a manifest path.
* @param {string} line - line to parse from a dependencies.txt file
* @returns {PackageURL} The parsed packageURL
* @private
*/
function parseDep(line) {
let match = line.match(ROOT_REGEX);
if (!match) {
match = line.match(DEP_REGEX);
}
if(!match) {
throw new Error(`Unable generate SBOM from dependency tree. Line: ${line} cannot be parsed into a PackageURL`);
}
let version = match[3];
let override = line.match(CONFLICT_REGEX);
if (override) {
version = override[1];
}
return toPurl(match[1], match[2], version);
}

/**
Expand Down Expand Up @@ -129,8 +173,8 @@
// create dependency graph in a temp file
let tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'exhort_'))
let tmpDepTree = path.join(tmpDir, 'mvn_deptree.txt')
// build initial command
let depTreeCmd = `${mvn} -q dependency:tree -DoutputType=dot -DoutputFile=${tmpDepTree} -f ${manifest}`
// build initial command (dot outputType is not available for verbose mode)
let depTreeCmd = `${mvn} -q org.apache.maven.plugins:maven-dependency-plugin:3.6.0:tree -Dverbose -DoutputType=text -DoutputFile=${tmpDepTree} -f ${manifest}`
// 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)
let ignoredDeps = new Array()
Expand All @@ -148,7 +192,7 @@
})
// read dependency tree from temp file
let content= fs.readFileSync(`${tmpDepTree}`)
let sbom = createSbomFileFromDotGraphFormat(content.toString(),ignoredDeps);
let sbom = createSbomFileFromTextFormat(content.toString(),ignoredDeps);
// delete temp file and directory
fs.rmSync(tmpDir, {recursive: true, force: true})
// return dependency graph as string
Expand Down
9 changes: 5 additions & 4 deletions test/providers/java_maven.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ suite('testing the java-maven data provider', () => {
"pom_deps_with_ignore_on_wrong",
"pom_deps_with_no_ignore",
"poms_deps_with_ignore_long",
"poms_deps_with_no_ignore_long"
"poms_deps_with_no_ignore_long",
"pom_deps_with_no_ignore_common_paths"
].forEach(testCase => {
let scenario = testCase.replace('pom_deps_', '').replaceAll('_', ' ')
// test(`custom adhoc test`, async () => {
Expand Down Expand Up @@ -57,15 +58,15 @@ suite('testing the java-maven data provider', () => {
content: expectedSbom
})
// these test cases takes ~2500-2700 ms each pr >10000 in CI (for the first test-case)
}).timeout(process.env.GITHUB_ACTIONS ? 30000 : 5000)
}).timeout(process.env.GITHUB_ACTIONS ? 40000 : 10000)

test(`verify maven data provided for component analysis with scenario ${scenario}`, async () => {
// load the expected list for the scenario
let expectedSbom = fs.readFileSync(`test/providers/tst_manifests/maven/${testCase}/component_analysis_expected_sbom.json`,).toString().trim()
// read target manifest file
expectedSbom = JSON.stringify(JSON.parse(expectedSbom))
let manifestContent = fs.readFileSync(`test/providers/tst_manifests/maven/${testCase}/pom.xml`).toString()
// invoke sut stack analysis for scenario manifest
// invoke sut component analysis for scenario manifest
let providedDataForStack = await javaMvnProvider.provideComponent(manifestContent)
// verify returned data matches expectation
expect(providedDataForStack).to.deep.equal({
Expand All @@ -78,4 +79,4 @@ suite('testing the java-maven data provider', () => {
// these test cases takes ~1400-2000 ms each pr >10000 in CI (for the first test-case)

})
}).beforeAll(() => clock = sinon.useFakeTimers(new Date(2023,7,7))).afterAll(()=> {clock.restore()});
}).beforeAll(() => clock = sinon.useFakeTimers(new Date('2023-08-07T00:00:00.000Z'))).afterAll(()=> {clock.restore()});
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
{
"bomFormat": "CycloneDX",
"specVersion": "1.4",
"version": 1,
"metadata": {
"timestamp": "2023-08-07T00:00:00.000Z",
"component": {
"group": "pom-with-deps-no-ignore",
"name": "pom-with-dependency-not-ignored-common-paths",
"version": "0.0.1",
"purl": "pkg:maven/pom-with-deps-no-ignore/[email protected]",
"type": "application",
"bom-ref": "pkg:maven/pom-with-deps-no-ignore/[email protected]"
}
},
"components": [
{
"group": "pom-with-deps-no-ignore",
"name": "pom-with-dependency-not-ignored-common-paths",
"version": "0.0.1",
"purl": "pkg:maven/pom-with-deps-no-ignore/[email protected]",
"type": "application",
"bom-ref": "pkg:maven/pom-with-deps-no-ignore/[email protected]"
},
{
"group": "org.springframework.boot",
"name": "spring-boot-starter",
"version": "2.3.5.RELEASE",
"purl": "pkg:maven/org.springframework.boot/[email protected]",
"type": "library",
"bom-ref": "pkg:maven/org.springframework.boot/[email protected]"
},
{
"group": "org.springframework.boot",
"name": "spring-boot-starter-test",
"version": "2.3.5.RELEASE",
"purl": "pkg:maven/org.springframework.boot/[email protected]",
"type": "library",
"bom-ref": "pkg:maven/org.springframework.boot/[email protected]"
},
{
"group": "org.springframework.boot",
"name": "spring-boot-starter-web",
"version": "2.3.5.RELEASE",
"purl": "pkg:maven/org.springframework.boot/[email protected]",
"type": "library",
"bom-ref": "pkg:maven/org.springframework.boot/[email protected]"
},
{
"group": "io.quarkus",
"name": "quarkus-resteasy",
"version": "2.7.7.Final",
"purl": "pkg:maven/io.quarkus/[email protected]",
"type": "library",
"bom-ref": "pkg:maven/io.quarkus/[email protected]"
},
{
"group": "org.keycloak",
"name": "keycloak-saml-core",
"version": "1.8.1.Final",
"purl": "pkg:maven/org.keycloak/[email protected]",
"type": "library",
"bom-ref": "pkg:maven/org.keycloak/[email protected]"
},
{
"group": "io.quarkus",
"name": "quarkus-vertx-http",
"version": "2.13.5.Final",
"purl": "pkg:maven/io.quarkus/[email protected]",
"type": "library",
"bom-ref": "pkg:maven/io.quarkus/[email protected]"
},
{
"group": "io.quarkus",
"name": "quarkus-jdbc-postgresql",
"version": "2.13.6.Final",
"purl": "pkg:maven/io.quarkus/[email protected]",
"type": "library",
"bom-ref": "pkg:maven/io.quarkus/[email protected]"
}
],
"dependencies": [
{
"ref": "pkg:maven/pom-with-deps-no-ignore/[email protected]",
"dependsOn": [
"pkg:maven/org.springframework.boot/[email protected]",
"pkg:maven/org.springframework.boot/[email protected]",
"pkg:maven/org.springframework.boot/[email protected]",
"pkg:maven/io.quarkus/[email protected]",
"pkg:maven/org.keycloak/[email protected]",
"pkg:maven/io.quarkus/[email protected]",
"pkg:maven/io.quarkus/[email protected]"
]
},
{
"ref": "pkg:maven/org.springframework.boot/[email protected]",
"dependsOn": []
},
{
"ref": "pkg:maven/org.springframework.boot/[email protected]",
"dependsOn": []
},
{
"ref": "pkg:maven/org.springframework.boot/[email protected]",
"dependsOn": []
},
{
"ref": "pkg:maven/io.quarkus/[email protected]",
"dependsOn": []
},
{
"ref": "pkg:maven/org.keycloak/[email protected]",
"dependsOn": []
},
{
"ref": "pkg:maven/io.quarkus/[email protected]",
"dependsOn": []
},
{
"ref": "pkg:maven/io.quarkus/[email protected]",
"dependsOn": []
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>pom-with-deps-no-ignore</groupId>
<artifactId>pom-with-dependency-not-ignored-common-paths</artifactId>
<version>0.0.1</version>
<name>pom-with-dependency-not-ignored-common-paths</name>
<description>Demo project for Spring Boot</description>

<properties>
<java.version>17</java.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy</artifactId>
<version>2.7.7.Final</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-saml-core</artifactId>
<version>1.8.1.Final</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-vertx-http</artifactId>
<version>2.13.5.Final</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jdbc-postgresql</artifactId>
<version>2.13.6.Final</version>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>
Loading
Loading