diff --git a/src/common/rbac.ts b/src/common/rbac.ts index 7d02e3be51..67b6647111 100644 --- a/src/common/rbac.ts +++ b/src/common/rbac.ts @@ -4,42 +4,54 @@ export type KubeResource = "namespaces" | "nodes" | "events" | "resourcequotas" | "services" | "limitranges" | "secrets" | "configmaps" | "ingresses" | "networkpolicies" | "persistentvolumeclaims" | "persistentvolumes" | "storageclasses" | "pods" | "daemonsets" | "deployments" | "statefulsets" | "replicasets" | "jobs" | "cronjobs" | - "endpoints" | "customresourcedefinitions" | "horizontalpodautoscalers" | "podsecuritypolicies" | "poddisruptionbudgets"; + "endpoints" | "customresourcedefinitions" | "horizontalpodautoscalers" | "podsecuritypolicies" | "poddisruptionbudgets" | + "roles" | "clusterroles" | "rolebindings" | "clusterrolebindings" | "serviceaccounts"; -export interface KubeApiResource { - kind: string; // resource type (e.g. "Namespace") +export interface KubeApiResource extends KubeApiResourceData { apiName: KubeResource; // valid api resource name (e.g. "namespaces") +} + +export interface KubeApiResourceData { + kind: string; // resource type (e.g. "Namespace") group?: string; // api-group } +export const apiResourceRecord: Record = { + "clusterroles": { kind: "ClusterRole", group: "rbac.authorization.k8s.io" }, + "clusterrolebindings": { kind: "ClusterRoleBinding", group: "rbac.authorization.k8s.io" }, + "configmaps": { kind: "ConfigMap" }, + "cronjobs": { kind: "CronJob", group: "batch" }, + "customresourcedefinitions": { kind: "CustomResourceDefinition", group: "apiextensions.k8s.io" }, + "daemonsets": { kind: "DaemonSet", group: "apps" }, + "deployments": { kind: "Deployment", group: "apps" }, + "endpoints": { kind: "Endpoint" }, + "events": { kind: "Event" }, + "horizontalpodautoscalers": { kind: "HorizontalPodAutoscaler" }, + "ingresses": { kind: "Ingress", group: "networking.k8s.io" }, + "jobs": { kind: "Job", group: "batch" }, + "namespaces": { kind: "Namespace" }, + "limitranges": { kind: "LimitRange" }, + "networkpolicies": { kind: "NetworkPolicy", group: "networking.k8s.io" }, + "nodes": { kind: "Node" }, + "persistentvolumes": { kind: "PersistentVolume" }, + "persistentvolumeclaims": { kind: "PersistentVolumeClaim" }, + "pods": { kind: "Pod" }, + "poddisruptionbudgets": { kind: "PodDisruptionBudget", group: "policy" }, + "podsecuritypolicies": { kind: "PodSecurityPolicy" }, + "resourcequotas": { kind: "ResourceQuota" }, + "replicasets": { kind: "ReplicaSet", group: "apps" }, + "roles": { kind: "Role", group: "rbac.authorization.k8s.io" }, + "rolebindings": { kind: "RoleBinding", group: "rbac.authorization.k8s.io" }, + "secrets": { kind: "Secret" }, + "serviceaccounts": { kind: "ServiceAccount", group: "core" }, + "services": { kind: "Service" }, + "statefulsets": { kind: "StatefulSet", group: "apps" }, + "storageclasses": { kind: "StorageClass", group: "storage.k8s.io" }, +}; + // TODO: auto-populate all resources dynamically (see: kubectl api-resources -o=wide -v=7) -export const apiResources: KubeApiResource[] = [ - { 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", group: "policy" }, - { 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 const apiResources: KubeApiResource[] = Object.entries(apiResourceRecord) + .map(([apiName, data]) => ({ apiName: apiName as KubeResource, ...data })); export function isAllowedResource(resources: KubeResource | KubeResource[]) { if (!Array.isArray(resources)) { diff --git a/src/main/cluster.ts b/src/main/cluster.ts index 1b02e79a02..725b9330c0 100644 --- a/src/main/cluster.ts +++ b/src/main/cluster.ts @@ -7,7 +7,7 @@ import { AuthorizationV1Api, CoreV1Api, HttpError, KubeConfig, V1ResourceAttribu import { Kubectl } from "./kubectl"; import { KubeconfigManager } from "./kubeconfig-manager"; import { loadConfig, validateKubeConfig } from "../common/kube-helpers"; -import { apiResources, KubeApiResource } from "../common/rbac"; +import { apiResourceRecord, apiResources, KubeApiResource, KubeResource } from "../common/rbac"; import logger from "./logger"; import { VersionDetector } from "./cluster-detectors/version-detector"; import { detectorRegistry } from "./cluster-detectors/detector-registry"; @@ -678,7 +678,11 @@ export class Cluster implements ClusterModel, ClusterState { } isAllowedResource(kind: string): boolean { - const apiResource = apiResources.find(resource => resource.kind === kind || resource.apiName === kind); + if ((kind as KubeResource) in apiResourceRecord) { + return this.allowedResources.includes(kind); + } + + const apiResource = apiResources.find(resource => resource.kind === kind); if (apiResource) { return this.allowedResources.includes(apiResource.apiName); diff --git a/src/renderer/components/+user-management/user-management.tsx b/src/renderer/components/+user-management/user-management.tsx index 480808d6a1..4f0f6a706b 100644 --- a/src/renderer/components/+user-management/user-management.tsx +++ b/src/renderer/components/+user-management/user-management.tsx @@ -13,29 +13,37 @@ import { isAllowedResource } from "../../../common/rbac"; @observer export class UserManagement extends React.Component { static get tabRoutes() { - const tabRoutes: TabLayoutRoute[] = []; const query = namespaceUrlParam.toObjectParam(); + const tabRoutes: TabLayoutRoute[] = []; - tabRoutes.push( - { + if (isAllowedResource("serviceaccounts")) { + tabRoutes.push({ title: "Service Accounts", component: ServiceAccounts, url: serviceAccountsURL({ query }), routePath: serviceAccountsRoute.path.toString(), - }, - { + }); + } + + if (isAllowedResource("rolebindings") || isAllowedResource("clusterrolebindings")) { + // TODO: seperate out these two pages + tabRoutes.push({ title: "Role Bindings", component: RoleBindings, url: roleBindingsURL({ query }), routePath: roleBindingsRoute.path.toString(), - }, - { + }); + } + + if (isAllowedResource("roles") || isAllowedResource("clusterroles")) { + // TODO: seperate out these two pages + tabRoutes.push({ title: "Roles", component: Roles, url: rolesURL({ query }), routePath: rolesRoute.path.toString(), - }, - ); + }); + } if (isAllowedResource("podsecuritypolicies")) { tabRoutes.push({ diff --git a/src/renderer/components/layout/sidebar.tsx b/src/renderer/components/layout/sidebar.tsx index be8753642f..e18c946b27 100644 --- a/src/renderer/components/layout/sidebar.tsx +++ b/src/renderer/components/layout/sidebar.tsx @@ -257,6 +257,7 @@ export class Sidebar extends React.Component { id="users" text="Access Control" isActive={isActiveRoute(usersManagementRoute)} + isHidden={UserManagement.tabRoutes.length === 0} url={usersManagementURL({ query })} icon={} > diff --git a/src/renderer/utils/rbac.ts b/src/renderer/utils/rbac.ts index 36737ccf3a..c716e4b27c 100644 --- a/src/renderer/utils/rbac.ts +++ b/src/renderer/utils/rbac.ts @@ -26,4 +26,9 @@ export const ResourceNames: Record = { "podsecuritypolicies": "Pod Security Policies", "poddisruptionbudgets": "Pod Disruption Budgets", "limitranges": "Limit Ranges", + "roles": "Roles", + "rolebindings": "Role Bindings", + "clusterrolebindings": "Cluster Role Bindings", + "clusterroles": "Cluster Roles", + "serviceaccounts": "Service Accounts" };