diff --git a/package.json b/package.json index 54e18da7c6..2f19f24ac0 100644 --- a/package.json +++ b/package.json @@ -267,6 +267,7 @@ "request": "^2.88.2", "request-promise-native": "^1.0.9", "rfc6902": "^4.0.2", + "selfsigned": "^2.0.0", "semver": "^7.3.2", "shell-env": "^3.0.1", "spdy": "^4.0.2", diff --git a/src/main/context-handler/context-handler.ts b/src/main/context-handler/context-handler.ts index ad1ba734b8..7f4b44715a 100644 --- a/src/main/context-handler/context-handler.ts +++ b/src/main/context-handler/context-handler.ts @@ -12,6 +12,8 @@ import url, { UrlWithStringQuery } from "url"; import { CoreV1Api } from "@kubernetes/client-node"; import logger from "../logger"; import type { KubeAuthProxy } from "../kube-auth-proxy/kube-auth-proxy"; +import path from "path"; +import { readFile } from "fs/promises"; export interface PrometheusDetails { prometheusPath: string; @@ -27,6 +29,7 @@ interface PrometheusServicePreferences { interface Dependencies { createKubeAuthProxy: (cluster: Cluster, environmentVariables: NodeJS.ProcessEnv) => KubeAuthProxy; + certPath: string; } export class ContextHandler { @@ -118,7 +121,7 @@ export class ContextHandler { await this.ensureServer(); const path = this.clusterUrl.path !== "/" ? this.clusterUrl.path : ""; - return `http://127.0.0.1:${this.kubeAuthProxy.port}${this.kubeAuthProxy.apiPrefix.slice(0, -1)}${path}`; + return `https://127.0.0.1:${this.kubeAuthProxy.port}${this.kubeAuthProxy.apiPrefix.slice(0, -1)}${path}`; } async getApiTarget(isLongRunningRequest = false): Promise { @@ -132,10 +135,21 @@ export class ContextHandler { } protected async newApiTarget(timeout: number): Promise { + await this.ensureServer(); + + const ca = (await readFile(path.join(this.dependencies.certPath, "proxy.crt"))).toString(); + return { - target: await this.resolveAuthProxyUrl(), + //target: await this.resolveAuthProxyUrl(), + target: { + protocol: "https:", + host: "127.0.0.1", + port: this.kubeAuthProxy.port, + ca, + }, changeOrigin: true, timeout, + secure: true, headers: { "Host": this.clusterUrl.hostname, }, diff --git a/src/main/context-handler/create-context-handler.injectable.ts b/src/main/context-handler/create-context-handler.injectable.ts index 47b935ceaa..083298be99 100644 --- a/src/main/context-handler/create-context-handler.injectable.ts +++ b/src/main/context-handler/create-context-handler.injectable.ts @@ -6,6 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable"; import type { Cluster } from "../../common/cluster/cluster"; import { ContextHandler } from "./context-handler"; import createKubeAuthProxyInjectable from "../kube-auth-proxy/create-kube-auth-proxy.injectable"; +import getKubeAuthProxyCertDirInjectable from "../kube-auth-proxy/kube-auth-proxy-cert.injectable"; const createContextHandlerInjectable = getInjectable({ id: "create-context-handler", @@ -13,6 +14,7 @@ const createContextHandlerInjectable = getInjectable({ instantiate: (di) => { const dependencies = { createKubeAuthProxy: di.inject(createKubeAuthProxyInjectable), + certPath: di.inject(getKubeAuthProxyCertDirInjectable), }; return (cluster: Cluster) => new ContextHandler(dependencies, cluster); diff --git a/src/main/kube-auth-proxy/create-kube-auth-proxy.injectable.ts b/src/main/kube-auth-proxy/create-kube-auth-proxy.injectable.ts index 411cfddc0c..1de0f0f0fb 100644 --- a/src/main/kube-auth-proxy/create-kube-auth-proxy.injectable.ts +++ b/src/main/kube-auth-proxy/create-kube-auth-proxy.injectable.ts @@ -8,6 +8,7 @@ import type { Cluster } from "../../common/cluster/cluster"; import path from "path"; import { isDevelopment, isWindows } from "../../common/vars"; import directoryForBundledBinariesInjectable from "../../common/app-paths/directory-for-bundled-binaries/directory-for-bundled-binaries.injectable"; +import getKubeAuthProxyCertDirInjectable from "./kube-auth-proxy-cert.injectable"; const createKubeAuthProxyInjectable = getInjectable({ id: "create-kube-auth-proxy", @@ -17,6 +18,7 @@ const createKubeAuthProxyInjectable = getInjectable({ const proxyPath = isDevelopment ? path.join("client", process.platform, process.arch) : process.arch; const dependencies = { proxyBinPath: path.join(di.inject(directoryForBundledBinariesInjectable), proxyPath, binaryName), + proxyCertPath: di.inject(getKubeAuthProxyCertDirInjectable), }; return (cluster: Cluster, environmentVariables: NodeJS.ProcessEnv) => diff --git a/src/main/kube-auth-proxy/kube-auth-proxy-cert.injectable.ts b/src/main/kube-auth-proxy/kube-auth-proxy-cert.injectable.ts new file mode 100644 index 0000000000..e501d9fa07 --- /dev/null +++ b/src/main/kube-auth-proxy/kube-auth-proxy-cert.injectable.ts @@ -0,0 +1,19 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import directoryForUserDataInjectable from "../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable"; +import { createKubeAuthProxyCertificateFiles, getKubeAuthProxyCertificatePath } from "./kube-auth-proxy-cert"; + +const getKubeAuthProxyCertDirInjectable = getInjectable({ + id: "get-kube-auth-proxy-cert-dir", + + setup: async (di) => { + await createKubeAuthProxyCertificateFiles(getKubeAuthProxyCertificatePath(await di.inject(directoryForUserDataInjectable))); + }, + + instantiate: (di) => getKubeAuthProxyCertificatePath(di.inject(directoryForUserDataInjectable)), +}); + +export default getKubeAuthProxyCertDirInjectable; diff --git a/src/main/kube-auth-proxy/kube-auth-proxy-cert.ts b/src/main/kube-auth-proxy/kube-auth-proxy-cert.ts new file mode 100644 index 0000000000..bbbd8e6c1e --- /dev/null +++ b/src/main/kube-auth-proxy/kube-auth-proxy-cert.ts @@ -0,0 +1,47 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import { access, mkdir, writeFile } from "fs/promises"; +import path from "path"; +import * as selfsigned from "selfsigned"; + +export function getKubeAuthProxyCertificate(): selfsigned.SelfSignedCert { + const opts = [ + { name: "commonName", value: "Lens Certificate Authority" }, + { name: "organizationName", value: "Lens" }, + ]; + + return selfsigned.generate(opts, { + keySize: 2048, + algorithm: "sha256", + days: 365, + extensions: [ + { name: "basicConstraints", cA: true }, + { name: "subjectAltName", altNames: [ + { type: 2, value: "localhost" }, + { type: 7, ip: "127.0.0.1" }, + ] }, + ], + }); +} + +export function getKubeAuthProxyCertificatePath(baseDir: string) { + return path.join(baseDir, "kube-auth-proxy"); +} + +export async function createKubeAuthProxyCertificateFiles(dir: string): Promise { + const cert = getKubeAuthProxyCertificate(); + + try { + await access(dir); + } catch { + await mkdir(dir); + } + + await writeFile(path.join(dir, "proxy.key"), cert.private); + await writeFile(path.join(dir, "proxy.crt"), cert.cert); + + return dir; +} diff --git a/src/main/kube-auth-proxy/kube-auth-proxy.ts b/src/main/kube-auth-proxy/kube-auth-proxy.ts index d8a291b50c..7e71f33c81 100644 --- a/src/main/kube-auth-proxy/kube-auth-proxy.ts +++ b/src/main/kube-auth-proxy/kube-auth-proxy.ts @@ -15,6 +15,7 @@ const startingServeRegex = /starting to serve on (?
.+)/i; interface Dependencies { proxyBinPath: string; + proxyCertPath: string; } export class KubeAuthProxy { @@ -42,13 +43,15 @@ export class KubeAuthProxy { } const proxyBin = this.dependencies.proxyBinPath; - - this.proxyProcess = spawn(proxyBin, [], { + const certPath = await this.dependencies.proxyCertPath; + + this.proxyProcess = spawn(proxyBin, [], { env: { ...this.env, KUBECONFIG: this.cluster.kubeConfigPath, KUBECONFIG_CONTEXT: this.cluster.contextName, API_PREFIX: this.apiPrefix, + CERT_PATH: certPath, }, }); this.proxyProcess.on("error", (error) => { diff --git a/types/selfsigned.d.ts b/types/selfsigned.d.ts new file mode 100644 index 0000000000..b46052a9fa --- /dev/null +++ b/types/selfsigned.d.ts @@ -0,0 +1,26 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + + declare module "selfsigned" { + export type SelfSignedCert = { + private: string; + public: string; + cert: string; + }; + + type GenerateAttributes = Array; + + type GenerateOptions = { + keySize?: number; + days?: number; + algorithm?: "sha1" | "sha256"; + extensions?: any; + pkcs7?: boolean; + clientCertificate?: boolean; + clientCertificateCN?: string; + }; + + export function generate(GenerateAttributes, GenerateOptions): SelfSignedCert; + }