1
0
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:
Sebastian Malton 2021-04-01 11:47:22 -04:00 committed by GitHub
parent 34712dd51f
commit 86b0fa6479
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 139 additions and 57 deletions

9
SECURITY.md Normal file
View 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.

View File

@ -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",

View 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);
});
});

View File

@ -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;

View File

@ -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);

View File

@ -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

View File

@ -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>
); );
} }

View File

@ -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"

View File

@ -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