From 33175a85598ce5c4e4673ef1a6678ca74282eab7 Mon Sep 17 00:00:00 2001 From: krmodelski <55512362+krmodelski@users.noreply.github.com> Date: Thu, 5 Dec 2024 18:33:16 +0100 Subject: [PATCH 1/4] add proxy support --- package-lock.json | 137 +++++++++++++++++++++++++++++++++++-- package.json | 3 +- src/config.ts | 9 +++ src/config_test.ts | 28 ++++++++ src/config_types.ts | 3 + src/web-socket-handler.ts | 2 +- testdata/kubeconfig-2.yaml | 4 +- testdata/kubeconfig.yaml | 16 +++-- 8 files changed, 185 insertions(+), 17 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0523498c87b..385566877fc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "jsonpath-plus": "^10.2.0", "request": "^2.88.0", "rfc4648": "^1.3.0", + "socks-proxy-agent": "^8.0.4", "stream-buffers": "^3.0.2", "tar": "^7.0.0", "tslib": "^2.4.1", @@ -1300,6 +1301,18 @@ "@types/node": "*" } }, + "node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/aggregate-error": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", @@ -1796,7 +1809,6 @@ "version": "4.3.5", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", - "dev": true, "dependencies": { "ms": "2.1.2" }, @@ -1812,8 +1824,7 @@ "node_modules/debug/node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/decamelize": { "version": "1.2.0", @@ -2429,6 +2440,31 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, + "node_modules/ip-address": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", + "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "license": "MIT", + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/ip-address/node_modules/jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", + "license": "MIT" + }, + "node_modules/ip-address/node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "license": "BSD-3-Clause" + }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -4145,6 +4181,44 @@ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", + "integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==", + "license": "MIT", + "dependencies": { + "ip-address": "^9.0.5", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.4.tgz", + "integrity": "sha512-GNAq/eg8Udq2x0eNiFkr9gRg5bA7PXEWagQdeRX4cPSG+X/8V38v637gim9bjFptMk1QWsCTr0ttrJEiXbNnRw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.1", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -5813,6 +5887,14 @@ "@types/node": "*" } }, + "agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "requires": { + "debug": "^4.3.4" + } + }, "aggregate-error": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", @@ -6178,7 +6260,6 @@ "version": "4.3.5", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", - "dev": true, "requires": { "ms": "2.1.2" }, @@ -6186,8 +6267,7 @@ "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" } } }, @@ -6617,6 +6697,27 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, + "ip-address": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", + "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "requires": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "dependencies": { + "jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==" + }, + "sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==" + } + } + }, "is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -7866,6 +7967,30 @@ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true }, + "smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==" + }, + "socks": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", + "integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==", + "requires": { + "ip-address": "^9.0.5", + "smart-buffer": "^4.2.0" + } + }, + "socks-proxy-agent": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.4.tgz", + "integrity": "sha512-GNAq/eg8Udq2x0eNiFkr9gRg5bA7PXEWagQdeRX4cPSG+X/8V38v637gim9bjFptMk1QWsCTr0ttrJEiXbNnRw==", + "requires": { + "agent-base": "^7.1.1", + "debug": "^4.3.4", + "socks": "^2.8.3" + } + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", diff --git a/package.json b/package.json index 21a8f77edbc..fb6add02532 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,8 @@ "stream-buffers": "^3.0.2", "tar": "^7.0.0", "tslib": "^2.4.1", - "ws": "^8.18.0" + "ws": "^8.18.0", + "socks-proxy-agent": "^8.0.4" }, "devDependencies": { "@types/byline": "^4.2.31", diff --git a/src/config.ts b/src/config.ts index 73a3f89f056..9e3a57e17d3 100644 --- a/src/config.ts +++ b/src/config.ts @@ -7,6 +7,7 @@ import path = require('path'); import request = require('request'); import WebSocket = require('ws'); +import SocksProxyAgent = require('socks-proxy-agent'); import * as api from './api'; import { Authenticator } from './auth'; @@ -170,6 +171,14 @@ export class KubeConfig { if (cluster && cluster.tlsServerName) { opts.agentOptions = { servername: cluster.tlsServerName } as https.AgentOptions; } + + if (cluster?.proxyUrl) { + if (cluster.proxyUrl.startsWith('socks')) { + opts.agent = new SocksProxyAgent.SocksProxyAgent(cluster.proxyUrl); + } else { + opts.proxy = cluster.proxyUrl; + } + } } public loadFromString(config: string, opts?: Partial): void { diff --git a/src/config_test.ts b/src/config_test.ts index ee807ec2a79..1d3cff3244d 100644 --- a/src/config_test.ts +++ b/src/config_test.ts @@ -36,6 +36,7 @@ function validateFileLoad(kc: KubeConfig) { expect(cluster1.name).to.equal('cluster1'); expect(cluster1.caData).to.equal('Q0FEQVRB'); expect(cluster1.server).to.equal('http://example.com'); + expect(cluster1.proxyUrl).to.equal('socks5://localhost:1181'); expect(cluster2.name).to.equal('cluster2'); expect(cluster2.caData).to.equal('Q0FEQVRBMg=='); expect(cluster2.server).to.equal('http://example2.com'); @@ -403,6 +404,33 @@ describe('KubeConfig', () => { rejectUnauthorized: false, }); }); + it('should apply proxy to request.Options', async () => { + const kc = new KubeConfig(); + kc.loadFromFile(kc2FileName); + + const opts = {} as requestlib.Options; + + await kc.applyToRequest(opts); + console.log(opts); + + expect(opts).to.deep.equal({ + headers: {}, + ca: Buffer.from('CADAT@', 'utf-8'), + cert: Buffer.from(']SER_CADATA', 'utf-8'), + key: Buffer.from(']SER_CKDATA', 'utf-8'), + proxy: 'https://localhost:1181', + }); + }); + it('should apply agent to request.Options', async () => { + const kc = new KubeConfig(); + kc.loadFromFile(kcFileName); + + const opts = {} as requestlib.Options; + + await kc.applyToRequest(opts); + + expect(opts.agent).to.exist; + }); }); describe('loadClusterConfigObjects', () => { diff --git a/src/config_types.ts b/src/config_types.ts index 8da7bf4064e..2cf87542687 100644 --- a/src/config_types.ts +++ b/src/config_types.ts @@ -22,6 +22,7 @@ export interface Cluster { readonly server: string; readonly skipTLSVerify?: boolean; readonly tlsServerName?: string; + readonly proxyUrl?: string; } export function newClusters(a: any, opts?: Partial): Cluster[] { @@ -43,6 +44,7 @@ export function exportCluster(cluster: Cluster): any { 'certificate-authority': cluster.caFile, 'insecure-skip-tls-verify': cluster.skipTLSVerify, 'tls-server-name': cluster.tlsServerName, + 'proxy-url': cluster.proxyUrl, }, }; } @@ -68,6 +70,7 @@ function clusterIterator( server: elt.cluster.server.replace(/\/$/, ''), skipTLSVerify: elt.cluster['insecure-skip-tls-verify'] === true, tlsServerName: elt.cluster['tls-server-name'], + proxyUrl: elt.cluster['proxy-url'], }; } catch (err) { switch (onInvalidEntry) { diff --git a/src/web-socket-handler.ts b/src/web-socket-handler.ts index 31d2332823c..2b78b4e0024 100644 --- a/src/web-socket-handler.ts +++ b/src/web-socket-handler.ts @@ -126,7 +126,7 @@ export class WebSocketHandler implements WebSocketInterface { if (data instanceof Buffer) { data.copy(buff, 1); } else { - buff.write(data, 1); + buff.write(data as string, 1); } let i = 0; diff --git a/testdata/kubeconfig-2.yaml b/testdata/kubeconfig-2.yaml index db0b80d028f..aadbab1b8ae 100644 --- a/testdata/kubeconfig-2.yaml +++ b/testdata/kubeconfig-2.yaml @@ -1,8 +1,9 @@ apiVersion: v1 clusters: - cluster: - certificate-authority-data: Q0FEQVRA + certificate-authority-data: Q0FEQVRA server: http://example2.com + proxy-url: https://localhost:1181 name: clusterA contexts: @@ -27,4 +28,3 @@ users: user: username: foo password: bar - \ No newline at end of file diff --git a/testdata/kubeconfig.yaml b/testdata/kubeconfig.yaml index 7b9e0dfca0d..35310fd6b2c 100644 --- a/testdata/kubeconfig.yaml +++ b/testdata/kubeconfig.yaml @@ -1,20 +1,22 @@ apiVersion: v1 clusters: - cluster: - certificate-authority-data: Q0FEQVRB - server: http://example.com - name: cluster1 + certificate-authority-data: Q0FEQVRB + server: http://example.com + proxy-url: socks5://localhost:1181 + name: cluster1 - cluster: certificate-authority-data: Q0FEQVRBMg== server: http://example2.com insecure-skip-tls-verify: true + proxy-url: socks5://example2:8888 name: cluster2 contexts: - context: - cluster: cluster1 + cluster: cluster1 user: user1 - name: context1 + name: context1 - context: cluster: cluster2 namespace: namespace2 @@ -25,7 +27,7 @@ contexts: user: user3 name: passwd -current-context: context2 +current-context: context2 kind: Config preferences: {} users: @@ -40,4 +42,4 @@ users: - name: user3 user: username: foo - password: bar \ No newline at end of file + password: bar From 9f3bcd34f578784c291ff949c80914b31b8f2bd8 Mon Sep 17 00:00:00 2001 From: krmodelski <55512362+krmodelski@users.noreply.github.com> Date: Thu, 5 Dec 2024 20:48:00 +0100 Subject: [PATCH 2/4] add proxy-url test kubeconfig --- src/config_test.ts | 3 ++- testdata/kubeconfig-proxy-url.yaml | 30 ++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 testdata/kubeconfig-proxy-url.yaml diff --git a/src/config_test.ts b/src/config_test.ts index 1d3cff3244d..9b91f2cf44f 100644 --- a/src/config_test.ts +++ b/src/config_test.ts @@ -18,6 +18,7 @@ const kc2FileName = 'testdata/kubeconfig-2.yaml'; const kcDupeCluster = 'testdata/kubeconfig-dupe-cluster.yaml'; const kcDupeContext = 'testdata/kubeconfig-dupe-context.yaml'; const kcDupeUser = 'testdata/kubeconfig-dupe-user.yaml'; +const kcProxyUrl = 'testdata/kubeconfig-proxy-url.yaml'; const kcNoUserFileName = 'testdata/empty-user-kubeconfig.yaml'; const kcInvalidContextFileName = 'testdata/empty-context-kubeconfig.yaml'; @@ -423,7 +424,7 @@ describe('KubeConfig', () => { }); it('should apply agent to request.Options', async () => { const kc = new KubeConfig(); - kc.loadFromFile(kcFileName); + kc.loadFromFile(kcProxyUrl); const opts = {} as requestlib.Options; diff --git a/testdata/kubeconfig-proxy-url.yaml b/testdata/kubeconfig-proxy-url.yaml new file mode 100644 index 00000000000..38d425a3627 --- /dev/null +++ b/testdata/kubeconfig-proxy-url.yaml @@ -0,0 +1,30 @@ +apiVersion: v1 +clusters: +- cluster: + certificate-authority-data: Q0FEQVRA + server: http://example2.com + proxy-url: https://example:1187 + name: clusterA + +contexts: +- context: + cluster: clusterA + user: userA + name: contextA + +current-context: contextA +kind: Config +preferences: {} +users: +- name: userA + user: + client-certificate-data: XVNFUl9DQURBVEE= + client-key-data: XVNFUl9DS0RBVEE= +- name: userB + user: + client-certificate-data: XVNFUjJfQ0FEQVRB + client-key-data: XVNFUjJfQ0tEQVRB +- name: userC + user: + username: foo + password: bar From 869c0e704e326dd6d6bb73b4b761017cef25dd54 Mon Sep 17 00:00:00 2001 From: krmodelski <55512362+krmodelski@users.noreply.github.com> Date: Thu, 5 Dec 2024 20:57:01 +0100 Subject: [PATCH 3/4] update proxy-url tests --- src/config_test.ts | 3 --- testdata/kubeconfig-proxy-url.yaml | 2 +- testdata/kubeconfig.yaml | 1 - 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/config_test.ts b/src/config_test.ts index 9b91f2cf44f..50435c49876 100644 --- a/src/config_test.ts +++ b/src/config_test.ts @@ -412,7 +412,6 @@ describe('KubeConfig', () => { const opts = {} as requestlib.Options; await kc.applyToRequest(opts); - console.log(opts); expect(opts).to.deep.equal({ headers: {}, @@ -427,9 +426,7 @@ describe('KubeConfig', () => { kc.loadFromFile(kcProxyUrl); const opts = {} as requestlib.Options; - await kc.applyToRequest(opts); - expect(opts.agent).to.exist; }); }); diff --git a/testdata/kubeconfig-proxy-url.yaml b/testdata/kubeconfig-proxy-url.yaml index 38d425a3627..6e994b098dd 100644 --- a/testdata/kubeconfig-proxy-url.yaml +++ b/testdata/kubeconfig-proxy-url.yaml @@ -3,7 +3,7 @@ clusters: - cluster: certificate-authority-data: Q0FEQVRA server: http://example2.com - proxy-url: https://example:1187 + proxy-url: socks://example:1187 name: clusterA contexts: diff --git a/testdata/kubeconfig.yaml b/testdata/kubeconfig.yaml index 35310fd6b2c..94ffdc13470 100644 --- a/testdata/kubeconfig.yaml +++ b/testdata/kubeconfig.yaml @@ -9,7 +9,6 @@ clusters: certificate-authority-data: Q0FEQVRBMg== server: http://example2.com insecure-skip-tls-verify: true - proxy-url: socks5://example2:8888 name: cluster2 contexts: From f7fe1fe7bfec0e325bc1b02c9638ac8c85d365cf Mon Sep 17 00:00:00 2001 From: krmodelski <55512362+krmodelski@users.noreply.github.com> Date: Sat, 7 Dec 2024 12:48:28 +0100 Subject: [PATCH 4/4] refactor proxyUrl condition --- src/config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config.ts b/src/config.ts index 9e3a57e17d3..fa836f0b3e3 100644 --- a/src/config.ts +++ b/src/config.ts @@ -172,7 +172,7 @@ export class KubeConfig { opts.agentOptions = { servername: cluster.tlsServerName } as https.AgentOptions; } - if (cluster?.proxyUrl) { + if (cluster && cluster.proxyUrl) { if (cluster.proxyUrl.startsWith('socks')) { opts.agent = new SocksProxyAgent.SocksProxyAgent(cluster.proxyUrl); } else {