@@ -22,9 +22,12 @@ import com.intellij.openapi.project.Project
22
22
import com.intellij.openapi.util.Disposer
23
23
import com.intellij.openapi.util.Key
24
24
import com.intellij.openapi.util.SystemInfo
25
+ import com.intellij.util.EnvironmentUtil
26
+ import com.intellij.util.io.DigestUtil
25
27
import com.intellij.util.io.await
26
28
import com.intellij.util.net.HttpConfigurable
27
29
import com.intellij.util.net.JdkProxyProvider
30
+ import com.intellij.util.net.ssl.CertificateManager
28
31
import kotlinx.coroutines.CoroutineScope
29
32
import kotlinx.coroutines.Deferred
30
33
import kotlinx.coroutines.Job
@@ -77,6 +80,7 @@ import software.aws.toolkits.jetbrains.services.amazonq.lsp.textdocument.TextDoc
77
80
import software.aws.toolkits.jetbrains.services.amazonq.lsp.util.WorkspaceFolderUtil.createWorkspaceFolders
78
81
import software.aws.toolkits.jetbrains.services.amazonq.lsp.workspace.WorkspaceServiceHandler
79
82
import software.aws.toolkits.jetbrains.services.amazonq.profile.QDefaultServiceConfig
83
+ import software.aws.toolkits.jetbrains.services.amazonq.profile.QEndpoints
80
84
import software.aws.toolkits.jetbrains.services.cwc.controller.chat.telemetry.getStartUrl
81
85
import software.aws.toolkits.jetbrains.services.telemetry.ClientMetadata
82
86
import software.aws.toolkits.jetbrains.settings.LspSettings
@@ -369,21 +373,60 @@ private class AmazonQServerInstance(private val project: Project, private val cs
369
373
// will cause slow service init, but maybe fine for now. will not block UI since fetch/extract will be under background progress
370
374
val artifact = runBlocking { service<ArtifactManager >().fetchArtifact(project) }.toAbsolutePath()
371
375
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 " }
383
402
}
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
+
386
410
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
+ }
387
430
}
388
431
389
432
val node = if (SystemInfo .isWindows) " node.exe" else " node"
@@ -396,8 +439,13 @@ private class AmazonQServerInstance(private val project: Project, private val cs
396
439
" --set-credentials-encryption-key" ,
397
440
).withEnvironment(
398
441
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
+ }
400
446
447
+ // assume default endpoint will pick correct proxy if needed
448
+ val qUri = URI (QDefaultServiceConfig .ENDPOINT )
401
449
val proxy = JdkProxyProvider .getInstance().proxySelector.select(qUri)
402
450
// log if only socks proxy available
403
451
.firstOrNull { it.type() == Proxy .Type .HTTP }
0 commit comments