Skip to content

Commit

Permalink
chore: lay foundations for IaC ignores analytics
Browse files Browse the repository at this point in the history
Leave the actual data commented out while we ensure support for this
field is in place on the backend.
  • Loading branch information
Craig Furman committed Aug 6, 2021
1 parent ac10cdd commit eec20be
Show file tree
Hide file tree
Showing 4 changed files with 49 additions and 20 deletions.
8 changes: 7 additions & 1 deletion src/cli/commands/test/iac-local-execution/analytics.ts
@@ -1,7 +1,11 @@
import { FormattedResult } from './types';
import * as analytics from '../../../../lib/analytics';

export function addIacAnalytics(formattedResults: FormattedResult[]) {
export function addIacAnalytics(
formattedResults: FormattedResult[],
/* eslint-disable @typescript-eslint/no-unused-vars */
ignoredIssuesCount: number,
) {
let totalIssuesCount = 0;
const issuesByType: Record<string, object> = {};
const packageManagers = Array<string>();
Expand All @@ -22,6 +26,8 @@ export function addIacAnalytics(formattedResults: FormattedResult[]) {

analytics.add('packageManager', Array.from(new Set(packageManagers)));
analytics.add('iac-issues-count', totalIssuesCount);
// TODO enable once we have support for it in registry
// analytics.add('iac-ignored-issues-count', ignoredIssuesCount);
analytics.add('iac-type', issuesByType);
analytics.add('iac-metrics', performanceAnalyticsObject);
analytics.add('iac-test-count', formattedResults.length);
Expand Down
11 changes: 7 additions & 4 deletions src/cli/commands/test/iac-local-execution/index.ts
Expand Up @@ -69,10 +69,13 @@ export async function test(
iacOrgSettings.meta,
);

const filteredResults = filterIgnoredIssues(policy, formattedResults);
const { filteredIssues, ignoreCount } = filterIgnoredIssues(
policy,
formattedResults,
);

try {
await trackUsage(filteredResults);
await trackUsage(filteredIssues);
} catch (e) {
if (e instanceof TestLimitReachedError) {
throw e;
Expand All @@ -81,11 +84,11 @@ export async function test(
// run their tests by squashing the error.
}

addIacAnalytics(filteredResults);
addIacAnalytics(filteredIssues, ignoreCount);

// TODO: add support for proper typing of old TestResult interface.
return {
results: (filteredResults as unknown) as TestResult[],
results: (filteredIssues as unknown) as TestResult[],
// NOTE: No file or parsed file data should leave this function.
failures: isLocalFolder(pathToScan)
? failedFiles.map(removeFileContent)
Expand Down
15 changes: 10 additions & 5 deletions src/cli/commands/test/iac-local-execution/policy.ts
Expand Up @@ -4,13 +4,17 @@ import { Policy } from '../../../../lib/policy/find-and-load-policy';
export function filterIgnoredIssues(
policy: Policy | undefined,
results: FormattedResult[],
): FormattedResult[] {
) {
if (!policy) {
return results;
return { filteredIssues: results, ignoreCount: 0 };
}
return results
.map((res) => policy.filter(toIaCVulnAdapter(res)))
.map((vuln) => toFormattedResult(vuln));
const vulns = results.map((res) => policy.filter(toIaCVulnAdapter(res)));
const ignoreCount: number = vulns.reduce(
(totalIgnored, vuln) => totalIgnored + vuln.filtered.ignore.length,
0,
);
const filteredIssues = vulns.map((vuln) => toFormattedResult(vuln));
return { filteredIssues, ignoreCount };
}

type IacVulnAdapter = {
Expand All @@ -19,6 +23,7 @@ type IacVulnAdapter = {
from: string[];
}[];
originalResult: FormattedResult;
filtered?: { ignore: any[] };
};

// This is a total cop-out. The type I really want is AnnotatedIacIssue from
Expand Down
35 changes: 25 additions & 10 deletions test/jest/unit/iac-unit-tests/policy.spec.ts
Expand Up @@ -18,7 +18,11 @@ async function filterFixture(policyName: string) {
// assertions, deep-clone the original fixture.
const filtered = filterIgnoredIssues(policy, cloneDeep(fixture));

return { fixture: fixture, filtered: filtered };
return {
fixture: fixture,
filtered: filtered.filteredIssues,
ignoreCount: filtered.ignoreCount,
};
}

async function loadPolicy(policyName: string) {
Expand All @@ -44,28 +48,33 @@ function assertK8sPolicyPruned(

describe('filtering ignored issues', () => {
it('returns the original issues when policy is not loaded', async () => {
const { fixture, filtered } = await filterFixture('');
const { fixture, filtered, ignoreCount } = await filterFixture('');
expect(filtered).toEqual(fixture);
expect(ignoreCount).toEqual(0);
});

it('filters ignored issues when path=*', async () => {
const { fixture, filtered } = await filterFixture('policy-ignore-star.yml');
const { fixture, filtered, ignoreCount } = await filterFixture(
'policy-ignore-star.yml',
);
assertK8sPolicyPruned(fixture, filtered);
expect(ignoreCount).toEqual(1);
});

// This might seem paranoid, but given that our handling of resource paths is
// in a state of flux, e.g. to support multi-doc YAML properly, having some
// regression tests around each currently supported config type might be wise.
describe('filtering ignored issues by resource path', () => {
it('filters ignored issues when path is resource path (Kubernetes)', async () => {
const { fixture, filtered } = await filterFixture(
const { fixture, filtered, ignoreCount } = await filterFixture(
'policy-ignore-resource-path-kubernetes.yml',
);
assertK8sPolicyPruned(fixture, filtered);
expect(ignoreCount).toEqual(1);
});

it('filters ignored issues when path is resource path (CloudFormation)', async () => {
const { fixture, filtered } = await filterFixture(
const { fixture, filtered, ignoreCount } = await filterFixture(
'policy-ignore-resource-path-cloudformation.yml',
);
expect(filtered[0]).toEqual(fixture[0]);
Expand All @@ -74,45 +83,51 @@ describe('filtering ignored issues', () => {
const cfResults = filtered[1].result.cloudConfigResults;
expect(cfResults).toHaveLength(cfFixture.length - 1);
expect(cfResults.some((e) => e.id === 'SNYK-CC-TF-53')).toEqual(false);
expect(ignoreCount).toEqual(1);
});

it('filters ignored issues when path is resource path (Terraform)', async () => {
const { fixture, filtered } = await filterFixture(
const { fixture, filtered, ignoreCount } = await filterFixture(
'policy-ignore-resource-path-terraform.yml',
);
expect(filtered[1]).toEqual(fixture[1]);
expect(filtered[2]).toEqual(fixture[2]);
const tfFixture = fixture[0].result.cloudConfigResults;
const tfResults = filtered[0].result.cloudConfigResults;
expect(tfResults).toHaveLength(tfFixture.length - 1);
expect(ignoreCount).toEqual(1);
});
});

it('filters no issues when path is non-matching resource path', async () => {
const { fixture, filtered } = await filterFixture(
const { fixture, filtered, ignoreCount } = await filterFixture(
'policy-ignore-resource-path-non-matching.yml',
);
expect(filtered).toEqual(fixture);
expect(ignoreCount).toEqual(0);
});

it('filters ignored issues when path is file path', async () => {
const { fixture, filtered } = await filterFixture(
const { fixture, filtered, ignoreCount } = await filterFixture(
'policy-ignore-file-path.yml',
);
assertK8sPolicyPruned(fixture, filtered);
expect(ignoreCount).toEqual(1);
});

it('filters no issues when path is non-matching file path', async () => {
const { fixture, filtered } = await filterFixture(
const { fixture, filtered, ignoreCount } = await filterFixture(
'policy-ignore-file-path-non-matching.yml',
);
expect(filtered).toEqual(fixture);
expect(ignoreCount).toEqual(0);
});

it('filters no issues when path is non-matching file path but matching resource path', async () => {
const { fixture, filtered } = await filterFixture(
const { fixture, filtered, ignoreCount } = await filterFixture(
'policy-ignore-non-matching-file-matching-resource.yml',
);
expect(filtered).toEqual(fixture);
expect(ignoreCount).toEqual(0);
});
});

0 comments on commit eec20be

Please sign in to comment.