Skip to content

Commit

Permalink
feat: add SARIF support (CFG-1993)
Browse files Browse the repository at this point in the history
  • Loading branch information
YairZ101 committed Jul 20, 2022
1 parent 859be69 commit 622c8f4
Show file tree
Hide file tree
Showing 5 changed files with 432 additions and 12 deletions.
56 changes: 45 additions & 11 deletions src/lib/iac/test/v2/output.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { formatSnykIacTestTestData } from '../../../formatters/iac-output';
import { jsonStringifyLargeObject } from '../../../json';
import { IacOrgSettings } from '../../../../cli/commands/test/iac/local-execution/types';
import { SnykIacTestError } from './errors';
import { convertEngineToSarifResults } from './sarif';

export function buildOutput({
scanResult,
Expand All @@ -35,7 +36,40 @@ export function buildOutput({
testSpinner?.stop();
}

const response = buildTextOutput({ scanResult, projectName, orgSettings });
const { responseData, jsonData, sarifData } = buildTestCommandResultData({
scanResult,
projectName,
orgSettings,
options,
});

if (options.json || options.sarif) {
return TestCommandResult.createJsonTestCommandResult(
responseData,
jsonData,
sarifData,
);
}

return TestCommandResult.createHumanReadableTestCommandResult(
responseData,
jsonData,
sarifData,
);
}

function buildTestCommandResultData({
scanResult,
projectName,
orgSettings,
options,
}: {
scanResult: SnykIacTestOutput;
projectName: string;
orgSettings: IacOrgSettings;
options: any;
}) {
let responseData = '';

const jsonData = jsonStringifyLargeObject(
convertEngineToJsonResults({
Expand All @@ -45,19 +79,19 @@ export function buildOutput({
}),
);

const sarifData = jsonStringifyLargeObject(
convertEngineToSarifResults(scanResult),
);

if (options.json) {
return TestCommandResult.createJsonTestCommandResult(
jsonData,
jsonData,
'',
);
responseData = jsonData;
} else if (options.sarif) {
responseData = sarifData;
} else {
responseData = buildTextOutput({ scanResult, projectName, orgSettings });
}

return TestCommandResult.createHumanReadableTestCommandResult(
response,
jsonData,
'',
);
return { responseData, jsonData, sarifData };
}

const SEPARATOR = '\n-------------------------------------------------------\n';
Expand Down
180 changes: 180 additions & 0 deletions src/lib/iac/test/v2/sarif.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
import { pathToFileURL } from 'url';
import { marked } from 'marked';
import * as sarif from 'sarif';
import * as path from 'path';
import * as upperFirst from 'lodash.upperfirst';
import * as camelCase from 'lodash.camelcase';

import { getVersion } from '../../../version';
import { Results, SnykIacTestOutput } from './scan/results';
import { getIssueLevel } from '../../../formatters/sarif-output';
import { getRepositoryRoot } from '../../git';

// Used to reference the base path in results.
const PROJECT_ROOT_KEY = 'PROJECTROOT';

export function convertEngineToSarifResults(
scanResult: SnykIacTestOutput,
): sarif.Log {
let repoRoot;
try {
repoRoot = getRepositoryRoot() + '/';
} catch {
repoRoot = path.join(process.cwd(), '/'); // the slash at the end is required, otherwise the artifactLocation.uri starts with a slash
}
const tool: sarif.Tool = {
driver: {
name: 'Snyk IaC',
fullName: 'Snyk Infrastructure as Code',
version: getVersion(),
informationUri:
'https://docs.snyk.io/products/snyk-infrastructure-as-code',
rules: extractReportingDescriptor(scanResult.results),
},
};

return {
$schema:
'https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json',
version: '2.1.0',
runs: [
{
// https://docs.oasis-open.org/sarif/sarif/v2.1.0/os/sarif-v2.1.0-os.html#_Toc34317498
originalUriBaseIds: {
[PROJECT_ROOT_KEY]: {
uri: pathToFileURL(repoRoot).href,
description: {
text: 'The root directory for all project files.',
},
},
},

tool,
automationDetails: {
id: 'snyk-iac',
},
results: mapSnykIacTestResultsToSarifResults(scanResult.results),
},
],
};
}

function extractReportingDescriptor(
results: Results | undefined,
): sarif.ReportingDescriptor[] {
const rules: Record<string, sarif.ReportingDescriptor> = {};

if (!results?.vulnerabilities) {
return Object.values(rules);
}

results.vulnerabilities.forEach((vulnerability) => {
if (rules[vulnerability.rule.id]) {
return;
}

const tags = ['security']; // switch to rules.labels once `snyk-iac-test` includes this info

rules[vulnerability.rule.id] = {
id: vulnerability.rule.id,
name: upperFirst(camelCase(vulnerability.rule.title)).replace(/ /g, ''),
shortDescription: {
text: `${upperFirst(vulnerability.severity)} severity - ${
vulnerability.rule.title
}`,
},
fullDescription: {
text: vulnerability.rule.description,
},
help: {
text: renderMarkdown(vulnerability.remediation),
markdown: vulnerability.remediation,
},
defaultConfiguration: {
level: getIssueLevel(vulnerability.severity),
},
properties: {
tags,
problem: {
severity: vulnerability.severity,
},
},
helpUri: `https://snyk.io/security-rules/${vulnerability.rule.id}`,
};
});
return Object.values(rules);
}

function renderMarkdown(markdown: string) {
const renderer = {
em(text) {
return text;
},
strong(text) {
return text;
},
link(text) {
return text;
},
blockquote(quote) {
return quote;
},
list(body) {
return body;
},
listitem(text) {
return text;
},
paragraph(text) {
return text;
},
codespan(text) {
return text;
},
code(code) {
return code;
},
heading(text) {
return `${text}\n`;
},
};

marked.use({ renderer });
return marked.parse(markdown);
}

function mapSnykIacTestResultsToSarifResults(
results: Results | undefined,
): sarif.Result[] {
const result: sarif.Result[] = [];

if (!results?.vulnerabilities) {
return result;
}

results.vulnerabilities.forEach((vulnerability) => {
result.push({
ruleId: vulnerability.rule.id,
message: {
text: `This line contains a potential ${vulnerability.severity} severity misconfiguration`,
},
locations: [
{
physicalLocation: {
artifactLocation: {
uri: vulnerability.resource.file,
uriBaseId: PROJECT_ROOT_KEY,
},
// We exclude the `region` key when the line number is missing or -1.
// https://docs.oasis-open.org/sarif/sarif/v2.0/csprd02/sarif-v2.0-csprd02.html#_Toc10127873
region: {
startLine: vulnerability.resource.line,
},
},
},
],
});
});

return result;
}
14 changes: 13 additions & 1 deletion test/jest/unit/cli/commands/test/iac/v2/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,5 +97,17 @@ describe('test', () => {
expect(result).toContain(`"ok": false`);
});

it.skip('with `--sarif` flag', async () => {});
it('with `--sarif` flag', async () => {
const result = (
await test(['path/to/test'], {
...defaultOptions,
sarif: true,
})
).getSarifResult();

expect(isValidJSONString(result)).toBe(true);
expect(result).toContain(
`"$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json"`,
);
});
});

0 comments on commit 622c8f4

Please sign in to comment.