@cyclonedx/cdxgen 9.9.4 → 9.9.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/analyzer.js +1 -1
- package/docker.js +251 -26
- package/docker.test.js +9 -0
- package/index.js +32 -14
- package/package.json +2 -2
- package/server.js +1 -1
- package/utils.js +130 -58
package/README.md
CHANGED
|
@@ -445,7 +445,7 @@ This feature is powered by osquery, which is [installed](https://github.com/cycl
|
|
|
445
445
|
|
|
446
446
|
See [evinse mode](./ADVANCED.md) in the advanced documentation.
|
|
447
447
|
|
|
448
|
-
##
|
|
448
|
+
## BOM signing
|
|
449
449
|
|
|
450
450
|
cdxgen can sign the generated BOM json file to increase authenticity and non-repudiation capabilities. To enable this, set the following environment variables.
|
|
451
451
|
|
package/analyzer.js
CHANGED
|
@@ -108,7 +108,7 @@ const setFileRef = (allImports, src, file, pathnode, specifiers = []) => {
|
|
|
108
108
|
}
|
|
109
109
|
const fileRelativeLoc = relative(src, file);
|
|
110
110
|
// remove unexpected extension imports
|
|
111
|
-
if (/\.(svg|png|jpg|d\.ts)/.test(pathway)) {
|
|
111
|
+
if (/\.(svg|png|jpg|json|d\.ts)/.test(pathway)) {
|
|
112
112
|
return;
|
|
113
113
|
}
|
|
114
114
|
const importedModules = specifiers
|
package/docker.js
CHANGED
|
@@ -30,9 +30,25 @@ let dockerConn = undefined;
|
|
|
30
30
|
let isPodman = false;
|
|
31
31
|
let isPodmanRootless = true;
|
|
32
32
|
let isDockerRootless = false;
|
|
33
|
+
// https://github.com/containerd/containerd
|
|
34
|
+
let isContainerd = !!process.env.CONTAINERD_ADDRESS;
|
|
33
35
|
const WIN_LOCAL_TLS = "http://localhost:2375";
|
|
34
36
|
let isWinLocalTLS = false;
|
|
35
37
|
|
|
38
|
+
if (
|
|
39
|
+
!process.env.DOCKER_HOST &&
|
|
40
|
+
(process.env.CONTAINERD_ADDRESS ||
|
|
41
|
+
(process.env.XDG_RUNTIME_DIR &&
|
|
42
|
+
existsSync(
|
|
43
|
+
join(process.env.XDG_RUNTIME_DIR, "containerd-rootless", "api.sock")
|
|
44
|
+
)))
|
|
45
|
+
) {
|
|
46
|
+
isContainerd = true;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Cache the registry auth keys
|
|
50
|
+
const registry_auth_keys = {};
|
|
51
|
+
|
|
36
52
|
/**
|
|
37
53
|
* Method to get all dirs matching a name
|
|
38
54
|
*
|
|
@@ -94,7 +110,17 @@ export const getOnlyDirs = (srcpath, dirName) => {
|
|
|
94
110
|
].filter((d) => d.endsWith(dirName));
|
|
95
111
|
};
|
|
96
112
|
|
|
97
|
-
const getDefaultOptions = () => {
|
|
113
|
+
const getDefaultOptions = (forRegistry) => {
|
|
114
|
+
let authTokenSet = false;
|
|
115
|
+
if (!forRegistry && process.env.DOCKER_SERVER_ADDRESS) {
|
|
116
|
+
forRegistry = process.env.DOCKER_SERVER_ADDRESS;
|
|
117
|
+
}
|
|
118
|
+
if (forRegistry) {
|
|
119
|
+
forRegistry = forRegistry.replace("http://", "").replace("https://", "");
|
|
120
|
+
if (forRegistry.includes("/")) {
|
|
121
|
+
forRegistry = forRegistry.split("/")[0];
|
|
122
|
+
}
|
|
123
|
+
}
|
|
98
124
|
const opts = {
|
|
99
125
|
enableUnixSockets: true,
|
|
100
126
|
throwHttpErrors: true,
|
|
@@ -102,6 +128,97 @@ const getDefaultOptions = () => {
|
|
|
102
128
|
hooks: { beforeError: [] },
|
|
103
129
|
mutableDefaults: true
|
|
104
130
|
};
|
|
131
|
+
const DOCKER_CONFIG = process.env.DOCKER_CONFIG || join(homedir(), ".docker");
|
|
132
|
+
// Support for private registry
|
|
133
|
+
if (process.env.DOCKER_AUTH_CONFIG) {
|
|
134
|
+
opts.headers = {
|
|
135
|
+
"X-Registry-Auth": process.env.DOCKER_AUTH_CONFIG
|
|
136
|
+
};
|
|
137
|
+
authTokenSet = true;
|
|
138
|
+
}
|
|
139
|
+
if (
|
|
140
|
+
!authTokenSet &&
|
|
141
|
+
process.env.DOCKER_USER &&
|
|
142
|
+
process.env.DOCKER_PASSWORD &&
|
|
143
|
+
process.env.DOCKER_EMAIL &&
|
|
144
|
+
forRegistry
|
|
145
|
+
) {
|
|
146
|
+
const authPayload = {
|
|
147
|
+
username: process.env.DOCKER_USER,
|
|
148
|
+
email: process.env.DOCKER_EMAIL,
|
|
149
|
+
serveraddress: forRegistry
|
|
150
|
+
};
|
|
151
|
+
if (process.env.DOCKER_USER === "<token>") {
|
|
152
|
+
authPayload.IdentityToken = process.env.DOCKER_PASSWORD;
|
|
153
|
+
} else {
|
|
154
|
+
authPayload.password = process.env.DOCKER_PASSWORD;
|
|
155
|
+
}
|
|
156
|
+
opts.headers = {
|
|
157
|
+
"X-Registry-Auth": Buffer.from(JSON.stringify(authPayload)).toString(
|
|
158
|
+
"base64"
|
|
159
|
+
)
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
if (!authTokenSet && existsSync(join(DOCKER_CONFIG, "config.json"))) {
|
|
163
|
+
const configData = readFileSync(
|
|
164
|
+
join(DOCKER_CONFIG, "config.json"),
|
|
165
|
+
"utf-8"
|
|
166
|
+
);
|
|
167
|
+
if (configData) {
|
|
168
|
+
try {
|
|
169
|
+
const configJson = JSON.parse(configData);
|
|
170
|
+
if (configJson.auths) {
|
|
171
|
+
// Check if there are hardcoded tokens
|
|
172
|
+
for (const serverAddress of Object.keys(configJson.auths)) {
|
|
173
|
+
if (forRegistry && !serverAddress.includes(forRegistry)) {
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
if (configJson.auths[serverAddress].auth) {
|
|
177
|
+
opts.headers = {
|
|
178
|
+
"X-Registry-Auth": configJson.auths[serverAddress].auth
|
|
179
|
+
};
|
|
180
|
+
authTokenSet = true;
|
|
181
|
+
break;
|
|
182
|
+
} else if (configJson.credsStore) {
|
|
183
|
+
const helperAuthToken = getCredsFromHelper(
|
|
184
|
+
configJson.credsStore,
|
|
185
|
+
serverAddress
|
|
186
|
+
);
|
|
187
|
+
if (helperAuthToken) {
|
|
188
|
+
opts.headers = {
|
|
189
|
+
"X-Registry-Auth": helperAuthToken
|
|
190
|
+
};
|
|
191
|
+
authTokenSet = true;
|
|
192
|
+
break;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
} else if (configJson.credHelpers) {
|
|
197
|
+
// Support for credential helpers
|
|
198
|
+
for (const serverAddress of Object.keys(configJson.credHelpers)) {
|
|
199
|
+
if (forRegistry && !serverAddress.includes(forRegistry)) {
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
if (configJson.credHelpers[serverAddress]) {
|
|
203
|
+
const helperAuthToken = getCredsFromHelper(
|
|
204
|
+
configJson.credHelpers[serverAddress],
|
|
205
|
+
serverAddress
|
|
206
|
+
);
|
|
207
|
+
if (helperAuthToken) {
|
|
208
|
+
opts.headers = {
|
|
209
|
+
"X-Registry-Auth": helperAuthToken
|
|
210
|
+
};
|
|
211
|
+
authTokenSet = true;
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
} catch (err) {
|
|
218
|
+
// pass
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
105
222
|
const userInfo = _userInfo();
|
|
106
223
|
opts.podmanPrefixUrl = isWin ? "" : `http://unix:/run/podman/podman.sock:`;
|
|
107
224
|
opts.podmanRootlessPrefixUrl = isWin
|
|
@@ -148,22 +265,34 @@ const getDefaultOptions = () => {
|
|
|
148
265
|
),
|
|
149
266
|
key: readFileSync(join(process.env.DOCKER_CERT_PATH, "key.pem"), "utf8")
|
|
150
267
|
};
|
|
268
|
+
// Disable tls on empty values
|
|
269
|
+
// From the docker docs: Setting the DOCKER_TLS_VERIFY environment variable to any value other than the empty string is equivalent to setting the --tlsverify flag
|
|
270
|
+
if (
|
|
271
|
+
process.env.DOCKER_TLS_VERIFY &&
|
|
272
|
+
process.env.DOCKER_TLS_VERIFY === ""
|
|
273
|
+
) {
|
|
274
|
+
opts.https.rejectUnauthorized = false;
|
|
275
|
+
console.log("TLS Verification disabled for", hostStr);
|
|
276
|
+
}
|
|
151
277
|
}
|
|
152
278
|
}
|
|
153
279
|
|
|
154
280
|
return opts;
|
|
155
281
|
};
|
|
156
282
|
|
|
157
|
-
export const getConnection = async (options) => {
|
|
158
|
-
if (
|
|
159
|
-
|
|
283
|
+
export const getConnection = async (options, forRegistry) => {
|
|
284
|
+
if (isContainerd) {
|
|
285
|
+
return undefined;
|
|
286
|
+
} else if (!dockerConn) {
|
|
287
|
+
const defaultOptions = getDefaultOptions(forRegistry);
|
|
160
288
|
const opts = Object.assign(
|
|
161
289
|
{},
|
|
162
290
|
{
|
|
163
291
|
enableUnixSockets: defaultOptions.enableUnixSockets,
|
|
164
292
|
throwHttpErrors: defaultOptions.throwHttpErrors,
|
|
165
293
|
method: defaultOptions.method,
|
|
166
|
-
prefixUrl: defaultOptions.prefixUrl
|
|
294
|
+
prefixUrl: defaultOptions.prefixUrl,
|
|
295
|
+
headers: defaultOptions.headers
|
|
167
296
|
},
|
|
168
297
|
options
|
|
169
298
|
);
|
|
@@ -247,8 +376,8 @@ export const getConnection = async (options) => {
|
|
|
247
376
|
return dockerConn;
|
|
248
377
|
};
|
|
249
378
|
|
|
250
|
-
export const makeRequest = async (path, method = "GET") => {
|
|
251
|
-
const client = await getConnection();
|
|
379
|
+
export const makeRequest = async (path, method = "GET", forRegistry) => {
|
|
380
|
+
const client = await getConnection({}, forRegistry);
|
|
252
381
|
if (!client) {
|
|
253
382
|
return undefined;
|
|
254
383
|
}
|
|
@@ -258,14 +387,15 @@ export const makeRequest = async (path, method = "GET") => {
|
|
|
258
387
|
enableUnixSockets: true,
|
|
259
388
|
method
|
|
260
389
|
};
|
|
261
|
-
const defaultOptions = getDefaultOptions();
|
|
390
|
+
const defaultOptions = getDefaultOptions(forRegistry);
|
|
262
391
|
const opts = Object.assign(
|
|
263
392
|
{},
|
|
264
393
|
{
|
|
265
394
|
enableUnixSockets: defaultOptions.enableUnixSockets,
|
|
266
395
|
throwHttpErrors: defaultOptions.throwHttpErrors,
|
|
267
396
|
method: defaultOptions.method,
|
|
268
|
-
prefixUrl: defaultOptions.prefixUrl
|
|
397
|
+
prefixUrl: defaultOptions.prefixUrl,
|
|
398
|
+
headers: defaultOptions.headers
|
|
269
399
|
},
|
|
270
400
|
extraOptions
|
|
271
401
|
);
|
|
@@ -331,20 +461,44 @@ export const parseImageName = (fullImageName) => {
|
|
|
331
461
|
return nameObj;
|
|
332
462
|
};
|
|
333
463
|
|
|
464
|
+
/**
|
|
465
|
+
* Prefer cli on windows or when using tcp/ssh based host.
|
|
466
|
+
*
|
|
467
|
+
* @returns boolean true if we should use the cli. false otherwise
|
|
468
|
+
*/
|
|
469
|
+
const needsCliFallback = () => {
|
|
470
|
+
return (
|
|
471
|
+
isWin ||
|
|
472
|
+
(process.env.DOCKER_HOST &&
|
|
473
|
+
(process.env.DOCKER_HOST.startsWith("tcp://") ||
|
|
474
|
+
process.env.DOCKER_HOST.startsWith("ssh://")))
|
|
475
|
+
);
|
|
476
|
+
};
|
|
477
|
+
|
|
334
478
|
/**
|
|
335
479
|
* Method to get image to the local registry by pulling from the remote if required
|
|
336
480
|
*/
|
|
337
481
|
export const getImage = async (fullImageName) => {
|
|
338
482
|
let localData = undefined;
|
|
339
483
|
let pullData = undefined;
|
|
340
|
-
const { repo, tag, digest } = parseImageName(fullImageName);
|
|
341
|
-
let repoWithTag =
|
|
484
|
+
const { registry, repo, tag, digest } = parseImageName(fullImageName);
|
|
485
|
+
let repoWithTag =
|
|
486
|
+
registry && registry !== "docker.io"
|
|
487
|
+
? fullImageName
|
|
488
|
+
: `${repo}:${tag !== "" ? tag : ":latest"}`;
|
|
342
489
|
// Fetch only the latest tag if none is specified
|
|
343
490
|
if (tag === "" && digest === "") {
|
|
344
491
|
fullImageName = fullImageName + ":latest";
|
|
345
492
|
}
|
|
346
|
-
if (
|
|
347
|
-
|
|
493
|
+
if (isContainerd) {
|
|
494
|
+
console.log(
|
|
495
|
+
"containerd/nerdctl is currently unsupported. Export the image manually and run cdxgen against the tar image."
|
|
496
|
+
);
|
|
497
|
+
return undefined;
|
|
498
|
+
}
|
|
499
|
+
if (needsCliFallback()) {
|
|
500
|
+
const dockerCmd = process.env.DOCKER_CMD || "docker";
|
|
501
|
+
let result = spawnSync(dockerCmd, ["pull", fullImageName], {
|
|
348
502
|
encoding: "utf-8"
|
|
349
503
|
});
|
|
350
504
|
if (result.status !== 0 || result.error) {
|
|
@@ -355,12 +509,16 @@ export const getImage = async (fullImageName) => {
|
|
|
355
509
|
console.log(
|
|
356
510
|
"Ensure Docker for Desktop is running as an administrator with 'Exposing daemon on TCP without TLS' setting turned on."
|
|
357
511
|
);
|
|
512
|
+
} else if (result.stderr && result.stderr.includes("not found")) {
|
|
513
|
+
console.log(
|
|
514
|
+
"Set the environment variable DOCKER_CMD to use an alternative command such as nerdctl or podman."
|
|
515
|
+
);
|
|
358
516
|
} else {
|
|
359
517
|
console.log(result.stderr);
|
|
360
518
|
}
|
|
361
519
|
return localData;
|
|
362
520
|
} else {
|
|
363
|
-
result = spawnSync(
|
|
521
|
+
result = spawnSync(dockerCmd, ["inspect", fullImageName], {
|
|
364
522
|
encoding: "utf-8"
|
|
365
523
|
});
|
|
366
524
|
if (result.status !== 0 || result.error) {
|
|
@@ -385,7 +543,11 @@ export const getImage = async (fullImageName) => {
|
|
|
385
543
|
}
|
|
386
544
|
}
|
|
387
545
|
try {
|
|
388
|
-
localData = await makeRequest(
|
|
546
|
+
localData = await makeRequest(
|
|
547
|
+
`images/${repoWithTag}/json`,
|
|
548
|
+
"GET",
|
|
549
|
+
registry
|
|
550
|
+
);
|
|
389
551
|
if (localData) {
|
|
390
552
|
return localData;
|
|
391
553
|
}
|
|
@@ -393,10 +555,14 @@ export const getImage = async (fullImageName) => {
|
|
|
393
555
|
// ignore
|
|
394
556
|
}
|
|
395
557
|
try {
|
|
396
|
-
localData = await makeRequest(`images/${repo}/json
|
|
558
|
+
localData = await makeRequest(`images/${repo}/json`, "GET", registry);
|
|
397
559
|
} catch (err) {
|
|
398
560
|
try {
|
|
399
|
-
localData = await makeRequest(
|
|
561
|
+
localData = await makeRequest(
|
|
562
|
+
`images/${fullImageName}/json`,
|
|
563
|
+
"GET",
|
|
564
|
+
registry
|
|
565
|
+
);
|
|
400
566
|
if (localData) {
|
|
401
567
|
return localData;
|
|
402
568
|
}
|
|
@@ -412,7 +578,8 @@ export const getImage = async (fullImageName) => {
|
|
|
412
578
|
try {
|
|
413
579
|
pullData = await makeRequest(
|
|
414
580
|
`images/create?fromImage=${fullImageName}`,
|
|
415
|
-
"POST"
|
|
581
|
+
"POST",
|
|
582
|
+
registry
|
|
416
583
|
);
|
|
417
584
|
if (
|
|
418
585
|
pullData &&
|
|
@@ -434,7 +601,8 @@ export const getImage = async (fullImageName) => {
|
|
|
434
601
|
}
|
|
435
602
|
pullData = await makeRequest(
|
|
436
603
|
`images/create?fromImage=${repoWithTag}`,
|
|
437
|
-
"POST"
|
|
604
|
+
"POST",
|
|
605
|
+
registry
|
|
438
606
|
);
|
|
439
607
|
} catch (err) {
|
|
440
608
|
// continue regardless of error
|
|
@@ -444,7 +612,11 @@ export const getImage = async (fullImageName) => {
|
|
|
444
612
|
if (DEBUG_MODE) {
|
|
445
613
|
console.log(`Trying with ${repoWithTag}`);
|
|
446
614
|
}
|
|
447
|
-
localData = await makeRequest(
|
|
615
|
+
localData = await makeRequest(
|
|
616
|
+
`images/${repoWithTag}/json`,
|
|
617
|
+
"GET",
|
|
618
|
+
registry
|
|
619
|
+
);
|
|
448
620
|
if (localData) {
|
|
449
621
|
return localData;
|
|
450
622
|
}
|
|
@@ -453,7 +625,7 @@ export const getImage = async (fullImageName) => {
|
|
|
453
625
|
if (DEBUG_MODE) {
|
|
454
626
|
console.log(`Trying with ${repo}`);
|
|
455
627
|
}
|
|
456
|
-
localData = await makeRequest(`images/${repo}/json
|
|
628
|
+
localData = await makeRequest(`images/${repo}/json`, "GET", registry);
|
|
457
629
|
if (localData) {
|
|
458
630
|
return localData;
|
|
459
631
|
}
|
|
@@ -464,7 +636,11 @@ export const getImage = async (fullImageName) => {
|
|
|
464
636
|
if (DEBUG_MODE) {
|
|
465
637
|
console.log(`Trying with ${fullImageName}`);
|
|
466
638
|
}
|
|
467
|
-
localData = await makeRequest(
|
|
639
|
+
localData = await makeRequest(
|
|
640
|
+
`images/${fullImageName}/json`,
|
|
641
|
+
"GET",
|
|
642
|
+
registry
|
|
643
|
+
);
|
|
468
644
|
} catch (err) {
|
|
469
645
|
// continue regardless of error
|
|
470
646
|
}
|
|
@@ -684,7 +860,7 @@ export const exportImage = async (fullImageName) => {
|
|
|
684
860
|
if (!localData) {
|
|
685
861
|
return undefined;
|
|
686
862
|
}
|
|
687
|
-
const { tag, digest } = parseImageName(fullImageName);
|
|
863
|
+
const { registry, tag, digest } = parseImageName(fullImageName);
|
|
688
864
|
// Fetch only the latest tag if none is specified
|
|
689
865
|
if (tag === "" && digest === "") {
|
|
690
866
|
fullImageName = fullImageName + ":latest";
|
|
@@ -695,7 +871,7 @@ export const exportImage = async (fullImageName) => {
|
|
|
695
871
|
// Windows containers use index.json
|
|
696
872
|
const manifestIndexFile = join(tempDir, "index.json");
|
|
697
873
|
// On Windows, fallback to invoking cli
|
|
698
|
-
if (
|
|
874
|
+
if (needsCliFallback()) {
|
|
699
875
|
const imageTarFile = join(tempDir, "image.tar");
|
|
700
876
|
console.log(
|
|
701
877
|
`About to export image ${fullImageName} to ${imageTarFile} using docker cli`
|
|
@@ -722,10 +898,16 @@ export const exportImage = async (fullImageName) => {
|
|
|
722
898
|
}
|
|
723
899
|
}
|
|
724
900
|
} else {
|
|
725
|
-
const client = await getConnection();
|
|
901
|
+
const client = await getConnection({}, registry);
|
|
726
902
|
try {
|
|
727
903
|
if (DEBUG_MODE) {
|
|
728
|
-
|
|
904
|
+
if (registry && registry.trim().length) {
|
|
905
|
+
console.log(
|
|
906
|
+
`About to export image ${fullImageName} from ${registry} to ${tempDir}`
|
|
907
|
+
);
|
|
908
|
+
} else {
|
|
909
|
+
console.log(`About to export image ${fullImageName} to ${tempDir}`);
|
|
910
|
+
}
|
|
729
911
|
}
|
|
730
912
|
await stream.pipeline(
|
|
731
913
|
client.stream(`images/${fullImageName}/get`),
|
|
@@ -896,3 +1078,46 @@ export const removeImage = async (fullImageName, force = false) => {
|
|
|
896
1078
|
);
|
|
897
1079
|
return removeData;
|
|
898
1080
|
};
|
|
1081
|
+
|
|
1082
|
+
export const getCredsFromHelper = (exeSuffix, serverAddress) => {
|
|
1083
|
+
if (registry_auth_keys[serverAddress]) {
|
|
1084
|
+
return registry_auth_keys[serverAddress];
|
|
1085
|
+
}
|
|
1086
|
+
let credHelperExe = `docker-credential-${exeSuffix}`;
|
|
1087
|
+
if (isWin) {
|
|
1088
|
+
credHelperExe = credHelperExe + ".exe";
|
|
1089
|
+
}
|
|
1090
|
+
const result = spawnSync(credHelperExe, ["get"], {
|
|
1091
|
+
input: serverAddress,
|
|
1092
|
+
encoding: "utf-8"
|
|
1093
|
+
});
|
|
1094
|
+
if (result.status !== 0 || result.error) {
|
|
1095
|
+
console.log(result.stdout, result.stderr);
|
|
1096
|
+
} else if (result.stdout) {
|
|
1097
|
+
const cmdOutput = Buffer.from(result.stdout).toString();
|
|
1098
|
+
try {
|
|
1099
|
+
const authPayload = JSON.parse(cmdOutput);
|
|
1100
|
+
const fixedAuthPayload = {
|
|
1101
|
+
username:
|
|
1102
|
+
authPayload.username ||
|
|
1103
|
+
authPayload.Username ||
|
|
1104
|
+
process.env.DOCKER_USER,
|
|
1105
|
+
password:
|
|
1106
|
+
authPayload.password ||
|
|
1107
|
+
authPayload.Secret ||
|
|
1108
|
+
process.env.DOCKER_PASSWORD,
|
|
1109
|
+
email:
|
|
1110
|
+
authPayload.email || authPayload.username || process.env.DOCKER_USER,
|
|
1111
|
+
serveraddress: serverAddress
|
|
1112
|
+
};
|
|
1113
|
+
const authKey = Buffer.from(JSON.stringify(fixedAuthPayload)).toString(
|
|
1114
|
+
"base64"
|
|
1115
|
+
);
|
|
1116
|
+
registry_auth_keys[serverAddress] = authKey;
|
|
1117
|
+
return authKey;
|
|
1118
|
+
} catch (err) {
|
|
1119
|
+
return undefined;
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
return undefined;
|
|
1123
|
+
};
|
package/docker.test.js
CHANGED
|
@@ -54,6 +54,15 @@ test("parseImageName tests", () => {
|
|
|
54
54
|
digest: "",
|
|
55
55
|
platform: ""
|
|
56
56
|
});
|
|
57
|
+
expect(
|
|
58
|
+
parseImageName("foocorp.jfrog.io/docker/library/eclipse-temurin:latest")
|
|
59
|
+
).toEqual({
|
|
60
|
+
registry: "foocorp.jfrog.io",
|
|
61
|
+
repo: "docker/library/eclipse-temurin",
|
|
62
|
+
tag: "latest",
|
|
63
|
+
digest: "",
|
|
64
|
+
platform: ""
|
|
65
|
+
});
|
|
57
66
|
expect(
|
|
58
67
|
parseImageName(
|
|
59
68
|
"quay.io/shiftleft/scan-java@sha256:5d008306a7c5d09ba0161a3408fa3839dc2c9dd991ffb68adecc1040399fe9e1"
|
package/index.js
CHANGED
|
@@ -1860,7 +1860,7 @@ export const createNodejsBom = async (path, options) => {
|
|
|
1860
1860
|
const parentSubComponents = [];
|
|
1861
1861
|
let ppurl = "";
|
|
1862
1862
|
// Docker mode requires special handling
|
|
1863
|
-
if (["docker", "oci", "os"].includes(options.projectType)) {
|
|
1863
|
+
if (["docker", "oci", "container", "os"].includes(options.projectType)) {
|
|
1864
1864
|
const pkgJsonFiles = getAllFiles(path, "**/package.json", options);
|
|
1865
1865
|
// Are there any package.json files in the container?
|
|
1866
1866
|
if (pkgJsonFiles.length) {
|
|
@@ -1880,7 +1880,7 @@ export const createNodejsBom = async (path, options) => {
|
|
|
1880
1880
|
}
|
|
1881
1881
|
let allImports = {};
|
|
1882
1882
|
if (
|
|
1883
|
-
!["docker", "oci", "os"].includes(options.projectType) &&
|
|
1883
|
+
!["docker", "oci", "container", "os"].includes(options.projectType) &&
|
|
1884
1884
|
!options.noBabel
|
|
1885
1885
|
) {
|
|
1886
1886
|
if (DEBUG_MODE) {
|
|
@@ -2753,7 +2753,7 @@ export const createGoBom = async (path, options) => {
|
|
|
2753
2753
|
if (gomodFiles.length) {
|
|
2754
2754
|
let shouldManuallyParse = false;
|
|
2755
2755
|
// Use the go list -deps and go mod why commands to generate a good quality BOM for non-docker invocations
|
|
2756
|
-
if (!["docker", "oci", "os"].includes(options.projectType)) {
|
|
2756
|
+
if (!["docker", "oci", "container", "os"].includes(options.projectType)) {
|
|
2757
2757
|
for (const f of gomodFiles) {
|
|
2758
2758
|
const basePath = dirname(f);
|
|
2759
2759
|
// Ignore vendor packages
|
|
@@ -2865,7 +2865,7 @@ export const createGoBom = async (path, options) => {
|
|
|
2865
2865
|
}
|
|
2866
2866
|
}
|
|
2867
2867
|
// Parse the gomod files manually. The resultant BOM would be incomplete
|
|
2868
|
-
if (!["docker", "oci", "os"].includes(options.projectType)) {
|
|
2868
|
+
if (!["docker", "oci", "container", "os"].includes(options.projectType)) {
|
|
2869
2869
|
console.log(
|
|
2870
2870
|
"Manually parsing go.mod files. The resultant BOM would be incomplete."
|
|
2871
2871
|
);
|
|
@@ -3154,7 +3154,7 @@ export const createCppBom = (path, options) => {
|
|
|
3154
3154
|
// inside of other project types. So we currently limit this analyis only when -t argument
|
|
3155
3155
|
// is used.
|
|
3156
3156
|
if (
|
|
3157
|
-
!["docker", "oci", "os"].includes(options.projectType) &&
|
|
3157
|
+
!["docker", "oci", "container", "os"].includes(options.projectType) &&
|
|
3158
3158
|
(!options.createMultiXBom || options.deep)
|
|
3159
3159
|
) {
|
|
3160
3160
|
let osPkgsList = [];
|
|
@@ -5296,6 +5296,7 @@ export const createBom = async (path, options) => {
|
|
|
5296
5296
|
projectType === "docker" ||
|
|
5297
5297
|
projectType === "podman" ||
|
|
5298
5298
|
projectType === "oci" ||
|
|
5299
|
+
projectType === "container" ||
|
|
5299
5300
|
path.startsWith("docker.io") ||
|
|
5300
5301
|
path.startsWith("quay.io") ||
|
|
5301
5302
|
path.startsWith("ghcr.io") ||
|
|
@@ -5544,19 +5545,36 @@ export async function submitBom(args, bomContents) {
|
|
|
5544
5545
|
if (encodedBomContents.startsWith("77u/")) {
|
|
5545
5546
|
encodedBomContents = encodedBomContents.substring(4);
|
|
5546
5547
|
}
|
|
5547
|
-
let projectVersion = args.projectVersion || "master";
|
|
5548
|
-
if (projectVersion == true) {
|
|
5549
|
-
projectVersion = "master";
|
|
5550
|
-
}
|
|
5551
5548
|
const bomPayload = {
|
|
5552
|
-
project: args.projectId,
|
|
5553
|
-
projectName: args.projectName,
|
|
5554
|
-
projectVersion: projectVersion,
|
|
5555
5549
|
autoCreate: "true",
|
|
5556
5550
|
bom: encodedBomContents
|
|
5557
5551
|
};
|
|
5558
|
-
|
|
5559
|
-
|
|
5552
|
+
let projectVersion = args.projectVersion || "master";
|
|
5553
|
+
if (
|
|
5554
|
+
typeof args.projectId !== "undefined" ||
|
|
5555
|
+
(typeof args.projectName !== "undefined" &&
|
|
5556
|
+
typeof projectVersion !== "undefined")
|
|
5557
|
+
) {
|
|
5558
|
+
if (typeof args.projectId !== "undefined") {
|
|
5559
|
+
bomPayload.project = args.projectId;
|
|
5560
|
+
}
|
|
5561
|
+
if (typeof args.projectName !== "undefined") {
|
|
5562
|
+
bomPayload.projectName = args.projectName;
|
|
5563
|
+
}
|
|
5564
|
+
if (typeof projectVersion !== "undefined") {
|
|
5565
|
+
bomPayload.projectVersion = projectVersion;
|
|
5566
|
+
}
|
|
5567
|
+
} else {
|
|
5568
|
+
console.log(
|
|
5569
|
+
"projectId, projectName and projectVersion, or all three must be provided."
|
|
5570
|
+
);
|
|
5571
|
+
return;
|
|
5572
|
+
}
|
|
5573
|
+
if (
|
|
5574
|
+
typeof args.parentProjectId !== "undefined" ||
|
|
5575
|
+
typeof args.parentUUID !== "undefined"
|
|
5576
|
+
) {
|
|
5577
|
+
bomPayload.parentUUID = args.parentProjectId || args.parentUUID;
|
|
5560
5578
|
}
|
|
5561
5579
|
if (DEBUG_MODE) {
|
|
5562
5580
|
console.log(
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cyclonedx/cdxgen",
|
|
3
|
-
"version": "9.9.
|
|
3
|
+
"version": "9.9.5",
|
|
4
4
|
"description": "Creates CycloneDX Software Bill of Materials (SBOM) from source or container image",
|
|
5
5
|
"homepage": "http://github.com/cyclonedx/cdxgen",
|
|
6
6
|
"author": "Prabhu Subramanian <prabhu@appthreat.com>",
|
|
@@ -83,7 +83,7 @@
|
|
|
83
83
|
"yargs": "^17.7.2"
|
|
84
84
|
},
|
|
85
85
|
"optionalDependencies": {
|
|
86
|
-
"@appthreat/atom": "1.6.
|
|
86
|
+
"@appthreat/atom": "1.6.4",
|
|
87
87
|
"@cyclonedx/cdxgen-plugins-bin": "^1.4.0",
|
|
88
88
|
"@cyclonedx/cdxgen-plugins-bin-arm64": "^1.4.0",
|
|
89
89
|
"@cyclonedx/cdxgen-plugins-bin-ppc64": "^1.4.0",
|
package/server.js
CHANGED
package/utils.js
CHANGED
|
@@ -19,7 +19,8 @@ import {
|
|
|
19
19
|
readFileSync,
|
|
20
20
|
rmSync,
|
|
21
21
|
unlinkSync,
|
|
22
|
-
writeFileSync
|
|
22
|
+
writeFileSync,
|
|
23
|
+
readdirSync
|
|
23
24
|
} from "node:fs";
|
|
24
25
|
import got from "got";
|
|
25
26
|
import Arborist from "@npmcli/arborist";
|
|
@@ -6514,12 +6515,67 @@ export const parseJarManifest = function (jarMetadata) {
|
|
|
6514
6515
|
return metadata;
|
|
6515
6516
|
};
|
|
6516
6517
|
|
|
6518
|
+
export const parsePomProperties = function (pomProperties) {
|
|
6519
|
+
const properties = {};
|
|
6520
|
+
if (!pomProperties) {
|
|
6521
|
+
return properties;
|
|
6522
|
+
}
|
|
6523
|
+
pomProperties.split("\n").forEach((l) => {
|
|
6524
|
+
l = l.replace("\r", "");
|
|
6525
|
+
if (l.includes("=")) {
|
|
6526
|
+
const tmpA = l.split("=");
|
|
6527
|
+
if (tmpA && tmpA.length === 2) {
|
|
6528
|
+
properties[tmpA[0]] = tmpA[1].replace("\r", "");
|
|
6529
|
+
}
|
|
6530
|
+
}
|
|
6531
|
+
});
|
|
6532
|
+
return properties;
|
|
6533
|
+
};
|
|
6534
|
+
|
|
6517
6535
|
export const encodeForPurl = (s) => {
|
|
6518
6536
|
return s && !s.includes("%40")
|
|
6519
6537
|
? encodeURIComponent(s).replace(/%3A/g, ":").replace(/%2F/g, "/")
|
|
6520
6538
|
: s;
|
|
6521
6539
|
};
|
|
6522
6540
|
|
|
6541
|
+
/**
|
|
6542
|
+
* Method to get pom properties from maven directory
|
|
6543
|
+
*
|
|
6544
|
+
* @param {string} mavenDir Path to maven directory
|
|
6545
|
+
*
|
|
6546
|
+
* @return array with pom properties
|
|
6547
|
+
*/
|
|
6548
|
+
export const getPomPropertiesFromMavenDir = function (mavenDir) {
|
|
6549
|
+
let pomProperties = {};
|
|
6550
|
+
if (existsSync(mavenDir) && lstatSync(mavenDir).isDirectory()) {
|
|
6551
|
+
let mavenDirEntries = readdirSync(mavenDir, { withFileTypes: true });
|
|
6552
|
+
mavenDirEntries.forEach((mavenDirEntry) => {
|
|
6553
|
+
if (mavenDirEntry.isDirectory()) {
|
|
6554
|
+
let groupDirEntries = readdirSync(
|
|
6555
|
+
join(mavenDirEntry.path, mavenDirEntry.name),
|
|
6556
|
+
{ withFileTypes: true }
|
|
6557
|
+
);
|
|
6558
|
+
groupDirEntries.forEach((groupDirEntry) => {
|
|
6559
|
+
if (groupDirEntry.isDirectory()) {
|
|
6560
|
+
let pomPropertiesFile = join(
|
|
6561
|
+
groupDirEntry.path,
|
|
6562
|
+
groupDirEntry.name,
|
|
6563
|
+
"pom.properties"
|
|
6564
|
+
);
|
|
6565
|
+
if (existsSync(pomPropertiesFile)) {
|
|
6566
|
+
const pomPropertiesString = readFileSync(pomPropertiesFile, {
|
|
6567
|
+
encoding: "utf-8"
|
|
6568
|
+
});
|
|
6569
|
+
pomProperties = parsePomProperties(pomPropertiesString);
|
|
6570
|
+
}
|
|
6571
|
+
}
|
|
6572
|
+
});
|
|
6573
|
+
}
|
|
6574
|
+
});
|
|
6575
|
+
}
|
|
6576
|
+
return pomProperties;
|
|
6577
|
+
};
|
|
6578
|
+
|
|
6523
6579
|
/**
|
|
6524
6580
|
* Method to extract a war or ear file
|
|
6525
6581
|
*
|
|
@@ -6601,13 +6657,14 @@ export const extractJarArchive = function (
|
|
|
6601
6657
|
}
|
|
6602
6658
|
const manifestDir = join(tempDir, "META-INF");
|
|
6603
6659
|
const manifestFile = join(manifestDir, "MANIFEST.MF");
|
|
6660
|
+
const mavenDir = join(manifestDir, "maven");
|
|
6604
6661
|
let jarResult = {
|
|
6605
6662
|
status: 1
|
|
6606
6663
|
};
|
|
6607
6664
|
if (existsSync(pomname)) {
|
|
6608
6665
|
jarResult = { status: 0 };
|
|
6609
6666
|
} else {
|
|
6610
|
-
jarResult = spawnSync("jar", ["-xf", jf], {
|
|
6667
|
+
jarResult = spawnSync("jar", ["-xf", jf, "META-INF"], {
|
|
6611
6668
|
encoding: "utf-8",
|
|
6612
6669
|
cwd: tempDir,
|
|
6613
6670
|
shell: isWin,
|
|
@@ -6617,29 +6674,42 @@ export const extractJarArchive = function (
|
|
|
6617
6674
|
if (jarResult.status !== 0) {
|
|
6618
6675
|
console.error(jarResult.stdout, jarResult.stderr);
|
|
6619
6676
|
} else {
|
|
6620
|
-
|
|
6677
|
+
// When maven descriptor is available take group, name and version from pom.properties
|
|
6678
|
+
// META-INF/maven/${groupId}/${artifactId}/pom.properties
|
|
6679
|
+
// see https://maven.apache.org/shared/maven-archiver/index.html
|
|
6680
|
+
const pomProperties = getPomPropertiesFromMavenDir(mavenDir);
|
|
6681
|
+
let group = pomProperties["groupId"],
|
|
6682
|
+
name = pomProperties["artifactId"],
|
|
6683
|
+
version = pomProperties["version"],
|
|
6684
|
+
confidence = 1,
|
|
6685
|
+
technique = "manifest-analysis";
|
|
6686
|
+
if ((!group || !name || !version) && existsSync(manifestFile)) {
|
|
6687
|
+
confidence = 0.8;
|
|
6621
6688
|
const jarMetadata = parseJarManifest(
|
|
6622
6689
|
readFileSync(manifestFile, {
|
|
6623
6690
|
encoding: "utf-8"
|
|
6624
6691
|
})
|
|
6625
6692
|
);
|
|
6626
|
-
|
|
6693
|
+
group =
|
|
6694
|
+
group ||
|
|
6627
6695
|
jarMetadata["Extension-Name"] ||
|
|
6628
6696
|
jarMetadata["Implementation-Vendor-Id"] ||
|
|
6629
6697
|
jarMetadata["Bundle-SymbolicName"] ||
|
|
6630
6698
|
jarMetadata["Bundle-Vendor"] ||
|
|
6631
6699
|
jarMetadata["Automatic-Module-Name"] ||
|
|
6632
6700
|
"";
|
|
6633
|
-
|
|
6701
|
+
version =
|
|
6702
|
+
version ||
|
|
6634
6703
|
jarMetadata["Bundle-Version"] ||
|
|
6635
6704
|
jarMetadata["Implementation-Version"] ||
|
|
6636
6705
|
jarMetadata["Specification-Version"];
|
|
6637
6706
|
if (version && version.includes(" ")) {
|
|
6638
6707
|
version = version.split(" ")[0];
|
|
6639
6708
|
}
|
|
6640
|
-
let name = "";
|
|
6641
6709
|
// Prefer jar filename to construct name and version
|
|
6642
6710
|
if (!name || !version || name === "" || version === "") {
|
|
6711
|
+
confidence = 0.5;
|
|
6712
|
+
technique = "filename";
|
|
6643
6713
|
const tmpA = jarname.split("-");
|
|
6644
6714
|
if (tmpA && tmpA.length > 1) {
|
|
6645
6715
|
const lastPart = tmpA[tmpA.length - 1];
|
|
@@ -6688,56 +6758,56 @@ export const extractJarArchive = function (
|
|
|
6688
6758
|
break;
|
|
6689
6759
|
}
|
|
6690
6760
|
}
|
|
6691
|
-
if
|
|
6692
|
-
|
|
6693
|
-
|
|
6694
|
-
|
|
6761
|
+
// if group is empty use name as group
|
|
6762
|
+
group = group === "." ? name : group || name;
|
|
6763
|
+
}
|
|
6764
|
+
if (name && version) {
|
|
6765
|
+
let apkg = {
|
|
6766
|
+
group: group ? encodeForPurl(group) : "",
|
|
6767
|
+
name: name ? encodeForPurl(name) : "",
|
|
6768
|
+
version,
|
|
6769
|
+
purl: new PackageURL(
|
|
6770
|
+
"maven",
|
|
6695
6771
|
group,
|
|
6696
|
-
name
|
|
6772
|
+
name,
|
|
6697
6773
|
version,
|
|
6698
|
-
|
|
6699
|
-
|
|
6700
|
-
|
|
6701
|
-
|
|
6702
|
-
|
|
6703
|
-
|
|
6704
|
-
|
|
6705
|
-
|
|
6706
|
-
|
|
6707
|
-
|
|
6708
|
-
|
|
6709
|
-
|
|
6710
|
-
|
|
6711
|
-
|
|
6712
|
-
|
|
6713
|
-
|
|
6714
|
-
|
|
6715
|
-
|
|
6716
|
-
|
|
6717
|
-
|
|
6718
|
-
}
|
|
6719
|
-
|
|
6720
|
-
|
|
6721
|
-
|
|
6722
|
-
|
|
6723
|
-
|
|
6724
|
-
|
|
6725
|
-
|
|
6726
|
-
|
|
6727
|
-
|
|
6728
|
-
jarNSMapping[apkg.purl]
|
|
6729
|
-
|
|
6730
|
-
|
|
6731
|
-
|
|
6732
|
-
|
|
6733
|
-
|
|
6734
|
-
|
|
6735
|
-
}
|
|
6736
|
-
pkgList.push(apkg);
|
|
6737
|
-
} else {
|
|
6738
|
-
if (DEBUG_MODE) {
|
|
6739
|
-
console.log(`Ignored jar ${jarname}`, jarMetadata, name, version);
|
|
6740
|
-
}
|
|
6774
|
+
{ type: "jar" },
|
|
6775
|
+
null
|
|
6776
|
+
).toString(),
|
|
6777
|
+
evidence: {
|
|
6778
|
+
identity: {
|
|
6779
|
+
field: "purl",
|
|
6780
|
+
confidence: confidence,
|
|
6781
|
+
methods: [
|
|
6782
|
+
{
|
|
6783
|
+
technique: technique,
|
|
6784
|
+
confidence: confidence,
|
|
6785
|
+
value: jarname
|
|
6786
|
+
}
|
|
6787
|
+
]
|
|
6788
|
+
}
|
|
6789
|
+
},
|
|
6790
|
+
properties: [
|
|
6791
|
+
{
|
|
6792
|
+
name: "SrcFile",
|
|
6793
|
+
value: jarname
|
|
6794
|
+
}
|
|
6795
|
+
]
|
|
6796
|
+
};
|
|
6797
|
+
if (
|
|
6798
|
+
jarNSMapping &&
|
|
6799
|
+
jarNSMapping[apkg.purl] &&
|
|
6800
|
+
jarNSMapping[apkg.purl].namespaces
|
|
6801
|
+
) {
|
|
6802
|
+
apkg.properties.push({
|
|
6803
|
+
name: "Namespaces",
|
|
6804
|
+
value: jarNSMapping[apkg.purl].namespaces.join("\n")
|
|
6805
|
+
});
|
|
6806
|
+
}
|
|
6807
|
+
pkgList.push(apkg);
|
|
6808
|
+
} else {
|
|
6809
|
+
if (DEBUG_MODE) {
|
|
6810
|
+
console.log(`Ignored jar ${jarname}`, name, version);
|
|
6741
6811
|
}
|
|
6742
6812
|
}
|
|
6743
6813
|
try {
|
|
@@ -6929,6 +6999,7 @@ export const getMavenCommand = (srcPath, rootPath) => {
|
|
|
6929
6999
|
let isWrapperReady = false;
|
|
6930
7000
|
let isWrapperFound = false;
|
|
6931
7001
|
let findMavenFile = "mvnw";
|
|
7002
|
+
let mavenWrapperCmd = null;
|
|
6932
7003
|
if (platform() == "win32") {
|
|
6933
7004
|
findMavenFile = "mvnw.bat";
|
|
6934
7005
|
if (
|
|
@@ -6947,7 +7018,7 @@ export const getMavenCommand = (srcPath, rootPath) => {
|
|
|
6947
7018
|
} catch (e) {
|
|
6948
7019
|
// continue regardless of error
|
|
6949
7020
|
}
|
|
6950
|
-
|
|
7021
|
+
mavenWrapperCmd = resolve(join(srcPath, findMavenFile));
|
|
6951
7022
|
isWrapperFound = true;
|
|
6952
7023
|
} else if (rootPath && existsSync(join(rootPath, findMavenFile))) {
|
|
6953
7024
|
// Check if the root directory has a wrapper script
|
|
@@ -6956,7 +7027,7 @@ export const getMavenCommand = (srcPath, rootPath) => {
|
|
|
6956
7027
|
} catch (e) {
|
|
6957
7028
|
// continue regardless of error
|
|
6958
7029
|
}
|
|
6959
|
-
|
|
7030
|
+
mavenWrapperCmd = resolve(join(rootPath, findMavenFile));
|
|
6960
7031
|
isWrapperFound = true;
|
|
6961
7032
|
}
|
|
6962
7033
|
if (isWrapperFound) {
|
|
@@ -6965,14 +7036,15 @@ export const getMavenCommand = (srcPath, rootPath) => {
|
|
|
6965
7036
|
"Testing the wrapper script by invoking wrapper:wrapper task"
|
|
6966
7037
|
);
|
|
6967
7038
|
}
|
|
6968
|
-
const result = spawnSync(
|
|
7039
|
+
const result = spawnSync(mavenWrapperCmd, ["wrapper:wrapper"], {
|
|
6969
7040
|
encoding: "utf-8",
|
|
6970
7041
|
cwd: rootPath,
|
|
6971
7042
|
timeout: TIMEOUT_MS,
|
|
6972
7043
|
shell: isWin
|
|
6973
7044
|
});
|
|
6974
|
-
if (!result.error) {
|
|
7045
|
+
if (!result.error && !result.status) {
|
|
6975
7046
|
isWrapperReady = true;
|
|
7047
|
+
mavenCmd = mavenWrapperCmd;
|
|
6976
7048
|
} else {
|
|
6977
7049
|
if (DEBUG_MODE) {
|
|
6978
7050
|
console.log(
|