Skip to content

Commit

Permalink
[8.15] [Security AI Assistant] Fixed license issue for Knowledge Base…
Browse files Browse the repository at this point in the history
… resources initialization (#198239) (#198449)

# Backport

This will backport the following commits from `main` to `8.15`:
- [[Security AI Assistant] Fixed license issue for Knowledge Base
resources initialization
(#198239)](#198239)

<!--- Backport version: 8.9.8 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Yuliia
Naumenko","email":"[email protected]"},"sourceCommit":{"committedDate":"2024-10-30T21:13:33Z","message":"[Security
AI Assistant] Fixed license issue for Knowledge Base resources
initialization
(#198239)","sha":"ed81e4334f4d5608517dacdba28d46dfea966be0","branchLabelMapping":{"^v9.0.0$":"main","^v8.17.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["bug","release_note:skip","v9.0.0","v8.16.0","backport:version","v8.17.0","v8.15.4"],"number":198239,"url":"https://github.com/elastic/kibana/pull/198239","mergeCommit":{"message":"[Security
AI Assistant] Fixed license issue for Knowledge Base resources
initialization
(#198239)","sha":"ed81e4334f4d5608517dacdba28d46dfea966be0"}},"sourceBranch":"main","suggestedTargetBranches":["8.16","8.15"],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","labelRegex":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/198239","number":198239,"mergeCommit":{"message":"[Security
AI Assistant] Fixed license issue for Knowledge Base resources
initialization
(#198239)","sha":"ed81e4334f4d5608517dacdba28d46dfea966be0"}},{"branch":"8.16","label":"v8.16.0","labelRegex":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"8.x","label":"v8.17.0","labelRegex":"^v8.17.0$","isSourceBranch":false,"url":"https://github.com/elastic/kibana/pull/198447","number":198447,"state":"OPEN"},{"branch":"8.15","label":"v8.15.4","labelRegex":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"}]}]
BACKPORT-->

---------

Co-authored-by: Yuliia Naumenko <[email protected]>
Co-authored-by: kibanamachine <[email protected]>
  • Loading branch information
3 people authored Nov 6, 2024
1 parent ecc9bf3 commit ec6faed
Show file tree
Hide file tree
Showing 36 changed files with 287 additions and 214 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export interface UseKnowledgeBaseStatusParams {
http: HttpSetup;
resource?: string;
toasts?: IToasts;
enabled: boolean;
}

/**
Expand All @@ -36,13 +37,15 @@ export const useKnowledgeBaseStatus = ({
http,
resource,
toasts,
enabled,
}: UseKnowledgeBaseStatusParams): UseQueryResult<ReadKnowledgeBaseResponse, IHttpFetchError> => {
return useQuery(
KNOWLEDGE_BASE_STATUS_QUERY_KEY,
async ({ signal }) => {
return getKnowledgeBaseStatus({ http, resource, signal });
},
{
enabled,
retry: false,
keepPreviousData: true,
// Deprecated, hoist to `queryCache` w/in `QueryClient. See: https://stackoverflow.com/a/76961109
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ describe('use chat send', () => {
assistantTelemetry: {
reportAssistantMessageSent,
},
assistantAvailability: {
isAssistantEnabled: true,
},
});
});
it('handleOnChatCleared clears the conversation', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,20 @@ export const useChatSend = ({
setSelectedPromptContexts,
setCurrentConversation,
}: UseChatSendProps): UseChatSend => {
const { assistantTelemetry, toasts } = useAssistantContext();
const {
assistantTelemetry,
toasts,
assistantAvailability: { isAssistantEnabled },
} = useAssistantContext();
const [userPrompt, setUserPrompt] = useState<string | null>(null);

const { isLoading, sendMessage, abortStream } = useSendMessage();
const { clearConversation, removeLastMessage } = useConversation();
const { data: kbStatus } = useKnowledgeBaseStatus({ http, resource: ESQL_RESOURCE });
const { data: kbStatus } = useKnowledgeBaseStatus({
http,
enabled: isAssistantEnabled,
resource: ESQL_RESOURCE,
});
const isSetupComplete =
kbStatus?.elser_exists &&
kbStatus?.index_exists &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ const mockUseAssistantContext = {
},
setAllSystemPrompts: jest.fn(),
setConversations: jest.fn(),
assistantAvailability: {
isAssistantEnabled: true,
},
};

jest.mock('../assistant_context', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,16 @@ interface Props {
*/
export const KnowledgeBaseSettings: React.FC<Props> = React.memo(
({ knowledgeBase, setUpdatedKnowledgeBaseSettings }) => {
const { http, toasts } = useAssistantContext();
const {
http,
toasts,
assistantAvailability: { isAssistantEnabled },
} = useAssistantContext();
const {
data: kbStatus,
isLoading,
isFetching,
} = useKnowledgeBaseStatus({ http, resource: ESQL_RESOURCE });
} = useKnowledgeBaseStatus({ http, enabled: isAssistantEnabled, resource: ESQL_RESOURCE });
const { mutate: setupKB, isLoading: isSettingUpKB } = useSetupKnowledgeBase({ http, toasts });

// Resource enabled state
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,11 @@ const KNOWLEDGE_BASE_INDEX_PATTERN = '.kibana-elastic-ai-assistant-knowledge-bas
* Knowledge Base Settings -- set up the Knowledge Base and configure RAG on alerts
*/
export const KnowledgeBaseSettingsManagement: React.FC = React.memo(() => {
const { http, toasts } = useAssistantContext();
const {
http,
toasts,
assistantAvailability: { isAssistantEnabled },
} = useAssistantContext();
const [hasPendingChanges, setHasPendingChanges] = useState(false);

const { knowledgeBase, setUpdatedKnowledgeBaseSettings, resetSettings, saveSettings } =
Expand Down Expand Up @@ -88,7 +92,7 @@ export const KnowledgeBaseSettingsManagement: React.FC = React.memo(() => {
data: kbStatus,
isLoading,
isFetching,
} = useKnowledgeBaseStatus({ http, resource: ESQL_RESOURCE });
} = useKnowledgeBaseStatus({ enabled: isAssistantEnabled, http, resource: ESQL_RESOURCE });
const { mutate: setupKB, isLoading: isSettingUpKB } = useSetupKnowledgeBase({ http, toasts });

// Resource enabled state
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,17 @@ export const ESQL_RESOURCE = 'esql';
*
*/
export const SetupKnowledgeBaseButton: React.FC = React.memo(() => {
const { http, toasts } = useAssistantContext();
const {
http,
toasts,
assistantAvailability: { isAssistantEnabled },
} = useAssistantContext();

const { data: kbStatus } = useKnowledgeBaseStatus({ http, resource: ESQL_RESOURCE });
const { data: kbStatus } = useKnowledgeBaseStatus({
http,
enabled: isAssistantEnabled,
resource: ESQL_RESOURCE,
});
const { mutate: setupKB, isLoading: isSettingUpKB } = useSetupKnowledgeBase({ http, toasts });

const isSetupInProgress = kbStatus?.is_setup_in_progress || isSettingUpKB;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ describe('createResourceInstallationHelper', () => {
async () => (await getContextInitialized(helper)) === false
);

expect(logger.error).toHaveBeenCalledWith(`Error initializing resources test1 - fail`);
expect(logger.warn).toHaveBeenCalledWith(`Error initializing resources test1 - fail`);
expect(await helper.getInitializedResources('test1')).toEqual({
result: false,
error: `fail`,
Expand Down Expand Up @@ -204,7 +204,7 @@ describe('createResourceInstallationHelper', () => {
async () => (await getContextInitialized(helper)) === false
);

expect(logger.error).toHaveBeenCalledWith(`Error initializing resources default - first error`);
expect(logger.warn).toHaveBeenCalledWith(`Error initializing resources default - first error`);
expect(await helper.getInitializedResources(DEFAULT_NAMESPACE_STRING)).toEqual({
result: false,
error: `first error`,
Expand All @@ -221,9 +221,7 @@ describe('createResourceInstallationHelper', () => {
return logger.error.mock.calls.length === 1;
});

expect(logger.error).toHaveBeenCalledWith(
`Error initializing resources default - second error`
);
expect(logger.warn).toHaveBeenCalledWith(`Error initializing resources default - second error`);

// the second retry is throttled so this is never called
expect(logger.info).not.toHaveBeenCalledWith('test1_default successfully retried');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export function createResourceInstallationHelper(
return errorResult(commonInitError);
}
} catch (err) {
logger.error(`Error initializing resources ${namespace} - ${err.message}`);
logger.warn(`Error initializing resources ${namespace} - ${err.message}`);
return errorResult(err.message);
}
};
Expand Down Expand Up @@ -113,7 +113,7 @@ export function createResourceInstallationHelper(
const key = namespace;
return (
initializedResources.has(key)
? initializedResources.get(key)
? await initializedResources.get(key)
: errorResult(`Unrecognized spaceId ${key}`)
) as InitializationPromise;
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,17 @@ import { AIAssistantService, AIAssistantServiceOpts } from '.';
import { retryUntil } from './create_resource_installation_helper.test';
import { mlPluginMock } from '@kbn/ml-plugin/public/mocks';
import type { MlPluginSetup } from '@kbn/ml-plugin/server';
import { licensingMock } from '@kbn/licensing-plugin/server/mocks';

jest.mock('../ai_assistant_data_clients/conversations', () => ({
AIAssistantConversationsDataClient: jest.fn(),
}));

const licensing = Promise.resolve(
licensingMock.createRequestHandlerContext({
license: { type: 'enterprise' },
})
);
let logger: ReturnType<typeof loggingSystemMock['createLogger']>;
const clusterClient = elasticsearchServiceMock.createClusterClient().asInternalUser;

Expand Down Expand Up @@ -191,6 +197,7 @@ describe('AI Assistant Service', () => {
logger,
spaceId: 'default',
currentUser: mockUser1,
licensing,
});

expect(AIAssistantConversationsDataClient).toHaveBeenCalledWith({
Expand Down Expand Up @@ -221,6 +228,7 @@ describe('AI Assistant Service', () => {
logger,
spaceId: 'default',
currentUser: mockUser1,
licensing,
});

expect(clusterClient.indices.putIndexTemplate).toHaveBeenCalled();
Expand Down Expand Up @@ -274,11 +282,13 @@ describe('AI Assistant Service', () => {
logger,
spaceId: 'default',
currentUser: mockUser1,
licensing,
}),
assistantService.createAIAssistantConversationsDataClient({
logger,
spaceId: 'default',
currentUser: mockUser1,
licensing,
}),
]);

Expand Down Expand Up @@ -340,6 +350,7 @@ describe('AI Assistant Service', () => {
logger,
spaceId: 'default',
currentUser: mockUser1,
licensing,
});

expect(AIAssistantConversationsDataClient).toHaveBeenCalledWith({
Expand Down Expand Up @@ -400,6 +411,7 @@ describe('AI Assistant Service', () => {
logger,
spaceId: 'default',
currentUser: mockUser1,
licensing,
});
};

Expand Down Expand Up @@ -472,6 +484,7 @@ describe('AI Assistant Service', () => {
logger,
spaceId: 'default',
currentUser: mockUser1,
licensing,
});
};

Expand Down Expand Up @@ -513,6 +526,7 @@ describe('AI Assistant Service', () => {
logger,
spaceId: 'test',
currentUser: mockUser1,
licensing,
});

expect(clusterClient.indices.putIndexTemplate).not.toHaveBeenCalled();
Expand Down Expand Up @@ -560,6 +574,7 @@ describe('AI Assistant Service', () => {
logger,
spaceId: 'test',
currentUser: mockUser1,
licensing,
});

expect(clusterClient.indices.putIndexTemplate).not.toHaveBeenCalled();
Expand Down Expand Up @@ -607,6 +622,7 @@ describe('AI Assistant Service', () => {
logger,
spaceId: 'test',
currentUser: mockUser1,
licensing,
});

expect(AIAssistantConversationsDataClient).not.toHaveBeenCalled();
Expand Down Expand Up @@ -752,6 +768,7 @@ describe('AI Assistant Service', () => {
logger,
spaceId: 'default',
currentUser: mockUser1,
licensing,
});

await retryUntil(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import type { AuthenticatedUser, Logger, ElasticsearchClient } from '@kbn/core/s
import type { TaskManagerSetupContract } from '@kbn/task-manager-plugin/server';
import type { MlPluginSetup } from '@kbn/ml-plugin/server';
import { Subject } from 'rxjs';
import { LicensingApiRequestHandlerContext } from '@kbn/licensing-plugin/server';
import { attackDiscoveryFieldMap } from '../ai_assistant_data_clients/attack_discovery/field_maps_configuration';
import { getDefaultAnonymizationFields } from '../../common/anonymization';
import { AssistantResourceNames, GetElser } from '../types';
Expand All @@ -30,6 +31,7 @@ import { knowledgeBaseFieldMap } from '../ai_assistant_data_clients/knowledge_ba
import { AIAssistantKnowledgeBaseDataClient } from '../ai_assistant_data_clients/knowledge_base';
import { AttackDiscoveryDataClient } from '../ai_assistant_data_clients/attack_discovery';
import { createGetElserId, createPipeline, pipelineExists } from './helpers';
import { hasAIAssistantLicense } from '../routes/helpers';

const TOTAL_FIELDS_LIMIT = 2500;

Expand All @@ -50,6 +52,7 @@ export interface CreateAIAssistantClientParams {
logger: Logger;
spaceId: string;
currentUser: AuthenticatedUser | null;
licensing: Promise<LicensingApiRequestHandlerContext>;
}

export type CreateDataStream = (params: {
Expand Down Expand Up @@ -217,7 +220,7 @@ export class AIAssistantService {
pluginStop$: this.options.pluginStop$,
});
} catch (error) {
this.options.logger.error(`Error initializing AI assistant resources: ${error.message}`);
this.options.logger.warn(`Error initializing AI assistant resources: ${error.message}`);
this.initialized = false;
this.isInitializing = false;
return errorResult(error.message);
Expand Down Expand Up @@ -262,6 +265,8 @@ export class AIAssistantService {
};

private async checkResourcesInstallation(opts: CreateAIAssistantClientParams) {
const licensing = await opts.licensing;
if (!hasAIAssistantLicense(licensing.license)) return null;
// Check if resources installation has succeeded
const { result: initialized, error } = await this.getSpaceResourcesInitializationPromise(
opts.spaceId
Expand Down Expand Up @@ -456,7 +461,7 @@ export class AIAssistantService {
await this.createDefaultAnonymizationFields(spaceId);
}
} catch (error) {
this.options.logger.error(
this.options.logger.warn(
`Error initializing AI assistant namespace level resources: ${error.message}`
);
throw error;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ import {
EsAnonymizationFieldsSchema,
UpdateAnonymizationFieldSchema,
} from '../../ai_assistant_data_clients/anonymization_fields/types';
import { UPGRADE_LICENSE_MESSAGE, hasAIAssistantLicense } from '../helpers';
import { performChecks } from '../helpers';

export interface BulkOperationError {
message: string;
Expand Down Expand Up @@ -158,22 +158,18 @@ export const bulkActionAnonymizationFieldsRoute = (
request.events.completed$.subscribe(() => abortController.abort());
try {
const ctx = await context.resolve(['core', 'elasticAssistant', 'licensing']);
const license = ctx.licensing.license;
if (!hasAIAssistantLicense(license)) {
return response.forbidden({
body: {
message: UPGRADE_LICENSE_MESSAGE,
},
});
}
// Perform license and authenticated user checks
const checkResponse = performChecks({
context: ctx,
request,
response,
});

const authenticatedUser = ctx.elasticAssistant.getCurrentUser();
if (authenticatedUser == null) {
return assistantResponse.error({
body: `Authenticated user not found`,
statusCode: 401,
});
if (!checkResponse.isSuccess) {
return checkResponse.response;
}
const authenticatedUser = checkResponse.currentUser;

const dataClient =
await ctx.elasticAssistant.getAIAssistantAnonymizationFieldsDataClient();

Expand All @@ -195,20 +191,20 @@ export const bulkActionAnonymizationFieldsRoute = (
}

const writer = await dataClient?.getWriter();
const changedAt = new Date().toISOString();
const createdAt = new Date().toISOString();
const {
errors,
docs_created: docsCreated,
docs_updated: docsUpdated,
docs_deleted: docsDeleted,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
} = await writer!.bulk({
documentsToCreate: body.create?.map((f) =>
transformToCreateScheme(authenticatedUser, changedAt, f)
documentsToCreate: body.create?.map((doc) =>
transformToCreateScheme(authenticatedUser, createdAt, doc)
),
documentsToDelete: body.delete?.ids,
documentsToUpdate: body.update?.map((f) =>
transformToUpdateScheme(authenticatedUser, changedAt, f)
documentsToUpdate: body.update?.map((doc) =>
transformToUpdateScheme(authenticatedUser, createdAt, doc)
),
getUpdateScript: (document: UpdateAnonymizationFieldSchema) =>
getUpdateScript({ anonymizationField: document, isPatch: true }),
Expand Down
Loading

0 comments on commit ec6faed

Please sign in to comment.