From 74fdcd35fc7d5260186c856559afb0ae4b93d8b1 Mon Sep 17 00:00:00 2001 From: Jonathan Hess Date: Mon, 17 Aug 2026 23:56:42 +0000 Subject: [PATCH 1/2] feat: proactively probe database on Auto-IAM refresh to update MCP tokens --- src/cloud-sql-instance.ts | 74 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/src/cloud-sql-instance.ts b/src/cloud-sql-instance.ts index 8d9f0e91..ce73d391 100644 --- a/src/cloud-sql-instance.ts +++ b/src/cloud-sql-instance.ts @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +import tls from 'node:tls'; import {IpAddressTypes, selectIpAddress} from './ip-addresses'; import {InstanceConnectionInfo} from './instance-connection-info'; import { @@ -28,6 +29,7 @@ import {SslCert} from './ssl-cert'; import {getRefreshInterval, isExpirationTimeValid} from './time'; import {AuthTypes} from './auth-types'; import {CloudSQLConnectorError} from './errors'; +import {validateCertificate} from './socket'; // Private types that describe exactly the methods // needed from tls.Socket to be able to close @@ -331,6 +333,10 @@ export class CloudSQLInstance { serverCaCert, }; + if (this.authType === AuthTypes.IAM) { + await this.probeConnection(nextValues, metadata); + } + // In the rather odd case that the current ephemeral certificate is still // valid while we get an invalid result from the API calls, then preserve // the current metadata. @@ -341,6 +347,74 @@ export class CloudSQLInstance { return nextValues; } + private async probeConnection( + refreshResult: RefreshResult, + metadata: InstanceMetadata + ): Promise { + const targets: string[] = []; + if (this.instanceInfo && this.instanceInfo.domainName) { + targets.push(this.instanceInfo.domainName); + } else { + if (metadata.ipAddresses.psc) { + targets.push(metadata.ipAddresses.psc); + } + if (metadata.ipAddresses.private) { + targets.push(metadata.ipAddresses.private); + } + if (metadata.ipAddresses.public) { + targets.push(metadata.ipAddresses.public); + } + } + + if (targets.length === 0 && refreshResult.host) { + targets.push(refreshResult.host); + } + + const port = this.port || 3307; + for (const target of targets) { + try { + await new Promise((resolve, reject) => { + const timeout = setTimeout(() => { + socket.destroy(new Error('Probe timeout')); + reject(new Error('Probe timeout')); + }, 15000); + + const socket: tls.TLSSocket = tls.connect( + { + host: target, + port, + secureContext: tls.createSecureContext({ + ca: refreshResult.serverCaCert.cert, + cert: refreshResult.ephemeralCert.cert, + key: refreshResult.privateKey, + minVersion: 'TLSv1.3', + }), + checkServerIdentity: validateCertificate( + this.instanceInfo, + metadata.dnsName || '', + target + ), + }, + () => { + clearTimeout(timeout); + socket.end(); + resolve(); + } + ); + + socket.on('error', err => { + clearTimeout(timeout); + socket.destroy(); + reject(err); + }); + }); + return; + } catch (e) { + // Ignore probe error across single target and try next target + } + } + } + private isValid({ ephemeralCert, host, From ab0ab25adde9197a618a052d4cf32d2732d96766 Mon Sep 17 00:00:00 2001 From: Jonathan Hess Date: Tue, 18 Aug 2026 02:04:52 +0000 Subject: [PATCH 2/2] fix: address code review comments on proactive IAM token probe Code review comments addressed: - Respect the IP settings in connection configuration (this.ipType) rather than probing all available IPs - Use DEFAULT_SERVER_PROXY_PORT constant (3307) - Use DEFAULT_CONNECT_TIMEOUT_MS constant (30000) --- src/cloud-sql-instance.ts | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/cloud-sql-instance.ts b/src/cloud-sql-instance.ts index ce73d391..dfb3244d 100644 --- a/src/cloud-sql-instance.ts +++ b/src/cloud-sql-instance.ts @@ -31,6 +31,9 @@ import {AuthTypes} from './auth-types'; import {CloudSQLConnectorError} from './errors'; import {validateCertificate} from './socket'; +export const DEFAULT_SERVER_PROXY_PORT = 3307; +export const DEFAULT_CONNECT_TIMEOUT_MS = 30 * 1000; + // Private types that describe exactly the methods // needed from tls.Socket to be able to close // sockets when the DNS Name changes. @@ -355,29 +358,28 @@ export class CloudSQLInstance { if (this.instanceInfo && this.instanceInfo.domainName) { targets.push(this.instanceInfo.domainName); } else { - if (metadata.ipAddresses.psc) { - targets.push(metadata.ipAddresses.psc); - } - if (metadata.ipAddresses.private) { - targets.push(metadata.ipAddresses.private); - } - if (metadata.ipAddresses.public) { - targets.push(metadata.ipAddresses.public); + try { + const selectedIp = selectIpAddress(metadata.ipAddresses, this.ipType); + if (selectedIp) { + targets.push(selectedIp); + } + } catch { + // If the configured IP type is not available in metadata, skip probe } } - if (targets.length === 0 && refreshResult.host) { - targets.push(refreshResult.host); + if (targets.length === 0) { + return; } - const port = this.port || 3307; + const port = this.port || DEFAULT_SERVER_PROXY_PORT; for (const target of targets) { try { await new Promise((resolve, reject) => { const timeout = setTimeout(() => { socket.destroy(new Error('Probe timeout')); reject(new Error('Probe timeout')); - }, 15000); + }, DEFAULT_CONNECT_TIMEOUT_MS); const socket: tls.TLSSocket = tls.connect( {