Skip to content

Commit

Permalink
feat: Enabling SCLE [NEBULA-1371] (#447)
Browse files Browse the repository at this point in the history
* feat: Enabling SCLE

* fix: PR feedback update #1

* fix: PR feedback update #2

* testing...

* fix: breaking tests, better UX

* fix: unit tests

* fix: updated with PR feedback

* fix: text when SAST setting is misconigured

Signed-off-by: Matthew Barbara <matthew.barbara@snyk.io>

* fix: lint

Signed-off-by: Matthew Barbara <matthew.barbara@snyk.io>

* fix: Added changes in changelog.md

Signed-off-by: Matthew Barbara <matthew.barbara@snyk.io>

* fix: lint

Signed-off-by: Matthew Barbara <matthew.barbara@snyk.io>

---------

Signed-off-by: Matthew Barbara <matthew.barbara@snyk.io>
  • Loading branch information
metju90 authored Sep 11, 2023
1 parent 013d0d6 commit fa48159
Show file tree
Hide file tree
Showing 16 changed files with 202 additions and 85 deletions.
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

0 comments on commit fa48159

Please sign in to comment.