mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Add authentication header requirments
Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
parent
4867286179
commit
63ad078d63
@ -6,8 +6,8 @@
|
|||||||
import type { KubeConfig, V1ResourceAttributes } from "@kubernetes/client-node";
|
import type { KubeConfig, V1ResourceAttributes } from "@kubernetes/client-node";
|
||||||
import { AuthorizationV1Api } from "@kubernetes/client-node";
|
import { AuthorizationV1Api } from "@kubernetes/client-node";
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import type { Logger } from "../logger";
|
|
||||||
import loggerInjectable from "../logger.injectable";
|
import loggerInjectable from "../logger.injectable";
|
||||||
|
import makeApiClientInjectable from "./make-api-client.injectable";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Requests the permissions for actions on the kube cluster
|
* Requests the permissions for actions on the kube cluster
|
||||||
@ -21,13 +21,14 @@ export type CanI = (resourceAttributes: V1ResourceAttributes) => Promise<boolean
|
|||||||
*/
|
*/
|
||||||
export type AuthorizationReview = (proxyConfig: KubeConfig) => CanI;
|
export type AuthorizationReview = (proxyConfig: KubeConfig) => CanI;
|
||||||
|
|
||||||
interface Dependencies {
|
const authorizationReviewInjectable = getInjectable({
|
||||||
logger: Logger;
|
id: "authorization-review",
|
||||||
}
|
instantiate: (di): AuthorizationReview => {
|
||||||
|
const logger = di.inject(loggerInjectable);
|
||||||
|
const makeApiClient = di.inject(makeApiClientInjectable);
|
||||||
|
|
||||||
const authorizationReview = ({ logger }: Dependencies): AuthorizationReview => {
|
|
||||||
return (proxyConfig) => {
|
return (proxyConfig) => {
|
||||||
const api = proxyConfig.makeApiClient(AuthorizationV1Api);
|
const api = makeApiClient(proxyConfig, AuthorizationV1Api);
|
||||||
|
|
||||||
return async (resourceAttributes: V1ResourceAttributes): Promise<boolean> => {
|
return async (resourceAttributes: V1ResourceAttributes): Promise<boolean> => {
|
||||||
try {
|
try {
|
||||||
@ -45,14 +46,6 @@ const authorizationReview = ({ logger }: Dependencies): AuthorizationReview => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
|
||||||
|
|
||||||
const authorizationReviewInjectable = getInjectable({
|
|
||||||
id: "authorization-review",
|
|
||||||
instantiate: (di) => {
|
|
||||||
const logger = di.inject(loggerInjectable);
|
|
||||||
|
|
||||||
return authorizationReview({ logger });
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -6,11 +6,18 @@ import type { KubeConfig } from "@kubernetes/client-node";
|
|||||||
import { CoreV1Api } from "@kubernetes/client-node";
|
import { CoreV1Api } from "@kubernetes/client-node";
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import { isDefined } from "../utils";
|
import { isDefined } from "../utils";
|
||||||
|
import makeApiClientInjectable from "./make-api-client.injectable";
|
||||||
|
|
||||||
export type ListNamespaces = () => Promise<string[]>;
|
export type ListNamespaces = () => Promise<string[]>;
|
||||||
|
export type ListNamespacesFor = (config: KubeConfig) => ListNamespaces;
|
||||||
|
|
||||||
export function listNamespaces(config: KubeConfig): ListNamespaces {
|
const listNamespacesForInjectable = getInjectable({
|
||||||
const coreApi = config.makeApiClient(CoreV1Api);
|
id: "list-namespaces-for",
|
||||||
|
instantiate: (di): ListNamespacesFor => {
|
||||||
|
const makeApiClient = di.inject(makeApiClientInjectable);
|
||||||
|
|
||||||
|
return (config) => {
|
||||||
|
const coreApi = makeApiClient(config, CoreV1Api);
|
||||||
|
|
||||||
return async () => {
|
return async () => {
|
||||||
const { body: { items }} = await coreApi.listNamespace();
|
const { body: { items }} = await coreApi.listNamespace();
|
||||||
@ -19,11 +26,8 @@ export function listNamespaces(config: KubeConfig): ListNamespaces {
|
|||||||
.map(ns => ns.metadata?.name)
|
.map(ns => ns.metadata?.name)
|
||||||
.filter(isDefined);
|
.filter(isDefined);
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
|
},
|
||||||
const listNamespacesInjectable = getInjectable({
|
|
||||||
id: "list-namespaces",
|
|
||||||
instantiate: () => listNamespaces,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default listNamespacesInjectable;
|
export default listNamespacesForInjectable;
|
||||||
|
|||||||
37
src/common/cluster/make-api-client.injectable.ts
Normal file
37
src/common/cluster/make-api-client.injectable.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { Authentication, Interceptor, KubeConfig } from "@kubernetes/client-node";
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import authHeaderValueInjectable from "../../features/auth-header/common/header-value.injectable";
|
||||||
|
import { lensAuthHeaderName } from "../../features/auth-header/common/vars";
|
||||||
|
|
||||||
|
export interface ApiType {
|
||||||
|
defaultHeaders: any;
|
||||||
|
setDefaultAuthentication(config: Authentication): void;
|
||||||
|
addInterceptor(interceptor: Interceptor): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type MakeApiClient = <T extends ApiType>(config: KubeConfig, apiClientType: new (server: string) => T) => T;
|
||||||
|
|
||||||
|
const makeApiClientInjectable = getInjectable({
|
||||||
|
id: "make-api-client",
|
||||||
|
instantiate: (di): MakeApiClient => {
|
||||||
|
const authHeaderValue = di.inject(authHeaderValueInjectable);
|
||||||
|
|
||||||
|
return (config, apiClientType) => {
|
||||||
|
const api = config.makeApiClient(apiClientType);
|
||||||
|
|
||||||
|
api.addInterceptor((opts) => {
|
||||||
|
opts.headers ??= {};
|
||||||
|
opts.headers[lensAuthHeaderName] = authHeaderValue;
|
||||||
|
});
|
||||||
|
|
||||||
|
return api;
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default makeApiClientInjectable;
|
||||||
@ -8,6 +8,7 @@ import { AuthorizationV1Api } from "@kubernetes/client-node";
|
|||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import loggerInjectable from "../logger.injectable";
|
import loggerInjectable from "../logger.injectable";
|
||||||
import type { KubeApiResource } from "../rbac";
|
import type { KubeApiResource } from "../rbac";
|
||||||
|
import makeApiClientInjectable from "./make-api-client.injectable";
|
||||||
|
|
||||||
export type CanListResource = (resource: KubeApiResource) => boolean;
|
export type CanListResource = (resource: KubeApiResource) => boolean;
|
||||||
|
|
||||||
@ -26,9 +27,10 @@ const requestNamespaceListPermissionsForInjectable = getInjectable({
|
|||||||
id: "request-namespace-list-permissions-for",
|
id: "request-namespace-list-permissions-for",
|
||||||
instantiate: (di): RequestNamespaceListPermissionsFor => {
|
instantiate: (di): RequestNamespaceListPermissionsFor => {
|
||||||
const logger = di.inject(loggerInjectable);
|
const logger = di.inject(loggerInjectable);
|
||||||
|
const makeApiClient = di.inject(makeApiClientInjectable);
|
||||||
|
|
||||||
return (proxyConfig) => {
|
return (proxyConfig) => {
|
||||||
const api = proxyConfig.makeApiClient(AuthorizationV1Api);
|
const api = makeApiClient(proxyConfig, AuthorizationV1Api);
|
||||||
|
|
||||||
return async (namespace) => {
|
return async (namespace) => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@ -5,6 +5,8 @@
|
|||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import { Agent } from "https";
|
import { Agent } from "https";
|
||||||
import type { RequestInit, Response } from "node-fetch";
|
import type { RequestInit, Response } from "node-fetch";
|
||||||
|
import authHeaderValueInjectable from "../../features/auth-header/common/header-value.injectable";
|
||||||
|
import { lensAuthHeaderName } from "../../features/auth-header/common/vars";
|
||||||
import lensProxyPortInjectable from "../../main/lens-proxy/lens-proxy-port.injectable";
|
import lensProxyPortInjectable from "../../main/lens-proxy/lens-proxy-port.injectable";
|
||||||
import lensProxyCertificateInjectable from "../certificate/lens-proxy-certificate.injectable";
|
import lensProxyCertificateInjectable from "../certificate/lens-proxy-certificate.injectable";
|
||||||
import nodeFetchModuleInjectable from "./fetch-module.injectable";
|
import nodeFetchModuleInjectable from "./fetch-module.injectable";
|
||||||
@ -16,14 +18,21 @@ export type LensFetch = (pathnameAndQuery: string, init?: LensRequestInit) => Pr
|
|||||||
const lensFetchInjectable = getInjectable({
|
const lensFetchInjectable = getInjectable({
|
||||||
id: "lens-fetch",
|
id: "lens-fetch",
|
||||||
instantiate: (di): LensFetch => {
|
instantiate: (di): LensFetch => {
|
||||||
const { default: fetch } = di.inject(nodeFetchModuleInjectable);
|
const { default: fetch, Headers } = di.inject(nodeFetchModuleInjectable);
|
||||||
const lensProxyPort = di.inject(lensProxyPortInjectable);
|
const lensProxyPort = di.inject(lensProxyPortInjectable);
|
||||||
const lensProxyCertificate = di.inject(lensProxyCertificateInjectable);
|
const lensProxyCertificate = di.inject(lensProxyCertificateInjectable);
|
||||||
|
const authHeaderValue = di.inject(authHeaderValueInjectable);
|
||||||
|
|
||||||
return async (pathnameAndQuery, init = {}) => {
|
return async (pathnameAndQuery, {
|
||||||
|
headers: _headers,
|
||||||
|
...init
|
||||||
|
} = {}) => {
|
||||||
const agent = new Agent({
|
const agent = new Agent({
|
||||||
ca: lensProxyCertificate.get().cert,
|
ca: lensProxyCertificate.get().cert,
|
||||||
});
|
});
|
||||||
|
const headers = new Headers(_headers);
|
||||||
|
|
||||||
|
headers.set(lensAuthHeaderName, authHeaderValue);
|
||||||
|
|
||||||
return fetch(`https://127.0.0.1:${lensProxyPort.get()}${pathnameAndQuery}`, {
|
return fetch(`https://127.0.0.1:${lensProxyPort.get()}${pathnameAndQuery}`, {
|
||||||
...init,
|
...init,
|
||||||
|
|||||||
@ -3,6 +3,8 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import authHeaderValueInjectable from "../../features/auth-header/common/header-value.injectable";
|
||||||
|
import { lensAuthHeaderName } from "../../features/auth-header/common/vars";
|
||||||
import { apiPrefix } from "../vars";
|
import { apiPrefix } from "../vars";
|
||||||
import isDebuggingInjectable from "../vars/is-debugging.injectable";
|
import isDebuggingInjectable from "../vars/is-debugging.injectable";
|
||||||
import isDevelopmentInjectable from "../vars/is-development.injectable";
|
import isDevelopmentInjectable from "../vars/is-development.injectable";
|
||||||
@ -17,6 +19,7 @@ const apiBaseInjectable = getInjectable({
|
|||||||
const isDevelopment = di.inject(isDevelopmentInjectable);
|
const isDevelopment = di.inject(isDevelopmentInjectable);
|
||||||
const serverAddress = di.inject(apiBaseServerAddressInjectionToken);
|
const serverAddress = di.inject(apiBaseServerAddressInjectionToken);
|
||||||
const hostHeaderValue = di.inject(apiBaseHostHeaderInjectionToken);
|
const hostHeaderValue = di.inject(apiBaseHostHeaderInjectionToken);
|
||||||
|
const authHeaderValue = di.inject(authHeaderValueInjectable);
|
||||||
|
|
||||||
return createJsonApi({
|
return createJsonApi({
|
||||||
serverAddress,
|
serverAddress,
|
||||||
@ -25,6 +28,7 @@ const apiBaseInjectable = getInjectable({
|
|||||||
}, {
|
}, {
|
||||||
headers: {
|
headers: {
|
||||||
"Host": hostHeaderValue,
|
"Host": hostHeaderValue,
|
||||||
|
[lensAuthHeaderName]: authHeaderValue,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
@ -3,6 +3,8 @@
|
|||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import authHeaderValueInjectable from "../../features/auth-header/common/header-value.injectable";
|
||||||
|
import { lensAuthHeaderName } from "../../features/auth-header/common/vars";
|
||||||
import { apiKubePrefix } from "../vars";
|
import { apiKubePrefix } from "../vars";
|
||||||
import isDebuggingInjectable from "../vars/is-debugging.injectable";
|
import isDebuggingInjectable from "../vars/is-debugging.injectable";
|
||||||
import { apiBaseHostHeaderInjectionToken, apiBaseServerAddressInjectionToken } from "./api-base-configs";
|
import { apiBaseHostHeaderInjectionToken, apiBaseServerAddressInjectionToken } from "./api-base-configs";
|
||||||
@ -16,6 +18,7 @@ const createKubeJsonApiForClusterInjectable = getInjectable({
|
|||||||
instantiate: (di): CreateKubeJsonApiForCluster => {
|
instantiate: (di): CreateKubeJsonApiForCluster => {
|
||||||
const createKubeJsonApi = di.inject(createKubeJsonApiInjectable);
|
const createKubeJsonApi = di.inject(createKubeJsonApiInjectable);
|
||||||
const isDebugging = di.inject(isDebuggingInjectable);
|
const isDebugging = di.inject(isDebuggingInjectable);
|
||||||
|
const authHeaderValue = di.inject(authHeaderValueInjectable);
|
||||||
|
|
||||||
return (clusterId) => createKubeJsonApi(
|
return (clusterId) => createKubeJsonApi(
|
||||||
{
|
{
|
||||||
@ -26,6 +29,7 @@ const createKubeJsonApiForClusterInjectable = getInjectable({
|
|||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
"Host": `${clusterId}.${di.inject(apiBaseHostHeaderInjectionToken)}`,
|
"Host": `${clusterId}.${di.inject(apiBaseHostHeaderInjectionToken)}`,
|
||||||
|
[lensAuthHeaderName]: authHeaderValue,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
13
src/features/auth-header/common/header-value.injectable.ts
Normal file
13
src/features/auth-header/common/header-value.injectable.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
/**
|
||||||
|
* 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 authHeaderStateInjectable from "./header-state.injectable";
|
||||||
|
|
||||||
|
const authHeaderValueInjectable = getInjectable({
|
||||||
|
id: "auth-header-value",
|
||||||
|
instantiate: (di) => `Bearer ${di.inject(authHeaderStateInjectable).get()}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default authHeaderValueInjectable;
|
||||||
@ -6,3 +6,5 @@
|
|||||||
import { getRequestChannel } from "../../../common/utils/channel/get-request-channel";
|
import { getRequestChannel } from "../../../common/utils/channel/get-request-channel";
|
||||||
|
|
||||||
export const authHeaderChannel = getRequestChannel<void, string>("auth-header-value");
|
export const authHeaderChannel = getRequestChannel<void, string>("auth-header-value");
|
||||||
|
|
||||||
|
export const lensAuthHeaderName = "Authorization";
|
||||||
@ -4,7 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { getRequestChannelListenerInjectable } from "../../../main/utils/channel/channel-listeners/listener-tokens";
|
import { getRequestChannelListenerInjectable } from "../../../main/utils/channel/channel-listeners/listener-tokens";
|
||||||
import { authHeaderChannel } from "../common/channel";
|
import { authHeaderChannel } from "../common/vars";
|
||||||
import authHeaderStateInjectable from "../common/header-state.injectable";
|
import authHeaderStateInjectable from "../common/header-state.injectable";
|
||||||
|
|
||||||
const authHeaderRequestListenerInjectable = getRequestChannelListenerInjectable({
|
const authHeaderRequestListenerInjectable = getRequestChannelListenerInjectable({
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import requestFromChannelInjectable from "../../../renderer/utils/channel/request-from-channel.injectable";
|
import requestFromChannelInjectable from "../../../renderer/utils/channel/request-from-channel.injectable";
|
||||||
import { authHeaderChannel } from "../common/channel";
|
import { authHeaderChannel } from "../common/vars";
|
||||||
|
|
||||||
const requestAuthHeaderValueInjectable = getInjectable({
|
const requestAuthHeaderValueInjectable = getInjectable({
|
||||||
id: "request-auth-header-value",
|
id: "request-auth-header-value",
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import type { CreateCluster } from "../../common/cluster/create-cluster-injectio
|
|||||||
import { createClusterInjectionToken } from "../../common/cluster/create-cluster-injection-token";
|
import { createClusterInjectionToken } from "../../common/cluster/create-cluster-injection-token";
|
||||||
import authorizationReviewInjectable from "../../common/cluster/authorization-review.injectable";
|
import authorizationReviewInjectable from "../../common/cluster/authorization-review.injectable";
|
||||||
import requestNamespaceListPermissionsForInjectable from "../../common/cluster/request-namespace-list-permissions.injectable";
|
import requestNamespaceListPermissionsForInjectable from "../../common/cluster/request-namespace-list-permissions.injectable";
|
||||||
import listNamespacesInjectable from "../../common/cluster/list-namespaces.injectable";
|
import listNamespacesForInjectable from "../../common/cluster/list-namespaces.injectable";
|
||||||
import createContextHandlerInjectable from "../context-handler/create-context-handler.injectable";
|
import createContextHandlerInjectable from "../context-handler/create-context-handler.injectable";
|
||||||
import type { ClusterContextHandler } from "../context-handler/context-handler";
|
import type { ClusterContextHandler } from "../context-handler/context-handler";
|
||||||
import { parse } from "url";
|
import { parse } from "url";
|
||||||
@ -42,7 +42,7 @@ describe("create clusters", () => {
|
|||||||
di.override(broadcastMessageInjectable, () => async () => {});
|
di.override(broadcastMessageInjectable, () => async () => {});
|
||||||
di.override(authorizationReviewInjectable, () => () => () => Promise.resolve(true));
|
di.override(authorizationReviewInjectable, () => () => () => Promise.resolve(true));
|
||||||
di.override(requestNamespaceListPermissionsForInjectable, () => () => async () => () => true);
|
di.override(requestNamespaceListPermissionsForInjectable, () => () => async () => () => true);
|
||||||
di.override(listNamespacesInjectable, () => () => () => Promise.resolve([ "default" ]));
|
di.override(listNamespacesForInjectable, () => () => () => Promise.resolve([ "default" ]));
|
||||||
di.override(createContextHandlerInjectable, () => (cluster) => ({
|
di.override(createContextHandlerInjectable, () => (cluster) => ({
|
||||||
restartServer: jest.fn(),
|
restartServer: jest.fn(),
|
||||||
stopServer: jest.fn(),
|
stopServer: jest.fn(),
|
||||||
|
|||||||
@ -15,6 +15,7 @@ import type { CreateKubeAuthProxy } from "../kube-auth-proxy/create-kube-auth-pr
|
|||||||
import type { GetPrometheusProviderByKind } from "../prometheus/get-by-kind.injectable";
|
import type { GetPrometheusProviderByKind } from "../prometheus/get-by-kind.injectable";
|
||||||
import type { IComputedValue } from "mobx";
|
import type { IComputedValue } from "mobx";
|
||||||
import type { Logger } from "../../common/logger";
|
import type { Logger } from "../../common/logger";
|
||||||
|
import type { MakeApiClient } from "../../common/cluster/make-api-client.injectable";
|
||||||
|
|
||||||
export interface PrometheusDetails {
|
export interface PrometheusDetails {
|
||||||
prometheusPath: string;
|
prometheusPath: string;
|
||||||
@ -31,6 +32,7 @@ interface PrometheusServicePreferences {
|
|||||||
export interface ContextHandlerDependencies {
|
export interface ContextHandlerDependencies {
|
||||||
createKubeAuthProxy: CreateKubeAuthProxy;
|
createKubeAuthProxy: CreateKubeAuthProxy;
|
||||||
getPrometheusProviderByKind: GetPrometheusProviderByKind;
|
getPrometheusProviderByKind: GetPrometheusProviderByKind;
|
||||||
|
makeApiClient: MakeApiClient;
|
||||||
readonly authProxyCa: string;
|
readonly authProxyCa: string;
|
||||||
readonly prometheusProviders: IComputedValue<PrometheusProvider[]>;
|
readonly prometheusProviders: IComputedValue<PrometheusProvider[]>;
|
||||||
readonly logger: Logger;
|
readonly logger: Logger;
|
||||||
@ -110,7 +112,7 @@ export class ContextHandler implements ClusterContextHandler {
|
|||||||
|
|
||||||
const providers = this.listPotentialProviders();
|
const providers = this.listPotentialProviders();
|
||||||
const proxyConfig = await this.cluster.getProxyKubeconfig();
|
const proxyConfig = await this.cluster.getProxyKubeconfig();
|
||||||
const apiClient = proxyConfig.makeApiClient(CoreV1Api);
|
const apiClient = this.dependencies.makeApiClient(proxyConfig, CoreV1Api);
|
||||||
const potentialServices = await Promise.allSettled(
|
const potentialServices = await Promise.allSettled(
|
||||||
providers.map(provider => provider.getPrometheusService(apiClient)),
|
providers.map(provider => provider.getPrometheusService(apiClient)),
|
||||||
);
|
);
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import URLParse from "url-parse";
|
|||||||
import getPrometheusProviderByKindInjectable from "../prometheus/get-by-kind.injectable";
|
import getPrometheusProviderByKindInjectable from "../prometheus/get-by-kind.injectable";
|
||||||
import prometheusProvidersInjectable from "../prometheus/providers.injectable";
|
import prometheusProvidersInjectable from "../prometheus/providers.injectable";
|
||||||
import loggerInjectable from "../../common/logger.injectable";
|
import loggerInjectable from "../../common/logger.injectable";
|
||||||
|
import makeApiClientInjectable from "../../common/cluster/make-api-client.injectable";
|
||||||
|
|
||||||
const createContextHandlerInjectable = getInjectable({
|
const createContextHandlerInjectable = getInjectable({
|
||||||
id: "create-context-handler",
|
id: "create-context-handler",
|
||||||
@ -22,6 +23,7 @@ const createContextHandlerInjectable = getInjectable({
|
|||||||
getPrometheusProviderByKind: di.inject(getPrometheusProviderByKindInjectable),
|
getPrometheusProviderByKind: di.inject(getPrometheusProviderByKindInjectable),
|
||||||
prometheusProviders: di.inject(prometheusProvidersInjectable),
|
prometheusProviders: di.inject(prometheusProvidersInjectable),
|
||||||
logger: di.inject(loggerInjectable),
|
logger: di.inject(loggerInjectable),
|
||||||
|
makeApiClient: di.inject(makeApiClientInjectable),
|
||||||
};
|
};
|
||||||
|
|
||||||
return (cluster: Cluster): ClusterContextHandler => {
|
return (cluster: Cluster): ClusterContextHandler => {
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import createKubectlInjectable from "../kubectl/create-kubectl.injectable";
|
|||||||
import createContextHandlerInjectable from "../context-handler/create-context-handler.injectable";
|
import createContextHandlerInjectable from "../context-handler/create-context-handler.injectable";
|
||||||
import { createClusterInjectionToken } from "../../common/cluster/create-cluster-injection-token";
|
import { createClusterInjectionToken } from "../../common/cluster/create-cluster-injection-token";
|
||||||
import authorizationReviewInjectable from "../../common/cluster/authorization-review.injectable";
|
import authorizationReviewInjectable from "../../common/cluster/authorization-review.injectable";
|
||||||
import listNamespacesInjectable from "../../common/cluster/list-namespaces.injectable";
|
import listNamespacesForInjectable from "../../common/cluster/list-namespaces.injectable";
|
||||||
import createListApiResourcesInjectable from "../cluster/request-api-resources.injectable";
|
import createListApiResourcesInjectable from "../cluster/request-api-resources.injectable";
|
||||||
import loggerInjectable from "../../common/logger.injectable";
|
import loggerInjectable from "../../common/logger.injectable";
|
||||||
import broadcastMessageInjectable from "../../common/ipc/broadcast-message.injectable";
|
import broadcastMessageInjectable from "../../common/ipc/broadcast-message.injectable";
|
||||||
@ -34,7 +34,7 @@ const createClusterInjectable = getInjectable({
|
|||||||
createAuthorizationReview: di.inject(authorizationReviewInjectable),
|
createAuthorizationReview: di.inject(authorizationReviewInjectable),
|
||||||
requestNamespaceListPermissionsFor: di.inject(requestNamespaceListPermissionsForInjectable),
|
requestNamespaceListPermissionsFor: di.inject(requestNamespaceListPermissionsForInjectable),
|
||||||
requestApiResources: di.inject(createListApiResourcesInjectable),
|
requestApiResources: di.inject(createListApiResourcesInjectable),
|
||||||
createListNamespaces: di.inject(listNamespacesInjectable),
|
createListNamespaces: di.inject(listNamespacesForInjectable),
|
||||||
broadcastMessage: di.inject(broadcastMessageInjectable),
|
broadcastMessage: di.inject(broadcastMessageInjectable),
|
||||||
loadConfigfromFile: di.inject(loadConfigfromFileInjectable),
|
loadConfigfromFile: di.inject(loadConfigfromFileInjectable),
|
||||||
detectClusterMetadata: di.inject(detectClusterMetadataInjectable),
|
detectClusterMetadata: di.inject(detectClusterMetadataInjectable),
|
||||||
|
|||||||
60
src/main/lens-proxy/handle-proxy-upgrade.injectable.ts
Normal file
60
src/main/lens-proxy/handle-proxy-upgrade.injectable.ts
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
/**
|
||||||
|
* 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 { IncomingMessage } from "http";
|
||||||
|
import type { Socket } from "net";
|
||||||
|
import type { SetRequired } from "type-fest";
|
||||||
|
import loggerInjectable from "../../common/logger.injectable";
|
||||||
|
import { apiPrefix, apiKubePrefix } from "../../common/vars";
|
||||||
|
import authHeaderStateInjectable from "../../features/auth-header/common/header-state.injectable";
|
||||||
|
import { lensAuthHeaderName } from "../../features/auth-header/common/vars";
|
||||||
|
import getClusterForRequestInjectable from "./get-cluster-for-request.injectable";
|
||||||
|
import { kubeApiUpgradeRequest } from "./proxy-functions";
|
||||||
|
import shellApiRequestInjectable from "./proxy-functions/shell-api-request.injectable";
|
||||||
|
|
||||||
|
const handleProxyUpgradeRequestInjectable = getInjectable({
|
||||||
|
id: "handle-proxy-upgrade-request",
|
||||||
|
instantiate: (di) => {
|
||||||
|
const getClusterForRequest = di.inject(getClusterForRequestInjectable);
|
||||||
|
const shellApiRequest = di.inject(shellApiRequestInjectable);
|
||||||
|
const logger = di.inject(loggerInjectable);
|
||||||
|
const authHeaderValue = `Bearer ${di.inject(authHeaderStateInjectable).get()}`;
|
||||||
|
|
||||||
|
return (req: SetRequired<IncomingMessage, "url" | "method">, socket: Socket, head: Buffer) => {
|
||||||
|
const cluster = getClusterForRequest(req);
|
||||||
|
const url = new URL(req.url, "https://localhost");
|
||||||
|
|
||||||
|
if (url.searchParams.get(lensAuthHeaderName) !== authHeaderValue) {
|
||||||
|
logger.warn(`[LENS-PROXY]: Request from url=${req.url} missing authentication`);
|
||||||
|
socket.destroy();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!cluster) {
|
||||||
|
logger.error(`[LENS-PROXY]: Could not find cluster for upgrade request from url=${req.url}`);
|
||||||
|
socket.destroy();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
if (url.pathname === apiPrefix) {
|
||||||
|
await shellApiRequest({ req, socket, cluster, head });
|
||||||
|
} else if (url.pathname.startsWith(`${apiKubePrefix}/`)) {
|
||||||
|
await kubeApiUpgradeRequest({ req, socket, cluster, head });
|
||||||
|
} else {
|
||||||
|
logger.warn(`[LENS-PROXY]: unknown upgrade request, url=${req.url}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("[LENS-PROXY]: failed to handle proxy upgrade", error);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default handleProxyUpgradeRequestInjectable;
|
||||||
@ -5,6 +5,8 @@
|
|||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import type { ServerResponse } from "http";
|
import type { ServerResponse } from "http";
|
||||||
import loggerInjectable from "../../common/logger.injectable";
|
import loggerInjectable from "../../common/logger.injectable";
|
||||||
|
import authHeaderValueInjectable from "../../features/auth-header/common/header-value.injectable";
|
||||||
|
import { lensAuthHeaderName } from "../../features/auth-header/common/vars";
|
||||||
import type { LensApiRequest, Route } from "./route";
|
import type { LensApiRequest, Route } from "./route";
|
||||||
import { contentTypes } from "./router-content-types";
|
import { contentTypes } from "./router-content-types";
|
||||||
import { writeServerResponseFor } from "./write-server-response";
|
import { writeServerResponseFor } from "./write-server-response";
|
||||||
@ -16,10 +18,24 @@ const createHandlerForRouteInjectable = getInjectable({
|
|||||||
id: "create-handler-for-route",
|
id: "create-handler-for-route",
|
||||||
instantiate: (di): CreateHandlerForRoute => {
|
instantiate: (di): CreateHandlerForRoute => {
|
||||||
const logger = di.inject(loggerInjectable);
|
const logger = di.inject(loggerInjectable);
|
||||||
|
const authHeaderValue = di.inject(authHeaderValueInjectable);
|
||||||
|
|
||||||
return (route) => async (request, response) => {
|
return (route) => async (request, response) => {
|
||||||
const writeServerResponse = writeServerResponseFor(response);
|
const writeServerResponse = writeServerResponseFor(response);
|
||||||
|
|
||||||
|
if (route.requireAuthentication) {
|
||||||
|
const authHeader = request.getHeader(lensAuthHeaderName);
|
||||||
|
|
||||||
|
if (authHeader !== authHeaderValue) {
|
||||||
|
writeServerResponse(contentTypes.txt.resultMapper({
|
||||||
|
statusCode: 401,
|
||||||
|
response: "Missing authorization",
|
||||||
|
}));
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await route.handler(request);
|
const result = await route.handler(request);
|
||||||
|
|
||||||
|
|||||||
@ -66,6 +66,7 @@ export interface RouteHandler<TResponse, Path extends string>{
|
|||||||
export interface BaseRoutePaths<Path extends string> {
|
export interface BaseRoutePaths<Path extends string> {
|
||||||
path: Path;
|
path: Path;
|
||||||
method: "get" | "post" | "put" | "patch" | "delete";
|
method: "get" | "post" | "put" | "patch" | "delete";
|
||||||
|
requireAuthentication?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PayloadValidator<Payload> {
|
export interface PayloadValidator<Payload> {
|
||||||
@ -78,18 +79,21 @@ export interface ValidatorBaseRoutePaths<Path extends string, Payload> extends B
|
|||||||
|
|
||||||
export interface Route<TResponse, Path extends string> extends BaseRoutePaths<Path> {
|
export interface Route<TResponse, Path extends string> extends BaseRoutePaths<Path> {
|
||||||
handler: RouteHandler<TResponse, Path>;
|
handler: RouteHandler<TResponse, Path>;
|
||||||
|
readonly requireAuthentication: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BindHandler<Path extends string> {
|
export interface BindHandler<Path extends string> {
|
||||||
<TResponse>(handler: RouteHandler<TResponse, Path>): Route<TResponse, Path>;
|
<TResponse>(handler: RouteHandler<TResponse, Path>): Route<TResponse, Path>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function route<Path extends string>(parts: BaseRoutePaths<Path>): BindHandler<Path> {
|
export const route = <Path extends string>({
|
||||||
return (handler) => ({
|
requireAuthentication = true,
|
||||||
|
...parts
|
||||||
|
}: BaseRoutePaths<Path>): BindHandler<Path> => (handler) => ({
|
||||||
...parts,
|
...parts,
|
||||||
handler,
|
handler,
|
||||||
|
requireAuthentication,
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
export interface ClusterRouteHandler<Response, Path extends string>{
|
export interface ClusterRouteHandler<Response, Path extends string>{
|
||||||
(request: ClusterLensApiRequest<Path>): RouteResponse<Response> | Promise<RouteResponse<Response>>;
|
(request: ClusterLensApiRequest<Path>): RouteResponse<Response> | Promise<RouteResponse<Response>>;
|
||||||
@ -99,8 +103,10 @@ export interface BindClusterHandler<Path extends string> {
|
|||||||
<TResponse>(handler: ClusterRouteHandler<TResponse, Path>): Route<TResponse, Path>;
|
<TResponse>(handler: ClusterRouteHandler<TResponse, Path>): Route<TResponse, Path>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function clusterRoute<Path extends string>(parts: BaseRoutePaths<Path>): BindClusterHandler<Path> {
|
export const clusterRoute = <Path extends string>({
|
||||||
return (handler) => ({
|
requireAuthentication = true,
|
||||||
|
...parts
|
||||||
|
}: BaseRoutePaths<Path>): BindClusterHandler<Path> => (handler) => ({
|
||||||
...parts,
|
...parts,
|
||||||
handler: ({ cluster, ...rest }) => {
|
handler: ({ cluster, ...rest }) => {
|
||||||
if (!cluster) {
|
if (!cluster) {
|
||||||
@ -112,8 +118,8 @@ export function clusterRoute<Path extends string>(parts: BaseRoutePaths<Path>):
|
|||||||
|
|
||||||
return handler({ cluster, ...rest });
|
return handler({ cluster, ...rest });
|
||||||
},
|
},
|
||||||
|
requireAuthentication,
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
export interface ValidatedClusterLensApiRequest<Path extends string, Payload> extends ClusterLensApiRequest<Path> {
|
export interface ValidatedClusterLensApiRequest<Path extends string, Payload> extends ClusterLensApiRequest<Path> {
|
||||||
payload: Payload;
|
payload: Payload;
|
||||||
|
|||||||
@ -17,6 +17,7 @@ const staticFileRouteInjectable = getRouteInjectable({
|
|||||||
return route({
|
return route({
|
||||||
method: "get",
|
method: "get",
|
||||||
path: `/{path*}`,
|
path: `/{path*}`,
|
||||||
|
requireAuthentication: false,
|
||||||
})(
|
})(
|
||||||
isDevelopment
|
isDevelopment
|
||||||
? di.inject(devStaticFileRouteHandlerInjectable)
|
? di.inject(devStaticFileRouteHandlerInjectable)
|
||||||
|
|||||||
@ -10,15 +10,18 @@ import type { V1Secret } from "@kubernetes/client-node";
|
|||||||
import { CoreV1Api } from "@kubernetes/client-node";
|
import { CoreV1Api } from "@kubernetes/client-node";
|
||||||
import { clusterRoute } from "../../router/route";
|
import { clusterRoute } from "../../router/route";
|
||||||
import { dump } from "js-yaml";
|
import { dump } from "js-yaml";
|
||||||
|
import makeApiClientInjectable from "../../../common/cluster/make-api-client.injectable";
|
||||||
|
|
||||||
const getServiceAccountRouteInjectable = getRouteInjectable({
|
const getServiceAccountRouteInjectable = getRouteInjectable({
|
||||||
id: "get-service-account-route",
|
id: "get-service-account-route",
|
||||||
|
|
||||||
instantiate: () => clusterRoute({
|
instantiate: (di) => clusterRoute({
|
||||||
method: "get",
|
method: "get",
|
||||||
path: `${apiPrefix}/kubeconfig/service-account/{namespace}/{account}`,
|
path: `${apiPrefix}/kubeconfig/service-account/{namespace}/{account}`,
|
||||||
})(async ({ params, cluster }) => {
|
})(async ({ params, cluster }) => {
|
||||||
const client = (await cluster.getProxyKubeconfig()).makeApiClient(CoreV1Api);
|
const makeApiClient = di.inject(makeApiClientInjectable);
|
||||||
|
const config = await cluster.getProxyKubeconfig();
|
||||||
|
const client = makeApiClient(config, CoreV1Api);
|
||||||
const secretList = await client.listNamespacedSecret(params.namespace);
|
const secretList = await client.listNamespacedSecret(params.namespace);
|
||||||
|
|
||||||
const secret = secretList.body.items.find(secret => {
|
const secret = secretList.body.items.find(secret => {
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import { NodeApi } from "../../../common/k8s-api/endpoints";
|
|||||||
import { TerminalChannels } from "../../../common/terminal/channels";
|
import { TerminalChannels } from "../../../common/terminal/channels";
|
||||||
import type { CreateKubeJsonApiForCluster } from "../../../common/k8s-api/create-kube-json-api-for-cluster.injectable";
|
import type { CreateKubeJsonApiForCluster } from "../../../common/k8s-api/create-kube-json-api-for-cluster.injectable";
|
||||||
import type { CreateKubeApi } from "../../../common/k8s-api/create-kube-api.injectable";
|
import type { CreateKubeApi } from "../../../common/k8s-api/create-kube-api.injectable";
|
||||||
|
import type { MakeApiClient } from "../../../common/cluster/make-api-client.injectable";
|
||||||
|
|
||||||
export interface NodeShellSessionArgs extends ShellSessionArgs {
|
export interface NodeShellSessionArgs extends ShellSessionArgs {
|
||||||
nodeName: string;
|
nodeName: string;
|
||||||
@ -21,6 +22,7 @@ export interface NodeShellSessionArgs extends ShellSessionArgs {
|
|||||||
export interface NodeShellSessionDependencies extends ShellSessionDependencies {
|
export interface NodeShellSessionDependencies extends ShellSessionDependencies {
|
||||||
createKubeJsonApiForCluster: CreateKubeJsonApiForCluster;
|
createKubeJsonApiForCluster: CreateKubeJsonApiForCluster;
|
||||||
createKubeApi: CreateKubeApi;
|
createKubeApi: CreateKubeApi;
|
||||||
|
makeApiClient: MakeApiClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class NodeShellSession extends ShellSession {
|
export class NodeShellSession extends ShellSession {
|
||||||
@ -36,8 +38,8 @@ export class NodeShellSession extends ShellSession {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async open() {
|
public async open() {
|
||||||
const kc = await this.cluster.getProxyKubeconfig();
|
const config = await this.cluster.getProxyKubeconfig();
|
||||||
const coreApi = kc.makeApiClient(CoreV1Api);
|
const coreApi = this.dependencies.makeApiClient(config, CoreV1Api);
|
||||||
const shell = await this.kubectl.getPath();
|
const shell = await this.kubectl.getPath();
|
||||||
|
|
||||||
const cleanup = once(() => {
|
const cleanup = once(() => {
|
||||||
@ -50,7 +52,7 @@ export class NodeShellSession extends ShellSession {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await this.createNodeShellPod(coreApi);
|
await this.createNodeShellPod(coreApi);
|
||||||
await this.waitForRunningPod(kc);
|
await this.waitForRunningPod(config);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
cleanup();
|
cleanup();
|
||||||
|
|
||||||
|
|||||||
@ -20,6 +20,7 @@ import buildVersionInjectable from "../../vars/build-version/build-version.injec
|
|||||||
import emitAppEventInjectable from "../../../common/app-event-bus/emit-event.injectable";
|
import emitAppEventInjectable from "../../../common/app-event-bus/emit-event.injectable";
|
||||||
import statInjectable from "../../../common/fs/stat.injectable";
|
import statInjectable from "../../../common/fs/stat.injectable";
|
||||||
import createKubeApiInjectable from "../../../common/k8s-api/create-kube-api.injectable";
|
import createKubeApiInjectable from "../../../common/k8s-api/create-kube-api.injectable";
|
||||||
|
import makeApiClientInjectable from "../../../common/cluster/make-api-client.injectable";
|
||||||
|
|
||||||
export interface NodeShellSessionArgs {
|
export interface NodeShellSessionArgs {
|
||||||
websocket: WebSocket;
|
websocket: WebSocket;
|
||||||
@ -47,6 +48,7 @@ const openNodeShellSessionInjectable = getInjectable({
|
|||||||
emitAppEvent: di.inject(emitAppEventInjectable),
|
emitAppEvent: di.inject(emitAppEventInjectable),
|
||||||
stat: di.inject(statInjectable),
|
stat: di.inject(statInjectable),
|
||||||
createKubeApi: di.inject(createKubeApiInjectable),
|
createKubeApi: di.inject(createKubeApiInjectable),
|
||||||
|
makeApiClient: di.inject(makeApiClientInjectable),
|
||||||
};
|
};
|
||||||
|
|
||||||
return async (args) => {
|
return async (args) => {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user