From f88555a1d793cfb0bf79f2bd81ef512c2b80451e Mon Sep 17 00:00:00 2001 From: Jari Kolehmainen Date: Mon, 6 Mar 2023 17:00:59 +0200 Subject: [PATCH] Port #7282 to master (#7289) * fix requestNamespaceListPermissionsForInjectable * more tests * fix test descriptions * fake -> stub * fake-namespace -> irrelevant-namespace --------- Signed-off-by: Jari Kolehmainen --- ...t-namespace-list-permissions.injectable.ts | 14 +- ...request-namespace-list-permissions.test.ts | 336 ++++++++++++++++++ 2 files changed, 340 insertions(+), 10 deletions(-) create mode 100644 packages/core/src/common/cluster/request-namespace-list-permissions.test.ts diff --git a/packages/core/src/common/cluster/request-namespace-list-permissions.injectable.ts b/packages/core/src/common/cluster/request-namespace-list-permissions.injectable.ts index 62d2477e42..4b1aadeee6 100644 --- a/packages/core/src/common/cluster/request-namespace-list-permissions.injectable.ts +++ b/packages/core/src/common/cluster/request-namespace-list-permissions.injectable.ts @@ -47,9 +47,9 @@ const requestNamespaceListPermissionsForInjectable = getInjectable({ const { resourceRules } = status; return (resource) => { - const resourceRule = resourceRules.find(({ - apiGroups = [], - resources = [], + const rules = resourceRules.filter(({ + apiGroups = ["*"], + resources = ["*"], }) => { const isAboutRelevantApiGroup = apiGroups.includes("*") || apiGroups.includes(resource.group); const isAboutResource = resources.includes("*") || resources.includes(resource.apiName); @@ -57,13 +57,7 @@ const requestNamespaceListPermissionsForInjectable = getInjectable({ return isAboutRelevantApiGroup && isAboutResource; }); - if (!resourceRule) { - return false; - } - - const { verbs } = resourceRule; - - return verbs.includes("*") || verbs.includes("list"); + return rules.some(({ verbs }) => verbs.includes("*") || verbs.includes("list")); }; } catch (error) { logger.error(`[AUTHORIZATION-NAMESPACE-REVIEW]: failed to create subject rules review`, { namespace, error }); diff --git a/packages/core/src/common/cluster/request-namespace-list-permissions.test.ts b/packages/core/src/common/cluster/request-namespace-list-permissions.test.ts new file mode 100644 index 0000000000..9734683404 --- /dev/null +++ b/packages/core/src/common/cluster/request-namespace-list-permissions.test.ts @@ -0,0 +1,336 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import type { V1SubjectRulesReviewStatus } from "@kubernetes/client-node"; +import type { DiContainer } from "@ogre-tools/injectable"; +import { getDiForUnitTesting } from "../../main/getDiForUnitTesting"; +import type { RequestNamespaceListPermissionsFor } from "./request-namespace-list-permissions.injectable"; +import requestNamespaceListPermissionsForInjectable from "./request-namespace-list-permissions.injectable"; + +const createStubProxyConfig = (statusResponse: Promise<{ body: { status: V1SubjectRulesReviewStatus }}>) => ({ + makeApiClient: () => ({ + createSelfSubjectRulesReview: (): Promise<{ body: { status: V1SubjectRulesReviewStatus }}> => statusResponse, + }), +}); + +describe("requestNamespaceListPermissions", () => { + let di: DiContainer; + let requestNamespaceListPermissions: RequestNamespaceListPermissionsFor; + + beforeEach(() => { + di = getDiForUnitTesting({ doGeneralOverrides: true }); + requestNamespaceListPermissions = di.inject(requestNamespaceListPermissionsForInjectable); + }); + + describe("when api returns incomplete data", () => { + it("returns truthy function", async () => { + const requestPermissions = requestNamespaceListPermissions(createStubProxyConfig( + new Promise((resolve) => resolve({ + body: { + status: { + incomplete: true, + resourceRules: [], + nonResourceRules: [], + }, + }, + })), + ) as any); + + const permissionCheck = await requestPermissions("irrelevant-namespace"); + + expect(permissionCheck({ + apiName: "pods", + group: "", + kind: "Pod", + namespaced: true, + })).toBeTruthy(); + }); + }); + + describe("when api rejects", () => { + it("returns truthy function", async () => { + const requestPermissions = requestNamespaceListPermissions(createStubProxyConfig( + new Promise((resolve, reject) => reject("unknown error")), + ) as any); + + const permissionCheck = await requestPermissions("irrelevant-namespace"); + + expect(permissionCheck({ + apiName: "pods", + group: "", + kind: "Pod", + namespaced: true, + })).toBeTruthy(); + }); + }); + + describe("when first resourceRule has all permissions for everything", () => { + it("return truthy function", async () => { + const requestPermissions = requestNamespaceListPermissions(createStubProxyConfig( + new Promise((resolve) => resolve({ + body: { + status: { + incomplete: false, + resourceRules: [ + { + apiGroups: ["*"], + verbs: ["*"], + }, + { + apiGroups: ["*"], + verbs: ["get"], + }, + ], + nonResourceRules: [], + }, + }, + })), + ) as any); + + const permissionCheck = await requestPermissions("irrelevant-namespace"); + + expect(permissionCheck({ + apiName: "pods", + group: "", + kind: "Pod", + namespaced: true, + })).toBeTruthy(); + }); + }); + + describe("when first resourceRule has list permissions for everything", () => { + it("return truthy function", async () => { + const requestPermissions = requestNamespaceListPermissions(createStubProxyConfig( + new Promise((resolve) => resolve({ + body: { + status: { + incomplete: false, + resourceRules: [ + { + apiGroups: ["*"], + verbs: ["list"], + }, + { + apiGroups: ["*"], + verbs: ["get"], + }, + ], + nonResourceRules: [], + }, + }, + })), + ) as any); + + const permissionCheck = await requestPermissions("irrelevant-namespace"); + + expect(permissionCheck({ + apiName: "pods", + group: "", + kind: "Pod", + namespaced: true, + })).toBeTruthy(); + }); + }); + + describe("when first resourceRule has list permissions for asked resource", () => { + it("return truthy function", async () => { + const requestPermissions = requestNamespaceListPermissions(createStubProxyConfig( + new Promise((resolve) => resolve({ + body: { + status: { + incomplete: false, + resourceRules: [ + { + apiGroups: [""], + resources: ["pods"], + verbs: ["list"], + }, + { + apiGroups: ["*"], + verbs: ["get"], + }, + ], + nonResourceRules: [], + }, + }, + })), + ) as any); + + const permissionCheck = await requestPermissions("irrelevant-namespace"); + + expect(permissionCheck({ + apiName: "pods", + group: "", + kind: "Pod", + namespaced: true, + })).toBeTruthy(); + }); + }); + + describe("when last resourceRule has all permissions for everything", () => { + it("return truthy function", async () => { + const requestPermissions = requestNamespaceListPermissions(createStubProxyConfig( + new Promise((resolve) => resolve({ + body: { + status: { + incomplete: false, + resourceRules: [ + { + apiGroups: ["*"], + verbs: ["get"], + }, + { + apiGroups: ["*"], + verbs: ["*"], + }, + ], + nonResourceRules: [], + }, + }, + })), + ) as any); + + const permissionCheck = await requestPermissions("irrelevant-namespace"); + + expect(permissionCheck({ + apiName: "pods", + group: "", + kind: "Pod", + namespaced: true, + })).toBeTruthy(); + }); + }); + + describe("when last resourceRule has list permissions for everything", () => { + it("return truthy function", async () => { + const requestPermissions = requestNamespaceListPermissions(createStubProxyConfig( + new Promise((resolve) => resolve({ + body: { + status: { + incomplete: false, + resourceRules: [ + { + apiGroups: ["*"], + verbs: ["get"], + }, + { + apiGroups: ["*"], + verbs: ["list"], + }, + ], + nonResourceRules: [], + }, + }, + })), + ) as any); + + const permissionCheck = await requestPermissions("irrelevant-namespace"); + + expect(permissionCheck({ + apiName: "pods", + group: "", + kind: "Pod", + namespaced: true, + })).toBeTruthy(); + }); + }); + + describe("when last resourceRule has list permissions for asked resource", () => { + it("return truthy function", async () => { + const requestPermissions = requestNamespaceListPermissions(createStubProxyConfig( + new Promise((resolve) => resolve({ + body: { + status: { + incomplete: false, + resourceRules: [ + { + apiGroups: ["*"], + verbs: ["get"], + }, + { + apiGroups: [""], + resources: ["pods"], + verbs: ["list"], + }, + ], + nonResourceRules: [], + }, + }, + })), + ) as any); + + const permissionCheck = await requestPermissions("irrelevant-namespace"); + + expect(permissionCheck({ + apiName: "pods", + group: "", + kind: "Pod", + namespaced: true, + })).toBeTruthy(); + }); + }); + + describe("when resourceRules has matching resource without list verb", () => { + it("return falsy function", async () => { + const requestPermissions = requestNamespaceListPermissions(createStubProxyConfig( + new Promise((resolve) => resolve({ + body: { + status: { + incomplete: false, + resourceRules: [ + { + apiGroups: [""], + resources: ["pods"], + verbs: ["get"], + }, + ], + nonResourceRules: [], + }, + }, + })), + ) as any); + + const permissionCheck = await requestPermissions("irrelevant-namespace"); + + expect(permissionCheck({ + apiName: "pods", + group: "", + kind: "Pod", + namespaced: true, + })).toBeFalsy(); + }); + }); + + describe("when resourceRules has no matching resource with list verb", () => { + it("return falsy function", async () => { + const requestPermissions = requestNamespaceListPermissions(createStubProxyConfig( + new Promise((resolve) => resolve({ + body: { + status: { + incomplete: false, + resourceRules: [ + { + apiGroups: [""], + resources: ["services"], + verbs: ["list"], + }, + ], + nonResourceRules: [], + }, + }, + })), + ) as any); + + const permissionCheck = await requestPermissions("irrelevant-namespace"); + + expect(permissionCheck({ + apiName: "pods", + group: "", + kind: "Pod", + namespaced: true, + })).toBeFalsy(); + }); + }); +});