diff --git a/package.json b/package.json index e1b1d07a12..fc793860a4 100644 --- a/package.json +++ b/package.json @@ -204,6 +204,7 @@ "semver": "^7.3.2", "serializr": "^2.0.3", "shell-env": "^3.0.0", + "spdy": "^4.0.2", "tar": "^6.0.2", "tcp-port-used": "^1.0.1", "tempy": "^0.5.0", @@ -248,6 +249,7 @@ "@types/request-promise-native": "^1.0.17", "@types/semver": "^7.2.0", "@types/shelljs": "^0.8.8", + "@types/spdy": "^3.4.4", "@types/tcp-port-used": "^1.0.0", "@types/tempy": "^0.3.0", "@types/terser-webpack-plugin": "^3.0.0", diff --git a/src/main/cluster-manager.ts b/src/main/cluster-manager.ts index ff5eaae32d..302317e52e 100644 --- a/src/main/cluster-manager.ts +++ b/src/main/cluster-manager.ts @@ -44,7 +44,7 @@ export class ClusterManager { // lens-server is connecting to 127.0.0.1:/ if (req.headers.host.startsWith("127.0.0.1")) { const clusterId = req.url.split("/")[1] - const cluster = clusterStore.getById(clusterId) + cluster = clusterStore.getById(clusterId) if (cluster) { // we need to swap path prefix so that request is proxied to kube api req.url = req.url.replace(`/${clusterId}`, apiKubePrefix) diff --git a/src/main/cluster.ts b/src/main/cluster.ts index f1e59aa355..48bf01ee02 100644 --- a/src/main/cluster.ts +++ b/src/main/cluster.ts @@ -92,7 +92,7 @@ export class Cluster implements ClusterModel { async init(port: number) { try { this.contextHandler = new ContextHandler(this); - this.kubeconfigManager = new KubeconfigManager(this, this.contextHandler); + this.kubeconfigManager = new KubeconfigManager(this, this.contextHandler, port); this.kubeProxyUrl = `http://localhost:${port}${apiKubePrefix}`; this.initialized = true; logger.info(`[CLUSTER]: "${this.contextName}" init success`, { diff --git a/src/main/kubeconfig-manager.ts b/src/main/kubeconfig-manager.ts index f7d85e3a24..59a47c3fbc 100644 --- a/src/main/kubeconfig-manager.ts +++ b/src/main/kubeconfig-manager.ts @@ -11,7 +11,7 @@ export class KubeconfigManager { protected configDir = app.getPath("temp") protected tempFile: string; - constructor(protected cluster: Cluster, protected contextHandler: ContextHandler) { + constructor(protected cluster: Cluster, protected contextHandler: ContextHandler, protected port: number) { this.init(); } @@ -28,6 +28,10 @@ export class KubeconfigManager { return this.tempFile; } + protected resolveProxyUrl() { + return `http://127.0.0.1:${this.port}/${this.cluster.id}` + } + /** * Creates new "temporary" kubeconfig that point to the kubectl-proxy. * This way any user of the config does not need to know anything about the auth etc. details. @@ -42,7 +46,7 @@ export class KubeconfigManager { clusters: [ { name: contextName, - server: await contextHandler.resolveAuthProxyUrl(), + server: this.resolveProxyUrl(), skipTLSVerify: undefined, } ], diff --git a/src/main/lens-proxy.ts b/src/main/lens-proxy.ts index 7cc4584750..73abd97dff 100644 --- a/src/main/lens-proxy.ts +++ b/src/main/lens-proxy.ts @@ -1,13 +1,14 @@ import net from "net"; import http from "http"; +import spdy from "spdy"; import httpProxy from "http-proxy"; import url from "url"; import * as WebSocket from "ws" +import { apiPrefix, apiKubePrefix } from "../common/vars" import { openShell } from "./node-shell-session"; import { Router } from "./router" import { ClusterManager } from "./cluster-manager" import { ContextHandler } from "./context-handler"; -import { apiKubePrefix } from "../common/vars"; import logger from "./logger" export class LensProxy { @@ -40,37 +41,49 @@ export class LensProxy { protected buildCustomProxy(): http.Server { const proxy = this.createProxy(); - const customProxy = http.createServer((req: http.IncomingMessage, res: http.ServerResponse) => { - this.handleRequest(proxy, req, res); - }); - customProxy.on("upgrade", (req: http.IncomingMessage, socket: net.Socket, head: Buffer) => { - this.handleWsUpgrade(req, socket, head) - }); - customProxy.on("error", (err) => { + const spdyProxy = spdy.createServer({ + spdy: { + plain: true, + connection: { + autoSpdy31: true + } + } + }, (req: http.IncomingMessage, res: http.ServerResponse) => { + this.handleRequest(proxy, req, res) + }) + spdyProxy.on("upgrade", (req: http.IncomingMessage, socket: net.Socket, head: Buffer) => { + if (req.url.startsWith(`${apiPrefix}/?`)) { + this.handleWsUpgrade(req, socket, head) + } else { + if (req.headers.upgrade?.startsWith("SPDY")) { + this.handleSpdyProxy(proxy, req, socket, head) + } else { + socket.end() + } + } + }) + spdyProxy.on("error", (err) => { logger.error("proxy error", err) - }); - return customProxy; + }) + return spdyProxy + } + + protected async handleSpdyProxy(proxy: httpProxy, req: http.IncomingMessage, socket: net.Socket, head: Buffer) { + const cluster = this.clusterManager.getClusterForRequest(req) + if (cluster) { + const proxyUrl = await cluster.contextHandler.resolveAuthProxyUrl() + req.url.replace(apiKubePrefix, "") + const apiUrl = url.parse(cluster.apiUrl) + const res = new http.ServerResponse(req) + res.assignSocket(socket) + res.setHeader("Location", proxyUrl) + res.setHeader("Host", apiUrl.hostname) + res.statusCode = 302 + res.end() + } } protected createProxy(): httpProxy { const proxy = httpProxy.createProxyServer(); - proxy.on("proxyRes", (proxyRes, req, res) => { - if (req.method !== "GET") { - return; - } - if (proxyRes.statusCode === 502) { - const cluster = this.clusterManager.getClusterForRequest(req) - const proxyError = cluster?.contextHandler.proxyLastError; - if (proxyError) { - return res.writeHead(502).end(proxyError); - } - } - const reqId = this.getRequestId(req); - if (this.retryCounters.has(reqId)) { - logger.debug(`Resetting proxy retry cache for url: ${reqId}`); - this.retryCounters.delete(reqId) - } - }) proxy.on("error", (error, req, res, target) => { if (this.closed) { return; diff --git a/yarn.lock b/yarn.lock index c7d43c4472..300efc58ce 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2156,6 +2156,13 @@ resolved "https://registry.yarnpkg.com/@types/source-list-map/-/source-list-map-0.1.2.tgz#0078836063ffaf17412349bba364087e0ac02ec9" integrity sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA== +"@types/spdy@^3.4.4": + version "3.4.4" + resolved "https://registry.yarnpkg.com/@types/spdy/-/spdy-3.4.4.tgz#3282fd4ad8c4603aa49f7017dd520a08a345b2bc" + integrity sha512-N9LBlbVRRYq6HgYpPkqQc3a9HJ/iEtVZToW6xlTtJiMhmRJ7jJdV7TaZQJw/Ve/1ePUsQiCTDc4JMuzzag94GA== + dependencies: + "@types/node" "*" + "@types/stack-utils@^1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e" @@ -5922,6 +5929,11 @@ growly@^1.3.0: resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" integrity sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE= +handle-thing@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.1.tgz#857f79ce359580c340d43081cc648970d0bb234e" + integrity sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg== + handlebars@^4.7.6: version "4.7.6" resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.6.tgz#d4c05c1baf90e9945f77aa68a7a219aa4a7df74e" @@ -6108,6 +6120,16 @@ hosted-git-info@^3.0.4: dependencies: lru-cache "^5.1.1" +hpack.js@^2.1.6: + version "2.1.6" + resolved "https://registry.yarnpkg.com/hpack.js/-/hpack.js-2.1.6.tgz#87774c0949e513f42e84575b3c45681fade2a0b2" + integrity sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI= + dependencies: + inherits "^2.0.1" + obuf "^1.0.0" + readable-stream "^2.0.1" + wbuf "^1.1.0" + html-encoding-sniffer@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz#42a6dc4fd33f00281176e8b23759ca4e4fa185f3" @@ -6170,6 +6192,11 @@ http-cache-semantics@^4.0.0: resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390" integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ== +http-deceiver@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" + integrity sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc= + http-proxy@^1.18.1: version "1.18.1" resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.1.tgz#401541f0534884bbf95260334e72f88ee3976549" @@ -8636,6 +8663,11 @@ object.pick@^1.3.0: dependencies: isobject "^3.0.1" +obuf@^1.0.0, obuf@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" + integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== + oidc-token-hash@^3.0.1: version "3.0.2" resolved "https://registry.yarnpkg.com/oidc-token-hash/-/oidc-token-hash-3.0.2.tgz#5bd4716cc48ad433f4e4e99276811019b165697e" @@ -9753,7 +9785,7 @@ read-pkg@^5.2.0: string_decoder "~1.1.1" util-deprecate "~1.0.1" -readable-stream@^3.1.1, readable-stream@^3.6.0: +readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== @@ -10284,6 +10316,11 @@ scss-tokenizer@^0.2.3: js-base64 "^2.1.8" source-map "^0.4.2" +select-hose@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" + integrity sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo= + semver-compare@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc" @@ -10593,6 +10630,29 @@ spdx-license-ids@^3.0.0: resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz#3694b5804567a458d3c8045842a6358632f62654" integrity sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q== +spdy-transport@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/spdy-transport/-/spdy-transport-3.0.0.tgz#00d4863a6400ad75df93361a1608605e5dcdcf31" + integrity sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw== + dependencies: + debug "^4.1.0" + detect-node "^2.0.4" + hpack.js "^2.1.6" + obuf "^1.1.2" + readable-stream "^3.0.6" + wbuf "^1.7.3" + +spdy@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/spdy/-/spdy-4.0.2.tgz#b74f466203a3eda452c02492b91fb9e84a27677b" + integrity sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA== + dependencies: + debug "^4.1.0" + handle-thing "^2.0.0" + http-deceiver "^1.2.7" + select-hose "^2.0.0" + spdy-transport "^3.0.0" + spectron@11.0.0: version "11.0.0" resolved "https://registry.yarnpkg.com/spectron/-/spectron-11.0.0.tgz#79d785e6b8898638e77b5186711e3910ed4ca09b" @@ -11783,6 +11843,13 @@ watchpack@^1.6.1: chokidar "^3.4.0" watchpack-chokidar2 "^2.0.0" +wbuf@^1.1.0, wbuf@^1.7.3: + version "1.7.3" + resolved "https://registry.yarnpkg.com/wbuf/-/wbuf-1.7.3.tgz#c1d8d149316d3ea852848895cb6a0bfe887b87df" + integrity sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA== + dependencies: + minimalistic-assert "^1.0.0" + wcwidth@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8"