@@ -22,9 +22,12 @@ import com.intellij.openapi.project.Project
2222import com.intellij.openapi.util.Disposer
2323import com.intellij.openapi.util.Key
2424import com.intellij.openapi.util.SystemInfo
25+ import com.intellij.util.EnvironmentUtil
26+ import com.intellij.util.io.DigestUtil
2527import com.intellij.util.io.await
2628import com.intellij.util.net.HttpConfigurable
2729import com.intellij.util.net.JdkProxyProvider
30+ import com.intellij.util.net.ssl.CertificateManager
2831import kotlinx.coroutines.CoroutineScope
2932import kotlinx.coroutines.Deferred
3033import kotlinx.coroutines.Job
@@ -77,6 +80,7 @@ import software.aws.toolkits.jetbrains.services.amazonq.lsp.textdocument.TextDoc
7780import software.aws.toolkits.jetbrains.services.amazonq.lsp.util.WorkspaceFolderUtil.createWorkspaceFolders
7881import software.aws.toolkits.jetbrains.services.amazonq.lsp.workspace.WorkspaceServiceHandler
7982import software.aws.toolkits.jetbrains.services.amazonq.profile.QDefaultServiceConfig
83+ import software.aws.toolkits.jetbrains.services.amazonq.profile.QEndpoints
8084import software.aws.toolkits.jetbrains.services.cwc.controller.chat.telemetry.getStartUrl
8185import software.aws.toolkits.jetbrains.services.telemetry.ClientMetadata
8286import software.aws.toolkits.jetbrains.settings.LspSettings
@@ -369,21 +373,60 @@ private class AmazonQServerInstance(private val project: Project, private val cs
369373 // will cause slow service init, but maybe fine for now. will not block UI since fetch/extract will be under background progress
370374 val artifact = runBlocking { service<ArtifactManager >().fetchArtifact(project) }.toAbsolutePath()
371375
372- // more network calls
373- // make assumption that all requests will resolve to the same CA
374- // also terrible assumption that default endpoint is reachable
375- val qUri = URI (QDefaultServiceConfig .ENDPOINT )
376- val extraCaCerts = try {
377- val rtsTrustChain = TrustChainUtil .getTrustChain(qUri)
378-
379- Files .createTempFile(" q-extra-ca" , " .pem" ).apply {
380- writeText(
381- TrustChainUtil .certsToPem(rtsTrustChain)
382- )
376+ // make some network calls for troubleshooting
377+ listOf (* QEndpoints .listRegionEndpoints().map { it.endpoint }.toTypedArray(), QDefaultServiceConfig .ENDPOINT ).forEach { endpoint ->
378+ try {
379+ val qUri = URI (endpoint)
380+ val rtsTrustChain = TrustChainUtil .getTrustChain(qUri)
381+ val trustRoot = rtsTrustChain.last()
382+ // ATS is cross-signed against starfield certs: https://www.amazontrust.com/repository/
383+ if (listOf (" Amazon Root CA" , " Starfield Technologies" ).any { trustRoot.subjectX500Principal.name.contains(it) }) {
384+ LOG .info { " Trust chain for $endpoint ends with public-like CA with sha256 fingerprint: ${DigestUtil .sha256Hex(trustRoot.encoded)} " }
385+ } else {
386+ LOG .info {
387+ """
388+ |Trust chain for $endpoint transits private CA:
389+ |${buildString {
390+ rtsTrustChain.forEach { cert ->
391+ append(" Issuer: ${cert.issuerX500Principal} , " )
392+ append(" Subject: ${cert.subjectX500Principal} , " )
393+ append(" Fingerprint: ${DigestUtil .sha256Hex(cert.encoded)} \n\t " )
394+ }
395+ }}
396+ """ .trimMargin(" |" )
397+ }
398+ LOG .debug { " Full trust chain info for $endpoint : $rtsTrustChain " }
399+ }
400+ } catch (e: Exception ) {
401+ LOG .info { " ${e.message} : Could not resolve trust chain for $endpoint " }
383402 }
384- } catch (e: Exception ) {
385- LOG .info(e) { " Could not resolve trust chain for $qUri , skipping NODE_EXTRA_CA_CERTS" }
403+ }
404+
405+ val userEnvNodeCaCerts = EnvironmentUtil .getValue(" NODE_EXTRA_CA_CERTS" )
406+ // if user has NODE_EXTRA_CA_CERTS in their environment, assume they know what they're doing
407+ val extraCaCerts = if (! userEnvNodeCaCerts.isNullOrEmpty()) {
408+ LOG .info { " Skipping injection of IDE trust store, user already defines NODE_EXTRA_CA_CERTS: $userEnvNodeCaCerts " }
409+
386410 null
411+ } else {
412+ try {
413+ // otherwise include everything the IDE knows about
414+ val allAcceptedIssuers = CertificateManager .getInstance().trustManager.acceptedIssuers
415+ val customIssuers = CertificateManager .getInstance().customTrustManager.acceptedIssuers
416+ LOG .info {
417+ " Injecting ${allAcceptedIssuers.size} IDE trusted certificates (${customIssuers.size} from IDE custom manager) into NODE_EXTRA_CA_CERTS"
418+ }
419+
420+ Files .createTempFile(" q-extra-ca" , " .pem" ).apply {
421+ writeText(
422+ TrustChainUtil .certsToPem(allAcceptedIssuers.toList())
423+ )
424+ }.toAbsolutePath().toString()
425+ } catch (e: Exception ) {
426+ LOG .warn(e) { " Could not inject IDE trust store into NODE_EXTRA_CA_CERTS" }
427+
428+ null
429+ }
387430 }
388431
389432 val node = if (SystemInfo .isWindows) " node.exe" else " node"
@@ -396,8 +439,13 @@ private class AmazonQServerInstance(private val project: Project, private val cs
396439 " --set-credentials-encryption-key" ,
397440 ).withEnvironment(
398441 buildMap {
399- extraCaCerts?.let { put(" NODE_EXTRA_CA_CERTS" , it.toAbsolutePath().toString()) }
442+ extraCaCerts?.let {
443+ LOG .info { " Starting Flare with NODE_EXTRA_CA_CERTS: $it " }
444+ put(" NODE_EXTRA_CA_CERTS" , it)
445+ }
400446
447+ // assume default endpoint will pick correct proxy if needed
448+ val qUri = URI (QDefaultServiceConfig .ENDPOINT )
401449 val proxy = JdkProxyProvider .getInstance().proxySelector.select(qUri)
402450 // log if only socks proxy available
403451 .firstOrNull { it.type() == Proxy .Type .HTTP }
0 commit comments