From 5fa297cc9a590943bce57f2d57f35c234349fc04 Mon Sep 17 00:00:00 2001 From: JSON Date: Thu, 31 Aug 2023 11:48:00 +0100 Subject: [PATCH] feat: disable amplitude and sentry when using fedramp endpoints (#372) * feat: disable sending amplitude and sentry events when using fedramp endpoints --- CHANGELOG.md | 12 +++++ src/snyk/common/analytics/itly.ts | 9 ++-- .../common/configuration/configuration.ts | 28 ++++++++++ src/snyk/common/error/errorReporter.ts | 2 +- src/snyk/extension.ts | 1 + src/test/integration/analytics.test.ts | 14 +++-- src/test/unit/common/analytics/itly.test.ts | 52 +++++++++++++++++++ .../unit/common/error/errorReporter.test.ts | 10 +++- 8 files changed, 120 insertions(+), 8 deletions(-) create mode 100644 src/test/unit/common/analytics/itly.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index e392ec673..218b4265e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,16 +1,27 @@ # Snyk Security - Code and Open Source Dependencies Changelog +## [1.21.5] + +### Added + +- Fedrammp endpoints will not send Sentry/Amplitude events + ## [1.21.4] ### Changed + - Use Language Server to retrieve vulnerability count for HTML files ## [1.21.1] + ### Fixed + - Snyk Learn links ## [1.21.0] + ### Fixed + - Plugin Initialization ## [1.20.3] @@ -28,6 +39,7 @@ ## [1.18.3] ### Added + - Added support for OAuth2 authentication - Snyk Learn: now uses language server to retrieve lessons diff --git a/src/snyk/common/analytics/itly.ts b/src/snyk/common/analytics/itly.ts index befd3ea65..2743addec 100644 --- a/src/snyk/common/analytics/itly.ts +++ b/src/snyk/common/analytics/itly.ts @@ -1,13 +1,13 @@ import SegmentPlugin from '@itly/plugin-segment-node'; import itly, { AnalysisIsReadyProperties, - AnalysisIsTriggeredProperties as _AnalysisIsTriggeredProperties, FalsePositiveIsSubmittedProperties, IssueHoverIsDisplayedProperties, IssueInTreeIsClickedProperties, - QuickFixIsDisplayedProperties as _QuickFixIsDisplayedProperties, ScanModeIsSelectedProperties, TrackOptions, + AnalysisIsTriggeredProperties as _AnalysisIsTriggeredProperties, + QuickFixIsDisplayedProperties as _QuickFixIsDisplayedProperties, } from '../../../ampli'; import { Configuration } from '../configuration/configuration'; import { SnykConfiguration } from '../configuration/snykConfiguration'; @@ -67,6 +67,7 @@ export class Iteratively implements IAnalytics { private readonly user: User, private logger: ILog, private shouldReportEvents: boolean, + private isFedramp: boolean, private isDevelopment: boolean, private snykConfiguration?: SnykConfiguration, ) {} @@ -77,9 +78,11 @@ export class Iteratively implements IAnalytics { } load(): Iteratively | null { - if (!this.shouldReportEvents) { + if (!this.shouldReportEvents || this.isFedramp) { + this.logger.debug(`Analytics are disabled. No analytics will be collected.`); return null; } + this.logger.debug(`Analytics are enabled. Analytics will be collected.`); const segmentWriteKey = this.snykConfiguration?.segmentWriteKey; if (!segmentWriteKey) { diff --git a/src/snyk/common/configuration/configuration.ts b/src/snyk/common/configuration/configuration.ts index 62db63731..5942bc144 100644 --- a/src/snyk/common/configuration/configuration.ts +++ b/src/snyk/common/configuration/configuration.ts @@ -98,6 +98,8 @@ export interface IConfiguration { getInsecure(): boolean; + isFedramp: boolean; + severityFilter: SeverityFilter; scanningMode: string | undefined; @@ -109,6 +111,8 @@ export interface IConfiguration { getTrustedFolders(): string[]; setTrustedFolders(trustedFolders: string[]): Promise; + + setEndpoint(endpoint: string): Promise; } export class Configuration implements IConfiguration { @@ -180,6 +184,30 @@ export class Configuration implements IConfiguration { return this.defaultAuthHost; } + async setEndpoint(endpoint: string): Promise { + await this.workspace.updateConfiguration( + CONFIGURATION_IDENTIFIER, + this.getConfigName(ADVANCED_CUSTOM_ENDPOINT), + endpoint.toString(), + true, + ); + } + + get isFedramp(): boolean { + if (!this.customEndpoint) return false; + + // FEDRAMP URL e.g. https://api.fedramp.snykgov.io + const endpoint = new URL(this.customEndpoint); + + // hostname validation + const hostnameParts = endpoint.hostname.split('.'); + if (hostnameParts.length < 3) return false; + + const isFedrampInstance = hostnameParts[1].includes('fedramp'); + const isFedrampDomain = hostnameParts[2].includes('snykgov') && hostnameParts[3].includes('io'); + return isFedrampDomain && isFedrampInstance; + } + get snykOssApiEndpoint(): string { if (this.customEndpoint) { return this.customEndpoint; // E.g. https://app.eu.snyk.io/api diff --git a/src/snyk/common/error/errorReporter.ts b/src/snyk/common/error/errorReporter.ts index b445e7f97..906d3e2ad 100644 --- a/src/snyk/common/error/errorReporter.ts +++ b/src/snyk/common/error/errorReporter.ts @@ -66,7 +66,7 @@ export class ErrorReporter { beforeSend(event) { // drop reporting, if user doesn't want to report events here // https://github.com/getsentry/sentry-javascript/issues/2039 - if (!userConfig.shouldReportErrors) { + if (!userConfig.shouldReportErrors || userConfig.isFedramp) { return null; } diff --git a/src/snyk/extension.ts b/src/snyk/extension.ts index 934091ce8..aeae6683a 100644 --- a/src/snyk/extension.ts +++ b/src/snyk/extension.ts @@ -115,6 +115,7 @@ class SnykExtension extends SnykLib implements IExtension { this.user, Logger, configuration.shouldReportEvents, + configuration.isFedramp, configuration.isDevelopment, snykConfiguration, ); diff --git a/src/test/integration/analytics.test.ts b/src/test/integration/analytics.test.ts index bc2d64006..1975e4834 100644 --- a/src/test/integration/analytics.test.ts +++ b/src/test/integration/analytics.test.ts @@ -1,9 +1,9 @@ import { strictEqual } from 'assert'; -import { configuration } from '../../snyk/common/configuration/instance'; -import * as vscode from 'vscode'; -import { VSCODE_VIEW_CONTAINER_COMMAND } from '../../snyk/common/constants/commands'; import * as sinon from 'sinon'; +import * as vscode from 'vscode'; import itly from '../../ampli'; +import { configuration } from '../../snyk/common/configuration/instance'; +import { VSCODE_VIEW_CONTAINER_COMMAND } from '../../snyk/common/constants/commands'; suite('Analytics', () => { let welcomeIsViewed: sinon.SinonSpy; @@ -34,4 +34,12 @@ suite('Analytics', () => { strictEqual(welcomeIsViewed.notCalled, true); }); + + test('"Welcome Is Viewed" not tracked if using fedramp endpoint', async () => { + await configuration.setEndpoint('https://api.fedramp.snykgov.io'); + await vscode.commands.executeCommand('workbench.action.toggleSidebarVisibility'); + await vscode.commands.executeCommand(VSCODE_VIEW_CONTAINER_COMMAND); + + strictEqual(welcomeIsViewed.notCalled, true); + }); }); diff --git a/src/test/unit/common/analytics/itly.test.ts b/src/test/unit/common/analytics/itly.test.ts new file mode 100644 index 000000000..d49379162 --- /dev/null +++ b/src/test/unit/common/analytics/itly.test.ts @@ -0,0 +1,52 @@ +import { strictEqual } from 'assert'; +import { Iteratively } from '../../../../snyk/common/analytics/itly'; +import { SnykConfiguration } from '../../../../snyk/common/configuration/snykConfiguration'; +import { User } from '../../../../snyk/common/user'; +import { LoggerMock } from '../../mocks/logger.mock'; + +suite.only('Iteratively', () => { + const snykConfig = {} as SnykConfiguration; + const isDevelopment = false; + + suite('.load()', () => { + suite('when connecting to FEDRAMP endpoints', () => { + const isFedramp = true; + [true, false].forEach(shouldReportEvents => { + test(`Returns "null" when shouldReportEvents == ${shouldReportEvents}`, () => { + const iteratively = new Iteratively( + new User(), + new LoggerMock(), + shouldReportEvents, + isFedramp, + isDevelopment, + snykConfig, + ); + + const result = iteratively.load(); + + strictEqual(result, null); + }); + }); + }); + + suite('when connecting to non-FEDRAMP endpoints', () => { + const isFedramp = false; + + test('Returns "null" when shouldReportEvents == false', () => { + const iteratively = new Iteratively(new User(), new LoggerMock(), false, isFedramp, isDevelopment, snykConfig); + + const result = iteratively.load(); + + strictEqual(result, null); + }); + + test('Returns "Iteratively" when shouldReportEvents == true', () => { + const iteratively = new Iteratively(new User(), new LoggerMock(), true, isFedramp, isDevelopment, snykConfig); + + const result = iteratively.load(); + + strictEqual(result instanceof Iteratively, true); + }); + }); + }); +}); diff --git a/src/test/unit/common/error/errorReporter.test.ts b/src/test/unit/common/error/errorReporter.test.ts index 6e164b198..da56125e4 100644 --- a/src/test/unit/common/error/errorReporter.test.ts +++ b/src/test/unit/common/error/errorReporter.test.ts @@ -14,7 +14,7 @@ suite('ErrorReporter', () => { let configuration: IConfiguration; setup(async () => { - configuration = {} as IConfiguration; + configuration = { isFedramp: false } as IConfiguration; await ErrorReporter.init(configuration, snykConfig, '', envMock, new LoggerMock(), sentryTransport); }); @@ -59,4 +59,12 @@ suite('ErrorReporter', () => { strictEqual(testkit.reports().length, 0); }); + + test("Doesn't report error when isFedramp == true", () => { + configuration.isFedramp = true; + + ErrorReporter.capture(new Error()); + + strictEqual(testkit.reports().length, 0); + }); });