Skip to content

Commit 5b796b8

Browse files
authored
feat: [CN-280] Add parameter to support usr/lib jar files (#696)
1 parent 2b75d7e commit 5b796b8

6 files changed

Lines changed: 247 additions & 4 deletions

File tree

lib/analyzer/image-inspector.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@ async function pullWithDockerBinary(
5959
return true;
6060
} catch (err) {
6161
debug(`couldn't pull ${targetImage} using docker binary: ${err.message}`);
62-
handleDockerPullError(err.stderr, platform);
62+
const errorMessage = err.stderr || err.message || err.toString();
63+
handleDockerPullError(errorMessage, platform);
6364

6465
return false;
6566
}

lib/analyzer/static-analyzer.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,10 @@ import {
2525
getDpkgPackageFileContentAction,
2626
} from "../inputs/distroless/static";
2727
import * as filePatternStatic from "../inputs/file-pattern/static";
28-
import { getJarFileContentAction } from "../inputs/java/static";
28+
import {
29+
getJarFileContentAction,
30+
getUsrLibJarFileContentAction,
31+
} from "../inputs/java/static";
2932
import {
3033
getNodeAppFileContentAction,
3134
getNodeJsTsAppFileContentAction,
@@ -116,13 +119,20 @@ export async function analyze(
116119
const collectApplicationFiles = isTrue(options["collect-application-files"]);
117120

118121
if (appScan) {
122+
const jarActions = [getJarFileContentAction];
123+
124+
// Include system JARs from /usr/lib if flag is enabled
125+
if (isTrue(options["include-system-jars"])) {
126+
jarActions.push(getUsrLibJarFileContentAction);
127+
}
128+
119129
staticAnalysisActions.push(
120130
...[
121131
getNodeAppFileContentAction,
122132
getPhpAppFileContentAction,
123133
getPoetryAppFileContentAction,
124134
getPipAppFileContentAction,
125-
getJarFileContentAction,
135+
...jarActions,
126136
getGoModulesContentAction,
127137
],
128138
);

lib/inputs/java/static.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ import * as path from "path";
22
import { ExtractAction } from "../../extractor/types";
33
import { streamToBuffer } from "../../stream-utils";
44

5-
const ignoredPaths = ["/usr/lib", "gradle/cache"];
5+
const usrLibPath = "/usr/lib";
6+
const ignoredPaths = [usrLibPath, "gradle/cache"];
67
const javaArchiveFileFormats = [".jar", ".war"];
78

89
function filePathMatches(filePath: string): boolean {
@@ -21,3 +22,18 @@ export const getJarFileContentAction: ExtractAction = {
2122
filePathMatches,
2223
callback: streamToBuffer,
2324
};
25+
26+
function usrLibFilePathMatches(filePath: string): boolean {
27+
const dirName = path.dirname(filePath);
28+
const fileExtension = filePath.slice(-4);
29+
return (
30+
javaArchiveFileFormats.includes(fileExtension) &&
31+
dirName.includes(path.normalize(usrLibPath))
32+
);
33+
}
34+
35+
export const getUsrLibJarFileContentAction: ExtractAction = {
36+
actionName: "jar",
37+
filePathMatches: usrLibFilePathMatches,
38+
callback: streamToBuffer,
39+
};

lib/types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,10 @@ export interface PluginOptions {
227227

228228
/** The default is "false". */
229229
"collect-application-files": boolean | string;
230+
231+
/** Include system-level JARs and WARs from /usr/lib in scan results. The default is "false". */
232+
"include-system-jars": boolean | string;
233+
230234
"target-reference": string;
231235
}
232236

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import {
2+
getJarFileContentAction,
3+
getUsrLibJarFileContentAction,
4+
} from "../../../lib/inputs/java/static";
5+
6+
describe("Java static input", () => {
7+
describe("getJarFileContentAction", () => {
8+
it("should exclude /usr/lib JARs by default", () => {
9+
expect(getJarFileContentAction.actionName).toBe("jar");
10+
expect(getJarFileContentAction.filePathMatches).toBeDefined();
11+
12+
// Test that /usr/lib JARs are excluded
13+
const usrLibJar = "/usr/lib/java/some-lib.jar";
14+
expect(getJarFileContentAction.filePathMatches!(usrLibJar)).toBe(false);
15+
16+
// Test that gradle cache is still excluded
17+
const gradleCacheJar = "/gradle/cache/some-lib.jar";
18+
expect(getJarFileContentAction.filePathMatches!(gradleCacheJar)).toBe(
19+
false,
20+
);
21+
22+
// Test regular JARs are included
23+
const regularJar = "/app/lib/app.jar";
24+
expect(getJarFileContentAction.filePathMatches!(regularJar)).toBe(true);
25+
});
26+
27+
it("should only match JAR and WAR files", () => {
28+
// Test JAR files
29+
expect(getJarFileContentAction.filePathMatches!("/app/lib/app.jar")).toBe(
30+
true,
31+
);
32+
33+
// Test WAR files
34+
expect(getJarFileContentAction.filePathMatches!("/app/lib/app.war")).toBe(
35+
true,
36+
);
37+
38+
// Test non-archive files
39+
expect(getJarFileContentAction.filePathMatches!("/app/lib/app.txt")).toBe(
40+
false,
41+
);
42+
expect(
43+
getJarFileContentAction.filePathMatches!("/app/lib/app.class"),
44+
).toBe(false);
45+
expect(getJarFileContentAction.filePathMatches!("/app/lib/app.xml")).toBe(
46+
false,
47+
);
48+
});
49+
50+
it("should exclude gradle cache paths", () => {
51+
const gradleCacheJar =
52+
"/home/user/.gradle/cache/modules-2/files-2.1/some-lib.jar";
53+
expect(getJarFileContentAction.filePathMatches!(gradleCacheJar)).toBe(
54+
false,
55+
);
56+
});
57+
});
58+
59+
describe("getUsrLibJarFileContentAction", () => {
60+
it("should only include JARs from /usr/lib", () => {
61+
expect(getUsrLibJarFileContentAction.actionName).toBe("jar");
62+
expect(getUsrLibJarFileContentAction.filePathMatches).toBeDefined();
63+
64+
// Test that /usr/lib JARs are included
65+
const usrLibJar = "/usr/lib/java/some-lib.jar";
66+
expect(getUsrLibJarFileContentAction.filePathMatches!(usrLibJar)).toBe(
67+
true,
68+
);
69+
70+
// Test that non-/usr/lib JARs are excluded
71+
const regularJar = "/app/lib/app.jar";
72+
expect(getUsrLibJarFileContentAction.filePathMatches!(regularJar)).toBe(
73+
false,
74+
);
75+
76+
// Test that gradle cache is excluded (even though it wouldn't contain /usr/lib)
77+
const gradleCacheJar = "/gradle/cache/some-lib.jar";
78+
expect(
79+
getUsrLibJarFileContentAction.filePathMatches!(gradleCacheJar),
80+
).toBe(false);
81+
});
82+
83+
it("should only match JAR and WAR files in /usr/lib", () => {
84+
// Test JAR files in /usr/lib
85+
expect(
86+
getUsrLibJarFileContentAction.filePathMatches!("/usr/lib/app.jar"),
87+
).toBe(true);
88+
89+
// Test WAR files in /usr/lib
90+
expect(
91+
getUsrLibJarFileContentAction.filePathMatches!("/usr/lib/app.war"),
92+
).toBe(true);
93+
94+
// Test non-archive files in /usr/lib
95+
expect(
96+
getUsrLibJarFileContentAction.filePathMatches!("/usr/lib/app.txt"),
97+
).toBe(false);
98+
expect(
99+
getUsrLibJarFileContentAction.filePathMatches!("/usr/lib/app.class"),
100+
).toBe(false);
101+
expect(
102+
getUsrLibJarFileContentAction.filePathMatches!("/usr/lib/app.xml"),
103+
).toBe(false);
104+
});
105+
106+
it("should match nested paths within /usr/lib", () => {
107+
const nestedUsrLibJar = "/usr/lib/java/jvm/lib/some-lib.jar";
108+
expect(
109+
getUsrLibJarFileContentAction.filePathMatches!(nestedUsrLibJar),
110+
).toBe(true);
111+
});
112+
});
113+
});
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import { scan } from "../../../lib";
2+
import { getFixture } from "../../util";
3+
4+
describe("include-system-jars flag", () => {
5+
it("excludes system JARs by default", async () => {
6+
const fixturePath = getFixture("docker-archives/docker-save/java.tar");
7+
const imageNameAndTag = `docker-archive:${fixturePath}`;
8+
9+
const pluginResult = await scan({
10+
path: imageNameAndTag,
11+
});
12+
13+
expect(pluginResult.scanResults).toBeDefined();
14+
15+
// Verify that no system JARs from /usr/lib are included
16+
const javaResults = pluginResult.scanResults.filter(
17+
(result) => result.identity.type === "maven",
18+
);
19+
20+
for (const result of javaResults) {
21+
if (result.identity?.targetFile) {
22+
expect(result.identity.targetFile).not.toContain("/usr/lib");
23+
}
24+
}
25+
});
26+
27+
it("includes system JARs when --include-system-jars is true", async () => {
28+
const fixturePath = getFixture("docker-archives/docker-save/java.tar");
29+
const imageNameAndTag = `docker-archive:${fixturePath}`;
30+
31+
const pluginResult = await scan({
32+
path: imageNameAndTag,
33+
"include-system-jars": true,
34+
});
35+
36+
// Verify the scan runs successfully and that the option is properly processed
37+
expect(pluginResult.scanResults).toBeDefined();
38+
39+
// Since the test fixture doesn't contain /usr/lib JARs, we verify the flag is processed
40+
// by checking that Maven results are still present (confirms our logic didn't break anything)
41+
const javaResults = pluginResult.scanResults.filter(
42+
(result) => result.identity.type === "maven",
43+
);
44+
expect(javaResults.length).toBeGreaterThan(0);
45+
});
46+
47+
it("excludes system JARs when --include-system-jars is false", async () => {
48+
const fixturePath = getFixture("docker-archives/docker-save/java.tar");
49+
const imageNameAndTag = `docker-archive:${fixturePath}`;
50+
51+
const pluginResult = await scan({
52+
path: imageNameAndTag,
53+
"include-system-jars": false,
54+
});
55+
56+
expect(pluginResult.scanResults).toBeDefined();
57+
58+
// Verify that no system JARs from /usr/lib are included
59+
const javaResults = pluginResult.scanResults.filter(
60+
(result) => result.identity.type === "maven",
61+
);
62+
63+
for (const result of javaResults) {
64+
if (result.identity?.targetFile) {
65+
expect(result.identity.targetFile).not.toContain("/usr/lib");
66+
}
67+
}
68+
});
69+
70+
it("handles string values for include-system-jars flag", async () => {
71+
const fixturePath = getFixture("docker-archives/docker-save/java.tar");
72+
const imageNameAndTag = `docker-archive:${fixturePath}`;
73+
74+
const pluginResultTrue = await scan({
75+
path: imageNameAndTag,
76+
"include-system-jars": "true",
77+
});
78+
79+
const pluginResultFalse = await scan({
80+
path: imageNameAndTag,
81+
"include-system-jars": "false",
82+
});
83+
84+
// Both should run successfully with proper flag handling
85+
expect(pluginResultTrue.scanResults).toBeDefined();
86+
expect(pluginResultFalse.scanResults).toBeDefined();
87+
88+
// Verify both produce Maven scan results
89+
const javaResultsTrue = pluginResultTrue.scanResults.filter(
90+
(result) => result.identity.type === "maven",
91+
);
92+
const javaResultsFalse = pluginResultFalse.scanResults.filter(
93+
(result) => result.identity.type === "maven",
94+
);
95+
96+
expect(javaResultsTrue.length).toBeGreaterThan(0);
97+
expect(javaResultsFalse.length).toBeGreaterThan(0);
98+
});
99+
});

0 commit comments

Comments
 (0)