Skip to content

Commit

Permalink
fix: Pipe cyclonedx sbom to std_out (#194)
Browse files Browse the repository at this point in the history
* added 'sbom' subcommand to pipe the cyclonedx bom to std_out so it can be picked up by other scanners

* fixed the linting issues

* pinned the production dependency on minimist from node-persist -> mkdirp -> [email protected] to [email protected] due to a security vuln failing our dogfood scan

* added node script in package.json for an iq dogfood scan

* made include license data false for getInstalledDeps, but getSbomFromCommand already had it as false so it didn't change IQ scan times.  Might be worth stripping everything but the purls to keep it standard with the boms of the other tools

* fixed linting errors and removed the vscode settings json as those options are going to differ locally

* changed the circle-ci build to install from the package-lock.json

* trying npm ci install...

* removed the package-lock.json dependency

* Spartan (#195)

💥

* implemented spartan bom for sbom output and iq scans

Co-authored-by: Jeffry Hesse <[email protected]>
  • Loading branch information
ButterB0wl and DarthHater authored May 8, 2020
1 parent 0c6b4b6 commit 9bf2ae0
Show file tree
Hide file tree
Showing 11 changed files with 74 additions and 52 deletions.
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
key: dependency-cache-{{ checksum "package-lock.json" }}
- run:
name: install-npm-wee
command: npm install
command: npm ci install
- save_cache:
key: dependency-cache-{{ checksum "package-lock.json" }}
paths:
Expand Down
3 changes: 0 additions & 3 deletions .vscode/settings.json

This file was deleted.

13 changes: 6 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
"lint": "eslint src/**/*.ts",
"start": "node ./bin/index.js",
"prepare": "npm run build",
"prepublishOnly": "npm run test"
"prepublishOnly": "npm run test",
"iq-scan": "npx auditjs@latest iq -a auditjs"
},
"keywords": [
"Audit",
Expand Down
3 changes: 3 additions & 0 deletions src/Application/Application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@ export class Application {
logMessage('Auditing your application with Sonatype OSS Index', DEBUG);
this.spinner.maybeCreateMessageForSpinner('Auditing your application with Sonatype OSS Index');
await this.auditWithOSSIndex(args);
} else if (args._[0] == 'sbom') {
await this.populateCoordinatesForIQ();
console.log(this.sbom);
} else {
shutDownLoggerAndExit(0);
}
Expand Down
12 changes: 11 additions & 1 deletion src/CycloneDX/CycloneDXSbomCreator.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,9 @@ const object = {
},
};

const expectedResponse = `<?xml version="1.0" encoding="utf-8"?><bom xmlns="http://cyclonedx.org/schema/bom/1.1" version="1"><components><component type="library" bom-ref="pkg:npm/[email protected]"><name>testdependency</name><version>1.0.1</version><description/><purl>pkg:npm/[email protected]</purl><externalReferences><reference type="issue-tracker"><url>git+ssh://[email protected]/slackhq/csp-html-webpack-plugin.git</url></reference></externalReferences></component><component type="library" bom-ref="pkg:npm/[email protected]"><name>testdependency2</name><version>1.0.2</version><description/><purl>pkg:npm/[email protected]</purl></component><component type="library" bom-ref="pkg:npm/[email protected]"><name>testdependency</name><version>1.0.0</version><description/><purl>pkg:npm/[email protected]</purl></component><component type="library" bom-ref="pkg:npm/%40scope/[email protected]"><group>@scope</group><name>testdependency3</name><version>1.0.2</version><description/><purl>pkg:npm/%40scope/[email protected]</purl></component></components></bom>`;
const expectedResponse = `<?xml version="1.0" encoding="utf-8"?><bom xmlns="http://cyclonedx.org/schema/bom/1.1" version="1"><components><component type="library" bom-ref="pkg:npm/[email protected]"><name>testdependency</name><version>1.0.1</version><purl>pkg:npm/[email protected]</purl><description/><externalReferences><reference type="issue-tracker"><url>git+ssh://[email protected]/slackhq/csp-html-webpack-plugin.git</url></reference></externalReferences></component><component type="library" bom-ref="pkg:npm/[email protected]"><name>testdependency2</name><version>1.0.2</version><purl>pkg:npm/[email protected]</purl><description/></component><component type="library" bom-ref="pkg:npm/[email protected]"><name>testdependency</name><version>1.0.0</version><purl>pkg:npm/[email protected]</purl><description/></component><component type="library" bom-ref="pkg:npm/%40scope/[email protected]"><group>@scope</group><name>testdependency3</name><version>1.0.2</version><purl>pkg:npm/%40scope/[email protected]</purl><description/></component></components></bom>`;

const expectedSpartanResponse = `<?xml version="1.0" encoding="utf-8"?><bom xmlns="http://cyclonedx.org/schema/bom/1.1" version="1"><components><component type="library" bom-ref="pkg:npm/[email protected]"><name>testdependency</name><version>1.0.1</version><purl>pkg:npm/[email protected]</purl></component><component type="library" bom-ref="pkg:npm/[email protected]"><name>testdependency2</name><version>1.0.2</version><purl>pkg:npm/[email protected]</purl></component><component type="library" bom-ref="pkg:npm/[email protected]"><name>testdependency</name><version>1.0.0</version><purl>pkg:npm/[email protected]</purl></component><component type="library" bom-ref="pkg:npm/%40scope/[email protected]"><group>@scope</group><name>testdependency3</name><version>1.0.2</version><purl>pkg:npm/%40scope/[email protected]</purl></component></components></bom>`;

describe('CycloneDXSbomCreator', async () => {
it('should create an sbom string given a minimal valid object', async () => {
Expand All @@ -65,4 +67,12 @@ describe('CycloneDXSbomCreator', async () => {

expect(string).to.eq(expectedResponse);
});

it('should create a spartan sbom string given a minimal valid object', async () => {
const sbomCreator = new CycloneDXSbomCreator(process.cwd(), { spartan: true });

const string = await sbomCreator.createBom(object);

expect(string).to.eq(expectedSpartanResponse);
});
});
40 changes: 24 additions & 16 deletions src/CycloneDX/CycloneDXSbomCreator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ export class CycloneDXSbomCreator {
}

private addComponent(pkg: any, list: any, isRootPkg = false): void {
const spartan = this.options?.spartan ? this.options.spartan : false;
//read-installed with default options marks devDependencies as extraneous
//if a package is marked as extraneous, do not include it as a component
if (pkg.extraneous) {
Expand All @@ -116,36 +117,39 @@ export class CycloneDXSbomCreator {
const name: string = pkgIdentifier.fullName as string;
const version: string = pkg.version as string;
const purl: string = toPurl(name, version, group);
const description: GenericDescription = { '#cdata': pkg.description };

const component: Component = {
'@type': this.determinePackageType(pkg),
'@bom-ref': purl,
group: group,
name: name,
version: version,
description: description,
hashes: [],
licenses: [],
purl: purl,
externalReferences: this.addExternalReferences(pkg),
};

if (component.group === '') {
delete component.group;
}

if (this.options && this.options.includeLicenseData) {
component.licenses = this.getLicenses(pkg);
} else {
delete component.licenses;
}
if (!spartan) {
const description: GenericDescription = { '#cdata': pkg.description };

if (component.externalReferences === undefined || component.externalReferences.length === 0) {
delete component.externalReferences;
}
component.description = description;
component.hashes = [];
component.licenses = [];
component.externalReferences = this.addExternalReferences(pkg);

if (this.options && this.options.includeLicenseData) {
component.licenses = this.getLicenses(pkg);
} else {
delete component.licenses;
}

if (component.externalReferences === undefined || component.externalReferences.length === 0) {
delete component.externalReferences;
}

this.processHashes(pkg, component);
this.processHashes(pkg, component);
}

if (list[component.purl]) return; //remove cycles
list[component.purl] = component;
Expand Down Expand Up @@ -228,7 +232,11 @@ export class CycloneDXSbomCreator {
return externalReferences;
}

private pushURLToExternalReferences(typeOfURL: string, url: string, externalReferences: Array<ExternalReference>) {
private pushURLToExternalReferences(
typeOfURL: string,
url: string,
externalReferences: Array<ExternalReference>,
): void {
try {
const uri = new URL(url);
externalReferences.push({ reference: { '@type': typeOfURL, url: uri.toString() } });
Expand Down
1 change: 1 addition & 0 deletions src/CycloneDX/Options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@ export interface Options {
includeBomSerialNumber?: boolean;
includeLicenseData?: boolean;
includeLicenseText?: boolean;
spartan?: boolean;
}
28 changes: 14 additions & 14 deletions src/CycloneDX/Types/Component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,23 +13,23 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ExternalReference } from "./ExternalReference";
import { Hash } from "./Hash";
import { ExternalReference } from './ExternalReference';
import { Hash } from './Hash';

export interface Component {
'@type': string,
'@bom-ref': string,
group: string,
name: string,
version: string,
description: Object,
hashes?: Array<Hash>,
licenses?: Array<any>,
purl: string,
externalReferences?: Array<ExternalReference>
'@type': string;
'@bom-ref': string;
group: string;
name: string;
version: string;
description?: Object;
hashes?: Array<Hash>;
licenses?: Array<any>;
purl: string;
externalReferences?: Array<ExternalReference>;
}

export interface GenericDescription {
'#cdata': string,
'@content-type'?: string
'#cdata': string;
'@content-type'?: string;
}
5 changes: 3 additions & 2 deletions src/Munchers/NpmList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,12 @@ export class NpmList implements Muncher {
return fs.existsSync(nodeModulesPath);
}

// TODO: There is a 1 component discrepency in what gets identified by our installed deps implementation
// and what gets identified by the iq server being passed the sbom, gotta figure out what that is and why...
public async getSbomFromCommand(): Promise<string> {
const sbomCreator = new CycloneDXSbomCreator(process.cwd(), {
devDependencies: this.devDependencies,
includeLicenseData: false,
includeBomSerialNumber: true,
spartan: true,
});

const pkgInfo = await sbomCreator.getPackageInfoFromReadInstalled();
Expand All @@ -53,6 +52,8 @@ export class NpmList implements Muncher {
public async getInstalledDeps(): Promise<Array<Coordinates>> {
const sbomCreator = new CycloneDXSbomCreator(process.cwd(), {
devDependencies: this.devDependencies,
includeLicenseData: false,
includeBomSerialNumber: true,
});

const data = await sbomCreator.getPackageInfoFromReadInstalled();
Expand Down
16 changes: 9 additions & 7 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@
* limitations under the License.
*/
import yargs from 'yargs';
import { Argv } from 'yargs';
import { Application } from './Application/Application';
import { AppConfig } from './Config/AppConfig';
import { OssIndexServerConfig } from './Config/OssIndexServerConfig';
import {Argv} from 'yargs';
import {Application} from './Application/Application';
import {AppConfig} from './Config/AppConfig';
import {OssIndexServerConfig} from './Config/OssIndexServerConfig';

// TODO: Flesh out the remaining set of args that NEED to be moved over, look at them with a fine toothed comb and lots of skepticism
const normalizeHostAddress = (address: string) => {
Expand Down Expand Up @@ -140,7 +140,8 @@ let argv = yargs
type: 'boolean',
demandOption: false,
},
});
})
.command('sbom', 'Output the purl only CycloneDx sbom to std_out');
}).argv;

if (argv) {
Expand Down Expand Up @@ -178,8 +179,9 @@ if (argv) {
'Attempted to clear cache but no config file present, run `auditjs config` to set a cache location.',
);
}
} else if (argv._[0] == 'iq' || argv._[0] == 'ossi') {
let silence = argv.json || argv.quiet || argv.xml ? true : false;
} else if (argv._[0] == 'iq' || argv._[0] == 'ossi' || argv._[0] == 'sbom') {
// silence all output if quiet or if sending file to std_out
let silence = argv.json || argv.quiet || argv.xml || argv._[0] == 'sbom' ? true : false;
let artie = argv.artie ? true : false;
let allen = argv.allen ? true : false;
let bower = argv.bower ? true : false;
Expand Down

0 comments on commit 9bf2ae0

Please sign in to comment.