Skip to content

Commit

Permalink
Add authorization to kserve
Browse files Browse the repository at this point in the history
  • Loading branch information
lucferbux committed Apr 5, 2024
1 parent 3c8af69 commit b21c93d
Show file tree
Hide file tree
Showing 40 changed files with 811 additions and 143 deletions.
12 changes: 12 additions & 0 deletions backend/src/routes/api/dsci/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { getClusterInitialization } from '../../../utils/dsci';
import { KubeFastifyInstance } from '../../../types';
import { secureRoute } from '../../../utils/route-security';

module.exports = async (fastify: KubeFastifyInstance) => {
fastify.get(
'/status',
secureRoute(fastify)(async () => {
return getClusterInitialization(fastify);
}),
);
};
20 changes: 18 additions & 2 deletions backend/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export type DashboardConfig = K8sResourceCommon & {
disableBiasMetrics: boolean;
disablePerformanceMetrics: boolean;
disableKServe: boolean;
disableKServeAuth: boolean;
disableModelMesh: boolean;
disableAcceleratorProfiles: boolean;
disablePipelineExperiments: boolean;
Expand Down Expand Up @@ -945,7 +946,7 @@ type ComponentNames =
| 'workbenches';

export type DataScienceClusterKindStatus = {
conditions: [];
conditions: K8sCondition[];
installedComponents: { [key in ComponentNames]?: boolean };
phase?: string;
};
Expand All @@ -960,6 +961,21 @@ export type DataScienceClusterList = {
items: DataScienceClusterKind[];
};

export type DataScienceClusterInitializationKindStatus = {
conditions: K8sCondition[];
phase?: string;
};

export type DataScienceClusterInitializationKind = K8sResourceCommon & {
spec: unknown; // we should never need to look into this
status: DataScienceClusterInitializationKindStatus;
};

export type DataScienceClusterInitializationList = {
kind: 'DataScienceClusterInitializationList';
items: DataScienceClusterInitializationKind[];
};

export type SubscriptionStatusData = {
installedCSV?: string;
installPlanRefNamespace?: string;
Expand Down Expand Up @@ -997,4 +1013,4 @@ export type DSPipelineKind = K8sResourceCommon & {
status?: {
conditions?: K8sCondition[];
};
};
};
1 change: 1 addition & 0 deletions backend/src/utils/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export const blankDashboardCR: DashboardConfig = {
disablePerformanceMetrics: false,
disablePipelines: false,
disableKServe: false,
disableKServeAuth: false,
disableModelMesh: false,
disableAcceleratorProfiles: false,
disablePipelineExperiments: true,
Expand Down
25 changes: 25 additions & 0 deletions backend/src/utils/dsci.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import {
DataScienceClusterInitializationKind,
DataScienceClusterInitializationKindStatus,
DataScienceClusterInitializationList,
KubeFastifyInstance,
} from '../types';
import { createCustomError } from './requestUtils';

export const getClusterInitialization = async (
fastify: KubeFastifyInstance,
): Promise<DataScienceClusterInitializationKindStatus> => {
const result: DataScienceClusterInitializationKind | null = await fastify.kube.customObjectsApi
.listClusterCustomObject('dscinitialization.opendatahub.io', 'v1', 'dscinitializations')
.then((res) => (res.body as DataScienceClusterInitializationList).items[0])
.catch((e) => {
fastify.log.error(`Failure to fetch dsci: ${e.response.body}`);
return null;
});

if (!result) {
throw createCustomError('DSCI Unavailable', 'Unable to get status', 404);
}

return result.status;
};
1 change: 1 addition & 0 deletions docs/dashboard-config.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ The following are a list of features that are supported, along with there defaul
| disableProjectSharing | false | Disables Project Sharing from Data Science Projects. |
| disableCustomServingRuntimes | false | Disables Custom Serving Runtimes from the Admin Panel. |
| disableKServe | false | Disables the ability to select KServe as a Serving Platform. |
| disableKServeAuth | false | Disables the ability to use auth in KServe. |
| disableModelMesh | false | Disables the ability to select ModelMesh as a Serving Platform. |
| disableAcceleratorProfiles | false | Disables Accelerator profiles from the Admin Panel. |
| modelMetricsNamespace | false | Enables the namespace in which the Model Serving Metrics' Prometheus Operator is installed. |
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/__mocks__/mockDashboardConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ type MockDashboardConfigType = {
disableModelServing?: boolean;
disableCustomServingRuntimes?: boolean;
disableKServe?: boolean;
disableKServeAuth?: boolean;
disableModelMesh?: boolean;
disableAcceleratorProfiles?: boolean;
disablePerformanceMetrics?: boolean;
Expand All @@ -37,6 +38,7 @@ export const mockDashboardConfig = ({
disableCustomServingRuntimes = false,
disablePipelines = false,
disableKServe = false,
disableKServeAuth = false,
disableModelMesh = false,
disableAcceleratorProfiles = false,
disablePerformanceMetrics = false,
Expand Down Expand Up @@ -73,6 +75,7 @@ export const mockDashboardConfig = ({
disableBiasMetrics,
disablePerformanceMetrics,
disableKServe,
disableKServeAuth,
disableModelMesh,
disableAcceleratorProfiles,
disablePipelineExperiments,
Expand Down
37 changes: 37 additions & 0 deletions frontend/src/__mocks__/mockDsciStatus.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { StackCapability } from '~/concepts/areas/types';
import { DataScienceClusterInitializationKindStatus, K8sCondition } from '~/k8sTypes';

export type MockDsciStatus = {
conditions?: K8sCondition[];
requiredCapabilities?: StackCapability[];
phase?: string;
};

export const mockDsciStatus = ({
conditions = [],
requiredCapabilities = [],
phase = 'Ready',
}: MockDsciStatus): DataScienceClusterInitializationKindStatus => ({
conditions: [
...[
{
lastHeartbeatTime: '2023-10-20T11:45:04Z',
lastTransitionTime: '2023-10-20T11:45:04Z',
message: 'Reconcile completed successfully',
reason: 'ReconcileCompleted',
status: 'True',
type: 'ReconcileComplete',
},
...requiredCapabilities.map((capability) => ({
lastHeartbeatTime: '2023-10-20T11:45:04Z',
lastTransitionTime: '2023-10-20T11:45:04Z',
message: `Capability ${capability} installed`,
reason: 'ReconcileCompleted',
status: 'True',
type: capability,
})),
],
...conditions,
],
phase,
});
6 changes: 6 additions & 0 deletions frontend/src/__mocks__/mockInferenceServiceModalData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ export const mockInferenceServiceModalData = ({
},
minReplicas = 1,
maxReplicas = 1,
externalRoute = false,
tokenAuth = false,
tokens = [],
}: MockResourceConfigType): CreatingInferenceServiceObject => ({
name,
project,
Expand All @@ -29,4 +32,7 @@ export const mockInferenceServiceModalData = ({
format,
minReplicas,
maxReplicas,
externalRoute,
tokenAuth,
tokens,
});
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,12 @@ import { be } from '~/__tests__/cypress/cypress/utils/should';
import { InferenceServiceKind, ServingRuntimeKind } from '~/k8sTypes';
import { ServingRuntimePlatform } from '~/types';
import { deleteModal } from '~/__tests__/cypress/cypress/pages/components/DeleteModal';
import { StackCapability } from '~/concepts/areas/types';
import { mockDsciStatus } from '~/__mocks__/mockDsciStatus';

type HandlersProps = {
disableKServeConfig?: boolean;
disableKServeAuthConfig?: boolean;
disableModelMeshConfig?: boolean;
disableAccelerator?: boolean;
projectEnableModelMesh?: boolean;
Expand All @@ -50,10 +53,12 @@ type HandlersProps = {
rejectInferenceService?: boolean;
rejectServingRuntime?: boolean;
rejectDataConnection?: boolean;
requiredCapabilities?: StackCapability[];
};

const initIntercepts = ({
disableKServeConfig,
disableKServeAuthConfig,
disableModelMeshConfig,
disableAccelerator,
projectEnableModelMesh,
Expand Down Expand Up @@ -86,19 +91,27 @@ const initIntercepts = ({
rejectInferenceService = false,
rejectServingRuntime = false,
rejectDataConnection = false,
requiredCapabilities = [],
}: HandlersProps) => {
cy.intercept(
'/api/dsc/status',
mockDscStatus({
installedComponents: { kserve: true, 'model-mesh': true },
}),
);
cy.intercept(
'/api/dsci/status',
mockDsciStatus({
requiredCapabilities,
}),
);
cy.intercept('/api/status', mockStatus());
cy.intercept(
'/api/config',
mockDashboardConfig({
disableKServe: disableKServeConfig,
disableModelMesh: disableModelMeshConfig,
disableKServeAuth: disableKServeAuthConfig,
}),
);
cy.intercept(
Expand Down Expand Up @@ -545,6 +558,7 @@ describe('Serving Runtime List', () => {
disableModelMeshConfig: false,
disableKServeConfig: false,
servingRuntimes: [],
requiredCapabilities: [StackCapability.SERVICE_MESH, StackCapability.SERVICE_MESH_AUTHZ],
});

projectDetails.visitSection('test-project', 'model-server');
Expand All @@ -561,6 +575,10 @@ describe('Serving Runtime List', () => {
kserveModal.findServingRuntimeTemplateDropdown().findDropdownItem('Caikit').click();
kserveModal.findModelFrameworkSelect().findSelectOption('onnx - 1').click();
kserveModal.findSubmitButton().should('be.disabled');
// check external route, token should be checked and no alert
kserveModal.findAuthenticationCheckbox().check();
kserveModal.findExternalRouteError().should('not.exist');
kserveModal.findServiceAccountNameInput().should('have.value', 'default-name');
kserveModal.findExistingConnectionSelect().findSelectOption('Test Secret').click();
kserveModal.findLocationPathInput().type('test-model/');
kserveModal.findSubmitButton().should('be.enabled');
Expand Down Expand Up @@ -615,6 +633,31 @@ describe('Serving Runtime List', () => {
});
});

it('Kserve auth should be hidden when auth is disabled', () => {
initIntercepts({
disableModelMeshConfig: false,
disableKServeConfig: false,
servingRuntimes: [],
});

projectDetails.visitSection('test-project', 'model-server');

modelServingSection.getServingPlatformCard('single-serving').findDeployModelButton().click();

kserveModal.shouldBeOpen();

// test that you can not submit on empty
kserveModal.findSubmitButton().should('be.disabled');

// test filling in minimum required fields
kserveModal.findModelNameInput().type('Test Name');
kserveModal.findServingRuntimeTemplateDropdown().findDropdownItem('Caikit').click();
kserveModal.findModelFrameworkSelect().findSelectOption('onnx - 1').click();
kserveModal.findSubmitButton().should('be.disabled');
// check external route, token should be checked and no alert
kserveModal.findAuthenticationCheckbox().should('not.exist');
});

it('Do not deploy KServe model when user cannot edit namespace', () => {
initIntercepts({
disableModelMeshConfig: false,
Expand Down Expand Up @@ -1495,6 +1538,37 @@ describe('Serving Runtime List', () => {
});
});

describe('Check token section in serving runtime details', () => {
it('Check token section is enabled if capability is enabled', () => {
initIntercepts({
projectEnableModelMesh: false,
disableKServeConfig: false,
disableModelMeshConfig: true,
disableAccelerator: true,
requiredCapabilities: [StackCapability.SERVICE_MESH, StackCapability.SERVICE_MESH_AUTHZ],
});
projectDetails.visitSection('test-project', 'model-server');
const kserveRow = modelServingSection.getKServeRow('Llama Caikit');
kserveRow.findExpansion().should(be.collapsed);
kserveRow.findToggleButton().click();
kserveRow.findDescriptionListItem('Token authorization').should('exist');
});

it('Check token section is disabled if capability is disabled', () => {
initIntercepts({
projectEnableModelMesh: false,
disableKServeConfig: false,
disableModelMeshConfig: true,
disableAccelerator: true,
});
projectDetails.visitSection('test-project', 'model-server');
const kserveRow = modelServingSection.getKServeRow('Llama Caikit');
kserveRow.findExpansion().should(be.collapsed);
kserveRow.findToggleButton().click();
kserveRow.findDescriptionListItem('Token authorization').should('not.exist');
});
});

describe('Dry run check', () => {
it('Check when inference service dryRun fails', () => {
initIntercepts({
Expand Down
8 changes: 8 additions & 0 deletions frontend/src/__tests__/cypress/cypress/pages/modelServing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,14 @@ class InferenceServiceModal extends Modal {
return this.find().findByTestId('existing-data-connection-radio');
}

findExternalRouteError() {
return this.find().findByTestId('external-route-no-token-alert');
}

findServiceAccountNameInput() {
return this.find().findByTestId('service-account-form-name');
}

findNewDataConnectionOption() {
return this.find().findByTestId('new-data-connection-radio');
}
Expand Down
24 changes: 24 additions & 0 deletions frontend/src/api/k8s/__tests__/inferenceServices.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,30 @@ describe('assembleInferenceService', () => {
expect(inferenceService.metadata.annotations?.['serving.kserve.io/deploymentMode']).toBe(
undefined,
);
expect(inferenceService.metadata.annotations?.['security.opendatahub.io/enable-auth']).toBe(
undefined,
);
expect(
inferenceService.metadata.annotations?.['serving.knative.openshift.io/enablePassthrough'],
).toBe('true');
expect(inferenceService.metadata.annotations?.['sidecar.istio.io/inject']).toBe('true');
expect(inferenceService.metadata.annotations?.['sidecar.istio.io/rewriteAppHTTPProbers']).toBe(
'true',
);
});

it('should have the right annotations when creating for Kserve with auth', async () => {
const inferenceService = assembleInferenceService(
mockInferenceServiceModalData({ tokenAuth: true }),
);

expect(inferenceService.metadata.annotations).toBeDefined();
expect(inferenceService.metadata.annotations?.['serving.kserve.io/deploymentMode']).toBe(
undefined,
);
expect(inferenceService.metadata.annotations?.['security.opendatahub.io/enable-auth']).toBe(
'true',
);
expect(
inferenceService.metadata.annotations?.['serving.knative.openshift.io/enablePassthrough'],
).toBe('true');
Expand Down
Loading

0 comments on commit b21c93d

Please sign in to comment.