Skip to content

Commit

Permalink
[Obs AI Assistant] Add route privilege tests for Serverless (#205210)
Browse files Browse the repository at this point in the history
Closes #204884

## Summary

This PR adds security and route privilege tests to the serverless test
suite.

### Checklist

- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [x] [Flaky Test
Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was
used on any tests changed
- [x] The PR description includes the appropriate Release Notes section,
and the correct `release_note:*` label is applied per the
[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)
  • Loading branch information
viduni94 authored Jan 3, 2025
1 parent b851db3 commit 7d76276
Show file tree
Hide file tree
Showing 9 changed files with 268 additions and 59 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,11 @@ export function getObservabilityAIAssistantApiClient({
}
}

type ObservabilityAIAssistantApiClientKey = 'slsAdmin' | 'slsEditor' | 'slsUser';
type ObservabilityAIAssistantApiClientKey =
| 'slsAdmin'
| 'slsEditor'
| 'slsUser'
| 'slsUnauthorized';

export type ObservabilityAIAssistantApiClient = Record<
ObservabilityAIAssistantApiClientKey,
Expand Down Expand Up @@ -195,18 +199,27 @@ export async function getObservabilityAIAssistantApiClientService({
const svlSharedConfig = getService('config');
const roleScopedSupertest = getService('roleScopedSupertest');

// admin user
const supertestAdminWithCookieCredentials: SupertestWithRoleScope =
await roleScopedSupertest.getSupertestWithRoleScope('admin', {
useCookieHeader: true,
withInternalHeaders: true,
});

// editor user
const supertestEditorWithCookieCredentials: SupertestWithRoleScope =
await roleScopedSupertest.getSupertestWithRoleScope('editor', {
useCookieHeader: true,
withInternalHeaders: true,
});

// unauthorized user
const supertestUnauthorizedWithCookieCredentials: SupertestWithRoleScope =
await roleScopedSupertest.getSupertestWithRoleScope('viewer', {
useCookieHeader: false,
withInternalHeaders: true,
});

return {
// defaults to elastic_admin user when used without auth
slsUser: await getObservabilityAIAssistantApiClient({
Expand All @@ -222,5 +235,9 @@ export async function getObservabilityAIAssistantApiClientService({
svlSharedConfig,
supertestUserWithCookieCredentials: supertestEditorWithCookieCredentials,
}),
slsUnauthorized: await getObservabilityAIAssistantApiClient({
svlSharedConfig,
supertestUserWithCookieCredentials: supertestUnauthorizedWithCookieCredentials,
}),
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
const svlCommonApi = getService('svlCommonApi');
const log = getService('log');
const roleScopedSupertest = getService('roleScopedSupertest');
const observabilityAIAssistantAPIClient = getService('observabilityAIAssistantAPIClient');

let supertestEditorWithCookieCredentials: SupertestWithRoleScope;

Expand Down Expand Up @@ -170,57 +171,23 @@ export default function ApiTest({ getService }: FtrProviderContext) {
]);
});

it.skip('returns a useful error if the request fails', async () => {
const interceptor = proxy.intercept('conversation', () => true);

const passThrough = new PassThrough();

supertestWithoutAuth
.post(CHAT_API_URL)
.set(roleAuthc.apiKeyHeader)
.set(internalReqHeader)
.set('kbn-xsrf', 'foo')
.send({
name: 'my_api_call',
messages,
connectorId,
functions: [],
scopes: ['all'],
})
.expect(200)
.pipe(passThrough);

let data: string = '';

passThrough.on('data', (chunk) => {
data += chunk.toString('utf-8');
describe('security roles and access privileges', () => {
it('should deny access for users without the ai_assistant privilege', async () => {
await observabilityAIAssistantAPIClient
.slsUnauthorized({
endpoint: `POST ${CHAT_API_URL}`,
params: {
body: {
name: 'my_api_call',
messages,
connectorId,
functions: [],
scopes: ['all'],
},
},
})
.expect(403);
});

const simulator = await interceptor.waitForIntercept();

await simulator.status(400);

await simulator.rawWrite(
JSON.stringify({
error: {
code: 'context_length_exceeded',
message:
"This model's maximum context length is 8192 tokens. However, your messages resulted in 11036 tokens. Please reduce the length of the messages.",
param: 'messages',
type: 'invalid_request_error',
},
})
);

await simulator.rawEnd();

await new Promise<void>((resolve) => passThrough.on('end', () => resolve()));

const response = JSON.parse(data.trim());

expect(response.error.message).to.be(
`Token limit reached. Token limit is 8192, but the current conversation has 11036 tokens.`
);
});
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -547,5 +547,24 @@ export default function ApiTest({ getService }: FtrProviderContext) {

// todo
it.skip('executes a function', async () => {});

describe('security roles and access privileges', () => {
it('should deny access for users without the ai_assistant privilege', async () => {
await observabilityAIAssistantAPIClient
.slsUnauthorized({
endpoint: 'POST /internal/observability_ai_assistant/chat/complete',
params: {
body: {
messages,
connectorId,
persist: false,
screenContexts: [],
scopes: ['all'],
},
},
})
.expect(403);
});
});
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,14 @@ export default function ApiTest({ getService }: FtrProviderContext) {
it('Returns a 2xx for enterprise license', async () => {
await observabilityAIAssistantAPIClient
.slsEditor({
endpoint: 'GET /internal/observability_ai_assistant/connectors',
endpoint: `GET /internal/observability_ai_assistant/connectors`,
})
.expect(200);
});

it('returns an empty list of connectors', async () => {
const res = await observabilityAIAssistantAPIClient.slsEditor({
endpoint: 'GET /internal/observability_ai_assistant/connectors',
endpoint: `GET /internal/observability_ai_assistant/connectors`,
});

expect(res.body.length).to.be(0);
Expand All @@ -70,7 +70,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
});

const res = await observabilityAIAssistantAPIClient.slsEditor({
endpoint: 'GET /internal/observability_ai_assistant/connectors',
endpoint: `GET /internal/observability_ai_assistant/connectors`,
});

expect(res.body.length).to.be(1);
Expand All @@ -83,6 +83,16 @@ export default function ApiTest({ getService }: FtrProviderContext) {
roleAuthc,
});
});

describe('security roles and access privileges', () => {
it('should deny access for users without the ai_assistant privilege', async () => {
await observabilityAIAssistantAPIClient
.slsUnauthorized({
endpoint: `GET /internal/observability_ai_assistant/connectors`,
})
.expect(403);
});
});
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -253,5 +253,103 @@ export default function ApiTest({ getService }: FtrProviderContext) {
});
});
});

describe('security roles and access privileges', () => {
describe('should deny access for users without the ai_assistant privilege', () => {
let createResponse: Awaited<
SupertestReturnType<'POST /internal/observability_ai_assistant/conversation'>
>;
before(async () => {
createResponse = await observabilityAIAssistantAPIClient
.slsEditor({
endpoint: 'POST /internal/observability_ai_assistant/conversation',
params: {
body: {
conversation: conversationCreate,
},
},
})
.expect(200);
});

after(async () => {
await observabilityAIAssistantAPIClient
.slsEditor({
endpoint: 'DELETE /internal/observability_ai_assistant/conversation/{conversationId}',
params: {
path: {
conversationId: createResponse.body.conversation.id,
},
},
})
.expect(200);
});

it('POST /internal/observability_ai_assistant/conversation', async () => {
await observabilityAIAssistantAPIClient
.slsUnauthorized({
endpoint: 'POST /internal/observability_ai_assistant/conversation',
params: {
body: {
conversation: conversationCreate,
},
},
})
.expect(403);
});

it('POST /internal/observability_ai_assistant/conversations', async () => {
await observabilityAIAssistantAPIClient
.slsUnauthorized({
endpoint: 'POST /internal/observability_ai_assistant/conversations',
})
.expect(403);
});

it('PUT /internal/observability_ai_assistant/conversation/{conversationId}', async () => {
await observabilityAIAssistantAPIClient
.slsUnauthorized({
endpoint: 'PUT /internal/observability_ai_assistant/conversation/{conversationId}',
params: {
path: {
conversationId: createResponse.body.conversation.id,
},
body: {
conversation: merge(omit(conversationUpdate, 'conversation.id'), {
conversation: { id: createResponse.body.conversation.id },
}),
},
},
})
.expect(403);
});

it('GET /internal/observability_ai_assistant/conversation/{conversationId}', async () => {
await observabilityAIAssistantAPIClient
.slsUnauthorized({
endpoint: 'GET /internal/observability_ai_assistant/conversation/{conversationId}',
params: {
path: {
conversationId: createResponse.body.conversation.id,
},
},
})
.expect(403);
});

it('DELETE /internal/observability_ai_assistant/conversation/{conversationId}', async () => {
await observabilityAIAssistantAPIClient
.slsUnauthorized({
endpoint: 'DELETE /internal/observability_ai_assistant/conversation/{conversationId}',
params: {
path: {
conversationId: createResponse.body.conversation.id,
},
},
})
.expect(403);
});
});
});
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,47 @@ export default function ApiTest({ getService }: FtrProviderContext) {
expect(entries[0].title).to.eql('My title b');
});
});

describe('security roles and access privileges', () => {
describe('should deny access for users without the ai_assistant privilege', () => {
it('POST /internal/observability_ai_assistant/kb/entries/save', async () => {
await observabilityAIAssistantAPIClient
.slsUnauthorized({
endpoint: 'POST /internal/observability_ai_assistant/kb/entries/save',
params: {
body: {
id: 'my-doc-id-1',
title: 'My title',
text: 'My content',
},
},
})
.expect(403);
});

it('GET /internal/observability_ai_assistant/kb/entries', async () => {
await observabilityAIAssistantAPIClient
.slsUnauthorized({
endpoint: 'GET /internal/observability_ai_assistant/kb/entries',
params: {
query: { query: '', sortBy: 'title', sortDirection: 'asc' },
},
})
.expect(403);
});

it('DELETE /internal/observability_ai_assistant/kb/entries/{entryId}', async () => {
await observabilityAIAssistantAPIClient
.slsUnauthorized({
endpoint: 'DELETE /internal/observability_ai_assistant/kb/entries/{entryId}',
params: {
path: { entryId: 'my-doc-id-1' },
},
})
.expect(403);
});
});
});
});
}

Expand Down
Loading

0 comments on commit 7d76276

Please sign in to comment.