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

feat: Enabling SCLE [NEBULA-1371] #447

Merged
merged 12 commits into from
Sep 11, 2023
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.2]

### Added

- Snyk Code support for on-prem solutions (Snyk Code Local Engine)

## [2.5.1]

### Changed
Expand Down
8 changes: 7 additions & 1 deletion src/main/kotlin/io/snyk/plugin/Utils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import com.intellij.psi.PsiManager
import com.intellij.util.Alarm
import com.intellij.util.FileContentUtil
import com.intellij.util.messages.Topic
import io.snyk.plugin.net.ClientException
import io.snyk.plugin.services.SnykAnalyticsService
import io.snyk.plugin.services.SnykApiService
import io.snyk.plugin.services.SnykApplicationSettingsStateService
Expand Down Expand Up @@ -193,7 +194,12 @@ fun startSastEnablementCheckLoop(parentDisposable: Disposable, onSuccess: () ->
lateinit var checkIfSastEnabled: () -> Unit
checkIfSastEnabled = {
if (settings.sastOnServerEnabled != true) {
settings.sastOnServerEnabled = getSnykApiService().getSastSettings()?.sastEnabled ?: false
settings.sastOnServerEnabled = try {
getSnykApiService().getSastSettings()?.sastEnabled ?: false
} catch (ignored: ClientException) {
false
}

if (settings.sastOnServerEnabled == true) {
onSuccess.invoke()
} else if (!alarm.isDisposed && currentAttempt < maxAttempts) {
Expand Down
16 changes: 15 additions & 1 deletion src/main/kotlin/io/snyk/plugin/net/CliConfigService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,24 @@ data class CliConfigSettings(
val reportFalsePositivesEnabled: Boolean
)

data class CliConfigSettingsError(
@SerializedName("userMessage")
val userMessage: String,

@SerializedName("code")
val code: Int?
)

/**
* SAST local code engine configuration.
*/
data class LocalCodeEngine(
@SerializedName("enabled")
val enabled: Boolean
val enabled: Boolean,

@SerializedName("url")
val url: String,

@SerializedName("allowCloudUpload")
val allowCloudUpload: Boolean
)
18 changes: 18 additions & 0 deletions src/main/kotlin/io/snyk/plugin/net/SnykApiClient.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package io.snyk.plugin.net

import com.google.gson.Gson
import com.intellij.openapi.diagnostic.logger
import io.ktor.http.HttpStatusCode
import retrofit2.Call
import retrofit2.Retrofit

Expand Down Expand Up @@ -29,11 +31,25 @@ class SnykApiClient(
log.debug("Executing request to $apiName")
val response = retrofitCall.execute()
if (!response.isSuccessful) {
if (response.code() == HttpStatusCode.UnprocessableEntity.value) {
val responseBodyString = response.errorBody()?.string()
val errorBody = Gson().fromJson<CliConfigSettingsError?>(
responseBodyString,
CliConfigSettingsError::class.java
)
if (errorBody?.userMessage?.isNotEmpty() == true) {
throw ClientException(errorBody.userMessage)
}
return null
}
log.warn("Failed to execute `$apiName` call: ${response.errorBody()?.string()}")
executeRequest(apiName, retrofitCall.clone(), retryCounter - 1)
} else {
response.body()
}
} catch (t: ClientException) {
// Consumers are expected to handle ClientException throws.
throw t
} catch (t: Throwable) {
log.warn("Failed to execute '$apiName' network request: ${t.message}", t)
executeRequest(apiName, retrofitCall.clone(), retryCounter - 1)
Expand All @@ -44,3 +60,5 @@ class SnykApiClient(
private val log = logger<SnykApiClient>()
}
}

class ClientException(userMessage: String) : RuntimeException(userMessage)
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,9 @@ class SnykApplicationSettingsStateService : PersistentStateComponent<SnykApplica
var containerScanEnabled: Boolean = true

var sastOnServerEnabled: Boolean? = null
var sastSettingsError: Boolean? = null
var localCodeEngineEnabled: Boolean? = null
var localCodeEngineUrl: String? = ""
var reportFalsePositivesEnabled: Boolean? = null
var usageAnalyticsEnabled = true
var crashReportingEnabled = true
Expand Down
24 changes: 10 additions & 14 deletions src/main/kotlin/io/snyk/plugin/services/SnykTaskQueueService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import io.snyk.plugin.isCliInstalled
import io.snyk.plugin.isContainerEnabled
import io.snyk.plugin.isIacEnabled
import io.snyk.plugin.isSnykCodeRunning
import io.snyk.plugin.net.ClientException
import io.snyk.plugin.pluginSettings
import io.snyk.plugin.snykcode.core.RunUtils
import io.snyk.plugin.ui.SnykBalloonNotifications
Expand Down Expand Up @@ -165,25 +166,20 @@ class SnykTaskQueueService(val project: Project) {
if (settings.token.isNullOrBlank()) {
return
}
val sastCliConfigSettings = getSnykApiService().getSastSettings()
val sastCliConfigSettings = try {
getSnykApiService().getSastSettings()
} catch (ignored: ClientException) {
null
}

settings.sastOnServerEnabled = sastCliConfigSettings?.sastEnabled
settings.localCodeEngineEnabled = sastCliConfigSettings?.localCodeEngine?.enabled
settings.localCodeEngineUrl = sastCliConfigSettings?.localCodeEngine?.url
settings.reportFalsePositivesEnabled = sastCliConfigSettings?.reportFalsePositivesEnabled
when (settings.sastOnServerEnabled) {
true -> {
if (settings.localCodeEngineEnabled == true) {
SnykBalloonNotifications.showSastForLocalCodeEngineMessage(project)
scanPublisher?.scanningSnykCodeError(
SnykError(
SnykBalloonNotifications.sastForLocalCodeEngineMessage, project.basePath ?: ""
)
)
settings.snykCodeSecurityIssuesScanEnable = false
settings.snykCodeQualityIssuesScanEnable = false
} else {
getSnykCode(project)?.scan()
scanPublisher?.scanningStarted()
}
getSnykCode(project)?.scan()
scanPublisher?.scanningStarted()
}

false -> {
Expand Down
13 changes: 0 additions & 13 deletions src/main/kotlin/io/snyk/plugin/ui/SnykBalloonNotifications.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@ object SnykBalloonNotifications {
private val logger = logger<SnykBalloonNotifications>()
private val alarm = Alarm()

const val sastForLocalCodeEngineMessage = "Snyk Code is configured to use a Local Code Engine instance." +
"This setup is not yet supported."
const val sastForOrgEnablementMessage = "Snyk Code is disabled by your organisation's configuration."
const val networkErrorAlertMessage = "Not able to connect to Snyk server."

Expand All @@ -46,17 +44,6 @@ object SnykBalloonNotifications {
notification.notify(project)
}

fun showSastForLocalCodeEngineMessage(project: Project): Notification {
return SnykBalloonNotificationHelper.showInfo(
sastForLocalCodeEngineMessage,
project,
NotificationAction.createSimpleExpiring("SNYK SETTINGS") {
ShowSettingsUtil.getInstance()
.showSettingsDialog(project, SnykProjectSettingsConfigurable::class.java)
}
)
}

fun showSastForOrgEnablement(project: Project): Notification {
val notification = SnykBalloonNotificationHelper.showInfo(
"$sastForOrgEnablementMessage To enable navigate to ",
Expand Down
8 changes: 4 additions & 4 deletions src/main/kotlin/io/snyk/plugin/ui/UIUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -96,12 +96,12 @@ fun insertTitleAndResizableTextIntoPanelColumns(

fun snykCodeAvailabilityPostfix(): String {
val sastOnServerEnabled = pluginSettings().sastOnServerEnabled
val localCodeEngineEnabled = pluginSettings().localCodeEngineEnabled
val sastSettingsError = pluginSettings().sastSettingsError
return when {
sastSettingsError == true -> " (Snyk Code settings misconfigured)"
sastOnServerEnabled == false -> " (disabled in Snyk.io)"
!isSnykCodeAvailable(pluginSettings().customEndpointUrl) -> " (disabled for endpoint)"
(sastOnServerEnabled == null && localCodeEngineEnabled == null) -> " (unreachable server settings)"
(sastOnServerEnabled != false && localCodeEngineEnabled == true) -> " (disabled due to Local Code Engine)"
sastOnServerEnabled != true -> " (disabled in Snyk.io)"
sastOnServerEnabled == null -> " (unreachable server settings)"
else -> ""
}
}
Expand Down
34 changes: 22 additions & 12 deletions src/main/kotlin/io/snyk/plugin/ui/settings/ScanTypesPanel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import io.snyk.plugin.getKubernetesImageCache
import io.snyk.plugin.getSnykApiService
import io.snyk.plugin.isContainerEnabled
import io.snyk.plugin.isIacEnabled
import io.snyk.plugin.net.CliConfigSettings
import io.snyk.plugin.net.ClientException
import io.snyk.plugin.pluginSettings
import io.snyk.plugin.snykcode.core.SnykCodeUtils
import io.snyk.plugin.startSastEnablementCheckLoop
Expand Down Expand Up @@ -219,25 +221,33 @@ class ScanTypesPanel(
return
}

var sastSettingsError: String = ""
val sastCliConfigSettings: CliConfigSettings? = try {
val sastSettings = getSnykApiService().getSastSettings()
settings.sastSettingsError = false
sastSettings
} catch (t: ClientException) {
settings.sastSettingsError = true
val defaultErrorMsg = "Your org's SAST settings are misconfigured."
val userMessage = if (t.message.isNullOrEmpty()) defaultErrorMsg else t.message!!
SnykBalloonNotificationHelper.showError(userMessage, null)
sastSettingsError = userMessage
null
}

settings.sastOnServerEnabled = sastCliConfigSettings?.sastEnabled
settings.localCodeEngineEnabled = sastCliConfigSettings?.localCodeEngine?.enabled
settings.localCodeEngineUrl = sastCliConfigSettings?.localCodeEngine?.url
val snykCodeAvailable = isSnykCodeAvailable(settings.customEndpointUrl)
showSnykCodeAlert(
if (snykCodeAvailable) "" else "Snyk Code only works in SAAS mode (i.e. no Custom Endpoint usage)"
sastSettingsError.ifEmpty { "" }
)
if (snykCodeAvailable) {
setSnykCodeComment(progressMessage = "Checking if Snyk Code enabled for organisation...") {
val sastCliConfigSettings = getSnykApiService().getSastSettings()
settings.sastOnServerEnabled = sastCliConfigSettings?.sastEnabled
settings.localCodeEngineEnabled = sastCliConfigSettings?.localCodeEngine?.enabled

when (settings.sastOnServerEnabled) {
true -> {
if (settings.localCodeEngineEnabled == true) {
showSnykCodeAlert(
message = "Snyk Code is configured to use a Local Code Engine instance." +
" This setup is not yet supported."
)
} else {
doShowFilesToUpload()
}
doShowFilesToUpload()
}

false -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import io.snyk.plugin.isOssRunning
import io.snyk.plugin.isScanRunning
import io.snyk.plugin.isSnykCodeRunning
import io.snyk.plugin.navigateToSource
import io.snyk.plugin.net.ClientException
import io.snyk.plugin.pluginSettings
import io.snyk.plugin.refreshAnnotationsForOpenFiles
import io.snyk.plugin.snykToolWindow
Expand Down Expand Up @@ -488,10 +489,13 @@ class SnykToolWindowPanel(val project: Project) : JPanel(), Disposable {

private fun enableCodeScanAccordingToServerSetting() {
pluginSettings().apply {
val sastSettings = getSnykApiService().getSastSettings()
val sastSettings = try {
getSnykApiService().getSastSettings()
} catch (ignored: ClientException) {
null
}
sastOnServerEnabled = sastSettings?.sastEnabled
localCodeEngineEnabled = sastSettings?.localCodeEngine?.enabled
val codeScanAllowed = sastOnServerEnabled == true && localCodeEngineEnabled != true
val codeScanAllowed = sastOnServerEnabled == true
snykCodeSecurityIssuesScanEnable = this.snykCodeSecurityIssuesScanEnable && codeScanAllowed
snykCodeQualityIssuesScanEnable = this.snykCodeQualityIssuesScanEnable && codeScanAllowed
}
Expand Down
14 changes: 11 additions & 3 deletions src/main/kotlin/snyk/common/CustomEndpoints.kt
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package snyk.common

import io.snyk.plugin.pluginSettings
import io.snyk.plugin.prefixIfNot
import io.snyk.plugin.suffixIfNot
import io.snyk.plugin.prefixIfNot
import java.net.URI
import java.net.URISyntaxException

Expand All @@ -12,6 +12,9 @@ fun toSnykCodeApiUrl(endpointUrl: String?): String {

val codeSubdomain = "deeproxy"
val snykCodeApiUrl = when {
uri.isLocalCodeEngine() ->
return pluginSettings().localCodeEngineUrl!!

uri.isDeeproxy() ->
endpoint

Expand All @@ -29,7 +32,10 @@ fun toSnykCodeApiUrl(endpointUrl: String?): String {
fun toSnykCodeSettingsUrl(endpointUrl: String?): String {
val endpoint = resolveCustomEndpoint(endpointUrl)
val uri = URI(endpoint)

val baseUrl = when {
uri.isLocalCodeEngine() -> return uri.toString()

uri.host == "snyk.io" ->
"https://app.snyk.io/"

Expand All @@ -50,7 +56,7 @@ fun toSnykCodeSettingsUrl(endpointUrl: String?): String {

fun needsSnykToken(endpoint: String): Boolean {
val uri = URI(endpoint)
return uri.isSnykApi() || uri.isSnykTenant() || uri.isDeeproxy()
return uri.isSnykApi() || uri.isSnykTenant() || uri.isDeeproxy() || uri.isLocalCodeEngine()
}

fun getEndpointUrl(): String {
Expand All @@ -67,7 +73,7 @@ fun getEndpointUrl(): String {
fun isSnykCodeAvailable(endpointUrl: String?): Boolean {
val endpoint = resolveCustomEndpoint(endpointUrl)
val uri = URI(endpoint)
return uri.isSnykTenant()
return uri.isSnykTenant() || uri.isLocalCodeEngine()
}

/**
Expand Down Expand Up @@ -129,6 +135,8 @@ fun isFedramp(): Boolean {
?.isFedramp() ?: false
}

fun URI.isLocalCodeEngine() = pluginSettings().localCodeEngineEnabled == true

internal fun String.removeTrailingSlashesIfPresent(): String {
val candidate = this.replace(Regex("/+$"), "")
return try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,12 @@ class SnykTaskQueueServiceTest : LightPlatformTestCase() {
snykApiServiceMock = mockk()
every { snykApiServiceMock.getSastSettings() } returns CliConfigSettings(
true,
LocalCodeEngine(false),
LocalCodeEngine(false, "", false),
false
)
every { snykApiServiceMock.getSastSettings() } returns CliConfigSettings(
true,
LocalCodeEngine(false),
LocalCodeEngine(false, "", false),
false
)
}
Expand Down Expand Up @@ -187,30 +187,6 @@ class SnykTaskQueueServiceTest : LightPlatformTestCase() {
assertNull(settings.localCodeEngineEnabled)
}

fun `test should disable Code settings when LCE is enabled`() {
val snykTaskQueueService = project.service<SnykTaskQueueService>()
val settings = pluginSettings()
settings.sastOnServerEnabled = true
settings.localCodeEngineEnabled = true
settings.snykCodeQualityIssuesScanEnable = true
settings.snykCodeSecurityIssuesScanEnable = true
settings.token = "testToken"
// overwrite default setup
snykApiServiceMock = mockk(relaxed = true)
replaceSnykApiServiceMockInContainer()
every { snykApiServiceMock.getSastSettings() } returns CliConfigSettings(
true,
LocalCodeEngine(true),
false
)

snykTaskQueueService.scan()
PlatformTestUtil.dispatchAllInvocationEventsInIdeEventQueue()

assertThat(settings.snykCodeSecurityIssuesScanEnable, equalTo(false))
assertThat(settings.snykCodeQualityIssuesScanEnable, equalTo(false))
}

private fun replaceSnykApiServiceMockInContainer() {
val application = ApplicationManager.getApplication()
application.replaceService(SnykApiService::class.java, snykApiServiceMock, application)
Expand Down
Loading