diff --git a/src/common/rbac.ts b/src/common/rbac.ts index afa5255712..de242b114a 100644 --- a/src/common/rbac.ts +++ b/src/common/rbac.ts @@ -7,50 +7,40 @@ export type KubeResource = "endpoints" | "customresourcedefinitions" | "horizontalpodautoscalers" | "podsecuritypolicies" | "poddisruptionbudgets"; export interface KubeApiResource { - kind: string; // resource type - resource: KubeResource; // valid resource name + kind: string; // resource type (e.g. "Namespace") + apiName: KubeResource; // valid api resource name (e.g. "namespaces") group?: string; // api-group } // TODO: auto-populate all resources dynamically (see: kubectl api-resources -o=wide -v=7) export const apiResources: KubeApiResource[] = [ - { kind: "ConfigMap", resource: "configmaps" }, - { kind: "CronJob", resource: "cronjobs", group: "batch" }, - { kind: "CustomResourceDefinition", resource: "customresourcedefinitions", group: "apiextensions.k8s.io" }, - { kind: "DaemonSet", resource: "daemonsets", group: "apps" }, - { kind: "Deployment", resource: "deployments", group: "apps" }, - { kind: "Endpoint", resource: "endpoints" }, - { kind: "Event", resource: "events" }, - { kind: "HorizontalPodAutoscaler", resource: "horizontalpodautoscalers" }, - { kind: "Ingress", resource: "ingresses", group: "networking.k8s.io" }, - { kind: "Job", resource: "jobs", group: "batch" }, - { kind: "Namespace", resource: "namespaces" }, - { kind: "LimitRange", resource: "limitranges" }, - { kind: "NetworkPolicy", resource: "networkpolicies", group: "networking.k8s.io" }, - { kind: "Node", resource: "nodes" }, - { kind: "PersistentVolume", resource: "persistentvolumes" }, - { kind: "PersistentVolumeClaim", resource: "persistentvolumeclaims" }, - { kind: "Pod", resource: "pods" }, - { kind: "PodDisruptionBudget", resource: "poddisruptionbudgets" }, - { kind: "PodSecurityPolicy", resource: "podsecuritypolicies" }, - { kind: "ResourceQuota", resource: "resourcequotas" }, - { kind: "ReplicaSet", resource: "replicasets", group: "apps" }, - { kind: "Secret", resource: "secrets" }, - { kind: "Service", resource: "services" }, - { kind: "StatefulSet", resource: "statefulsets", group: "apps" }, - { kind: "StorageClass", resource: "storageclasses", group: "storage.k8s.io" }, + { kind: "ConfigMap", apiName: "configmaps" }, + { kind: "CronJob", apiName: "cronjobs", group: "batch" }, + { kind: "CustomResourceDefinition", apiName: "customresourcedefinitions", group: "apiextensions.k8s.io" }, + { kind: "DaemonSet", apiName: "daemonsets", group: "apps" }, + { kind: "Deployment", apiName: "deployments", group: "apps" }, + { kind: "Endpoint", apiName: "endpoints" }, + { kind: "Event", apiName: "events" }, + { kind: "HorizontalPodAutoscaler", apiName: "horizontalpodautoscalers" }, + { kind: "Ingress", apiName: "ingresses", group: "networking.k8s.io" }, + { kind: "Job", apiName: "jobs", group: "batch" }, + { kind: "Namespace", apiName: "namespaces" }, + { kind: "LimitRange", apiName: "limitranges" }, + { kind: "NetworkPolicy", apiName: "networkpolicies", group: "networking.k8s.io" }, + { kind: "Node", apiName: "nodes" }, + { kind: "PersistentVolume", apiName: "persistentvolumes" }, + { kind: "PersistentVolumeClaim", apiName: "persistentvolumeclaims" }, + { kind: "Pod", apiName: "pods" }, + { kind: "PodDisruptionBudget", apiName: "poddisruptionbudgets" }, + { kind: "PodSecurityPolicy", apiName: "podsecuritypolicies" }, + { kind: "ResourceQuota", apiName: "resourcequotas" }, + { kind: "ReplicaSet", apiName: "replicasets", group: "apps" }, + { kind: "Secret", apiName: "secrets" }, + { kind: "Service", apiName: "services" }, + { kind: "StatefulSet", apiName: "statefulsets", group: "apps" }, + { kind: "StorageClass", apiName: "storageclasses", group: "storage.k8s.io" }, ]; -export function isAllowedResourceType(kind: string): boolean { - const apiResource = apiResources.find(resource => resource.kind === kind); - - if (apiResource) { - return getHostedCluster().allowedResources.includes(apiResource.resource); - } - - return true; // allowed by default for other resources -} - export function isAllowedResource(resources: KubeResource | KubeResource[]) { if (!Array.isArray(resources)) { resources = [resources]; diff --git a/src/main/cluster.ts b/src/main/cluster.ts index 03e97d47dd..65e76296c3 100644 --- a/src/main/cluster.ts +++ b/src/main/cluster.ts @@ -216,7 +216,7 @@ export class Cluster implements ClusterModel, ClusterState { * @computed */ @computed get name() { - return this.preferences.clusterName || this.contextName; + return this.preferences.clusterName || this.contextName; } /** @@ -271,7 +271,8 @@ export class Cluster implements ClusterModel, ClusterState { * @param port port where internal auth proxy is listening * @internal */ - @action async init(port: number) { + @action + async init(port: number) { try { this.contextHandler = new ContextHandler(this); this.kubeconfigManager = await KubeconfigManager.create(this, this.contextHandler, port); @@ -323,7 +324,8 @@ export class Cluster implements ClusterModel, ClusterState { * @param force force activation * @internal */ - @action async activate(force = false) { + @action + async activate(force = false) { if (this.activated && !force) { return this.pushState(); } @@ -362,7 +364,8 @@ export class Cluster implements ClusterModel, ClusterState { /** * @internal */ - @action async reconnect() { + @action + async reconnect() { logger.info(`[CLUSTER]: reconnect`, this.getMeta()); this.contextHandler?.stopServer(); await this.contextHandler?.ensureServer(); @@ -389,7 +392,8 @@ export class Cluster implements ClusterModel, ClusterState { * @internal * @param opts refresh options */ - @action async refresh(opts: ClusterRefreshOptions = {}) { + @action + async refresh(opts: ClusterRefreshOptions = {}) { logger.info(`[CLUSTER]: refresh`, this.getMeta()); await this.whenInitialized; await this.refreshConnectionStatus(); @@ -409,7 +413,8 @@ export class Cluster implements ClusterModel, ClusterState { /** * @internal */ - @action async refreshMetadata() { + @action + async refreshMetadata() { logger.info(`[CLUSTER]: refreshMetadata`, this.getMeta()); const metadata = await detectorRegistry.detectForCluster(this); const existingMetadata = this.metadata; @@ -420,7 +425,8 @@ export class Cluster implements ClusterModel, ClusterState { /** * @internal */ - @action async refreshConnectionStatus() { + @action + async refreshConnectionStatus() { const connectionStatus = await this.getConnectionStatus(); this.online = connectionStatus > ClusterStatus.Offline; @@ -430,7 +436,8 @@ export class Cluster implements ClusterModel, ClusterState { /** * @internal */ - @action async refreshAllowedResources() { + @action + async refreshAllowedResources() { this.allowedNamespaces = await this.getAllowedNamespaces(); this.allowedResources = await this.getAllowedResources(); } @@ -657,7 +664,7 @@ export class Cluster implements ClusterModel, ClusterState { for (const namespace of this.allowedNamespaces.slice(0, 10)) { if (!this.resourceAccessStatuses.get(apiResource)) { const result = await this.canI({ - resource: apiResource.resource, + resource: apiResource.apiName, group: apiResource.group, verb: "list", namespace @@ -672,9 +679,19 @@ export class Cluster implements ClusterModel, ClusterState { return apiResources .filter((resource) => this.resourceAccessStatuses.get(resource)) - .map(apiResource => apiResource.resource); + .map(apiResource => apiResource.apiName); } catch (error) { return []; } } + + isAllowedResource(kind: string): boolean { + const apiResource = apiResources.find(resource => resource.kind === kind || resource.apiName === kind); + + if (apiResource) { + return this.allowedResources.includes(apiResource.apiName); + } + + return true; // allowed by default for other resources + } } diff --git a/src/renderer/kube-object.store.ts b/src/renderer/kube-object.store.ts index d84517a26d..956f5aa5f6 100644 --- a/src/renderer/kube-object.store.ts +++ b/src/renderer/kube-object.store.ts @@ -1,3 +1,4 @@ +import type { Cluster } from "../main/cluster"; import { action, observable, reaction } from "mobx"; import { autobind } from "./utils"; import { KubeObject } from "./api/kube-object"; @@ -6,7 +7,6 @@ import { ItemStore } from "./item.store"; import { apiManager } from "./api/api-manager"; import { IKubeApiQueryParams, KubeApi } from "./api/kube-api"; import { KubeJsonApiData } from "./api/kube-json-api"; -import { isAllowedResourceType } from "../common/rbac"; export interface KubeObjectStoreLoadingParams { namespaces: string[]; @@ -76,8 +76,16 @@ export abstract class KubeObjectStore extends ItemSt } } + protected async resolveCluster(): Promise { + const { getHostedCluster } = await import("../common/cluster-store"); + + return getHostedCluster(); + } + protected async loadItems({ namespaces, api }: KubeObjectStoreLoadingParams): Promise { - if (isAllowedResourceType(api.kind)) { + const cluster = await this.resolveCluster(); + + if (cluster.isAllowedResource(api.kind)) { if (api.isNamespaced) { return Promise .all(namespaces.map(namespace => api.list({ namespace })))