@cyclonedx/cdxgen 9.8.7 → 9.8.8
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 +16 -7
- package/analyzer.js +3 -3
- package/bin/cdxgen.js +3 -3
- package/bin/repl.js +20 -20
- package/binary.js +1 -1
- package/docker.js +1 -1
- package/index.js +162 -74
- package/package.json +8 -8
- package/server.js +1 -2
- package/utils.js +319 -66
- package/utils.test.js +75 -18
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
cdxgen is a cli tool, library, [REPL](./ADVANCED.md), and server to create a valid and compliant [CycloneDX][cyclonedx-homepage] Software Bill of Materials (SBOM) containing an aggregate of all project dependencies for c/c++, node.js, php, python, ruby, rust, java, .Net, dart, haskell, elixir, and Go projects in JSON format. CycloneDX 1.5 is a lightweight SBOM specification that is easily created, human and machine-readable, and simple to parse.
|
|
6
6
|
|
|
7
|
-
When used with plugins, cdxgen could generate an
|
|
7
|
+
When used with plugins, cdxgen could generate an OBOM for Linux docker images and even VMs running Linux or Windows operating systems. cdxgen also includes an evinse tool to generate component evidence and SaaSBOM for some languages.
|
|
8
8
|
|
|
9
9
|
NOTE:
|
|
10
10
|
|
|
@@ -61,7 +61,7 @@ NOTE:
|
|
|
61
61
|
|
|
62
62
|
Footnotes:
|
|
63
63
|
|
|
64
|
-
- [1] - For multi-module applications, the
|
|
64
|
+
- [1] - For multi-module applications, the BOM file could include components not included in the packaged war or ear file.
|
|
65
65
|
- [2] - Pip freeze is automatically performed to improve precision. Requires virtual environment.
|
|
66
66
|
- [3] - Perform dotnet or nuget restore to generate project.assets.json. Without this file, cdxgen would not include indirect dependencies.
|
|
67
67
|
- [4] - See the section on plugins
|
|
@@ -197,7 +197,7 @@ To print the SBOM as a table pass `-p` argument.
|
|
|
197
197
|
cdxgen -t java -o bom.json -p
|
|
198
198
|
```
|
|
199
199
|
|
|
200
|
-
To recursively generate a single
|
|
200
|
+
To recursively generate a single BOM for all languages pass `-r` argument.
|
|
201
201
|
|
|
202
202
|
```shell
|
|
203
203
|
cdxgen -r -o bom.json
|
|
@@ -254,6 +254,14 @@ Arguments can be passed either via the query string or as a JSON body. The follo
|
|
|
254
254
|
| projectGroup | Dependency track project group |
|
|
255
255
|
| projectVersion | Dependency track project version [default: ""] |
|
|
256
256
|
|
|
257
|
+
### Health endpoint
|
|
258
|
+
|
|
259
|
+
Use the /health endpoint to check if the SBOM server is up and running.
|
|
260
|
+
|
|
261
|
+
```shell
|
|
262
|
+
curl "http://127.0.0.1:9090/health"
|
|
263
|
+
```
|
|
264
|
+
|
|
257
265
|
### Scanning a local path
|
|
258
266
|
|
|
259
267
|
```shell
|
|
@@ -281,7 +289,7 @@ docker compose up
|
|
|
281
289
|
|
|
282
290
|
## War file support
|
|
283
291
|
|
|
284
|
-
cdxgen can generate a
|
|
292
|
+
cdxgen can generate a BOM file from a given war file.
|
|
285
293
|
|
|
286
294
|
```shell
|
|
287
295
|
# cdxgen -t java app.war
|
|
@@ -318,6 +326,7 @@ cdxgen can retain the dependency tree under the `dependencies` attribute for a s
|
|
|
318
326
|
- Scala SBT
|
|
319
327
|
- Python (requirements.txt, setup.py, pyproject.toml, poetry.lock)
|
|
320
328
|
- csharp (projects.assets.json)
|
|
329
|
+
- Go (go.mod)
|
|
321
330
|
|
|
322
331
|
## Environment variables
|
|
323
332
|
|
|
@@ -399,9 +408,9 @@ systemctl --user start podman.socket
|
|
|
399
408
|
podman system service -t 0 &
|
|
400
409
|
```
|
|
401
410
|
|
|
402
|
-
### Generate
|
|
411
|
+
### Generate OBOM for a live system
|
|
403
412
|
|
|
404
|
-
You can use the `obom` command to generate an
|
|
413
|
+
You can use the `obom` command to generate an OBOM for a live system or a VM for compliance and vulnerability management purposes. Windows and Linux operating systems are supported in this mode.
|
|
405
414
|
|
|
406
415
|
```shell
|
|
407
416
|
# obom is an alias for cdxgen -t os
|
|
@@ -417,7 +426,7 @@ See [evinse mode](./ADVANCED.md) in the advanced documentation.
|
|
|
417
426
|
|
|
418
427
|
## BoM signing
|
|
419
428
|
|
|
420
|
-
cdxgen can sign the generated
|
|
429
|
+
cdxgen can sign the generated BOM json file to increase authenticity and non-repudiation capabilities. To enable this, set the following environment variables.
|
|
421
430
|
|
|
422
431
|
- SBOM_SIGN_ALGORITHM: Algorithm. Example: RS512
|
|
423
432
|
- SBOM_SIGN_PRIVATE_KEY: Location to the RSA private key
|
package/analyzer.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { parse } from "@babel/parser";
|
|
2
2
|
import traverse from "@babel/traverse";
|
|
3
|
-
import { join } from "path";
|
|
4
|
-
import { readdirSync, statSync, readFileSync } from "fs";
|
|
5
|
-
import { basename, resolve, isAbsolute, relative } from "path";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { readdirSync, statSync, readFileSync } from "node:fs";
|
|
5
|
+
import { basename, resolve, isAbsolute, relative } from "node:path";
|
|
6
6
|
|
|
7
7
|
const IGNORE_DIRS = process.env.ASTGEN_IGNORE_DIRS
|
|
8
8
|
? process.env.ASTGEN_IGNORE_DIRS.split(",")
|
package/bin/cdxgen.js
CHANGED
|
@@ -6,8 +6,7 @@ import fs from "node:fs";
|
|
|
6
6
|
import { tmpdir } from "node:os";
|
|
7
7
|
import { basename, dirname, join, resolve } from "node:path";
|
|
8
8
|
import jws from "jws";
|
|
9
|
-
import crypto from "crypto";
|
|
10
|
-
import { start as _serverStart } from "../server.js";
|
|
9
|
+
import crypto from "node:crypto";
|
|
11
10
|
import { fileURLToPath } from "node:url";
|
|
12
11
|
import globalAgent from "global-agent";
|
|
13
12
|
import process from "node:process";
|
|
@@ -243,7 +242,8 @@ const checkPermissions = (filePath) => {
|
|
|
243
242
|
(async () => {
|
|
244
243
|
// Start SBOM server
|
|
245
244
|
if (args.server) {
|
|
246
|
-
|
|
245
|
+
const serverModule = await import("../server.js");
|
|
246
|
+
return await serverModule.start(options);
|
|
247
247
|
}
|
|
248
248
|
// Check if cdxgen has the required permissions
|
|
249
249
|
if (!checkPermissions(filePath)) {
|
package/bin/repl.js
CHANGED
|
@@ -110,16 +110,16 @@ cdxgenRepl.defineCommand("create", {
|
|
|
110
110
|
});
|
|
111
111
|
if (bomNSData) {
|
|
112
112
|
sbom = bomNSData.bomJson;
|
|
113
|
-
console.log("✅
|
|
114
|
-
console.log("💭 Type .print to view the
|
|
113
|
+
console.log("✅ BOM imported successfully.");
|
|
114
|
+
console.log("💭 Type .print to view the BOM as a table");
|
|
115
115
|
} else {
|
|
116
|
-
console.log("
|
|
116
|
+
console.log("BOM was not generated successfully");
|
|
117
117
|
}
|
|
118
118
|
this.displayPrompt();
|
|
119
119
|
}
|
|
120
120
|
});
|
|
121
121
|
cdxgenRepl.defineCommand("import", {
|
|
122
|
-
help: "import an existing
|
|
122
|
+
help: "import an existing BOM",
|
|
123
123
|
action(sbomOrPath) {
|
|
124
124
|
this.clearBufferedCommand();
|
|
125
125
|
importSbom(sbomOrPath);
|
|
@@ -139,7 +139,7 @@ cdxgenRepl.defineCommand("sbom", {
|
|
|
139
139
|
console.log(sbom);
|
|
140
140
|
} else {
|
|
141
141
|
console.log(
|
|
142
|
-
"⚠ No
|
|
142
|
+
"⚠ No BOM is loaded. Use .import command to import an existing BOM"
|
|
143
143
|
);
|
|
144
144
|
}
|
|
145
145
|
this.displayPrompt();
|
|
@@ -171,7 +171,7 @@ cdxgenRepl.defineCommand("search", {
|
|
|
171
171
|
}
|
|
172
172
|
} else {
|
|
173
173
|
console.log(
|
|
174
|
-
"⚠ No
|
|
174
|
+
"⚠ No BOM is loaded. Use .import command to import an existing BOM"
|
|
175
175
|
);
|
|
176
176
|
}
|
|
177
177
|
this.displayPrompt();
|
|
@@ -205,7 +205,7 @@ cdxgenRepl.defineCommand("sort", {
|
|
|
205
205
|
}
|
|
206
206
|
} else {
|
|
207
207
|
console.log(
|
|
208
|
-
"⚠ No
|
|
208
|
+
"⚠ No BOM is loaded. Use .import command to import an existing BOM"
|
|
209
209
|
);
|
|
210
210
|
}
|
|
211
211
|
this.displayPrompt();
|
|
@@ -229,7 +229,7 @@ cdxgenRepl.defineCommand("query", {
|
|
|
229
229
|
}
|
|
230
230
|
} else {
|
|
231
231
|
console.log(
|
|
232
|
-
"⚠ No
|
|
232
|
+
"⚠ No BOM is loaded. Use .import command to import an existing BOM"
|
|
233
233
|
);
|
|
234
234
|
}
|
|
235
235
|
this.displayPrompt();
|
|
@@ -242,7 +242,7 @@ cdxgenRepl.defineCommand("print", {
|
|
|
242
242
|
printTable(sbom);
|
|
243
243
|
} else {
|
|
244
244
|
console.log(
|
|
245
|
-
"⚠ No
|
|
245
|
+
"⚠ No BOM is loaded. Use .import command to import an existing BOM"
|
|
246
246
|
);
|
|
247
247
|
}
|
|
248
248
|
this.displayPrompt();
|
|
@@ -255,7 +255,7 @@ cdxgenRepl.defineCommand("tree", {
|
|
|
255
255
|
printDependencyTree(sbom);
|
|
256
256
|
} else {
|
|
257
257
|
console.log(
|
|
258
|
-
"⚠ No
|
|
258
|
+
"⚠ No BOM is loaded. Use .import command to import an existing BOM"
|
|
259
259
|
);
|
|
260
260
|
}
|
|
261
261
|
this.displayPrompt();
|
|
@@ -271,7 +271,7 @@ cdxgenRepl.defineCommand("validate", {
|
|
|
271
271
|
}
|
|
272
272
|
} else {
|
|
273
273
|
console.log(
|
|
274
|
-
"⚠ No
|
|
274
|
+
"⚠ No BOM is loaded. Use .import command to import an existing BOM"
|
|
275
275
|
);
|
|
276
276
|
}
|
|
277
277
|
this.displayPrompt();
|
|
@@ -285,10 +285,10 @@ cdxgenRepl.defineCommand("save", {
|
|
|
285
285
|
saveToFile = "bom.json";
|
|
286
286
|
}
|
|
287
287
|
fs.writeFileSync(saveToFile, JSON.stringify(sbom, null, 2));
|
|
288
|
-
console.log(`
|
|
288
|
+
console.log(`BOM saved successfully to ${saveToFile}`);
|
|
289
289
|
} else {
|
|
290
290
|
console.log(
|
|
291
|
-
"⚠ No
|
|
291
|
+
"⚠ No BOM is loaded. Use .import command to import an existing BOM"
|
|
292
292
|
);
|
|
293
293
|
}
|
|
294
294
|
this.displayPrompt();
|
|
@@ -313,10 +313,10 @@ cdxgenRepl.defineCommand("update", {
|
|
|
313
313
|
if (newSbom && newSbom.components.length <= sbom.components.length) {
|
|
314
314
|
sbom = newSbom;
|
|
315
315
|
}
|
|
316
|
-
console.log("
|
|
316
|
+
console.log("BOM updated successfully.");
|
|
317
317
|
} else {
|
|
318
318
|
console.log(
|
|
319
|
-
"⚠ No
|
|
319
|
+
"⚠ No BOM is loaded. Use .import command to import an existing BOM"
|
|
320
320
|
);
|
|
321
321
|
}
|
|
322
322
|
this.displayPrompt();
|
|
@@ -333,7 +333,7 @@ cdxgenRepl.defineCommand("occurrences", {
|
|
|
333
333
|
let components = await expression.evaluate(sbom);
|
|
334
334
|
if (!components) {
|
|
335
335
|
console.log(
|
|
336
|
-
"No results found. Use evinse command to generate an
|
|
336
|
+
"No results found. Use evinse command to generate an BOM with evidence."
|
|
337
337
|
);
|
|
338
338
|
} else {
|
|
339
339
|
if (!Array.isArray(components)) {
|
|
@@ -346,7 +346,7 @@ cdxgenRepl.defineCommand("occurrences", {
|
|
|
346
346
|
}
|
|
347
347
|
} else {
|
|
348
348
|
console.log(
|
|
349
|
-
"⚠ No
|
|
349
|
+
"⚠ No BOM is loaded. Use .import command to import an evinse BOM"
|
|
350
350
|
);
|
|
351
351
|
}
|
|
352
352
|
this.displayPrompt();
|
|
@@ -437,7 +437,7 @@ cdxgenRepl.defineCommand("osinfocategories", {
|
|
|
437
437
|
let catgories = await expression.evaluate(sbom);
|
|
438
438
|
if (!catgories) {
|
|
439
439
|
console.log(
|
|
440
|
-
"Unable to retrieve the os info categories. Only
|
|
440
|
+
"Unable to retrieve the os info categories. Only OBOMs generated by cdxgen are supported by this tool."
|
|
441
441
|
);
|
|
442
442
|
} else {
|
|
443
443
|
console.log(catgories.join("\n"));
|
|
@@ -447,7 +447,7 @@ cdxgenRepl.defineCommand("osinfocategories", {
|
|
|
447
447
|
}
|
|
448
448
|
} else {
|
|
449
449
|
console.log(
|
|
450
|
-
"⚠ No
|
|
450
|
+
"⚠ No OBOM is loaded. Use .import command to import an OBOM"
|
|
451
451
|
);
|
|
452
452
|
}
|
|
453
453
|
this.displayPrompt();
|
|
@@ -546,7 +546,7 @@ cdxgenRepl.defineCommand("osinfocategories", {
|
|
|
546
546
|
}
|
|
547
547
|
} else {
|
|
548
548
|
console.log(
|
|
549
|
-
"⚠ No
|
|
549
|
+
"⚠ No OBOM is loaded. Use .import command to import an OBOM"
|
|
550
550
|
);
|
|
551
551
|
}
|
|
552
552
|
this.displayPrompt();
|
package/binary.js
CHANGED
|
@@ -230,7 +230,7 @@ export const getGoBuildInfo = (src) => {
|
|
|
230
230
|
let result = spawnSync(GOVERSION_BIN, [src], {
|
|
231
231
|
encoding: "utf-8"
|
|
232
232
|
});
|
|
233
|
-
if (result.status !== 0 || result.error) {
|
|
233
|
+
if (result.status !== 0 || result.error || !result.stdout) {
|
|
234
234
|
if (result.stdout || result.stderr) {
|
|
235
235
|
console.error(result.stdout, result.stderr);
|
|
236
236
|
}
|
package/docker.js
CHANGED
package/index.js
CHANGED
|
@@ -60,6 +60,7 @@ import {
|
|
|
60
60
|
parseGoVersionData,
|
|
61
61
|
parseGosumData,
|
|
62
62
|
parseGoListDep,
|
|
63
|
+
parseGoModGraph,
|
|
63
64
|
parseGoModWhy,
|
|
64
65
|
parseGoModData,
|
|
65
66
|
parseGopkgData,
|
|
@@ -2309,24 +2310,36 @@ export const createPythonBom = async (path, options) => {
|
|
|
2309
2310
|
for (const f of poetryFiles) {
|
|
2310
2311
|
const basePath = dirname(f);
|
|
2311
2312
|
const lockData = readFileSync(f, { encoding: "utf-8" });
|
|
2312
|
-
|
|
2313
|
-
if (
|
|
2314
|
-
pkgList = pkgList.concat(
|
|
2315
|
-
|
|
2316
|
-
const pkgMap = getPipFrozenTree(basePath, f, tempDir);
|
|
2317
|
-
if (pkgMap.pkgList && pkgMap.pkgList.length) {
|
|
2318
|
-
pkgList = pkgList.concat(pkgMap.pkgList);
|
|
2313
|
+
let retMap = await parsePoetrylockData(lockData, f);
|
|
2314
|
+
if (retMap.pkgList && retMap.pkgList.length) {
|
|
2315
|
+
pkgList = pkgList.concat(retMap.pkgList);
|
|
2316
|
+
pkgList = trimComponents(pkgList, "json");
|
|
2319
2317
|
}
|
|
2320
|
-
if (
|
|
2318
|
+
if (retMap.dependenciesList && retMap.dependenciesList.length) {
|
|
2321
2319
|
dependencies = mergeDependencies(
|
|
2322
2320
|
dependencies,
|
|
2323
|
-
|
|
2321
|
+
retMap.dependenciesList,
|
|
2324
2322
|
parentComponent
|
|
2325
2323
|
);
|
|
2326
2324
|
}
|
|
2325
|
+
// Retrieve the tree using virtualenv in deep mode and as a fallback
|
|
2326
|
+
// This is a slow operation
|
|
2327
|
+
if (options.deep || !dependencies.length) {
|
|
2328
|
+
retMap = getPipFrozenTree(basePath, f, tempDir);
|
|
2329
|
+
if (retMap.pkgList && retMap.pkgList.length) {
|
|
2330
|
+
pkgList = pkgList.concat(retMap.pkgList);
|
|
2331
|
+
}
|
|
2332
|
+
if (retMap.dependenciesList) {
|
|
2333
|
+
dependencies = mergeDependencies(
|
|
2334
|
+
dependencies,
|
|
2335
|
+
retMap.dependenciesList,
|
|
2336
|
+
parentComponent
|
|
2337
|
+
);
|
|
2338
|
+
}
|
|
2339
|
+
}
|
|
2327
2340
|
const parentDependsOn = [];
|
|
2328
2341
|
// Complete the dependency tree by making parent component depend on the first level
|
|
2329
|
-
for (const p of
|
|
2342
|
+
for (const p of retMap.rootList) {
|
|
2330
2343
|
parentDependsOn.push(`pkg:pypi/${p.name}@${p.version}`);
|
|
2331
2344
|
}
|
|
2332
2345
|
const pdependencies = {
|
|
@@ -2397,6 +2410,8 @@ export const createPythonBom = async (path, options) => {
|
|
|
2397
2410
|
let frozen = false;
|
|
2398
2411
|
// Attempt to pip freeze in a virtualenv to improve precision
|
|
2399
2412
|
if (options.installDeps) {
|
|
2413
|
+
// If there are multiple requirements files then the tree is getting constructed for each one
|
|
2414
|
+
// adding to the delay.
|
|
2400
2415
|
const pkgMap = getPipFrozenTree(basePath, f, tempDir);
|
|
2401
2416
|
if (pkgMap.pkgList && pkgMap.pkgList.length) {
|
|
2402
2417
|
pkgList = pkgList.concat(pkgMap.pkgList);
|
|
@@ -2454,7 +2469,7 @@ export const createPythonBom = async (path, options) => {
|
|
|
2454
2469
|
}
|
|
2455
2470
|
// Get the imported modules and a dedupe list of packages
|
|
2456
2471
|
const parentDependsOn = new Set();
|
|
2457
|
-
const retMap = await getPyModules(path, pkgList);
|
|
2472
|
+
const retMap = await getPyModules(path, pkgList, options);
|
|
2458
2473
|
if (retMap.pkgList && retMap.pkgList.length) {
|
|
2459
2474
|
pkgList = pkgList.concat(retMap.pkgList);
|
|
2460
2475
|
for (const p of retMap.pkgList) {
|
|
@@ -2548,6 +2563,9 @@ export const createPythonBom = async (path, options) => {
|
|
|
2548
2563
|
*/
|
|
2549
2564
|
export const createGoBom = async (path, options) => {
|
|
2550
2565
|
let pkgList = [];
|
|
2566
|
+
let dependencies = [];
|
|
2567
|
+
const allImports = {};
|
|
2568
|
+
let parentComponent = createDefaultParentComponent(path, "golang", options);
|
|
2551
2569
|
// Is this a binary file
|
|
2552
2570
|
let maybeBinary = false;
|
|
2553
2571
|
try {
|
|
@@ -2569,6 +2587,8 @@ export const createGoBom = async (path, options) => {
|
|
|
2569
2587
|
}
|
|
2570
2588
|
return buildBomNSData(options, pkgList, "golang", {
|
|
2571
2589
|
allImports,
|
|
2590
|
+
dependencies,
|
|
2591
|
+
parentComponent,
|
|
2572
2592
|
src: path,
|
|
2573
2593
|
filename: path
|
|
2574
2594
|
});
|
|
@@ -2598,8 +2618,70 @@ export const createGoBom = async (path, options) => {
|
|
|
2598
2618
|
pkgList = pkgList.concat(dlist);
|
|
2599
2619
|
}
|
|
2600
2620
|
}
|
|
2621
|
+
const doneList = {};
|
|
2622
|
+
let circuitBreak = false;
|
|
2623
|
+
if (DEBUG_MODE) {
|
|
2624
|
+
console.log(
|
|
2625
|
+
`Attempting to detect required packages using "go mod why" command for ${pkgList.length} packages`
|
|
2626
|
+
);
|
|
2627
|
+
}
|
|
2628
|
+
// Using go mod why detect required packages
|
|
2629
|
+
for (const apkg of pkgList) {
|
|
2630
|
+
if (circuitBreak) {
|
|
2631
|
+
break;
|
|
2632
|
+
}
|
|
2633
|
+
const pkgFullName = `${apkg.name}`;
|
|
2634
|
+
if (apkg.scope === "required") {
|
|
2635
|
+
allImports[pkgFullName] = true;
|
|
2636
|
+
continue;
|
|
2637
|
+
}
|
|
2638
|
+
if (
|
|
2639
|
+
apkg.scope === "optional" ||
|
|
2640
|
+
allImports[pkgFullName] ||
|
|
2641
|
+
doneList[pkgFullName]
|
|
2642
|
+
) {
|
|
2643
|
+
continue;
|
|
2644
|
+
}
|
|
2645
|
+
if (DEBUG_MODE) {
|
|
2646
|
+
console.log(`go mod why -m -vendor ${pkgFullName}`);
|
|
2647
|
+
}
|
|
2648
|
+
const mresult = spawnSync(
|
|
2649
|
+
"go",
|
|
2650
|
+
["mod", "why", "-m", "-vendor", pkgFullName],
|
|
2651
|
+
{ cwd: path, encoding: "utf-8", timeout: TIMEOUT_MS }
|
|
2652
|
+
);
|
|
2653
|
+
if (mresult.status !== 0 || mresult.error) {
|
|
2654
|
+
if (DEBUG_MODE) {
|
|
2655
|
+
if (mresult.stdout) {
|
|
2656
|
+
console.log(mresult.stdout);
|
|
2657
|
+
}
|
|
2658
|
+
if (mresult.stderr) {
|
|
2659
|
+
console.log(mresult.stderr);
|
|
2660
|
+
}
|
|
2661
|
+
}
|
|
2662
|
+
circuitBreak = true;
|
|
2663
|
+
} else {
|
|
2664
|
+
const mstdout = mresult.stdout;
|
|
2665
|
+
if (mstdout) {
|
|
2666
|
+
const cmdOutput = Buffer.from(mstdout).toString();
|
|
2667
|
+
const whyPkg = parseGoModWhy(cmdOutput);
|
|
2668
|
+
// whyPkg would include this package string
|
|
2669
|
+
// github.com/golang/protobuf/proto github.com/golang/protobuf
|
|
2670
|
+
// golang.org/x/tools/cmd/goimports golang.org/x/tools
|
|
2671
|
+
if (whyPkg && whyPkg.includes(pkgFullName)) {
|
|
2672
|
+
allImports[pkgFullName] = true;
|
|
2673
|
+
}
|
|
2674
|
+
doneList[pkgFullName] = true;
|
|
2675
|
+
}
|
|
2676
|
+
}
|
|
2677
|
+
}
|
|
2678
|
+
if (DEBUG_MODE) {
|
|
2679
|
+
console.log(`Required packages: ${Object.keys(allImports).length}`);
|
|
2680
|
+
}
|
|
2601
2681
|
return buildBomNSData(options, pkgList, "golang", {
|
|
2602
2682
|
src: path,
|
|
2683
|
+
dependencies,
|
|
2684
|
+
parentComponent,
|
|
2603
2685
|
filename: gosumFiles.join(", ")
|
|
2604
2686
|
});
|
|
2605
2687
|
}
|
|
@@ -2615,7 +2697,7 @@ export const createGoBom = async (path, options) => {
|
|
|
2615
2697
|
const dlist = await parseGosumData(gosumData);
|
|
2616
2698
|
if (dlist && dlist.length) {
|
|
2617
2699
|
dlist.forEach((pkg) => {
|
|
2618
|
-
gosumMap[`${pkg.group}/${pkg.name}
|
|
2700
|
+
gosumMap[`${pkg.group}/${pkg.name}@${pkg.version}`] = pkg._integrity;
|
|
2619
2701
|
});
|
|
2620
2702
|
}
|
|
2621
2703
|
}
|
|
@@ -2634,7 +2716,7 @@ export const createGoBom = async (path, options) => {
|
|
|
2634
2716
|
);
|
|
2635
2717
|
if (gomodFiles.length) {
|
|
2636
2718
|
let shouldManuallyParse = false;
|
|
2637
|
-
// Use the go list -deps and go mod why commands to generate a good quality
|
|
2719
|
+
// Use the go list -deps and go mod why commands to generate a good quality BOM for non-docker invocations
|
|
2638
2720
|
if (!["docker", "oci", "os"].includes(options.projectType)) {
|
|
2639
2721
|
for (const f of gomodFiles) {
|
|
2640
2722
|
const basePath = dirname(f);
|
|
@@ -2642,36 +2724,85 @@ export const createGoBom = async (path, options) => {
|
|
|
2642
2724
|
if (basePath.includes("/vendor/") || basePath.includes("/build/")) {
|
|
2643
2725
|
continue;
|
|
2644
2726
|
}
|
|
2727
|
+
// First we execute the go list -deps command which gives the correct list of dependencies
|
|
2645
2728
|
if (DEBUG_MODE) {
|
|
2646
2729
|
console.log("Executing go list -deps in", basePath);
|
|
2647
2730
|
}
|
|
2648
|
-
|
|
2731
|
+
let result = spawnSync(
|
|
2649
2732
|
"go",
|
|
2650
2733
|
[
|
|
2651
2734
|
"list",
|
|
2652
2735
|
"-deps",
|
|
2653
2736
|
"-f",
|
|
2654
|
-
"'{{with .Module}}{{.Path}} {{.Version}} {{.Indirect}} {{.GoMod}} {{.GoVersion}}{{end}}'",
|
|
2737
|
+
"'{{with .Module}}{{.Path}} {{.Version}} {{.Indirect}} {{.GoMod}} {{.GoVersion}} {{.Main}}{{end}}'",
|
|
2655
2738
|
"./..."
|
|
2656
2739
|
],
|
|
2657
2740
|
{ cwd: basePath, encoding: "utf-8", timeout: TIMEOUT_MS }
|
|
2658
2741
|
);
|
|
2742
|
+
if (DEBUG_MODE) {
|
|
2743
|
+
console.log("Executing go mod graph in", basePath);
|
|
2744
|
+
}
|
|
2659
2745
|
if (result.status !== 0 || result.error) {
|
|
2660
2746
|
shouldManuallyParse = true;
|
|
2661
|
-
if (result.stdout) {
|
|
2747
|
+
if (DEBUG_MODE && result.stdout) {
|
|
2662
2748
|
console.log(result.stdout);
|
|
2663
2749
|
}
|
|
2664
|
-
if (result.stderr) {
|
|
2750
|
+
if (DEBUG_MODE && result.stderr) {
|
|
2665
2751
|
console.log(result.stderr);
|
|
2666
2752
|
}
|
|
2667
2753
|
options.failOnError && process.exit(1);
|
|
2668
2754
|
}
|
|
2669
2755
|
const stdout = result.stdout;
|
|
2670
2756
|
if (stdout) {
|
|
2671
|
-
|
|
2672
|
-
const
|
|
2673
|
-
if (
|
|
2674
|
-
pkgList = pkgList.concat(
|
|
2757
|
+
let cmdOutput = Buffer.from(stdout).toString();
|
|
2758
|
+
const retMap = await parseGoListDep(cmdOutput, gosumMap);
|
|
2759
|
+
if (retMap.pkgList && retMap.pkgList.length) {
|
|
2760
|
+
pkgList = pkgList.concat(retMap.pkgList);
|
|
2761
|
+
}
|
|
2762
|
+
// We treat the main module as our parent
|
|
2763
|
+
if (
|
|
2764
|
+
retMap.parentComponent &&
|
|
2765
|
+
Object.keys(retMap.parentComponent).length
|
|
2766
|
+
) {
|
|
2767
|
+
parentComponent = retMap.parentComponent;
|
|
2768
|
+
parentComponent.type = "application";
|
|
2769
|
+
}
|
|
2770
|
+
// Next we use the go mod graph command to construct the dependency tree
|
|
2771
|
+
result = spawnSync("go", ["mod", "graph"], {
|
|
2772
|
+
cwd: basePath,
|
|
2773
|
+
encoding: "utf-8",
|
|
2774
|
+
timeout: TIMEOUT_MS
|
|
2775
|
+
});
|
|
2776
|
+
// Check if got a mod graph successfully
|
|
2777
|
+
if (result.status !== 0 || result.error) {
|
|
2778
|
+
if (DEBUG_MODE && result.stdout) {
|
|
2779
|
+
console.log(result.stdout);
|
|
2780
|
+
}
|
|
2781
|
+
if (DEBUG_MODE && result.stderr) {
|
|
2782
|
+
console.log(result.stderr);
|
|
2783
|
+
}
|
|
2784
|
+
options.failOnError && process.exit(1);
|
|
2785
|
+
}
|
|
2786
|
+
if (result.stdout) {
|
|
2787
|
+
cmdOutput = Buffer.from(result.stdout).toString();
|
|
2788
|
+
const retMap = await parseGoModGraph(
|
|
2789
|
+
cmdOutput,
|
|
2790
|
+
f,
|
|
2791
|
+
gosumMap,
|
|
2792
|
+
pkgList,
|
|
2793
|
+
parentComponent
|
|
2794
|
+
);
|
|
2795
|
+
if (retMap.pkgList && retMap.pkgList.length) {
|
|
2796
|
+
pkgList = pkgList.concat(retMap.pkgList);
|
|
2797
|
+
pkgList = trimComponents(pkgList, "json");
|
|
2798
|
+
}
|
|
2799
|
+
if (retMap.dependenciesList && retMap.dependenciesList.length) {
|
|
2800
|
+
dependencies = mergeDependencies(
|
|
2801
|
+
dependencies,
|
|
2802
|
+
retMap.dependenciesList,
|
|
2803
|
+
parentComponent
|
|
2804
|
+
);
|
|
2805
|
+
}
|
|
2675
2806
|
}
|
|
2676
2807
|
} else {
|
|
2677
2808
|
shouldManuallyParse = true;
|
|
@@ -2681,67 +2812,20 @@ export const createGoBom = async (path, options) => {
|
|
|
2681
2812
|
options.failOnError && process.exit(1);
|
|
2682
2813
|
}
|
|
2683
2814
|
}
|
|
2684
|
-
const allImports = {};
|
|
2685
|
-
let circuitBreak = false;
|
|
2686
|
-
if (DEBUG_MODE) {
|
|
2687
|
-
console.log(
|
|
2688
|
-
`Attempting to detect required packages using "go mod why" command for ${pkgList.length} packages`
|
|
2689
|
-
);
|
|
2690
|
-
}
|
|
2691
|
-
// Using go mod why detect required packages
|
|
2692
|
-
for (const apkg of pkgList) {
|
|
2693
|
-
if (circuitBreak) {
|
|
2694
|
-
break;
|
|
2695
|
-
}
|
|
2696
|
-
const pkgFullName = `${apkg.name}`;
|
|
2697
|
-
if (apkg.scope === "required") {
|
|
2698
|
-
allImports[pkgFullName] = true;
|
|
2699
|
-
continue;
|
|
2700
|
-
}
|
|
2701
|
-
if (DEBUG_MODE) {
|
|
2702
|
-
console.log(`go mod why -m -vendor ${pkgFullName}`);
|
|
2703
|
-
}
|
|
2704
|
-
const mresult = spawnSync(
|
|
2705
|
-
"go",
|
|
2706
|
-
["mod", "why", "-m", "-vendor", pkgFullName],
|
|
2707
|
-
{ cwd: path, encoding: "utf-8", timeout: TIMEOUT_MS }
|
|
2708
|
-
);
|
|
2709
|
-
if (mresult.status !== 0 || mresult.error) {
|
|
2710
|
-
if (DEBUG_MODE) {
|
|
2711
|
-
if (mresult.stdout) {
|
|
2712
|
-
console.log(mresult.stdout);
|
|
2713
|
-
}
|
|
2714
|
-
if (mresult.stderr) {
|
|
2715
|
-
console.log(mresult.stderr);
|
|
2716
|
-
}
|
|
2717
|
-
}
|
|
2718
|
-
circuitBreak = true;
|
|
2719
|
-
} else {
|
|
2720
|
-
const mstdout = mresult.stdout;
|
|
2721
|
-
if (mstdout) {
|
|
2722
|
-
const cmdOutput = Buffer.from(mstdout).toString();
|
|
2723
|
-
const whyPkg = parseGoModWhy(cmdOutput);
|
|
2724
|
-
if (whyPkg == pkgFullName) {
|
|
2725
|
-
allImports[pkgFullName] = true;
|
|
2726
|
-
}
|
|
2727
|
-
}
|
|
2728
|
-
}
|
|
2729
|
-
}
|
|
2730
|
-
if (DEBUG_MODE) {
|
|
2731
|
-
console.log(`Required packages: ${Object.keys(allImports).length}`);
|
|
2732
|
-
}
|
|
2733
2815
|
if (pkgList.length && !shouldManuallyParse) {
|
|
2734
2816
|
return buildBomNSData(options, pkgList, "golang", {
|
|
2735
2817
|
allImports,
|
|
2818
|
+
dependencies,
|
|
2819
|
+
parentComponent,
|
|
2736
2820
|
src: path,
|
|
2737
2821
|
filename: gomodFiles.join(", ")
|
|
2738
2822
|
});
|
|
2739
2823
|
}
|
|
2740
2824
|
}
|
|
2741
|
-
// Parse the gomod files manually. The resultant
|
|
2825
|
+
// Parse the gomod files manually. The resultant BOM would be incomplete
|
|
2742
2826
|
if (!["docker", "oci", "os"].includes(options.projectType)) {
|
|
2743
2827
|
console.log(
|
|
2744
|
-
"Manually parsing go.mod files. The resultant
|
|
2828
|
+
"Manually parsing go.mod files. The resultant BOM would be incomplete."
|
|
2745
2829
|
);
|
|
2746
2830
|
}
|
|
2747
2831
|
for (const f of gomodFiles) {
|
|
@@ -2756,6 +2840,8 @@ export const createGoBom = async (path, options) => {
|
|
|
2756
2840
|
}
|
|
2757
2841
|
return buildBomNSData(options, pkgList, "golang", {
|
|
2758
2842
|
src: path,
|
|
2843
|
+
dependencies,
|
|
2844
|
+
parentComponent,
|
|
2759
2845
|
filename: gomodFiles.join(", ")
|
|
2760
2846
|
});
|
|
2761
2847
|
} else if (gopkgLockFiles.length) {
|
|
@@ -2773,6 +2859,8 @@ export const createGoBom = async (path, options) => {
|
|
|
2773
2859
|
}
|
|
2774
2860
|
return buildBomNSData(options, pkgList, "golang", {
|
|
2775
2861
|
src: path,
|
|
2862
|
+
dependencies,
|
|
2863
|
+
parentComponent,
|
|
2776
2864
|
filename: gopkgLockFiles.join(", ")
|
|
2777
2865
|
});
|
|
2778
2866
|
}
|
|
@@ -3285,7 +3373,7 @@ export const createCloudBuildBom = (path, options) => {
|
|
|
3285
3373
|
*/
|
|
3286
3374
|
export const createOSBom = (path, options) => {
|
|
3287
3375
|
console.warn(
|
|
3288
|
-
"About to generate
|
|
3376
|
+
"About to generate OBOM for the current OS installation. This will take several minutes ..."
|
|
3289
3377
|
);
|
|
3290
3378
|
let pkgList = [];
|
|
3291
3379
|
let bomData = {};
|
|
@@ -4165,7 +4253,7 @@ export const dedupeBom = (
|
|
|
4165
4253
|
componentsXmls = trimComponents(componentsXmls, "xml");
|
|
4166
4254
|
if (DEBUG_MODE) {
|
|
4167
4255
|
console.log(
|
|
4168
|
-
`
|
|
4256
|
+
`BOM includes ${components.length} components and ${dependencies.length} dependencies after dedupe`
|
|
4169
4257
|
);
|
|
4170
4258
|
}
|
|
4171
4259
|
const serialNum = "urn:uuid:" + uuidv4();
|