From 172858978684e6f055687c029af112df37e59e5e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Nov 2026 16:00:06 +0000 Subject: [PATCH 1/3] Initial plan From d73af3344bfff94129229508e87003e47a6b3c8c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Nov 2026 16:02:30 +0000 Subject: [PATCH 2/3] test: cover minimum TLS version across dispatcher paths Co-authored-by: brendandburns <5751682+brendandburns@users.noreply.github.com> --- src/config_test.ts | 63 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/src/config_test.ts b/src/config_test.ts index 9dd2b599a79..afee86e4efd 100644 --- a/src/config_test.ts +++ b/src/config_test.ts @@ -443,6 +443,69 @@ describe('KubeConfig', () => { expectedCA.toString(), ); }); + for (const proxyUrl of [undefined, 'http://proxy:8080', 'socks5://proxy:1080']) { + it(`should pass the minimum TLS version to dispatcher options (${proxyUrl ?? 'direct'})`, () => { + const kc = new KubeConfig(); + const cluster = { name: 'cluster', server: 'https://example.com', proxyUrl }; + const options = kc.createDispatcherOptions(cluster, { minVersion: 'TLSv1.3' }); + assert.notStrictEqual(options.type, 'none'); + const tlsOptions = options.type === 'agent' ? options.connect : options.requestTls; + strictEqual(tlsOptions.minVersion, 'TLSv1.3'); + }); + + it(`should configure the minimum TLS version through authentication (${proxyUrl ?? 'direct'})`, async () => { + const kc = new KubeConfig(); + kc.loadFromClusterAndUser( + { name: 'cluster', server: 'https://example.com', proxyUrl }, + { name: 'user' }, + ); + kc.setTLSMinVersion('TLSv1.3'); + const createOptions = kc.createDispatcherOptions.bind(kc); + let observedMinVersion: tls.SecureVersion | undefined; + mock.method(kc, 'createDispatcherOptions', (cluster, agentOptions) => { + const options = createOptions(cluster, agentOptions); + assert.notStrictEqual(options.type, 'none'); + const tlsOptions = options.type === 'agent' ? options.connect : options.requestTls; + observedMinVersion = tlsOptions.minVersion; + return options; + }); + const rc = new RequestContext('https://example.com', HttpMethod.GET); + await kc.applySecurityAuthentication(rc); + try { + strictEqual(observedMinVersion, 'TLSv1.3'); + } finally { + await rc.getDispatcher()?.close(); + } + + const opts: https.RequestOptions = {}; + await kc.applyToHTTPSOptions(opts); + strictEqual(opts.minVersion, 'TLSv1.3'); + strictEqual((opts.agent as https.Agent).options.minVersion, 'TLSv1.3'); + + kc.setTLSMinVersion(undefined); + const defaultOpts: https.RequestOptions = {}; + await kc.applyToHTTPSOptions(defaultOpts); + strictEqual(defaultOpts.minVersion, undefined); + strictEqual((defaultOpts.agent as https.Agent).options.minVersion, undefined); + notStrictEqual(defaultOpts.agent, opts.agent); + (opts.agent as https.Agent).destroy(); + (defaultOpts.agent as https.Agent).destroy(); + }); + + it(`should preserve the runtime TLS default when unset (${proxyUrl ?? 'direct'})`, () => { + const kc = new KubeConfig(); + const options = kc.createDispatcherOptions( + { name: 'cluster', server: 'https://example.com', proxyUrl }, + {}, + ); + if (options.type !== 'none') { + const tlsOptions = options.type === 'agent' ? options.connect : options.requestTls; + strictEqual(tlsOptions.minVersion, undefined); + } else { + strictEqual(proxyUrl, undefined); + } + }); + } it('should override default 60s keep-alive with 30s for cloud LB compatibility', () => { const kc = new KubeConfig(); kc.loadFromClusterAndUser( From d47220b7d4ca97dda323f016b832429ba94eb19f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Nov 2026 16:05:09 +0000 Subject: [PATCH 3/3] feat: support configuring the minimum TLS version Co-authored-by: brendandburns <5751682+brendandburns@users.noreply.github.com> --- README.md | 17 +++++++++++++++++ src/config.ts | 20 ++++++++++++++++++++ src/config_test.ts | 15 ++++++++++----- 3 files changed, 47 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 1983ff92664..1204a9c7688 100644 --- a/README.md +++ b/README.md @@ -95,6 +95,23 @@ const k8sApi = kc.makeApiClient(k8s.CoreV1Api); ... ``` +## Configure the minimum TLS version + +Set the minimum TLS version before making API requests: + +```javascript +const kc = new k8s.KubeConfig(); +kc.loadFromDefault(); +kc.setTLSMinVersion('TLSv1.3'); +const k8sApi = kc.makeApiClient(k8s.CoreV1Api); +``` + +`setTLSMinVersion` accepts Node.js TLS version names (`TLSv1`, `TLSv1.1`, `TLSv1.2`, or `TLSv1.3`). +The setting applies to API-server TLS connections, including direct connections and connections +through HTTP(S) or SOCKS proxies. It does not configure TLS to the proxy itself. +When unset, or reset with `kc.setTLSMinVersion(undefined)`, the Node.js runtime default is preserved +(including command-line TLS settings); the client does not impose its own minimum. + ## Call APIs with protocol buffers ```javascript diff --git a/src/config.ts b/src/config.ts index eb6c533ff1f..8ce0817f160 100644 --- a/src/config.ts +++ b/src/config.ts @@ -141,6 +141,7 @@ export interface ApiType {} export class KubeConfig implements SecurityAuthentication { private execAuth: ExecAuth = new ExecAuth(); + private tlsMinVersion?: tls.SecureVersion; private authenticators: Authenticator[] = [ new AzureAuth(), @@ -165,6 +166,15 @@ export class KubeConfig implements SecurityAuthentication { this.custom_authenticators.push(authenticator); } + /** + * Sets the minimum TLS version for API connections. Configure before making requests. + * @param minVersion undefined preserves the Node.js runtime default. + */ + public setTLSMinVersion(minVersion?: tls.SecureVersion): void { + this.tlsMinVersion = minVersion; + this.agentCache.clear(); + } + /** * Sets the application policy for kubeconfig `exec` credential plugins. * The semantics match the `credentialPluginPolicy` and `credentialPluginAllowlist` @@ -322,6 +332,9 @@ export class KubeConfig implements SecurityAuthentication { agentOptions.key = opts.key; agentOptions.pfx = opts.pfx; agentOptions.passphrase = opts.passphrase; + if (opts.minVersion !== undefined) { + agentOptions.minVersion = opts.minVersion; + } // Only set rejectUnauthorized if explicitly configured. When not set, fetch will use NODE_TLS_REJECT_UNAUTHORIZED env var if (opts.rejectUnauthorized !== undefined) { agentOptions.rejectUnauthorized = opts.rejectUnauthorized; @@ -374,6 +387,9 @@ export class KubeConfig implements SecurityAuthentication { agentOptions.key = httpsOptions.key; agentOptions.pfx = httpsOptions.pfx; agentOptions.passphrase = httpsOptions.passphrase; + if (httpsOptions.minVersion !== undefined) { + agentOptions.minVersion = httpsOptions.minVersion; + } // Only set rejectUnauthorized if explicitly configured. When not set, fetch will use NODE_TLS_REJECT_UNAUTHORIZED env var if (httpsOptions.rejectUnauthorized !== undefined) { agentOptions.rejectUnauthorized = httpsOptions.rejectUnauthorized; @@ -718,6 +734,7 @@ export class KubeConfig implements SecurityAuthentication { if (agentOptions.key !== undefined) tlsOptions.key = agentOptions.key; if (agentOptions.pfx !== undefined) tlsOptions.pfx = agentOptions.pfx; if (agentOptions.passphrase !== undefined) tlsOptions.passphrase = agentOptions.passphrase; + if (agentOptions.minVersion !== undefined) tlsOptions.minVersion = agentOptions.minVersion; if (agentOptions.rejectUnauthorized !== undefined) tlsOptions.rejectUnauthorized = agentOptions.rejectUnauthorized; if ((agentOptions as any).servername !== undefined) @@ -770,6 +787,9 @@ export class KubeConfig implements SecurityAuthentication { } private applyHTTPSOptions(opts: https.RequestOptions | WebSocket.ClientOptions): void { + if (this.tlsMinVersion !== undefined) { + opts.minVersion = this.tlsMinVersion; + } const cluster = this.getCurrentCluster(); const user = this.getCurrentUser(); if (!user) { diff --git a/src/config_test.ts b/src/config_test.ts index afee86e4efd..c2073f8a115 100644 --- a/src/config_test.ts +++ b/src/config_test.ts @@ -446,9 +446,14 @@ describe('KubeConfig', () => { for (const proxyUrl of [undefined, 'http://proxy:8080', 'socks5://proxy:1080']) { it(`should pass the minimum TLS version to dispatcher options (${proxyUrl ?? 'direct'})`, () => { const kc = new KubeConfig(); - const cluster = { name: 'cluster', server: 'https://example.com', proxyUrl }; + const cluster = { + name: 'cluster', + server: 'https://example.com', + skipTLSVerify: false, + proxyUrl, + }; const options = kc.createDispatcherOptions(cluster, { minVersion: 'TLSv1.3' }); - assert.notStrictEqual(options.type, 'none'); + assert(options.type !== 'none'); const tlsOptions = options.type === 'agent' ? options.connect : options.requestTls; strictEqual(tlsOptions.minVersion, 'TLSv1.3'); }); @@ -456,7 +461,7 @@ describe('KubeConfig', () => { it(`should configure the minimum TLS version through authentication (${proxyUrl ?? 'direct'})`, async () => { const kc = new KubeConfig(); kc.loadFromClusterAndUser( - { name: 'cluster', server: 'https://example.com', proxyUrl }, + { name: 'cluster', server: 'https://example.com', skipTLSVerify: false, proxyUrl }, { name: 'user' }, ); kc.setTLSMinVersion('TLSv1.3'); @@ -464,7 +469,7 @@ describe('KubeConfig', () => { let observedMinVersion: tls.SecureVersion | undefined; mock.method(kc, 'createDispatcherOptions', (cluster, agentOptions) => { const options = createOptions(cluster, agentOptions); - assert.notStrictEqual(options.type, 'none'); + assert(options.type !== 'none'); const tlsOptions = options.type === 'agent' ? options.connect : options.requestTls; observedMinVersion = tlsOptions.minVersion; return options; @@ -495,7 +500,7 @@ describe('KubeConfig', () => { it(`should preserve the runtime TLS default when unset (${proxyUrl ?? 'direct'})`, () => { const kc = new KubeConfig(); const options = kc.createDispatcherOptions( - { name: 'cluster', server: 'https://example.com', proxyUrl }, + { name: 'cluster', server: 'https://example.com', skipTLSVerify: false, proxyUrl }, {}, ); if (options.type !== 'none') {