@cyclonedx/cdxgen 8.0.0
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/.eslintrc.js +15 -0
- package/LICENSE +201 -0
- package/README.md +354 -0
- package/analyzer.js +189 -0
- package/bin/cdxgen +316 -0
- package/binary.js +507 -0
- package/docker.js +769 -0
- package/docker.test.js +72 -0
- package/index.js +4292 -0
- package/jest.config.js +181 -0
- package/known-licenses.json +27 -0
- package/lic-mapping.json +294 -0
- package/package.json +94 -0
- package/queries.json +68 -0
- package/server.js +110 -0
- package/spdx-licenses.json +500 -0
- package/utils.js +4284 -0
- package/utils.test.js +1660 -0
- package/vendor-alias.json +10 -0
package/index.js
ADDED
|
@@ -0,0 +1,4292 @@
|
|
|
1
|
+
const parsePackageJsonName = require("parse-packagejson-name");
|
|
2
|
+
const os = require("os");
|
|
3
|
+
const pathLib = require("path");
|
|
4
|
+
const ssri = require("ssri");
|
|
5
|
+
const fs = require("fs");
|
|
6
|
+
const got = require("got");
|
|
7
|
+
const { v4: uuidv4 } = require("uuid");
|
|
8
|
+
const { PackageURL } = require("packageurl-js");
|
|
9
|
+
const builder = require("xmlbuilder");
|
|
10
|
+
const utils = require("./utils");
|
|
11
|
+
const { spawnSync } = require("child_process");
|
|
12
|
+
const selfPjson = require("./package.json");
|
|
13
|
+
const { findJSImports } = require("./analyzer");
|
|
14
|
+
const semver = require("semver");
|
|
15
|
+
const dockerLib = require("./docker");
|
|
16
|
+
const binaryLib = require("./binary");
|
|
17
|
+
const osQueries = require("./queries.json");
|
|
18
|
+
const isWin = require("os").platform() === "win32";
|
|
19
|
+
|
|
20
|
+
const { table } = require("table");
|
|
21
|
+
|
|
22
|
+
// Construct gradle cache directory
|
|
23
|
+
let GRADLE_CACHE_DIR =
|
|
24
|
+
process.env.GRADLE_CACHE_DIR ||
|
|
25
|
+
pathLib.join(os.homedir(), ".gradle", "caches", "modules-2", "files-2.1");
|
|
26
|
+
if (process.env.GRADLE_USER_HOME) {
|
|
27
|
+
GRADLE_CACHE_DIR =
|
|
28
|
+
process.env.GRADLE_USER_HOME + "/caches/modules-2/files-2.1";
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Clojure CLI
|
|
32
|
+
let CLJ_CMD = "clj";
|
|
33
|
+
if (process.env.CLJ_CMD) {
|
|
34
|
+
CLJ_CMD = process.env.CLJ_CMD;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
let LEIN_CMD = "lein";
|
|
38
|
+
if (process.env.LEIN_CMD) {
|
|
39
|
+
LEIN_CMD = process.env.LEIN_CMD;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Construct sbt cache directory
|
|
43
|
+
let SBT_CACHE_DIR =
|
|
44
|
+
process.env.SBT_CACHE_DIR || pathLib.join(os.homedir(), ".ivy2", "cache");
|
|
45
|
+
|
|
46
|
+
// Debug mode flag
|
|
47
|
+
const DEBUG_MODE =
|
|
48
|
+
process.env.SCAN_DEBUG_MODE === "debug" ||
|
|
49
|
+
process.env.SHIFTLEFT_LOGGING_LEVEL === "debug" ||
|
|
50
|
+
process.env.NODE_ENV === "development";
|
|
51
|
+
|
|
52
|
+
// CycloneDX Hash pattern
|
|
53
|
+
const HASH_PATTERN =
|
|
54
|
+
"^([a-fA-F0-9]{32}|[a-fA-F0-9]{40}|[a-fA-F0-9]{64}|[a-fA-F0-9]{96}|[a-fA-F0-9]{128})$";
|
|
55
|
+
|
|
56
|
+
// Timeout milliseconds. Default 10 mins
|
|
57
|
+
const TIMEOUT_MS = parseInt(process.env.CDXGEN_TIMEOUT_MS) || 10 * 60 * 1000;
|
|
58
|
+
|
|
59
|
+
const determineParentComponent = (options) => {
|
|
60
|
+
let parentComponent = undefined;
|
|
61
|
+
if (options.projectName) {
|
|
62
|
+
parentComponent = {
|
|
63
|
+
group: options.projectGroup || "",
|
|
64
|
+
name: options.projectName,
|
|
65
|
+
version: "" + options.projectVersion || "",
|
|
66
|
+
type: "application"
|
|
67
|
+
};
|
|
68
|
+
} else if (
|
|
69
|
+
options.parentComponent &&
|
|
70
|
+
Object.keys(options.parentComponent).length
|
|
71
|
+
) {
|
|
72
|
+
return options.parentComponent;
|
|
73
|
+
}
|
|
74
|
+
return parentComponent;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Method to create global external references
|
|
79
|
+
*
|
|
80
|
+
* @param pkg
|
|
81
|
+
* @returns {Array}
|
|
82
|
+
*/
|
|
83
|
+
function addGlobalReferences(src, filename, format = "xml") {
|
|
84
|
+
let externalReferences = [];
|
|
85
|
+
if (format === "json") {
|
|
86
|
+
externalReferences.push({
|
|
87
|
+
type: "other",
|
|
88
|
+
url: src,
|
|
89
|
+
comment: "Base path"
|
|
90
|
+
});
|
|
91
|
+
} else {
|
|
92
|
+
externalReferences.push({
|
|
93
|
+
reference: { "@type": "other", url: src, comment: "Base path" }
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
let packageFileMeta = filename;
|
|
97
|
+
if (!filename.includes(src)) {
|
|
98
|
+
packageFileMeta = pathLib.join(src, filename);
|
|
99
|
+
}
|
|
100
|
+
if (format === "json") {
|
|
101
|
+
externalReferences.push({
|
|
102
|
+
type: "other",
|
|
103
|
+
url: packageFileMeta,
|
|
104
|
+
comment: "Package file"
|
|
105
|
+
});
|
|
106
|
+
} else {
|
|
107
|
+
externalReferences.push({
|
|
108
|
+
reference: {
|
|
109
|
+
"@type": "other",
|
|
110
|
+
url: packageFileMeta,
|
|
111
|
+
comment: "Package file"
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
return externalReferences;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Function to create the services block
|
|
120
|
+
*/
|
|
121
|
+
function addServices(services, format = "xml") {
|
|
122
|
+
let serv_list = [];
|
|
123
|
+
for (const aserv of services) {
|
|
124
|
+
if (format === "xml") {
|
|
125
|
+
let service = {
|
|
126
|
+
"@bom-ref": aserv["bom-ref"],
|
|
127
|
+
group: aserv.group || "",
|
|
128
|
+
name: aserv.name,
|
|
129
|
+
version: aserv.version | "latest"
|
|
130
|
+
};
|
|
131
|
+
delete service["bom-ref"];
|
|
132
|
+
const aentry = {
|
|
133
|
+
service
|
|
134
|
+
};
|
|
135
|
+
serv_list.push(aentry);
|
|
136
|
+
} else {
|
|
137
|
+
serv_list.push(aserv);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return serv_list;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Function to create the dependency block
|
|
145
|
+
*/
|
|
146
|
+
function addDependencies(dependencies) {
|
|
147
|
+
let deps_list = [];
|
|
148
|
+
for (const adep of dependencies) {
|
|
149
|
+
let dependsOnList = adep.dependsOn.map((v) => ({
|
|
150
|
+
"@ref": v
|
|
151
|
+
}));
|
|
152
|
+
const aentry = {
|
|
153
|
+
dependency: { "@ref": adep.ref }
|
|
154
|
+
};
|
|
155
|
+
if (dependsOnList.length) {
|
|
156
|
+
aentry.dependency.dependency = dependsOnList;
|
|
157
|
+
}
|
|
158
|
+
deps_list.push(aentry);
|
|
159
|
+
}
|
|
160
|
+
return deps_list;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Function to create metadata block
|
|
165
|
+
*
|
|
166
|
+
*/
|
|
167
|
+
function addMetadata(parentComponent = {}, format = "xml", options = {}) {
|
|
168
|
+
// DO NOT fork this project to just change the vendor or author's name
|
|
169
|
+
// Try to contribute to this project by sending PR or filing issues
|
|
170
|
+
let metadata = {
|
|
171
|
+
timestamp: new Date().toISOString(),
|
|
172
|
+
tools: [
|
|
173
|
+
{
|
|
174
|
+
tool: {
|
|
175
|
+
vendor: "cyclonedx",
|
|
176
|
+
name: "cdxgen",
|
|
177
|
+
version: selfPjson.version
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
],
|
|
181
|
+
authors: [
|
|
182
|
+
{
|
|
183
|
+
author: { name: "Prabhu Subramanian", email: "prabhu@appthreat.com" }
|
|
184
|
+
}
|
|
185
|
+
],
|
|
186
|
+
supplier: undefined
|
|
187
|
+
};
|
|
188
|
+
if (format === "json") {
|
|
189
|
+
metadata.tools = [
|
|
190
|
+
{
|
|
191
|
+
vendor: "cyclonedx",
|
|
192
|
+
name: "cdxgen",
|
|
193
|
+
version: selfPjson.version
|
|
194
|
+
}
|
|
195
|
+
];
|
|
196
|
+
metadata.authors = [
|
|
197
|
+
{ name: "Prabhu Subramanian", email: "prabhu@appthreat.com" }
|
|
198
|
+
];
|
|
199
|
+
}
|
|
200
|
+
if (
|
|
201
|
+
parentComponent &&
|
|
202
|
+
Object.keys(parentComponent) &&
|
|
203
|
+
Object.keys(parentComponent).length
|
|
204
|
+
) {
|
|
205
|
+
const allPComponents = listComponents(
|
|
206
|
+
{},
|
|
207
|
+
{},
|
|
208
|
+
parentComponent,
|
|
209
|
+
parentComponent.type,
|
|
210
|
+
format
|
|
211
|
+
);
|
|
212
|
+
if (allPComponents.length) {
|
|
213
|
+
const firstPComp = allPComponents[0];
|
|
214
|
+
if (format == "xml" && firstPComp.component) {
|
|
215
|
+
metadata.component = firstPComp.component;
|
|
216
|
+
} else {
|
|
217
|
+
metadata.component = firstPComp;
|
|
218
|
+
}
|
|
219
|
+
} else {
|
|
220
|
+
// As a fallback, retain the parent component
|
|
221
|
+
if (format === "json") {
|
|
222
|
+
metadata.component = parentComponent;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
if (options) {
|
|
227
|
+
const mproperties = [];
|
|
228
|
+
if (options.exportData) {
|
|
229
|
+
const inspectData = options.exportData.inspectData;
|
|
230
|
+
if (inspectData) {
|
|
231
|
+
if (inspectData.Id) {
|
|
232
|
+
mproperties.push({
|
|
233
|
+
name: "oci:image:Id",
|
|
234
|
+
value: inspectData.Id
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
if (
|
|
238
|
+
inspectData.RepoTags &&
|
|
239
|
+
Array.isArray(inspectData.RepoTags) &&
|
|
240
|
+
inspectData.RepoTags.length
|
|
241
|
+
) {
|
|
242
|
+
mproperties.push({
|
|
243
|
+
name: "oci:image:RepoTag",
|
|
244
|
+
value: inspectData.RepoTags[0]
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
if (
|
|
248
|
+
inspectData.RepoDigests &&
|
|
249
|
+
Array.isArray(inspectData.RepoDigests) &&
|
|
250
|
+
inspectData.RepoDigests.length
|
|
251
|
+
) {
|
|
252
|
+
mproperties.push({
|
|
253
|
+
name: "oci:image:RepoDigest",
|
|
254
|
+
value: inspectData.RepoDigests[0]
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
if (inspectData.Created) {
|
|
258
|
+
mproperties.push({
|
|
259
|
+
name: "oci:image:Created",
|
|
260
|
+
value: inspectData.Created
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
if (inspectData.Architecture) {
|
|
264
|
+
mproperties.push({
|
|
265
|
+
name: "oci:image:Architecture",
|
|
266
|
+
value: inspectData.Architecture
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
if (inspectData.Os) {
|
|
270
|
+
mproperties.push({
|
|
271
|
+
name: "oci:image:Os",
|
|
272
|
+
value: inspectData.Os
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
const manifestList = options.exportData.manifest;
|
|
277
|
+
if (manifestList && Array.isArray(manifestList) && manifestList.length) {
|
|
278
|
+
const manifest = manifestList[0] || {};
|
|
279
|
+
if (manifest.Config) {
|
|
280
|
+
mproperties.push({
|
|
281
|
+
name: "oci:image:manifest:Config",
|
|
282
|
+
value: manifest.Config
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
if (
|
|
286
|
+
manifest.Layers &&
|
|
287
|
+
Array.isArray(manifest.Layers) &&
|
|
288
|
+
manifest.Layers.length
|
|
289
|
+
) {
|
|
290
|
+
mproperties.push({
|
|
291
|
+
name: "oci:image:manifest:Layers",
|
|
292
|
+
value: manifest.Layers.join("\\n")
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
const lastLayerConfig = options.exportData.lastLayerConfig;
|
|
297
|
+
if (lastLayerConfig) {
|
|
298
|
+
if (lastLayerConfig.id) {
|
|
299
|
+
mproperties.push({
|
|
300
|
+
name: "oci:image:lastLayer:Id",
|
|
301
|
+
value: lastLayerConfig.id
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
if (lastLayerConfig.parent) {
|
|
305
|
+
mproperties.push({
|
|
306
|
+
name: "oci:image:lastLayer:ParentId",
|
|
307
|
+
value: lastLayerConfig.parent
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
if (lastLayerConfig.created) {
|
|
311
|
+
mproperties.push({
|
|
312
|
+
name: "oci:image:lastLayer:Created",
|
|
313
|
+
value: lastLayerConfig.created
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
if (lastLayerConfig.config) {
|
|
317
|
+
const env = lastLayerConfig.config.Env;
|
|
318
|
+
if (env && Array.isArray(env) && env.length) {
|
|
319
|
+
mproperties.push({
|
|
320
|
+
name: "oci:image:lastLayer:Env",
|
|
321
|
+
value: env.join("\\n")
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
const ccmd = lastLayerConfig.config.Cmd;
|
|
325
|
+
if (ccmd && Array.isArray(ccmd) && ccmd.length) {
|
|
326
|
+
mproperties.push({
|
|
327
|
+
name: "oci:image:lastLayer:Cmd",
|
|
328
|
+
value: ccmd.join(" ")
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
if (options.allOSComponentTypes && options.allOSComponentTypes.length) {
|
|
335
|
+
mproperties.push({
|
|
336
|
+
name: "oci:image:componentTypes",
|
|
337
|
+
value: options.allOSComponentTypes.join("\\n")
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if (mproperties.length) {
|
|
342
|
+
if (format === "json") {
|
|
343
|
+
metadata.properties = mproperties;
|
|
344
|
+
} else {
|
|
345
|
+
metadata.properties = mproperties.map((v) => {
|
|
346
|
+
return {
|
|
347
|
+
property: {
|
|
348
|
+
"@name": v.name,
|
|
349
|
+
"#text": v.value
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
return metadata;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Method to create external references
|
|
361
|
+
*
|
|
362
|
+
* @param pkg
|
|
363
|
+
* @returns {Array}
|
|
364
|
+
*/
|
|
365
|
+
function addExternalReferences(opkg, format = "xml") {
|
|
366
|
+
let externalReferences = [];
|
|
367
|
+
let pkgList = [];
|
|
368
|
+
if (Array.isArray(opkg)) {
|
|
369
|
+
pkgList = opkg;
|
|
370
|
+
} else {
|
|
371
|
+
pkgList = [opkg];
|
|
372
|
+
}
|
|
373
|
+
for (const pkg of pkgList) {
|
|
374
|
+
if (pkg.externalReferences) {
|
|
375
|
+
if (format === "xml") {
|
|
376
|
+
for (const ref of pkg.externalReferences) {
|
|
377
|
+
// If the value already comes from json format
|
|
378
|
+
if (ref.type && ref.url) {
|
|
379
|
+
externalReferences.push({
|
|
380
|
+
reference: { "@type": ref.type, url: ref.url }
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
} else {
|
|
385
|
+
externalReferences.concat(pkg.externalReferences);
|
|
386
|
+
}
|
|
387
|
+
} else {
|
|
388
|
+
if (format === "xml") {
|
|
389
|
+
if (pkg.homepage && pkg.homepage.url) {
|
|
390
|
+
externalReferences.push({
|
|
391
|
+
reference: { "@type": "website", url: pkg.homepage.url }
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
if (pkg.bugs && pkg.bugs.url) {
|
|
395
|
+
externalReferences.push({
|
|
396
|
+
reference: { "@type": "issue-tracker", url: pkg.bugs.url }
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
if (pkg.repository && pkg.repository.url) {
|
|
400
|
+
externalReferences.push({
|
|
401
|
+
reference: { "@type": "vcs", url: pkg.repository.url }
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
} else {
|
|
405
|
+
if (pkg.homepage && pkg.homepage.url) {
|
|
406
|
+
externalReferences.push({
|
|
407
|
+
type: "website",
|
|
408
|
+
url: pkg.homepage.url
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
if (pkg.bugs && pkg.bugs.url) {
|
|
412
|
+
externalReferences.push({
|
|
413
|
+
type: "issue-tracker",
|
|
414
|
+
url: pkg.bugs.url
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
if (pkg.repository && pkg.repository.url) {
|
|
418
|
+
externalReferences.push({
|
|
419
|
+
type: "vcs",
|
|
420
|
+
url: pkg.repository.url
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
return externalReferences;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* For all modules in the specified package, creates a list of
|
|
431
|
+
* component objects from each one.
|
|
432
|
+
*/
|
|
433
|
+
exports.listComponents = listComponents;
|
|
434
|
+
function listComponents(
|
|
435
|
+
options,
|
|
436
|
+
allImports,
|
|
437
|
+
pkg,
|
|
438
|
+
ptype = "npm",
|
|
439
|
+
format = "xml"
|
|
440
|
+
) {
|
|
441
|
+
let compMap = {};
|
|
442
|
+
let isRootPkg = ptype === "npm";
|
|
443
|
+
if (Array.isArray(pkg)) {
|
|
444
|
+
pkg.forEach((p) => {
|
|
445
|
+
addComponent(options, allImports, p, ptype, compMap, false, format);
|
|
446
|
+
});
|
|
447
|
+
} else {
|
|
448
|
+
addComponent(options, allImports, pkg, ptype, compMap, isRootPkg, format);
|
|
449
|
+
}
|
|
450
|
+
if (format === "xml") {
|
|
451
|
+
return Object.keys(compMap).map((k) => ({ component: compMap[k] }));
|
|
452
|
+
} else {
|
|
453
|
+
return Object.keys(compMap).map((k) => compMap[k]);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Given the specified package, create a CycloneDX component and add it to the list.
|
|
459
|
+
*/
|
|
460
|
+
function addComponent(
|
|
461
|
+
options,
|
|
462
|
+
allImports,
|
|
463
|
+
pkg,
|
|
464
|
+
ptype,
|
|
465
|
+
compMap,
|
|
466
|
+
isRootPkg = false,
|
|
467
|
+
format = "xml"
|
|
468
|
+
) {
|
|
469
|
+
if (!pkg || pkg.extraneous) {
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
if (!isRootPkg) {
|
|
473
|
+
let pkgIdentifier = parsePackageJsonName(pkg.name);
|
|
474
|
+
let publisher = pkg.publisher || "";
|
|
475
|
+
let group = pkg.group || pkgIdentifier.scope;
|
|
476
|
+
// Create empty group
|
|
477
|
+
group = group || "";
|
|
478
|
+
let name = pkgIdentifier.fullName || pkg.name || "";
|
|
479
|
+
// name is mandatory
|
|
480
|
+
if (!name) {
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
if (!ptype && pkg.qualifiers && pkg.qualifiers.type === "jar") {
|
|
484
|
+
ptype = "maven";
|
|
485
|
+
}
|
|
486
|
+
// Skip @types package for npm
|
|
487
|
+
if (
|
|
488
|
+
ptype == "npm" &&
|
|
489
|
+
(group === "types" || !name || name.startsWith("@types"))
|
|
490
|
+
) {
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
let version = pkg.version;
|
|
494
|
+
if (!version || ["dummy", "ignore"].includes(version)) {
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
let licenses = pkg.licenses || utils.getLicenses(pkg, format);
|
|
498
|
+
|
|
499
|
+
let purl =
|
|
500
|
+
pkg.purl ||
|
|
501
|
+
new PackageURL(ptype, group, name, version, pkg.qualifiers, pkg.subpath);
|
|
502
|
+
let purlString = purl.toString();
|
|
503
|
+
purlString = decodeURIComponent(purlString);
|
|
504
|
+
let description = { "#cdata": pkg.description };
|
|
505
|
+
if (format === "json") {
|
|
506
|
+
description = pkg.description || "";
|
|
507
|
+
}
|
|
508
|
+
let compScope = pkg.scope;
|
|
509
|
+
if (allImports) {
|
|
510
|
+
const impPkgs = Object.keys(allImports);
|
|
511
|
+
if (
|
|
512
|
+
impPkgs.includes(name) ||
|
|
513
|
+
impPkgs.includes(group + "/" + name) ||
|
|
514
|
+
impPkgs.includes("@" + group + "/" + name) ||
|
|
515
|
+
impPkgs.includes(group) ||
|
|
516
|
+
impPkgs.includes("@" + group)
|
|
517
|
+
) {
|
|
518
|
+
compScope = "required";
|
|
519
|
+
} else if (impPkgs.length) {
|
|
520
|
+
compScope = "optional";
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
if (options.requiredOnly && ["optional", "excluded"].includes(compScope)) {
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
let component = {
|
|
527
|
+
publisher,
|
|
528
|
+
group,
|
|
529
|
+
name,
|
|
530
|
+
version,
|
|
531
|
+
description,
|
|
532
|
+
scope: compScope,
|
|
533
|
+
hashes: [],
|
|
534
|
+
licenses,
|
|
535
|
+
purl: purlString,
|
|
536
|
+
externalReferences: addExternalReferences(pkg, format)
|
|
537
|
+
};
|
|
538
|
+
if (format === "xml") {
|
|
539
|
+
component["@type"] = determinePackageType(pkg);
|
|
540
|
+
component["@bom-ref"] = purlString;
|
|
541
|
+
} else {
|
|
542
|
+
component["type"] = determinePackageType(pkg);
|
|
543
|
+
component["bom-ref"] = purlString;
|
|
544
|
+
}
|
|
545
|
+
if (
|
|
546
|
+
component.externalReferences === undefined ||
|
|
547
|
+
component.externalReferences.length === 0
|
|
548
|
+
) {
|
|
549
|
+
delete component.externalReferences;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
processHashes(pkg, component, format);
|
|
553
|
+
// Retain any component properties
|
|
554
|
+
if (format === "json" && pkg.properties && pkg.properties.length) {
|
|
555
|
+
component.properties = pkg.properties;
|
|
556
|
+
}
|
|
557
|
+
if (compMap[component.purl]) return; //remove cycles
|
|
558
|
+
compMap[component.purl] = component;
|
|
559
|
+
}
|
|
560
|
+
if (pkg.dependencies) {
|
|
561
|
+
Object.keys(pkg.dependencies)
|
|
562
|
+
.map((x) => pkg.dependencies[x])
|
|
563
|
+
.filter((x) => typeof x !== "string") //remove cycles
|
|
564
|
+
.map((x) =>
|
|
565
|
+
addComponent(options, allImports, x, ptype, compMap, false, format)
|
|
566
|
+
);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
/**
|
|
571
|
+
* If the author has described the module as a 'framework', the take their
|
|
572
|
+
* word for it, otherwise, identify the module as a 'library'.
|
|
573
|
+
*/
|
|
574
|
+
function determinePackageType(pkg) {
|
|
575
|
+
if (pkg.type === "application") {
|
|
576
|
+
return "application";
|
|
577
|
+
}
|
|
578
|
+
if (pkg.purl) {
|
|
579
|
+
try {
|
|
580
|
+
let purl = PackageURL.fromString(pkg.purl);
|
|
581
|
+
if (purl.type) {
|
|
582
|
+
if (["docker", "oci", "container"].includes(purl.type)) {
|
|
583
|
+
return "container";
|
|
584
|
+
}
|
|
585
|
+
if (["github"].includes(purl.type)) {
|
|
586
|
+
return "application";
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
if (purl.namespace) {
|
|
590
|
+
for (const cf of [
|
|
591
|
+
"System.Web",
|
|
592
|
+
"System.ServiceModel",
|
|
593
|
+
"System.Data",
|
|
594
|
+
"spring",
|
|
595
|
+
"flask",
|
|
596
|
+
"django",
|
|
597
|
+
"beego",
|
|
598
|
+
"chi",
|
|
599
|
+
"echo",
|
|
600
|
+
"gin",
|
|
601
|
+
"gorilla",
|
|
602
|
+
"rye",
|
|
603
|
+
"httprouter",
|
|
604
|
+
"akka",
|
|
605
|
+
"dropwizard",
|
|
606
|
+
"vertx",
|
|
607
|
+
"gwt",
|
|
608
|
+
"jax-rs",
|
|
609
|
+
"jax-ws",
|
|
610
|
+
"jsf",
|
|
611
|
+
"play",
|
|
612
|
+
"spark",
|
|
613
|
+
"struts",
|
|
614
|
+
"angular",
|
|
615
|
+
"react",
|
|
616
|
+
"next",
|
|
617
|
+
"ember",
|
|
618
|
+
"express",
|
|
619
|
+
"knex",
|
|
620
|
+
"vue",
|
|
621
|
+
"aiohttp",
|
|
622
|
+
"bottle",
|
|
623
|
+
"cherrypy",
|
|
624
|
+
"drt",
|
|
625
|
+
"falcon",
|
|
626
|
+
"hug",
|
|
627
|
+
"pyramid",
|
|
628
|
+
"sanic",
|
|
629
|
+
"tornado",
|
|
630
|
+
"vibora"
|
|
631
|
+
]) {
|
|
632
|
+
if (purl.namespace.includes(cf)) {
|
|
633
|
+
return "framework";
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
} catch (e) {
|
|
638
|
+
// continue regardless of error
|
|
639
|
+
}
|
|
640
|
+
} else if (pkg.group) {
|
|
641
|
+
if (["actions"].includes(pkg.group)) {
|
|
642
|
+
return "application";
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
if (Object.prototype.hasOwnProperty.call(pkg, "keywords")) {
|
|
646
|
+
for (let keyword of pkg.keywords) {
|
|
647
|
+
if (keyword.toLowerCase() === "framework") {
|
|
648
|
+
return "framework";
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
return "library";
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
/**
|
|
656
|
+
* Uses the SHA1 shasum (if present) otherwise utilizes Subresource Integrity
|
|
657
|
+
* of the package with support for multiple hashing algorithms.
|
|
658
|
+
*/
|
|
659
|
+
function processHashes(pkg, component, format = "xml") {
|
|
660
|
+
if (pkg.hashes) {
|
|
661
|
+
// This attribute would be available when we read a bom json directly
|
|
662
|
+
// Eg: cyclonedx-maven-plugin. See: Bugs: #172, #175
|
|
663
|
+
for (const ahash of pkg.hashes) {
|
|
664
|
+
addComponentHash(ahash.alg, ahash.content, component, format);
|
|
665
|
+
}
|
|
666
|
+
} else if (pkg._shasum) {
|
|
667
|
+
let ahash = { "@alg": "SHA-1", "#text": pkg._shasum };
|
|
668
|
+
if (format === "json") {
|
|
669
|
+
ahash = { alg: "SHA-1", content: pkg._shasum };
|
|
670
|
+
component.hashes.push(ahash);
|
|
671
|
+
} else {
|
|
672
|
+
component.hashes.push({
|
|
673
|
+
hash: ahash
|
|
674
|
+
});
|
|
675
|
+
}
|
|
676
|
+
} else if (pkg._integrity) {
|
|
677
|
+
let integrity = ssri.parse(pkg._integrity) || {};
|
|
678
|
+
// Components may have multiple hashes with various lengths. Check each one
|
|
679
|
+
// that is supported by the CycloneDX specification.
|
|
680
|
+
if (Object.prototype.hasOwnProperty.call(integrity, "sha512")) {
|
|
681
|
+
addComponentHash(
|
|
682
|
+
"SHA-512",
|
|
683
|
+
integrity.sha512[0].digest,
|
|
684
|
+
component,
|
|
685
|
+
format
|
|
686
|
+
);
|
|
687
|
+
}
|
|
688
|
+
if (Object.prototype.hasOwnProperty.call(integrity, "sha384")) {
|
|
689
|
+
addComponentHash(
|
|
690
|
+
"SHA-384",
|
|
691
|
+
integrity.sha384[0].digest,
|
|
692
|
+
component,
|
|
693
|
+
format
|
|
694
|
+
);
|
|
695
|
+
}
|
|
696
|
+
if (Object.prototype.hasOwnProperty.call(integrity, "sha256")) {
|
|
697
|
+
addComponentHash(
|
|
698
|
+
"SHA-256",
|
|
699
|
+
integrity.sha256[0].digest,
|
|
700
|
+
component,
|
|
701
|
+
format
|
|
702
|
+
);
|
|
703
|
+
}
|
|
704
|
+
if (Object.prototype.hasOwnProperty.call(integrity, "sha1")) {
|
|
705
|
+
addComponentHash("SHA-1", integrity.sha1[0].digest, component, format);
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
if (component.hashes.length === 0) {
|
|
709
|
+
delete component.hashes; // If no hashes exist, delete the hashes node (it's optional)
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
/**
|
|
714
|
+
* Adds a hash to component.
|
|
715
|
+
*/
|
|
716
|
+
function addComponentHash(alg, digest, component, format = "xml") {
|
|
717
|
+
let hash = "";
|
|
718
|
+
// If it is a valid hash simply use it
|
|
719
|
+
if (new RegExp(HASH_PATTERN).test(digest)) {
|
|
720
|
+
hash = digest;
|
|
721
|
+
} else {
|
|
722
|
+
// Check if base64 encoded
|
|
723
|
+
const isBase64Encoded =
|
|
724
|
+
Buffer.from(digest, "base64").toString("base64") === digest;
|
|
725
|
+
hash = isBase64Encoded
|
|
726
|
+
? Buffer.from(digest, "base64").toString("hex")
|
|
727
|
+
: digest;
|
|
728
|
+
}
|
|
729
|
+
let ahash = { "@alg": alg, "#text": hash };
|
|
730
|
+
if (format === "json") {
|
|
731
|
+
ahash = { alg: alg, content: hash };
|
|
732
|
+
component.hashes.push(ahash);
|
|
733
|
+
} else {
|
|
734
|
+
component.hashes.push({ hash: ahash });
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
/**
|
|
739
|
+
* Return Bom in xml format
|
|
740
|
+
*
|
|
741
|
+
* @param {String} Serial number
|
|
742
|
+
* @param {Object} parentComponent Parent component object
|
|
743
|
+
* @param {Array} components Bom components
|
|
744
|
+
* @param {Object} context Context object
|
|
745
|
+
* @returns bom xml string
|
|
746
|
+
*/
|
|
747
|
+
const buildBomXml = (
|
|
748
|
+
serialNum,
|
|
749
|
+
parentComponent,
|
|
750
|
+
components,
|
|
751
|
+
context,
|
|
752
|
+
options = {}
|
|
753
|
+
) => {
|
|
754
|
+
const bom = builder
|
|
755
|
+
.create("bom", { encoding: "utf-8", separateArrayItems: true })
|
|
756
|
+
.att("xmlns", "http://cyclonedx.org/schema/bom/1.4");
|
|
757
|
+
bom.att("serialNumber", serialNum);
|
|
758
|
+
bom.att("version", 1);
|
|
759
|
+
const metadata = addMetadata(parentComponent, "xml", options);
|
|
760
|
+
bom.ele("metadata").ele(metadata);
|
|
761
|
+
if (components && components.length) {
|
|
762
|
+
bom.ele("components").ele(components);
|
|
763
|
+
if (context && context.src && context.filename) {
|
|
764
|
+
bom
|
|
765
|
+
.ele("externalReferences")
|
|
766
|
+
.ele(addGlobalReferences(context.src, context.filename, "xml"));
|
|
767
|
+
}
|
|
768
|
+
if (context) {
|
|
769
|
+
if (context.services && context.services.length) {
|
|
770
|
+
bom.ele("services").ele(addServices(context.services, "xml"));
|
|
771
|
+
}
|
|
772
|
+
if (context.dependencies && context.dependencies.length) {
|
|
773
|
+
bom.ele("dependencies").ele(addDependencies(context.dependencies));
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
const bomString = bom.end({
|
|
777
|
+
pretty: true,
|
|
778
|
+
indent: " ",
|
|
779
|
+
newline: "\n",
|
|
780
|
+
width: 0,
|
|
781
|
+
allowEmpty: false,
|
|
782
|
+
spacebeforeslash: ""
|
|
783
|
+
});
|
|
784
|
+
return bomString;
|
|
785
|
+
}
|
|
786
|
+
return "";
|
|
787
|
+
};
|
|
788
|
+
|
|
789
|
+
/**
|
|
790
|
+
* Return the BOM in xml, json format including any namespace mapping
|
|
791
|
+
*/
|
|
792
|
+
const buildBomNSData = (options, pkgInfo, ptype, context) => {
|
|
793
|
+
const bomNSData = {
|
|
794
|
+
bomXml: undefined,
|
|
795
|
+
bomXmlFiles: undefined,
|
|
796
|
+
bomJson: undefined,
|
|
797
|
+
bomJsonFiles: undefined,
|
|
798
|
+
nsMapping: undefined,
|
|
799
|
+
dependencies: undefined,
|
|
800
|
+
parentComponent: undefined
|
|
801
|
+
};
|
|
802
|
+
const serialNum = "urn:uuid:" + uuidv4();
|
|
803
|
+
let allImports = {};
|
|
804
|
+
if (context && context.allImports) {
|
|
805
|
+
allImports = context.allImports;
|
|
806
|
+
}
|
|
807
|
+
const nsMapping = context.nsMapping || {};
|
|
808
|
+
const dependencies = context.dependencies || [];
|
|
809
|
+
const parentComponent =
|
|
810
|
+
determineParentComponent(options) || context.parentComponent;
|
|
811
|
+
const metadata = addMetadata(parentComponent, "json", options);
|
|
812
|
+
const components = listComponents(options, allImports, pkgInfo, ptype, "xml");
|
|
813
|
+
if (components && (components.length || parentComponent)) {
|
|
814
|
+
const bomString = buildBomXml(
|
|
815
|
+
serialNum,
|
|
816
|
+
parentComponent,
|
|
817
|
+
components,
|
|
818
|
+
context,
|
|
819
|
+
options
|
|
820
|
+
);
|
|
821
|
+
// CycloneDX 1.4 Json Template
|
|
822
|
+
const jsonTpl = {
|
|
823
|
+
bomFormat: "CycloneDX",
|
|
824
|
+
specVersion: "1.4",
|
|
825
|
+
serialNumber: serialNum,
|
|
826
|
+
version: 1,
|
|
827
|
+
metadata: metadata,
|
|
828
|
+
components: listComponents(options, allImports, pkgInfo, ptype, "json"),
|
|
829
|
+
dependencies
|
|
830
|
+
};
|
|
831
|
+
if (context && context.src && context.filename) {
|
|
832
|
+
jsonTpl.externalReferences = addGlobalReferences(
|
|
833
|
+
context.src,
|
|
834
|
+
context.filename,
|
|
835
|
+
"json"
|
|
836
|
+
);
|
|
837
|
+
}
|
|
838
|
+
bomNSData.bomXml = bomString;
|
|
839
|
+
bomNSData.bomJson = jsonTpl;
|
|
840
|
+
bomNSData.nsMapping = nsMapping;
|
|
841
|
+
bomNSData.dependencies = dependencies;
|
|
842
|
+
bomNSData.parentComponent = parentComponent;
|
|
843
|
+
}
|
|
844
|
+
return bomNSData;
|
|
845
|
+
};
|
|
846
|
+
|
|
847
|
+
/**
|
|
848
|
+
* Function to create bom string for Java jars
|
|
849
|
+
*
|
|
850
|
+
* @param path to the project
|
|
851
|
+
* @param options Parse options from the cli
|
|
852
|
+
*/
|
|
853
|
+
const createJarBom = (path, options) => {
|
|
854
|
+
console.log(
|
|
855
|
+
`About to create SBoM for all jar files under ${path}. This would take a while ...`
|
|
856
|
+
);
|
|
857
|
+
let pkgList = [];
|
|
858
|
+
let jarFiles = utils.getAllFiles(
|
|
859
|
+
path,
|
|
860
|
+
(options.multiProject ? "**/" : "") + "*.[jw]ar"
|
|
861
|
+
);
|
|
862
|
+
// Jenkins plugins
|
|
863
|
+
const hpiFiles = utils.getAllFiles(
|
|
864
|
+
path,
|
|
865
|
+
(options.multiProject ? "**/" : "") + "*.hpi"
|
|
866
|
+
);
|
|
867
|
+
if (hpiFiles.length) {
|
|
868
|
+
jarFiles = jarFiles.concat(hpiFiles);
|
|
869
|
+
}
|
|
870
|
+
let tempDir = fs.mkdtempSync(pathLib.join(os.tmpdir(), "jar-deps-"));
|
|
871
|
+
for (let jar of jarFiles) {
|
|
872
|
+
if (DEBUG_MODE) {
|
|
873
|
+
console.log(`Parsing ${jar}`);
|
|
874
|
+
}
|
|
875
|
+
const dlist = utils.extractJarArchive(jar, tempDir);
|
|
876
|
+
if (dlist && dlist.length) {
|
|
877
|
+
pkgList = pkgList.concat(dlist);
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
// Clean up
|
|
881
|
+
if (tempDir && tempDir.startsWith(os.tmpdir()) && fs.rmSync) {
|
|
882
|
+
console.log(`Cleaning up ${tempDir}`);
|
|
883
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
884
|
+
}
|
|
885
|
+
return buildBomNSData(options, pkgList, "maven", {
|
|
886
|
+
src: path,
|
|
887
|
+
filename: jarFiles.join(", "),
|
|
888
|
+
nsMapping: {}
|
|
889
|
+
});
|
|
890
|
+
};
|
|
891
|
+
|
|
892
|
+
/**
|
|
893
|
+
* Function to create bom string for Java projects
|
|
894
|
+
*
|
|
895
|
+
* @param path to the project
|
|
896
|
+
* @param options Parse options from the cli
|
|
897
|
+
*/
|
|
898
|
+
const createJavaBom = async (path, options) => {
|
|
899
|
+
let jarNSMapping = {};
|
|
900
|
+
let pkgList = [];
|
|
901
|
+
let dependencies = [];
|
|
902
|
+
// cyclone-dx-maven plugin creates a component for the app under metadata
|
|
903
|
+
// This is subsequently referred to in the dependencies list
|
|
904
|
+
let parentComponent = {};
|
|
905
|
+
// war/ear mode
|
|
906
|
+
if (path.endsWith(".war")) {
|
|
907
|
+
// Check if the file exists
|
|
908
|
+
if (fs.existsSync(path)) {
|
|
909
|
+
if (DEBUG_MODE) {
|
|
910
|
+
console.log(`Retrieving packages from ${path}`);
|
|
911
|
+
}
|
|
912
|
+
let tempDir = fs.mkdtempSync(pathLib.join(os.tmpdir(), "war-deps-"));
|
|
913
|
+
pkgList = utils.extractJarArchive(path, tempDir);
|
|
914
|
+
if (pkgList.length) {
|
|
915
|
+
pkgList = await utils.getMvnMetadata(pkgList);
|
|
916
|
+
}
|
|
917
|
+
// Should we attempt to resolve class names
|
|
918
|
+
if (options.resolveClass) {
|
|
919
|
+
console.log(
|
|
920
|
+
"Creating class names list based on available jars. This might take a few mins ..."
|
|
921
|
+
);
|
|
922
|
+
jarNSMapping = utils.collectJarNS(tempDir);
|
|
923
|
+
}
|
|
924
|
+
// Clean up
|
|
925
|
+
if (tempDir && tempDir.startsWith(os.tmpdir()) && fs.rmSync) {
|
|
926
|
+
console.log(`Cleaning up ${tempDir}`);
|
|
927
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
928
|
+
}
|
|
929
|
+
} else {
|
|
930
|
+
console.log(`${path} doesn't exist`);
|
|
931
|
+
}
|
|
932
|
+
return buildBomNSData(options, pkgList, "maven", {
|
|
933
|
+
src: pathLib.dirname(path),
|
|
934
|
+
filename: path,
|
|
935
|
+
nsMapping: jarNSMapping,
|
|
936
|
+
dependencies,
|
|
937
|
+
parentComponent
|
|
938
|
+
});
|
|
939
|
+
} else {
|
|
940
|
+
// maven - pom.xml
|
|
941
|
+
const pomFiles = utils.getAllFiles(
|
|
942
|
+
path,
|
|
943
|
+
(options.multiProject ? "**/" : "") + "pom.xml"
|
|
944
|
+
);
|
|
945
|
+
if (pomFiles && pomFiles.length) {
|
|
946
|
+
let mvnArgs = [
|
|
947
|
+
"org.cyclonedx:cyclonedx-maven-plugin:2.7.2:makeAggregateBom",
|
|
948
|
+
"-DoutputName=bom"
|
|
949
|
+
];
|
|
950
|
+
// By using quiet mode we can reduce the maxBuffer used and avoid crashes
|
|
951
|
+
if (!DEBUG_MODE) {
|
|
952
|
+
mvnArgs.push("-q");
|
|
953
|
+
}
|
|
954
|
+
// Support for passing additional settings and profile to maven
|
|
955
|
+
if (process.env.MVN_ARGS) {
|
|
956
|
+
const addArgs = process.env.MVN_ARGS.split(" ");
|
|
957
|
+
mvnArgs = mvnArgs.concat(addArgs);
|
|
958
|
+
}
|
|
959
|
+
for (let f of pomFiles) {
|
|
960
|
+
const basePath = pathLib.dirname(f);
|
|
961
|
+
let mavenCmd = utils.getMavenCommand(basePath, path);
|
|
962
|
+
// Should we attempt to resolve class names
|
|
963
|
+
if (options.resolveClass) {
|
|
964
|
+
console.log(
|
|
965
|
+
"Creating class names list based on available jars. This might take a few mins ..."
|
|
966
|
+
);
|
|
967
|
+
jarNSMapping = utils.collectMvnDependencies(mavenCmd, basePath);
|
|
968
|
+
}
|
|
969
|
+
console.log(
|
|
970
|
+
`Executing '${mavenCmd} ${mvnArgs.join(" ")}' in`,
|
|
971
|
+
basePath
|
|
972
|
+
);
|
|
973
|
+
let result = spawnSync(mavenCmd, mvnArgs, {
|
|
974
|
+
cwd: basePath,
|
|
975
|
+
shell: true,
|
|
976
|
+
encoding: "utf-8",
|
|
977
|
+
timeout: TIMEOUT_MS
|
|
978
|
+
});
|
|
979
|
+
// Check if the cyclonedx plugin created the required bom.xml file
|
|
980
|
+
// Sometimes the plugin fails silently for complex maven projects
|
|
981
|
+
const bomJsonFiles = utils.getAllFiles(path, "**/target/*.json");
|
|
982
|
+
const bomGenerated = bomJsonFiles.length;
|
|
983
|
+
if (!bomGenerated || result.status !== 0 || result.error) {
|
|
984
|
+
let tempDir = fs.mkdtempSync(pathLib.join(os.tmpdir(), "cdxmvn-"));
|
|
985
|
+
let tempMvnTree = pathLib.join(tempDir, "mvn-tree.txt");
|
|
986
|
+
let mvnTreeArgs = ["dependency:tree", "-DoutputFile=" + tempMvnTree];
|
|
987
|
+
if (process.env.MVN_ARGS) {
|
|
988
|
+
const addArgs = process.env.MVN_ARGS.split(" ");
|
|
989
|
+
mvnTreeArgs = mvnTreeArgs.concat(addArgs);
|
|
990
|
+
}
|
|
991
|
+
console.log(
|
|
992
|
+
`Fallback to executing ${mavenCmd} ${mvnTreeArgs.join(" ")}`
|
|
993
|
+
);
|
|
994
|
+
result = spawnSync(mavenCmd, mvnTreeArgs, {
|
|
995
|
+
cwd: basePath,
|
|
996
|
+
shell: true,
|
|
997
|
+
encoding: "utf-8",
|
|
998
|
+
timeout: TIMEOUT_MS
|
|
999
|
+
});
|
|
1000
|
+
if (result.status !== 0 || result.error) {
|
|
1001
|
+
console.error(result.stdout, result.stderr);
|
|
1002
|
+
console.log(
|
|
1003
|
+
"Resolve the above maven error. This could be due to the following:\n"
|
|
1004
|
+
);
|
|
1005
|
+
console.log(
|
|
1006
|
+
"1. Java version requirement - Scan or the CI build agent could be using an incompatible version"
|
|
1007
|
+
);
|
|
1008
|
+
console.log(
|
|
1009
|
+
"2. Private maven repository is not serving all the required maven plugins correctly. Refer to your registry documentation to add support for jitpack.io"
|
|
1010
|
+
);
|
|
1011
|
+
console.log(
|
|
1012
|
+
"3. Check if all required environment variables including any maven profile arguments are passed correctly to this tool"
|
|
1013
|
+
);
|
|
1014
|
+
// Do not fall back to methods that can produce incomplete results when failOnError is set
|
|
1015
|
+
options.failOnError && process.exit(1);
|
|
1016
|
+
console.log(
|
|
1017
|
+
"\nFalling back to manual pom.xml parsing. The result would be incomplete!"
|
|
1018
|
+
);
|
|
1019
|
+
const dlist = utils.parsePom(f);
|
|
1020
|
+
if (dlist && dlist.length) {
|
|
1021
|
+
pkgList = pkgList.concat(dlist);
|
|
1022
|
+
}
|
|
1023
|
+
} else {
|
|
1024
|
+
if (fs.existsSync(tempMvnTree)) {
|
|
1025
|
+
const mvnTreeString = fs.readFileSync(tempMvnTree, {
|
|
1026
|
+
encoding: "utf-8"
|
|
1027
|
+
});
|
|
1028
|
+
const parsedList = utils.parseMavenTree(mvnTreeString);
|
|
1029
|
+
const dlist = parsedList.pkgList;
|
|
1030
|
+
parentComponent = dlist.splice(0, 1)[0];
|
|
1031
|
+
parentComponent.type = "application";
|
|
1032
|
+
if (dlist && dlist.length) {
|
|
1033
|
+
pkgList = pkgList.concat(dlist);
|
|
1034
|
+
}
|
|
1035
|
+
if (parsedList.dependenciesList && parsedList.dependenciesList) {
|
|
1036
|
+
dependencies = dependencies.concat(parsedList.dependenciesList);
|
|
1037
|
+
}
|
|
1038
|
+
fs.unlinkSync(tempMvnTree);
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
} // for
|
|
1043
|
+
const bomFiles = utils.getAllFiles(path, "**/target/bom.xml");
|
|
1044
|
+
const bomJsonFiles = utils.getAllFiles(path, "**/target/*.json");
|
|
1045
|
+
for (const abjson of bomJsonFiles) {
|
|
1046
|
+
let bomJsonObj = undefined;
|
|
1047
|
+
try {
|
|
1048
|
+
if (DEBUG_MODE) {
|
|
1049
|
+
console.log(`Extracting data from generated bom file ${abjson}`);
|
|
1050
|
+
}
|
|
1051
|
+
bomJsonObj = JSON.parse(
|
|
1052
|
+
fs.readFileSync(abjson, {
|
|
1053
|
+
encoding: "utf-8"
|
|
1054
|
+
})
|
|
1055
|
+
);
|
|
1056
|
+
if (bomJsonObj) {
|
|
1057
|
+
if (
|
|
1058
|
+
bomJsonObj.metadata &&
|
|
1059
|
+
bomJsonObj.metadata.component &&
|
|
1060
|
+
!Object.keys(parentComponent).length
|
|
1061
|
+
) {
|
|
1062
|
+
parentComponent = bomJsonObj.metadata.component;
|
|
1063
|
+
pkgList = [];
|
|
1064
|
+
}
|
|
1065
|
+
if (bomJsonObj.components) {
|
|
1066
|
+
pkgList = pkgList.concat(bomJsonObj.components);
|
|
1067
|
+
}
|
|
1068
|
+
if (bomJsonObj.dependencies && !options.requiredOnly) {
|
|
1069
|
+
dependencies = mergeDependencies(
|
|
1070
|
+
dependencies,
|
|
1071
|
+
bomJsonObj.dependencies
|
|
1072
|
+
);
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
} catch (err) {
|
|
1076
|
+
if (options.failOnError || DEBUG_MODE) {
|
|
1077
|
+
console.log(err);
|
|
1078
|
+
options.failOnError && process.exit(1);
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
if (pkgList) {
|
|
1083
|
+
pkgList = trimComponents(pkgList, "json");
|
|
1084
|
+
pkgList = await utils.getMvnMetadata(pkgList);
|
|
1085
|
+
return buildBomNSData(options, pkgList, "maven", {
|
|
1086
|
+
src: path,
|
|
1087
|
+
filename: pomFiles.join(", "),
|
|
1088
|
+
nsMapping: jarNSMapping,
|
|
1089
|
+
dependencies,
|
|
1090
|
+
parentComponent
|
|
1091
|
+
});
|
|
1092
|
+
} else if (bomJsonFiles.length) {
|
|
1093
|
+
const bomNSData = {};
|
|
1094
|
+
bomNSData.bomXmlFiles = bomFiles;
|
|
1095
|
+
bomNSData.bomJsonFiles = bomJsonFiles;
|
|
1096
|
+
bomNSData.nsMapping = jarNSMapping;
|
|
1097
|
+
bomNSData.dependencies = dependencies;
|
|
1098
|
+
bomNSData.parentComponent = parentComponent;
|
|
1099
|
+
return bomNSData;
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
// gradle
|
|
1103
|
+
let gradleFiles = utils.getAllFiles(
|
|
1104
|
+
path,
|
|
1105
|
+
(options.multiProject ? "**/" : "") + "build.gradle*"
|
|
1106
|
+
);
|
|
1107
|
+
if (gradleFiles && gradleFiles.length && options.installDeps) {
|
|
1108
|
+
let gradleCmd = utils.getGradleCommand(path, null);
|
|
1109
|
+
// Support for multi-project applications
|
|
1110
|
+
if (process.env.GRADLE_MULTI_PROJECT_MODE) {
|
|
1111
|
+
console.log("Executing", gradleCmd, "projects in", path);
|
|
1112
|
+
const result = spawnSync(
|
|
1113
|
+
gradleCmd,
|
|
1114
|
+
["projects", "-q", "--console", "plain"],
|
|
1115
|
+
{ cwd: path, encoding: "utf-8", timeout: TIMEOUT_MS }
|
|
1116
|
+
);
|
|
1117
|
+
if (result.status !== 0 || result.error) {
|
|
1118
|
+
if (result.stderr) {
|
|
1119
|
+
console.error(result.stdout, result.stderr);
|
|
1120
|
+
}
|
|
1121
|
+
console.log(
|
|
1122
|
+
"1. Check if the correct version of java and gradle are installed and available in PATH. For example, some project might require Java 11 with gradle 7."
|
|
1123
|
+
);
|
|
1124
|
+
options.failOnError && process.exit(1);
|
|
1125
|
+
}
|
|
1126
|
+
const stdout = result.stdout;
|
|
1127
|
+
if (stdout) {
|
|
1128
|
+
const cmdOutput = Buffer.from(stdout).toString();
|
|
1129
|
+
const allProjects = utils.parseGradleProjects(cmdOutput);
|
|
1130
|
+
if (!allProjects) {
|
|
1131
|
+
console.log(
|
|
1132
|
+
"No projects found. Is this a gradle multi-project application?"
|
|
1133
|
+
);
|
|
1134
|
+
options.failOnError && process.exit(1);
|
|
1135
|
+
} else {
|
|
1136
|
+
console.log("Found", allProjects.length, "gradle sub-projects");
|
|
1137
|
+
for (let sp of allProjects) {
|
|
1138
|
+
let gradleDepArgs = [
|
|
1139
|
+
sp + ":dependencies",
|
|
1140
|
+
"-q",
|
|
1141
|
+
"--console",
|
|
1142
|
+
"plain"
|
|
1143
|
+
];
|
|
1144
|
+
// Support custom GRADLE_ARGS such as --configuration runtimeClassPath
|
|
1145
|
+
if (process.env.GRADLE_ARGS) {
|
|
1146
|
+
const addArgs = process.env.GRADLE_ARGS.split(" ");
|
|
1147
|
+
gradleDepArgs = gradleDepArgs.concat(addArgs);
|
|
1148
|
+
}
|
|
1149
|
+
console.log(
|
|
1150
|
+
"Executing",
|
|
1151
|
+
gradleCmd,
|
|
1152
|
+
gradleDepArgs.join(" "),
|
|
1153
|
+
"in",
|
|
1154
|
+
path
|
|
1155
|
+
);
|
|
1156
|
+
const sresult = spawnSync(gradleCmd, gradleDepArgs, {
|
|
1157
|
+
cwd: path,
|
|
1158
|
+
encoding: "utf-8",
|
|
1159
|
+
timeout: TIMEOUT_MS
|
|
1160
|
+
});
|
|
1161
|
+
if (sresult.status !== 0 || sresult.error) {
|
|
1162
|
+
if (options.failOnError || DEBUG_MODE) {
|
|
1163
|
+
console.error(sresult.stdout, sresult.stderr);
|
|
1164
|
+
}
|
|
1165
|
+
options.failOnError && process.exit(1);
|
|
1166
|
+
}
|
|
1167
|
+
const sstdout = sresult.stdout;
|
|
1168
|
+
if (sstdout) {
|
|
1169
|
+
const cmdOutput = Buffer.from(sstdout).toString();
|
|
1170
|
+
const parsedList = utils.parseGradleDep(cmdOutput);
|
|
1171
|
+
const dlist = parsedList.pkgList;
|
|
1172
|
+
parentComponent = dlist.splice(0, 1)[0];
|
|
1173
|
+
if (
|
|
1174
|
+
parsedList.dependenciesList &&
|
|
1175
|
+
parsedList.dependenciesList
|
|
1176
|
+
) {
|
|
1177
|
+
dependencies = dependencies.concat(
|
|
1178
|
+
parsedList.dependenciesList
|
|
1179
|
+
);
|
|
1180
|
+
}
|
|
1181
|
+
if (dlist && dlist.length) {
|
|
1182
|
+
if (DEBUG_MODE) {
|
|
1183
|
+
console.log(
|
|
1184
|
+
"Found",
|
|
1185
|
+
dlist.length,
|
|
1186
|
+
"packages in gradle project",
|
|
1187
|
+
sp
|
|
1188
|
+
);
|
|
1189
|
+
}
|
|
1190
|
+
pkgList = pkgList.concat(dlist);
|
|
1191
|
+
} else {
|
|
1192
|
+
if (options.failOnError || DEBUG_MODE) {
|
|
1193
|
+
console.log("No packages were found in gradle project", sp);
|
|
1194
|
+
}
|
|
1195
|
+
options.failOnError && process.exit(1);
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
if (pkgList.length) {
|
|
1200
|
+
console.log(
|
|
1201
|
+
"Obtained",
|
|
1202
|
+
pkgList.length,
|
|
1203
|
+
"from this gradle multi-project"
|
|
1204
|
+
);
|
|
1205
|
+
} else {
|
|
1206
|
+
console.log(
|
|
1207
|
+
"No packages found. Unset the environment variable GRADLE_MULTI_PROJECT_MODE and try again."
|
|
1208
|
+
);
|
|
1209
|
+
options.failOnError && process.exit(1);
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
} else {
|
|
1213
|
+
console.error("Gradle unexpectedly didn't return any output");
|
|
1214
|
+
options.failOnError && process.exit(1);
|
|
1215
|
+
}
|
|
1216
|
+
} else {
|
|
1217
|
+
let gradleDepArgs = ["dependencies", "-q", "--console", "plain"];
|
|
1218
|
+
// Support for overriding the gradle task name. Issue# 90
|
|
1219
|
+
if (process.env.GRADLE_DEPENDENCY_TASK) {
|
|
1220
|
+
gradleDepArgs = process.env.GRADLE_DEPENDENCY_TASK.split(" ");
|
|
1221
|
+
} else if (process.env.GRADLE_ARGS) {
|
|
1222
|
+
// Support custom GRADLE_ARGS such as --configuration runtimeClassPath
|
|
1223
|
+
const addArgs = process.env.GRADLE_ARGS.split(" ");
|
|
1224
|
+
gradleDepArgs = gradleDepArgs.concat(addArgs);
|
|
1225
|
+
}
|
|
1226
|
+
for (let f of gradleFiles) {
|
|
1227
|
+
const basePath = pathLib.dirname(f);
|
|
1228
|
+
// Fixes #157. Look for wrapper script in the nested directory
|
|
1229
|
+
gradleCmd = utils.getGradleCommand(basePath, path);
|
|
1230
|
+
console.log(
|
|
1231
|
+
"Executing",
|
|
1232
|
+
gradleCmd,
|
|
1233
|
+
gradleDepArgs.join(" "),
|
|
1234
|
+
"in",
|
|
1235
|
+
basePath
|
|
1236
|
+
);
|
|
1237
|
+
const result = spawnSync(gradleCmd, gradleDepArgs, {
|
|
1238
|
+
cwd: basePath,
|
|
1239
|
+
encoding: "utf-8",
|
|
1240
|
+
timeout: TIMEOUT_MS
|
|
1241
|
+
});
|
|
1242
|
+
if (result.status !== 0 || result.error) {
|
|
1243
|
+
if (result.stderr) {
|
|
1244
|
+
console.error(result.stdout, result.stderr);
|
|
1245
|
+
}
|
|
1246
|
+
if (DEBUG_MODE || !result.stderr || options.failOnError) {
|
|
1247
|
+
console.log(
|
|
1248
|
+
"1. Check if the correct version of java and gradle are installed and available in PATH. For example, some project might require Java 11 with gradle 7."
|
|
1249
|
+
);
|
|
1250
|
+
console.log(
|
|
1251
|
+
"2. When using tools such as sdkman, the init script must be invoked to set the PATH variables correctly."
|
|
1252
|
+
);
|
|
1253
|
+
options.failOnError && process.exit(1);
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
const stdout = result.stdout;
|
|
1257
|
+
if (stdout) {
|
|
1258
|
+
const cmdOutput = Buffer.from(stdout).toString();
|
|
1259
|
+
const parsedList = utils.parseGradleDep(cmdOutput);
|
|
1260
|
+
const dlist = parsedList.pkgList;
|
|
1261
|
+
parentComponent = dlist.splice(0, 1)[0];
|
|
1262
|
+
if (parsedList.dependenciesList && parsedList.dependenciesList) {
|
|
1263
|
+
dependencies = dependencies.concat(parsedList.dependenciesList);
|
|
1264
|
+
}
|
|
1265
|
+
if (dlist && dlist.length) {
|
|
1266
|
+
pkgList = pkgList.concat(dlist);
|
|
1267
|
+
} else {
|
|
1268
|
+
console.log(
|
|
1269
|
+
"No packages were detected. If this is a multi-project gradle application set the environment variable GRADLE_MULTI_PROJECT_MODE to true and try again."
|
|
1270
|
+
);
|
|
1271
|
+
options.failOnError && process.exit(1);
|
|
1272
|
+
}
|
|
1273
|
+
} else {
|
|
1274
|
+
console.log("Gradle unexpectedly didn't produce any output");
|
|
1275
|
+
options.failOnError && process.exit(1);
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
pkgList = await utils.getMvnMetadata(pkgList);
|
|
1280
|
+
// Should we attempt to resolve class names
|
|
1281
|
+
if (options.resolveClass) {
|
|
1282
|
+
console.log(
|
|
1283
|
+
"Creating class names list based on available jars. This might take a few mins ..."
|
|
1284
|
+
);
|
|
1285
|
+
jarNSMapping = utils.collectJarNS(GRADLE_CACHE_DIR);
|
|
1286
|
+
}
|
|
1287
|
+
return buildBomNSData(options, pkgList, "maven", {
|
|
1288
|
+
src: path,
|
|
1289
|
+
filename: gradleFiles.join(", "),
|
|
1290
|
+
nsMapping: jarNSMapping,
|
|
1291
|
+
dependencies,
|
|
1292
|
+
parentComponent
|
|
1293
|
+
});
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
// Bazel
|
|
1297
|
+
// Look for the BUILD file only in the root directory
|
|
1298
|
+
let bazelFiles = utils.getAllFiles(path, "BUILD");
|
|
1299
|
+
if (bazelFiles && bazelFiles.length) {
|
|
1300
|
+
let BAZEL_CMD = "bazel";
|
|
1301
|
+
if (process.env.BAZEL_HOME) {
|
|
1302
|
+
BAZEL_CMD = pathLib.join(process.env.BAZEL_HOME, "bin", "bazel");
|
|
1303
|
+
}
|
|
1304
|
+
for (let f of bazelFiles) {
|
|
1305
|
+
const basePath = pathLib.dirname(f);
|
|
1306
|
+
// Invoke bazel build first
|
|
1307
|
+
const bazelTarget = process.env.BAZEL_TARGET || ":all";
|
|
1308
|
+
console.log(
|
|
1309
|
+
"Executing",
|
|
1310
|
+
BAZEL_CMD,
|
|
1311
|
+
"build",
|
|
1312
|
+
bazelTarget,
|
|
1313
|
+
"in",
|
|
1314
|
+
basePath
|
|
1315
|
+
);
|
|
1316
|
+
let result = spawnSync(BAZEL_CMD, ["build", bazelTarget], {
|
|
1317
|
+
cwd: basePath,
|
|
1318
|
+
shell: true,
|
|
1319
|
+
encoding: "utf-8",
|
|
1320
|
+
timeout: TIMEOUT_MS
|
|
1321
|
+
});
|
|
1322
|
+
if (result.status !== 0 || result.error) {
|
|
1323
|
+
if (result.stderr) {
|
|
1324
|
+
console.error(result.stdout, result.stderr);
|
|
1325
|
+
}
|
|
1326
|
+
console.log(
|
|
1327
|
+
"1. Check if bazel is installed and available in PATH.\n2. Try building your app with bazel prior to invoking cdxgen"
|
|
1328
|
+
);
|
|
1329
|
+
options.failOnError && process.exit(1);
|
|
1330
|
+
} else {
|
|
1331
|
+
console.log(
|
|
1332
|
+
"Executing",
|
|
1333
|
+
BAZEL_CMD,
|
|
1334
|
+
"aquery --output=textproto --skyframe_state in",
|
|
1335
|
+
basePath
|
|
1336
|
+
);
|
|
1337
|
+
result = spawnSync(
|
|
1338
|
+
BAZEL_CMD,
|
|
1339
|
+
["aquery", "--output=textproto", "--skyframe_state"],
|
|
1340
|
+
{ cwd: basePath, encoding: "utf-8", timeout: TIMEOUT_MS }
|
|
1341
|
+
);
|
|
1342
|
+
if (result.status !== 0 || result.error) {
|
|
1343
|
+
console.error(result.stdout, result.stderr);
|
|
1344
|
+
options.failOnError && process.exit(1);
|
|
1345
|
+
}
|
|
1346
|
+
let stdout = result.stdout;
|
|
1347
|
+
if (stdout) {
|
|
1348
|
+
const cmdOutput = Buffer.from(stdout).toString();
|
|
1349
|
+
const dlist = utils.parseBazelSkyframe(cmdOutput);
|
|
1350
|
+
if (dlist && dlist.length) {
|
|
1351
|
+
pkgList = pkgList.concat(dlist);
|
|
1352
|
+
} else {
|
|
1353
|
+
console.log(
|
|
1354
|
+
"No packages were detected.\n1. Build your project using bazel build command before running cdxgen\n2. Try running the bazel aquery command manually to see if skyframe state can be retrieved."
|
|
1355
|
+
);
|
|
1356
|
+
console.log(
|
|
1357
|
+
"If your project requires a different query, please file a bug at cyclonedx/cdxgen repo!"
|
|
1358
|
+
);
|
|
1359
|
+
options.failOnError && process.exit(1);
|
|
1360
|
+
}
|
|
1361
|
+
} else {
|
|
1362
|
+
console.log("Bazel unexpectedly didn't produce any output");
|
|
1363
|
+
options.failOnError && process.exit(1);
|
|
1364
|
+
}
|
|
1365
|
+
pkgList = await utils.getMvnMetadata(pkgList);
|
|
1366
|
+
return buildBomNSData(options, pkgList, "maven", {
|
|
1367
|
+
src: path,
|
|
1368
|
+
filename: "BUILD",
|
|
1369
|
+
nsMapping: {},
|
|
1370
|
+
dependencies,
|
|
1371
|
+
parentComponent
|
|
1372
|
+
});
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
// scala sbt
|
|
1378
|
+
// Identify sbt projects via its `project` directory:
|
|
1379
|
+
// - all SBT project _should_ define build.properties file with sbt version info
|
|
1380
|
+
// - SBT projects _typically_ have some configs/plugins defined in .sbt files
|
|
1381
|
+
// - SBT projects that are still on 0.13.x, can still use the old approach,
|
|
1382
|
+
// where configs are defined via Scala files
|
|
1383
|
+
// Detecting one of those should be enough to determine an SBT project.
|
|
1384
|
+
let sbtProjectFiles = utils.getAllFiles(
|
|
1385
|
+
path,
|
|
1386
|
+
(options.multiProject ? "**/" : "") +
|
|
1387
|
+
"project/{build.properties,*.sbt,*.scala}"
|
|
1388
|
+
);
|
|
1389
|
+
|
|
1390
|
+
let sbtProjects = [];
|
|
1391
|
+
for (let i in sbtProjectFiles) {
|
|
1392
|
+
// parent dir of sbtProjectFile is the `project` directory
|
|
1393
|
+
// parent dir of `project` is the sbt root project directory
|
|
1394
|
+
const baseDir = pathLib.dirname(pathLib.dirname(sbtProjectFiles[i]));
|
|
1395
|
+
sbtProjects = sbtProjects.concat(baseDir);
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
// Fallback in case sbt's project directory is non-existent
|
|
1399
|
+
if (!sbtProjects.length) {
|
|
1400
|
+
sbtProjectFiles = utils.getAllFiles(
|
|
1401
|
+
path,
|
|
1402
|
+
(options.multiProject ? "**/" : "") + "*.sbt"
|
|
1403
|
+
);
|
|
1404
|
+
for (let i in sbtProjectFiles) {
|
|
1405
|
+
const baseDir = pathLib.dirname(sbtProjectFiles[i]);
|
|
1406
|
+
sbtProjects = sbtProjects.concat(baseDir);
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
sbtProjects = [...new Set(sbtProjects)]; // eliminate duplicates
|
|
1411
|
+
|
|
1412
|
+
let sbtLockFiles = utils.getAllFiles(
|
|
1413
|
+
path,
|
|
1414
|
+
(options.multiProject ? "**/" : "") + "build.sbt.lock"
|
|
1415
|
+
);
|
|
1416
|
+
|
|
1417
|
+
if (sbtProjects && sbtProjects.length) {
|
|
1418
|
+
let pkgList = [];
|
|
1419
|
+
// If the project use sbt lock files
|
|
1420
|
+
if (sbtLockFiles && sbtLockFiles.length) {
|
|
1421
|
+
for (let f of sbtLockFiles) {
|
|
1422
|
+
const dlist = utils.parseSbtLock(f);
|
|
1423
|
+
if (dlist && dlist.length) {
|
|
1424
|
+
pkgList = pkgList.concat(dlist);
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
} else {
|
|
1428
|
+
let SBT_CMD = process.env.SBT_CMD || "sbt";
|
|
1429
|
+
let sbtVersion = utils.determineSbtVersion(path);
|
|
1430
|
+
if (DEBUG_MODE) {
|
|
1431
|
+
console.log("Detected sbt version: " + sbtVersion);
|
|
1432
|
+
}
|
|
1433
|
+
// Introduced in 1.2.0 https://www.scala-sbt.org/1.x/docs/sbt-1.2-Release-Notes.html#addPluginSbtFile+command,
|
|
1434
|
+
// however working properly for real only since 1.3.4: https://github.com/sbt/sbt/releases/tag/v1.3.4
|
|
1435
|
+
const standalonePluginFile =
|
|
1436
|
+
sbtVersion != null &&
|
|
1437
|
+
semver.gte(sbtVersion, "1.3.4") &&
|
|
1438
|
+
semver.lte(sbtVersion, "1.4.0");
|
|
1439
|
+
const isDependencyTreeBuiltIn =
|
|
1440
|
+
sbtVersion != null && semver.gte(sbtVersion, "1.4.0");
|
|
1441
|
+
let tempDir = fs.mkdtempSync(pathLib.join(os.tmpdir(), "cdxsbt-"));
|
|
1442
|
+
let tempSbtgDir = fs.mkdtempSync(pathLib.join(os.tmpdir(), "cdxsbtg-"));
|
|
1443
|
+
fs.mkdirSync(tempSbtgDir, { recursive: true });
|
|
1444
|
+
// Create temporary plugins file
|
|
1445
|
+
let tempSbtPlugins = pathLib.join(tempSbtgDir, "dep-plugins.sbt");
|
|
1446
|
+
|
|
1447
|
+
// Requires a custom version of `sbt-dependency-graph` that
|
|
1448
|
+
// supports `--append` for `toFile` subtask.
|
|
1449
|
+
let sbtPluginDefinition = `\naddSbtPlugin("io.shiftleft" % "sbt-dependency-graph" % "0.10.0-append-to-file3")\n`;
|
|
1450
|
+
if (isDependencyTreeBuiltIn) {
|
|
1451
|
+
sbtPluginDefinition = `\naddDependencyTreePlugin\n`;
|
|
1452
|
+
if (DEBUG_MODE) {
|
|
1453
|
+
console.log("Using addDependencyTreePlugin as the custom plugin");
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
fs.writeFileSync(tempSbtPlugins, sbtPluginDefinition);
|
|
1457
|
+
|
|
1458
|
+
for (let i in sbtProjects) {
|
|
1459
|
+
const basePath = sbtProjects[i];
|
|
1460
|
+
let dlFile = pathLib.join(tempDir, "dl-" + i + ".tmp");
|
|
1461
|
+
console.log(
|
|
1462
|
+
"Executing",
|
|
1463
|
+
SBT_CMD,
|
|
1464
|
+
"dependencyList in",
|
|
1465
|
+
basePath,
|
|
1466
|
+
"using plugins",
|
|
1467
|
+
tempSbtgDir
|
|
1468
|
+
);
|
|
1469
|
+
var sbtArgs = [];
|
|
1470
|
+
var pluginFile = null;
|
|
1471
|
+
if (standalonePluginFile) {
|
|
1472
|
+
sbtArgs = [
|
|
1473
|
+
`-addPluginSbtFile=${tempSbtPlugins}`,
|
|
1474
|
+
`"dependencyList::toFile ${dlFile} --force"`
|
|
1475
|
+
];
|
|
1476
|
+
} else {
|
|
1477
|
+
// write to the existing plugins file
|
|
1478
|
+
sbtArgs = [`"dependencyList::toFile ${dlFile} --force"`];
|
|
1479
|
+
pluginFile = utils.addPlugin(basePath, sbtPluginDefinition);
|
|
1480
|
+
}
|
|
1481
|
+
// Note that the command has to be invoked with `shell: true` to properly execut sbt
|
|
1482
|
+
const result = spawnSync(SBT_CMD, sbtArgs, {
|
|
1483
|
+
cwd: basePath,
|
|
1484
|
+
shell: true,
|
|
1485
|
+
encoding: "utf-8",
|
|
1486
|
+
timeout: TIMEOUT_MS
|
|
1487
|
+
});
|
|
1488
|
+
if (result.status !== 0 || result.error) {
|
|
1489
|
+
console.error(result.stdout, result.stderr);
|
|
1490
|
+
console.log(
|
|
1491
|
+
`1. Check if scala and sbt is installed and available in PATH. Only scala 2.10 + sbt 0.13.6+ and 2.12 + sbt 1.0+ is supported for now.`
|
|
1492
|
+
);
|
|
1493
|
+
console.log(
|
|
1494
|
+
`2. Check if the plugin net.virtual-void:sbt-dependency-graph 0.10.0-RC1 can be used in the environment`
|
|
1495
|
+
);
|
|
1496
|
+
console.log(
|
|
1497
|
+
"3. Consider creating a lockfile using sbt-dependency-lock plugin. See https://github.com/stringbean/sbt-dependency-lock"
|
|
1498
|
+
);
|
|
1499
|
+
options.failOnError && process.exit(1);
|
|
1500
|
+
} else if (DEBUG_MODE) {
|
|
1501
|
+
console.log(result.stdout);
|
|
1502
|
+
}
|
|
1503
|
+
if (!standalonePluginFile) {
|
|
1504
|
+
utils.cleanupPlugin(basePath, pluginFile);
|
|
1505
|
+
}
|
|
1506
|
+
if (fs.existsSync(dlFile)) {
|
|
1507
|
+
const cmdOutput = fs.readFileSync(dlFile, { encoding: "utf-8" });
|
|
1508
|
+
if (DEBUG_MODE) {
|
|
1509
|
+
console.log(cmdOutput);
|
|
1510
|
+
}
|
|
1511
|
+
const dlist = utils.parseKVDep(cmdOutput);
|
|
1512
|
+
if (dlist && dlist.length) {
|
|
1513
|
+
pkgList = pkgList.concat(dlist);
|
|
1514
|
+
}
|
|
1515
|
+
} else {
|
|
1516
|
+
if (options.failOnError || DEBUG_MODE) {
|
|
1517
|
+
console.log(`sbt dependencyList did not yield ${dlFile}`);
|
|
1518
|
+
}
|
|
1519
|
+
options.failOnError && process.exit(1);
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
// Cleanup
|
|
1524
|
+
fs.unlinkSync(tempSbtPlugins);
|
|
1525
|
+
} // else
|
|
1526
|
+
|
|
1527
|
+
if (DEBUG_MODE) {
|
|
1528
|
+
console.log(`Found ${pkgList.length} packages`);
|
|
1529
|
+
}
|
|
1530
|
+
pkgList = await utils.getMvnMetadata(pkgList);
|
|
1531
|
+
// Should we attempt to resolve class names
|
|
1532
|
+
if (options.resolveClass) {
|
|
1533
|
+
console.log(
|
|
1534
|
+
"Creating class names list based on available jars. This might take a few mins ..."
|
|
1535
|
+
);
|
|
1536
|
+
jarNSMapping = utils.collectJarNS(SBT_CACHE_DIR);
|
|
1537
|
+
}
|
|
1538
|
+
return buildBomNSData(options, pkgList, "maven", {
|
|
1539
|
+
src: path,
|
|
1540
|
+
filename: sbtProjects.join(", "),
|
|
1541
|
+
nsMapping: jarNSMapping,
|
|
1542
|
+
dependencies,
|
|
1543
|
+
parentComponent
|
|
1544
|
+
});
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
1547
|
+
};
|
|
1548
|
+
|
|
1549
|
+
/**
|
|
1550
|
+
* Function to create bom string for Node.js projects
|
|
1551
|
+
*
|
|
1552
|
+
* @param path to the project
|
|
1553
|
+
* @param options Parse options from the cli
|
|
1554
|
+
*/
|
|
1555
|
+
const createNodejsBom = async (path, options) => {
|
|
1556
|
+
let pkgList = [];
|
|
1557
|
+
let manifestFiles = [];
|
|
1558
|
+
let dependencies = [];
|
|
1559
|
+
let parentComponent = {};
|
|
1560
|
+
let ppurl = "";
|
|
1561
|
+
// Docker mode requires special handling
|
|
1562
|
+
if (["docker", "oci", "os"].includes(options.projectType)) {
|
|
1563
|
+
const pkgJsonFiles = utils.getAllFiles(path, "**/package.json");
|
|
1564
|
+
// Are there any package.json files in the container?
|
|
1565
|
+
if (pkgJsonFiles.length) {
|
|
1566
|
+
for (let pj of pkgJsonFiles) {
|
|
1567
|
+
const dlist = await utils.parsePkgJson(pj);
|
|
1568
|
+
if (dlist && dlist.length) {
|
|
1569
|
+
pkgList = pkgList.concat(dlist);
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
return buildBomNSData(options, pkgList, "npm", {
|
|
1573
|
+
allImports: {},
|
|
1574
|
+
src: path,
|
|
1575
|
+
filename: "package.json",
|
|
1576
|
+
parentComponent
|
|
1577
|
+
});
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1580
|
+
let allImports = {};
|
|
1581
|
+
if (
|
|
1582
|
+
!["docker", "oci", "os"].includes(options.projectType) &&
|
|
1583
|
+
!options.noBabel
|
|
1584
|
+
) {
|
|
1585
|
+
if (DEBUG_MODE) {
|
|
1586
|
+
console.log(
|
|
1587
|
+
`Performing babel-based package usage analysis with source code at ${path}`
|
|
1588
|
+
);
|
|
1589
|
+
}
|
|
1590
|
+
allImports = await findJSImports(path);
|
|
1591
|
+
}
|
|
1592
|
+
const yarnLockFiles = utils.getAllFiles(
|
|
1593
|
+
path,
|
|
1594
|
+
(options.multiProject ? "**/" : "") + "yarn.lock"
|
|
1595
|
+
);
|
|
1596
|
+
const shrinkwrapFiles = utils.getAllFiles(
|
|
1597
|
+
path,
|
|
1598
|
+
(options.multiProject ? "**/" : "") + "npm-shrinkwrap.json"
|
|
1599
|
+
);
|
|
1600
|
+
let pkgLockFiles = utils.getAllFiles(
|
|
1601
|
+
path,
|
|
1602
|
+
(options.multiProject ? "**/" : "") + "package-lock.json"
|
|
1603
|
+
);
|
|
1604
|
+
if (shrinkwrapFiles.length) {
|
|
1605
|
+
pkgLockFiles = pkgLockFiles.concat(shrinkwrapFiles);
|
|
1606
|
+
}
|
|
1607
|
+
const pnpmLockFiles = utils.getAllFiles(
|
|
1608
|
+
path,
|
|
1609
|
+
(options.multiProject ? "**/" : "") + "pnpm-lock.yaml"
|
|
1610
|
+
);
|
|
1611
|
+
const minJsFiles = utils.getAllFiles(
|
|
1612
|
+
path,
|
|
1613
|
+
(options.multiProject ? "**/" : "") + "*min.js"
|
|
1614
|
+
);
|
|
1615
|
+
const bowerFiles = utils.getAllFiles(
|
|
1616
|
+
path,
|
|
1617
|
+
(options.multiProject ? "**/" : "") + "bower.json"
|
|
1618
|
+
);
|
|
1619
|
+
// Parse min js files
|
|
1620
|
+
if (minJsFiles && minJsFiles.length) {
|
|
1621
|
+
manifestFiles = manifestFiles.concat(minJsFiles);
|
|
1622
|
+
for (let f of minJsFiles) {
|
|
1623
|
+
const dlist = await utils.parseMinJs(f);
|
|
1624
|
+
if (dlist && dlist.length) {
|
|
1625
|
+
pkgList = pkgList.concat(dlist);
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1628
|
+
}
|
|
1629
|
+
// Parse bower json files
|
|
1630
|
+
if (bowerFiles && bowerFiles.length) {
|
|
1631
|
+
manifestFiles = manifestFiles.concat(bowerFiles);
|
|
1632
|
+
for (let f of bowerFiles) {
|
|
1633
|
+
const dlist = await utils.parseBowerJson(f);
|
|
1634
|
+
if (dlist && dlist.length) {
|
|
1635
|
+
pkgList = pkgList.concat(dlist);
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
if (pnpmLockFiles && pnpmLockFiles.length) {
|
|
1640
|
+
manifestFiles = manifestFiles.concat(pnpmLockFiles);
|
|
1641
|
+
for (let f of pnpmLockFiles) {
|
|
1642
|
+
const basePath = pathLib.dirname(f);
|
|
1643
|
+
// Determine the parent component
|
|
1644
|
+
const packageJsonF = pathLib.join(basePath, "package.json");
|
|
1645
|
+
if (fs.existsSync(packageJsonF)) {
|
|
1646
|
+
const pcs = await utils.parsePkgJson(packageJsonF);
|
|
1647
|
+
if (pcs.length) {
|
|
1648
|
+
parentComponent = pcs[0];
|
|
1649
|
+
parentComponent.type = "application";
|
|
1650
|
+
}
|
|
1651
|
+
} else {
|
|
1652
|
+
let dirName = pathLib.dirname(f);
|
|
1653
|
+
const tmpA = dirName.split(pathLib.sep);
|
|
1654
|
+
dirName = tmpA[tmpA.length - 1];
|
|
1655
|
+
parentComponent = {
|
|
1656
|
+
group: "",
|
|
1657
|
+
name: dirName,
|
|
1658
|
+
type: "application"
|
|
1659
|
+
};
|
|
1660
|
+
ppurl = new PackageURL(
|
|
1661
|
+
"application",
|
|
1662
|
+
parentComponent.group,
|
|
1663
|
+
parentComponent.name,
|
|
1664
|
+
parentComponent.version,
|
|
1665
|
+
null,
|
|
1666
|
+
null
|
|
1667
|
+
).toString();
|
|
1668
|
+
parentComponent["bom-ref"] = ppurl;
|
|
1669
|
+
parentComponent["purl"] = ppurl;
|
|
1670
|
+
}
|
|
1671
|
+
// Parse the pnpm file
|
|
1672
|
+
const parsedList = await utils.parsePnpmLock(f, parentComponent);
|
|
1673
|
+
const dlist = parsedList.pkgList;
|
|
1674
|
+
if (dlist && dlist.length) {
|
|
1675
|
+
pkgList = pkgList.concat(dlist);
|
|
1676
|
+
}
|
|
1677
|
+
if (parsedList.dependenciesList && parsedList.dependenciesList) {
|
|
1678
|
+
dependencies = mergeDependencies(
|
|
1679
|
+
dependencies,
|
|
1680
|
+
parsedList.dependenciesList
|
|
1681
|
+
);
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
return buildBomNSData(options, pkgList, "npm", {
|
|
1685
|
+
allImports,
|
|
1686
|
+
src: path,
|
|
1687
|
+
filename: manifestFiles.join(", "),
|
|
1688
|
+
dependencies,
|
|
1689
|
+
parentComponent
|
|
1690
|
+
});
|
|
1691
|
+
}
|
|
1692
|
+
if (pkgLockFiles && pkgLockFiles.length) {
|
|
1693
|
+
manifestFiles = manifestFiles.concat(pkgLockFiles);
|
|
1694
|
+
for (let f of pkgLockFiles) {
|
|
1695
|
+
if (DEBUG_MODE) {
|
|
1696
|
+
console.log(`Parsing ${f}`);
|
|
1697
|
+
}
|
|
1698
|
+
// Parse package-lock.json if available
|
|
1699
|
+
const parsedList = await utils.parsePkgLock(f);
|
|
1700
|
+
const dlist = parsedList.pkgList;
|
|
1701
|
+
parentComponent = dlist.splice(0, 1)[0];
|
|
1702
|
+
parentComponent.type = "application";
|
|
1703
|
+
if (dlist && dlist.length) {
|
|
1704
|
+
pkgList = pkgList.concat(dlist);
|
|
1705
|
+
}
|
|
1706
|
+
if (parsedList.dependenciesList && parsedList.dependenciesList) {
|
|
1707
|
+
dependencies = mergeDependencies(
|
|
1708
|
+
dependencies,
|
|
1709
|
+
parsedList.dependenciesList
|
|
1710
|
+
);
|
|
1711
|
+
}
|
|
1712
|
+
}
|
|
1713
|
+
}
|
|
1714
|
+
if (fs.existsSync(pathLib.join(path, "rush.json"))) {
|
|
1715
|
+
// Rush.js creates node_modules inside common/temp directory
|
|
1716
|
+
const nmDir = pathLib.join(path, "common", "temp", "node_modules");
|
|
1717
|
+
// Do rush install if we don't have node_modules directory
|
|
1718
|
+
if (!fs.existsSync(nmDir)) {
|
|
1719
|
+
console.log("Executing 'rush install --no-link'", path);
|
|
1720
|
+
const result = spawnSync(
|
|
1721
|
+
"rush",
|
|
1722
|
+
["install", "--no-link", "--bypass-policy"],
|
|
1723
|
+
{
|
|
1724
|
+
cwd: path,
|
|
1725
|
+
encoding: "utf-8"
|
|
1726
|
+
}
|
|
1727
|
+
);
|
|
1728
|
+
if (result.status == 1 || result.error) {
|
|
1729
|
+
console.error(result.stdout, result.stderr);
|
|
1730
|
+
options.failOnError && process.exit(1);
|
|
1731
|
+
}
|
|
1732
|
+
}
|
|
1733
|
+
// Look for shrinkwrap file
|
|
1734
|
+
const swFile = pathLib.join(
|
|
1735
|
+
path,
|
|
1736
|
+
"tools",
|
|
1737
|
+
"build-tasks",
|
|
1738
|
+
".rush",
|
|
1739
|
+
"temp",
|
|
1740
|
+
"shrinkwrap-deps.json"
|
|
1741
|
+
);
|
|
1742
|
+
const pnpmLock = pathLib.join(
|
|
1743
|
+
path,
|
|
1744
|
+
"common",
|
|
1745
|
+
"config",
|
|
1746
|
+
"rush",
|
|
1747
|
+
"pnpm-lock.yaml"
|
|
1748
|
+
);
|
|
1749
|
+
if (fs.existsSync(swFile)) {
|
|
1750
|
+
const pkgList = await utils.parseNodeShrinkwrap(swFile);
|
|
1751
|
+
return buildBomNSData(options, pkgList, "npm", {
|
|
1752
|
+
allImports,
|
|
1753
|
+
src: path,
|
|
1754
|
+
filename: "shrinkwrap-deps.json"
|
|
1755
|
+
});
|
|
1756
|
+
} else if (fs.existsSync(pnpmLock)) {
|
|
1757
|
+
const pkgList = await utils.parsePnpmLock(pnpmLock);
|
|
1758
|
+
return buildBomNSData(options, pkgList, "npm", {
|
|
1759
|
+
allImports,
|
|
1760
|
+
src: path,
|
|
1761
|
+
filename: "pnpm-lock.yaml"
|
|
1762
|
+
});
|
|
1763
|
+
} else {
|
|
1764
|
+
console.log(
|
|
1765
|
+
"Neither shrinkwrap file: ",
|
|
1766
|
+
swFile,
|
|
1767
|
+
" nor pnpm lockfile",
|
|
1768
|
+
pnpmLock,
|
|
1769
|
+
"was found!"
|
|
1770
|
+
);
|
|
1771
|
+
options.failOnError && process.exit(1);
|
|
1772
|
+
}
|
|
1773
|
+
}
|
|
1774
|
+
if (yarnLockFiles && yarnLockFiles.length) {
|
|
1775
|
+
manifestFiles = manifestFiles.concat(yarnLockFiles);
|
|
1776
|
+
for (let f of yarnLockFiles) {
|
|
1777
|
+
if (DEBUG_MODE) {
|
|
1778
|
+
console.log(`Parsing ${f}`);
|
|
1779
|
+
}
|
|
1780
|
+
const basePath = pathLib.dirname(f);
|
|
1781
|
+
// Determine the parent component
|
|
1782
|
+
const packageJsonF = pathLib.join(basePath, "package.json");
|
|
1783
|
+
if (fs.existsSync(packageJsonF)) {
|
|
1784
|
+
const pcs = await utils.parsePkgJson(packageJsonF);
|
|
1785
|
+
if (pcs.length) {
|
|
1786
|
+
parentComponent = pcs[0];
|
|
1787
|
+
parentComponent.type = "application";
|
|
1788
|
+
}
|
|
1789
|
+
} else {
|
|
1790
|
+
let dirName = pathLib.dirname(f);
|
|
1791
|
+
const tmpA = dirName.split(pathLib.sep);
|
|
1792
|
+
dirName = tmpA[tmpA.length - 1];
|
|
1793
|
+
parentComponent = {
|
|
1794
|
+
group: "",
|
|
1795
|
+
name: dirName,
|
|
1796
|
+
type: "application"
|
|
1797
|
+
};
|
|
1798
|
+
ppurl = new PackageURL(
|
|
1799
|
+
"application",
|
|
1800
|
+
parentComponent.group,
|
|
1801
|
+
parentComponent.name,
|
|
1802
|
+
parentComponent.version,
|
|
1803
|
+
null,
|
|
1804
|
+
null
|
|
1805
|
+
).toString();
|
|
1806
|
+
parentComponent["bom-ref"] = ppurl;
|
|
1807
|
+
parentComponent["purl"] = ppurl;
|
|
1808
|
+
}
|
|
1809
|
+
// Parse yarn.lock if available. This check is after rush.json since
|
|
1810
|
+
// rush.js could include yarn.lock :(
|
|
1811
|
+
const parsedList = await utils.parseYarnLock(f);
|
|
1812
|
+
const dlist = parsedList.pkgList;
|
|
1813
|
+
if (dlist && dlist.length) {
|
|
1814
|
+
pkgList = pkgList.concat(dlist);
|
|
1815
|
+
}
|
|
1816
|
+
const rdeplist = [];
|
|
1817
|
+
if (parsedList.dependenciesList && parsedList.dependenciesList) {
|
|
1818
|
+
// Inject parent component to the dependency tree to make it complete
|
|
1819
|
+
// In case of yarn, yarn list command lists every root package as a direct dependency
|
|
1820
|
+
// The same logic is matched with this for loop although this is incorrect since even dev dependencies would get included here
|
|
1821
|
+
for (const dobj of parsedList.dependenciesList) {
|
|
1822
|
+
rdeplist.push(dobj.ref);
|
|
1823
|
+
}
|
|
1824
|
+
// Fixes: 212. Handle case where there are no package.json to determine the parent package
|
|
1825
|
+
if (Object.keys(parentComponent).length && parentComponent.name) {
|
|
1826
|
+
const ppurl = new PackageURL(
|
|
1827
|
+
"application",
|
|
1828
|
+
parentComponent.group,
|
|
1829
|
+
parentComponent.name,
|
|
1830
|
+
parentComponent.version,
|
|
1831
|
+
null,
|
|
1832
|
+
null
|
|
1833
|
+
).toString();
|
|
1834
|
+
parsedList.dependenciesList.push({
|
|
1835
|
+
ref: decodeURIComponent(ppurl),
|
|
1836
|
+
dependsOn: rdeplist
|
|
1837
|
+
});
|
|
1838
|
+
}
|
|
1839
|
+
dependencies = mergeDependencies(
|
|
1840
|
+
dependencies,
|
|
1841
|
+
parsedList.dependenciesList
|
|
1842
|
+
);
|
|
1843
|
+
}
|
|
1844
|
+
}
|
|
1845
|
+
}
|
|
1846
|
+
if (!pkgList.length && fs.existsSync(pathLib.join(path, "node_modules"))) {
|
|
1847
|
+
const pkgJsonFiles = utils.getAllFiles(
|
|
1848
|
+
pathLib.join(path, "node_modules"),
|
|
1849
|
+
"**/package.json"
|
|
1850
|
+
);
|
|
1851
|
+
manifestFiles = manifestFiles.concat(pkgJsonFiles);
|
|
1852
|
+
for (let pkgjf of pkgJsonFiles) {
|
|
1853
|
+
const dlist = await utils.parsePkgJson(pkgjf);
|
|
1854
|
+
if (dlist && dlist.length) {
|
|
1855
|
+
pkgList = pkgList.concat(dlist);
|
|
1856
|
+
}
|
|
1857
|
+
}
|
|
1858
|
+
return buildBomNSData(options, pkgList, "npm", {
|
|
1859
|
+
allImports,
|
|
1860
|
+
src: path,
|
|
1861
|
+
filename: manifestFiles.join(", "),
|
|
1862
|
+
dependencies,
|
|
1863
|
+
parentComponent
|
|
1864
|
+
});
|
|
1865
|
+
}
|
|
1866
|
+
// Projects containing just min files or bower
|
|
1867
|
+
if (pkgList.length) {
|
|
1868
|
+
return buildBomNSData(options, pkgList, "npm", {
|
|
1869
|
+
allImports,
|
|
1870
|
+
src: path,
|
|
1871
|
+
filename: manifestFiles.join(", "),
|
|
1872
|
+
dependencies,
|
|
1873
|
+
parentComponent
|
|
1874
|
+
});
|
|
1875
|
+
}
|
|
1876
|
+
return {};
|
|
1877
|
+
};
|
|
1878
|
+
|
|
1879
|
+
/**
|
|
1880
|
+
* Function to create bom string for Python projects
|
|
1881
|
+
*
|
|
1882
|
+
* @param path to the project
|
|
1883
|
+
* @param options Parse options from the cli
|
|
1884
|
+
*/
|
|
1885
|
+
const createPythonBom = async (path, options) => {
|
|
1886
|
+
let pkgList = [];
|
|
1887
|
+
let dlist = [];
|
|
1888
|
+
let metadataFilename = "";
|
|
1889
|
+
const pipenvMode = fs.existsSync(pathLib.join(path, "Pipfile"));
|
|
1890
|
+
const poetryFiles = utils.getAllFiles(
|
|
1891
|
+
path,
|
|
1892
|
+
(options.multiProject ? "**/" : "") + "poetry.lock"
|
|
1893
|
+
);
|
|
1894
|
+
const reqFiles = utils.getAllFiles(
|
|
1895
|
+
path,
|
|
1896
|
+
(options.multiProject ? "**/" : "") + "requirements.txt"
|
|
1897
|
+
);
|
|
1898
|
+
const reqDirFiles = utils.getAllFiles(
|
|
1899
|
+
path,
|
|
1900
|
+
(options.multiProject ? "**/" : "") + "requirements/*.txt"
|
|
1901
|
+
);
|
|
1902
|
+
const metadataFiles = utils.getAllFiles(
|
|
1903
|
+
path,
|
|
1904
|
+
(options.multiProject ? "**/site-packages/**/" : "") + "METADATA"
|
|
1905
|
+
);
|
|
1906
|
+
const whlFiles = utils.getAllFiles(
|
|
1907
|
+
path,
|
|
1908
|
+
(options.multiProject ? "**/" : "") + "*.whl"
|
|
1909
|
+
);
|
|
1910
|
+
const eggInfoFiles = utils.getAllFiles(
|
|
1911
|
+
path,
|
|
1912
|
+
(options.multiProject ? "**/" : "") + "*.egg-info"
|
|
1913
|
+
);
|
|
1914
|
+
const setupPy = pathLib.join(path, "setup.py");
|
|
1915
|
+
const requirementsMode =
|
|
1916
|
+
(reqFiles && reqFiles.length) || (reqDirFiles && reqDirFiles.length);
|
|
1917
|
+
const poetryMode = poetryFiles && poetryFiles.length;
|
|
1918
|
+
const setupPyMode = fs.existsSync(setupPy);
|
|
1919
|
+
// Poetry sets up its own virtual env containing site-packages so
|
|
1920
|
+
// we give preference to poetry lock file. Issue# 129
|
|
1921
|
+
if (poetryMode) {
|
|
1922
|
+
for (let f of poetryFiles) {
|
|
1923
|
+
const lockData = fs.readFileSync(f, { encoding: "utf-8" });
|
|
1924
|
+
const dlist = await utils.parsePoetrylockData(lockData);
|
|
1925
|
+
if (dlist && dlist.length) {
|
|
1926
|
+
pkgList = pkgList.concat(dlist);
|
|
1927
|
+
}
|
|
1928
|
+
}
|
|
1929
|
+
return buildBomNSData(options, pkgList, "pypi", {
|
|
1930
|
+
src: path,
|
|
1931
|
+
filename: poetryFiles.join(", ")
|
|
1932
|
+
});
|
|
1933
|
+
} else if (metadataFiles && metadataFiles.length) {
|
|
1934
|
+
// dist-info directories
|
|
1935
|
+
for (let mf of metadataFiles) {
|
|
1936
|
+
const mData = fs.readFileSync(mf, {
|
|
1937
|
+
encoding: "utf-8"
|
|
1938
|
+
});
|
|
1939
|
+
const dlist = utils.parseBdistMetadata(mData);
|
|
1940
|
+
if (dlist && dlist.length) {
|
|
1941
|
+
pkgList = pkgList.concat(dlist);
|
|
1942
|
+
}
|
|
1943
|
+
}
|
|
1944
|
+
}
|
|
1945
|
+
// .whl files. Zip file containing dist-info directory
|
|
1946
|
+
if (whlFiles && whlFiles.length) {
|
|
1947
|
+
for (let wf of whlFiles) {
|
|
1948
|
+
const mData = await utils.readZipEntry(wf, "METADATA");
|
|
1949
|
+
if (mData) {
|
|
1950
|
+
const dlist = utils.parseBdistMetadata(mData);
|
|
1951
|
+
if (dlist && dlist.length) {
|
|
1952
|
+
pkgList = pkgList.concat(dlist);
|
|
1953
|
+
}
|
|
1954
|
+
}
|
|
1955
|
+
}
|
|
1956
|
+
}
|
|
1957
|
+
// .egg-info files
|
|
1958
|
+
if (eggInfoFiles && eggInfoFiles.length) {
|
|
1959
|
+
for (let ef of eggInfoFiles) {
|
|
1960
|
+
dlist = utils.parseBdistMetadata(
|
|
1961
|
+
fs.readFileSync(ef, { encoding: "utf-8" })
|
|
1962
|
+
);
|
|
1963
|
+
if (dlist && dlist.length) {
|
|
1964
|
+
pkgList = pkgList.concat(dlist);
|
|
1965
|
+
}
|
|
1966
|
+
}
|
|
1967
|
+
}
|
|
1968
|
+
if (requirementsMode || pipenvMode || setupPyMode) {
|
|
1969
|
+
if (pipenvMode) {
|
|
1970
|
+
spawnSync("pipenv", ["install"], { cwd: path, encoding: "utf-8" });
|
|
1971
|
+
const piplockFile = pathLib.join(path, "Pipfile.lock");
|
|
1972
|
+
if (fs.existsSync(piplockFile)) {
|
|
1973
|
+
const lockData = JSON.parse(fs.readFileSync(piplockFile));
|
|
1974
|
+
dlist = await utils.parsePiplockData(lockData);
|
|
1975
|
+
if (dlist && dlist.length) {
|
|
1976
|
+
pkgList = pkgList.concat(dlist);
|
|
1977
|
+
}
|
|
1978
|
+
} else {
|
|
1979
|
+
console.error("Pipfile.lock not found at", path);
|
|
1980
|
+
options.failOnError && process.exit(1);
|
|
1981
|
+
}
|
|
1982
|
+
} else if (requirementsMode) {
|
|
1983
|
+
metadataFilename = "requirements.txt";
|
|
1984
|
+
if (reqFiles && reqFiles.length) {
|
|
1985
|
+
for (let f of reqFiles) {
|
|
1986
|
+
const reqData = fs.readFileSync(f, { encoding: "utf-8" });
|
|
1987
|
+
const dlist = await utils.parseReqFile(reqData);
|
|
1988
|
+
if (dlist && dlist.length) {
|
|
1989
|
+
pkgList = pkgList.concat(dlist);
|
|
1990
|
+
}
|
|
1991
|
+
}
|
|
1992
|
+
metadataFilename = reqFiles.join(", ");
|
|
1993
|
+
} else if (reqDirFiles && reqDirFiles.length) {
|
|
1994
|
+
for (let j in reqDirFiles) {
|
|
1995
|
+
const f = reqDirFiles[j];
|
|
1996
|
+
const reqData = fs.readFileSync(f, { encoding: "utf-8" });
|
|
1997
|
+
const dlist = await utils.parseReqFile(reqData);
|
|
1998
|
+
if (dlist && dlist.length) {
|
|
1999
|
+
pkgList = pkgList.concat(dlist);
|
|
2000
|
+
}
|
|
2001
|
+
}
|
|
2002
|
+
metadataFilename = reqDirFiles.join(", ");
|
|
2003
|
+
}
|
|
2004
|
+
} else if (setupPyMode) {
|
|
2005
|
+
const setupPyData = fs.readFileSync(setupPy, { encoding: "utf-8" });
|
|
2006
|
+
dlist = await utils.parseSetupPyFile(setupPyData);
|
|
2007
|
+
if (dlist && dlist.length) {
|
|
2008
|
+
pkgList = pkgList.concat(dlist);
|
|
2009
|
+
}
|
|
2010
|
+
}
|
|
2011
|
+
}
|
|
2012
|
+
if (pkgList.length) {
|
|
2013
|
+
return buildBomNSData(options, pkgList, "pypi", {
|
|
2014
|
+
src: path,
|
|
2015
|
+
filename: metadataFilename
|
|
2016
|
+
});
|
|
2017
|
+
}
|
|
2018
|
+
return {};
|
|
2019
|
+
};
|
|
2020
|
+
|
|
2021
|
+
/**
|
|
2022
|
+
* Function to create bom string for Go projects
|
|
2023
|
+
*
|
|
2024
|
+
* @param path to the project
|
|
2025
|
+
* @param options Parse options from the cli
|
|
2026
|
+
*/
|
|
2027
|
+
const createGoBom = async (path, options) => {
|
|
2028
|
+
let pkgList = [];
|
|
2029
|
+
// Is this a binary file
|
|
2030
|
+
let maybeBinary = false;
|
|
2031
|
+
try {
|
|
2032
|
+
maybeBinary = fs.statSync(path).isFile();
|
|
2033
|
+
} catch (err) {
|
|
2034
|
+
maybeBinary = false;
|
|
2035
|
+
}
|
|
2036
|
+
if (maybeBinary) {
|
|
2037
|
+
const buildInfoData = binaryLib.getGoBuildInfo(path);
|
|
2038
|
+
const dlist = await utils.parseGoVersionData(buildInfoData);
|
|
2039
|
+
if (dlist && dlist.length) {
|
|
2040
|
+
pkgList = pkgList.concat(dlist);
|
|
2041
|
+
}
|
|
2042
|
+
// Since this pkg list is derived from the binary mark them as used.
|
|
2043
|
+
const allImports = {};
|
|
2044
|
+
for (let mpkg of pkgList) {
|
|
2045
|
+
let pkgFullName = `${mpkg.group}/${mpkg.name}`;
|
|
2046
|
+
allImports[pkgFullName] = true;
|
|
2047
|
+
}
|
|
2048
|
+
return buildBomNSData(options, pkgList, "golang", {
|
|
2049
|
+
allImports,
|
|
2050
|
+
src: path,
|
|
2051
|
+
filename: path
|
|
2052
|
+
});
|
|
2053
|
+
}
|
|
2054
|
+
|
|
2055
|
+
// Read in go.sum and merge all go.sum files.
|
|
2056
|
+
const gosumFiles = utils.getAllFiles(
|
|
2057
|
+
path,
|
|
2058
|
+
(options.multiProject ? "**/" : "") + "go.sum"
|
|
2059
|
+
);
|
|
2060
|
+
|
|
2061
|
+
// If USE_GOSUM is true, generate BOM components only using go.sum.
|
|
2062
|
+
const useGosum = process.env.USE_GOSUM == "true";
|
|
2063
|
+
if (useGosum && gosumFiles.length) {
|
|
2064
|
+
console.warn(
|
|
2065
|
+
"Using go.sum to generate BOMs for go projects may return an inaccurate representation of transitive dependencies.\nSee: https://github.com/golang/go/wiki/Modules#is-gosum-a-lock-file-why-does-gosum-include-information-for-module-versions-i-am-no-longer-using\n",
|
|
2066
|
+
"Set USE_GOSUM=false to generate BOMs using go.mod as the dependency source of truth."
|
|
2067
|
+
);
|
|
2068
|
+
for (let f of gosumFiles) {
|
|
2069
|
+
if (DEBUG_MODE) {
|
|
2070
|
+
console.log(`Parsing ${f}`);
|
|
2071
|
+
}
|
|
2072
|
+
const gosumData = fs.readFileSync(f, { encoding: "utf-8" });
|
|
2073
|
+
const dlist = await utils.parseGosumData(gosumData);
|
|
2074
|
+
if (dlist && dlist.length) {
|
|
2075
|
+
pkgList = pkgList.concat(dlist);
|
|
2076
|
+
}
|
|
2077
|
+
}
|
|
2078
|
+
return buildBomNSData(options, pkgList, "golang", {
|
|
2079
|
+
src: path,
|
|
2080
|
+
filename: gosumFiles.join(", ")
|
|
2081
|
+
});
|
|
2082
|
+
}
|
|
2083
|
+
|
|
2084
|
+
// If USE_GOSUM is false, generate BOM components using go.mod.
|
|
2085
|
+
const gosumMap = {};
|
|
2086
|
+
if (gosumFiles.length) {
|
|
2087
|
+
for (let f of gosumFiles) {
|
|
2088
|
+
if (DEBUG_MODE) {
|
|
2089
|
+
console.log(`Parsing ${f}`);
|
|
2090
|
+
}
|
|
2091
|
+
const gosumData = fs.readFileSync(f, { encoding: "utf-8" });
|
|
2092
|
+
const dlist = await utils.parseGosumData(gosumData);
|
|
2093
|
+
if (dlist && dlist.length) {
|
|
2094
|
+
dlist.forEach((pkg) => {
|
|
2095
|
+
gosumMap[`${pkg.group}/${pkg.name}/${pkg.version}`] = pkg._integrity;
|
|
2096
|
+
});
|
|
2097
|
+
}
|
|
2098
|
+
}
|
|
2099
|
+
}
|
|
2100
|
+
|
|
2101
|
+
// Read in data from Gopkg.lock files if they exist
|
|
2102
|
+
const gopkgLockFiles = utils.getAllFiles(
|
|
2103
|
+
path,
|
|
2104
|
+
(options.multiProject ? "**/" : "") + "Gopkg.lock"
|
|
2105
|
+
);
|
|
2106
|
+
|
|
2107
|
+
// Read in go.mod files and parse BOM components with checksums from gosumData
|
|
2108
|
+
const gomodFiles = utils.getAllFiles(
|
|
2109
|
+
path,
|
|
2110
|
+
(options.multiProject ? "**/" : "") + "go.mod"
|
|
2111
|
+
);
|
|
2112
|
+
if (gomodFiles.length) {
|
|
2113
|
+
// Use the go list -deps and go mod why commands to generate a good quality BoM for non-docker invocations
|
|
2114
|
+
if (!["docker", "oci", "os"].includes(options.projectType)) {
|
|
2115
|
+
for (let f of gomodFiles) {
|
|
2116
|
+
const basePath = pathLib.dirname(f);
|
|
2117
|
+
// Ignore vendor packages
|
|
2118
|
+
if (basePath.includes("vendor") || basePath.includes("build")) {
|
|
2119
|
+
continue;
|
|
2120
|
+
}
|
|
2121
|
+
if (DEBUG_MODE) {
|
|
2122
|
+
console.log("Executing go list -deps in", basePath);
|
|
2123
|
+
}
|
|
2124
|
+
const result = spawnSync(
|
|
2125
|
+
"go",
|
|
2126
|
+
[
|
|
2127
|
+
"list",
|
|
2128
|
+
"-deps",
|
|
2129
|
+
"-f",
|
|
2130
|
+
"'{{with .Module}}{{.Path}} {{.Version}}{{end}}'",
|
|
2131
|
+
"./..."
|
|
2132
|
+
],
|
|
2133
|
+
{ cwd: basePath, encoding: "utf-8", timeout: TIMEOUT_MS }
|
|
2134
|
+
);
|
|
2135
|
+
if (result.status !== 0 || result.error) {
|
|
2136
|
+
console.error(result.stdout, result.stderr);
|
|
2137
|
+
options.failOnError && process.exit(1);
|
|
2138
|
+
}
|
|
2139
|
+
const stdout = result.stdout;
|
|
2140
|
+
if (stdout) {
|
|
2141
|
+
const cmdOutput = Buffer.from(stdout).toString();
|
|
2142
|
+
const dlist = await utils.parseGoListDep(cmdOutput, gosumMap);
|
|
2143
|
+
if (dlist && dlist.length) {
|
|
2144
|
+
pkgList = pkgList.concat(dlist);
|
|
2145
|
+
}
|
|
2146
|
+
} else {
|
|
2147
|
+
console.error("go unexpectedly didn't return any output");
|
|
2148
|
+
options.failOnError && process.exit(1);
|
|
2149
|
+
}
|
|
2150
|
+
}
|
|
2151
|
+
const allImports = {};
|
|
2152
|
+
let circuitBreak = false;
|
|
2153
|
+
if (DEBUG_MODE) {
|
|
2154
|
+
console.log(
|
|
2155
|
+
`Attempting to detect required packages using "go mod why" command for ${pkgList.length} packages`
|
|
2156
|
+
);
|
|
2157
|
+
}
|
|
2158
|
+
// Using go mod why detect required packages
|
|
2159
|
+
for (let apkg of pkgList) {
|
|
2160
|
+
if (circuitBreak) {
|
|
2161
|
+
break;
|
|
2162
|
+
}
|
|
2163
|
+
let pkgFullName = `${apkg.group}/${apkg.name}`;
|
|
2164
|
+
if (DEBUG_MODE) {
|
|
2165
|
+
console.log(`go mod why -m -vendor ${pkgFullName}`);
|
|
2166
|
+
}
|
|
2167
|
+
const mresult = spawnSync(
|
|
2168
|
+
"go",
|
|
2169
|
+
["mod", "why", "-m", "-vendor", pkgFullName],
|
|
2170
|
+
{ cwd: path, encoding: "utf-8", timeout: TIMEOUT_MS }
|
|
2171
|
+
);
|
|
2172
|
+
if (mresult.status !== 0 || mresult.error) {
|
|
2173
|
+
if (DEBUG_MODE) {
|
|
2174
|
+
console.log(mresult.stdout, mresult.stderr);
|
|
2175
|
+
}
|
|
2176
|
+
circuitBreak = true;
|
|
2177
|
+
} else {
|
|
2178
|
+
const mstdout = mresult.stdout;
|
|
2179
|
+
if (mstdout) {
|
|
2180
|
+
const cmdOutput = Buffer.from(mstdout).toString();
|
|
2181
|
+
let whyPkg = utils.parseGoModWhy(cmdOutput);
|
|
2182
|
+
if (whyPkg == pkgFullName) {
|
|
2183
|
+
allImports[pkgFullName] = true;
|
|
2184
|
+
}
|
|
2185
|
+
}
|
|
2186
|
+
}
|
|
2187
|
+
}
|
|
2188
|
+
if (DEBUG_MODE) {
|
|
2189
|
+
console.log(`Required packages: ${Object.keys(allImports).length}`);
|
|
2190
|
+
}
|
|
2191
|
+
return buildBomNSData(options, pkgList, "golang", {
|
|
2192
|
+
allImports,
|
|
2193
|
+
src: path,
|
|
2194
|
+
filename: gomodFiles.join(", ")
|
|
2195
|
+
});
|
|
2196
|
+
}
|
|
2197
|
+
// Parse the gomod files manually. The resultant BoM would be incomplete
|
|
2198
|
+
if (!["docker", "oci", "os"].includes(options.projectType)) {
|
|
2199
|
+
console.log(
|
|
2200
|
+
"Manually parsing go.mod files. The resultant BoM would be incomplete."
|
|
2201
|
+
);
|
|
2202
|
+
}
|
|
2203
|
+
for (let f of gomodFiles) {
|
|
2204
|
+
if (DEBUG_MODE) {
|
|
2205
|
+
console.log(`Parsing ${f}`);
|
|
2206
|
+
}
|
|
2207
|
+
const gomodData = fs.readFileSync(f, { encoding: "utf-8" });
|
|
2208
|
+
const dlist = await utils.parseGoModData(gomodData, gosumMap);
|
|
2209
|
+
if (dlist && dlist.length) {
|
|
2210
|
+
pkgList = pkgList.concat(dlist);
|
|
2211
|
+
}
|
|
2212
|
+
}
|
|
2213
|
+
return buildBomNSData(options, pkgList, "golang", {
|
|
2214
|
+
src: path,
|
|
2215
|
+
filename: gomodFiles.join(", ")
|
|
2216
|
+
});
|
|
2217
|
+
} else if (gopkgLockFiles.length) {
|
|
2218
|
+
for (let f of gopkgLockFiles) {
|
|
2219
|
+
if (DEBUG_MODE) {
|
|
2220
|
+
console.log(`Parsing ${f}`);
|
|
2221
|
+
}
|
|
2222
|
+
const gopkgData = fs.readFileSync(f, {
|
|
2223
|
+
encoding: "utf-8"
|
|
2224
|
+
});
|
|
2225
|
+
const dlist = await utils.parseGopkgData(gopkgData);
|
|
2226
|
+
if (dlist && dlist.length) {
|
|
2227
|
+
pkgList = pkgList.concat(dlist);
|
|
2228
|
+
}
|
|
2229
|
+
}
|
|
2230
|
+
return buildBomNSData(options, pkgList, "golang", {
|
|
2231
|
+
src: path,
|
|
2232
|
+
filename: gopkgLockFiles.join(", ")
|
|
2233
|
+
});
|
|
2234
|
+
}
|
|
2235
|
+
return {};
|
|
2236
|
+
};
|
|
2237
|
+
|
|
2238
|
+
/**
|
|
2239
|
+
* Function to create bom string for Rust projects
|
|
2240
|
+
*
|
|
2241
|
+
* @param path to the project
|
|
2242
|
+
* @param options Parse options from the cli
|
|
2243
|
+
*/
|
|
2244
|
+
const createRustBom = async (path, options) => {
|
|
2245
|
+
let pkgList = [];
|
|
2246
|
+
// Is this a binary file
|
|
2247
|
+
let maybeBinary = false;
|
|
2248
|
+
try {
|
|
2249
|
+
maybeBinary = fs.statSync(path).isFile();
|
|
2250
|
+
} catch (err) {
|
|
2251
|
+
maybeBinary = false;
|
|
2252
|
+
}
|
|
2253
|
+
if (maybeBinary) {
|
|
2254
|
+
const cargoData = binaryLib.getCargoAuditableInfo(path);
|
|
2255
|
+
const dlist = await utils.parseCargoAuditableData(cargoData);
|
|
2256
|
+
if (dlist && dlist.length) {
|
|
2257
|
+
pkgList = pkgList.concat(dlist);
|
|
2258
|
+
}
|
|
2259
|
+
// Since this pkg list is derived from the binary mark them as used.
|
|
2260
|
+
const allImports = {};
|
|
2261
|
+
for (let mpkg of pkgList) {
|
|
2262
|
+
let pkgFullName = `${mpkg.group}/${mpkg.name}`;
|
|
2263
|
+
allImports[pkgFullName] = true;
|
|
2264
|
+
}
|
|
2265
|
+
return buildBomNSData(options, pkgList, "cargo", {
|
|
2266
|
+
allImports,
|
|
2267
|
+
src: path,
|
|
2268
|
+
filename: path
|
|
2269
|
+
});
|
|
2270
|
+
}
|
|
2271
|
+
let cargoLockFiles = utils.getAllFiles(
|
|
2272
|
+
path,
|
|
2273
|
+
(options.multiProject ? "**/" : "") + "Cargo.lock"
|
|
2274
|
+
);
|
|
2275
|
+
const cargoFiles = utils.getAllFiles(
|
|
2276
|
+
path,
|
|
2277
|
+
(options.multiProject ? "**/" : "") + "Cargo.toml"
|
|
2278
|
+
);
|
|
2279
|
+
const cargoMode = cargoFiles.length;
|
|
2280
|
+
let cargoLockMode = cargoLockFiles.length;
|
|
2281
|
+
if (cargoMode && !cargoLockMode) {
|
|
2282
|
+
for (let f of cargoFiles) {
|
|
2283
|
+
if (DEBUG_MODE) {
|
|
2284
|
+
console.log(`Parsing ${f}`);
|
|
2285
|
+
}
|
|
2286
|
+
const cargoData = fs.readFileSync(f, { encoding: "utf-8" });
|
|
2287
|
+
const dlist = await utils.parseCargoTomlData(cargoData);
|
|
2288
|
+
if (dlist && dlist.length) {
|
|
2289
|
+
pkgList = pkgList.concat(dlist);
|
|
2290
|
+
}
|
|
2291
|
+
}
|
|
2292
|
+
return buildBomNSData(options, pkgList, "cargo", {
|
|
2293
|
+
src: path,
|
|
2294
|
+
filename: cargoFiles.join(", ")
|
|
2295
|
+
});
|
|
2296
|
+
}
|
|
2297
|
+
// Get the new lock files
|
|
2298
|
+
cargoLockFiles = utils.getAllFiles(
|
|
2299
|
+
path,
|
|
2300
|
+
(options.multiProject ? "**/" : "") + "Cargo.lock"
|
|
2301
|
+
);
|
|
2302
|
+
if (cargoLockFiles.length) {
|
|
2303
|
+
for (let f of cargoLockFiles) {
|
|
2304
|
+
if (DEBUG_MODE) {
|
|
2305
|
+
console.log(`Parsing ${f}`);
|
|
2306
|
+
}
|
|
2307
|
+
const cargoData = fs.readFileSync(f, { encoding: "utf-8" });
|
|
2308
|
+
const dlist = await utils.parseCargoData(cargoData);
|
|
2309
|
+
if (dlist && dlist.length) {
|
|
2310
|
+
pkgList = pkgList.concat(dlist);
|
|
2311
|
+
}
|
|
2312
|
+
}
|
|
2313
|
+
return buildBomNSData(options, pkgList, "cargo", {
|
|
2314
|
+
src: path,
|
|
2315
|
+
filename: cargoLockFiles.join(", ")
|
|
2316
|
+
});
|
|
2317
|
+
}
|
|
2318
|
+
return {};
|
|
2319
|
+
};
|
|
2320
|
+
|
|
2321
|
+
/**
|
|
2322
|
+
* Function to create bom string for Dart projects
|
|
2323
|
+
*
|
|
2324
|
+
* @param path to the project
|
|
2325
|
+
* @param options Parse options from the cli
|
|
2326
|
+
*/
|
|
2327
|
+
const createDartBom = async (path, options) => {
|
|
2328
|
+
const pubFiles = utils.getAllFiles(
|
|
2329
|
+
path,
|
|
2330
|
+
(options.multiProject ? "**/" : "") + "pubspec.lock"
|
|
2331
|
+
);
|
|
2332
|
+
const pubSpecYamlFiles = utils.getAllFiles(
|
|
2333
|
+
path,
|
|
2334
|
+
(options.multiProject ? "**/" : "") + "pubspec.yaml"
|
|
2335
|
+
);
|
|
2336
|
+
let pkgList = [];
|
|
2337
|
+
if (pubFiles.length) {
|
|
2338
|
+
for (let f of pubFiles) {
|
|
2339
|
+
if (DEBUG_MODE) {
|
|
2340
|
+
console.log(`Parsing ${f}`);
|
|
2341
|
+
}
|
|
2342
|
+
const pubLockData = fs.readFileSync(f, { encoding: "utf-8" });
|
|
2343
|
+
const dlist = await utils.parsePubLockData(pubLockData);
|
|
2344
|
+
if (dlist && dlist.length) {
|
|
2345
|
+
pkgList = pkgList.concat(dlist);
|
|
2346
|
+
}
|
|
2347
|
+
}
|
|
2348
|
+
return buildBomNSData(options, pkgList, "pub", {
|
|
2349
|
+
src: path,
|
|
2350
|
+
filename: pubFiles.join(", ")
|
|
2351
|
+
});
|
|
2352
|
+
} else if (pubSpecYamlFiles.length) {
|
|
2353
|
+
for (let f of pubSpecYamlFiles) {
|
|
2354
|
+
if (DEBUG_MODE) {
|
|
2355
|
+
console.log(`Parsing ${f}`);
|
|
2356
|
+
}
|
|
2357
|
+
const pubYamlData = fs.readFileSync(f, { encoding: "utf-8" });
|
|
2358
|
+
const dlist = await utils.parsePubYamlData(pubYamlData);
|
|
2359
|
+
if (dlist && dlist.length) {
|
|
2360
|
+
pkgList = pkgList.concat(dlist);
|
|
2361
|
+
}
|
|
2362
|
+
}
|
|
2363
|
+
return buildBomNSData(options, pkgList, "pub", {
|
|
2364
|
+
src: path,
|
|
2365
|
+
filename: pubSpecYamlFiles.join(", ")
|
|
2366
|
+
});
|
|
2367
|
+
}
|
|
2368
|
+
|
|
2369
|
+
return {};
|
|
2370
|
+
};
|
|
2371
|
+
|
|
2372
|
+
/**
|
|
2373
|
+
* Function to create bom string for cpp projects
|
|
2374
|
+
*
|
|
2375
|
+
* @param path to the project
|
|
2376
|
+
* @param options Parse options from the cli
|
|
2377
|
+
*/
|
|
2378
|
+
const createCppBom = async (path, options) => {
|
|
2379
|
+
const conanLockFiles = utils.getAllFiles(
|
|
2380
|
+
path,
|
|
2381
|
+
(options.multiProject ? "**/" : "") + "conan.lock"
|
|
2382
|
+
);
|
|
2383
|
+
const conanFiles = utils.getAllFiles(
|
|
2384
|
+
path,
|
|
2385
|
+
(options.multiProject ? "**/" : "") + "conanfile.txt"
|
|
2386
|
+
);
|
|
2387
|
+
let pkgList = [];
|
|
2388
|
+
if (conanLockFiles.length) {
|
|
2389
|
+
for (let f of conanLockFiles) {
|
|
2390
|
+
if (DEBUG_MODE) {
|
|
2391
|
+
console.log(`Parsing ${f}`);
|
|
2392
|
+
}
|
|
2393
|
+
const conanLockData = fs.readFileSync(f, { encoding: "utf-8" });
|
|
2394
|
+
const dlist = await utils.parseConanLockData(conanLockData);
|
|
2395
|
+
if (dlist && dlist.length) {
|
|
2396
|
+
pkgList = pkgList.concat(dlist);
|
|
2397
|
+
}
|
|
2398
|
+
}
|
|
2399
|
+
return buildBomNSData(options, pkgList, "conan", {
|
|
2400
|
+
src: path,
|
|
2401
|
+
filename: conanLockFiles.join(", ")
|
|
2402
|
+
});
|
|
2403
|
+
} else if (conanFiles.length) {
|
|
2404
|
+
for (let f of conanFiles) {
|
|
2405
|
+
if (DEBUG_MODE) {
|
|
2406
|
+
console.log(`Parsing ${f}`);
|
|
2407
|
+
}
|
|
2408
|
+
const conanData = fs.readFileSync(f, { encoding: "utf-8" });
|
|
2409
|
+
const dlist = await utils.parseConanData(conanData);
|
|
2410
|
+
if (dlist && dlist.length) {
|
|
2411
|
+
pkgList = pkgList.concat(dlist);
|
|
2412
|
+
}
|
|
2413
|
+
}
|
|
2414
|
+
return buildBomNSData(options, pkgList, "conan", {
|
|
2415
|
+
src: path,
|
|
2416
|
+
filename: conanFiles.join(", ")
|
|
2417
|
+
});
|
|
2418
|
+
}
|
|
2419
|
+
|
|
2420
|
+
return {};
|
|
2421
|
+
};
|
|
2422
|
+
|
|
2423
|
+
/**
|
|
2424
|
+
* Function to create bom string for clojure projects
|
|
2425
|
+
*
|
|
2426
|
+
* @param path to the project
|
|
2427
|
+
* @param options Parse options from the cli
|
|
2428
|
+
*/
|
|
2429
|
+
const createClojureBom = async (path, options) => {
|
|
2430
|
+
const ednFiles = utils.getAllFiles(
|
|
2431
|
+
path,
|
|
2432
|
+
(options.multiProject ? "**/" : "") + "deps.edn"
|
|
2433
|
+
);
|
|
2434
|
+
const leinFiles = utils.getAllFiles(
|
|
2435
|
+
path,
|
|
2436
|
+
(options.multiProject ? "**/" : "") + "project.clj"
|
|
2437
|
+
);
|
|
2438
|
+
let pkgList = [];
|
|
2439
|
+
if (leinFiles.length) {
|
|
2440
|
+
let LEIN_ARGS = ["deps", ":tree-data"];
|
|
2441
|
+
if (process.env.LEIN_ARGS) {
|
|
2442
|
+
LEIN_ARGS = process.env.LEIN_ARGS.split(" ");
|
|
2443
|
+
}
|
|
2444
|
+
for (let f of leinFiles) {
|
|
2445
|
+
if (DEBUG_MODE) {
|
|
2446
|
+
console.log(`Parsing ${f}`);
|
|
2447
|
+
}
|
|
2448
|
+
const basePath = pathLib.dirname(f);
|
|
2449
|
+
console.log("Executing", LEIN_CMD, LEIN_ARGS.join(" "), "in", basePath);
|
|
2450
|
+
const result = spawnSync(LEIN_CMD, LEIN_ARGS, {
|
|
2451
|
+
cwd: basePath,
|
|
2452
|
+
encoding: "utf-8",
|
|
2453
|
+
timeout: TIMEOUT_MS
|
|
2454
|
+
});
|
|
2455
|
+
if (result.status !== 0 || result.error) {
|
|
2456
|
+
if (result.stderr) {
|
|
2457
|
+
console.error(result.stdout, result.stderr);
|
|
2458
|
+
options.failOnError && process.exit(1);
|
|
2459
|
+
}
|
|
2460
|
+
console.log(
|
|
2461
|
+
"Check if the correct version of lein is installed and available in PATH. Falling back to manual parsing."
|
|
2462
|
+
);
|
|
2463
|
+
if (DEBUG_MODE) {
|
|
2464
|
+
console.log(`Parsing ${f}`);
|
|
2465
|
+
}
|
|
2466
|
+
const leinData = fs.readFileSync(f, { encoding: "utf-8" });
|
|
2467
|
+
const dlist = utils.parseLeiningenData(leinData);
|
|
2468
|
+
if (dlist && dlist.length) {
|
|
2469
|
+
pkgList = pkgList.concat(dlist);
|
|
2470
|
+
}
|
|
2471
|
+
} else {
|
|
2472
|
+
const stdout = result.stdout;
|
|
2473
|
+
if (stdout) {
|
|
2474
|
+
const cmdOutput = Buffer.from(stdout).toString();
|
|
2475
|
+
const dlist = utils.parseLeinDep(cmdOutput);
|
|
2476
|
+
if (dlist && dlist.length) {
|
|
2477
|
+
pkgList = pkgList.concat(dlist);
|
|
2478
|
+
}
|
|
2479
|
+
} else {
|
|
2480
|
+
console.error("lein unexpectedly didn't return any output");
|
|
2481
|
+
options.failOnError && process.exit(1);
|
|
2482
|
+
}
|
|
2483
|
+
}
|
|
2484
|
+
}
|
|
2485
|
+
return buildBomNSData(options, pkgList, "clojars", {
|
|
2486
|
+
src: path,
|
|
2487
|
+
filename: leinFiles.join(", ")
|
|
2488
|
+
});
|
|
2489
|
+
} else if (ednFiles.length) {
|
|
2490
|
+
let CLJ_ARGS = ["-Stree"];
|
|
2491
|
+
if (process.env.CLJ_ARGS) {
|
|
2492
|
+
CLJ_ARGS = process.env.CLJ_ARGS.split(" ");
|
|
2493
|
+
}
|
|
2494
|
+
for (let f of ednFiles) {
|
|
2495
|
+
const basePath = pathLib.dirname(f);
|
|
2496
|
+
console.log("Executing", CLJ_CMD, CLJ_ARGS.join(" "), "in", basePath);
|
|
2497
|
+
const result = spawnSync(CLJ_CMD, CLJ_ARGS, {
|
|
2498
|
+
cwd: basePath,
|
|
2499
|
+
encoding: "utf-8",
|
|
2500
|
+
timeout: TIMEOUT_MS
|
|
2501
|
+
});
|
|
2502
|
+
if (result.status !== 0 || result.error) {
|
|
2503
|
+
if (result.stderr) {
|
|
2504
|
+
console.error(result.stdout, result.stderr);
|
|
2505
|
+
options.failOnError && process.exit(1);
|
|
2506
|
+
}
|
|
2507
|
+
console.log(
|
|
2508
|
+
"Check if the correct version of clojure cli is installed and available in PATH. Falling back to manual parsing."
|
|
2509
|
+
);
|
|
2510
|
+
if (DEBUG_MODE) {
|
|
2511
|
+
console.log(`Parsing ${f}`);
|
|
2512
|
+
}
|
|
2513
|
+
const ednData = fs.readFileSync(f, { encoding: "utf-8" });
|
|
2514
|
+
const dlist = utils.parseEdnData(ednData);
|
|
2515
|
+
if (dlist && dlist.length) {
|
|
2516
|
+
pkgList = pkgList.concat(dlist);
|
|
2517
|
+
}
|
|
2518
|
+
} else {
|
|
2519
|
+
const stdout = result.stdout;
|
|
2520
|
+
if (stdout) {
|
|
2521
|
+
const cmdOutput = Buffer.from(stdout).toString();
|
|
2522
|
+
const dlist = utils.parseCljDep(cmdOutput);
|
|
2523
|
+
if (dlist && dlist.length) {
|
|
2524
|
+
pkgList = pkgList.concat(dlist);
|
|
2525
|
+
}
|
|
2526
|
+
} else {
|
|
2527
|
+
console.error("clj unexpectedly didn't return any output");
|
|
2528
|
+
options.failOnError && process.exit(1);
|
|
2529
|
+
}
|
|
2530
|
+
}
|
|
2531
|
+
}
|
|
2532
|
+
return buildBomNSData(options, pkgList, "clojars", {
|
|
2533
|
+
src: path,
|
|
2534
|
+
filename: ednFiles.join(", ")
|
|
2535
|
+
});
|
|
2536
|
+
}
|
|
2537
|
+
|
|
2538
|
+
return {};
|
|
2539
|
+
};
|
|
2540
|
+
|
|
2541
|
+
/**
|
|
2542
|
+
* Function to create bom string for Haskell projects
|
|
2543
|
+
*
|
|
2544
|
+
* @param path to the project
|
|
2545
|
+
* @param options Parse options from the cli
|
|
2546
|
+
*/
|
|
2547
|
+
const createHaskellBom = async (path, options) => {
|
|
2548
|
+
const cabalFiles = utils.getAllFiles(
|
|
2549
|
+
path,
|
|
2550
|
+
(options.multiProject ? "**/" : "") + "cabal.project.freeze"
|
|
2551
|
+
);
|
|
2552
|
+
let pkgList = [];
|
|
2553
|
+
if (cabalFiles.length) {
|
|
2554
|
+
for (let f of cabalFiles) {
|
|
2555
|
+
if (DEBUG_MODE) {
|
|
2556
|
+
console.log(`Parsing ${f}`);
|
|
2557
|
+
}
|
|
2558
|
+
const cabalData = fs.readFileSync(f, { encoding: "utf-8" });
|
|
2559
|
+
const dlist = await utils.parseCabalData(cabalData);
|
|
2560
|
+
if (dlist && dlist.length) {
|
|
2561
|
+
pkgList = pkgList.concat(dlist);
|
|
2562
|
+
}
|
|
2563
|
+
}
|
|
2564
|
+
return buildBomNSData(options, pkgList, "hackage", {
|
|
2565
|
+
src: path,
|
|
2566
|
+
filename: cabalFiles.join(", ")
|
|
2567
|
+
});
|
|
2568
|
+
}
|
|
2569
|
+
return {};
|
|
2570
|
+
};
|
|
2571
|
+
|
|
2572
|
+
/**
|
|
2573
|
+
* Function to create bom string for Elixir projects
|
|
2574
|
+
*
|
|
2575
|
+
* @param path to the project
|
|
2576
|
+
* @param options Parse options from the cli
|
|
2577
|
+
*/
|
|
2578
|
+
const createElixirBom = async (path, options) => {
|
|
2579
|
+
const mixFiles = utils.getAllFiles(
|
|
2580
|
+
path,
|
|
2581
|
+
(options.multiProject ? "**/" : "") + "mix.lock"
|
|
2582
|
+
);
|
|
2583
|
+
let pkgList = [];
|
|
2584
|
+
if (mixFiles.length) {
|
|
2585
|
+
for (let f of mixFiles) {
|
|
2586
|
+
if (DEBUG_MODE) {
|
|
2587
|
+
console.log(`Parsing ${f}`);
|
|
2588
|
+
}
|
|
2589
|
+
const mixData = fs.readFileSync(f, { encoding: "utf-8" });
|
|
2590
|
+
const dlist = await utils.parseMixLockData(mixData);
|
|
2591
|
+
if (dlist && dlist.length) {
|
|
2592
|
+
pkgList = pkgList.concat(dlist);
|
|
2593
|
+
}
|
|
2594
|
+
}
|
|
2595
|
+
return buildBomNSData(options, pkgList, "hex", {
|
|
2596
|
+
src: path,
|
|
2597
|
+
filename: mixFiles.join(", ")
|
|
2598
|
+
});
|
|
2599
|
+
}
|
|
2600
|
+
return {};
|
|
2601
|
+
};
|
|
2602
|
+
|
|
2603
|
+
/**
|
|
2604
|
+
* Function to create bom string for GitHub action workflows
|
|
2605
|
+
*
|
|
2606
|
+
* @param path to the project
|
|
2607
|
+
* @param options Parse options from the cli
|
|
2608
|
+
*/
|
|
2609
|
+
const createGitHubBom = async (path, options) => {
|
|
2610
|
+
const ghactionFiles = utils.getAllFiles(path, ".github/workflows/" + "*.yml");
|
|
2611
|
+
let pkgList = [];
|
|
2612
|
+
if (ghactionFiles.length) {
|
|
2613
|
+
for (let f of ghactionFiles) {
|
|
2614
|
+
if (DEBUG_MODE) {
|
|
2615
|
+
console.log(`Parsing ${f}`);
|
|
2616
|
+
}
|
|
2617
|
+
const ghwData = fs.readFileSync(f, { encoding: "utf-8" });
|
|
2618
|
+
const dlist = await utils.parseGitHubWorkflowData(ghwData);
|
|
2619
|
+
if (dlist && dlist.length) {
|
|
2620
|
+
pkgList = pkgList.concat(dlist);
|
|
2621
|
+
}
|
|
2622
|
+
}
|
|
2623
|
+
return buildBomNSData(options, pkgList, "github", {
|
|
2624
|
+
src: path,
|
|
2625
|
+
filename: ghactionFiles.join(", ")
|
|
2626
|
+
});
|
|
2627
|
+
}
|
|
2628
|
+
return {};
|
|
2629
|
+
};
|
|
2630
|
+
|
|
2631
|
+
/**
|
|
2632
|
+
* Function to create bom string for cloudbuild yaml
|
|
2633
|
+
*
|
|
2634
|
+
* @param path to the project
|
|
2635
|
+
* @param options Parse options from the cli
|
|
2636
|
+
*/
|
|
2637
|
+
const createCloudBuildBom = async (path, options) => {
|
|
2638
|
+
const cbFiles = utils.getAllFiles(path, "cloudbuild.yml");
|
|
2639
|
+
let pkgList = [];
|
|
2640
|
+
if (cbFiles.length) {
|
|
2641
|
+
for (let f of cbFiles) {
|
|
2642
|
+
if (DEBUG_MODE) {
|
|
2643
|
+
console.log(`Parsing ${f}`);
|
|
2644
|
+
}
|
|
2645
|
+
const cbwData = fs.readFileSync(f, { encoding: "utf-8" });
|
|
2646
|
+
const dlist = await utils.parseCloudBuildData(cbwData);
|
|
2647
|
+
if (dlist && dlist.length) {
|
|
2648
|
+
pkgList = pkgList.concat(dlist);
|
|
2649
|
+
}
|
|
2650
|
+
}
|
|
2651
|
+
return buildBomNSData(options, pkgList, "cloudbuild", {
|
|
2652
|
+
src: path,
|
|
2653
|
+
filename: cbFiles.join(", ")
|
|
2654
|
+
});
|
|
2655
|
+
}
|
|
2656
|
+
return {};
|
|
2657
|
+
};
|
|
2658
|
+
|
|
2659
|
+
/**
|
|
2660
|
+
* Function to create bom string for the current OS using osquery
|
|
2661
|
+
*
|
|
2662
|
+
* @param path to the project
|
|
2663
|
+
* @param options Parse options from the cli
|
|
2664
|
+
*/
|
|
2665
|
+
const createOSBom = async (path, options) => {
|
|
2666
|
+
console.warn(
|
|
2667
|
+
"About to generate SBoM for the current OS installation. This would take several minutes ..."
|
|
2668
|
+
);
|
|
2669
|
+
let pkgList = [];
|
|
2670
|
+
let bomData = {};
|
|
2671
|
+
for (const queryCategory of Object.keys(osQueries)) {
|
|
2672
|
+
const queryObj = osQueries[queryCategory];
|
|
2673
|
+
const results = binaryLib.executeOsQuery(queryObj.query);
|
|
2674
|
+
const dlist = utils.convertOSQueryResults(queryCategory, queryObj, results);
|
|
2675
|
+
if (dlist && dlist.length) {
|
|
2676
|
+
pkgList = pkgList.concat(dlist);
|
|
2677
|
+
}
|
|
2678
|
+
} // for
|
|
2679
|
+
if (pkgList.length) {
|
|
2680
|
+
bomData = buildBomNSData(options, pkgList, "", {
|
|
2681
|
+
src: "",
|
|
2682
|
+
filename: ""
|
|
2683
|
+
});
|
|
2684
|
+
}
|
|
2685
|
+
options.bomData = bomData;
|
|
2686
|
+
options.multiProject = true;
|
|
2687
|
+
options.installDeps = false;
|
|
2688
|
+
// Force the project type to os
|
|
2689
|
+
options.projectType = "os";
|
|
2690
|
+
options.lastWorkingDir = undefined;
|
|
2691
|
+
options.allLayersExplodedDir = isWin ? "C:\\" : "";
|
|
2692
|
+
const exportData = {
|
|
2693
|
+
lastWorkingDir: undefined,
|
|
2694
|
+
allLayersDir: options.allLayersExplodedDir,
|
|
2695
|
+
allLayersExplodedDir: options.allLayersExplodedDir
|
|
2696
|
+
};
|
|
2697
|
+
let pkgPathList = [];
|
|
2698
|
+
if (options.deep) {
|
|
2699
|
+
dockerLib.getPkgPathList(exportData, undefined);
|
|
2700
|
+
}
|
|
2701
|
+
return createMultiXBom(pkgPathList, options);
|
|
2702
|
+
};
|
|
2703
|
+
|
|
2704
|
+
/**
|
|
2705
|
+
* Function to create bom string for Jenkins plugins
|
|
2706
|
+
*
|
|
2707
|
+
* @param path to the project
|
|
2708
|
+
* @param options Parse options from the cli
|
|
2709
|
+
*/
|
|
2710
|
+
const createJenkinsBom = async (path, options) => {
|
|
2711
|
+
let pkgList = [];
|
|
2712
|
+
const hpiFiles = utils.getAllFiles(
|
|
2713
|
+
path,
|
|
2714
|
+
(options.multiProject ? "**/" : "") + "*.hpi"
|
|
2715
|
+
);
|
|
2716
|
+
let tempDir = fs.mkdtempSync(pathLib.join(os.tmpdir(), "hpi-deps-"));
|
|
2717
|
+
if (hpiFiles.length) {
|
|
2718
|
+
for (let f of hpiFiles) {
|
|
2719
|
+
if (DEBUG_MODE) {
|
|
2720
|
+
console.log(`Parsing ${f}`);
|
|
2721
|
+
}
|
|
2722
|
+
const dlist = utils.extractJarArchive(f, tempDir);
|
|
2723
|
+
if (dlist && dlist.length) {
|
|
2724
|
+
pkgList = pkgList.concat(dlist);
|
|
2725
|
+
}
|
|
2726
|
+
}
|
|
2727
|
+
}
|
|
2728
|
+
const jsFiles = utils.getAllFiles(tempDir, "**/*.js");
|
|
2729
|
+
if (jsFiles.length) {
|
|
2730
|
+
for (let f of jsFiles) {
|
|
2731
|
+
if (DEBUG_MODE) {
|
|
2732
|
+
console.log(`Parsing ${f}`);
|
|
2733
|
+
}
|
|
2734
|
+
const dlist = await utils.parseMinJs(f);
|
|
2735
|
+
if (dlist && dlist.length) {
|
|
2736
|
+
pkgList = pkgList.concat(dlist);
|
|
2737
|
+
}
|
|
2738
|
+
}
|
|
2739
|
+
}
|
|
2740
|
+
// Clean up
|
|
2741
|
+
if (tempDir && tempDir.startsWith(os.tmpdir()) && fs.rmSync) {
|
|
2742
|
+
console.log(`Cleaning up ${tempDir}`);
|
|
2743
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
2744
|
+
}
|
|
2745
|
+
return buildBomNSData(options, pkgList, "maven", {
|
|
2746
|
+
src: path,
|
|
2747
|
+
filename: hpiFiles.join(", "),
|
|
2748
|
+
nsMapping: {}
|
|
2749
|
+
});
|
|
2750
|
+
};
|
|
2751
|
+
|
|
2752
|
+
/**
|
|
2753
|
+
* Function to create bom string for Helm charts
|
|
2754
|
+
*
|
|
2755
|
+
* @param path to the project
|
|
2756
|
+
* @param options Parse options from the cli
|
|
2757
|
+
*/
|
|
2758
|
+
const createHelmBom = async (path, options) => {
|
|
2759
|
+
let pkgList = [];
|
|
2760
|
+
const yamlFiles = utils.getAllFiles(
|
|
2761
|
+
path,
|
|
2762
|
+
(options.multiProject ? "**/" : "") + "*.yaml"
|
|
2763
|
+
);
|
|
2764
|
+
if (yamlFiles.length) {
|
|
2765
|
+
for (let f of yamlFiles) {
|
|
2766
|
+
if (DEBUG_MODE) {
|
|
2767
|
+
console.log(`Parsing ${f}`);
|
|
2768
|
+
}
|
|
2769
|
+
const helmData = fs.readFileSync(f, { encoding: "utf-8" });
|
|
2770
|
+
const dlist = await utils.parseHelmYamlData(helmData);
|
|
2771
|
+
if (dlist && dlist.length) {
|
|
2772
|
+
pkgList = pkgList.concat(dlist);
|
|
2773
|
+
}
|
|
2774
|
+
}
|
|
2775
|
+
return buildBomNSData(options, pkgList, "helm", {
|
|
2776
|
+
src: path,
|
|
2777
|
+
filename: yamlFiles.join(", ")
|
|
2778
|
+
});
|
|
2779
|
+
}
|
|
2780
|
+
return {};
|
|
2781
|
+
};
|
|
2782
|
+
|
|
2783
|
+
/**
|
|
2784
|
+
* Function to create bom string for docker compose
|
|
2785
|
+
*
|
|
2786
|
+
* @param path to the project
|
|
2787
|
+
* @param options Parse options from the cli
|
|
2788
|
+
*/
|
|
2789
|
+
const createContainerSpecLikeBom = async (path, options) => {
|
|
2790
|
+
let services = [];
|
|
2791
|
+
let ociSpecs = [];
|
|
2792
|
+
let components = [];
|
|
2793
|
+
let componentsXmls = [];
|
|
2794
|
+
let parentComponent = {};
|
|
2795
|
+
let dependencies = [];
|
|
2796
|
+
let doneimages = [];
|
|
2797
|
+
let doneservices = [];
|
|
2798
|
+
let origProjectType = options.projectType;
|
|
2799
|
+
let dcFiles = utils.getAllFiles(
|
|
2800
|
+
path,
|
|
2801
|
+
(options.multiProject ? "**/" : "") + "*.yml"
|
|
2802
|
+
);
|
|
2803
|
+
const yamlFiles = utils.getAllFiles(
|
|
2804
|
+
path,
|
|
2805
|
+
(options.multiProject ? "**/" : "") + "*.yaml"
|
|
2806
|
+
);
|
|
2807
|
+
let oapiFiles = utils.getAllFiles(
|
|
2808
|
+
path,
|
|
2809
|
+
(options.multiProject ? "**/" : "") + "open*.json"
|
|
2810
|
+
);
|
|
2811
|
+
let oapiYamlFiles = utils.getAllFiles(
|
|
2812
|
+
path,
|
|
2813
|
+
(options.multiProject ? "**/" : "") + "open*.yaml"
|
|
2814
|
+
);
|
|
2815
|
+
if (oapiYamlFiles && oapiYamlFiles.length) {
|
|
2816
|
+
oapiFiles = oapiFiles.concat(oapiYamlFiles);
|
|
2817
|
+
}
|
|
2818
|
+
if (yamlFiles.length) {
|
|
2819
|
+
dcFiles = dcFiles.concat(yamlFiles);
|
|
2820
|
+
}
|
|
2821
|
+
// Privado.ai json files
|
|
2822
|
+
const privadoFiles = utils.getAllFiles(path, ".privado/" + "*.json");
|
|
2823
|
+
// parse yaml manifest files
|
|
2824
|
+
if (dcFiles.length) {
|
|
2825
|
+
for (let f of dcFiles) {
|
|
2826
|
+
if (DEBUG_MODE) {
|
|
2827
|
+
console.log(`Parsing ${f}`);
|
|
2828
|
+
}
|
|
2829
|
+
const dcData = fs.readFileSync(f, { encoding: "utf-8" });
|
|
2830
|
+
const imglist = await utils.parseContainerSpecData(dcData);
|
|
2831
|
+
if (imglist && imglist.length) {
|
|
2832
|
+
if (DEBUG_MODE) {
|
|
2833
|
+
console.log("Images identified in", f, "are", imglist);
|
|
2834
|
+
}
|
|
2835
|
+
for (const img of imglist) {
|
|
2836
|
+
let commonProperties = [
|
|
2837
|
+
{
|
|
2838
|
+
name: "SrcFile",
|
|
2839
|
+
value: f
|
|
2840
|
+
}
|
|
2841
|
+
];
|
|
2842
|
+
if (img.image) {
|
|
2843
|
+
commonProperties.push({
|
|
2844
|
+
name: "oci:SrcImage",
|
|
2845
|
+
value: img.image
|
|
2846
|
+
});
|
|
2847
|
+
}
|
|
2848
|
+
if (img.service) {
|
|
2849
|
+
commonProperties.push({
|
|
2850
|
+
name: "ServiceName",
|
|
2851
|
+
value: img.service
|
|
2852
|
+
});
|
|
2853
|
+
}
|
|
2854
|
+
|
|
2855
|
+
// img could have .service, .ociSpec or .image
|
|
2856
|
+
if (img.ociSpec) {
|
|
2857
|
+
console.log(
|
|
2858
|
+
`NOTE: ${img.ociSpec} needs to built using docker or podman and referred with a name to get included in this SBoM.`
|
|
2859
|
+
);
|
|
2860
|
+
ociSpecs.push({
|
|
2861
|
+
group: "",
|
|
2862
|
+
name: img.ociSpec,
|
|
2863
|
+
version: "latest",
|
|
2864
|
+
properties: commonProperties
|
|
2865
|
+
});
|
|
2866
|
+
}
|
|
2867
|
+
if (img.service) {
|
|
2868
|
+
let version = "latest";
|
|
2869
|
+
let name = img.service;
|
|
2870
|
+
if (img.service.includes(":")) {
|
|
2871
|
+
const tmpA = img.service.split(":");
|
|
2872
|
+
if (tmpA && tmpA.length === 2) {
|
|
2873
|
+
name = tmpA[0];
|
|
2874
|
+
version = tmpA[1];
|
|
2875
|
+
}
|
|
2876
|
+
}
|
|
2877
|
+
const servbomRef = `urn:service:${name}:${version}`;
|
|
2878
|
+
if (!doneservices.includes(servbomRef)) {
|
|
2879
|
+
services.push({
|
|
2880
|
+
"bom-ref": servbomRef,
|
|
2881
|
+
name: name,
|
|
2882
|
+
version: version,
|
|
2883
|
+
group: "",
|
|
2884
|
+
properties: commonProperties
|
|
2885
|
+
});
|
|
2886
|
+
doneservices.push(servbomRef);
|
|
2887
|
+
}
|
|
2888
|
+
}
|
|
2889
|
+
if (img.image) {
|
|
2890
|
+
if (doneimages.includes(img.image)) {
|
|
2891
|
+
if (DEBUG_MODE) {
|
|
2892
|
+
console.log("Skipping", img.image);
|
|
2893
|
+
}
|
|
2894
|
+
continue;
|
|
2895
|
+
}
|
|
2896
|
+
if (DEBUG_MODE) {
|
|
2897
|
+
console.log(`Parsing image ${img.image}`);
|
|
2898
|
+
}
|
|
2899
|
+
const imageObj = dockerLib.parseImageName(img.image);
|
|
2900
|
+
const pkg = {
|
|
2901
|
+
name: imageObj.repo,
|
|
2902
|
+
version:
|
|
2903
|
+
imageObj.tag ||
|
|
2904
|
+
(imageObj.digest ? "sha256:" + imageObj.digest : "latest"),
|
|
2905
|
+
qualifiers: {},
|
|
2906
|
+
properties: commonProperties
|
|
2907
|
+
};
|
|
2908
|
+
if (imageObj.registry) {
|
|
2909
|
+
pkg["qualifiers"]["repository_url"] = imageObj.registry;
|
|
2910
|
+
}
|
|
2911
|
+
if (imageObj.platform) {
|
|
2912
|
+
pkg["qualifiers"]["platform"] = imageObj.platform;
|
|
2913
|
+
}
|
|
2914
|
+
// Create an entry for the oci image
|
|
2915
|
+
const imageBomData = buildBomNSData(options, [pkg], "oci", {
|
|
2916
|
+
src: img.image,
|
|
2917
|
+
filename: f,
|
|
2918
|
+
nsMapping: {}
|
|
2919
|
+
});
|
|
2920
|
+
if (
|
|
2921
|
+
imageBomData &&
|
|
2922
|
+
imageBomData.bomJson &&
|
|
2923
|
+
imageBomData.bomJson.components
|
|
2924
|
+
) {
|
|
2925
|
+
components = components.concat(imageBomData.bomJson.components);
|
|
2926
|
+
componentsXmls = componentsXmls.concat(
|
|
2927
|
+
listComponents(
|
|
2928
|
+
options,
|
|
2929
|
+
{},
|
|
2930
|
+
imageBomData.bomJson.components,
|
|
2931
|
+
"oci",
|
|
2932
|
+
"xml"
|
|
2933
|
+
)
|
|
2934
|
+
);
|
|
2935
|
+
}
|
|
2936
|
+
const bomData = await createBom(img.image, { projectType: "oci" });
|
|
2937
|
+
doneimages.push(img.image);
|
|
2938
|
+
if (bomData) {
|
|
2939
|
+
if (bomData.components && bomData.components.length) {
|
|
2940
|
+
// Inject properties
|
|
2941
|
+
for (const co of bomData.components) {
|
|
2942
|
+
co.properties = commonProperties;
|
|
2943
|
+
}
|
|
2944
|
+
components = components.concat(bomData.components);
|
|
2945
|
+
}
|
|
2946
|
+
if (bomData.componentsXmls && bomData.componentsXmls.length) {
|
|
2947
|
+
componentsXmls = componentsXmls.concat(bomData.componentsXmls);
|
|
2948
|
+
}
|
|
2949
|
+
}
|
|
2950
|
+
} // img.image
|
|
2951
|
+
} // for img
|
|
2952
|
+
}
|
|
2953
|
+
} // for
|
|
2954
|
+
} // if
|
|
2955
|
+
// Parse openapi files
|
|
2956
|
+
if (oapiFiles.length) {
|
|
2957
|
+
for (let af of oapiFiles) {
|
|
2958
|
+
if (DEBUG_MODE) {
|
|
2959
|
+
console.log(`Parsing ${af}`);
|
|
2960
|
+
}
|
|
2961
|
+
const oaData = fs.readFileSync(af, { encoding: "utf-8" });
|
|
2962
|
+
const servlist = await utils.parseOpenapiSpecData(oaData);
|
|
2963
|
+
if (servlist && servlist.length) {
|
|
2964
|
+
// Inject SrcFile property
|
|
2965
|
+
for (const se of servlist) {
|
|
2966
|
+
se.properties = [
|
|
2967
|
+
{
|
|
2968
|
+
name: "SrcFile",
|
|
2969
|
+
value: af
|
|
2970
|
+
}
|
|
2971
|
+
];
|
|
2972
|
+
}
|
|
2973
|
+
services = services.concat(servlist);
|
|
2974
|
+
}
|
|
2975
|
+
}
|
|
2976
|
+
}
|
|
2977
|
+
// Parse privado files
|
|
2978
|
+
if (privadoFiles.length) {
|
|
2979
|
+
console.log(
|
|
2980
|
+
"Enriching your SBoM with information from privado.ai scan reports"
|
|
2981
|
+
);
|
|
2982
|
+
let rows = [["Classification", "Flow"]];
|
|
2983
|
+
let config = {
|
|
2984
|
+
header: {
|
|
2985
|
+
alignment: "center",
|
|
2986
|
+
content: "Data Privacy Insights from privado.ai"
|
|
2987
|
+
},
|
|
2988
|
+
columns: [{ width: 50 }, { width: 10 }]
|
|
2989
|
+
};
|
|
2990
|
+
for (let f of privadoFiles) {
|
|
2991
|
+
if (DEBUG_MODE) {
|
|
2992
|
+
console.log(`Parsing ${f}`);
|
|
2993
|
+
}
|
|
2994
|
+
const servlist = utils.parsePrivadoFile(f);
|
|
2995
|
+
services = services.concat(servlist);
|
|
2996
|
+
if (servlist.length) {
|
|
2997
|
+
const aservice = servlist[0];
|
|
2998
|
+
if (aservice.data) {
|
|
2999
|
+
for (let d of aservice.data) {
|
|
3000
|
+
rows.push([d.classification, d.flow]);
|
|
3001
|
+
}
|
|
3002
|
+
console.log(table(rows, config));
|
|
3003
|
+
}
|
|
3004
|
+
if (aservice.endpoints) {
|
|
3005
|
+
rows = [["Leaky Endpoints"]];
|
|
3006
|
+
for (let e of aservice.endpoints) {
|
|
3007
|
+
rows.push([e]);
|
|
3008
|
+
}
|
|
3009
|
+
console.log(
|
|
3010
|
+
table(rows, {
|
|
3011
|
+
columnDefault: {
|
|
3012
|
+
width: 50
|
|
3013
|
+
}
|
|
3014
|
+
})
|
|
3015
|
+
);
|
|
3016
|
+
}
|
|
3017
|
+
}
|
|
3018
|
+
}
|
|
3019
|
+
}
|
|
3020
|
+
if (origProjectType === "universal") {
|
|
3021
|
+
// In case of universal, repeat to collect multiX Boms
|
|
3022
|
+
const mbomData = await createMultiXBom([path], {
|
|
3023
|
+
projectType: origProjectType,
|
|
3024
|
+
multiProject: true
|
|
3025
|
+
});
|
|
3026
|
+
if (mbomData) {
|
|
3027
|
+
if (mbomData.components && mbomData.components.length) {
|
|
3028
|
+
components = components.concat(mbomData.components);
|
|
3029
|
+
}
|
|
3030
|
+
if (mbomData.componentsXmls && mbomData.componentsXmls.length) {
|
|
3031
|
+
componentsXmls = componentsXmls.concat(mbomData.componentsXmls);
|
|
3032
|
+
}
|
|
3033
|
+
if (mbomData.bomJson) {
|
|
3034
|
+
if (mbomData.bomJson.dependencies) {
|
|
3035
|
+
dependencies = mergeDependencies(
|
|
3036
|
+
dependencies,
|
|
3037
|
+
mbomData.bomJson.dependencies
|
|
3038
|
+
);
|
|
3039
|
+
}
|
|
3040
|
+
if (mbomData.bomJson.services) {
|
|
3041
|
+
services = services.concat(mbomData.bomJson.services);
|
|
3042
|
+
}
|
|
3043
|
+
}
|
|
3044
|
+
if (DEBUG_MODE) {
|
|
3045
|
+
console.log(
|
|
3046
|
+
`BOM includes ${components.length} unfiltered components ${dependencies.length} dependencies so far`
|
|
3047
|
+
);
|
|
3048
|
+
}
|
|
3049
|
+
}
|
|
3050
|
+
}
|
|
3051
|
+
options.services = services;
|
|
3052
|
+
options.ociSpecs = ociSpecs;
|
|
3053
|
+
return dedupeBom(
|
|
3054
|
+
options,
|
|
3055
|
+
components,
|
|
3056
|
+
componentsXmls,
|
|
3057
|
+
parentComponent,
|
|
3058
|
+
dependencies
|
|
3059
|
+
);
|
|
3060
|
+
};
|
|
3061
|
+
|
|
3062
|
+
/**
|
|
3063
|
+
* Function to create bom string for php projects
|
|
3064
|
+
*
|
|
3065
|
+
* @param path to the project
|
|
3066
|
+
* @param options Parse options from the cli
|
|
3067
|
+
*/
|
|
3068
|
+
const createPHPBom = async (path, options) => {
|
|
3069
|
+
const composerJsonFiles = utils.getAllFiles(
|
|
3070
|
+
path,
|
|
3071
|
+
(options.multiProject ? "**/" : "") + "composer.json"
|
|
3072
|
+
);
|
|
3073
|
+
let composerLockFiles = utils.getAllFiles(
|
|
3074
|
+
path,
|
|
3075
|
+
(options.multiProject ? "**/" : "") + "composer.lock"
|
|
3076
|
+
);
|
|
3077
|
+
let pkgList = [];
|
|
3078
|
+
const composerJsonMode = composerJsonFiles.length;
|
|
3079
|
+
const composerLockMode = composerLockFiles.length;
|
|
3080
|
+
// Create a composer.lock file for each composer.json file if needed.
|
|
3081
|
+
if (!composerLockMode && composerJsonMode && options.installDeps) {
|
|
3082
|
+
const versionResult = spawnSync("composer", ["--version"], {
|
|
3083
|
+
encoding: "utf-8"
|
|
3084
|
+
});
|
|
3085
|
+
if (versionResult.status !== 0 || versionResult.error) {
|
|
3086
|
+
console.error(
|
|
3087
|
+
"No composer version found. Check if composer is installed and available in PATH."
|
|
3088
|
+
);
|
|
3089
|
+
console.log(versionResult.error, versionResult.stderr);
|
|
3090
|
+
options.failOnError && process.exit(1);
|
|
3091
|
+
return {};
|
|
3092
|
+
}
|
|
3093
|
+
const composerVersion = versionResult.stdout.match(/version (\d)/)[1];
|
|
3094
|
+
if (DEBUG_MODE) {
|
|
3095
|
+
console.log("Detected composer version:", composerVersion);
|
|
3096
|
+
}
|
|
3097
|
+
for (let f of composerJsonFiles) {
|
|
3098
|
+
const basePath = pathLib.dirname(f);
|
|
3099
|
+
let args = [];
|
|
3100
|
+
if (composerVersion > 1) {
|
|
3101
|
+
console.log("Generating composer.lock in", basePath);
|
|
3102
|
+
args = ["update", "--no-install", "--ignore-platform-reqs"];
|
|
3103
|
+
} else {
|
|
3104
|
+
console.log("Executing 'composer install' in", basePath);
|
|
3105
|
+
args = ["install", "--ignore-platform-reqs"];
|
|
3106
|
+
}
|
|
3107
|
+
const result = spawnSync("composer", args, {
|
|
3108
|
+
cwd: basePath,
|
|
3109
|
+
encoding: "utf-8"
|
|
3110
|
+
});
|
|
3111
|
+
if (result.status !== 0 || result.error) {
|
|
3112
|
+
console.error("Error running composer:");
|
|
3113
|
+
console.log(result.error, result.stderr);
|
|
3114
|
+
options.failOnError && process.exit(1);
|
|
3115
|
+
}
|
|
3116
|
+
}
|
|
3117
|
+
}
|
|
3118
|
+
composerLockFiles = utils.getAllFiles(
|
|
3119
|
+
path,
|
|
3120
|
+
(options.multiProject ? "**/" : "") + "composer.lock"
|
|
3121
|
+
);
|
|
3122
|
+
if (composerLockFiles.length) {
|
|
3123
|
+
for (let f of composerLockFiles) {
|
|
3124
|
+
if (DEBUG_MODE) {
|
|
3125
|
+
console.log(`Parsing ${f}`);
|
|
3126
|
+
}
|
|
3127
|
+
let dlist = utils.parseComposerLock(f);
|
|
3128
|
+
if (dlist && dlist.length) {
|
|
3129
|
+
pkgList = pkgList.concat(dlist);
|
|
3130
|
+
}
|
|
3131
|
+
}
|
|
3132
|
+
return buildBomNSData(options, pkgList, "composer", {
|
|
3133
|
+
src: path,
|
|
3134
|
+
filename: composerLockFiles.join(", ")
|
|
3135
|
+
});
|
|
3136
|
+
}
|
|
3137
|
+
return {};
|
|
3138
|
+
};
|
|
3139
|
+
|
|
3140
|
+
/**
|
|
3141
|
+
* Function to create bom string for ruby projects
|
|
3142
|
+
*
|
|
3143
|
+
* @param path to the project
|
|
3144
|
+
* @param options Parse options from the cli
|
|
3145
|
+
*/
|
|
3146
|
+
const createRubyBom = async (path, options) => {
|
|
3147
|
+
const gemFiles = utils.getAllFiles(
|
|
3148
|
+
path,
|
|
3149
|
+
(options.multiProject ? "**/" : "") + "Gemfile"
|
|
3150
|
+
);
|
|
3151
|
+
let gemLockFiles = utils.getAllFiles(
|
|
3152
|
+
path,
|
|
3153
|
+
(options.multiProject ? "**/" : "") + "Gemfile.lock"
|
|
3154
|
+
);
|
|
3155
|
+
let pkgList = [];
|
|
3156
|
+
const gemFileMode = gemFiles.length;
|
|
3157
|
+
let gemLockMode = gemLockFiles.length;
|
|
3158
|
+
if (gemFileMode && !gemLockMode && options.installDeps) {
|
|
3159
|
+
for (let f of gemFiles) {
|
|
3160
|
+
const basePath = pathLib.dirname(f);
|
|
3161
|
+
console.log("Executing 'bundle install' in", basePath);
|
|
3162
|
+
const result = spawnSync("bundle", ["install"], {
|
|
3163
|
+
cwd: basePath,
|
|
3164
|
+
encoding: "utf-8"
|
|
3165
|
+
});
|
|
3166
|
+
if (result.status !== 0 || result.error) {
|
|
3167
|
+
console.error(
|
|
3168
|
+
"Bundle install has failed. Check if bundle is installed and available in PATH."
|
|
3169
|
+
);
|
|
3170
|
+
console.log(result.error, result.stderr);
|
|
3171
|
+
options.failOnError && process.exit(1);
|
|
3172
|
+
}
|
|
3173
|
+
}
|
|
3174
|
+
}
|
|
3175
|
+
gemLockFiles = utils.getAllFiles(
|
|
3176
|
+
path,
|
|
3177
|
+
(options.multiProject ? "**/" : "") + "Gemfile.lock"
|
|
3178
|
+
);
|
|
3179
|
+
if (gemLockFiles.length) {
|
|
3180
|
+
for (let f of gemLockFiles) {
|
|
3181
|
+
if (DEBUG_MODE) {
|
|
3182
|
+
console.log(`Parsing ${f}`);
|
|
3183
|
+
}
|
|
3184
|
+
let gemLockData = fs.readFileSync(f, { encoding: "utf-8" });
|
|
3185
|
+
const dlist = await utils.parseGemfileLockData(gemLockData);
|
|
3186
|
+
if (dlist && dlist.length) {
|
|
3187
|
+
pkgList = pkgList.concat(dlist);
|
|
3188
|
+
}
|
|
3189
|
+
}
|
|
3190
|
+
return buildBomNSData(options, pkgList, "gem", {
|
|
3191
|
+
src: path,
|
|
3192
|
+
filename: gemLockFiles.join(", ")
|
|
3193
|
+
});
|
|
3194
|
+
}
|
|
3195
|
+
return {};
|
|
3196
|
+
};
|
|
3197
|
+
|
|
3198
|
+
/**
|
|
3199
|
+
* Function to create bom string for csharp projects
|
|
3200
|
+
*
|
|
3201
|
+
* @param path to the project
|
|
3202
|
+
* @param options Parse options from the cli
|
|
3203
|
+
*/
|
|
3204
|
+
const createCsharpBom = async (path, options) => {
|
|
3205
|
+
let manifestFiles = [];
|
|
3206
|
+
const csProjFiles = utils.getAllFiles(
|
|
3207
|
+
path,
|
|
3208
|
+
(options.multiProject ? "**/" : "") + "*.csproj"
|
|
3209
|
+
);
|
|
3210
|
+
const pkgConfigFiles = utils.getAllFiles(
|
|
3211
|
+
path,
|
|
3212
|
+
(options.multiProject ? "**/" : "") + "packages.config"
|
|
3213
|
+
);
|
|
3214
|
+
const projAssetsFiles = utils.getAllFiles(
|
|
3215
|
+
path,
|
|
3216
|
+
(options.multiProject ? "**/" : "") + "project.assets.json"
|
|
3217
|
+
);
|
|
3218
|
+
const pkgLockFiles = utils.getAllFiles(
|
|
3219
|
+
path,
|
|
3220
|
+
(options.multiProject ? "**/" : "") + "packages.lock.json"
|
|
3221
|
+
);
|
|
3222
|
+
const nupkgFiles = utils.getAllFiles(
|
|
3223
|
+
path,
|
|
3224
|
+
(options.multiProject ? "**/" : "") + "*.nupkg"
|
|
3225
|
+
);
|
|
3226
|
+
let pkgList = [];
|
|
3227
|
+
if (nupkgFiles.length) {
|
|
3228
|
+
manifestFiles = manifestFiles.concat(nupkgFiles);
|
|
3229
|
+
for (let nf of nupkgFiles) {
|
|
3230
|
+
if (DEBUG_MODE) {
|
|
3231
|
+
console.log(`Parsing ${nf}`);
|
|
3232
|
+
}
|
|
3233
|
+
const dlist = await utils.parseNupkg(nf);
|
|
3234
|
+
if (dlist && dlist.length) {
|
|
3235
|
+
pkgList = pkgList.concat(dlist);
|
|
3236
|
+
}
|
|
3237
|
+
}
|
|
3238
|
+
}
|
|
3239
|
+
// project.assets.json parsing
|
|
3240
|
+
if (projAssetsFiles.length) {
|
|
3241
|
+
manifestFiles = manifestFiles.concat(projAssetsFiles);
|
|
3242
|
+
for (let af of projAssetsFiles) {
|
|
3243
|
+
if (DEBUG_MODE) {
|
|
3244
|
+
console.log(`Parsing ${af}`);
|
|
3245
|
+
}
|
|
3246
|
+
let pkgData = fs.readFileSync(af, { encoding: "utf-8" });
|
|
3247
|
+
const dlist = await utils.parseCsProjAssetsData(pkgData);
|
|
3248
|
+
if (dlist && dlist.length) {
|
|
3249
|
+
pkgList = pkgList.concat(dlist);
|
|
3250
|
+
}
|
|
3251
|
+
}
|
|
3252
|
+
} else if (pkgLockFiles.length) {
|
|
3253
|
+
manifestFiles = manifestFiles.concat(pkgLockFiles);
|
|
3254
|
+
// packages.lock.json from nuget
|
|
3255
|
+
for (let af of pkgLockFiles) {
|
|
3256
|
+
if (DEBUG_MODE) {
|
|
3257
|
+
console.log(`Parsing ${af}`);
|
|
3258
|
+
}
|
|
3259
|
+
let pkgData = fs.readFileSync(af, { encoding: "utf-8" });
|
|
3260
|
+
const dlist = await utils.parseCsPkgLockData(pkgData);
|
|
3261
|
+
if (dlist && dlist.length) {
|
|
3262
|
+
pkgList = pkgList.concat(dlist);
|
|
3263
|
+
}
|
|
3264
|
+
}
|
|
3265
|
+
} else if (pkgConfigFiles.length) {
|
|
3266
|
+
manifestFiles = manifestFiles.concat(pkgConfigFiles);
|
|
3267
|
+
// packages.config parsing
|
|
3268
|
+
for (let f of pkgConfigFiles) {
|
|
3269
|
+
if (DEBUG_MODE) {
|
|
3270
|
+
console.log(`Parsing ${f}`);
|
|
3271
|
+
}
|
|
3272
|
+
let pkgData = fs.readFileSync(f, { encoding: "utf-8" });
|
|
3273
|
+
// Remove byte order mark
|
|
3274
|
+
if (pkgData.charCodeAt(0) === 0xfeff) {
|
|
3275
|
+
pkgData = pkgData.slice(1);
|
|
3276
|
+
}
|
|
3277
|
+
const dlist = await utils.parseCsPkgData(pkgData);
|
|
3278
|
+
if (dlist && dlist.length) {
|
|
3279
|
+
pkgList = pkgList.concat(dlist);
|
|
3280
|
+
}
|
|
3281
|
+
}
|
|
3282
|
+
} else if (csProjFiles.length) {
|
|
3283
|
+
manifestFiles = manifestFiles.concat(csProjFiles);
|
|
3284
|
+
// .csproj parsing
|
|
3285
|
+
for (let f of csProjFiles) {
|
|
3286
|
+
if (DEBUG_MODE) {
|
|
3287
|
+
console.log(`Parsing ${f}`);
|
|
3288
|
+
}
|
|
3289
|
+
let csProjData = fs.readFileSync(f, { encoding: "utf-8" });
|
|
3290
|
+
// Remove byte order mark
|
|
3291
|
+
if (csProjData.charCodeAt(0) === 0xfeff) {
|
|
3292
|
+
csProjData = csProjData.slice(1);
|
|
3293
|
+
}
|
|
3294
|
+
const dlist = await utils.parseCsProjData(csProjData);
|
|
3295
|
+
if (dlist && dlist.length) {
|
|
3296
|
+
pkgList = pkgList.concat(dlist);
|
|
3297
|
+
}
|
|
3298
|
+
}
|
|
3299
|
+
}
|
|
3300
|
+
if (pkgList.length) {
|
|
3301
|
+
return buildBomNSData(options, pkgList, "nuget", {
|
|
3302
|
+
src: path,
|
|
3303
|
+
filename: manifestFiles.join(", ")
|
|
3304
|
+
});
|
|
3305
|
+
}
|
|
3306
|
+
return {};
|
|
3307
|
+
};
|
|
3308
|
+
|
|
3309
|
+
const mergeDependencies = (dependencies, newDependencies) => {
|
|
3310
|
+
const deps_map = {};
|
|
3311
|
+
let combinedDeps = dependencies.concat(newDependencies || []);
|
|
3312
|
+
for (const adep of combinedDeps) {
|
|
3313
|
+
if (!deps_map[adep.ref]) {
|
|
3314
|
+
deps_map[adep.ref] = new Set();
|
|
3315
|
+
}
|
|
3316
|
+
for (const eachDepends of adep["dependsOn"]) {
|
|
3317
|
+
deps_map[adep.ref].add(eachDepends);
|
|
3318
|
+
}
|
|
3319
|
+
}
|
|
3320
|
+
let retlist = [];
|
|
3321
|
+
for (const akey of Object.keys(deps_map)) {
|
|
3322
|
+
retlist.push({
|
|
3323
|
+
ref: akey,
|
|
3324
|
+
dependsOn: Array.from(deps_map[akey])
|
|
3325
|
+
});
|
|
3326
|
+
}
|
|
3327
|
+
return retlist;
|
|
3328
|
+
};
|
|
3329
|
+
exports.mergeDependencies = mergeDependencies;
|
|
3330
|
+
|
|
3331
|
+
const trimComponents = (components, format) => {
|
|
3332
|
+
const keyCache = {};
|
|
3333
|
+
const filteredComponents = [];
|
|
3334
|
+
for (let comp of components) {
|
|
3335
|
+
if (format === "xml" && comp.component) {
|
|
3336
|
+
if (!keyCache[comp.component.purl]) {
|
|
3337
|
+
keyCache[comp.component.purl] = true;
|
|
3338
|
+
filteredComponents.push(comp);
|
|
3339
|
+
}
|
|
3340
|
+
} else {
|
|
3341
|
+
if (!keyCache[comp.purl]) {
|
|
3342
|
+
keyCache[comp.purl] = true;
|
|
3343
|
+
filteredComponents.push(comp);
|
|
3344
|
+
}
|
|
3345
|
+
}
|
|
3346
|
+
}
|
|
3347
|
+
return filteredComponents;
|
|
3348
|
+
};
|
|
3349
|
+
exports.trimComponents = trimComponents;
|
|
3350
|
+
|
|
3351
|
+
const dedupeBom = (
|
|
3352
|
+
options,
|
|
3353
|
+
components,
|
|
3354
|
+
componentsXmls,
|
|
3355
|
+
parentComponent,
|
|
3356
|
+
dependencies
|
|
3357
|
+
) => {
|
|
3358
|
+
if (!components) {
|
|
3359
|
+
return {};
|
|
3360
|
+
}
|
|
3361
|
+
if (!dependencies) {
|
|
3362
|
+
dependencies = [];
|
|
3363
|
+
}
|
|
3364
|
+
components = trimComponents(components, "json");
|
|
3365
|
+
componentsXmls = trimComponents(componentsXmls, "xml");
|
|
3366
|
+
if (DEBUG_MODE) {
|
|
3367
|
+
console.log(
|
|
3368
|
+
`BoM includes ${components.length} components and ${dependencies.length} dependencies after dedupe`
|
|
3369
|
+
);
|
|
3370
|
+
}
|
|
3371
|
+
const serialNum = "urn:uuid:" + uuidv4();
|
|
3372
|
+
return {
|
|
3373
|
+
options,
|
|
3374
|
+
parentComponent,
|
|
3375
|
+
components,
|
|
3376
|
+
componentsXmls,
|
|
3377
|
+
bomXml: buildBomXml(
|
|
3378
|
+
serialNum,
|
|
3379
|
+
parentComponent,
|
|
3380
|
+
componentsXmls,
|
|
3381
|
+
{
|
|
3382
|
+
dependencies: dependencies,
|
|
3383
|
+
services: options.services
|
|
3384
|
+
},
|
|
3385
|
+
options
|
|
3386
|
+
),
|
|
3387
|
+
bomJson: {
|
|
3388
|
+
bomFormat: "CycloneDX",
|
|
3389
|
+
specVersion: "1.4",
|
|
3390
|
+
serialNumber: serialNum,
|
|
3391
|
+
version: 1,
|
|
3392
|
+
metadata: addMetadata(parentComponent, "json", options),
|
|
3393
|
+
components,
|
|
3394
|
+
services: options.services || [],
|
|
3395
|
+
dependencies
|
|
3396
|
+
}
|
|
3397
|
+
};
|
|
3398
|
+
};
|
|
3399
|
+
exports.dedupeBom = dedupeBom;
|
|
3400
|
+
|
|
3401
|
+
/**
|
|
3402
|
+
* Function to create bom string for all languages
|
|
3403
|
+
*
|
|
3404
|
+
* @param pathList list of to the project
|
|
3405
|
+
* @param options Parse options from the cli
|
|
3406
|
+
*/
|
|
3407
|
+
const createMultiXBom = async (pathList, options) => {
|
|
3408
|
+
let components = [];
|
|
3409
|
+
let dependencies = [];
|
|
3410
|
+
let componentsXmls = [];
|
|
3411
|
+
let bomData = undefined;
|
|
3412
|
+
let parentComponent = determineParentComponent(options);
|
|
3413
|
+
if (
|
|
3414
|
+
["docker", "oci", "container"].includes(options.projectType) &&
|
|
3415
|
+
options.allLayersExplodedDir
|
|
3416
|
+
) {
|
|
3417
|
+
const { osPackages, allTypes } = binaryLib.getOSPackages(
|
|
3418
|
+
options.allLayersExplodedDir
|
|
3419
|
+
);
|
|
3420
|
+
if (DEBUG_MODE) {
|
|
3421
|
+
console.log(
|
|
3422
|
+
`Found ${osPackages.length} OS packages at ${options.allLayersExplodedDir}`
|
|
3423
|
+
);
|
|
3424
|
+
}
|
|
3425
|
+
if (allTypes && allTypes.length) {
|
|
3426
|
+
options.allOSComponentTypes = allTypes;
|
|
3427
|
+
}
|
|
3428
|
+
components = components.concat(osPackages);
|
|
3429
|
+
componentsXmls = componentsXmls.concat(
|
|
3430
|
+
listComponents(options, {}, osPackages, "", "xml")
|
|
3431
|
+
);
|
|
3432
|
+
}
|
|
3433
|
+
if (options.projectType === "os" && options.bomData) {
|
|
3434
|
+
bomData = options.bomData;
|
|
3435
|
+
if (bomData && bomData.bomJson && bomData.bomJson.components) {
|
|
3436
|
+
if (DEBUG_MODE) {
|
|
3437
|
+
console.log(`Found ${bomData.bomJson.components.length} OS components`);
|
|
3438
|
+
}
|
|
3439
|
+
components = components.concat(bomData.bomJson.components);
|
|
3440
|
+
componentsXmls = componentsXmls.concat(
|
|
3441
|
+
listComponents(options, {}, bomData.bomJson.components, "", "xml")
|
|
3442
|
+
);
|
|
3443
|
+
}
|
|
3444
|
+
}
|
|
3445
|
+
for (let path of pathList) {
|
|
3446
|
+
if (DEBUG_MODE) {
|
|
3447
|
+
console.log("Scanning", path);
|
|
3448
|
+
}
|
|
3449
|
+
bomData = await createNodejsBom(path, options);
|
|
3450
|
+
if (bomData && bomData.bomJson && bomData.bomJson.components) {
|
|
3451
|
+
if (DEBUG_MODE) {
|
|
3452
|
+
console.log(
|
|
3453
|
+
`Found ${bomData.bomJson.components.length} node.js packages at ${path}`
|
|
3454
|
+
);
|
|
3455
|
+
}
|
|
3456
|
+
components = components.concat(bomData.bomJson.components);
|
|
3457
|
+
dependencies = dependencies.concat(bomData.bomJson.dependencies);
|
|
3458
|
+
if (!parentComponent || !Object.keys(parentComponent).length) {
|
|
3459
|
+
parentComponent = bomData.parentComponent;
|
|
3460
|
+
}
|
|
3461
|
+
componentsXmls = componentsXmls.concat(
|
|
3462
|
+
listComponents(options, {}, bomData.bomJson.components, "npm", "xml")
|
|
3463
|
+
);
|
|
3464
|
+
}
|
|
3465
|
+
bomData = await createJavaBom(path, options);
|
|
3466
|
+
if (bomData && bomData.bomJson && bomData.bomJson.components) {
|
|
3467
|
+
if (DEBUG_MODE) {
|
|
3468
|
+
console.log(
|
|
3469
|
+
`Found ${bomData.bomJson.components.length} java packages at ${path}`
|
|
3470
|
+
);
|
|
3471
|
+
}
|
|
3472
|
+
components = components.concat(bomData.bomJson.components);
|
|
3473
|
+
dependencies = dependencies.concat(bomData.bomJson.dependencies);
|
|
3474
|
+
if (!parentComponent || !Object.keys(parentComponent).length) {
|
|
3475
|
+
parentComponent = bomData.parentComponent;
|
|
3476
|
+
}
|
|
3477
|
+
componentsXmls = componentsXmls.concat(
|
|
3478
|
+
listComponents(options, {}, bomData.bomJson.components, "maven", "xml")
|
|
3479
|
+
);
|
|
3480
|
+
}
|
|
3481
|
+
bomData = await createPythonBom(path, options);
|
|
3482
|
+
if (bomData && bomData.bomJson && bomData.bomJson.components) {
|
|
3483
|
+
if (DEBUG_MODE) {
|
|
3484
|
+
console.log(
|
|
3485
|
+
`Found ${bomData.bomJson.components.length} python packages at ${path}`
|
|
3486
|
+
);
|
|
3487
|
+
}
|
|
3488
|
+
components = components.concat(bomData.bomJson.components);
|
|
3489
|
+
dependencies = dependencies.concat(bomData.bomJson.dependencies);
|
|
3490
|
+
if (!parentComponent || !Object.keys(parentComponent).length) {
|
|
3491
|
+
parentComponent = bomData.parentComponent;
|
|
3492
|
+
}
|
|
3493
|
+
componentsXmls = componentsXmls.concat(
|
|
3494
|
+
listComponents(options, {}, bomData.bomJson.components, "pypi", "xml")
|
|
3495
|
+
);
|
|
3496
|
+
}
|
|
3497
|
+
bomData = await createGoBom(path, options);
|
|
3498
|
+
if (bomData && bomData.bomJson && bomData.bomJson.components) {
|
|
3499
|
+
if (DEBUG_MODE) {
|
|
3500
|
+
console.log(
|
|
3501
|
+
`Found ${bomData.bomJson.components.length} go packages at ${path}`
|
|
3502
|
+
);
|
|
3503
|
+
}
|
|
3504
|
+
components = components.concat(bomData.bomJson.components);
|
|
3505
|
+
dependencies = dependencies.concat(bomData.bomJson.dependencies);
|
|
3506
|
+
if (!parentComponent || !Object.keys(parentComponent).length) {
|
|
3507
|
+
parentComponent = bomData.parentComponent;
|
|
3508
|
+
}
|
|
3509
|
+
componentsXmls = componentsXmls.concat(
|
|
3510
|
+
listComponents(options, {}, bomData.bomJson.components, "golang", "xml")
|
|
3511
|
+
);
|
|
3512
|
+
}
|
|
3513
|
+
bomData = await createRustBom(path, options);
|
|
3514
|
+
if (bomData && bomData.bomJson && bomData.bomJson.components) {
|
|
3515
|
+
if (DEBUG_MODE) {
|
|
3516
|
+
console.log(
|
|
3517
|
+
`Found ${bomData.bomJson.components.length} rust packages at ${path}`
|
|
3518
|
+
);
|
|
3519
|
+
}
|
|
3520
|
+
components = components.concat(bomData.bomJson.components);
|
|
3521
|
+
dependencies = dependencies.concat(bomData.bomJson.dependencies);
|
|
3522
|
+
if (!parentComponent || !Object.keys(parentComponent).length) {
|
|
3523
|
+
parentComponent = bomData.parentComponent;
|
|
3524
|
+
}
|
|
3525
|
+
componentsXmls = componentsXmls.concat(
|
|
3526
|
+
listComponents(options, {}, bomData.bomJson.components, "cargo", "xml")
|
|
3527
|
+
);
|
|
3528
|
+
}
|
|
3529
|
+
bomData = await createPHPBom(path, options);
|
|
3530
|
+
if (bomData && bomData.bomJson && bomData.bomJson.components) {
|
|
3531
|
+
if (DEBUG_MODE) {
|
|
3532
|
+
console.log(
|
|
3533
|
+
`Found ${bomData.bomJson.components.length} php packages at ${path}`
|
|
3534
|
+
);
|
|
3535
|
+
}
|
|
3536
|
+
components = components.concat(bomData.bomJson.components);
|
|
3537
|
+
dependencies = dependencies.concat(bomData.bomJson.dependencies);
|
|
3538
|
+
if (!parentComponent || !Object.keys(parentComponent).length) {
|
|
3539
|
+
parentComponent = bomData.parentComponent;
|
|
3540
|
+
}
|
|
3541
|
+
componentsXmls = componentsXmls.concat(
|
|
3542
|
+
listComponents(
|
|
3543
|
+
options,
|
|
3544
|
+
{},
|
|
3545
|
+
bomData.bomJson.components,
|
|
3546
|
+
"composer",
|
|
3547
|
+
"xml"
|
|
3548
|
+
)
|
|
3549
|
+
);
|
|
3550
|
+
}
|
|
3551
|
+
bomData = await createRubyBom(path, options);
|
|
3552
|
+
if (bomData && bomData.bomJson && bomData.bomJson.components) {
|
|
3553
|
+
if (DEBUG_MODE) {
|
|
3554
|
+
console.log(
|
|
3555
|
+
`Found ${bomData.bomJson.components.length} ruby packages at ${path}`
|
|
3556
|
+
);
|
|
3557
|
+
}
|
|
3558
|
+
components = components.concat(bomData.bomJson.components);
|
|
3559
|
+
dependencies = dependencies.concat(bomData.bomJson.dependencies);
|
|
3560
|
+
if (!parentComponent || !Object.keys(parentComponent).length) {
|
|
3561
|
+
parentComponent = bomData.parentComponent;
|
|
3562
|
+
}
|
|
3563
|
+
componentsXmls = componentsXmls.concat(
|
|
3564
|
+
listComponents(options, {}, bomData.bomJson.components, "gem", "xml")
|
|
3565
|
+
);
|
|
3566
|
+
}
|
|
3567
|
+
bomData = await createCsharpBom(path, options);
|
|
3568
|
+
if (bomData && bomData.bomJson && bomData.bomJson.components) {
|
|
3569
|
+
if (DEBUG_MODE) {
|
|
3570
|
+
console.log(
|
|
3571
|
+
`Found ${bomData.bomJson.components.length} csharp packages at ${path}`
|
|
3572
|
+
);
|
|
3573
|
+
}
|
|
3574
|
+
components = components.concat(bomData.bomJson.components);
|
|
3575
|
+
dependencies = dependencies.concat(bomData.bomJson.dependencies);
|
|
3576
|
+
if (!parentComponent || !Object.keys(parentComponent).length) {
|
|
3577
|
+
parentComponent = bomData.parentComponent;
|
|
3578
|
+
}
|
|
3579
|
+
componentsXmls = componentsXmls.concat(
|
|
3580
|
+
listComponents(options, {}, bomData.bomJson.components, "nuget", "xml")
|
|
3581
|
+
);
|
|
3582
|
+
}
|
|
3583
|
+
bomData = await createDartBom(path, options);
|
|
3584
|
+
if (bomData && bomData.bomJson && bomData.bomJson.components) {
|
|
3585
|
+
if (DEBUG_MODE) {
|
|
3586
|
+
console.log(
|
|
3587
|
+
`Found ${bomData.bomJson.components.length} pub packages at ${path}`
|
|
3588
|
+
);
|
|
3589
|
+
}
|
|
3590
|
+
components = components.concat(bomData.bomJson.components);
|
|
3591
|
+
dependencies = dependencies.concat(bomData.bomJson.dependencies);
|
|
3592
|
+
if (!parentComponent || !Object.keys(parentComponent).length) {
|
|
3593
|
+
parentComponent = bomData.parentComponent;
|
|
3594
|
+
}
|
|
3595
|
+
componentsXmls = componentsXmls.concat(
|
|
3596
|
+
listComponents(options, {}, bomData.bomJson.components, "pub", "xml")
|
|
3597
|
+
);
|
|
3598
|
+
}
|
|
3599
|
+
bomData = await createHaskellBom(path, options);
|
|
3600
|
+
if (bomData && bomData.bomJson && bomData.bomJson.components) {
|
|
3601
|
+
if (DEBUG_MODE) {
|
|
3602
|
+
console.log(
|
|
3603
|
+
`Found ${bomData.bomJson.components.length} hackage packages at ${path}`
|
|
3604
|
+
);
|
|
3605
|
+
}
|
|
3606
|
+
components = components.concat(bomData.bomJson.components);
|
|
3607
|
+
dependencies = dependencies.concat(bomData.bomJson.dependencies);
|
|
3608
|
+
if (!parentComponent || !Object.keys(parentComponent).length) {
|
|
3609
|
+
parentComponent = bomData.parentComponent;
|
|
3610
|
+
}
|
|
3611
|
+
componentsXmls = componentsXmls.concat(
|
|
3612
|
+
listComponents(
|
|
3613
|
+
options,
|
|
3614
|
+
{},
|
|
3615
|
+
bomData.bomJson.components,
|
|
3616
|
+
"hackage",
|
|
3617
|
+
"xml"
|
|
3618
|
+
)
|
|
3619
|
+
);
|
|
3620
|
+
}
|
|
3621
|
+
bomData = await createElixirBom(path, options);
|
|
3622
|
+
if (bomData && bomData.bomJson && bomData.bomJson.components) {
|
|
3623
|
+
if (DEBUG_MODE) {
|
|
3624
|
+
console.log(
|
|
3625
|
+
`Found ${bomData.bomJson.components.length} mix packages at ${path}`
|
|
3626
|
+
);
|
|
3627
|
+
}
|
|
3628
|
+
components = components.concat(bomData.bomJson.components);
|
|
3629
|
+
dependencies = dependencies.concat(bomData.bomJson.dependencies);
|
|
3630
|
+
if (!parentComponent || !Object.keys(parentComponent).length) {
|
|
3631
|
+
parentComponent = bomData.parentComponent;
|
|
3632
|
+
}
|
|
3633
|
+
componentsXmls = componentsXmls.concat(
|
|
3634
|
+
listComponents(options, {}, bomData.bomJson.components, "hex", "xml")
|
|
3635
|
+
);
|
|
3636
|
+
}
|
|
3637
|
+
bomData = await createCppBom(path, options);
|
|
3638
|
+
if (bomData && bomData.bomJson && bomData.bomJson.components) {
|
|
3639
|
+
if (DEBUG_MODE) {
|
|
3640
|
+
console.log(
|
|
3641
|
+
`Found ${bomData.bomJson.components.length} cpp packages at ${path}`
|
|
3642
|
+
);
|
|
3643
|
+
}
|
|
3644
|
+
components = components.concat(bomData.bomJson.components);
|
|
3645
|
+
dependencies = dependencies.concat(bomData.bomJson.dependencies);
|
|
3646
|
+
if (!parentComponent || !Object.keys(parentComponent).length) {
|
|
3647
|
+
parentComponent = bomData.parentComponent;
|
|
3648
|
+
}
|
|
3649
|
+
componentsXmls = componentsXmls.concat(
|
|
3650
|
+
listComponents(options, {}, bomData.bomJson.components, "conan", "xml")
|
|
3651
|
+
);
|
|
3652
|
+
}
|
|
3653
|
+
bomData = await createClojureBom(path, options);
|
|
3654
|
+
if (bomData && bomData.bomJson && bomData.bomJson.components) {
|
|
3655
|
+
if (DEBUG_MODE) {
|
|
3656
|
+
console.log(
|
|
3657
|
+
`Found ${bomData.bomJson.components.length} clojure packages at ${path}`
|
|
3658
|
+
);
|
|
3659
|
+
}
|
|
3660
|
+
components = components.concat(bomData.bomJson.components);
|
|
3661
|
+
dependencies = dependencies.concat(bomData.bomJson.dependencies);
|
|
3662
|
+
if (!parentComponent || !Object.keys(parentComponent).length) {
|
|
3663
|
+
parentComponent = bomData.parentComponent;
|
|
3664
|
+
}
|
|
3665
|
+
componentsXmls = componentsXmls.concat(
|
|
3666
|
+
listComponents(
|
|
3667
|
+
options,
|
|
3668
|
+
{},
|
|
3669
|
+
bomData.bomJson.components,
|
|
3670
|
+
"clojars",
|
|
3671
|
+
"xml"
|
|
3672
|
+
)
|
|
3673
|
+
);
|
|
3674
|
+
}
|
|
3675
|
+
bomData = await createGitHubBom(path, options);
|
|
3676
|
+
if (bomData && bomData.bomJson && bomData.bomJson.components) {
|
|
3677
|
+
if (DEBUG_MODE) {
|
|
3678
|
+
console.log(
|
|
3679
|
+
`Found ${bomData.bomJson.components.length} GitHub action packages at ${path}`
|
|
3680
|
+
);
|
|
3681
|
+
}
|
|
3682
|
+
components = components.concat(bomData.bomJson.components);
|
|
3683
|
+
dependencies = dependencies.concat(bomData.bomJson.dependencies);
|
|
3684
|
+
if (!parentComponent || !Object.keys(parentComponent).length) {
|
|
3685
|
+
parentComponent = bomData.parentComponent;
|
|
3686
|
+
}
|
|
3687
|
+
componentsXmls = componentsXmls.concat(
|
|
3688
|
+
listComponents(options, {}, bomData.bomJson.components, "github", "xml")
|
|
3689
|
+
);
|
|
3690
|
+
}
|
|
3691
|
+
bomData = await createCloudBuildBom(path, options);
|
|
3692
|
+
if (bomData && bomData.bomJson && bomData.bomJson.components) {
|
|
3693
|
+
if (DEBUG_MODE) {
|
|
3694
|
+
console.log(
|
|
3695
|
+
`Found ${bomData.bomJson.components.length} CloudBuild configuration at ${path}`
|
|
3696
|
+
);
|
|
3697
|
+
}
|
|
3698
|
+
components = components.concat(bomData.bomJson.components);
|
|
3699
|
+
dependencies = dependencies.concat(bomData.bomJson.dependencies);
|
|
3700
|
+
if (!parentComponent || !Object.keys(parentComponent).length) {
|
|
3701
|
+
parentComponent = bomData.parentComponent;
|
|
3702
|
+
}
|
|
3703
|
+
componentsXmls = componentsXmls.concat(
|
|
3704
|
+
listComponents(
|
|
3705
|
+
options,
|
|
3706
|
+
{},
|
|
3707
|
+
bomData.bomJson.components,
|
|
3708
|
+
"cloudbuild",
|
|
3709
|
+
"xml"
|
|
3710
|
+
)
|
|
3711
|
+
);
|
|
3712
|
+
}
|
|
3713
|
+
// jar scanning is quite slow so this is limited to only deep scans
|
|
3714
|
+
if (options.deep) {
|
|
3715
|
+
bomData = createJarBom(path, options);
|
|
3716
|
+
if (bomData && bomData.bomJson && bomData.bomJson.components) {
|
|
3717
|
+
if (DEBUG_MODE) {
|
|
3718
|
+
console.log(
|
|
3719
|
+
`Found ${bomData.bomJson.components.length} jar packages at ${path}`
|
|
3720
|
+
);
|
|
3721
|
+
}
|
|
3722
|
+
components = components.concat(bomData.bomJson.components);
|
|
3723
|
+
dependencies = dependencies.concat(bomData.bomJson.dependencies);
|
|
3724
|
+
if (!parentComponent || !Object.keys(parentComponent).length) {
|
|
3725
|
+
parentComponent = bomData.parentComponent;
|
|
3726
|
+
}
|
|
3727
|
+
componentsXmls = componentsXmls.concat(
|
|
3728
|
+
listComponents(
|
|
3729
|
+
options,
|
|
3730
|
+
{},
|
|
3731
|
+
bomData.bomJson.components,
|
|
3732
|
+
"maven",
|
|
3733
|
+
"xml"
|
|
3734
|
+
)
|
|
3735
|
+
);
|
|
3736
|
+
}
|
|
3737
|
+
}
|
|
3738
|
+
} // for
|
|
3739
|
+
if (options.lastWorkingDir && options.lastWorkingDir !== "") {
|
|
3740
|
+
bomData = createJarBom(options.lastWorkingDir, options);
|
|
3741
|
+
if (bomData && bomData.bomJson && bomData.bomJson.components) {
|
|
3742
|
+
if (DEBUG_MODE) {
|
|
3743
|
+
console.log(
|
|
3744
|
+
`Found ${bomData.bomJson.components.length} jar packages at ${options.lastWorkingDir}`
|
|
3745
|
+
);
|
|
3746
|
+
}
|
|
3747
|
+
components = components.concat(bomData.bomJson.components);
|
|
3748
|
+
dependencies = dependencies.concat(bomData.bomJson.dependencies);
|
|
3749
|
+
if (!parentComponent || !Object.keys(parentComponent).length) {
|
|
3750
|
+
parentComponent = bomData.parentComponent;
|
|
3751
|
+
}
|
|
3752
|
+
componentsXmls = componentsXmls.concat(
|
|
3753
|
+
listComponents(options, {}, bomData.bomJson.components, "maven", "xml")
|
|
3754
|
+
);
|
|
3755
|
+
}
|
|
3756
|
+
}
|
|
3757
|
+
return dedupeBom(
|
|
3758
|
+
options,
|
|
3759
|
+
components,
|
|
3760
|
+
componentsXmls,
|
|
3761
|
+
parentComponent,
|
|
3762
|
+
dependencies
|
|
3763
|
+
);
|
|
3764
|
+
};
|
|
3765
|
+
|
|
3766
|
+
/**
|
|
3767
|
+
* Function to create bom string for various languages
|
|
3768
|
+
*
|
|
3769
|
+
* @param path to the project
|
|
3770
|
+
* @param options Parse options from the cli
|
|
3771
|
+
*/
|
|
3772
|
+
const createXBom = async (path, options) => {
|
|
3773
|
+
try {
|
|
3774
|
+
fs.accessSync(path, fs.constants.R_OK);
|
|
3775
|
+
} catch (err) {
|
|
3776
|
+
console.error(path, "is invalid");
|
|
3777
|
+
process.exit(1);
|
|
3778
|
+
}
|
|
3779
|
+
// node.js - package.json
|
|
3780
|
+
if (
|
|
3781
|
+
fs.existsSync(pathLib.join(path, "package.json")) ||
|
|
3782
|
+
fs.existsSync(pathLib.join(path, "rush.json")) ||
|
|
3783
|
+
fs.existsSync(pathLib.join(path, "yarn.lock"))
|
|
3784
|
+
) {
|
|
3785
|
+
return await createNodejsBom(path, options);
|
|
3786
|
+
}
|
|
3787
|
+
// maven - pom.xml
|
|
3788
|
+
const pomFiles = utils.getAllFiles(
|
|
3789
|
+
path,
|
|
3790
|
+
(options.multiProject ? "**/" : "") + "pom.xml"
|
|
3791
|
+
);
|
|
3792
|
+
// gradle
|
|
3793
|
+
let gradleFiles = utils.getAllFiles(
|
|
3794
|
+
path,
|
|
3795
|
+
(options.multiProject ? "**/" : "") + "build.gradle*"
|
|
3796
|
+
);
|
|
3797
|
+
// scala sbt
|
|
3798
|
+
let sbtFiles = utils.getAllFiles(
|
|
3799
|
+
path,
|
|
3800
|
+
(options.multiProject ? "**/" : "") + "{build.sbt,Build.scala}*"
|
|
3801
|
+
);
|
|
3802
|
+
if (pomFiles.length || gradleFiles.length || sbtFiles.length) {
|
|
3803
|
+
return await createJavaBom(path, options);
|
|
3804
|
+
}
|
|
3805
|
+
// python
|
|
3806
|
+
const pipenvMode = fs.existsSync(pathLib.join(path, "Pipfile"));
|
|
3807
|
+
const poetryMode = fs.existsSync(pathLib.join(path, "poetry.lock"));
|
|
3808
|
+
const reqFiles = utils.getAllFiles(
|
|
3809
|
+
path,
|
|
3810
|
+
(options.multiProject ? "**/" : "") + "requirements.txt"
|
|
3811
|
+
);
|
|
3812
|
+
const reqDirFiles = utils.getAllFiles(
|
|
3813
|
+
path,
|
|
3814
|
+
(options.multiProject ? "**/" : "") + "requirements/*.txt"
|
|
3815
|
+
);
|
|
3816
|
+
const setupPy = pathLib.join(path, "setup.py");
|
|
3817
|
+
const requirementsMode =
|
|
3818
|
+
(reqFiles && reqFiles.length) || (reqDirFiles && reqDirFiles.length);
|
|
3819
|
+
const whlFiles = utils.getAllFiles(
|
|
3820
|
+
path,
|
|
3821
|
+
(options.multiProject ? "**/" : "") + "*.whl"
|
|
3822
|
+
);
|
|
3823
|
+
const setupPyMode = fs.existsSync(setupPy);
|
|
3824
|
+
if (
|
|
3825
|
+
requirementsMode ||
|
|
3826
|
+
pipenvMode ||
|
|
3827
|
+
poetryMode ||
|
|
3828
|
+
setupPyMode ||
|
|
3829
|
+
whlFiles.length
|
|
3830
|
+
) {
|
|
3831
|
+
return await createPythonBom(path, options);
|
|
3832
|
+
}
|
|
3833
|
+
// go
|
|
3834
|
+
const gosumFiles = utils.getAllFiles(
|
|
3835
|
+
path,
|
|
3836
|
+
(options.multiProject ? "**/" : "") + "go.sum"
|
|
3837
|
+
);
|
|
3838
|
+
const gomodFiles = utils.getAllFiles(
|
|
3839
|
+
path,
|
|
3840
|
+
(options.multiProject ? "**/" : "") + "go.mod"
|
|
3841
|
+
);
|
|
3842
|
+
const gopkgLockFiles = utils.getAllFiles(
|
|
3843
|
+
path,
|
|
3844
|
+
(options.multiProject ? "**/" : "") + "Gopkg.lock"
|
|
3845
|
+
);
|
|
3846
|
+
if (gomodFiles.length || gosumFiles.length || gopkgLockFiles.length) {
|
|
3847
|
+
return await createGoBom(path, options);
|
|
3848
|
+
}
|
|
3849
|
+
|
|
3850
|
+
// rust
|
|
3851
|
+
const cargoLockFiles = utils.getAllFiles(
|
|
3852
|
+
path,
|
|
3853
|
+
(options.multiProject ? "**/" : "") + "Cargo.lock"
|
|
3854
|
+
);
|
|
3855
|
+
const cargoFiles = utils.getAllFiles(
|
|
3856
|
+
path,
|
|
3857
|
+
(options.multiProject ? "**/" : "") + "Cargo.toml"
|
|
3858
|
+
);
|
|
3859
|
+
if (cargoLockFiles.length || cargoFiles.length) {
|
|
3860
|
+
return await createRustBom(path, options);
|
|
3861
|
+
}
|
|
3862
|
+
|
|
3863
|
+
// php
|
|
3864
|
+
const composerJsonFiles = utils.getAllFiles(
|
|
3865
|
+
path,
|
|
3866
|
+
(options.multiProject ? "**/" : "") + "composer.json"
|
|
3867
|
+
);
|
|
3868
|
+
const composerLockFiles = utils.getAllFiles(
|
|
3869
|
+
path,
|
|
3870
|
+
(options.multiProject ? "**/" : "") + "composer.lock"
|
|
3871
|
+
);
|
|
3872
|
+
if (composerJsonFiles.length || composerLockFiles.length) {
|
|
3873
|
+
return await createPHPBom(path, options);
|
|
3874
|
+
}
|
|
3875
|
+
|
|
3876
|
+
// Ruby
|
|
3877
|
+
const gemFiles = utils.getAllFiles(
|
|
3878
|
+
path,
|
|
3879
|
+
(options.multiProject ? "**/" : "") + "Gemfile"
|
|
3880
|
+
);
|
|
3881
|
+
const gemLockFiles = utils.getAllFiles(
|
|
3882
|
+
path,
|
|
3883
|
+
(options.multiProject ? "**/" : "") + "Gemfile.lock"
|
|
3884
|
+
);
|
|
3885
|
+
if (gemFiles.length || gemLockFiles.length) {
|
|
3886
|
+
return await createRubyBom(path, options);
|
|
3887
|
+
}
|
|
3888
|
+
|
|
3889
|
+
// .Net
|
|
3890
|
+
const csProjFiles = utils.getAllFiles(
|
|
3891
|
+
path,
|
|
3892
|
+
(options.multiProject ? "**/" : "") + "*.csproj"
|
|
3893
|
+
);
|
|
3894
|
+
if (csProjFiles.length) {
|
|
3895
|
+
return await createCsharpBom(path, options);
|
|
3896
|
+
}
|
|
3897
|
+
|
|
3898
|
+
// Dart
|
|
3899
|
+
const pubFiles = utils.getAllFiles(
|
|
3900
|
+
path,
|
|
3901
|
+
(options.multiProject ? "**/" : "") + "pubspec.lock"
|
|
3902
|
+
);
|
|
3903
|
+
const pubSpecFiles = utils.getAllFiles(
|
|
3904
|
+
path,
|
|
3905
|
+
(options.multiProject ? "**/" : "") + "pubspec.yaml"
|
|
3906
|
+
);
|
|
3907
|
+
if (pubFiles.length || pubSpecFiles.length) {
|
|
3908
|
+
return await createDartBom(path, options);
|
|
3909
|
+
}
|
|
3910
|
+
|
|
3911
|
+
// Haskell
|
|
3912
|
+
const hackageFiles = utils.getAllFiles(
|
|
3913
|
+
path,
|
|
3914
|
+
(options.multiProject ? "**/" : "") + "cabal.project.freeze"
|
|
3915
|
+
);
|
|
3916
|
+
if (hackageFiles.length) {
|
|
3917
|
+
return await createHaskellBom(path, options);
|
|
3918
|
+
}
|
|
3919
|
+
|
|
3920
|
+
// Elixir
|
|
3921
|
+
const mixFiles = utils.getAllFiles(
|
|
3922
|
+
path,
|
|
3923
|
+
(options.multiProject ? "**/" : "") + "mix.lock"
|
|
3924
|
+
);
|
|
3925
|
+
if (mixFiles.length) {
|
|
3926
|
+
return await createElixirBom(path, options);
|
|
3927
|
+
}
|
|
3928
|
+
|
|
3929
|
+
// cpp
|
|
3930
|
+
const conanLockFiles = utils.getAllFiles(
|
|
3931
|
+
path,
|
|
3932
|
+
(options.multiProject ? "**/" : "") + "conan.lock"
|
|
3933
|
+
);
|
|
3934
|
+
const conanFiles = utils.getAllFiles(
|
|
3935
|
+
path,
|
|
3936
|
+
(options.multiProject ? "**/" : "") + "conanfile.txt"
|
|
3937
|
+
);
|
|
3938
|
+
if (conanLockFiles.length || conanFiles.length) {
|
|
3939
|
+
return await createCppBom(path, options);
|
|
3940
|
+
}
|
|
3941
|
+
|
|
3942
|
+
// clojure
|
|
3943
|
+
const ednFiles = utils.getAllFiles(
|
|
3944
|
+
path,
|
|
3945
|
+
(options.multiProject ? "**/" : "") + "deps.edn"
|
|
3946
|
+
);
|
|
3947
|
+
const leinFiles = utils.getAllFiles(
|
|
3948
|
+
path,
|
|
3949
|
+
(options.multiProject ? "**/" : "") + "project.clj"
|
|
3950
|
+
);
|
|
3951
|
+
if (ednFiles.length || leinFiles.length) {
|
|
3952
|
+
return await createClojureBom(path, options);
|
|
3953
|
+
}
|
|
3954
|
+
|
|
3955
|
+
// GitHub actions
|
|
3956
|
+
const ghactionFiles = utils.getAllFiles(path, ".github/workflows/" + "*.yml");
|
|
3957
|
+
if (ghactionFiles.length) {
|
|
3958
|
+
return await createGitHubBom(path, options);
|
|
3959
|
+
}
|
|
3960
|
+
|
|
3961
|
+
// Jenkins plugins
|
|
3962
|
+
const hpiFiles = utils.getAllFiles(
|
|
3963
|
+
path,
|
|
3964
|
+
(options.multiProject ? "**/" : "") + "*.hpi"
|
|
3965
|
+
);
|
|
3966
|
+
if (hpiFiles.length) {
|
|
3967
|
+
return await createJenkinsBom(path, options);
|
|
3968
|
+
}
|
|
3969
|
+
|
|
3970
|
+
// Helm charts
|
|
3971
|
+
const chartFiles = utils.getAllFiles(
|
|
3972
|
+
path,
|
|
3973
|
+
(options.multiProject ? "**/" : "") + "Chart.yaml"
|
|
3974
|
+
);
|
|
3975
|
+
const yamlFiles = utils.getAllFiles(
|
|
3976
|
+
path,
|
|
3977
|
+
(options.multiProject ? "**/" : "") + "values.yaml"
|
|
3978
|
+
);
|
|
3979
|
+
if (chartFiles.length || yamlFiles.length) {
|
|
3980
|
+
return await createHelmBom(path, options);
|
|
3981
|
+
}
|
|
3982
|
+
|
|
3983
|
+
// Docker compose, kubernetes and skaffold
|
|
3984
|
+
const dcFiles = utils.getAllFiles(
|
|
3985
|
+
path,
|
|
3986
|
+
(options.multiProject ? "**/" : "") + "docker-compose*.yml"
|
|
3987
|
+
);
|
|
3988
|
+
const skFiles = utils.getAllFiles(
|
|
3989
|
+
path,
|
|
3990
|
+
(options.multiProject ? "**/" : "") + "skaffold.yaml"
|
|
3991
|
+
);
|
|
3992
|
+
const deplFiles = utils.getAllFiles(
|
|
3993
|
+
path,
|
|
3994
|
+
(options.multiProject ? "**/" : "") + "deployment.yaml"
|
|
3995
|
+
);
|
|
3996
|
+
if (dcFiles.length || skFiles.length || deplFiles.length) {
|
|
3997
|
+
return await createContainerSpecLikeBom(path, options);
|
|
3998
|
+
}
|
|
3999
|
+
|
|
4000
|
+
// Google CloudBuild
|
|
4001
|
+
const cbFiles = utils.getAllFiles(
|
|
4002
|
+
path,
|
|
4003
|
+
(options.multiProject ? "**/" : "") + "cloudbuild.yaml"
|
|
4004
|
+
);
|
|
4005
|
+
if (cbFiles.length) {
|
|
4006
|
+
return await createCloudBuildBom(path, options);
|
|
4007
|
+
}
|
|
4008
|
+
};
|
|
4009
|
+
|
|
4010
|
+
/**
|
|
4011
|
+
* Function to create bom string for various languages
|
|
4012
|
+
*
|
|
4013
|
+
* @param path to the project
|
|
4014
|
+
* @param options Parse options from the cli
|
|
4015
|
+
*/
|
|
4016
|
+
const createBom = async (path, options) => {
|
|
4017
|
+
let { projectType } = options;
|
|
4018
|
+
if (!projectType) {
|
|
4019
|
+
projectType = "";
|
|
4020
|
+
}
|
|
4021
|
+
projectType = projectType.toLowerCase();
|
|
4022
|
+
let exportData = undefined;
|
|
4023
|
+
let isContainerMode = false;
|
|
4024
|
+
// Docker and image archive support
|
|
4025
|
+
if (path.endsWith(".tar") || path.endsWith(".tar.gz")) {
|
|
4026
|
+
exportData = await dockerLib.exportArchive(path);
|
|
4027
|
+
if (!exportData) {
|
|
4028
|
+
console.log(
|
|
4029
|
+
`OS BOM generation has failed due to problems with exporting the image ${path}`
|
|
4030
|
+
);
|
|
4031
|
+
return {};
|
|
4032
|
+
}
|
|
4033
|
+
isContainerMode = true;
|
|
4034
|
+
} else if (
|
|
4035
|
+
projectType === "docker" ||
|
|
4036
|
+
projectType === "podman" ||
|
|
4037
|
+
projectType === "oci" ||
|
|
4038
|
+
path.startsWith("docker.io") ||
|
|
4039
|
+
path.startsWith("quay.io") ||
|
|
4040
|
+
path.startsWith("ghcr.io") ||
|
|
4041
|
+
path.startsWith("mcr.microsoft.com") ||
|
|
4042
|
+
path.includes("@sha256") ||
|
|
4043
|
+
path.includes(":latest")
|
|
4044
|
+
) {
|
|
4045
|
+
exportData = await dockerLib.exportImage(path);
|
|
4046
|
+
if (!exportData) {
|
|
4047
|
+
console.log(
|
|
4048
|
+
"BOM generation has failed due to problems with exporting the image"
|
|
4049
|
+
);
|
|
4050
|
+
options.failOnError && process.exit(1);
|
|
4051
|
+
return {};
|
|
4052
|
+
}
|
|
4053
|
+
isContainerMode = true;
|
|
4054
|
+
} else if (projectType === "oci-dir") {
|
|
4055
|
+
isContainerMode = true;
|
|
4056
|
+
exportData = {
|
|
4057
|
+
inspectData: undefined,
|
|
4058
|
+
lastWorkingDir: "",
|
|
4059
|
+
allLayersDir: path,
|
|
4060
|
+
allLayersExplodedDir: path
|
|
4061
|
+
};
|
|
4062
|
+
if (fs.existsSync(pathLib.join(path, "all-layers"))) {
|
|
4063
|
+
exportData.allLayersDir = pathLib.join(path, "all-layers");
|
|
4064
|
+
}
|
|
4065
|
+
exportData.pkgPathList = dockerLib.getPkgPathList(exportData, undefined);
|
|
4066
|
+
}
|
|
4067
|
+
if (isContainerMode) {
|
|
4068
|
+
options.multiProject = true;
|
|
4069
|
+
options.installDeps = false;
|
|
4070
|
+
// Force the project type to docker
|
|
4071
|
+
options.projectType = "docker";
|
|
4072
|
+
// Pass the original path
|
|
4073
|
+
options.path = path;
|
|
4074
|
+
options.parentComponent = {};
|
|
4075
|
+
// Create parent component based on the inspect config
|
|
4076
|
+
const inspectData = exportData.inspectData;
|
|
4077
|
+
if (
|
|
4078
|
+
inspectData &&
|
|
4079
|
+
inspectData.RepoDigests &&
|
|
4080
|
+
inspectData.RepoTags &&
|
|
4081
|
+
Array.isArray(inspectData.RepoDigests) &&
|
|
4082
|
+
Array.isArray(inspectData.RepoTags) &&
|
|
4083
|
+
inspectData.RepoDigests.length &&
|
|
4084
|
+
inspectData.RepoTags.length
|
|
4085
|
+
) {
|
|
4086
|
+
const repoTag = inspectData.RepoTags[0];
|
|
4087
|
+
if (repoTag) {
|
|
4088
|
+
const tmpA = repoTag.split(":");
|
|
4089
|
+
if (tmpA && tmpA.length === 2) {
|
|
4090
|
+
options.parentComponent = {
|
|
4091
|
+
name: tmpA[0],
|
|
4092
|
+
version: tmpA[1],
|
|
4093
|
+
type: "container",
|
|
4094
|
+
purl: "pkg:oci/" + inspectData.RepoDigests[0],
|
|
4095
|
+
_integrity: inspectData.RepoDigests[0].replace("sha256:", "sha256-")
|
|
4096
|
+
};
|
|
4097
|
+
}
|
|
4098
|
+
}
|
|
4099
|
+
}
|
|
4100
|
+
// Pass the entire export data about the image layers
|
|
4101
|
+
options.exportData = exportData;
|
|
4102
|
+
options.lastWorkingDir = exportData.lastWorkingDir;
|
|
4103
|
+
options.allLayersExplodedDir = exportData.allLayersExplodedDir;
|
|
4104
|
+
const bomData = await createMultiXBom(
|
|
4105
|
+
[...new Set(exportData.pkgPathList)],
|
|
4106
|
+
options
|
|
4107
|
+
);
|
|
4108
|
+
if (
|
|
4109
|
+
exportData.allLayersDir &&
|
|
4110
|
+
exportData.allLayersDir.startsWith(os.tmpdir())
|
|
4111
|
+
) {
|
|
4112
|
+
if (DEBUG_MODE) {
|
|
4113
|
+
console.log(`Cleaning up ${exportData.allLayersDir}`);
|
|
4114
|
+
}
|
|
4115
|
+
try {
|
|
4116
|
+
if (fs.rmSync) {
|
|
4117
|
+
fs.rmSync(exportData.allLayersDir, { recursive: true, force: true });
|
|
4118
|
+
}
|
|
4119
|
+
} catch (err) {
|
|
4120
|
+
// continue regardless of error
|
|
4121
|
+
}
|
|
4122
|
+
}
|
|
4123
|
+
return bomData;
|
|
4124
|
+
}
|
|
4125
|
+
if (path.endsWith(".war")) {
|
|
4126
|
+
projectType = "java";
|
|
4127
|
+
}
|
|
4128
|
+
switch (projectType) {
|
|
4129
|
+
case "java":
|
|
4130
|
+
case "groovy":
|
|
4131
|
+
case "kotlin":
|
|
4132
|
+
case "scala":
|
|
4133
|
+
case "jvm":
|
|
4134
|
+
return await createJavaBom(path, options);
|
|
4135
|
+
case "jar":
|
|
4136
|
+
options.multiProject = true;
|
|
4137
|
+
return await createJarBom(path, options);
|
|
4138
|
+
case "gradle-index":
|
|
4139
|
+
case "gradle-cache":
|
|
4140
|
+
options.multiProject = true;
|
|
4141
|
+
return await createJarBom(GRADLE_CACHE_DIR, options);
|
|
4142
|
+
case "sbt-index":
|
|
4143
|
+
case "sbt-cache":
|
|
4144
|
+
options.multiProject = true;
|
|
4145
|
+
return await createJarBom(SBT_CACHE_DIR, options);
|
|
4146
|
+
case "maven-index":
|
|
4147
|
+
case "maven-cache":
|
|
4148
|
+
case "maven-repo":
|
|
4149
|
+
options.multiProject = true;
|
|
4150
|
+
return await createJarBom(
|
|
4151
|
+
pathLib.join(os.homedir(), ".m2", "repository"),
|
|
4152
|
+
options
|
|
4153
|
+
);
|
|
4154
|
+
case "nodejs":
|
|
4155
|
+
case "js":
|
|
4156
|
+
case "javascript":
|
|
4157
|
+
case "typescript":
|
|
4158
|
+
case "ts":
|
|
4159
|
+
return await createNodejsBom(path, options);
|
|
4160
|
+
case "python":
|
|
4161
|
+
case "py":
|
|
4162
|
+
options.multiProject = true;
|
|
4163
|
+
return await createPythonBom(path, options);
|
|
4164
|
+
case "go":
|
|
4165
|
+
case "golang":
|
|
4166
|
+
options.multiProject = true;
|
|
4167
|
+
return await createGoBom(path, options);
|
|
4168
|
+
case "rust":
|
|
4169
|
+
case "rust-lang":
|
|
4170
|
+
options.multiProject = true;
|
|
4171
|
+
return await createRustBom(path, options);
|
|
4172
|
+
case "php":
|
|
4173
|
+
options.multiProject = true;
|
|
4174
|
+
return await createPHPBom(path, options);
|
|
4175
|
+
case "ruby":
|
|
4176
|
+
options.multiProject = true;
|
|
4177
|
+
return await createRubyBom(path, options);
|
|
4178
|
+
case "csharp":
|
|
4179
|
+
case "netcore":
|
|
4180
|
+
case "dotnet":
|
|
4181
|
+
options.multiProject = true;
|
|
4182
|
+
return await createCsharpBom(path, options);
|
|
4183
|
+
case "dart":
|
|
4184
|
+
case "flutter":
|
|
4185
|
+
case "pub":
|
|
4186
|
+
options.multiProject = true;
|
|
4187
|
+
return await createDartBom(path, options);
|
|
4188
|
+
case "haskell":
|
|
4189
|
+
case "hackage":
|
|
4190
|
+
case "cabal":
|
|
4191
|
+
options.multiProject = true;
|
|
4192
|
+
return await createHaskellBom(path, options);
|
|
4193
|
+
case "elixir":
|
|
4194
|
+
case "hex":
|
|
4195
|
+
case "mix":
|
|
4196
|
+
options.multiProject = true;
|
|
4197
|
+
return await createElixirBom(path, options);
|
|
4198
|
+
case "c":
|
|
4199
|
+
case "cpp":
|
|
4200
|
+
case "c++":
|
|
4201
|
+
case "conan":
|
|
4202
|
+
options.multiProject = true;
|
|
4203
|
+
return await createCppBom(path, options);
|
|
4204
|
+
case "clojure":
|
|
4205
|
+
case "edn":
|
|
4206
|
+
case "clj":
|
|
4207
|
+
case "leiningen":
|
|
4208
|
+
options.multiProject = true;
|
|
4209
|
+
return await createClojureBom(path, options);
|
|
4210
|
+
case "github":
|
|
4211
|
+
case "actions":
|
|
4212
|
+
options.multiProject = true;
|
|
4213
|
+
return await createGitHubBom(path, options);
|
|
4214
|
+
case "os":
|
|
4215
|
+
case "osquery":
|
|
4216
|
+
case "windows":
|
|
4217
|
+
case "linux":
|
|
4218
|
+
options.multiProject = true;
|
|
4219
|
+
return await createOSBom(path, options);
|
|
4220
|
+
case "jenkins":
|
|
4221
|
+
options.multiProject = true;
|
|
4222
|
+
return await createJenkinsBom(path, options);
|
|
4223
|
+
case "helm":
|
|
4224
|
+
case "charts":
|
|
4225
|
+
options.multiProject = true;
|
|
4226
|
+
return await createHelmBom(path, options);
|
|
4227
|
+
case "helm-index":
|
|
4228
|
+
case "helm-repo":
|
|
4229
|
+
options.multiProject = true;
|
|
4230
|
+
return await createHelmBom(
|
|
4231
|
+
pathLib.join(os.homedir(), ".cache", "helm", "repository"),
|
|
4232
|
+
options
|
|
4233
|
+
);
|
|
4234
|
+
case "universal":
|
|
4235
|
+
case "docker-compose":
|
|
4236
|
+
case "swarm":
|
|
4237
|
+
case "tekton":
|
|
4238
|
+
case "kustomize":
|
|
4239
|
+
case "operator":
|
|
4240
|
+
case "skaffold":
|
|
4241
|
+
case "kubernetes":
|
|
4242
|
+
case "openshift":
|
|
4243
|
+
case "yaml-manifest":
|
|
4244
|
+
options.multiProject = true;
|
|
4245
|
+
return await createContainerSpecLikeBom(path, options);
|
|
4246
|
+
case "cloudbuild":
|
|
4247
|
+
options.multiProject = true;
|
|
4248
|
+
return await createCloudBuildBom(path, options);
|
|
4249
|
+
default:
|
|
4250
|
+
// In recurse mode return multi-language Bom
|
|
4251
|
+
// https://github.com/cyclonedx/cdxgen/issues/95
|
|
4252
|
+
if (options.multiProject) {
|
|
4253
|
+
return await createMultiXBom([path], options);
|
|
4254
|
+
} else {
|
|
4255
|
+
return await createXBom(path, options);
|
|
4256
|
+
}
|
|
4257
|
+
}
|
|
4258
|
+
};
|
|
4259
|
+
exports.createBom = createBom;
|
|
4260
|
+
|
|
4261
|
+
/**
|
|
4262
|
+
* Method to submit the generated bom to dependency-track or cyclonedx server
|
|
4263
|
+
*
|
|
4264
|
+
* @param args CLI args
|
|
4265
|
+
* @param bomContents BOM Xml
|
|
4266
|
+
*/
|
|
4267
|
+
exports.submitBom = async (args, bomContents) => {
|
|
4268
|
+
let serverUrl = args.serverUrl + "/api/v1/bom";
|
|
4269
|
+
let encodedBomContents = Buffer.from(bomContents).toString("base64");
|
|
4270
|
+
if (encodedBomContents.startsWith("77u/")) {
|
|
4271
|
+
encodedBomContents = encodedBomContents.substring(4);
|
|
4272
|
+
}
|
|
4273
|
+
const bomPayload = {
|
|
4274
|
+
project: args.projectId,
|
|
4275
|
+
projectName: args.projectName,
|
|
4276
|
+
projectVersion: args.projectVersion,
|
|
4277
|
+
autoCreate: "true",
|
|
4278
|
+
bom: encodedBomContents
|
|
4279
|
+
};
|
|
4280
|
+
if (DEBUG_MODE) {
|
|
4281
|
+
console.log("Submitting BOM to", serverUrl);
|
|
4282
|
+
}
|
|
4283
|
+
return await got(serverUrl, {
|
|
4284
|
+
method: "PUT",
|
|
4285
|
+
headers: {
|
|
4286
|
+
"X-Api-Key": args.apiKey,
|
|
4287
|
+
"Content-Type": "application/json"
|
|
4288
|
+
},
|
|
4289
|
+
json: bomPayload,
|
|
4290
|
+
responseType: "json"
|
|
4291
|
+
}).json();
|
|
4292
|
+
};
|