Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable TLS on lens-k8s-proxy #4941

Merged
merged 14 commits into from
Mar 17, 2022
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,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",
Expand Down
4 changes: 4 additions & 0 deletions src/common/__tests__/cluster-store.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import { createClusterInjectionToken } from "../cluster/create-cluster-injection

import directoryForUserDataInjectable
from "../app-paths/directory-for-user-data/directory-for-user-data.injectable";
import kubeAuthProxyCaInjectable from "../../main/kube-auth-proxy/kube-auth-proxy-ca.injectable";
import createKubeAuthProxyCertFilesInjectable from "../../main/kube-auth-proxy/create-kube-auth-proxy-cert-files.injectable";

console = new Console(stdout, stderr);

Expand Down Expand Up @@ -87,6 +89,8 @@ describe("cluster-store", () => {
mainDi = dis.mainDi;

mainDi.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");
mainDi.override(createKubeAuthProxyCertFilesInjectable, () => ({} as any));
mainDi.override(kubeAuthProxyCaInjectable, () => ({} as any));

await dis.runSetups();

Expand Down
2 changes: 2 additions & 0 deletions src/common/__tests__/hotbar-store.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type { CatalogEntity, CatalogEntityData, CatalogEntityKindData } from "..
import { HotbarStore } from "../hotbar-store";
import { getDiForUnitTesting } from "../../main/getDiForUnitTesting";
import directoryForUserDataInjectable from "../app-paths/directory-for-user-data/directory-for-user-data.injectable";
import writeFileInjectable from "../fs/write-file.injectable";

jest.mock("../../main/catalog/catalog-entity-registry", () => ({
catalogEntityRegistry: {
Expand Down Expand Up @@ -115,6 +116,7 @@ describe("HotbarStore", () => {
beforeEach(async () => {
const di = getDiForUnitTesting({ doGeneralOverrides: true });

di.override(writeFileInjectable, () => () => undefined);
di.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");

await di.runSetups();
Expand Down
2 changes: 2 additions & 0 deletions src/common/__tests__/user-store.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import type { DiContainer } from "@ogre-tools/injectable";
import directoryForUserDataInjectable from "../app-paths/directory-for-user-data/directory-for-user-data.injectable";
import type { ClusterStoreModel } from "../cluster-store/cluster-store";
import { defaultTheme } from "../vars";
import writeFileInjectable from "../fs/write-file.injectable";

console = new Console(stdout, stderr);

Expand All @@ -46,6 +47,7 @@ describe("user store tests", () => {

mainDi = dis.mainDi;

mainDi.override(writeFileInjectable, () => () => undefined);
mainDi.override(directoryForUserDataInjectable, () => "some-directory-for-user-data");

await dis.runSetups();
Expand Down
25 changes: 25 additions & 0 deletions src/common/fs/write-file.injectable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
* 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 path from "path";
import fsInjectable from "./fs.injectable";

const writeFileInjectable = getInjectable({
id: "write-file",

instantiate: (di) => {
const { writeFile, ensureDir } = di.inject(fsInjectable);

return async (filePath: string, content: string | Buffer) => {
await ensureDir(path.dirname(filePath), { mode: 0o755 });

await writeFile(filePath, content, {
encoding: "utf-8",
});
};
},
});

export default writeFileInjectable;
4 changes: 4 additions & 0 deletions src/main/__test__/cluster.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import type { ClusterModel } from "../../common/cluster-types";
import { createClusterInjectionToken } from "../../common/cluster/create-cluster-injection-token";
import authorizationReviewInjectable from "../../common/cluster/authorization-review.injectable";
import listNamespacesInjectable from "../../common/cluster/list-namespaces.injectable";
import createContextHandlerInjectable from "../context-handler/create-context-handler.injectable";

console = new Console(process.stdout, process.stderr); // fix mockFS

Expand Down Expand Up @@ -81,6 +82,9 @@ describe("create clusters", () => {

di.override(authorizationReviewInjectable, () => () => () => Promise.resolve(true));
di.override(listNamespacesInjectable, () => () => () => Promise.resolve([ "default" ]));
di.override(createContextHandlerInjectable, () => () => {
throw new Error("you should never come here");
});

createCluster = di.inject(createClusterInjectionToken);

Expand Down
5 changes: 5 additions & 0 deletions src/main/__test__/context-handler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import mockFs from "mock-fs";
import { getDiForUnitTesting } from "../getDiForUnitTesting";
import createContextHandlerInjectable from "../context-handler/create-context-handler.injectable";
import type { Cluster } from "../../common/cluster/cluster";
import kubeAuthProxyCaInjectable from "../kube-auth-proxy/kube-auth-proxy-ca.injectable";
import createKubeAuthProxyInjectable from "../kube-auth-proxy/create-kube-auth-proxy.injectable";

jest.mock("electron", () => ({
app: {
Expand Down Expand Up @@ -80,6 +82,9 @@ describe("ContextHandler", () => {
"tmp": {},
});

di.override(createKubeAuthProxyInjectable, () => ({} as any));
di.override(kubeAuthProxyCaInjectable, () => ({} as any));

await di.runSetups();

createContextHandler = di.inject(createContextHandlerInjectable);
Expand Down
9 changes: 8 additions & 1 deletion src/main/__test__/kube-auth-proxy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ import { getDiForUnitTesting } from "../getDiForUnitTesting";
import createKubeAuthProxyInjectable from "../kube-auth-proxy/create-kube-auth-proxy.injectable";
import { createClusterInjectionToken } from "../../common/cluster/create-cluster-injection-token";
import path from "path";
import spawnInjectable from "../child-process/spawn.injectable";
import kubeAuthProxyCaInjectable from "../kube-auth-proxy/kube-auth-proxy-ca.injectable";
import createKubeAuthProxyCertFilesInjectable from "../kube-auth-proxy/create-kube-auth-proxy-cert-files.injectable";

console = new Console(stdout, stderr);

Expand Down Expand Up @@ -92,6 +95,10 @@ describe("kube auth proxy tests", () => {

const di = getDiForUnitTesting({ doGeneralOverrides: true });

di.override(spawnInjectable, () => mockSpawn);
di.override(createKubeAuthProxyCertFilesInjectable, () => ({} as any));
di.override(kubeAuthProxyCaInjectable, () => ({} as any));

mockFs(mockMinikubeConfig);

await di.runSetups();
Expand Down Expand Up @@ -130,7 +137,7 @@ describe("kube auth proxy tests", () => {
beforeEach(async () => {
mockedCP = mock<ChildProcess>();
listeners = new EventEmitter();

jest.spyOn(Kubectl.prototype, "checkBinary").mockReturnValueOnce(Promise.resolve(true));
jest.spyOn(Kubectl.prototype, "ensureKubectl").mockReturnValueOnce(Promise.resolve(false));
mockedCP.on.mockImplementation((event: string, listener: (message: any, sendHandle: any) => void): ChildProcess => {
Expand Down
19 changes: 13 additions & 6 deletions src/main/__test__/kubeconfig-manager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,18 @@ import * as path from "path";
import createKubeconfigManagerInjectable from "../kubeconfig-manager/create-kubeconfig-manager.injectable";
import { createClusterInjectionToken } from "../../common/cluster/create-cluster-injection-token";
import directoryForTempInjectable from "../../common/app-paths/directory-for-temp/directory-for-temp.injectable";
import createContextHandlerInjectable from "../context-handler/create-context-handler.injectable";
import type { DiContainer } from "@ogre-tools/injectable";

console = new Console(process.stdout, process.stderr); // fix mockFS

describe("kubeconfig manager tests", () => {
let cluster: Cluster;
let clusterFake: Cluster;
let createKubeconfigManager: (cluster: Cluster) => KubeconfigManager;
let di: DiContainer;

beforeEach(async () => {
const di = getDiForUnitTesting({ doGeneralOverrides: true });
di = getDiForUnitTesting({ doGeneralOverrides: true });

di.override(directoryForTempInjectable, () => "some-directory-for-temp");

Expand Down Expand Up @@ -78,17 +81,21 @@ describe("kubeconfig manager tests", () => {

await di.runSetups();

di.override(createContextHandlerInjectable, () => () => {
throw new Error("you should never come here");
});

const createCluster = di.inject(createClusterInjectionToken);

createKubeconfigManager = di.inject(createKubeconfigManagerInjectable);

cluster = createCluster({
clusterFake = createCluster({
id: "foo",
contextName: "minikube",
kubeConfigPath: "minikube-config.yml",
});

cluster.contextHandler = {
clusterFake.contextHandler = {
ensureServer: () => Promise.resolve(),
} as any;

Expand All @@ -100,7 +107,7 @@ describe("kubeconfig manager tests", () => {
});

it("should create 'temp' kube config with proxy", async () => {
const kubeConfManager = createKubeconfigManager(cluster);
const kubeConfManager = createKubeconfigManager(clusterFake);

expect(logger.error).not.toBeCalled();
expect(await kubeConfManager.getPath()).toBe(`some-directory-for-temp${path.sep}kubeconfig-foo`);
Expand All @@ -115,7 +122,7 @@ describe("kubeconfig manager tests", () => {
});

it("should remove 'temp' kube config on unlink and remove reference from inside class", async () => {
const kubeConfManager = createKubeconfigManager(cluster);
const kubeConfManager = createKubeconfigManager(clusterFake);

const configPath = await kubeConfManager.getPath();

Expand Down
5 changes: 5 additions & 0 deletions src/main/catalog-sources/__test__/kubeconfig-sync.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import { getDiForUnitTesting } from "../../getDiForUnitTesting";
import { createClusterInjectionToken } from "../../../common/cluster/create-cluster-injection-token";
import directoryForKubeConfigsInjectable
from "../../../common/app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable";
import kubeAuthProxyCaInjectable from "../../kube-auth-proxy/kube-auth-proxy-ca.injectable";
import createKubeAuthProxyCertFilesInjectable from "../../kube-auth-proxy/create-kube-auth-proxy-cert-files.injectable";


jest.mock("electron", () => ({
Expand All @@ -40,6 +42,9 @@ describe("kubeconfig-sync.source tests", () => {
beforeEach(async () => {
const di = getDiForUnitTesting({ doGeneralOverrides: true });

di.override(kubeAuthProxyCaInjectable, () => Promise.resolve(Buffer.from("ca")));
di.override(createKubeAuthProxyCertFilesInjectable, () => ({} as any));

mockFs();

await di.runSetups();
Expand Down
17 changes: 17 additions & 0 deletions src/main/child-process/spawn.injectable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/**
* 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 { spawn } from "child_process";

const spawnInjectable = getInjectable({
id: "spawn",

instantiate: () => {
return spawn;
},
causesSideEffects: true,
});

export default spawnInjectable;
25 changes: 22 additions & 3 deletions src/main/context-handler/context-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ 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 type { CreateKubeAuthProxy } from "../kube-auth-proxy/create-kube-auth-proxy.injectable";

export interface PrometheusDetails {
prometheusPath: string;
Expand All @@ -26,7 +27,8 @@ interface PrometheusServicePreferences {
}

interface Dependencies {
createKubeAuthProxy: (cluster: Cluster, environmentVariables: NodeJS.ProcessEnv) => KubeAuthProxy;
createKubeAuthProxy: CreateKubeAuthProxy;
authProxyCa: Promise<Buffer>;
}

export class ContextHandler {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: some unrelated janky code around these parts needs a lovin' champion at some.

Expand Down Expand Up @@ -118,7 +120,11 @@ 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}${path}`;
return `https://127.0.0.1:${this.kubeAuthProxy.port}${this.kubeAuthProxy.apiPrefix}${path}`;
}

async resolveAuthProxyCa() {
return this.dependencies.authProxyCa;
}

async getApiTarget(isLongRunningRequest = false): Promise<httpProxy.ServerOptions> {
Expand All @@ -132,10 +138,23 @@ export class ContextHandler {
}

protected async newApiTarget(timeout: number): Promise<httpProxy.ServerOptions> {
await this.ensureServer();
jakolehm marked this conversation as resolved.
Show resolved Hide resolved

const caFileContents = await this.resolveAuthProxyCa();
const clusterPath = this.clusterUrl.path !== "/" ? this.clusterUrl.path : "";
const apiPrefix = `${this.kubeAuthProxy.apiPrefix}${clusterPath}`;

return {
target: await this.resolveAuthProxyUrl(),
target: {
protocol: "https:",
host: "127.0.0.1",
port: this.kubeAuthProxy.port,
path: apiPrefix,
ca: caFileContents.toString(),
},
changeOrigin: true,
timeout,
secure: true,
headers: {
"Host": this.clusterUrl.hostname,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,17 @@ 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 kubeAuthProxyCaInjectable from "../kube-auth-proxy/kube-auth-proxy-ca.injectable";

const createContextHandlerInjectable = getInjectable({
id: "create-context-handler",

instantiate: (di) => {
const authProxyCa = di.inject(kubeAuthProxyCaInjectable);

const dependencies = {
createKubeAuthProxy: di.inject(createKubeAuthProxyInjectable),
authProxyCa,
};

return (cluster: Cluster) => new ContextHandler(dependencies, cluster);
Expand Down
8 changes: 8 additions & 0 deletions src/main/getDiForUnitTesting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import writeJsonFileInjectable from "../common/fs/write-json-file.injectable";
import readJsonFileInjectable from "../common/fs/read-json-file.injectable";
import readFileInjectable from "../common/fs/read-file.injectable";
import directoryForBundledBinariesInjectable from "../common/app-paths/directory-for-bundled-binaries/directory-for-bundled-binaries.injectable";
import spawnInjectable from "./child-process/spawn.injectable";

export const getDiForUnitTesting = (
{ doGeneralOverrides } = { doGeneralOverrides: false },
Expand Down Expand Up @@ -46,6 +47,13 @@ export const getDiForUnitTesting = (
di.override(appNameInjectable, () => "some-electron-app-name");
di.override(registerChannelInjectable, () => () => undefined);
di.override(directoryForBundledBinariesInjectable, () => "some-bin-directory");
di.override(spawnInjectable, () => () => {
return {
stderr: { on: jest.fn(), removeAllListeners: jest.fn() },
stdout: { on: jest.fn(), removeAllListeners: jest.fn() },
on: jest.fn(),
} as any;
});

di.override(writeJsonFileInjectable, () => () => {
throw new Error("Tried to write JSON file to file system without specifying explicit override.");
Expand Down
Loading