-
Notifications
You must be signed in to change notification settings - Fork 285
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
build(tools): software bill of materials generation
Added a script to generate all SBoMs. The short hand to call the script is by running $ yarn generate-sbom and then it saves all the different .spdx files under ./dist/sbom/* where the file names are derived from the relative path of the directory of the build definition. Fixes #2081 Signed-off-by: Peter Somogyvari <peter.somogyvari@accenture.com>
- Loading branch information
Showing
2 changed files
with
113 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
import { fileURLToPath } from "url"; | ||
import { dirname } from "path"; | ||
import path from "path"; | ||
import fs from "fs-extra"; | ||
import { promisify } from "util"; | ||
import { exec, ExecOptions } from "child_process"; | ||
const execAsync = promisify(exec); | ||
|
||
import { globby, Options as GlobbyOptions } from "globby"; | ||
import { RuntimeError } from "run-time-error"; | ||
|
||
const __filename = fileURLToPath(import.meta.url); | ||
const __dirname = dirname(__filename); | ||
const SCRIPT_DIR = __dirname; | ||
const PROJECT_DIR = path.join(SCRIPT_DIR, "../"); | ||
console.log(`SCRIPT_DIR=${SCRIPT_DIR}`); | ||
console.log(`PROJECT_DIR=${PROJECT_DIR}`); | ||
|
||
const BUILD_FILE_GLOBS = [ | ||
"**/go.mod", | ||
"**/Cargo.toml", | ||
"**/build.gradle*", | ||
"yarn.lock", | ||
]; | ||
|
||
/** | ||
* # Software Bill of Materials Generator Script | ||
* | ||
* Can be used to generate all the .spdx files for the various components of | ||
* the framework. The reason why there isn't a single SBoM file generated is | ||
* due to the fact that different languages and their package managers are in | ||
* use in parallel. | ||
* For example we have | ||
* - Rust's cargo, | ||
* - NodeJS/Typescript: yarn/npm | ||
* - Go modules | ||
* - Java/Kotlin: Gradle | ||
* - etc. | ||
* | ||
* Dependencies: | ||
* To run, this script requires | ||
* - Network connection to the internet | ||
* - Bash shell on a Debian/Ubuntu flavored operating system | ||
* - NodeJS (v16 or newer) installation | ||
* - Functioning container engine (Docker/Rancher/etc.) | ||
* | ||
* How does it work: | ||
* 1. It uses a list of glob patterns to find build files defining dependencies. | ||
* For example build.gradle, yarn.lock, etc. | ||
* 2. Once a complete list of these files have been gathered, it iterates through | ||
* their respective directories and runs the SBoM generator tool's container | ||
* with the current working directory set to where the build file is. | ||
* 3. The results of each execution are saved to various .spdx files where the | ||
* name of the file is derived from its relative path calculated from the | ||
* project root directory. Slashes are replaced with double underscores (__). | ||
*/ | ||
const main = async (argv: string[], env: NodeJS.ProcessEnv) => { | ||
if (!argv) { | ||
throw new RuntimeError(`Process argv cannot be falsy.`); | ||
} | ||
if (!env) { | ||
throw new RuntimeError(`Process env cannot be falsy.`); | ||
} | ||
|
||
const globbyOptions: GlobbyOptions = { | ||
cwd: PROJECT_DIR, | ||
absolute: true, | ||
ignore: ["**/node_modules/**"], | ||
}; | ||
const buildFilePaths = await globby(BUILD_FILE_GLOBS, globbyOptions); | ||
console.log(`Package paths (${buildFilePaths.length}): `, buildFilePaths); | ||
|
||
const sbomDir = path.join(PROJECT_DIR, "dist", "sbom"); | ||
await fs.mkdirp(sbomDir); | ||
console.log("Created SBoM dir at: ", sbomDir); | ||
|
||
for (const buildFilePath of buildFilePaths) { | ||
const pkgDirPath = path.dirname(buildFilePath); | ||
const x = path.relative(PROJECT_DIR, pkgDirPath); | ||
await generateSBoM({ dirPath: x }); | ||
} | ||
}; | ||
|
||
export async function generateSBoM(req: { dirPath: string }): Promise<void> { | ||
const shellCmd = `docker run --rm --volume "$(pwd)/:/repository" anchore/syft packages -o spdx /repository/`; | ||
|
||
console.log(`SCANNING DIR: ${req.dirPath}`); | ||
|
||
// clean up the relative file path so that instead of slashes it has underscores | ||
// ./packages/x/build.gradle => packages_x_build_gradle | ||
const replacePattern = new RegExp("[^\\w]", "gm"); | ||
const sbomFilename = req.dirPath.replaceAll(replacePattern, "__"); | ||
const sbomExtension = ".spdx"; | ||
const sbomName = sbomFilename.concat(sbomExtension); | ||
console.log(`SBoM filename: ${sbomName}`); | ||
|
||
const execOpts: ExecOptions = { | ||
cwd: req.dirPath, | ||
maxBuffer: 32 * 1024 * 1024, // 32 MB of stdout will be allowed | ||
}; | ||
|
||
const { stderr, stdout } = await execAsync(shellCmd, execOpts); | ||
|
||
console.log(`STDERR: ${stderr}`); | ||
console.log(`STDOUT: ${stdout}`); | ||
|
||
const sbomPath = path.join(PROJECT_DIR, "dist", "sbom", sbomName); | ||
await fs.writeFile(sbomPath, stdout); | ||
console.log(`Scan OK. Written SBoM to ${sbomPath}`); | ||
} | ||
|
||
main(process.argv, process.env); |