Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: disable sentry and amplitude for fedramp instance #446

Merged
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Snyk Changelog

## [2.5.1]

### Changed

- Disable analytics and error reporting for snykgov.io domain

## [2.5.0]

### Changed
Expand Down
6 changes: 4 additions & 2 deletions src/main/kotlin/io/snyk/plugin/cli/ConsoleCommandRunner.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import io.snyk.plugin.getWaitForResultsTimeout
import io.snyk.plugin.pluginSettings
import io.snyk.plugin.ui.SnykBalloonNotificationHelper
import snyk.common.getEndpointUrl
import snyk.common.isFedramp
import snyk.common.isOauth
import snyk.errorHandler.SentryErrorReporter
import snyk.pluginInfo
Expand Down Expand Up @@ -95,7 +96,8 @@ open class ConsoleCommandRunner {
val oauthEnvVar = "INTERNAL_OAUTH_TOKEN_STORAGE"
val snykTokenEnvVar = "SNYK_TOKEN"

val oauthEnabled = URI(endpoint).isOauth()
val endpointURI = URI(endpoint)
val oauthEnabled = endpointURI.isOauth()
if (oauthEnabled) {
commandLine.environment[oauthEnabledEnvVar] = "1"
commandLine.environment.remove(snykTokenEnvVar)
Expand All @@ -114,7 +116,7 @@ open class ConsoleCommandRunner {

commandLine.environment["SNYK_API"] = endpoint

if (!pluginSettings().usageAnalyticsEnabled) {
if (!pluginSettings().usageAnalyticsEnabled || endpointURI.isFedramp()) {
commandLine.environment["SNYK_CFG_DISABLE_ANALYTICS"] = "1"
}

Expand Down
27 changes: 15 additions & 12 deletions src/main/kotlin/io/snyk/plugin/services/SnykAnalyticsService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import snyk.analytics.QuickFixIsDisplayed
import snyk.analytics.QuickFixIsTriggered
import snyk.analytics.WelcomeIsViewed
import snyk.common.SnykError
import snyk.common.isFedramp
import snyk.container.ContainerResult
import snyk.iac.IacResult
import snyk.oss.OssResult
Expand Down Expand Up @@ -145,9 +146,11 @@ class SnykAnalyticsService : Disposable {
}

fun obtainUserId(token: String?): String {
if (!settings.usageAnalyticsEnabled) {
if (!settings.usageAnalyticsEnabled || isFedramp()) {
log.warn("Token is null or empty, or analytics disabled. User public id will not be obtained.")
return ""
}

if (token.isNullOrBlank()) {
log.warn("Token is null or empty, user public id will not be obtained.")
return ""
Expand All @@ -161,7 +164,7 @@ class SnykAnalyticsService : Disposable {
}

fun identify() {
if (!settings.usageAnalyticsEnabled || userId.isBlank()) {
if (!settings.usageAnalyticsEnabled || isFedramp() || userId.isBlank()) {
return
}

Expand All @@ -171,15 +174,15 @@ class SnykAnalyticsService : Disposable {
}

fun logWelcomeIsViewed(event: WelcomeIsViewed) {
if (!settings.usageAnalyticsEnabled) return
if (!settings.usageAnalyticsEnabled || isFedramp()) return

catchAll(log, "welcomeIsViewed") {
itly.logWelcomeIsViewed(userId, event)
}
}

fun logAnalysisIsTriggered(event: AnalysisIsTriggered) {
if (!settings.usageAnalyticsEnabled || userId.isBlank()) {
if (!settings.usageAnalyticsEnabled || isFedramp() || userId.isBlank()) {
return
}

Expand All @@ -189,7 +192,7 @@ class SnykAnalyticsService : Disposable {
}

private fun logAnalysisIsReady(event: AnalysisIsReady) {
if (!settings.usageAnalyticsEnabled || userId.isBlank()) {
if (!settings.usageAnalyticsEnabled || isFedramp() || userId.isBlank()) {
return
}

Expand All @@ -199,7 +202,7 @@ class SnykAnalyticsService : Disposable {
}

fun logIssueInTreeIsClicked(event: IssueInTreeIsClicked) {
if (!settings.usageAnalyticsEnabled || userId.isBlank()) {
if (!settings.usageAnalyticsEnabled || isFedramp() || userId.isBlank()) {
return
}

Expand All @@ -209,7 +212,7 @@ class SnykAnalyticsService : Disposable {
}

fun logHealthScoreIsClicked(event: HealthScoreIsClicked) {
if (!settings.usageAnalyticsEnabled || userId.isBlank()) {
if (!settings.usageAnalyticsEnabled || isFedramp() || userId.isBlank()) {
return
}

Expand All @@ -219,39 +222,39 @@ class SnykAnalyticsService : Disposable {
}

fun logPluginIsInstalled(event: PluginIsInstalled) {
if (!settings.usageAnalyticsEnabled) return
if (!settings.usageAnalyticsEnabled || isFedramp()) return

catchAll(log, "pluginIsInstalled") {
itly.logPluginIsInstalled(userId, event)
}
}

fun logPluginIsUninstalled(event: PluginIsUninstalled) {
if (!settings.usageAnalyticsEnabled) return
if (!settings.usageAnalyticsEnabled || isFedramp()) return

catchAll(log, "pluginIsUninstalled") {
itly.logPluginIsUninstalled(userId, event)
}
}

fun logAuthenticateButtonIsClicked(event: AuthenticateButtonIsClicked) {
if (!settings.usageAnalyticsEnabled) return
if (!settings.usageAnalyticsEnabled || isFedramp()) return

catchAll(log, "authenticateButtonIsClicked") {
itly.logAuthenticateButtonIsClicked(userId, event)
}
}

fun logQuickFixIsDisplayed(event: QuickFixIsDisplayed) {
if (!settings.usageAnalyticsEnabled) return
if (!settings.usageAnalyticsEnabled || isFedramp()) return

catchAll(log, "quickFixIsDisplayed") {
itly.logQuickFixIsDisplayed(userId, event)
}
}

fun logQuickFixIsTriggered(event: QuickFixIsTriggered) {
if (!settings.usageAnalyticsEnabled) return
if (!settings.usageAnalyticsEnabled || isFedramp()) return

catchAll(log, "quickFixIsTriggered") {
itly.logQuickFixIsTriggered(userId, event)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ package snyk.amplitude
import com.intellij.openapi.Disposable
import com.intellij.openapi.components.Service
import com.intellij.openapi.diagnostic.logger
import io.snyk.plugin.pluginSettings
import org.jetbrains.annotations.TestOnly
import snyk.amplitude.api.AmplitudeExperimentApiClient
import snyk.amplitude.api.AmplitudeExperimentApiClient.Defaults.FALLBACK_VARIANT
import snyk.amplitude.api.ExperimentUser
import snyk.amplitude.api.Variant
import snyk.common.isFedramp
import java.io.IOException
import java.util.Properties
import java.util.concurrent.ConcurrentHashMap
Expand All @@ -28,7 +30,12 @@ class AmplitudeExperimentService : Disposable {
val prop = Properties()
prop.load(javaClass.classLoader.getResourceAsStream("application.properties"))
val apiKey = prop.getProperty("amplitude.experiment.api-key") ?: ""
apiClient = AmplitudeExperimentApiClient.create(apiKey = apiKey)
val settings = pluginSettings()
if (settings.usageAnalyticsEnabled && !isFedramp()) {
apiClient = AmplitudeExperimentApiClient.create(apiKey = apiKey)
} else {
LOG.debug("Amplitude experiment disabled.")
}
} catch (e: IllegalArgumentException) {
LOG.warn("Property file contains a malformed Unicode escape sequence", e)
} catch (e: IOException) {
Expand Down
40 changes: 32 additions & 8 deletions src/main/kotlin/snyk/common/CustomEndpoints.kt
Original file line number Diff line number Diff line change
Expand Up @@ -84,26 +84,50 @@ internal fun resolveCustomEndpoint(endpointUrl: String?): String {
}

fun URI.isSnykTenant() =
isSnykDomain() && (host.startsWith("app.") || host == "snyk.io" || isDev()) && path.endsWith("/api")
isSnykDomain() &&
path.lowercase().endsWith("/api") &&
(
host.lowercase().startsWith("app.") ||
host.lowercase() == "snyk.io" ||
isDev()
)

fun URI.isSnykApi() = isSnykDomain() && (host.startsWith("api.") || path.endsWith("/api"))
fun URI.isSnykApi() = isSnykDomain() && (host.lowercase().startsWith("api.") || path.lowercase().endsWith("/api"))

fun URI.toSnykAPIv1(): URI {
val host = host
val host = host.lowercase()
.replaceFirst("app.", "api.")
.replaceFirst("deeproxy.", "api.")
.prefixIfNot("api.")
.prefixIfNot(
"api."
)

return URI(scheme, host, "/v1/", null)
}

fun URI.isSnykDomain() = host != null && (host.endsWith("snyk.io") || host.endsWith("snykgov.io"))
fun URI.isSnykDomain() = host != null &&
(
Fixed Show fixed Hide fixed
host.lowercase().endsWith(".snyk.io") ||
host.lowercase() == "snyk.io" ||
host.lowercase().endsWith(".snykgov.io")
)

fun URI.isDeeproxy() = isSnykDomain() && host.startsWith("deeproxy.")
fun URI.isDeeproxy() = isSnykDomain() && host.lowercase().startsWith("deeproxy.")

fun URI.isOauth() = host != null && host.endsWith(".snykgov.io")
fun URI.isSnykGov() = host != null && host.lowercase().endsWith(".snykgov.io")

fun URI.isDev() = isSnykDomain() && host.startsWith("dev.")
fun URI.isOauth() = isSnykGov()

fun URI.isDev() = isSnykDomain() && host.lowercase().startsWith("dev.")

fun URI.isFedramp() = isSnykGov()

fun isFedramp(): Boolean {
val settings = pluginSettings()
return settings.customEndpointUrl
?.let { URI(it) }
?.isFedramp() ?: false
}

internal fun String.removeTrailingSlashesIfPresent(): String {
val candidate = this.replace(Regex("/+$"), "")
Expand Down
3 changes: 2 additions & 1 deletion src/main/kotlin/snyk/errorHandler/SentryErrorReporter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import io.sentry.protocol.SentryId
import io.sentry.protocol.SentryRuntime
import io.snyk.plugin.pluginSettings
import snyk.PropertyLoader
import snyk.common.isFedramp
import snyk.pluginInfo

object SentryErrorReporter {
Expand Down Expand Up @@ -108,7 +109,7 @@ object SentryErrorReporter {
if (ApplicationManager.getApplication().isUnitTestMode) return SentryId.EMPTY_ID

val settings = pluginSettings()
return if (settings.crashReportingEnabled) {
return if (settings.crashReportingEnabled && !isFedramp()) {
val sentryId = Sentry.captureException(throwable)
LOG.info("Sentry event reported: $sentryId")
sentryId
Expand Down
15 changes: 15 additions & 0 deletions src/test/kotlin/io/snyk/plugin/cli/ConsoleCommandRunnerTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,21 @@ class ConsoleCommandRunnerTest : LightPlatformTestCase() {
}
}

fun testSetupCliEnvironmentVariablesWithFedrampCustomEndpoint() {
val oldEndpoint = pluginSettings().customEndpointUrl
try {
val generalCommandLine = GeneralCommandLine("")
val expectedEndpoint = "https://api.fedramp.snykgov.io/"
pluginSettings().customEndpointUrl = expectedEndpoint

ConsoleCommandRunner().setupCliEnvironmentVariables(generalCommandLine, "")

assertEquals("1", generalCommandLine.environment["SNYK_CFG_DISABLE_ANALYTICS"])
} finally {
pluginSettings().customEndpointUrl = oldEndpoint
}
}

fun testSetupCliEnvironmentVariablesWithOAuthEndpoint() {
val oldEndpoint = pluginSettings().customEndpointUrl
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@ class AmplitudeExperimentServiceTest {
unmockkAll()
mockkStatic("io.snyk.plugin.UtilsKt")
every { pluginSettings() } returns SnykApplicationSettingsStateService()
cut = AmplitudeExperimentService()
cut.setApiClient(amplitudeApiClientMock)
user = ExperimentUser("testUser")
}

Expand All @@ -37,8 +35,21 @@ class AmplitudeExperimentServiceTest {
fun `fetch should call amplitude for data with the userId`() {
every { amplitudeApiClientMock.allVariants(any()) } returns emptyMap()

cut = AmplitudeExperimentService()
cut.setApiClient(amplitudeApiClientMock)
cut.fetch(user)

verify(exactly = 1) { amplitudeApiClientMock.allVariants(user) }
}

@Test
fun `fetch should not call amplitude for data with the userId when fedramp`() {
every { amplitudeApiClientMock.allVariants(any()) } returns emptyMap()
pluginSettings().customEndpointUrl = "https://api.fedramp.snykgov.io"
cut = AmplitudeExperimentService()

cut.fetch(user)

verify(exactly = 0) { amplitudeApiClientMock.allVariants(user) }
}
}
13 changes: 12 additions & 1 deletion src/test/kotlin/snyk/common/CustomEndpointsTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ class CustomEndpointsTest {

@Test
fun `isSnykAPI false for api subdomain and not snyk domain`() {
val uri = URI("https://api.NOTSNYK.io")
val uri = URI("https://api.notsnyk.io")
assertFalse(uri.isSnykApi())
}

Expand Down Expand Up @@ -163,4 +163,15 @@ class CustomEndpointsTest {
assertTrue(uri.isOauth())
}

@Test
fun `isFedramp true for the right URI`() {
val uri = URI("https://app.fedramp.snykgov.io")
assertTrue(uri.isFedramp())
}

@Test
fun `isFedramp false for the right URI`() {
val uri = URI("https://app.fedddramp.snykagov.io")
assertFalse(uri.isFedramp())
}
}
12 changes: 12 additions & 0 deletions src/test/kotlin/snyk/errorHandler/SentryErrorReporterTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,18 @@ class SentryErrorReporterTest {
verify(exactly = 1) { Sentry.captureException(any()) }
}

@Test
fun `captureException should not send exceptions to Sentry when crashReportingEnabled is true and fedramp`() {
val settings = mockPluginInformation()
setUnitTesting(false)
settings.crashReportingEnabled = true
settings.customEndpointUrl = "https://api.fedramp.snykgov.io"

SentryErrorReporter.captureException(RuntimeException("test"))

verify(exactly = 0) { Sentry.captureException(any()) }
}

@Test
fun `captureException should not send exceptions to Sentry when crashReportingEnabled is false`() {
val settings = mockPluginInformation()
Expand Down
Loading