diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/common/observability_ai_assistant_api_client.ts b/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/common/observability_ai_assistant_api_client.ts index 3ac941501ae7c..566d06702872f 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/common/observability_ai_assistant_api_client.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/common/observability_ai_assistant_api_client.ts @@ -40,7 +40,11 @@ export function getObservabilityAIAssistantApiClient({ } } -type ObservabilityAIAssistantApiClientKey = 'slsAdmin' | 'slsEditor' | 'slsUser'; +type ObservabilityAIAssistantApiClientKey = + | 'slsAdmin' + | 'slsEditor' + | 'slsUser' + | 'slsUnauthorized'; export type ObservabilityAIAssistantApiClient = Record< ObservabilityAIAssistantApiClientKey, @@ -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({ @@ -222,5 +235,9 @@ export async function getObservabilityAIAssistantApiClientService({ svlSharedConfig, supertestUserWithCookieCredentials: supertestEditorWithCookieCredentials, }), + slsUnauthorized: await getObservabilityAIAssistantApiClient({ + svlSharedConfig, + supertestUserWithCookieCredentials: supertestUnauthorizedWithCookieCredentials, + }), }; } diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/chat/chat.spec.ts b/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/chat/chat.spec.ts index 2a25a309e8174..40f3db279135e 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/chat/chat.spec.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/chat/chat.spec.ts @@ -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; @@ -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((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.` - ); }); }); } diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/complete/complete.spec.ts b/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/complete/complete.spec.ts index 4d3e1baed3f9b..47aa5018f810a 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/complete/complete.spec.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/complete/complete.spec.ts @@ -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); + }); + }); }); } diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/connectors/connectors.spec.ts b/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/connectors/connectors.spec.ts index 2096abe74e2e8..2c112f85fc219 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/connectors/connectors.spec.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/connectors/connectors.spec.ts @@ -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); @@ -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); @@ -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); + }); + }); }); } diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/conversations/conversations.spec.ts b/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/conversations/conversations.spec.ts index 7033e0660f5c6..6656ea0407817 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/conversations/conversations.spec.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/conversations/conversations.spec.ts @@ -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); + }); + }); + }); }); } diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/knowledge_base/knowledge_base.spec.ts b/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/knowledge_base/knowledge_base.spec.ts index f156ba7e583b5..f5413de3f3ff5 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/knowledge_base/knowledge_base.spec.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/knowledge_base/knowledge_base.spec.ts @@ -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); + }); + }); + }); }); } diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/knowledge_base/knowledge_base_setup.spec.ts b/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/knowledge_base/knowledge_base_setup.spec.ts index 87ceec18f1985..6f99a841f4d9f 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/knowledge_base/knowledge_base_setup.spec.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/knowledge_base/knowledge_base_setup.spec.ts @@ -15,6 +15,8 @@ import { import { FtrProviderContext } from '../../common/ftr_provider_context'; +export const KNOWLEDGE_BASE_SETUP_API_URL = '/internal/observability_ai_assistant/kb/setup'; + export default function ApiTest({ getService }: FtrProviderContext) { const ml = getService('ml'); const es = getService('es'); @@ -33,7 +35,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { await createKnowledgeBaseModel(ml); const res = await observabilityAIAssistantAPIClient .slsAdmin({ - endpoint: 'POST /internal/observability_ai_assistant/kb/setup', + endpoint: `POST ${KNOWLEDGE_BASE_SETUP_API_URL}`, params: { query: { model_id: TINY_ELSER.id, @@ -52,7 +54,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('returns bad request if model cannot be installed', async () => { const res = await observabilityAIAssistantAPIClient .slsAdmin({ - endpoint: 'POST /internal/observability_ai_assistant/kb/setup', + endpoint: `POST ${KNOWLEDGE_BASE_SETUP_API_URL}`, params: { query: { model_id: TINY_ELSER.id, @@ -66,5 +68,20 @@ export default function ApiTest({ getService }: FtrProviderContext) { 'No known trained model with model_id [pt_tiny_elser]' ); }); + + describe('security roles and access privileges', () => { + it('should deny access for users without the ai_assistant privilege', async () => { + await observabilityAIAssistantAPIClient + .slsUnauthorized({ + endpoint: `POST ${KNOWLEDGE_BASE_SETUP_API_URL}`, + params: { + query: { + model_id: TINY_ELSER.id, + }, + }, + }) + .expect(403); + }); + }); }); } diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/knowledge_base/knowledge_base_status.spec.ts b/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/knowledge_base/knowledge_base_status.spec.ts index 207badc1b855a..458cff655d404 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/knowledge_base/knowledge_base_status.spec.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/knowledge_base/knowledge_base_status.spec.ts @@ -14,6 +14,9 @@ import { } from '@kbn/test-suites-xpack/observability_ai_assistant_api_integration/tests/knowledge_base/helpers'; import { AI_ASSISTANT_KB_INFERENCE_ID } from '@kbn/observability-ai-assistant-plugin/server/service/inference_endpoint'; import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { KNOWLEDGE_BASE_SETUP_API_URL } from './knowledge_base_setup.spec'; + +const KNOWLEDGE_BASE_STATUS_API_URL = '/internal/observability_ai_assistant/kb/status'; export default function ApiTest({ getService }: FtrProviderContext) { const ml = getService('ml'); @@ -27,7 +30,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { await createKnowledgeBaseModel(ml); await observabilityAIAssistantAPIClient .slsAdmin({ - endpoint: 'POST /internal/observability_ai_assistant/kb/setup', + endpoint: `POST ${KNOWLEDGE_BASE_SETUP_API_URL}`, params: { query: { model_id: TINY_ELSER.id, @@ -45,7 +48,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('returns correct status after knowledge base is setup', async () => { const res = await observabilityAIAssistantAPIClient .slsEditor({ - endpoint: 'GET /internal/observability_ai_assistant/kb/status', + endpoint: `GET ${KNOWLEDGE_BASE_STATUS_API_URL}`, }) .expect(200); @@ -59,12 +62,22 @@ export default function ApiTest({ getService }: FtrProviderContext) { const res = await observabilityAIAssistantAPIClient .slsEditor({ - endpoint: 'GET /internal/observability_ai_assistant/kb/status', + endpoint: `GET ${KNOWLEDGE_BASE_STATUS_API_URL}`, }) .expect(200); expect(res.body.enabled).to.be(true); expect(res.body.ready).to.be(false); }); + + describe('security roles and access privileges', () => { + it('should deny access for users without the ai_assistant privilege', async () => { + await observabilityAIAssistantAPIClient + .slsUnauthorized({ + endpoint: `GET ${KNOWLEDGE_BASE_STATUS_API_URL}`, + }) + .expect(403); + }); + }); }); } diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/knowledge_base/knowledge_base_user_instructions.spec.ts b/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/knowledge_base/knowledge_base_user_instructions.spec.ts index 0557d43830bc0..e6d954529d759 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/knowledge_base/knowledge_base_user_instructions.spec.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/knowledge_base/knowledge_base_user_instructions.spec.ts @@ -329,5 +329,32 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(conversation.messages.length).to.be(5); }); }); + + describe('security roles and access privileges', () => { + describe('should deny access for users without the ai_assistant privilege', () => { + it('PUT /internal/observability_ai_assistant/kb/user_instructions', async () => { + await observabilityAIAssistantAPIClient + .slsUnauthorized({ + endpoint: 'PUT /internal/observability_ai_assistant/kb/user_instructions', + params: { + body: { + id: 'test-instruction', + text: 'Test user instruction', + public: true, + }, + }, + }) + .expect(403); + }); + + it('GET /internal/observability_ai_assistant/kb/user_instructions', async () => { + await observabilityAIAssistantAPIClient + .slsUnauthorized({ + endpoint: 'GET /internal/observability_ai_assistant/kb/user_instructions', + }) + .expect(403); + }); + }); + }); }); }