Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Obs AI Assistant] Add scoped privileges to readUser and writeUser #183592

Merged
merged 8 commits into from
May 22, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* 2.0.
*/

import { Config, FtrConfigProviderContext } from '@kbn/test';
import { Config, FtrConfigProviderContext, kbnTestConfig } from '@kbn/test';
import supertest from 'supertest';
import { format, UrlObject } from 'url';
import { ObservabilityAIAssistantFtrConfigName } from '../configs';
Expand All @@ -15,15 +15,22 @@ import {
createObservabilityAIAssistantApiClient,
ObservabilityAIAssistantAPIClient,
} from './observability_ai_assistant_api_client';
import { editorUser, viewerUser } from './users/users';

export interface ObservabilityAIAssistantFtrConfig {
name: ObservabilityAIAssistantFtrConfigName;
license: 'basic' | 'trial';
kibanaConfig?: Record<string, any>;
}

async function getObservabilityAIAssistantAPIClient(kibanaServerUrl: string) {
return createObservabilityAIAssistantApiClient(supertest(kibanaServerUrl));
async function getObservabilityAIAssistantAPIClient(kibanaServer: UrlObject, username: string) {
const { password } = kbnTestConfig.getUrlParts();
const baseUrlWithAuth = format({
...kibanaServer,
auth: `${username}:${password}`,
});

return createObservabilityAIAssistantApiClient(supertest(baseUrlWithAuth));
}

export type CreateTestConfig = ReturnType<typeof createTestConfig>;
Expand All @@ -33,16 +40,17 @@ export interface CreateTest {
servers: any;
services: InheritedServices & {
observabilityAIAssistantAPIClient: () => Promise<{
readUser: ObservabilityAIAssistantAPIClient;
writeUser: ObservabilityAIAssistantAPIClient;
adminUser: ObservabilityAIAssistantAPIClient;
viewerUser: ObservabilityAIAssistantAPIClient;
editorUser: ObservabilityAIAssistantAPIClient;
}>;
};
junit: { reportName: string };
esTestCluster: any;
kbnTestServer: any;
}

export function createObservabilityAIAssistantAPIConfig({
function createObservabilityAIAssistantAPIConfig({
config,
license,
name,
Expand All @@ -56,7 +64,6 @@ export function createObservabilityAIAssistantAPIConfig({
const services = config.get('services') as InheritedServices;
const servers = config.get('servers');
const kibanaServer = servers.kibana as UrlObject;
const kibanaServerUrl = format(kibanaServer);
const apmSynthtraceKibanaClient = services.apmSynthtraceKibanaClient();

const createTest: Omit<CreateTest, 'testFiles'> = {
Expand All @@ -68,8 +75,9 @@ export function createObservabilityAIAssistantAPIConfig({
getApmSynthtraceEsClient(context, apmSynthtraceKibanaClient),
observabilityAIAssistantAPIClient: async () => {
return {
readUser: await getObservabilityAIAssistantAPIClient(kibanaServerUrl),
writeUser: await getObservabilityAIAssistantAPIClient(kibanaServerUrl),
adminUser: await getObservabilityAIAssistantAPIClient(kibanaServer, 'elastic'),
viewerUser: await getObservabilityAIAssistantAPIClient(kibanaServer, viewerUser.username),
editorUser: await getObservabilityAIAssistantAPIClient(kibanaServer, editorUser.username),
};
},
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { InheritedFtrProviderContext } from '../ftr_provider_context';
import { allUsers } from './users';
import { allRoles } from './roles';

export async function createUsersAndRoles(getService: InheritedFtrProviderContext['getService']) {
const security = getService('security');
const log = getService('log');

// create roles
await Promise.all(
allRoles.map(({ name, privileges }) => {
return security.role.create(name, privileges);
})
);

// create users
await Promise.all(
allUsers.map((user) => {
log.info(`Creating user: ${user.username} with roles: ${user.roles.join(', ')}`);
return security.user.create(user.username, {
password: user.password,
roles: user.roles,
});
})
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

// Example role:
// export const allAccessRole: Role = {
// name: 'all_access',
// privileges: {
// elasticsearch: {
// indices: [
// {
// names: ['*'],
// privileges: ['all'],
// },
// ],
// },
// kibana: [
// {
// feature: {
// apm: ['all'],
// actions: ['all'],
// },
// spaces: ['*'],
// },
// ],
// },
// };

export interface Role {
name: string;
privileges: {
elasticsearch?: {
cluster?: string[];
indices?: Array<{
names: string[];
privileges: string[];
}>;
};
kibana?: Array<{
spaces: string[];
base?: string[];
feature?: {
[featureId: string]: string[];
};
}>;
};
}

export const allRoles = [];
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { kbnTestConfig } from '@kbn/test';
const password = kbnTestConfig.getUrlParts().password!;

export interface User {
username: string;
password: string;
roles: string[];
}

export const editorUser: User = {
username: 'editor',
password,
roles: ['editor'],
sorenlouv marked this conversation as resolved.
Show resolved Hide resolved
};

export const viewerUser: User = {
username: 'viewer',
password,
roles: ['viewer'],
};

export const allUsers = [editorUser, viewerUser];
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
)[0]?.conversation.id;

await observabilityAIAssistantAPIClient
.writeUser({
.adminUser({
endpoint: 'DELETE /internal/observability_ai_assistant/conversation/{conversationId}',
params: {
path: {
Expand Down Expand Up @@ -378,7 +378,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
).to.eql(0);

const conversations = await observabilityAIAssistantAPIClient
.writeUser({
.editorUser({
endpoint: 'POST /internal/observability_ai_assistant/conversations',
})
.expect(200);
Expand Down Expand Up @@ -422,7 +422,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
.complete();

const createResponse = await observabilityAIAssistantAPIClient
.writeUser({
.editorUser({
endpoint: 'POST /internal/observability_ai_assistant/chat/complete',
params: {
body: {
Expand All @@ -440,7 +440,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
conversationCreatedEvent = getConversationCreatedEvent(createResponse.body);

const conversationId = conversationCreatedEvent.conversation.id;
const fullConversation = await observabilityAIAssistantAPIClient.readUser({
const fullConversation = await observabilityAIAssistantAPIClient.editorUser({
endpoint: 'GET /internal/observability_ai_assistant/conversation/{conversationId}',
params: {
path: {
Expand All @@ -454,7 +454,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
.complete();

const updatedResponse = await observabilityAIAssistantAPIClient
.writeUser({
.editorUser({
endpoint: 'POST /internal/observability_ai_assistant/chat/complete',
params: {
body: {
Expand Down Expand Up @@ -484,7 +484,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {

after(async () => {
await observabilityAIAssistantAPIClient
.writeUser({
.editorUser({
endpoint: 'DELETE /internal/observability_ai_assistant/conversation/{conversationId}',
params: {
path: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,14 @@ export default function ApiTest({ getService }: FtrProviderContext) {

it('Returns a 2xx for enterprise license', async () => {
await observabilityAIAssistantAPIClient
.readUser({
.editorUser({
endpoint: 'GET /internal/observability_ai_assistant/connectors',
})
.expect(200);
});

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

Expand All @@ -55,7 +55,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
})
.expect(200);

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
describe('without conversations', () => {
it('returns no conversations when listing', async () => {
const response = await observabilityAIAssistantAPIClient
.readUser({
.editorUser({
endpoint: 'POST /internal/observability_ai_assistant/conversations',
})
.expect(200);
Expand All @@ -58,7 +58,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {

it('returns a 404 for updating conversations', async () => {
await observabilityAIAssistantAPIClient
.writeUser({
.editorUser({
endpoint: 'PUT /internal/observability_ai_assistant/conversation/{conversationId}',
params: {
path: {
Expand All @@ -74,7 +74,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {

it('returns a 404 for retrieving a conversation', async () => {
await observabilityAIAssistantAPIClient
.readUser({
.editorUser({
endpoint: 'GET /internal/observability_ai_assistant/conversation/{conversationId}',
params: {
path: {
Expand All @@ -92,7 +92,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
>;
before(async () => {
createResponse = await observabilityAIAssistantAPIClient
.writeUser({
.editorUser({
endpoint: 'POST /internal/observability_ai_assistant/conversation',
params: {
body: {
Expand All @@ -105,7 +105,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {

after(async () => {
await observabilityAIAssistantAPIClient
.writeUser({
.editorUser({
endpoint: 'DELETE /internal/observability_ai_assistant/conversation/{conversationId}',
params: {
path: {
Expand All @@ -116,7 +116,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
.expect(200);

await observabilityAIAssistantAPIClient
.writeUser({
.editorUser({
endpoint: 'GET /internal/observability_ai_assistant/conversation/{conversationId}',
params: {
path: {
Expand All @@ -141,14 +141,14 @@ export default function ApiTest({ getService }: FtrProviderContext) {
namespace: 'default',
public: conversationCreate.public,
user: {
name: 'elastic',
name: 'editor',
},
});
});

it('returns a 404 for updating a non-existing conversation', async () => {
await observabilityAIAssistantAPIClient
.writeUser({
.editorUser({
endpoint: 'PUT /internal/observability_ai_assistant/conversation/{conversationId}',
params: {
path: {
Expand All @@ -164,7 +164,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {

it('returns a 404 for retrieving a non-existing conversation', async () => {
await observabilityAIAssistantAPIClient
.readUser({
.editorUser({
endpoint: 'GET /internal/observability_ai_assistant/conversation/{conversationId}',
params: {
path: {
Expand All @@ -177,7 +177,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {

it('returns the conversation that was created', async () => {
const response = await observabilityAIAssistantAPIClient
.readUser({
.editorUser({
endpoint: 'GET /internal/observability_ai_assistant/conversation/{conversationId}',
params: {
path: {
Expand All @@ -192,7 +192,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {

it('returns the created conversation when listing', async () => {
const response = await observabilityAIAssistantAPIClient
.readUser({
.editorUser({
endpoint: 'POST /internal/observability_ai_assistant/conversations',
})
.expect(200);
Expand All @@ -210,7 +210,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {

before(async () => {
updateResponse = await observabilityAIAssistantAPIClient
.writeUser({
.editorUser({
endpoint: 'PUT /internal/observability_ai_assistant/conversation/{conversationId}',
params: {
path: {
Expand All @@ -234,7 +234,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {

it('returns the updated conversation after get', async () => {
const updateAfterCreateResponse = await observabilityAIAssistantAPIClient
.writeUser({
.editorUser({
endpoint: 'GET /internal/observability_ai_assistant/conversation/{conversationId}',
params: {
path: {
Expand Down
Loading