From ff331f2b6e8e17df573403b9812b7e0342417d1d Mon Sep 17 00:00:00 2001 From: Marius Iversen Date: Mon, 9 Dec 2024 13:25:08 +0100 Subject: [PATCH] [Rule Migration] Add RAG for prebuilt rules and new Retrievers (#202796) ## Summary Graph changes: ![image](https://github.com/user-attachments/assets/54ad563b-9023-4e46-a80c-73ba6b61cf70) This PR focuses on adding the functionality to retrieve currrently available prebuilt rules and create a new index with semantic_text mappings to allow the SIEM migration process to use it for RAG usecases. The below changes are some specific mentions that the PR changes: - Move the creation of the RAG indicies from `/create` to `/start`, also removes the `await` for `prepare` when `/start` is called. - Move all retrievers to a new `retriever` folder, together with a new `RuleMigrationsRetriever` class to encapsulate all the different retrievers at one place. - Adds timeout to integration and prebuilt rule bulk requests to ES because of the possible time it can take to generate initial embeddings. - Move some nodes from Translate Rule subgraph to the main agent graph, as semantic queries are used now for both translate and matching prebuilt. --- .../docs/siem_migration/img/agent_graph.png | Bin 42382 -> 35732 bytes .../siem_migration/draw_graphs_script.ts | 12 +- .../lib/siem_migrations/rules/api/create.ts | 2 +- .../lib/siem_migrations/rules/api/start.ts | 4 +- .../rules/data/rule_migrations_data_client.ts | 8 + ...ule_migrations_data_integrations_client.ts | 35 +++-- ...e_migrations_data_prebuilt_rules_client.ts | 137 ++++++++++++++++++ .../data/rule_migrations_data_service.test.ts | 13 +- .../data/rule_migrations_data_service.ts | 6 +- .../rules/data/rule_migrations_field_maps.ts | 9 ++ .../rules/task/agent/graph.test.ts | 12 +- .../siem_migrations/rules/task/agent/graph.ts | 29 ++-- .../create_semantic_query.ts | 39 +++++ .../nodes/create_semantic_query/index.ts | 8 + .../create_semantic_query}/prompts.ts | 9 +- .../match_prebuilt_rule.ts | 44 +++--- .../nodes/match_prebuilt_rule/prompts.ts | 2 +- .../nodes/process_query/index.ts | 0 .../nodes/process_query/process_query.ts | 10 +- .../nodes/process_query/prompts.ts | 2 +- .../siem_migrations/rules/task/agent/state.ts | 13 ++ .../agent/sub_graphs/translate_rule/graph.ts | 24 +-- .../retrieve_integrations.ts | 28 +--- .../nodes/translate_rule/translate_rule.ts | 4 - .../agent/sub_graphs/translate_rule/state.ts | 4 + .../agent/sub_graphs/translate_rule/types.ts | 8 +- .../siem_migrations/rules/task/agent/types.ts | 8 +- .../rules/task/retrievers/index.ts | 8 + .../integration_retriever.test.ts | 0 .../integration_retriever.ts | 0 .../retrievers/prebuilt_rules_retriever.ts | 29 ++++ .../retrievers/rule_migrations_retriever.ts | 23 +++ .../rule_resource_retriever.test.ts | 0 .../rule_resource_retriever.ts | 0 .../rules/task/rule_migrations_task_client.ts | 38 ++--- .../rules/task/util/prebuilt_rules.test.ts | 105 -------------- .../rules/task/util/prebuilt_rules.ts | 77 ---------- .../server/lib/siem_migrations/rules/types.ts | 9 ++ 38 files changed, 420 insertions(+), 339 deletions(-) create mode 100644 x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/rule_migrations_data_prebuilt_rules_client.ts create mode 100644 x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/nodes/create_semantic_query/create_semantic_query.ts create mode 100644 x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/nodes/create_semantic_query/index.ts rename x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/{sub_graphs/translate_rule/nodes/retrieve_integrations => nodes/create_semantic_query}/prompts.ts (83%) rename x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/{sub_graphs/translate_rule => }/nodes/process_query/index.ts (100%) rename x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/{sub_graphs/translate_rule => }/nodes/process_query/process_query.ts (81%) rename x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/{sub_graphs/translate_rule => }/nodes/process_query/prompts.ts (98%) create mode 100644 x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/retrievers/index.ts rename x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/{util => retrievers}/integration_retriever.test.ts (100%) rename x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/{util => retrievers}/integration_retriever.ts (100%) create mode 100644 x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/retrievers/prebuilt_rules_retriever.ts create mode 100644 x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/retrievers/rule_migrations_retriever.ts rename x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/{util => retrievers}/rule_resource_retriever.test.ts (100%) rename x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/{util => retrievers}/rule_resource_retriever.ts (100%) delete mode 100644 x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/util/prebuilt_rules.test.ts delete mode 100644 x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/util/prebuilt_rules.ts diff --git a/x-pack/plugins/security_solution/docs/siem_migration/img/agent_graph.png b/x-pack/plugins/security_solution/docs/siem_migration/img/agent_graph.png index ecccb602e20168b077eb3fef1278259c452d02ef..a1f9cca5a34a7b4a01535e799a3fd2b1c6aa7996 100644 GIT binary patch literal 35732 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 literal 42382 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 Promise; @@ -30,9 +28,7 @@ const mockLlm = new FakeLLM({ const inferenceClient = {} as InferenceClient; const connectorId = 'draw_graphs'; -const prebuiltRulesMap = {} as PrebuiltRulesMapByName; -const resourceRetriever = {} as RuleResourceRetriever; -const integrationRetriever = {} as IntegrationRetriever; +const ruleMigrationsRetriever = {} as RuleMigrationsRetriever; const createLlmInstance = () => { return mockLlm; @@ -43,9 +39,7 @@ async function getAgentGraph(logger: Logger): Promise { const graph = getRuleMigrationAgent({ model, inferenceClient, - prebuiltRulesMap, - resourceRetriever, - integrationRetriever, + ruleMigrationsRetriever, connectorId, logger, }); diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/create.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/create.ts index 7c94631be6b65..19669fa75cd3d 100644 --- a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/create.ts +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/create.ts @@ -50,7 +50,7 @@ export const registerSiemRuleMigrationsCreateRoute = ( migration_id: migrationId, original_rule: originalRule, })); - await ruleMigrationsClient.data.integrations.create(); + await ruleMigrationsClient.data.rules.create(ruleMigrations); return res.ok({ body: { migration_id: migrationId } }); diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/start.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/start.ts index e9369c0e8d19d..4e50d3d583c65 100644 --- a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/start.ts +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/start.ts @@ -6,15 +6,15 @@ */ import type { IKibanaResponse, Logger } from '@kbn/core/server'; -import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; import { APMTracer } from '@kbn/langchain/server/tracers/apm'; import { getLangSmithTracer } from '@kbn/langchain/server/tracers/langsmith'; +import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; +import { SIEM_RULE_MIGRATION_START_PATH } from '../../../../../common/siem_migrations/constants'; import { StartRuleMigrationRequestBody, StartRuleMigrationRequestParams, type StartRuleMigrationResponse, } from '../../../../../common/siem_migrations/model/api/rules/rule_migration.gen'; -import { SIEM_RULE_MIGRATION_START_PATH } from '../../../../../common/siem_migrations/constants'; import type { SecuritySolutionPluginRouter } from '../../../../types'; import { withLicense } from './util/with_license'; diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/rule_migrations_data_client.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/rule_migrations_data_client.ts index 8960edd0cce21..c06c889482360 100644 --- a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/rule_migrations_data_client.ts +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/rule_migrations_data_client.ts @@ -7,6 +7,7 @@ import type { ElasticsearchClient, Logger } from '@kbn/core/server'; import { RuleMigrationsDataIntegrationsClient } from './rule_migrations_data_integrations_client'; +import { RuleMigrationsDataPrebuiltRulesClient } from './rule_migrations_data_prebuilt_rules_client'; import { RuleMigrationsDataResourcesClient } from './rule_migrations_data_resources_client'; import { RuleMigrationsDataRulesClient } from './rule_migrations_data_rules_client'; import type { AdapterId } from './rule_migrations_data_service'; @@ -18,6 +19,7 @@ export class RuleMigrationsDataClient { public readonly rules: RuleMigrationsDataRulesClient; public readonly resources: RuleMigrationsDataResourcesClient; public readonly integrations: RuleMigrationsDataIntegrationsClient; + public readonly prebuiltRules: RuleMigrationsDataPrebuiltRulesClient; constructor( indexNameProviders: IndexNameProviders, @@ -43,5 +45,11 @@ export class RuleMigrationsDataClient { esClient, logger ); + this.prebuiltRules = new RuleMigrationsDataPrebuiltRulesClient( + indexNameProviders.prebuiltrules, + username, + esClient, + logger + ); } } diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/rule_migrations_data_integrations_client.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/rule_migrations_data_integrations_client.ts index 3fdf1d11de36c..fdb063836f9e4 100644 --- a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/rule_migrations_data_integrations_client.ts +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/rule_migrations_data_integrations_client.ts @@ -26,24 +26,27 @@ export class RuleMigrationsDataIntegrationsClient extends RuleMigrationsDataBase async create(): Promise { const index = await this.getIndexName(); await this.esClient - .bulk({ - refresh: 'wait_for', - operations: INTEGRATIONS.flatMap((integration) => [ - { update: { _index: index, _id: integration.id } }, - { - doc: { - title: integration.title, - description: integration.description, - data_streams: integration.data_streams, - elser_embedding: integration.elser_embedding, - '@timestamp': new Date().toISOString(), + .bulk( + { + refresh: 'wait_for', + operations: INTEGRATIONS.flatMap((integration) => [ + { update: { _index: index, _id: integration.id } }, + { + doc: { + title: integration.title, + description: integration.description, + data_streams: integration.data_streams, + elser_embedding: integration.elser_embedding, + '@timestamp': new Date().toISOString(), + }, + doc_as_upsert: true, }, - doc_as_upsert: true, - }, - ]), - }) + ]), + }, + { requestTimeout: 10 * 60 * 1000 } + ) .catch((error) => { - this.logger.error(`Error indexing integration details for ELSER: ${error.message}`); + this.logger.error(`Error preparing integrations for SIEM migration ${error.message}`); throw error; }); } diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/rule_migrations_data_prebuilt_rules_client.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/rule_migrations_data_prebuilt_rules_client.ts new file mode 100644 index 0000000000000..ccd158c347c77 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/rule_migrations_data_prebuilt_rules_client.ts @@ -0,0 +1,137 @@ +/* + * 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 type { RulesClient } from '@kbn/alerting-plugin/server'; +import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; +import { createPrebuiltRuleAssetsClient } from '../../../detection_engine/prebuilt_rules/logic/rule_assets/prebuilt_rule_assets_client'; +import { createPrebuiltRuleObjectsClient } from '../../../detection_engine/prebuilt_rules/logic/rule_objects/prebuilt_rule_objects_client'; +import { fetchRuleVersionsTriad } from '../../../detection_engine/prebuilt_rules/logic/rule_versions/fetch_rule_versions_triad'; +import type { RuleMigrationPrebuiltRule } from '../types'; +import { RuleMigrationsDataBaseClient } from './rule_migrations_data_base_client'; + +interface RetrievePrebuiltRulesParams { + soClient: SavedObjectsClientContract; + rulesClient: RulesClient; +} + +/* The minimum score required for a integration to be considered correct, might need to change this later */ +const MIN_SCORE = 40 as const; +/* The number of integrations the RAG will return, sorted by score */ +const RETURNED_RULES = 5 as const; + +/* BULK_MAX_SIZE defines the number to break down the bulk operations by. + * The 500 number was chosen as a reasonable number to avoid large payloads. It can be adjusted if needed. + */ +const BULK_MAX_SIZE = 500 as const; + +export class RuleMigrationsDataPrebuiltRulesClient extends RuleMigrationsDataBaseClient { + /** Indexes an array of integrations to be used with ELSER semantic search queries */ + async create({ soClient, rulesClient }: RetrievePrebuiltRulesParams): Promise { + const ruleAssetsClient = createPrebuiltRuleAssetsClient(soClient); + const ruleObjectsClient = createPrebuiltRuleObjectsClient(rulesClient); + + const ruleVersionsMap = await fetchRuleVersionsTriad({ + ruleAssetsClient, + ruleObjectsClient, + }); + + const filteredRules: RuleMigrationPrebuiltRule[] = []; + ruleVersionsMap.forEach((ruleVersions) => { + const rule = ruleVersions.target || ruleVersions.current; + if (rule) { + const mitreAttackIds = rule?.threat?.flatMap( + ({ technique }) => technique?.map(({ id }) => id) ?? [] + ); + + filteredRules.push({ + rule_id: rule.rule_id, + name: rule.name, + installedRuleId: ruleVersions.current?.id, + description: rule.description, + elser_embedding: `${rule.name} - ${rule.description}`, + ...(mitreAttackIds?.length && { mitre_attack_ids: mitreAttackIds }), + }); + } + }); + + const index = await this.getIndexName(); + const createdAt = new Date().toISOString(); + let prebuiltRuleSlice: RuleMigrationPrebuiltRule[]; + while ((prebuiltRuleSlice = filteredRules.splice(0, BULK_MAX_SIZE)).length) { + await this.esClient + .bulk( + { + refresh: 'wait_for', + operations: prebuiltRuleSlice.flatMap((prebuiltRule) => [ + { update: { _index: index, _id: prebuiltRule.rule_id } }, + { + doc: { + ...prebuiltRule, + '@timestamp': createdAt, + }, + doc_as_upsert: true, + }, + ]), + }, + { requestTimeout: 10 * 60 * 1000 } + ) + .catch((error) => { + this.logger.error(`Error preparing prebuilt rules for SIEM migration: ${error.message}`); + throw error; + }); + } + } + + /** Based on a LLM generated semantic string, returns the 5 best results with a score above 40 */ + async retrieveRules( + semanticString: string, + techniqueIds: string + ): Promise { + const index = await this.getIndexName(); + const query = { + bool: { + should: [ + { + semantic: { + query: semanticString, + field: 'elser_embedding', + boost: 1.5, + }, + }, + { + multi_match: { + query: semanticString, + fields: ['name^2', 'description'], + boost: 3, + }, + }, + { + multi_match: { + query: techniqueIds, + fields: ['mitre_attack_ids'], + boost: 2, + }, + }, + ], + }, + }; + const results = await this.esClient + .search({ + index, + query, + size: RETURNED_RULES, + min_score: MIN_SCORE, + }) + .then(this.processResponseHits.bind(this)) + .catch((error) => { + this.logger.error(`Error querying prebuilt rule details for ELSER: ${error.message}`); + throw error; + }); + + return results; + } +} diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/rule_migrations_data_service.test.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/rule_migrations_data_service.test.ts index f8cc0c3f1c076..e991ce2684f3e 100644 --- a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/rule_migrations_data_service.test.ts +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/rule_migrations_data_service.test.ts @@ -42,7 +42,7 @@ describe('SiemRuleMigrationsDataService', () => { describe('constructor', () => { it('should create IndexPatternAdapters', () => { new RuleMigrationsDataService(logger, kibanaVersion); - expect(MockedIndexPatternAdapter).toHaveBeenCalledTimes(3); + expect(MockedIndexPatternAdapter).toHaveBeenCalledTimes(4); }); it('should create component templates', () => { @@ -57,6 +57,9 @@ describe('SiemRuleMigrationsDataService', () => { expect(indexPatternAdapter.setComponentTemplate).toHaveBeenCalledWith( expect.objectContaining({ name: `${INDEX_PATTERN}-integrations` }) ); + expect(indexPatternAdapter.setComponentTemplate).toHaveBeenCalledWith( + expect.objectContaining({ name: `${INDEX_PATTERN}-prebuiltrules` }) + ); }); it('should create index templates', () => { @@ -71,6 +74,9 @@ describe('SiemRuleMigrationsDataService', () => { expect(indexPatternAdapter.setIndexTemplate).toHaveBeenCalledWith( expect.objectContaining({ name: `${INDEX_PATTERN}-integrations` }) ); + expect(indexPatternAdapter.setIndexTemplate).toHaveBeenCalledWith( + expect.objectContaining({ name: `${INDEX_PATTERN}-prebuiltrules` }) + ); }); }); @@ -102,6 +108,7 @@ describe('SiemRuleMigrationsDataService', () => { rulesIndexPatternAdapter, resourcesIndexPatternAdapter, integrationsIndexPatternAdapter, + prebuiltrulesIndexPatternAdapter, ] = MockedIndexPatternAdapter.mock.instances; (rulesIndexPatternAdapter.install as jest.Mock).mockResolvedValueOnce(undefined); @@ -111,6 +118,7 @@ describe('SiemRuleMigrationsDataService', () => { await mockIndexNameProviders.rules(); await mockIndexNameProviders.resources(); await mockIndexNameProviders.integrations(); + await mockIndexNameProviders.prebuiltrules(); expect(rulesIndexPatternAdapter.createIndex).toHaveBeenCalledWith('space1'); expect(rulesIndexPatternAdapter.getIndexName).toHaveBeenCalledWith('space1'); @@ -120,6 +128,9 @@ describe('SiemRuleMigrationsDataService', () => { expect(integrationsIndexPatternAdapter.createIndex).toHaveBeenCalledWith('space1'); expect(integrationsIndexPatternAdapter.getIndexName).toHaveBeenCalledWith('space1'); + + expect(prebuiltrulesIndexPatternAdapter.createIndex).toHaveBeenCalledWith('space1'); + expect(prebuiltrulesIndexPatternAdapter.getIndexName).toHaveBeenCalledWith('space1'); }); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/rule_migrations_data_service.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/rule_migrations_data_service.ts index ceff8e05f9f2f..5799e5ab84c07 100644 --- a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/rule_migrations_data_service.ts +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/rule_migrations_data_service.ts @@ -10,6 +10,7 @@ import type { IndexNameProvider, IndexNameProviders } from './rule_migrations_da import { RuleMigrationsDataClient } from './rule_migrations_data_client'; import { integrationsFieldMap, + prebuiltRulesFieldMap, ruleMigrationResourcesFieldMap, ruleMigrationsFieldMap, } from './rule_migrations_field_maps'; @@ -17,7 +18,7 @@ import { const TOTAL_FIELDS_LIMIT = 2500; export const INDEX_PATTERN = '.kibana-siem-rule-migrations'; -export type AdapterId = 'rules' | 'resources' | 'integrations'; +export type AdapterId = 'rules' | 'resources' | 'integrations' | 'prebuiltrules'; interface CreateClientParams { spaceId: string; @@ -33,6 +34,7 @@ export class RuleMigrationsDataService { rules: this.createAdapter({ id: 'rules', fieldMap: ruleMigrationsFieldMap }), resources: this.createAdapter({ id: 'resources', fieldMap: ruleMigrationResourcesFieldMap }), integrations: this.createAdapter({ id: 'integrations', fieldMap: integrationsFieldMap }), + prebuiltrules: this.createAdapter({ id: 'prebuiltrules', fieldMap: prebuiltRulesFieldMap }), }; } @@ -52,6 +54,7 @@ export class RuleMigrationsDataService { this.adapters.rules.install({ ...params, logger: this.logger }), this.adapters.resources.install({ ...params, logger: this.logger }), this.adapters.integrations.install({ ...params, logger: this.logger }), + this.adapters.prebuiltrules.install({ ...params, logger: this.logger }), ]); } @@ -60,6 +63,7 @@ export class RuleMigrationsDataService { rules: this.createIndexNameProvider('rules', spaceId), resources: this.createIndexNameProvider('resources', spaceId), integrations: this.createIndexNameProvider('integrations', spaceId), + prebuiltrules: this.createIndexNameProvider('prebuiltrules', spaceId), }; return new RuleMigrationsDataClient( diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/rule_migrations_field_maps.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/rule_migrations_field_maps.ts index f63953192844b..7aca804c12890 100644 --- a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/rule_migrations_field_maps.ts +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/data/rule_migrations_field_maps.ts @@ -62,3 +62,12 @@ export const integrationsFieldMap: FieldMap = { 'data_streams.index_pattern': { type: 'keyword', required: true }, elser_embeddings: { type: 'semantic_text', required: true }, }; + +export const prebuiltRulesFieldMap: FieldMap = { + '@timestamp': { type: 'date', required: true }, + name: { type: 'text', required: true }, + description: { type: 'text', required: true }, + elser_embedding: { type: 'semantic_text', required: true }, + rule_id: { type: 'keyword', required: true }, + mitre_attack_ids: { type: 'keyword', array: true, required: false }, +}; diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/graph.test.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/graph.test.ts index eece827726a33..ba43c369c6321 100644 --- a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/graph.test.ts +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/graph.test.ts @@ -12,9 +12,7 @@ import type { } from '@kbn/langchain/server/language_models'; import { loggerMock } from '@kbn/logging-mocks'; import { FakeLLM } from '@langchain/core/utils/testing'; -import type { IntegrationRetriever } from '../util/integration_retriever'; -import type { PrebuiltRulesMapByName } from '../util/prebuilt_rules'; -import type { RuleResourceRetriever } from '../util/rule_resource_retriever'; +import type { RuleMigrationsRetriever } from '../retrievers'; import { getRuleMigrationAgent } from './graph'; describe('getRuleMigrationAgent', () => { @@ -24,9 +22,7 @@ describe('getRuleMigrationAgent', () => { const inferenceClient = {} as InferenceClient; const connectorId = 'draw_graphs'; - const prebuiltRulesMap = {} as PrebuiltRulesMapByName; - const resourceRetriever = {} as RuleResourceRetriever; - const integrationRetriever = {} as IntegrationRetriever; + const ruleMigrationsRetriever = {} as RuleMigrationsRetriever; const logger = loggerMock.create(); it('Ensures that the graph compiles', async () => { @@ -34,9 +30,7 @@ describe('getRuleMigrationAgent', () => { await getRuleMigrationAgent({ model, inferenceClient, - prebuiltRulesMap, - resourceRetriever, - integrationRetriever, + ruleMigrationsRetriever, connectorId, logger, }); diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/graph.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/graph.ts index 4ce5e2d87a3fe..20b7cc6f1361d 100644 --- a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/graph.ts +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/graph.ts @@ -6,7 +6,10 @@ */ import { END, START, StateGraph } from '@langchain/langgraph'; +import { getCreateSemanticQueryNode } from './nodes/create_semantic_query'; import { getMatchPrebuiltRuleNode } from './nodes/match_prebuilt_rule'; +import { getProcessQueryNode } from './nodes/process_query'; + import { migrateRuleState } from './state'; import { getTranslateRuleGraph } from './sub_graphs/translate_rule'; import type { MigrateRuleGraphParams, MigrateRuleState } from './types'; @@ -14,29 +17,37 @@ import type { MigrateRuleGraphParams, MigrateRuleState } from './types'; export function getRuleMigrationAgent({ model, inferenceClient, - prebuiltRulesMap, - resourceRetriever, - integrationRetriever, + ruleMigrationsRetriever, connectorId, logger, }: MigrateRuleGraphParams) { - const matchPrebuiltRuleNode = getMatchPrebuiltRuleNode({ model, prebuiltRulesMap }); - const translationSubGraph = getTranslateRuleGraph({ + const matchPrebuiltRuleNode = getMatchPrebuiltRuleNode({ model, + ruleMigrationsRetriever, + }); + const translationSubGraph = getTranslateRuleGraph({ inferenceClient, - resourceRetriever, - integrationRetriever, + ruleMigrationsRetriever, connectorId, logger, }); + const createSemanticQueryNode = getCreateSemanticQueryNode({ model }); + const processQueryNode = getProcessQueryNode({ model, ruleMigrationsRetriever }); const siemMigrationAgentGraph = new StateGraph(migrateRuleState) // Nodes + .addNode('processQuery', processQueryNode) + .addNode('createSemanticQuery', createSemanticQueryNode) .addNode('matchPrebuiltRule', matchPrebuiltRuleNode) .addNode('translationSubGraph', translationSubGraph) // Edges - .addEdge(START, 'matchPrebuiltRule') - .addConditionalEdges('matchPrebuiltRule', matchedPrebuiltRuleConditional) + .addEdge(START, 'processQuery') + .addEdge('processQuery', 'createSemanticQuery') + .addEdge('createSemanticQuery', 'matchPrebuiltRule') + .addConditionalEdges('matchPrebuiltRule', matchedPrebuiltRuleConditional, [ + 'translationSubGraph', + END, + ]) .addEdge('translationSubGraph', END); const graph = siemMigrationAgentGraph.compile(); diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/nodes/create_semantic_query/create_semantic_query.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/nodes/create_semantic_query/create_semantic_query.ts new file mode 100644 index 0000000000000..446b96234711a --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/nodes/create_semantic_query/create_semantic_query.ts @@ -0,0 +1,39 @@ +/* + * 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 { JsonOutputParser } from '@langchain/core/output_parsers'; +import type { ChatModel } from '../../../util/actions_client_chat'; +import type { GraphNode } from '../../types'; +import { CREATE_SEMANTIC_QUERY_PROMPT } from './prompts'; + +interface GetCreateSemanticQueryNodeParams { + model: ChatModel; +} + +interface GetSemanticQueryResponse { + semantic_query: string; +} + +export const getCreateSemanticQueryNode = ({ + model, +}: GetCreateSemanticQueryNodeParams): GraphNode => { + const jsonParser = new JsonOutputParser(); + const semanticQueryChain = CREATE_SEMANTIC_QUERY_PROMPT.pipe(model).pipe(jsonParser); + return async (state) => { + const query = state.original_rule.query; + const integrationQuery = (await semanticQueryChain.invoke({ + title: state.original_rule.title, + description: state.original_rule.description, + query, + })) as GetSemanticQueryResponse; + if (!integrationQuery.semantic_query) { + return {}; + } + + return { semantic_query: integrationQuery.semantic_query }; + }; +}; diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/nodes/create_semantic_query/index.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/nodes/create_semantic_query/index.ts new file mode 100644 index 0000000000000..84bf247965a26 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/nodes/create_semantic_query/index.ts @@ -0,0 +1,8 @@ +/* + * 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. + */ + +export { getCreateSemanticQueryNode } from './create_semantic_query'; diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/retrieve_integrations/prompts.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/nodes/create_semantic_query/prompts.ts similarity index 83% rename from x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/retrieve_integrations/prompts.ts rename to x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/nodes/create_semantic_query/prompts.ts index 962de190acd02..54be39eb193f7 100644 --- a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/retrieve_integrations/prompts.ts +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/nodes/create_semantic_query/prompts.ts @@ -6,7 +6,6 @@ */ import { ChatPromptTemplate } from '@langchain/core/prompts'; - export const CREATE_SEMANTIC_QUERY_PROMPT = ChatPromptTemplate.fromMessages([ [ 'system', @@ -29,7 +28,7 @@ Go through the relevant title, description and data sources from the above query - The query should be short and concise. - Include keywords that are relevant to the use case. - Add related keywords you detected from the above query, like one or more vendor, product, cloud provider, OS platform etc. -- Always reply with a JSON object with the key "query" and the value as the semantic search query inside three backticks as shown in the below example. +- Always reply with a JSON object with the key "semantic_query" and the value as the semantic search query inside three backticks as shown in the below example. @@ -38,11 +37,11 @@ Title: Processes created by netsh Description: This search looks for processes launching netsh.exe to execute various commands via the netsh command-line utility. Netsh.exe is a command-line scripting utility that allows you to, either locally or remotely, display or modify the network configuration of a computer that is currently running. Netsh can be used as a persistence proxy technique to execute a helper .dll when netsh.exe is executed. In this search, we are looking for processes spawned by netsh.exe that are executing commands via the command line. Deprecated because we have another detection of the same type. Data Sources: -A: Please find the query keywords JSON object below: +A: Please find the semantic_query keywords JSON object below: \`\`\`json -{{"query": "windows host endpoint netsh.exe process creation command-line utility network configuration persistence proxy dll execution sysmon event id 1"}} +{{"semantic_query": "windows host endpoint netsh.exe process creation command-line utility network configuration persistence proxy dll execution sysmon event id 1"}} \`\`\` `, ], - ['ai', 'Please find the query keywords JSON object below:'], + ['ai', 'Please find the semantic_query keywords JSON object below:'], ]); diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/nodes/match_prebuilt_rule/match_prebuilt_rule.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/nodes/match_prebuilt_rule/match_prebuilt_rule.ts index 5900f45912599..ea403c5c4ffa7 100644 --- a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/nodes/match_prebuilt_rule/match_prebuilt_rule.ts +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/nodes/match_prebuilt_rule/match_prebuilt_rule.ts @@ -7,14 +7,14 @@ import { JsonOutputParser } from '@langchain/core/output_parsers'; import { SiemMigrationRuleTranslationResult } from '../../../../../../../../common/siem_migrations/constants'; +import type { RuleMigrationsRetriever } from '../../../retrievers'; import type { ChatModel } from '../../../util/actions_client_chat'; -import { filterPrebuiltRules, type PrebuiltRulesMapByName } from '../../../util/prebuilt_rules'; import type { GraphNode } from '../../types'; import { MATCH_PREBUILT_RULE_PROMPT } from './prompts'; interface GetMatchPrebuiltRuleNodeParams { model: ChatModel; - prebuiltRulesMap: PrebuiltRulesMapByName; + ruleMigrationsRetriever: RuleMigrationsRetriever; } interface GetMatchedRuleResponse { @@ -22,40 +22,42 @@ interface GetMatchedRuleResponse { } export const getMatchPrebuiltRuleNode = - ({ model, prebuiltRulesMap }: GetMatchPrebuiltRuleNodeParams): GraphNode => + ({ model, ruleMigrationsRetriever }: GetMatchPrebuiltRuleNodeParams): GraphNode => async (state) => { - const mitreAttackIds = state.original_rule.annotations?.mitre_attack; - if (!mitreAttackIds?.length) { - return {}; - } - - const filteredPrebuiltRulesMap = filterPrebuiltRules(prebuiltRulesMap, mitreAttackIds); - if (filteredPrebuiltRulesMap.size === 0) { - return {}; - } + const query = state.semantic_query; + const techniqueIds = state.original_rule.annotations?.mitre_attack || []; + const prebuiltRules = await ruleMigrationsRetriever.prebuiltRules.getRules( + query, + techniqueIds.join(',') + ); const outputParser = new JsonOutputParser(); const matchPrebuiltRule = MATCH_PREBUILT_RULE_PROMPT.pipe(model).pipe(outputParser); - const elasticSecurityRules = [...filteredPrebuiltRulesMap.keys()].join('\n'); + const elasticSecurityRules = prebuiltRules.map((rule) => { + return { + name: rule.name, + description: rule.description, + }; + }); + const response = (await matchPrebuiltRule.invoke({ - elasticSecurityRules, + rules: JSON.stringify(elasticSecurityRules, null, 2), ruleTitle: state.original_rule.title, })) as GetMatchedRuleResponse; if (response.match) { - const result = filteredPrebuiltRulesMap.get(response.match); - if (result != null) { + const matchedRule = prebuiltRules.find((r) => r.name === response.match); + if (matchedRule) { return { elastic_rule: { - title: result.rule.name, - description: result.rule.description, - prebuilt_rule_id: result.rule.rule_id, - id: result.installedRuleId, + title: matchedRule.name, + description: matchedRule.description, + id: matchedRule.installedRuleId, + prebuilt_rule_id: matchedRule.rule_id, }, translation_result: SiemMigrationRuleTranslationResult.FULL, }; } } - return {}; }; diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/nodes/match_prebuilt_rule/prompts.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/nodes/match_prebuilt_rule/prompts.ts index ab5d7383e27d4..60fea54250bb3 100644 --- a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/nodes/match_prebuilt_rule/prompts.ts +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/nodes/match_prebuilt_rule/prompts.ts @@ -15,7 +15,7 @@ Here are some context for you to reference for your task, read it carefully as y -{elasticSecurityRules} +{rules} `, diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/process_query/index.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/nodes/process_query/index.ts similarity index 100% rename from x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/process_query/index.ts rename to x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/nodes/process_query/index.ts diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/process_query/process_query.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/nodes/process_query/process_query.ts similarity index 81% rename from x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/process_query/process_query.ts rename to x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/nodes/process_query/process_query.ts index ae0e93ee0c4bb..27a9bca16390d 100644 --- a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/process_query/process_query.ts +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/nodes/process_query/process_query.ts @@ -7,23 +7,23 @@ import { StringOutputParser } from '@langchain/core/output_parsers'; import { isEmpty } from 'lodash/fp'; -import type { ChatModel } from '../../../../../util/actions_client_chat'; -import type { RuleResourceRetriever } from '../../../../../util/rule_resource_retriever'; +import type { RuleMigrationsRetriever } from '../../../retrievers'; +import type { ChatModel } from '../../../util/actions_client_chat'; import type { GraphNode } from '../../types'; import { REPLACE_QUERY_RESOURCE_PROMPT, getResourcesContext } from './prompts'; interface GetProcessQueryNodeParams { model: ChatModel; - resourceRetriever: RuleResourceRetriever; + ruleMigrationsRetriever: RuleMigrationsRetriever; } export const getProcessQueryNode = ({ model, - resourceRetriever, + ruleMigrationsRetriever, }: GetProcessQueryNodeParams): GraphNode => { return async (state) => { let query = state.original_rule.query; - const resources = await resourceRetriever.getResources(state.original_rule); + const resources = await ruleMigrationsRetriever.resources.getResources(state.original_rule); if (!isEmpty(resources)) { const replaceQueryParser = new StringOutputParser(); const replaceQueryResourcePrompt = diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/process_query/prompts.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/nodes/process_query/prompts.ts similarity index 98% rename from x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/process_query/prompts.ts rename to x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/nodes/process_query/prompts.ts index b4c6b0e74aaa9..be19ca8b0bf10 100644 --- a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/process_query/prompts.ts +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/nodes/process_query/prompts.ts @@ -6,7 +6,7 @@ */ import { ChatPromptTemplate } from '@langchain/core/prompts'; -import type { RuleMigrationResources } from '../../../../../util/rule_resource_retriever'; +import type { RuleMigrationResources } from '../../../retrievers/rule_resource_retriever'; interface ResourceContext { macros: string; diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/state.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/state.ts index 512406d6577de..edd33e2ec69b6 100644 --- a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/state.ts +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/state.ts @@ -13,6 +13,7 @@ import type { OriginalRule, RuleMigration, } from '../../../../../../common/siem_migrations/model/rule_migration.gen'; +import type { Integration } from '../../types'; export const migrateRuleState = Annotation.Root({ messages: Annotation({ @@ -23,6 +24,18 @@ export const migrateRuleState = Annotation.Root({ elastic_rule: Annotation({ reducer: (state, action) => ({ ...state, ...action }), }), + semantic_query: Annotation({ + reducer: (current, value) => value ?? current, + default: () => '', + }), + inline_query: Annotation({ + reducer: (current, value) => value ?? current, + default: () => '', + }), + integrations: Annotation({ + reducer: (current, value) => value ?? current, + default: () => [], + }), translation_result: Annotation(), comments: Annotation({ reducer: (current, value) => (value ? (current ?? []).concat(value) : current), diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/graph.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/graph.ts index 32f41e54619be..267a5bb0dd520 100644 --- a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/graph.ts +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/graph.ts @@ -9,7 +9,6 @@ import { END, START, StateGraph } from '@langchain/langgraph'; import { isEmpty } from 'lodash/fp'; import { SiemMigrationRuleTranslationResult } from '../../../../../../../../common/siem_migrations/constants'; import { getFixQueryErrorsNode } from './nodes/fix_query_errors'; -import { getProcessQueryNode } from './nodes/process_query'; import { getRetrieveIntegrationsNode } from './nodes/retrieve_integrations'; import { getTranslateRuleNode } from './nodes/translate_rule'; import { getValidationNode } from './nodes/validation'; @@ -20,45 +19,32 @@ import type { TranslateRuleGraphParams, TranslateRuleState } from './types'; const MAX_VALIDATION_ITERATIONS = 3; export function getTranslateRuleGraph({ - model, inferenceClient, - resourceRetriever, - integrationRetriever, connectorId, + ruleMigrationsRetriever, logger, }: TranslateRuleGraphParams) { const translateRuleNode = getTranslateRuleNode({ - model, inferenceClient, - resourceRetriever, connectorId, logger, }); - const processQueryNode = getProcessQueryNode({ - model, - resourceRetriever, - }); - const retrieveIntegrationsNode = getRetrieveIntegrationsNode({ - model, - integrationRetriever, - }); const validationNode = getValidationNode({ logger }); const fixQueryErrorsNode = getFixQueryErrorsNode({ inferenceClient, connectorId, logger }); + const retrieveIntegrationsNode = getRetrieveIntegrationsNode({ ruleMigrationsRetriever }); const translateRuleGraph = new StateGraph(translateRuleState) // Nodes - .addNode('processQuery', processQueryNode) - .addNode('retrieveIntegrations', retrieveIntegrationsNode) .addNode('translateRule', translateRuleNode) .addNode('validation', validationNode) .addNode('fixQueryErrors', fixQueryErrorsNode) + .addNode('retrieveIntegrations', retrieveIntegrationsNode) // Edges - .addEdge(START, 'processQuery') - .addEdge('processQuery', 'retrieveIntegrations') + .addEdge(START, 'retrieveIntegrations') .addEdge('retrieveIntegrations', 'translateRule') .addEdge('translateRule', 'validation') .addEdge('fixQueryErrors', 'validation') - .addConditionalEdges('validation', validationRouter); + .addConditionalEdges('validation', validationRouter, ['fixQueryErrors', END]); const graph = translateRuleGraph.compile(); graph.name = 'Translate Rule Graph'; diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/retrieve_integrations/retrieve_integrations.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/retrieve_integrations/retrieve_integrations.ts index 18577532fdf66..fa5b761806b5d 100644 --- a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/retrieve_integrations/retrieve_integrations.ts +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/retrieve_integrations/retrieve_integrations.ts @@ -5,38 +5,20 @@ * 2.0. */ -import { JsonOutputParser } from '@langchain/core/output_parsers'; -import type { ChatModel } from '../../../../../util/actions_client_chat'; -import type { IntegrationRetriever } from '../../../../../util/integration_retriever'; +import type { RuleMigrationsRetriever } from '../../../../../retrievers'; import type { GraphNode } from '../../types'; -import { CREATE_SEMANTIC_QUERY_PROMPT } from './prompts'; interface GetRetrieveIntegrationsNodeParams { - model: ChatModel; - integrationRetriever: IntegrationRetriever; -} - -interface GetSemanticQueryResponse { - query: string; + ruleMigrationsRetriever: RuleMigrationsRetriever; } export const getRetrieveIntegrationsNode = ({ - model, - integrationRetriever, + ruleMigrationsRetriever, }: GetRetrieveIntegrationsNodeParams): GraphNode => { - const jsonParser = new JsonOutputParser(); - const semanticQueryChain = CREATE_SEMANTIC_QUERY_PROMPT.pipe(model).pipe(jsonParser); - return async (state) => { - const query = state.inline_query; - - const integrationQuery = (await semanticQueryChain.invoke({ - title: state.original_rule.title, - description: state.original_rule.description, - query, - })) as GetSemanticQueryResponse; + const query = state.semantic_query; - const integrations = await integrationRetriever.getIntegrations(integrationQuery.query); + const integrations = await ruleMigrationsRetriever.integrations.getIntegrations(query); return { integrations, }; diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/translate_rule/translate_rule.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/translate_rule/translate_rule.ts index 6ba5edee11b22..85f5e7279d2b9 100644 --- a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/translate_rule/translate_rule.ts +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/translate_rule/translate_rule.ts @@ -8,17 +8,13 @@ import type { Logger } from '@kbn/core/server'; import type { InferenceClient } from '@kbn/inference-plugin/server'; import { SiemMigrationRuleTranslationResult } from '../../../../../../../../../../common/siem_migrations/constants'; -import type { ChatModel } from '../../../../../util/actions_client_chat'; import { getEsqlKnowledgeBase } from '../../../../../util/esql_knowledge_base_caller'; -import type { RuleResourceRetriever } from '../../../../../util/rule_resource_retriever'; import type { GraphNode } from '../../types'; import { SIEM_RULE_MIGRATION_CIM_ECS_MAP } from './cim_ecs_map'; import { ESQL_TRANSLATION_PROMPT } from './prompts'; interface GetTranslateRuleNodeParams { - model: ChatModel; inferenceClient: InferenceClient; - resourceRetriever: RuleResourceRetriever; connectorId: string; logger: Logger; } diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/state.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/state.ts index 391d7a54f9ea8..ac8799cb09d74 100644 --- a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/state.ts +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/state.ts @@ -30,6 +30,10 @@ export const translateRuleState = Annotation.Root({ reducer: (current, value) => value ?? current, default: () => '', }), + semantic_query: Annotation({ + reducer: (current, value) => value ?? current, + default: () => '', + }), elastic_rule: Annotation({ reducer: (state, action) => ({ ...state, ...action }), default: () => ({} as ElasticRule), diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/types.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/types.ts index 44a5750812be0..eddc415f23392 100644 --- a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/types.ts +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/types.ts @@ -7,9 +7,7 @@ import type { Logger } from '@kbn/core/server'; import type { InferenceClient } from '@kbn/inference-plugin/server'; -import type { ChatModel } from '../../../util/actions_client_chat'; -import type { IntegrationRetriever } from '../../../util/integration_retriever'; -import type { RuleResourceRetriever } from '../../../util/rule_resource_retriever'; +import type { RuleMigrationsRetriever } from '../../../retrievers'; import type { translateRuleState } from './state'; export type TranslateRuleState = typeof translateRuleState.State; @@ -17,10 +15,8 @@ export type GraphNode = (state: TranslateRuleState) => Promise { + return this.prebuiltRulesRetriever(semanticString, techniqueIds); + } + + private prebuiltRulesRetriever = async ( + semanticString: string, + techniqueIds: string + ): Promise => { + const rules = await this.dataClient.prebuiltRules.retrieveRules(semanticString, techniqueIds); + + return rules; + }; +} diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/retrievers/rule_migrations_retriever.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/retrievers/rule_migrations_retriever.ts new file mode 100644 index 0000000000000..22c884fa4043b --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/retrievers/rule_migrations_retriever.ts @@ -0,0 +1,23 @@ +/* + * 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 type { RuleMigrationsDataClient } from '../../data/rule_migrations_data_client'; +import { IntegrationRetriever } from './integration_retriever'; +import { PrebuiltRulesRetriever } from './prebuilt_rules_retriever'; +import { RuleResourceRetriever } from './rule_resource_retriever'; + +export class RuleMigrationsRetriever { + public readonly resources: RuleResourceRetriever; + public readonly integrations: IntegrationRetriever; + public readonly prebuiltRules: PrebuiltRulesRetriever; + + constructor(dataClient: RuleMigrationsDataClient, migrationId: string) { + this.resources = new RuleResourceRetriever(migrationId, dataClient); + this.integrations = new IntegrationRetriever(dataClient); + this.prebuiltRules = new PrebuiltRulesRetriever(dataClient); + } +} diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/util/rule_resource_retriever.test.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/retrievers/rule_resource_retriever.test.ts similarity index 100% rename from x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/util/rule_resource_retriever.test.ts rename to x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/retrievers/rule_resource_retriever.test.ts diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/util/rule_resource_retriever.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/retrievers/rule_resource_retriever.ts similarity index 100% rename from x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/util/rule_resource_retriever.ts rename to x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/retrievers/rule_resource_retriever.ts diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/rule_migrations_task_client.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/rule_migrations_task_client.ts index a6ea5c9040e16..6dbee5c64ee47 100644 --- a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/rule_migrations_task_client.ts +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/rule_migrations_task_client.ts @@ -9,14 +9,15 @@ import type { AuthenticatedUser, Logger } from '@kbn/core/server'; import { AbortError, abortSignalToPromise } from '@kbn/kibana-utils-plugin/server'; import type { RunnableConfig } from '@langchain/core/runnables'; import { - SiemMigrationTaskStatus, SiemMigrationStatus, + SiemMigrationTaskStatus, } from '../../../../../common/siem_migrations/constants'; import type { RuleMigrationTaskStats } from '../../../../../common/siem_migrations/model/rule_migration.gen'; import type { RuleMigrationsDataClient } from '../data/rule_migrations_data_client'; import type { RuleMigrationDataStats } from '../data/rule_migrations_data_rules_client'; import { getRuleMigrationAgent } from './agent'; import type { MigrateRuleState } from './agent/types'; +import { RuleMigrationsRetriever } from './retrievers'; import type { MigrationAgent, RuleMigrationTaskPrepareParams, @@ -26,9 +27,6 @@ import type { RuleMigrationTaskStopResult, } from './types'; import { ActionsClientChat } from './util/actions_client_chat'; -import { IntegrationRetriever } from './util/integration_retriever'; -import { retrievePrebuiltRulesMap } from './util/prebuilt_rules'; -import { RuleResourceRetriever } from './util/rule_resource_retriever'; const ITERATION_BATCH_SIZE = 50 as const; const ITERATION_SLEEP_SECONDS = 10 as const; @@ -67,14 +65,12 @@ export class RuleMigrationsTaskClient { const abortController = new AbortController(); - // Await the preparation to make sure the agent is created properly so the task can run - const agent = await this.prepare({ ...params, abortController }); - - // not awaiting the `run` promise to execute the task in the background - this.run({ ...params, agent, abortController }).catch((err) => { - // All errors in the `run` method are already catch, this should never happen, but just in case - this.logger.error(`Unexpected error running the migration ID:${migrationId}`, err); - }); + // Retrieve agent from prepare and pass it to run right after without awaiting but using .then + this.prepare({ ...params, abortController }) + .then((agent) => this.run({ ...params, agent, abortController })) + .catch((error) => { + this.logger.error(`Error starting migration ID:${migrationId} with error:${error}`, error); + }); return { exists: true, started: true }; } @@ -88,9 +84,17 @@ export class RuleMigrationsTaskClient { soClient, abortController, }: RuleMigrationTaskPrepareParams): Promise { - const prebuiltRulesMap = await retrievePrebuiltRulesMap({ soClient, rulesClient }); - const resourceRetriever = new RuleResourceRetriever(migrationId, this.data); - const integrationRetriever = new IntegrationRetriever(this.data); + await Promise.all([ + // Populates the indices used for RAG searches on prebuilt rules and integrations. + await this.data.prebuiltRules.create({ rulesClient, soClient }), + // Will use Fleet API client for integration retrieval as an argument once feature is available + await this.data.integrations.create(), + ]).catch((error) => { + this.logger.error(`Error preparing RAG indices for migration ID:${migrationId}`, error); + throw error; + }); + + const ruleMigrationsRetriever = new RuleMigrationsRetriever(this.data, migrationId); const actionsClientChat = new ActionsClientChat(connectorId, actionsClient, this.logger); const model = await actionsClientChat.createModel({ @@ -102,9 +106,7 @@ export class RuleMigrationsTaskClient { connectorId, model, inferenceClient, - prebuiltRulesMap, - resourceRetriever, - integrationRetriever, + ruleMigrationsRetriever, logger: this.logger, }); return agent; diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/util/prebuilt_rules.test.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/util/prebuilt_rules.test.ts deleted file mode 100644 index 55256d0ad0fdd..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/util/prebuilt_rules.test.ts +++ /dev/null @@ -1,105 +0,0 @@ -/* - * 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 { savedObjectsClientMock } from '@kbn/core-saved-objects-api-server-mocks'; -import type { PrebuiltRulesMapByName } from './prebuilt_rules'; -import { filterPrebuiltRules, retrievePrebuiltRulesMap } from './prebuilt_rules'; -import { rulesClientMock } from '@kbn/alerting-plugin/server/mocks'; - -jest.mock( - '../../../../detection_engine/prebuilt_rules/logic/rule_objects/prebuilt_rule_objects_client', - () => ({ createPrebuiltRuleObjectsClient: jest.fn() }) -); -jest.mock( - '../../../../detection_engine/prebuilt_rules/logic/rule_assets/prebuilt_rule_assets_client', - () => ({ createPrebuiltRuleAssetsClient: jest.fn() }) -); - -const mitreAttackIds = 'T1234'; -const rule1 = { - name: 'rule one', - id: 'rule1', - threat: [ - { - framework: 'MITRE ATT&CK', - technique: [{ id: mitreAttackIds, name: 'tactic one' }], - }, - ], -}; -const rule2 = { - name: 'rule two', - id: 'rule2', -}; - -const defaultRuleVersionsTriad = new Map([ - ['rule1', { target: rule1 }], - ['rule2', { target: rule2, current: rule2 }], -]); -const mockFetchRuleVersionsTriad = jest.fn().mockResolvedValue(defaultRuleVersionsTriad); -jest.mock( - '../../../../detection_engine/prebuilt_rules/logic/rule_versions/fetch_rule_versions_triad', - () => ({ - fetchRuleVersionsTriad: () => mockFetchRuleVersionsTriad(), - }) -); - -const defaultParams = { - soClient: savedObjectsClientMock.create(), - rulesClient: rulesClientMock.create(), -}; - -describe('retrievePrebuiltRulesMap', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - describe('when prebuilt rule is installed', () => { - it('should return isInstalled flag', async () => { - const prebuiltRulesMap = await retrievePrebuiltRulesMap(defaultParams); - expect(prebuiltRulesMap.size).toBe(2); - expect(prebuiltRulesMap.get('rule one')).toEqual( - expect.objectContaining({ installedRuleId: undefined }) - ); - expect(prebuiltRulesMap.get('rule two')).toEqual( - expect.objectContaining({ installedRuleId: rule2.id }) - ); - }); - }); -}); - -describe('filterPrebuiltRules', () => { - let prebuiltRulesMap: PrebuiltRulesMapByName; - - beforeEach(async () => { - prebuiltRulesMap = await retrievePrebuiltRulesMap(defaultParams); - jest.clearAllMocks(); - }); - - describe('when splunk rule contains empty mitreAttackIds', () => { - it('should return empty rules map', async () => { - const filteredPrebuiltRules = filterPrebuiltRules(prebuiltRulesMap, []); - expect(filteredPrebuiltRules.size).toBe(0); - }); - }); - - describe('when splunk rule does not match mitreAttackIds', () => { - it('should return empty rules map', async () => { - const filteredPrebuiltRules = filterPrebuiltRules(prebuiltRulesMap, [`${mitreAttackIds}_2`]); - expect(filteredPrebuiltRules.size).toBe(0); - }); - }); - - describe('when splunk rule contains matching mitreAttackIds', () => { - it('should return the filtered rules map', async () => { - const filteredPrebuiltRules = filterPrebuiltRules(prebuiltRulesMap, [mitreAttackIds]); - expect(filteredPrebuiltRules.size).toBe(1); - expect(filteredPrebuiltRules.get('rule one')).toEqual( - expect.objectContaining({ rule: rule1 }) - ); - }); - }); -}); diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/util/prebuilt_rules.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/util/prebuilt_rules.ts deleted file mode 100644 index ade6632aaa5b5..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/util/prebuilt_rules.ts +++ /dev/null @@ -1,77 +0,0 @@ -/* - * 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 type { RulesClient } from '@kbn/alerting-plugin/server'; -import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; -import type { PrebuiltRuleAsset } from '../../../../detection_engine/prebuilt_rules'; -import { fetchRuleVersionsTriad } from '../../../../detection_engine/prebuilt_rules/logic/rule_versions/fetch_rule_versions_triad'; -import { createPrebuiltRuleObjectsClient } from '../../../../detection_engine/prebuilt_rules/logic/rule_objects/prebuilt_rule_objects_client'; -import { createPrebuiltRuleAssetsClient } from '../../../../detection_engine/prebuilt_rules/logic/rule_assets/prebuilt_rule_assets_client'; - -export interface PrebuiltRuleMapped { - rule: PrebuiltRuleAsset; - installedRuleId?: string; -} - -export type PrebuiltRulesMapByName = Map; - -interface RetrievePrebuiltRulesParams { - soClient: SavedObjectsClientContract; - rulesClient: RulesClient; -} - -export const retrievePrebuiltRulesMap = async ({ - soClient, - rulesClient, -}: RetrievePrebuiltRulesParams): Promise => { - const ruleAssetsClient = createPrebuiltRuleAssetsClient(soClient); - const ruleObjectsClient = createPrebuiltRuleObjectsClient(rulesClient); - - const prebuiltRulesMap = await fetchRuleVersionsTriad({ - ruleAssetsClient, - ruleObjectsClient, - }); - const prebuiltRulesByName: PrebuiltRulesMapByName = new Map(); - prebuiltRulesMap.forEach((ruleVersions) => { - const rule = ruleVersions.target || ruleVersions.current; - if (rule) { - prebuiltRulesByName.set(rule.name, { - rule, - installedRuleId: ruleVersions.current?.id, - }); - } - }); - return prebuiltRulesByName; -}; - -export const filterPrebuiltRules = ( - prebuiltRulesByName: PrebuiltRulesMapByName, - mitreAttackIds: string[] -) => { - const filteredPrebuiltRulesByName = new Map(); - if (mitreAttackIds?.length) { - // If this rule has MITRE ATT&CK IDs, remove unrelated prebuilt rules - prebuiltRulesByName.forEach(({ rule }, ruleName) => { - const mitreAttackThreat = rule.threat?.filter( - ({ framework }) => framework === 'MITRE ATT&CK' - ); - if (!mitreAttackThreat) { - // If this rule has no MITRE ATT&CK reference we skip it - return; - } - - const sameTechnique = mitreAttackThreat.find((threat) => - threat.technique?.some(({ id }) => mitreAttackIds?.includes(id)) - ); - - if (sameTechnique) { - filteredPrebuiltRulesByName.set(ruleName, prebuiltRulesByName.get(ruleName)); - } - }); - } - return filteredPrebuiltRulesByName; -}; diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/types.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/types.ts index f8a0f0b3b25a7..f13a407ee2500 100644 --- a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/types.ts +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/types.ts @@ -22,3 +22,12 @@ export interface Integration { data_streams: Array<{ dataset: string; title: string; index_pattern: string }>; elser_embedding: string; } + +export interface RuleMigrationPrebuiltRule { + rule_id: string; + installedRuleId?: string; + name: string; + description: string; + elser_embedding: string; + mitre_attack_ids?: string[]; +}