Lens proxy with TLS (#6851)

* lens proxy tls support

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* integration test fix

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* don't override getRequestOptions if they are set

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* fix electronAppInjectable override

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* use runnables on renderer

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* move certificate generation to runnables

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

* simplify

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>
This commit is contained in:
Jari Kolehmainen authored and GitHub committed 2023-01-04 19:18:02 +02:00
1 parent fba93b889e
commit 7052dc0dba
35 files changed
+353 -66

No files matched your search

@@ -0,0 +1,9 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { SelfSignedCert } from "selfsigned";
import { getRequestChannel } from "../utils/channel/get-request-channel";
export const lensProxyCertificateChannel = getRequestChannel<void, SelfSignedCert>("request-lens-proxy-certificate");
@@ -0,0 +1,19 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getGlobalOverride } from "../test-utils/get-global-override";
import lensProxyCertificateInjectable from "./lens-proxy-certificate.injectable";
export default getGlobalOverride(lensProxyCertificateInjectable, () => {
return {
get: () => ({
public: "<public-data>",
private: "<private-data>",
cert: "<ca-data>",
}),
set: () => {},
};
});
@@ -0,0 +1,33 @@
/**
* 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 type { SelfSignedCert } from "selfsigned";
const lensProxyCertificateInjectable = getInjectable({
id: "lens-proxy-certificate",
instantiate: () => {
let certState: SelfSignedCert;
const cert = {
get: () => {
if (!certState) {
throw "certificate has not been set";
}
return certState;
},
set: (certificate: SelfSignedCert) => {
if (certState) {
throw "certificate has already been set";
}
certState = certificate;
},
};
return cert;
},
});
export default lensProxyCertificateInjectable;
@@ -3,7 +3,9 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { Agent } from "https";
import type { RequestInit } from "node-fetch";
import lensProxyCertificateInjectable from "../certificate/lens-proxy-certificate.injectable";
import fetchInjectable from "../fetch/fetch.injectable";
import loggerInjectable from "../logger.injectable";
import type { JsonApiConfig, JsonApiData, JsonApiDependencies, JsonApiParams } from "./json-api";
@@ -18,8 +20,23 @@ const createJsonApiInjectable = getInjectable({
fetch: di.inject(fetchInjectable),
logger: di.inject(loggerInjectable),
};
const lensProxyCert = di.inject(lensProxyCertificateInjectable);
return (config, reqInit) => new JsonApi(deps, config, reqInit);
return (config, reqInit) => {
if (!config.getRequestOptions) {
config.getRequestOptions = async () => {
const agent = new Agent({
ca: lensProxyCert.get().cert,
});
return {
agent,
};
};
}
return new JsonApi(deps, config, reqInit);
};
},
});
@@ -51,7 +51,7 @@ const createKubeApiForClusterInjectable = getInjectable({
debug: isDevelopment,
}, {
headers: {
"Host": `${cluster.metadata.uid}.localhost:${new URL(apiBase.config.serverAddress).port}`,
"Host": `${cluster.metadata.uid}.lens.app:${new URL(apiBase.config.serverAddress).port}`,
},
},
),
@@ -3,7 +3,9 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { Agent } from "https";
import type { RequestInit } from "node-fetch";
import lensProxyCertificateInjectable from "../certificate/lens-proxy-certificate.injectable";
import fetchInjectable from "../fetch/fetch.injectable";
import loggerInjectable from "../logger.injectable";
import type { JsonApiConfig, JsonApiDependencies } from "./json-api";
@@ -18,8 +20,23 @@ const createKubeJsonApiInjectable = getInjectable({
fetch: di.inject(fetchInjectable),
logger: di.inject(loggerInjectable),
};
const lensProxyCert = di.inject(lensProxyCertificateInjectable);
return (config, reqInit) => new KubeJsonApi(dependencies, config, reqInit);
return (config, reqInit) => {
if (!config.getRequestOptions) {
config.getRequestOptions = async () => {
const agent = new Agent({
ca: lensProxyCert.get().cert,
});
return {
agent,
};
};
}
return new KubeJsonApi(dependencies, config, reqInit);
};
},
});
+1 -1
View File
@@ -23,7 +23,7 @@ export interface IKubeApiParsed extends IKubeApiLinkRef {
}
export function parseKubeApi(path: string): IKubeApiParsed {
const apiPath = new URL(path, "http://localhost").pathname;
const apiPath = new URL(path, "https://localhost").pathname;
const [, prefix, ...parts] = apiPath.split("/");
const apiPrefix = `/${prefix}`;
const [left, right, namespaced] = splitArray(parts, "namespaces");
@@ -9,22 +9,22 @@ describe("getClusterIdFromHost", () => {
const clusterFakeId = "fe540901-0bd6-4f6c-b472-bce1559d7c4a";
it("should return undefined for non cluster frame hosts", () => {
expect(getClusterIdFromHost("localhost:45345")).toBeUndefined();
expect(getClusterIdFromHost("lens.app:45345")).toBeUndefined();
});
it("should return ClusterId for cluster frame hosts", () => {
expect(getClusterIdFromHost(`${clusterFakeId}.localhost:59110`)).toBe(clusterFakeId);
expect(getClusterIdFromHost(`${clusterFakeId}.lens.app:59110`)).toBe(clusterFakeId);
});
it("should return ClusterId for cluster frame hosts with additional subdomains", () => {
expect(getClusterIdFromHost(`abc.${clusterFakeId}.localhost:59110`)).toBe(clusterFakeId);
expect(getClusterIdFromHost(`abc.def.${clusterFakeId}.localhost:59110`)).toBe(clusterFakeId);
expect(getClusterIdFromHost(`abc.def.ghi.${clusterFakeId}.localhost:59110`)).toBe(clusterFakeId);
expect(getClusterIdFromHost(`abc.def.ghi.jkl.${clusterFakeId}.localhost:59110`)).toBe(clusterFakeId);
expect(getClusterIdFromHost(`abc.def.ghi.jkl.mno.${clusterFakeId}.localhost:59110`)).toBe(clusterFakeId);
expect(getClusterIdFromHost(`abc.def.ghi.jkl.mno.pqr.${clusterFakeId}.localhost:59110`)).toBe(clusterFakeId);
expect(getClusterIdFromHost(`abc.def.ghi.jkl.mno.pqr.stu.${clusterFakeId}.localhost:59110`)).toBe(clusterFakeId);
expect(getClusterIdFromHost(`abc.def.ghi.jkl.mno.pqr.stu.vwx.${clusterFakeId}.localhost:59110`)).toBe(clusterFakeId);
expect(getClusterIdFromHost(`abc.def.ghi.jkl.mno.pqr.stu.vwx.yz.${clusterFakeId}.localhost:59110`)).toBe(clusterFakeId);
expect(getClusterIdFromHost(`abc.${clusterFakeId}.lens.app:59110`)).toBe(clusterFakeId);
expect(getClusterIdFromHost(`abc.def.${clusterFakeId}.lens.app:59110`)).toBe(clusterFakeId);
expect(getClusterIdFromHost(`abc.def.ghi.${clusterFakeId}.lens.app:59110`)).toBe(clusterFakeId);
expect(getClusterIdFromHost(`abc.def.ghi.jkl.${clusterFakeId}.lens.app:59110`)).toBe(clusterFakeId);
expect(getClusterIdFromHost(`abc.def.ghi.jkl.mno.${clusterFakeId}.lens.app:59110`)).toBe(clusterFakeId);
expect(getClusterIdFromHost(`abc.def.ghi.jkl.mno.pqr.${clusterFakeId}.lens.app:59110`)).toBe(clusterFakeId);
expect(getClusterIdFromHost(`abc.def.ghi.jkl.mno.pqr.stu.${clusterFakeId}.lens.app:59110`)).toBe(clusterFakeId);
expect(getClusterIdFromHost(`abc.def.ghi.jkl.mno.pqr.stu.vwx.${clusterFakeId}.lens.app:59110`)).toBe(clusterFakeId);
expect(getClusterIdFromHost(`abc.def.ghi.jkl.mno.pqr.stu.vwx.yz.${clusterFakeId}.lens.app:59110`)).toBe(clusterFakeId);
});
});
-17
View File
@@ -1,17 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import requestPromise from "request-promise-native";
export async function getAppVersionFromProxyServer(proxyPort: number): Promise<string> {
const response = await requestPromise({
method: "GET",
uri: `http://127.0.0.1:${proxyPort}/version`,
resolveWithFullResponse: true,
proxy: undefined,
});
return JSON.parse(response.body).version;
}
@@ -0,0 +1,9 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { RequestChannel } from "./request-channel-listener-injection-token";
export const getRequestChannel = <Request, Response>(id: string): RequestChannel<Request, Response> => ({
id,
});
+1 -1
View File
@@ -14,7 +14,7 @@ export function getClusterIdFromHost(host: string): ClusterId | undefined {
// e.g host == "%clusterId.localhost:45345"
const subDomains = host.split(":")[0].split(".");
return subDomains.slice(-2, -1)[0]; // ClusterId or undefined
return subDomains.slice(-3, -2)[0]; // ClusterId or undefined
}
/**
-1
View File
@@ -4,7 +4,6 @@
*/
export * from "./abort-controller";
export * from "./app-version";
export * from "./autobind";
export * from "./camelCase";
export * from "./cluster-id-url-parsing";