Skip to content

Commit

Permalink
Fetch correct cluster admin list for the notebook controller admin pa…
Browse files Browse the repository at this point in the history
…nel (opendatahub-io#3538)

* Fetch correct cluster admin list for the notebook controller admin panel

* Only show cluster admins with notebooks
  • Loading branch information
DaoDaoNoCode authored and ConorOM1 committed Dec 12, 2024
1 parent 706b437 commit 9883692
Show file tree
Hide file tree
Showing 3 changed files with 53 additions and 5 deletions.
17 changes: 15 additions & 2 deletions backend/src/routes/api/status/adminAllowedUsers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { getUserInfo } from '../../../utils/userUtils';
import {
getAdminUserList,
getAllowedUserList,
getClusterAdminUserList,
isUserAdmin,
KUBE_SAFE_PREFIX,
} from '../../../utils/adminUtils';
Expand Down Expand Up @@ -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);
};
5 changes: 5 additions & 0 deletions backend/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1255,3 +1255,8 @@ export type NIMAccountKind = K8sResourceCommon & {
conditions?: K8sCondition[];
};
};

export type ResourceAccessReviewResponse = {
groups?: string[];
users?: string[];
};
36 changes: 33 additions & 3 deletions backend/src/utils/adminUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand All @@ -14,10 +15,11 @@ export const KUBE_SAFE_PREFIX = 'b64:';
const getGroupUserList = async (
fastify: KubeFastifyInstance,
groupListNames: string[],
additionalUsers: string[] = [],
): Promise<string[]> => {
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]),
);
};

Expand All @@ -26,10 +28,38 @@ export const getAdminUserList = async (fastify: KubeFastifyInstance): Promise<st
const adminGroupsList = adminGroups
.split(',')
.filter((groupName) => 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<string[]> => {
// 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<string[]> => {
const allowedGroups = getAllowedGroups();
const allowedGroupList = allowedGroups
Expand Down

0 comments on commit 9883692

Please sign in to comment.