Skip to content

Commit a0196f5

Browse files
authored
fix(amazonq): tweak CA resolution and add additional logging (#5815)
Instead of trying to intelligently select the specific custom CA needed to connect to Amazon Q, just send all IDE trust anchors along to Flare. Additionally, if user already defines NODE_EXTRA_CA_CERTS, use it directly and noop our logic
1 parent 35f05f9 commit a0196f5

File tree

1 file changed

+62
-14
lines changed
  • plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp

1 file changed

+62
-14
lines changed

plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt

Lines changed: 62 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,12 @@ import com.intellij.openapi.project.Project
2222
import com.intellij.openapi.util.Disposer
2323
import com.intellij.openapi.util.Key
2424
import com.intellij.openapi.util.SystemInfo
25+
import com.intellij.util.EnvironmentUtil
26+
import com.intellij.util.io.DigestUtil
2527
import com.intellij.util.io.await
2628
import com.intellij.util.net.HttpConfigurable
2729
import com.intellij.util.net.JdkProxyProvider
30+
import com.intellij.util.net.ssl.CertificateManager
2831
import kotlinx.coroutines.CoroutineScope
2932
import kotlinx.coroutines.Deferred
3033
import kotlinx.coroutines.Job
@@ -77,6 +80,7 @@ import software.aws.toolkits.jetbrains.services.amazonq.lsp.textdocument.TextDoc
7780
import software.aws.toolkits.jetbrains.services.amazonq.lsp.util.WorkspaceFolderUtil.createWorkspaceFolders
7881
import software.aws.toolkits.jetbrains.services.amazonq.lsp.workspace.WorkspaceServiceHandler
7982
import software.aws.toolkits.jetbrains.services.amazonq.profile.QDefaultServiceConfig
83+
import software.aws.toolkits.jetbrains.services.amazonq.profile.QEndpoints
8084
import software.aws.toolkits.jetbrains.services.cwc.controller.chat.telemetry.getStartUrl
8185
import software.aws.toolkits.jetbrains.services.telemetry.ClientMetadata
8286
import 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

Comments
 (0)