diff --git a/backend/src/routes/api/status/adminAllowedUsers.ts b/backend/src/routes/api/status/adminAllowedUsers.ts index 89663678f7..da1553fcec 100644 --- a/backend/src/routes/api/status/adminAllowedUsers.ts +++ b/backend/src/routes/api/status/adminAllowedUsers.ts @@ -4,6 +4,7 @@ import { getUserInfo } from '../../../utils/userUtils'; import { getAdminUserList, getAllowedUserList, + getClusterAdminUserList, isUserAdmin, KUBE_SAFE_PREFIX, } from '../../../utils/adminUtils'; @@ -77,22 +78,34 @@ export const getAllowedUsers = async ( return []; } + const activityMap = await getUserActivityFromNotebook(fastify, namespace); + + const withNotebookUsers = Object.keys(activityMap); const adminUsers = await getAdminUserList(fastify); const allowedUsers = await getAllowedUserList(fastify); - const activityMap = await getUserActivityFromNotebook(fastify, namespace); + // get cluster admins that have a notebook + const clusterAdminUsers = (await getClusterAdminUserList(fastify)).filter((user) => + withNotebookUsers.includes(user), + ); const usersWithNotebooksMap: AllowedUserMap = convertUserListToMap( - Object.keys(activityMap), + withNotebookUsers, 'User', activityMap, ); const allowedUsersMap: AllowedUserMap = convertUserListToMap(allowedUsers, 'User', activityMap); const adminUsersMap: AllowedUserMap = convertUserListToMap(adminUsers, 'Admin', activityMap); + const clusterAdminUsersMap: AllowedUserMap = convertUserListToMap( + clusterAdminUsers, + 'Admin', + activityMap, + ); const returnUsers: AllowedUserMap = { ...usersWithNotebooksMap, ...allowedUsersMap, ...adminUsersMap, + ...clusterAdminUsersMap, }; return Object.values(returnUsers); }; diff --git a/backend/src/types.ts b/backend/src/types.ts index c29a17d571..4024b82a55 100644 --- a/backend/src/types.ts +++ b/backend/src/types.ts @@ -1255,3 +1255,8 @@ export type NIMAccountKind = K8sResourceCommon & { conditions?: K8sCondition[]; }; }; + +export type ResourceAccessReviewResponse = { + groups?: string[]; + users?: string[]; +}; diff --git a/backend/src/utils/adminUtils.ts b/backend/src/utils/adminUtils.ts index 517ce0ddaf..e42a0c7848 100644 --- a/backend/src/utils/adminUtils.ts +++ b/backend/src/utils/adminUtils.ts @@ -3,9 +3,10 @@ import { V1ClusterRoleBinding, V1ClusterRoleBindingList, } from '@kubernetes/client-node'; -import { KubeFastifyInstance } from '../types'; +import { KubeFastifyInstance, ResourceAccessReviewResponse } from '../types'; import { getAdminGroups, getAllGroupsByUser, getAllowedGroups, getGroup } from './groupsUtils'; import { flatten, uniq } from 'lodash'; +import { getNamespaces } from '../utils/notebookUtils'; const SYSTEM_AUTHENTICATED = 'system:authenticated'; /** Usernames with invalid characters can start with `b64:` to keep their unwanted characters */ @@ -14,10 +15,11 @@ export const KUBE_SAFE_PREFIX = 'b64:'; const getGroupUserList = async ( fastify: KubeFastifyInstance, groupListNames: string[], + additionalUsers: string[] = [], ): Promise => { const customObjectApi = fastify.kube.customObjectsApi; return Promise.all(groupListNames.map((group) => getGroup(customObjectApi, group))).then( - (usersPerGroup: string[][]) => uniq(flatten(usersPerGroup)), + (usersPerGroup: string[][]) => uniq([...flatten(usersPerGroup), ...additionalUsers]), ); }; @@ -26,10 +28,38 @@ export const getAdminUserList = async (fastify: KubeFastifyInstance): Promise groupName && !groupName.startsWith('system:')); // Handle edge-cases and ignore k8s defaults - adminGroupsList.push('cluster-admins'); + return getGroupUserList(fastify, adminGroupsList); }; +export const getClusterAdminUserList = async (fastify: KubeFastifyInstance): Promise => { + // fetch all the users and groups who have cluster-admin role and put them into the admin user list + const { notebookNamespace } = getNamespaces(fastify); + const clusterAdminUsersAndGroups = await fastify.kube.customObjectsApi + // This is not actually fetching all the groups who have admin access to the notebook resources + // But only the cluster admins + // The "*" in the verb field is more like a placeholder + .createClusterCustomObject('authorization.openshift.io', 'v1', 'resourceaccessreviews', { + resource: 'notebooks', + resourceAPIGroup: 'kubeflow.org', + resourceAPIVersion: 'v1', + verb: '*', + namespace: notebookNamespace, + }) + .then((rar) => rar.body as ResourceAccessReviewResponse) + .catch((e) => { + fastify.log.error(`Failure to fetch cluster admin users and groups: ${e.response.body}`); + return { users: [], groups: [] }; + }); + const clusterAdminUsers = clusterAdminUsersAndGroups.users || []; + const clusterAdminGroups = clusterAdminUsersAndGroups.groups || []; + const filteredClusterAdminGroups = clusterAdminGroups.filter( + (group) => !group.startsWith('system:'), + ); + const filteredClusterAdminUsers = clusterAdminUsers.filter((user) => !user.startsWith('system:')); + return getGroupUserList(fastify, filteredClusterAdminGroups, filteredClusterAdminUsers); +}; + export const getAllowedUserList = async (fastify: KubeFastifyInstance): Promise => { const allowedGroups = getAllowedGroups(); const allowedGroupList = allowedGroups