Repository: kubernetes-client/javascript (ts)

Issue:
TypeError: Protocol "http:" not supported. Expected "https:"
So yeah I spent like 2 hours deep dive debugging into this only to discover a bug.

https://github.com/kubernetes-client/javascript/blob/3283cb12f146baf0ced6061d06697a3ecd25917a/src/config.ts#L527-L551

more specifically

https://github.com/kubernetes-client/javascript/blob/3283cb12f146baf0ced6061d06697a3ecd25917a/src/config.ts#L533-L536

if no `proxyUrl` is set, then the default will be an HTTPS agent, which does not work with `kubectl proxy` environments.

Pull request description:
fix: apply http agent if needed
This applies an http agent if needed.
I'm not entirely sure about the security implications of this, but I guess it should be not a problem though.

Fixes #2348


Patch:
diff --git a/package-lock.json b/package-lock.json
index ec4dd16a..e7da6327 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -739,12 +739,12 @@
             }
         },
         "node_modules/@types/node": {
-            "version": "22.14.0",
-            "resolved": "https://registry.npmjs.org/@types/node/-/node-22.14.0.tgz",
-            "integrity": "sha512-Kmpl+z84ILoG+3T/zQFyAJsU6EPTmOCj8/2+83fSN6djd6I4o7uOuGIH6vq3PrjY5BGitSbFuMN18j3iknubbA==",
+            "version": "22.13.17",
+            "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.17.tgz",
+            "integrity": "sha512-nAJuQXoyPj04uLgu+obZcSmsfOenUg6DxPKogeUy6yNCFwWaj5sBF8/G/pNo8EtBJjAfSVgfIlugR/BCOleO+g==",
             "license": "MIT",
             "dependencies": {
-                "undici-types": "~6.21.0"
+                "undici-types": "~6.20.0"
             }
         },
         "node_modules/@types/node-fetch": {
@@ -3718,10 +3718,9 @@
             "dev": true
         },
         "node_modules/undici-types": {
-            "version": "6.21.0",
-            "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
-            "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
-            "license": "MIT"
+            "version": "6.20.0",
+            "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
+            "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="
         },
         "node_modules/uri-js": {
             "version": "4.4.1",
diff --git a/src/config.ts b/src/config.ts
index aad84dfd..45a3f556 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -1,5 +1,6 @@
 import fs from 'node:fs';
 import https from 'node:https';
+import http from 'node:http';
 import yaml from 'js-yaml';
 import net from 'node:net';
 import path from 'node:path';
@@ -544,6 +545,10 @@ export class KubeConfig implements SecurityAuthentication {
             } else {
                 throw new Error('Unsupported proxy type');
             }
+        } else if (cluster?.server?.startsWith('http:') && cluster.skipTLSVerify) {
+            agent = new http.Agent(agentOptions);
+        } else if (cluster?.server?.startsWith('http:') && !cluster.skipTLSVerify) {
+            throw new Error('HTTP protocol is not allowed when skipTLSVerify is not set or false');
         } else {
             agent = new https.Agent(agentOptions);
         }


Test patch:
diff --git a/src/cache_test.ts b/src/cache_test.ts
index 493a42ba..315a4f55 100644
--- a/src/cache_test.ts
+++ b/src/cache_test.ts
@@ -11,7 +11,7 @@ import { ListPromise } from './informer.js';
 import nock from 'nock';
 import { Watch } from './watch.js';
 
-const server = 'http://foo.company.com';
+const server = 'https://foo.company.com';
 
 const fakeConfig: {
     clusters: Cluster[];
diff --git a/src/config_test.ts b/src/config_test.ts
index b5af672d..405b188d 100644
--- a/src/config_test.ts
+++ b/src/config_test.ts
@@ -1,8 +1,16 @@
 import { after, before, beforeEach, describe, it, mock } from 'node:test';
-import { deepEqual, deepStrictEqual, notStrictEqual, rejects, strictEqual, throws } from 'node:assert';
+import assert, {
+    deepEqual,
+    deepStrictEqual,
+    notStrictEqual,
+    rejects,
+    strictEqual,
+    throws,
+} from 'node:assert';
 import child_process from 'node:child_process';
 import { readFileSync } from 'node:fs';
 import https from 'node:https';
+import http from 'node:http';
 import { Agent, RequestOptions } from 'node:https';
 import path, { dirname, join } from 'node:path';
 import { fileURLToPath } from 'node:url';
@@ -448,6 +456,40 @@ describe('KubeConfig', () => {
                 message: 'Unsupported proxy type',
             });
         });
+        it('should apply http agent if cluster.server starts with http and no proxy-url is provided', async () => {
+            const kc = new KubeConfig();
+            kc.loadFromFile(kcProxyUrl);
+            kc.setCurrentContext('contextE');
+
+            const testServerName = 'http://example.com';
+            const rc = new RequestContext(testServerName, HttpMethod.GET);
+
+            await kc.applySecurityAuthentication(rc);
+
+            strictEqual(rc.getAgent() instanceof http.Agent, true);
+        });
+        it('should throw an error if cluster.server starts with http, no proxy-url is provided and insecure-skip-tls-verify is not set', async () => {
+            const kc = new KubeConfig();
+            kc.loadFromFile(kcProxyUrl);
+            kc.setCurrentContext('contextF');
+
+            const testServerName = 'http://example.com';
+            const rc = new RequestContext(testServerName, HttpMethod.GET);
+
+            await assert.rejects(kc.applySecurityAuthentication(rc), Error);
+        });
+        it('should apply https agent if cluster.server starts with https and no proxy-url is provided', async () => {
+            const kc = new KubeConfig();
+            kc.loadFromFile(kcProxyUrl);
+            kc.setCurrentContext('contextG');
+
+            const testServerName = 'https://example.com';
+            const rc = new RequestContext(testServerName, HttpMethod.GET);
+
+            await kc.applySecurityAuthentication(rc);
+
+            strictEqual(rc.getAgent() instanceof https.Agent, true);
+        });
     });
 
     describe('loadClusterConfigObjects', () => {
diff --git a/src/watch_test.ts b/src/watch_test.ts
index a9da7881..0e9c65c0 100644
--- a/src/watch_test.ts
+++ b/src/watch_test.ts
@@ -7,7 +7,7 @@ import { Cluster, Context, User } from './config_types.js';
 import { Watch } from './watch.js';
 import { IncomingMessage } from 'node:http';
 
-const server = 'http://foo.company.com';
+const server = 'https://foo.company.com';
 
 const fakeConfig: {
     clusters: Cluster[];
diff --git a/testdata/kubeconfig-proxy-url.yaml b/testdata/kubeconfig-proxy-url.yaml
index 8d88117a..03ea1a2c 100644
--- a/testdata/kubeconfig-proxy-url.yaml
+++ b/testdata/kubeconfig-proxy-url.yaml
@@ -20,6 +20,15 @@ clusters:
           server: htto://exampleerror.com
           proxy-url: http://example:8080
       name: clusterD
+    - cluster:
+          certificate-authority-data: Q0FEQVRA
+          server: http://exampleerror.com
+          insecure-skip-tls-verify: true
+      name: clusterE
+    - cluster:
+          certificate-authority-data: Q0FEQVRA
+          server: http://exampleerror.com
+      name: clusterF
 
 contexts:
     - context:
@@ -38,6 +47,14 @@ contexts:
           cluster: clusterD
           user: userD
       name: contextD
+    - context:
+          cluster: clusterE
+          user: userE
+      name: contextE
+    - context:
+          cluster: clusterF
+          user: userF
+      name: contextF
 
 current-context: contextA
 kind: Config
@@ -59,3 +76,11 @@ users:
       user:
           client-certificate-data: XVNFUl9DQURBVEE=
           client-key-data: XVNFUl9DS0RBVEE=
+    - name: userE
+      user:
+          client-certificate-data: XVNFUl9DQURBVEE=
+          client-key-data: XVNFUl9DS0RBVEE=
+    - name: userF
+      user:
+          client-certificate-data: XVNFUl9DQURBVEE=
+          client-key-data: XVNFUl9DS0RBVEE=