From 44d8582a30b186743c7858a9eecc2b55106232ba Mon Sep 17 00:00:00 2001 From: krmodelski <55512362+krmodelski@users.noreply.github.com> Date: Mon, 16 Dec 2024 22:33:54 +0100 Subject: [PATCH 01/12] add proxy support --- package-lock.json | 146 +++++++++++++++++++++++++++-- package.json | 4 +- src/config.ts | 26 ++++- src/config_types.ts | 3 + testdata/kubeconfig-proxy-url.yaml | 30 ++++++ 5 files changed, 201 insertions(+), 8 deletions(-) create mode 100644 testdata/kubeconfig-proxy-url.yaml diff --git a/package-lock.json b/package-lock.json index 777f943373..c97ab803d0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,12 +16,14 @@ "@types/tar": "^6.1.1", "@types/ws": "^8.5.4", "form-data": "^4.0.0", + "hpagent": "^1.2.0", "isomorphic-ws": "^5.0.0", "js-yaml": "^4.1.0", "jsonpath-plus": "^10.2.0", "node-fetch": "^2.6.9", "openid-client": "^6.1.3", "rfc4648": "^1.3.0", + "socks-proxy-agent": "^8.0.4", "stream-buffers": "^3.0.2", "tar": "^7.0.0", "tmp-promise": "^3.0.2", @@ -821,6 +823,15 @@ "@types/node": "*" } }, + "node_modules/agent-base": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, "node_modules/ansi-colors": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", @@ -1161,7 +1172,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" }, @@ -1177,8 +1187,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/deep-eql": { "version": "5.0.2", @@ -1500,6 +1509,15 @@ "he": "bin/he" } }, + "node_modules/hpagent": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/hpagent/-/hpagent-1.2.0.tgz", + "integrity": "sha512-A91dYTeIB6NoXG+PxTQpCCDDnfHsW9kc06Lvpu1TEe9gnd6ZFeiBoRO9JvzEv6xK7EX97/dUE8g/vBMTqTS3CA==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -1555,6 +1573,25 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" }, + "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/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", @@ -1831,6 +1868,12 @@ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, + "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/jsep": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/jsep/-/jsep-1.4.0.tgz", @@ -2826,6 +2869,44 @@ "url": "https://github.com/sponsors/isaacs" } }, + "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.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/source-map-support": { "version": "0.5.21", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", @@ -4008,6 +4089,11 @@ "@types/node": "*" } }, + "agent-base": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==" + }, "ansi-colors": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", @@ -4269,7 +4355,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" }, @@ -4277,8 +4362,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==" } } }, @@ -4501,6 +4585,11 @@ "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true }, + "hpagent": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/hpagent/-/hpagent-1.2.0.tgz", + "integrity": "sha512-A91dYTeIB6NoXG+PxTQpCCDDnfHsW9kc06Lvpu1TEe9gnd6ZFeiBoRO9JvzEv6xK7EX97/dUE8g/vBMTqTS3CA==" + }, "html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -4539,6 +4628,22 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" }, + "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": { + "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", @@ -4737,6 +4842,11 @@ } } }, + "jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==" + }, "jsep": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/jsep/-/jsep-1.4.0.tgz", @@ -5433,6 +5543,30 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==" }, + "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.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "requires": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + } + }, "source-map-support": { "version": "0.5.21", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", diff --git a/package.json b/package.json index a36961ef63..69946c9157 100644 --- a/package.json +++ b/package.json @@ -72,7 +72,9 @@ "tar": "^7.0.0", "tmp-promise": "^3.0.2", "tslib": "^2.5.0", - "ws": "^8.18.0" + "ws": "^8.18.0", + "socks-proxy-agent": "^8.0.4", + "hpagent": "^1.2.0" }, "devDependencies": { "@types/chai": "^5.0.0", diff --git a/src/config.ts b/src/config.ts index f8237710de..c1b7fede18 100644 --- a/src/config.ts +++ b/src/config.ts @@ -33,6 +33,8 @@ import { import { OpenIDConnectAuth } from './oidc_auth.js'; import WebSocket from 'isomorphic-ws'; import child_process from 'node:child_process'; +import { SocksProxyAgent } from 'socks-proxy-agent'; +import { HttpProxyAgent, HttpsProxyAgent } from 'hpagent'; const SERVICEACCOUNT_ROOT: string = '/var/run/secrets/kubernetes.io/serviceaccount'; const SERVICEACCOUNT_CA_PATH: string = SERVICEACCOUNT_ROOT + '/ca.crt'; @@ -248,7 +250,29 @@ export class KubeConfig implements SecurityAuthentication { agentOptions.passphrase = httpsOptions.passphrase; agentOptions.rejectUnauthorized = httpsOptions.rejectUnauthorized; - context.setAgent(new https.Agent(agentOptions)); + let agent: https.Agent | SocksProxyAgent | HttpProxyAgent | HttpsProxyAgent; + + if (cluster && cluster.proxyUrl) { + if (cluster.proxyUrl.startsWith('socks')) { + agent = new SocksProxyAgent(cluster.proxyUrl, agentOptions); + } else if (cluster.proxyUrl.startsWith('http')) { + agent = new HttpsProxyAgent({ + proxy: cluster.proxyUrl, + ...agentOptions, + }); + } else if (cluster.proxyUrl.startsWith('https')) { + agent = new HttpProxyAgent({ + proxy: cluster.proxyUrl, + ...agentOptions, + }); + } else { + throw new Error('Unsupported proxy type'); + } + } else { + agent = new https.Agent(agentOptions); + } + + context.setAgent(agent); } /** diff --git a/src/config_types.ts b/src/config_types.ts index e35ae6f7cf..f628e1ad68 100644 --- a/src/config_types.ts +++ b/src/config_types.ts @@ -22,6 +22,7 @@ export interface Cluster { readonly server: string; readonly tlsServerName?: string; readonly skipTLSVerify: boolean; + 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/testdata/kubeconfig-proxy-url.yaml b/testdata/kubeconfig-proxy-url.yaml new file mode 100644 index 0000000000..db0f6d63fc --- /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: socks://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 012e44c4f6bd1d165b08b1db00bb52dc3c68a493 Mon Sep 17 00:00:00 2001 From: krmodelski <55512362+krmodelski@users.noreply.github.com> Date: Mon, 16 Dec 2024 22:47:30 +0100 Subject: [PATCH 02/12] remove proxy-url test kubeconfig --- testdata/kubeconfig-proxy-url.yaml | 30 ------------------------------ 1 file changed, 30 deletions(-) delete mode 100644 testdata/kubeconfig-proxy-url.yaml diff --git a/testdata/kubeconfig-proxy-url.yaml b/testdata/kubeconfig-proxy-url.yaml deleted file mode 100644 index db0f6d63fc..0000000000 --- a/testdata/kubeconfig-proxy-url.yaml +++ /dev/null @@ -1,30 +0,0 @@ -apiVersion: v1 -clusters: - - cluster: - certificate-authority-data: Q0FEQVRA - server: http://example2.com - proxy-url: socks://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 511662e9fe84d3714286cca74861fdd408dc57a6 Mon Sep 17 00:00:00 2001 From: krmodelski <55512362+krmodelski@users.noreply.github.com> Date: Tue, 17 Dec 2024 09:33:02 +0100 Subject: [PATCH 03/12] fix dev in package-lock.json --- package-lock.json | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index c97ab803d0..02ae408472 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1172,6 +1172,7 @@ "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" }, @@ -1187,7 +1188,8 @@ "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==" + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true }, "node_modules/deep-eql": { "version": "5.0.2", @@ -4355,6 +4357,7 @@ "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" }, @@ -4362,7 +4365,8 @@ "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true } } }, From d7a29eef0ac554464e744b68d1e3c53731d864f5 Mon Sep 17 00:00:00 2001 From: krmodelski <55512362+krmodelski@users.noreply.github.com> Date: Tue, 17 Dec 2024 09:42:42 +0100 Subject: [PATCH 04/12] refactor proxy type condition --- src/config.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/config.ts b/src/config.ts index c1b7fede18..65f3aed376 100644 --- a/src/config.ts +++ b/src/config.ts @@ -255,13 +255,13 @@ export class KubeConfig implements SecurityAuthentication { if (cluster && cluster.proxyUrl) { if (cluster.proxyUrl.startsWith('socks')) { agent = new SocksProxyAgent(cluster.proxyUrl, agentOptions); - } else if (cluster.proxyUrl.startsWith('http')) { - agent = new HttpsProxyAgent({ + } else if (cluster.server.startsWith('http')) { + agent = new HttpProxyAgent({ proxy: cluster.proxyUrl, ...agentOptions, }); - } else if (cluster.proxyUrl.startsWith('https')) { - agent = new HttpProxyAgent({ + } else if (cluster.server.startsWith('https')) { + agent = new HttpsProxyAgent({ proxy: cluster.proxyUrl, ...agentOptions, }); From 1d47564ae9fc02abf358ad4887c34d6e13b9aed8 Mon Sep 17 00:00:00 2001 From: krmodelski <55512362+krmodelski@users.noreply.github.com> Date: Tue, 17 Dec 2024 19:34:36 +0100 Subject: [PATCH 05/12] refactor proxy condition --- src/config.ts | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/config.ts b/src/config.ts index 65f3aed376..6913bf278c 100644 --- a/src/config.ts +++ b/src/config.ts @@ -34,7 +34,7 @@ import { OpenIDConnectAuth } from './oidc_auth.js'; import WebSocket from 'isomorphic-ws'; import child_process from 'node:child_process'; import { SocksProxyAgent } from 'socks-proxy-agent'; -import { HttpProxyAgent, HttpsProxyAgent } from 'hpagent'; +import { HttpProxyAgent, HttpProxyAgentOptions, HttpsProxyAgent, HttpsProxyAgentOptions } from 'hpagent'; const SERVICEACCOUNT_ROOT: string = '/var/run/secrets/kubernetes.io/serviceaccount'; const SERVICEACCOUNT_CA_PATH: string = SERVICEACCOUNT_ROOT + '/ca.crt'; @@ -254,17 +254,16 @@ export class KubeConfig implements SecurityAuthentication { if (cluster && cluster.proxyUrl) { if (cluster.proxyUrl.startsWith('socks')) { + agentOptions.rejectUnauthorized = false; agent = new SocksProxyAgent(cluster.proxyUrl, agentOptions); - } else if (cluster.server.startsWith('http')) { - agent = new HttpProxyAgent({ - proxy: cluster.proxyUrl, - ...agentOptions, - }); } else if (cluster.server.startsWith('https')) { - agent = new HttpsProxyAgent({ - proxy: cluster.proxyUrl, - ...agentOptions, - }); + const httpsProxyAgentOptions: HttpsProxyAgentOptions = agentOptions as HttpsProxyAgentOptions; + httpsProxyAgentOptions.proxy = cluster.proxyUrl; + agent = new HttpsProxyAgent(httpsProxyAgentOptions); + } else if (cluster.server.startsWith('http')) { + const httpProxyAgentOptions: HttpProxyAgentOptions = agentOptions as HttpProxyAgentOptions; + httpProxyAgentOptions.proxy = cluster.proxyUrl; + agent = new HttpProxyAgent(httpProxyAgentOptions); } else { throw new Error('Unsupported proxy type'); } From 53b3b735424a8cd4e7bd12bdb9f03027ccb5c8c8 Mon Sep 17 00:00:00 2001 From: krmodelski <55512362+krmodelski@users.noreply.github.com> Date: Wed, 18 Dec 2024 22:27:29 +0100 Subject: [PATCH 06/12] add proxy-url tests --- src/config_test.ts | 67 ++++++++++++++++++++++++++++ testdata/kubeconfig-proxy-url.yaml | 61 +++++++++++++++++++++++++ testdata/kubeconfig.yaml | 71 +++++++++++++++--------------- 3 files changed, 164 insertions(+), 35 deletions(-) create mode 100644 testdata/kubeconfig-proxy-url.yaml diff --git a/src/config_test.ts b/src/config_test.ts index a9171d79d5..1505368c98 100644 --- a/src/config_test.ts +++ b/src/config_test.ts @@ -15,12 +15,15 @@ import { CoreV1Api, RequestContext } from './api.js'; import { bufferFromFileOrString, findHomeDir, findObject, KubeConfig, makeAbsolutePath } from './config.js'; import { ActionOnInvalid, Cluster, newClusters, newContexts, newUsers, User } from './config_types.js'; import { ExecAuth } from './exec_auth.js'; +import { HttpProxyAgent, HttpsProxyAgent } from 'hpagent'; +import { SocksProxyAgent } from 'socks-proxy-agent'; const kcFileName = 'testdata/kubeconfig.yaml'; 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'; @@ -43,6 +46,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'); @@ -358,6 +362,69 @@ describe('KubeConfig', () => { assertRequestOptionsEqual(opts, expectedOptions); }); + it('should apply socks proxy', async () => { + const kc = new KubeConfig(); + kc.loadFromFile(kcProxyUrl); + kc.setCurrentContext('contextA'); + + const testServerName = 'https://example.com'; + const rc = new RequestContext(testServerName, HttpMethod.GET); + + await kc.applySecurityAuthentication(rc); + const expectedCA = Buffer.from('CADAT@', 'utf-8'); + const expectedProxyHost = 'example'; + const expectedProxyPort = 1187; + + expect(rc.getAgent()).to.be.instanceOf(SocksProxyAgent); + const agent = rc.getAgent() as SocksProxyAgent; + expect(agent.options.ca?.toString()).to.equal(expectedCA.toString()); + expect(agent.proxy.host).to.equal(expectedProxyHost); + expect(agent.proxy.port).to.equal(expectedProxyPort); + }); + it('should apply https proxy', async () => { + const kc = new KubeConfig(); + kc.loadFromFile(kcProxyUrl); + kc.setCurrentContext('contextB'); + + const testServerName = 'https://example.com'; + const rc = new RequestContext(testServerName, HttpMethod.GET); + + await kc.applySecurityAuthentication(rc); + const expectedCA = Buffer.from('CADAT@', 'utf-8'); + const expectedProxyHref = 'http://example:9443/'; + + expect(rc.getAgent()).to.be.instanceOf(HttpsProxyAgent); + const agent = rc.getAgent() as HttpsProxyAgent; + expect(agent.options.ca?.toString()).to.equal(expectedCA.toString()); + expect(agent.proxy.href).to.equal(expectedProxyHref); + }); + it('should apply http proxy', async () => { + const kc = new KubeConfig(); + kc.loadFromFile(kcProxyUrl); + kc.setCurrentContext('contextC'); + + const testServerName = 'https://example.com'; + const rc = new RequestContext(testServerName, HttpMethod.GET); + + await kc.applySecurityAuthentication(rc); + const expectedCA = Buffer.from('CADAT@', 'utf-8'); + const expectedProxyHref = 'http://example:8080/'; + + expect(rc.getAgent()).to.be.instanceOf(HttpProxyAgent); + const agent = rc.getAgent() as HttpProxyAgent; + expect(agent.options.ca?.toString()).to.equal(expectedCA.toString()); + expect(agent.proxy.href).to.equal(expectedProxyHref); + }); + it('should throw an error if proxy-url is provided but the server protocol is not http or https', async () => { + const kc = new KubeConfig(); + kc.loadFromFile(kcProxyUrl); + kc.setCurrentContext('contextD'); + + const testServerName = 'https://example.com'; + const rc = new RequestContext(testServerName, HttpMethod.GET); + + return expect(kc.applySecurityAuthentication(rc)).to.be.rejectedWith('Unsupported proxy type'); + }); }); describe('loadClusterConfigObjects', () => { diff --git a/testdata/kubeconfig-proxy-url.yaml b/testdata/kubeconfig-proxy-url.yaml new file mode 100644 index 0000000000..8d88117af9 --- /dev/null +++ b/testdata/kubeconfig-proxy-url.yaml @@ -0,0 +1,61 @@ +apiVersion: v1 +clusters: + - cluster: + certificate-authority-data: Q0FEQVRA + server: http://example1.com + proxy-url: socks5://example:1187 + name: clusterA + - cluster: + certificate-authority-data: Q0FEQVRA + server: https://example2.com + proxy-url: http://example:9443 + name: clusterB + - cluster: + certificate-authority-data: Q0FEQVRA + server: http://example3.com + proxy-url: http://example:8080 + name: clusterC + - cluster: + certificate-authority-data: Q0FEQVRA + server: htto://exampleerror.com + proxy-url: http://example:8080 + name: clusterD + +contexts: + - context: + cluster: clusterA + user: userA + name: contextA + - context: + cluster: clusterB + user: userB + name: contextB + - context: + cluster: clusterC + user: userC + name: contextC + - context: + cluster: clusterD + user: userD + name: contextD + +current-context: contextA +kind: Config +preferences: {} +users: + - name: userA + user: + client-certificate-data: XVNFUl9DQURBVEE= + client-key-data: XVNFUl9DS0RBVEE= + - name: userB + user: + client-certificate-data: XVNFUl9DQURBVEE= + client-key-data: XVNFUl9DS0RBVEE= + - name: userC + user: + client-certificate-data: XVNFUl9DQURBVEE= + client-key-data: XVNFUl9DS0RBVEE= + - name: userD + user: + client-certificate-data: XVNFUl9DQURBVEE= + client-key-data: XVNFUl9DS0RBVEE= diff --git a/testdata/kubeconfig.yaml b/testdata/kubeconfig.yaml index 7b9e0dfca0..c04c3e17c6 100644 --- a/testdata/kubeconfig.yaml +++ b/testdata/kubeconfig.yaml @@ -1,43 +1,44 @@ apiVersion: v1 clusters: -- cluster: - certificate-authority-data: Q0FEQVRB - server: http://example.com - name: cluster1 -- cluster: - certificate-authority-data: Q0FEQVRBMg== - server: http://example2.com - insecure-skip-tls-verify: true - name: cluster2 + - cluster: + 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 + name: cluster2 contexts: -- context: - cluster: cluster1 - user: user1 - name: context1 -- context: - cluster: cluster2 - namespace: namespace2 - user: user2 - name: context2 -- context: - cluster: cluster2 - user: user3 - name: passwd + - context: + cluster: cluster1 + user: user1 + name: context1 + - context: + cluster: cluster2 + namespace: namespace2 + user: user2 + name: context2 + - context: + cluster: cluster2 + user: user3 + name: passwd -current-context: context2 +current-context: context2 kind: Config preferences: {} users: -- name: user1 - user: - client-certificate-data: VVNFUl9DQURBVEE= - client-key-data: VVNFUl9DS0RBVEE= -- name: user2 - user: - client-certificate-data: VVNFUjJfQ0FEQVRB - client-key-data: VVNFUjJfQ0tEQVRB -- name: user3 - user: - username: foo - password: bar \ No newline at end of file + - name: user1 + user: + client-certificate-data: VVNFUl9DQURBVEE= + client-key-data: VVNFUl9DS0RBVEE= + - name: user2 + user: + client-certificate-data: VVNFUjJfQ0FEQVRB + client-key-data: VVNFUjJfQ0tEQVRB + - name: user3 + user: + username: foo + password: bar From be1550e17cbded9d98904338ff22015063310f22 Mon Sep 17 00:00:00 2001 From: krmodelski <55512362+krmodelski@users.noreply.github.com> Date: Wed, 18 Dec 2024 22:32:24 +0100 Subject: [PATCH 07/12] add proxy-url tests From d5e80aa91ba9481ad92f2f4d2290c70f5f5763eb Mon Sep 17 00:00:00 2001 From: krmodelski <55512362+krmodelski@users.noreply.github.com> Date: Wed, 18 Dec 2024 22:36:30 +0100 Subject: [PATCH 08/12] add missing test value From c7fb1e4cb004e8d37c6b0651cbd93f6704259ce1 Mon Sep 17 00:00:00 2001 From: krmodelski <55512362+krmodelski@users.noreply.github.com> Date: Wed, 18 Dec 2024 22:38:02 +0100 Subject: [PATCH 09/12] add missing test value --- testdata/kubeconfig.yaml | 70 ++++++++++++++++++++-------------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/testdata/kubeconfig.yaml b/testdata/kubeconfig.yaml index c04c3e17c6..94ffdc1347 100644 --- a/testdata/kubeconfig.yaml +++ b/testdata/kubeconfig.yaml @@ -1,44 +1,44 @@ apiVersion: v1 clusters: - - cluster: - 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 - name: cluster2 +- cluster: + 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 + name: cluster2 contexts: - - context: - cluster: cluster1 - user: user1 - name: context1 - - context: - cluster: cluster2 - namespace: namespace2 - user: user2 - name: context2 - - context: - cluster: cluster2 - user: user3 - name: passwd +- context: + cluster: cluster1 + user: user1 + name: context1 +- context: + cluster: cluster2 + namespace: namespace2 + user: user2 + name: context2 +- context: + cluster: cluster2 + user: user3 + name: passwd current-context: context2 kind: Config preferences: {} users: - - name: user1 - user: - client-certificate-data: VVNFUl9DQURBVEE= - client-key-data: VVNFUl9DS0RBVEE= - - name: user2 - user: - client-certificate-data: VVNFUjJfQ0FEQVRB - client-key-data: VVNFUjJfQ0tEQVRB - - name: user3 - user: - username: foo - password: bar +- name: user1 + user: + client-certificate-data: VVNFUl9DQURBVEE= + client-key-data: VVNFUl9DS0RBVEE= +- name: user2 + user: + client-certificate-data: VVNFUjJfQ0FEQVRB + client-key-data: VVNFUjJfQ0tEQVRB +- name: user3 + user: + username: foo + password: bar From 6b0fe153e6bbc81cc692a78735adec6546139394 Mon Sep 17 00:00:00 2001 From: krmodelski <55512362+krmodelski@users.noreply.github.com> Date: Thu, 19 Dec 2024 16:15:15 +0100 Subject: [PATCH 10/12] remove socks proxy rejectUnauthorized --- src/config.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/config.ts b/src/config.ts index 6913bf278c..2e15185927 100644 --- a/src/config.ts +++ b/src/config.ts @@ -254,7 +254,6 @@ export class KubeConfig implements SecurityAuthentication { if (cluster && cluster.proxyUrl) { if (cluster.proxyUrl.startsWith('socks')) { - agentOptions.rejectUnauthorized = false; agent = new SocksProxyAgent(cluster.proxyUrl, agentOptions); } else if (cluster.server.startsWith('https')) { const httpsProxyAgentOptions: HttpsProxyAgentOptions = agentOptions as HttpsProxyAgentOptions; From 934d358c69e3c8fe58d202c569129c838f5196de Mon Sep 17 00:00:00 2001 From: krmodelski <55512362+krmodelski@users.noreply.github.com> Date: Thu, 19 Dec 2024 16:31:49 +0100 Subject: [PATCH 11/12] sort package.json dependencies --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 69946c9157..5e211691bd 100644 --- a/package.json +++ b/package.json @@ -62,19 +62,19 @@ "@types/tar": "^6.1.1", "@types/ws": "^8.5.4", "form-data": "^4.0.0", + "hpagent": "^1.2.0", "isomorphic-ws": "^5.0.0", "js-yaml": "^4.1.0", "jsonpath-plus": "^10.2.0", "node-fetch": "^2.6.9", "openid-client": "^6.1.3", "rfc4648": "^1.3.0", + "socks-proxy-agent": "^8.0.4", "stream-buffers": "^3.0.2", "tar": "^7.0.0", "tmp-promise": "^3.0.2", "tslib": "^2.5.0", - "ws": "^8.18.0", - "socks-proxy-agent": "^8.0.4", - "hpagent": "^1.2.0" + "ws": "^8.18.0" }, "devDependencies": { "@types/chai": "^5.0.0", From d02abae59c456859b5db94c37498d24e15ed47d5 Mon Sep 17 00:00:00 2001 From: krmodelski <55512362+krmodelski@users.noreply.github.com> Date: Thu, 19 Dec 2024 23:49:57 +0100 Subject: [PATCH 12/12] add createAgent method --- src/config.ts | 51 +++++++++++++++++++++++++++++---------------------- 1 file changed, 29 insertions(+), 22 deletions(-) diff --git a/src/config.ts b/src/config.ts index 2e15185927..a3c1603e91 100644 --- a/src/config.ts +++ b/src/config.ts @@ -173,6 +173,7 @@ export class KubeConfig implements SecurityAuthentication { public async applyToHTTPSOptions(opts: https.RequestOptions | WebSocket.ClientOptions): Promise { const user = this.getCurrentUser(); + const cluster = this.getCurrentCluster(); await this.applyOptions(opts); @@ -207,7 +208,7 @@ export class KubeConfig implements SecurityAuthentication { agentOptions.secureProtocol = opts.secureProtocol; agentOptions.sessionIdContext = opts.sessionIdContext; - opts.agent = new https.Agent(agentOptions); + opts.agent = this.createAgent(cluster, agentOptions); } /** @@ -250,27 +251,7 @@ export class KubeConfig implements SecurityAuthentication { agentOptions.passphrase = httpsOptions.passphrase; agentOptions.rejectUnauthorized = httpsOptions.rejectUnauthorized; - let agent: https.Agent | SocksProxyAgent | HttpProxyAgent | HttpsProxyAgent; - - if (cluster && cluster.proxyUrl) { - if (cluster.proxyUrl.startsWith('socks')) { - agent = new SocksProxyAgent(cluster.proxyUrl, agentOptions); - } else if (cluster.server.startsWith('https')) { - const httpsProxyAgentOptions: HttpsProxyAgentOptions = agentOptions as HttpsProxyAgentOptions; - httpsProxyAgentOptions.proxy = cluster.proxyUrl; - agent = new HttpsProxyAgent(httpsProxyAgentOptions); - } else if (cluster.server.startsWith('http')) { - const httpProxyAgentOptions: HttpProxyAgentOptions = agentOptions as HttpProxyAgentOptions; - httpProxyAgentOptions.proxy = cluster.proxyUrl; - agent = new HttpProxyAgent(httpProxyAgentOptions); - } else { - throw new Error('Unsupported proxy type'); - } - } else { - agent = new https.Agent(agentOptions); - } - - context.setAgent(agent); + context.setAgent(this.createAgent(cluster, agentOptions)); } /** @@ -531,6 +512,32 @@ export class KubeConfig implements SecurityAuthentication { return this.getContextObject(this.currentContext); } + private createAgent( + cluster: Cluster | null, + agentOptions: https.AgentOptions, + ): https.Agent | SocksProxyAgent | HttpProxyAgent | HttpsProxyAgent { + let agent: https.Agent | SocksProxyAgent | HttpProxyAgent | HttpsProxyAgent; + + if (cluster && cluster.proxyUrl) { + if (cluster.proxyUrl.startsWith('socks')) { + agent = new SocksProxyAgent(cluster.proxyUrl, agentOptions); + } else if (cluster.server.startsWith('https')) { + const httpsProxyAgentOptions: HttpsProxyAgentOptions = agentOptions as HttpsProxyAgentOptions; + httpsProxyAgentOptions.proxy = cluster.proxyUrl; + agent = new HttpsProxyAgent(httpsProxyAgentOptions); + } else if (cluster.server.startsWith('http')) { + const httpProxyAgentOptions: HttpProxyAgentOptions = agentOptions as HttpProxyAgentOptions; + httpProxyAgentOptions.proxy = cluster.proxyUrl; + agent = new HttpProxyAgent(httpProxyAgentOptions); + } else { + throw new Error('Unsupported proxy type'); + } + } else { + agent = new https.Agent(agentOptions); + } + return agent; + } + private applyHTTPSOptions(opts: https.RequestOptions | WebSocket.ClientOptions): void { const cluster = this.getCurrentCluster(); const user = this.getCurrentUser();