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 { AuthorizationV1Api } from "@kubernetes/client-node";
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import type { Logger } from "../logger";
|
||||
import loggerInjectable from "../logger.injectable";
|
||||
import makeApiClientInjectable from "./make-api-client.injectable";
|
||||
|
||||
/**
|
||||
* Requests the permissions for actions on the kube cluster
|
||||
@ -21,38 +21,31 @@ export type CanI = (resourceAttributes: V1ResourceAttributes) => Promise<boolean
|
||||
*/
|
||||
export type AuthorizationReview = (proxyConfig: KubeConfig) => CanI;
|
||||
|
||||
interface Dependencies {
|
||||
logger: Logger;
|
||||
}
|
||||
|
||||
const authorizationReview = ({ logger }: Dependencies): AuthorizationReview => {
|
||||
return (proxyConfig) => {
|
||||
const api = proxyConfig.makeApiClient(AuthorizationV1Api);
|
||||
|
||||
return async (resourceAttributes: V1ResourceAttributes): Promise<boolean> => {
|
||||
try {
|
||||
const { body } = await api.createSelfSubjectAccessReview({
|
||||
apiVersion: "authorization.k8s.io/v1",
|
||||
kind: "SelfSubjectAccessReview",
|
||||
spec: { resourceAttributes },
|
||||
});
|
||||
|
||||
return body.status?.allowed ?? false;
|
||||
} catch (error) {
|
||||
logger.error(`[AUTHORIZATION-REVIEW]: failed to create access review: ${error}`, { resourceAttributes });
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
const authorizationReviewInjectable = getInjectable({
|
||||
id: "authorization-review",
|
||||
instantiate: (di) => {
|
||||
instantiate: (di): AuthorizationReview => {
|
||||
const logger = di.inject(loggerInjectable);
|
||||
const makeApiClient = di.inject(makeApiClientInjectable);
|
||||
|
||||
return authorizationReview({ logger });
|
||||
return (proxyConfig) => {
|
||||
const api = makeApiClient(proxyConfig, AuthorizationV1Api);
|
||||
|
||||
return async (resourceAttributes: V1ResourceAttributes): Promise<boolean> => {
|
||||
try {
|
||||
const { body } = await api.createSelfSubjectAccessReview({
|
||||
apiVersion: "authorization.k8s.io/v1",
|
||||
kind: "SelfSubjectAccessReview",
|
||||
spec: { resourceAttributes },
|
||||
});
|
||||
|
||||
return body.status?.allowed ?? false;
|
||||
} catch (error) {
|
||||
logger.error(`[AUTHORIZATION-REVIEW]: failed to create access review: ${error}`, { resourceAttributes });
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@ -6,24 +6,28 @@ import type { KubeConfig } from "@kubernetes/client-node";
|
||||
import { CoreV1Api } from "@kubernetes/client-node";
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { isDefined } from "../utils";
|
||||
import makeApiClientInjectable from "./make-api-client.injectable";
|
||||
|
||||
export type ListNamespaces = () => Promise<string[]>;
|
||||
export type ListNamespacesFor = (config: KubeConfig) => ListNamespaces;
|
||||
|
||||
export function listNamespaces(config: KubeConfig): ListNamespaces {
|
||||
const coreApi = config.makeApiClient(CoreV1Api);
|
||||
const listNamespacesForInjectable = getInjectable({
|
||||
id: "list-namespaces-for",
|
||||
instantiate: (di): ListNamespacesFor => {
|
||||
const makeApiClient = di.inject(makeApiClientInjectable);
|
||||
|
||||
return async () => {
|
||||
const { body: { items }} = await coreApi.listNamespace();
|
||||
return (config) => {
|
||||
const coreApi = makeApiClient(config, CoreV1Api);
|
||||
|
||||
return items
|
||||
.map(ns => ns.metadata?.name)
|
||||
.filter(isDefined);
|
||||
};
|
||||
}
|
||||
return async () => {
|
||||
const { body: { items }} = await coreApi.listNamespace();
|
||||
|
||||
const listNamespacesInjectable = getInjectable({
|
||||
id: "list-namespaces",
|
||||
instantiate: () => listNamespaces,
|
||||
return items
|
||||
.map(ns => ns.metadata?.name)
|
||||
.filter(isDefined);
|
||||
};
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
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 loggerInjectable from "../logger.injectable";
|
||||
import type { KubeApiResource } from "../rbac";
|
||||
import makeApiClientInjectable from "./make-api-client.injectable";
|
||||
|
||||
export type CanListResource = (resource: KubeApiResource) => boolean;
|
||||
|
||||
@ -26,9 +27,10 @@ const requestNamespaceListPermissionsForInjectable = getInjectable({
|
||||
id: "request-namespace-list-permissions-for",
|
||||
instantiate: (di): RequestNamespaceListPermissionsFor => {
|
||||
const logger = di.inject(loggerInjectable);
|
||||
const makeApiClient = di.inject(makeApiClientInjectable);
|
||||
|
||||
return (proxyConfig) => {
|
||||
const api = proxyConfig.makeApiClient(AuthorizationV1Api);
|
||||
const api = makeApiClient(proxyConfig, AuthorizationV1Api);
|
||||
|
||||
return async (namespace) => {
|
||||
try {
|
||||
|
||||
@ -5,6 +5,8 @@
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { Agent } from "https";
|
||||
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 lensProxyCertificateInjectable from "../certificate/lens-proxy-certificate.injectable";
|
||||
import nodeFetchModuleInjectable from "./fetch-module.injectable";
|
||||
@ -16,14 +18,21 @@ export type LensFetch = (pathnameAndQuery: string, init?: LensRequestInit) => Pr
|
||||
const lensFetchInjectable = getInjectable({
|
||||
id: "lens-fetch",
|
||||
instantiate: (di): LensFetch => {
|
||||
const { default: fetch } = di.inject(nodeFetchModuleInjectable);
|
||||
const { default: fetch, Headers } = di.inject(nodeFetchModuleInjectable);
|
||||
const lensProxyPort = di.inject(lensProxyPortInjectable);
|
||||
const lensProxyCertificate = di.inject(lensProxyCertificateInjectable);
|
||||
const authHeaderValue = di.inject(authHeaderValueInjectable);
|
||||
|
||||
return async (pathnameAndQuery, init = {}) => {
|
||||
return async (pathnameAndQuery, {
|
||||
headers: _headers,
|
||||
...init
|
||||
} = {}) => {
|
||||
const agent = new Agent({
|
||||
ca: lensProxyCertificate.get().cert,
|
||||
});
|
||||
const headers = new Headers(_headers);
|
||||
|
||||
headers.set(lensAuthHeaderName, authHeaderValue);
|
||||
|
||||
return fetch(`https://127.0.0.1:${lensProxyPort.get()}${pathnameAndQuery}`, {
|
||||
...init,
|
||||
|
||||
@ -3,6 +3,8 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
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 isDebuggingInjectable from "../vars/is-debugging.injectable";
|
||||
import isDevelopmentInjectable from "../vars/is-development.injectable";
|
||||
@ -17,6 +19,7 @@ const apiBaseInjectable = getInjectable({
|
||||
const isDevelopment = di.inject(isDevelopmentInjectable);
|
||||
const serverAddress = di.inject(apiBaseServerAddressInjectionToken);
|
||||
const hostHeaderValue = di.inject(apiBaseHostHeaderInjectionToken);
|
||||
const authHeaderValue = di.inject(authHeaderValueInjectable);
|
||||
|
||||
return createJsonApi({
|
||||
serverAddress,
|
||||
@ -25,6 +28,7 @@ const apiBaseInjectable = getInjectable({
|
||||
}, {
|
||||
headers: {
|
||||
"Host": hostHeaderValue,
|
||||
[lensAuthHeaderName]: authHeaderValue,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
@ -3,6 +3,8 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
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 isDebuggingInjectable from "../vars/is-debugging.injectable";
|
||||
import { apiBaseHostHeaderInjectionToken, apiBaseServerAddressInjectionToken } from "./api-base-configs";
|
||||
@ -16,6 +18,7 @@ const createKubeJsonApiForClusterInjectable = getInjectable({
|
||||
instantiate: (di): CreateKubeJsonApiForCluster => {
|
||||
const createKubeJsonApi = di.inject(createKubeJsonApiInjectable);
|
||||
const isDebugging = di.inject(isDebuggingInjectable);
|
||||
const authHeaderValue = di.inject(authHeaderValueInjectable);
|
||||
|
||||
return (clusterId) => createKubeJsonApi(
|
||||
{
|
||||
@ -26,6 +29,7 @@ const createKubeJsonApiForClusterInjectable = getInjectable({
|
||||
{
|
||||
headers: {
|
||||
"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";
|
||||
|
||||
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 { authHeaderChannel } from "../common/channel";
|
||||
import { authHeaderChannel } from "../common/vars";
|
||||
import authHeaderStateInjectable from "../common/header-state.injectable";
|
||||
|
||||
const authHeaderRequestListenerInjectable = getRequestChannelListenerInjectable({
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import requestFromChannelInjectable from "../../../renderer/utils/channel/request-from-channel.injectable";
|
||||
import { authHeaderChannel } from "../common/channel";
|
||||
import { authHeaderChannel } from "../common/vars";
|
||||
|
||||
const requestAuthHeaderValueInjectable = getInjectable({
|
||||
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 authorizationReviewInjectable from "../../common/cluster/authorization-review.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 type { ClusterContextHandler } from "../context-handler/context-handler";
|
||||
import { parse } from "url";
|
||||
@ -42,7 +42,7 @@ describe("create clusters", () => {
|
||||
di.override(broadcastMessageInjectable, () => async () => {});
|
||||
di.override(authorizationReviewInjectable, () => () => () => Promise.resolve(true));
|
||||
di.override(requestNamespaceListPermissionsForInjectable, () => () => async () => () => true);
|
||||
di.override(listNamespacesInjectable, () => () => () => Promise.resolve([ "default" ]));
|
||||
di.override(listNamespacesForInjectable, () => () => () => Promise.resolve([ "default" ]));
|
||||
di.override(createContextHandlerInjectable, () => (cluster) => ({
|
||||
restartServer: 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 { IComputedValue } from "mobx";
|
||||
import type { Logger } from "../../common/logger";
|
||||
import type { MakeApiClient } from "../../common/cluster/make-api-client.injectable";
|
||||
|
||||
export interface PrometheusDetails {
|
||||
prometheusPath: string;
|
||||
@ -31,6 +32,7 @@ interface PrometheusServicePreferences {
|
||||
export interface ContextHandlerDependencies {
|
||||
createKubeAuthProxy: CreateKubeAuthProxy;
|
||||
getPrometheusProviderByKind: GetPrometheusProviderByKind;
|
||||
makeApiClient: MakeApiClient;
|
||||
readonly authProxyCa: string;
|
||||
readonly prometheusProviders: IComputedValue<PrometheusProvider[]>;
|
||||
readonly logger: Logger;
|
||||
@ -110,7 +112,7 @@ export class ContextHandler implements ClusterContextHandler {
|
||||
|
||||
const providers = this.listPotentialProviders();
|
||||
const proxyConfig = await this.cluster.getProxyKubeconfig();
|
||||
const apiClient = proxyConfig.makeApiClient(CoreV1Api);
|
||||
const apiClient = this.dependencies.makeApiClient(proxyConfig, CoreV1Api);
|
||||
const potentialServices = await Promise.allSettled(
|
||||
providers.map(provider => provider.getPrometheusService(apiClient)),
|
||||
);
|
||||
|
||||
@ -12,6 +12,7 @@ import URLParse from "url-parse";
|
||||
import getPrometheusProviderByKindInjectable from "../prometheus/get-by-kind.injectable";
|
||||
import prometheusProvidersInjectable from "../prometheus/providers.injectable";
|
||||
import loggerInjectable from "../../common/logger.injectable";
|
||||
import makeApiClientInjectable from "../../common/cluster/make-api-client.injectable";
|
||||
|
||||
const createContextHandlerInjectable = getInjectable({
|
||||
id: "create-context-handler",
|
||||
@ -22,6 +23,7 @@ const createContextHandlerInjectable = getInjectable({
|
||||
getPrometheusProviderByKind: di.inject(getPrometheusProviderByKindInjectable),
|
||||
prometheusProviders: di.inject(prometheusProvidersInjectable),
|
||||
logger: di.inject(loggerInjectable),
|
||||
makeApiClient: di.inject(makeApiClientInjectable),
|
||||
};
|
||||
|
||||
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 { 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 listNamespacesForInjectable from "../../common/cluster/list-namespaces.injectable";
|
||||
import createListApiResourcesInjectable from "../cluster/request-api-resources.injectable";
|
||||
import loggerInjectable from "../../common/logger.injectable";
|
||||
import broadcastMessageInjectable from "../../common/ipc/broadcast-message.injectable";
|
||||
@ -34,7 +34,7 @@ const createClusterInjectable = getInjectable({
|
||||
createAuthorizationReview: di.inject(authorizationReviewInjectable),
|
||||
requestNamespaceListPermissionsFor: di.inject(requestNamespaceListPermissionsForInjectable),
|
||||
requestApiResources: di.inject(createListApiResourcesInjectable),
|
||||
createListNamespaces: di.inject(listNamespacesInjectable),
|
||||
createListNamespaces: di.inject(listNamespacesForInjectable),
|
||||
broadcastMessage: di.inject(broadcastMessageInjectable),
|
||||
loadConfigfromFile: di.inject(loadConfigfromFileInjectable),
|
||||
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 type { ServerResponse } from "http";
|
||||
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 { contentTypes } from "./router-content-types";
|
||||
import { writeServerResponseFor } from "./write-server-response";
|
||||
@ -16,10 +18,24 @@ const createHandlerForRouteInjectable = getInjectable({
|
||||
id: "create-handler-for-route",
|
||||
instantiate: (di): CreateHandlerForRoute => {
|
||||
const logger = di.inject(loggerInjectable);
|
||||
const authHeaderValue = di.inject(authHeaderValueInjectable);
|
||||
|
||||
return (route) => async (request, 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 {
|
||||
const result = await route.handler(request);
|
||||
|
||||
|
||||
@ -66,6 +66,7 @@ export interface RouteHandler<TResponse, Path extends string>{
|
||||
export interface BaseRoutePaths<Path extends string> {
|
||||
path: Path;
|
||||
method: "get" | "post" | "put" | "patch" | "delete";
|
||||
requireAuthentication?: boolean;
|
||||
}
|
||||
|
||||
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> {
|
||||
handler: RouteHandler<TResponse, Path>;
|
||||
readonly requireAuthentication: boolean;
|
||||
}
|
||||
|
||||
export interface BindHandler<Path extends string> {
|
||||
<TResponse>(handler: RouteHandler<TResponse, Path>): Route<TResponse, Path>;
|
||||
}
|
||||
|
||||
export function route<Path extends string>(parts: BaseRoutePaths<Path>): BindHandler<Path> {
|
||||
return (handler) => ({
|
||||
export const route = <Path extends string>({
|
||||
requireAuthentication = true,
|
||||
...parts
|
||||
}: BaseRoutePaths<Path>): BindHandler<Path> => (handler) => ({
|
||||
...parts,
|
||||
handler,
|
||||
requireAuthentication,
|
||||
});
|
||||
}
|
||||
|
||||
export interface ClusterRouteHandler<Response, Path extends string>{
|
||||
(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>;
|
||||
}
|
||||
|
||||
export function clusterRoute<Path extends string>(parts: BaseRoutePaths<Path>): BindClusterHandler<Path> {
|
||||
return (handler) => ({
|
||||
export const clusterRoute = <Path extends string>({
|
||||
requireAuthentication = true,
|
||||
...parts
|
||||
}: BaseRoutePaths<Path>): BindClusterHandler<Path> => (handler) => ({
|
||||
...parts,
|
||||
handler: ({ cluster, ...rest }) => {
|
||||
if (!cluster) {
|
||||
@ -112,8 +118,8 @@ export function clusterRoute<Path extends string>(parts: BaseRoutePaths<Path>):
|
||||
|
||||
return handler({ cluster, ...rest });
|
||||
},
|
||||
requireAuthentication,
|
||||
});
|
||||
}
|
||||
|
||||
export interface ValidatedClusterLensApiRequest<Path extends string, Payload> extends ClusterLensApiRequest<Path> {
|
||||
payload: Payload;
|
||||
|
||||
@ -17,6 +17,7 @@ const staticFileRouteInjectable = getRouteInjectable({
|
||||
return route({
|
||||
method: "get",
|
||||
path: `/{path*}`,
|
||||
requireAuthentication: false,
|
||||
})(
|
||||
isDevelopment
|
||||
? di.inject(devStaticFileRouteHandlerInjectable)
|
||||
|
||||
@ -10,15 +10,18 @@ import type { V1Secret } from "@kubernetes/client-node";
|
||||
import { CoreV1Api } from "@kubernetes/client-node";
|
||||
import { clusterRoute } from "../../router/route";
|
||||
import { dump } from "js-yaml";
|
||||
import makeApiClientInjectable from "../../../common/cluster/make-api-client.injectable";
|
||||
|
||||
const getServiceAccountRouteInjectable = getRouteInjectable({
|
||||
id: "get-service-account-route",
|
||||
|
||||
instantiate: () => clusterRoute({
|
||||
instantiate: (di) => clusterRoute({
|
||||
method: "get",
|
||||
path: `${apiPrefix}/kubeconfig/service-account/{namespace}/{account}`,
|
||||
})(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 secret = secretList.body.items.find(secret => {
|
||||
|
||||
@ -13,6 +13,7 @@ import { NodeApi } from "../../../common/k8s-api/endpoints";
|
||||
import { TerminalChannels } from "../../../common/terminal/channels";
|
||||
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 { MakeApiClient } from "../../../common/cluster/make-api-client.injectable";
|
||||
|
||||
export interface NodeShellSessionArgs extends ShellSessionArgs {
|
||||
nodeName: string;
|
||||
@ -21,6 +22,7 @@ export interface NodeShellSessionArgs extends ShellSessionArgs {
|
||||
export interface NodeShellSessionDependencies extends ShellSessionDependencies {
|
||||
createKubeJsonApiForCluster: CreateKubeJsonApiForCluster;
|
||||
createKubeApi: CreateKubeApi;
|
||||
makeApiClient: MakeApiClient;
|
||||
}
|
||||
|
||||
export class NodeShellSession extends ShellSession {
|
||||
@ -36,8 +38,8 @@ export class NodeShellSession extends ShellSession {
|
||||
}
|
||||
|
||||
public async open() {
|
||||
const kc = await this.cluster.getProxyKubeconfig();
|
||||
const coreApi = kc.makeApiClient(CoreV1Api);
|
||||
const config = await this.cluster.getProxyKubeconfig();
|
||||
const coreApi = this.dependencies.makeApiClient(config, CoreV1Api);
|
||||
const shell = await this.kubectl.getPath();
|
||||
|
||||
const cleanup = once(() => {
|
||||
@ -50,7 +52,7 @@ export class NodeShellSession extends ShellSession {
|
||||
|
||||
try {
|
||||
await this.createNodeShellPod(coreApi);
|
||||
await this.waitForRunningPod(kc);
|
||||
await this.waitForRunningPod(config);
|
||||
} catch (error) {
|
||||
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 statInjectable from "../../../common/fs/stat.injectable";
|
||||
import createKubeApiInjectable from "../../../common/k8s-api/create-kube-api.injectable";
|
||||
import makeApiClientInjectable from "../../../common/cluster/make-api-client.injectable";
|
||||
|
||||
export interface NodeShellSessionArgs {
|
||||
websocket: WebSocket;
|
||||
@ -47,6 +48,7 @@ const openNodeShellSessionInjectable = getInjectable({
|
||||
emitAppEvent: di.inject(emitAppEventInjectable),
|
||||
stat: di.inject(statInjectable),
|
||||
createKubeApi: di.inject(createKubeApiInjectable),
|
||||
makeApiClient: di.inject(makeApiClientInjectable),
|
||||
};
|
||||
|
||||
return async (args) => {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user