diff --git a/packages/amazonq/.changes/next-release/Bug Fix-a06c2136-a87a-41af-9304-454bc77aaecc.json b/packages/amazonq/.changes/next-release/Bug Fix-a06c2136-a87a-41af-9304-454bc77aaecc.json new file mode 100644 index 00000000000..f38b0f1375f --- /dev/null +++ b/packages/amazonq/.changes/next-release/Bug Fix-a06c2136-a87a-41af-9304-454bc77aaecc.json @@ -0,0 +1,4 @@ +{ + "type": "Bug Fix", + "description": "Added automatic system certificate detection and VSCode proxy settings support" +} diff --git a/packages/amazonq/src/extension.ts b/packages/amazonq/src/extension.ts index 65caea3b2c8..9ca13136eab 100644 --- a/packages/amazonq/src/extension.ts +++ b/packages/amazonq/src/extension.ts @@ -122,7 +122,7 @@ export async function activateAmazonQCommon(context: vscode.ExtensionContext, is } // Configure proxy settings early - ProxyUtil.configureProxyForLanguageServer() + await ProxyUtil.configureProxyForLanguageServer() // This contains every lsp agnostic things (auth, security scan, code scan) await activateCodeWhisperer(extContext as ExtContext) diff --git a/packages/core/src/shared/lsp/utils/platform.ts b/packages/core/src/shared/lsp/utils/platform.ts index 67f3b95c296..fadeefb7e68 100644 --- a/packages/core/src/shared/lsp/utils/platform.ts +++ b/packages/core/src/shared/lsp/utils/platform.ts @@ -4,14 +4,10 @@ */ import { ToolkitError } from '../../errors' -import { Logger, getLogger } from '../../logger/logger' +import { Logger } from '../../logger/logger' import { ChildProcess } from '../../utilities/processUtils' import { waitUntil } from '../../utilities/timeoutUtils' import { isDebugInstance } from '../../vscode/env' -import { tmpdir } from 'os' -import { join } from 'path' -import * as nodefs from 'fs' // eslint-disable-line no-restricted-imports -import * as vscode from 'vscode' export function getNodeExecutableName(): string { return process.platform === 'win32' ? 'node.exe' : 'node' @@ -85,67 +81,18 @@ export async function validateNodeExe(nodePath: string[], lsp: string, args: str } } -/** - * Gets proxy settings and certificates from VS Code - */ -export async function getVSCodeSettings(): Promise<{ proxyUrl?: string; certificatePath?: string }> { - const result: { proxyUrl?: string; certificatePath?: string } = {} - const logger = getLogger('amazonqLsp') - - try { - // Get proxy settings from VS Code configuration - const httpConfig = vscode.workspace.getConfiguration('http') - const proxy = httpConfig.get('proxy') - if (proxy) { - result.proxyUrl = proxy - logger.info(`Using proxy from VS Code settings: ${proxy}`) - } - } catch (err) { - logger.error(`Failed to get VS Code settings: ${err}`) - return result - } - try { - const tls = await import('tls') - // @ts-ignore Get system certificates - const systemCerts = tls.getCACertificates('system') - // @ts-ignore Get any existing extra certificates - const extraCerts = tls.getCACertificates('extra') - const allCerts = [...systemCerts, ...extraCerts] - if (allCerts && allCerts.length > 0) { - logger.info(`Found ${allCerts.length} certificates in system's trust store`) - - const tempDir = join(tmpdir(), 'aws-toolkit-vscode') - if (!nodefs.existsSync(tempDir)) { - nodefs.mkdirSync(tempDir, { recursive: true }) - } - - const certPath = join(tempDir, 'vscode-ca-certs.pem') - const certContent = allCerts.join('\n') - - nodefs.writeFileSync(certPath, certContent) - result.certificatePath = certPath - logger.info(`Created certificate file at: ${certPath}`) - } - } catch (err) { - logger.error(`Failed to extract certificates: ${err}`) - } - return result -} - export function createServerOptions({ encryptionKey, executable, serverModule, execArgv, warnThresholds, - env, }: { encryptionKey: Buffer executable: string[] serverModule: string execArgv: string[] warnThresholds?: { cpu?: number; memory?: number } - env?: Record }) { return async () => { const bin = executable[0] @@ -154,43 +101,7 @@ export function createServerOptions({ args.unshift('--inspect=6080') } - // Merge environment variables - const processEnv = { ...process.env } - if (env) { - Object.assign(processEnv, env) - } - - // Get settings from VS Code - const settings = await getVSCodeSettings() - const logger = getLogger('amazonqLsp') - - // Add proxy settings to the Node.js process - if (settings.proxyUrl) { - processEnv.HTTPS_PROXY = settings.proxyUrl - } - - // Add certificate path if available - if (settings.certificatePath) { - processEnv.NODE_EXTRA_CA_CERTS = settings.certificatePath - logger.info(`Using certificate file: ${settings.certificatePath}`) - } - - // Get SSL verification settings - const httpConfig = vscode.workspace.getConfiguration('http') - const strictSSL = httpConfig.get('proxyStrictSSL', true) - - // Handle SSL certificate verification - if (!strictSSL) { - processEnv.NODE_TLS_REJECT_UNAUTHORIZED = '0' - logger.info('SSL verification disabled via VS Code settings') - } - - const lspProcess = new ChildProcess(bin, args, { - warnThresholds, - spawnOptions: { - env: processEnv, - }, - }) + const lspProcess = new ChildProcess(bin, args, { warnThresholds }) // this is a long running process, awaiting it will never resolve void lspProcess.run() diff --git a/packages/core/src/shared/utilities/proxyUtil.ts b/packages/core/src/shared/utilities/proxyUtil.ts index 5c37c5e3e46..a8d2056d7f4 100644 --- a/packages/core/src/shared/utilities/proxyUtil.ts +++ b/packages/core/src/shared/utilities/proxyUtil.ts @@ -5,9 +5,14 @@ import vscode from 'vscode' import { getLogger } from '../logger/logger' +import { tmpdir } from 'os' +import { join } from 'path' +import * as nodefs from 'fs' // eslint-disable-line no-restricted-imports interface ProxyConfig { proxyUrl: string | undefined + noProxy: string | undefined + proxyStrictSSL: boolean | true certificateAuthority: string | undefined } @@ -23,11 +28,11 @@ export class ProxyUtil { * See documentation here for setting the environement variables which are inherited by Flare LS process: * https://github.com/aws/language-server-runtimes/blob/main/runtimes/docs/proxy.md */ - public static configureProxyForLanguageServer(): void { + public static async configureProxyForLanguageServer(): Promise { try { const proxyConfig = this.getProxyConfiguration() - this.setProxyEnvironmentVariables(proxyConfig) + await this.setProxyEnvironmentVariables(proxyConfig) } catch (err) { this.logger.error(`Failed to configure proxy: ${err}`) } @@ -41,6 +46,13 @@ export class ProxyUtil { const proxyUrl = httpConfig.get('proxy') this.logger.debug(`Proxy URL Setting in VSCode Settings: ${proxyUrl}`) + const noProxy = httpConfig.get('noProxy') + if (noProxy) { + this.logger.info(`Using noProxy from VS Code settings: ${noProxy}`) + } + + const proxyStrictSSL = httpConfig.get('proxyStrictSSL', true) + const amazonQConfig = vscode.workspace.getConfiguration('amazonQ') const proxySettings = amazonQConfig.get<{ certificateAuthority?: string @@ -48,6 +60,8 @@ export class ProxyUtil { return { proxyUrl, + noProxy, + proxyStrictSSL, certificateAuthority: proxySettings.certificateAuthority, } } @@ -55,7 +69,7 @@ export class ProxyUtil { /** * Sets environment variables based on proxy configuration */ - private static setProxyEnvironmentVariables(config: ProxyConfig): void { + private static async setProxyEnvironmentVariables(config: ProxyConfig): Promise { const proxyUrl = config.proxyUrl // Set proxy environment variables if (proxyUrl) { @@ -64,11 +78,61 @@ export class ProxyUtil { this.logger.debug(`Set proxy environment variables: ${proxyUrl}`) } - // Set certificate bundle environment variables if configured + // set NO_PROXY vals + const noProxy = config.noProxy + if (noProxy) { + process.env.NO_PROXY = noProxy + this.logger.debug(`Set NO_PROXY environment variable: ${noProxy}`) + } + + const strictSSL = config.proxyStrictSSL + // Handle SSL certificate verification + if (!strictSSL) { + process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0' + this.logger.info('SSL verification disabled via VS Code settings') + return // No need to set CA certs when SSL verification is disabled + } + + // Set certificate bundle environment variables if user configured if (config.certificateAuthority) { process.env.NODE_EXTRA_CA_CERTS = config.certificateAuthority process.env.AWS_CA_BUNDLE = config.certificateAuthority this.logger.debug(`Set certificate bundle path: ${config.certificateAuthority}`) + } else { + // Fallback to system certificates if no custom CA is configured + await this.setSystemCertificates() + } + } + + /** + * Sets system certificates as fallback when no custom CA is configured + */ + private static async setSystemCertificates(): Promise { + try { + const tls = await import('tls') + // @ts-ignore Get system certificates + const systemCerts = tls.getCACertificates('system') + // @ts-ignore Get any existing extra certificates + const extraCerts = tls.getCACertificates('extra') + const allCerts = [...systemCerts, ...extraCerts] + if (allCerts && allCerts.length > 0) { + this.logger.debug(`Found ${allCerts.length} certificates in system's trust store`) + + const tempDir = join(tmpdir(), 'aws-toolkit-vscode') + if (!nodefs.existsSync(tempDir)) { + nodefs.mkdirSync(tempDir, { recursive: true }) + } + + const certPath = join(tempDir, 'vscode-ca-certs.pem') + const certContent = allCerts.join('\n') + + nodefs.writeFileSync(certPath, certContent) + process.env.NODE_EXTRA_CA_CERTS = certPath + process.env.AWS_CA_BUNDLE = certPath + this.logger.debug(`Set system certificate bundle path: ${certPath}`) + } + } catch (err) { + this.logger.error(`Failed to extract system certificates: ${err}`) } } }