mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
release v4.2.0 (#2432)
Signed-off-by: Sebastian Malton<sebastian@malton.name>
This commit is contained in:
parent
34712dd51f
commit
86b0fa6479
9
SECURITY.md
Normal file
9
SECURITY.md
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# Security Policy
|
||||||
|
|
||||||
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
|
Team Lens encourages users who become aware of a security vulnerability in Lens to contact Team Lens with details of the vulnerability. Team Lens has established an email address that should be used for reporting a vulnerability. Please send descriptions of any vulnerabilities found to security@k8slens.dev. Please include details on the software and hardware configuration of your system so that we can duplicate the issue being reported.
|
||||||
|
|
||||||
|
Team Lens hopes that users encountering a new vulnerability will contact us privately as it is in the best interests of our users that Team Lens has an opportunity to investigate and confirm a suspected vulnerability before it becomes public knowledge.
|
||||||
|
|
||||||
|
In the case of vulnerabilities found in third-party software components used in Lens, please also notify Team Lens as described above.
|
||||||
@ -2,7 +2,7 @@
|
|||||||
"name": "kontena-lens",
|
"name": "kontena-lens",
|
||||||
"productName": "Lens",
|
"productName": "Lens",
|
||||||
"description": "Lens - The Kubernetes IDE",
|
"description": "Lens - The Kubernetes IDE",
|
||||||
"version": "4.2.0-rc.2",
|
"version": "4.2.0",
|
||||||
"main": "static/build/main.js",
|
"main": "static/build/main.js",
|
||||||
"copyright": "© 2021, Mirantis, Inc.",
|
"copyright": "© 2021, Mirantis, Inc.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
40
src/main/__test__/router.test.ts
Normal file
40
src/main/__test__/router.test.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import { Router } from "../router";
|
||||||
|
|
||||||
|
const staticRoot = __dirname;
|
||||||
|
|
||||||
|
class TestRouter extends Router {
|
||||||
|
protected resolveStaticRootPath() {
|
||||||
|
return staticRoot;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("Router", () => {
|
||||||
|
it("blocks path traversal attacks", async () => {
|
||||||
|
const router = new TestRouter();
|
||||||
|
const res = {
|
||||||
|
statusCode: 200,
|
||||||
|
end: jest.fn()
|
||||||
|
};
|
||||||
|
|
||||||
|
await router.handleStaticFile("../index.ts", res as any, {} as any, 0);
|
||||||
|
|
||||||
|
expect(res.statusCode).toEqual(404);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("serves files under static root", async () => {
|
||||||
|
const router = new TestRouter();
|
||||||
|
const res = {
|
||||||
|
statusCode: 200,
|
||||||
|
write: jest.fn(),
|
||||||
|
setHeader: jest.fn(),
|
||||||
|
end: jest.fn()
|
||||||
|
};
|
||||||
|
const req = {
|
||||||
|
url: ""
|
||||||
|
};
|
||||||
|
|
||||||
|
await router.handleStaticFile("router.test.ts", res as any, req as any, 0);
|
||||||
|
|
||||||
|
expect(res.statusCode).toEqual(200);
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -28,7 +28,7 @@ export class LensProxy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
listen(port = this.port): this {
|
listen(port = this.port): this {
|
||||||
this.proxyServer = this.buildCustomProxy().listen(port);
|
this.proxyServer = this.buildCustomProxy().listen(port, "127.0.0.1");
|
||||||
logger.info(`[LENS-PROXY]: Proxy server has started at ${this.origin}`);
|
logger.info(`[LENS-PROXY]: Proxy server has started at ${this.origin}`);
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
|
|||||||
@ -40,10 +40,16 @@ export interface LensApiRequest<P = any> {
|
|||||||
|
|
||||||
export class Router {
|
export class Router {
|
||||||
protected router: any;
|
protected router: any;
|
||||||
|
protected staticRootPath: string;
|
||||||
|
|
||||||
public constructor() {
|
public constructor() {
|
||||||
this.router = new Call.Router();
|
this.router = new Call.Router();
|
||||||
this.addRoutes();
|
this.addRoutes();
|
||||||
|
this.staticRootPath = this.resolveStaticRootPath();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected resolveStaticRootPath() {
|
||||||
|
return path.resolve(__static);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async route(cluster: Cluster, req: http.IncomingMessage, res: http.ServerResponse): Promise<boolean> {
|
public async route(cluster: Cluster, req: http.IncomingMessage, res: http.ServerResponse): Promise<boolean> {
|
||||||
@ -102,7 +108,15 @@ export class Router {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async handleStaticFile(filePath: string, res: http.ServerResponse, req: http.IncomingMessage, retryCount = 0) {
|
async handleStaticFile(filePath: string, res: http.ServerResponse, req: http.IncomingMessage, retryCount = 0) {
|
||||||
const asset = path.join(__static, filePath);
|
const asset = path.join(this.staticRootPath, filePath);
|
||||||
|
const normalizedFilePath = path.resolve(asset);
|
||||||
|
|
||||||
|
if (!normalizedFilePath.startsWith(this.staticRootPath)) {
|
||||||
|
res.statusCode = 404;
|
||||||
|
res.end();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const filename = path.basename(req.url);
|
const filename = path.basename(req.url);
|
||||||
|
|||||||
@ -32,6 +32,7 @@ const defaultProps: Partial<Props> = {
|
|||||||
useWorker: false,
|
useWorker: false,
|
||||||
onBlur: noop,
|
onBlur: noop,
|
||||||
onFocus: noop,
|
onFocus: noop,
|
||||||
|
cursorPos: { row: 0, column: 0 },
|
||||||
};
|
};
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
|
|||||||
@ -26,7 +26,6 @@ interface SidebarItemProps {
|
|||||||
* this item should be shown as active
|
* this item should be shown as active
|
||||||
*/
|
*/
|
||||||
isActive?: boolean;
|
isActive?: boolean;
|
||||||
subMenus?: React.ReactNode | React.ComponentType<SidebarItemProps>[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
@ -53,11 +52,9 @@ export class SidebarItem extends React.Component<SidebarItemProps> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@computed get isExpandable(): boolean {
|
@computed get isExpandable(): boolean {
|
||||||
if (this.compact) {
|
if (this.compact) return false; // not available in compact-mode currently
|
||||||
return false; // not available currently
|
|
||||||
}
|
|
||||||
|
|
||||||
return Boolean(this.props.subMenus || this.props.children);
|
return Boolean(this.props.children);
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleExpand = () => {
|
toggleExpand = () => {
|
||||||
@ -66,8 +63,22 @@ export class SidebarItem extends React.Component<SidebarItemProps> {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
renderSubMenu() {
|
||||||
|
const { isExpandable, expanded, isActive } = this;
|
||||||
|
|
||||||
|
if (!isExpandable || !expanded) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ul className={cssNames("sub-menu", { active: isActive })}>
|
||||||
|
{this.props.children}
|
||||||
|
</ul>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { isHidden, icon, text, children, url, className, subMenus } = this.props;
|
const { isHidden, icon, text, url, className } = this.props;
|
||||||
|
|
||||||
if (isHidden) return null;
|
if (isHidden) return null;
|
||||||
|
|
||||||
@ -90,12 +101,7 @@ export class SidebarItem extends React.Component<SidebarItemProps> {
|
|||||||
material={expanded ? "keyboard_arrow_up" : "keyboard_arrow_down"}
|
material={expanded ? "keyboard_arrow_up" : "keyboard_arrow_down"}
|
||||||
/>}
|
/>}
|
||||||
</NavLink>
|
</NavLink>
|
||||||
{isExpandable && expanded && (
|
{this.renderSubMenu()}
|
||||||
<ul className={cssNames("sub-menu", { active: isActive })}>
|
|
||||||
{subMenus}
|
|
||||||
{children}
|
|
||||||
</ul>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -51,25 +51,20 @@ export class Sidebar extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return Object.entries(crdStore.groups).map(([group, crds]) => {
|
return Object.entries(crdStore.groups).map(([group, crds]) => {
|
||||||
const crdGroupSubMenu: React.ReactNode = crds.map((crd) => {
|
const id = `crd-group:${group}`;
|
||||||
|
const crdGroupsPageUrl = crdURL({ query: { groups: group } });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<SidebarItem key={id} id={id} text={group} url={crdGroupsPageUrl}>
|
||||||
|
{crds.map((crd) => (
|
||||||
<SidebarItem
|
<SidebarItem
|
||||||
key={crd.getResourceApiBase()}
|
key={crd.getResourceApiBase()}
|
||||||
id={`crd-resource:${crd.getResourceApiBase()}`}
|
id={`crd-resource:${crd.getResourceApiBase()}`}
|
||||||
url={crd.getResourceUrl()}
|
url={crd.getResourceUrl()}
|
||||||
text={crd.getResourceTitle()}
|
text={crd.getResourceTitle()}
|
||||||
/>
|
/>
|
||||||
);
|
))}
|
||||||
});
|
</SidebarItem>
|
||||||
|
|
||||||
return (
|
|
||||||
<SidebarItem
|
|
||||||
key={group}
|
|
||||||
text={group}
|
|
||||||
id={`crd-group:${group}`}
|
|
||||||
url={crdURL({ query: { groups: group } })}
|
|
||||||
subMenus={crdGroupSubMenu}
|
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -147,8 +142,9 @@ export class Sidebar extends React.Component<Props> {
|
|||||||
isActive={isActive}
|
isActive={isActive}
|
||||||
text={menuItem.title}
|
text={menuItem.title}
|
||||||
icon={<menuItem.components.Icon/>}
|
icon={<menuItem.components.Icon/>}
|
||||||
subMenus={this.renderTreeFromTabRoutes(tabRoutes)}
|
>
|
||||||
/>
|
{this.renderTreeFromTabRoutes(tabRoutes)}
|
||||||
|
</SidebarItem>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -175,88 +171,94 @@ export class Sidebar extends React.Component<Props> {
|
|||||||
<div className={cssNames("sidebar-nav flex column box grow-fixed", { compact })}>
|
<div className={cssNames("sidebar-nav flex column box grow-fixed", { compact })}>
|
||||||
<SidebarItem
|
<SidebarItem
|
||||||
id="cluster"
|
id="cluster"
|
||||||
|
text="Cluster"
|
||||||
isActive={isActiveRoute(clusterRoute)}
|
isActive={isActiveRoute(clusterRoute)}
|
||||||
isHidden={!isAllowedResource("nodes")}
|
isHidden={!isAllowedResource("nodes")}
|
||||||
url={clusterURL()}
|
url={clusterURL()}
|
||||||
text="Cluster"
|
|
||||||
icon={<Icon svg="kube"/>}
|
icon={<Icon svg="kube"/>}
|
||||||
/>
|
/>
|
||||||
<SidebarItem
|
<SidebarItem
|
||||||
id="nodes"
|
id="nodes"
|
||||||
|
text="Nodes"
|
||||||
isActive={isActiveRoute(nodesRoute)}
|
isActive={isActiveRoute(nodesRoute)}
|
||||||
isHidden={!isAllowedResource("nodes")}
|
isHidden={!isAllowedResource("nodes")}
|
||||||
url={nodesURL()}
|
url={nodesURL()}
|
||||||
text="Nodes"
|
|
||||||
icon={<Icon svg="nodes"/>}
|
icon={<Icon svg="nodes"/>}
|
||||||
/>
|
/>
|
||||||
<SidebarItem
|
<SidebarItem
|
||||||
id="workloads"
|
id="workloads"
|
||||||
|
text="Workloads"
|
||||||
isActive={isActiveRoute(workloadsRoute)}
|
isActive={isActiveRoute(workloadsRoute)}
|
||||||
isHidden={Workloads.tabRoutes.length == 0}
|
isHidden={Workloads.tabRoutes.length == 0}
|
||||||
url={workloadsURL({ query })}
|
url={workloadsURL({ query })}
|
||||||
subMenus={this.renderTreeFromTabRoutes(Workloads.tabRoutes)}
|
|
||||||
text="Workloads"
|
|
||||||
icon={<Icon svg="workloads"/>}
|
icon={<Icon svg="workloads"/>}
|
||||||
/>
|
>
|
||||||
|
{this.renderTreeFromTabRoutes(Workloads.tabRoutes)}
|
||||||
|
</SidebarItem>
|
||||||
<SidebarItem
|
<SidebarItem
|
||||||
id="config"
|
id="config"
|
||||||
|
text="Configuration"
|
||||||
isActive={isActiveRoute(configRoute)}
|
isActive={isActiveRoute(configRoute)}
|
||||||
isHidden={Config.tabRoutes.length == 0}
|
isHidden={Config.tabRoutes.length == 0}
|
||||||
url={configURL({ query })}
|
url={configURL({ query })}
|
||||||
subMenus={this.renderTreeFromTabRoutes(Config.tabRoutes)}
|
|
||||||
text="Configuration"
|
|
||||||
icon={<Icon material="list"/>}
|
icon={<Icon material="list"/>}
|
||||||
/>
|
>
|
||||||
|
{this.renderTreeFromTabRoutes(Config.tabRoutes)}
|
||||||
|
</SidebarItem>
|
||||||
<SidebarItem
|
<SidebarItem
|
||||||
id="networks"
|
id="networks"
|
||||||
|
text="Network"
|
||||||
isActive={isActiveRoute(networkRoute)}
|
isActive={isActiveRoute(networkRoute)}
|
||||||
isHidden={Network.tabRoutes.length == 0}
|
isHidden={Network.tabRoutes.length == 0}
|
||||||
url={networkURL({ query })}
|
url={networkURL({ query })}
|
||||||
subMenus={this.renderTreeFromTabRoutes(Network.tabRoutes)}
|
|
||||||
text="Network"
|
|
||||||
icon={<Icon material="device_hub"/>}
|
icon={<Icon material="device_hub"/>}
|
||||||
/>
|
>
|
||||||
|
{this.renderTreeFromTabRoutes(Network.tabRoutes)}
|
||||||
|
</SidebarItem>
|
||||||
<SidebarItem
|
<SidebarItem
|
||||||
id="storage"
|
id="storage"
|
||||||
|
text="Storage"
|
||||||
isActive={isActiveRoute(storageRoute)}
|
isActive={isActiveRoute(storageRoute)}
|
||||||
isHidden={Storage.tabRoutes.length == 0}
|
isHidden={Storage.tabRoutes.length == 0}
|
||||||
url={storageURL({ query })}
|
url={storageURL({ query })}
|
||||||
subMenus={this.renderTreeFromTabRoutes(Storage.tabRoutes)}
|
|
||||||
icon={<Icon svg="storage"/>}
|
icon={<Icon svg="storage"/>}
|
||||||
text="Storage"
|
>
|
||||||
/>
|
{this.renderTreeFromTabRoutes(Storage.tabRoutes)}
|
||||||
|
</SidebarItem>
|
||||||
<SidebarItem
|
<SidebarItem
|
||||||
id="namespaces"
|
id="namespaces"
|
||||||
|
text="Namespaces"
|
||||||
isActive={isActiveRoute(namespacesRoute)}
|
isActive={isActiveRoute(namespacesRoute)}
|
||||||
isHidden={!isAllowedResource("namespaces")}
|
isHidden={!isAllowedResource("namespaces")}
|
||||||
url={namespacesURL()}
|
url={namespacesURL()}
|
||||||
icon={<Icon material="layers"/>}
|
icon={<Icon material="layers"/>}
|
||||||
text="Namespaces"
|
|
||||||
/>
|
/>
|
||||||
<SidebarItem
|
<SidebarItem
|
||||||
id="events"
|
id="events"
|
||||||
|
text="Events"
|
||||||
isActive={isActiveRoute(eventRoute)}
|
isActive={isActiveRoute(eventRoute)}
|
||||||
isHidden={!isAllowedResource("events")}
|
isHidden={!isAllowedResource("events")}
|
||||||
url={eventsURL({ query })}
|
url={eventsURL({ query })}
|
||||||
icon={<Icon material="access_time"/>}
|
icon={<Icon material="access_time"/>}
|
||||||
text="Events"
|
|
||||||
/>
|
/>
|
||||||
<SidebarItem
|
<SidebarItem
|
||||||
id="apps"
|
id="apps"
|
||||||
|
text="Apps" // helm charts
|
||||||
isActive={isActiveRoute(appsRoute)}
|
isActive={isActiveRoute(appsRoute)}
|
||||||
url={appsURL({ query })}
|
url={appsURL({ query })}
|
||||||
subMenus={this.renderTreeFromTabRoutes(Apps.tabRoutes)}
|
|
||||||
icon={<Icon material="apps"/>}
|
icon={<Icon material="apps"/>}
|
||||||
text="Apps"
|
>
|
||||||
/>
|
{this.renderTreeFromTabRoutes(Apps.tabRoutes)}
|
||||||
|
</SidebarItem>
|
||||||
<SidebarItem
|
<SidebarItem
|
||||||
id="users"
|
id="users"
|
||||||
|
text="Access Control"
|
||||||
isActive={isActiveRoute(usersManagementRoute)}
|
isActive={isActiveRoute(usersManagementRoute)}
|
||||||
url={usersManagementURL({ query })}
|
url={usersManagementURL({ query })}
|
||||||
subMenus={this.renderTreeFromTabRoutes(UserManagement.tabRoutes)}
|
|
||||||
icon={<Icon material="security"/>}
|
icon={<Icon material="security"/>}
|
||||||
text="Access Control"
|
>
|
||||||
/>
|
{this.renderTreeFromTabRoutes(UserManagement.tabRoutes)}
|
||||||
|
</SidebarItem>
|
||||||
<SidebarItem
|
<SidebarItem
|
||||||
id="custom-resources"
|
id="custom-resources"
|
||||||
text="Custom Resources"
|
text="Custom Resources"
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
Here you can find description of changes we've built into each release. While we try our best to make each upgrade automatic and as smooth as possible, there may be some cases where you might need to do something to ensure the application works smoothly. So please read through the release highlights!
|
Here you can find description of changes we've built into each release. While we try our best to make each upgrade automatic and as smooth as possible, there may be some cases where you might need to do something to ensure the application works smoothly. So please read through the release highlights!
|
||||||
|
|
||||||
## 4.2.0-rc.2 (current version)
|
## 4.2.0 (current version)
|
||||||
|
|
||||||
- Add lens:// protocol handling with a routing mechanism
|
- Add lens:// protocol handling with a routing mechanism
|
||||||
- Add common app routes to the protocol renderer router from the documentation
|
- Add common app routes to the protocol renderer router from the documentation
|
||||||
@ -33,6 +33,16 @@ Here you can find description of changes we've built into each release. While we
|
|||||||
- Fix: Closing workspace menu after clicking on iframe
|
- Fix: Closing workspace menu after clicking on iframe
|
||||||
- Fix: extension global pages are never able to be visible
|
- Fix: extension global pages are never able to be visible
|
||||||
- Fix: recreate proxy kubeconfig if it is deleted
|
- Fix: recreate proxy kubeconfig if it is deleted
|
||||||
|
- Fix: Proxy should listen only on loopback device
|
||||||
|
- Fix: Block global path traversal in router
|
||||||
|
- Fix: Set initial cursor position for the editor to beginning
|
||||||
|
- Fix: Highlight sidebar's active section
|
||||||
|
|
||||||
|
## 4.1.5
|
||||||
|
|
||||||
|
- Proxy should listen only on loopback device
|
||||||
|
- Fix extension command palette loading
|
||||||
|
- Fix Lens not clearing other KUBECONFIG env vars
|
||||||
|
|
||||||
## 4.1.4
|
## 4.1.4
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user