From 0a7262d0fc213148fd7e80d3dc65f79c7eeae244 Mon Sep 17 00:00:00 2001 From: Marius Iversen Date: Thu, 12 Dec 2024 19:32:04 +0100 Subject: [PATCH] [Rule Migration] Improve rule translation prompts and processes (#204021) ## Summary This PR performs multiple changes that all focuses on improving the quality of the results returned when we translate rules that do not match with a prebuilt rule and both with/without related integrations. Changes include: - Add a filter_index_patterns node, to always ensure `logs-*` is removed with our `[indexPattern:logs-*]` value, which is similar to how we detect missing lookups and macros. - Split `translate_rule` into another `ecs_mapping` node, trying to ensure translation focuses on changing SPL to ESQL without any focus on actual field names, while the other node focuses only on the ESQL query and changing field names. - The summary now added in the comments have 1 for the translation and one for the ECS mapping. - Add default rule batch size `15` with PR comment/question. - Ensure we only return one integration related rather than an array for now, to make ESQL more focused on one related integration. - New prompt to filter out one or more integrations from the returned RAG; similar to how its done for rules RAG results already. --- .../model/rule_migration.gen.ts | 4 +- .../model/rule_migration.schema.yaml | 8 +- .../docs/siem_migration/img/agent_graph.png | Bin 35732 -> 45995 bytes .../rules/data/rule_migrations_field_maps.ts | 2 +- .../siem_migrations/rules/task/agent/graph.ts | 20 +- .../nodes/create_semantic_query/prompts.ts | 1 + .../match_prebuilt_rule.ts | 36 +++- .../nodes/match_prebuilt_rule/prompts.ts | 17 +- .../siem_migrations/rules/task/agent/state.ts | 5 - .../agent/sub_graphs/translate_rule/graph.ts | 22 ++- .../nodes/ecs_mapping/cim_ecs_map.ts | 181 ++++++++++++++++++ .../nodes/ecs_mapping/ecs_mapping.ts | 66 +++++++ .../translate_rule/nodes/ecs_mapping/index.ts | 7 + .../nodes/ecs_mapping/prompts.ts | 46 +++++ .../filter_index_patterns.ts | 40 ++++ .../nodes/filter_index_patterns/index.ts | 7 + .../nodes/retrieve_integrations/prompts.ts | 49 +++++ .../retrieve_integrations.ts | 39 +++- .../nodes/translate_rule/prompts.ts | 61 +++--- .../nodes/translate_rule/translate_rule.ts | 26 +-- .../agent/sub_graphs/translate_rule/state.ts | 8 +- .../agent/sub_graphs/translate_rule/types.ts | 2 + .../rules/task/rule_migrations_task_client.ts | 2 +- 23 files changed, 560 insertions(+), 89 deletions(-) create mode 100644 x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/ecs_mapping/cim_ecs_map.ts create mode 100644 x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/ecs_mapping/ecs_mapping.ts create mode 100644 x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/ecs_mapping/index.ts create mode 100644 x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/ecs_mapping/prompts.ts create mode 100644 x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/filter_index_patterns/filter_index_patterns.ts create mode 100644 x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/filter_index_patterns/index.ts create mode 100644 x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/retrieve_integrations/prompts.ts diff --git a/x-pack/plugins/security_solution/common/siem_migrations/model/rule_migration.gen.ts b/x-pack/plugins/security_solution/common/siem_migrations/model/rule_migration.gen.ts index 064df05c5ad41..d9c33ebbdf704 100644 --- a/x-pack/plugins/security_solution/common/siem_migrations/model/rule_migration.gen.ts +++ b/x-pack/plugins/security_solution/common/siem_migrations/model/rule_migration.gen.ts @@ -103,9 +103,9 @@ export const ElasticRule = z.object({ */ prebuilt_rule_id: NonEmptyString.optional(), /** - * The Elastic integration IDs related to the rule. + * The Elastic integration ID found to be most relevant to the splunk rule. */ - integration_ids: z.array(z.string()).optional(), + integration_id: z.string().optional(), /** * The Elastic rule id installed as a result. */ diff --git a/x-pack/plugins/security_solution/common/siem_migrations/model/rule_migration.schema.yaml b/x-pack/plugins/security_solution/common/siem_migrations/model/rule_migration.schema.yaml index 3171a62298f15..6fce9f0d51f5d 100644 --- a/x-pack/plugins/security_solution/common/siem_migrations/model/rule_migration.schema.yaml +++ b/x-pack/plugins/security_solution/common/siem_migrations/model/rule_migration.schema.yaml @@ -83,11 +83,9 @@ components: prebuilt_rule_id: description: The Elastic prebuilt rule id matched. $ref: '../../../common/api/model/primitives.schema.yaml#/components/schemas/NonEmptyString' - integration_ids: - type: array - items: - type: string - description: The Elastic integration IDs related to the rule. + integration_id: + type: string + description: The Elastic integration ID found to be most relevant to the splunk rule. id: description: The Elastic rule id installed as a result. $ref: '../../../common/api/model/primitives.schema.yaml#/components/schemas/NonEmptyString' 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 a1f9cca5a34a7b4a01535e799a3fd2b1c6aa7996..74623e69af0c896fabf0affc1604a60a096710ad 100644 GIT binary patch literal 45995 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 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 952663c36123c..c03b7ef52e784 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 @@ -27,7 +27,7 @@ export const ruleMigrationsFieldMap: FieldMap { if (state.elastic_rule?.prebuilt_rule_id) { return END; } - return 'translationSubGraph'; + if (state.translation_result === SiemMigrationRuleTranslationResult.UNTRANSLATABLE) { + return END; + } + return 'processQuery'; }; diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/nodes/create_semantic_query/prompts.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/nodes/create_semantic_query/prompts.ts index 54be39eb193f7..6f1e39f938692 100644 --- a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/nodes/create_semantic_query/prompts.ts +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/nodes/create_semantic_query/prompts.ts @@ -29,6 +29,7 @@ Go through the relevant title, description and data sources from the above query - 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 "semantic_query" and the value as the semantic search query inside three backticks as shown in the below example. +- If the related query focuses on Endpoint datamodel, make sure that "endpoint", "security" keywords are included. 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 ea403c5c4ffa7..e4b2162249cae 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 @@ -5,6 +5,7 @@ * 2.0. */ +import type { Logger } from '@kbn/core/server'; import { JsonOutputParser } from '@langchain/core/output_parsers'; import { SiemMigrationRuleTranslationResult } from '../../../../../../../../common/siem_migrations/constants'; import type { RuleMigrationsRetriever } from '../../../retrievers'; @@ -14,6 +15,7 @@ import { MATCH_PREBUILT_RULE_PROMPT } from './prompts'; interface GetMatchPrebuiltRuleNodeParams { model: ChatModel; + logger: Logger; ruleMigrationsRetriever: RuleMigrationsRetriever; } @@ -21,9 +23,12 @@ interface GetMatchedRuleResponse { match: string; } -export const getMatchPrebuiltRuleNode = - ({ model, ruleMigrationsRetriever }: GetMatchPrebuiltRuleNodeParams): GraphNode => - async (state) => { +export const getMatchPrebuiltRuleNode = ({ + model, + ruleMigrationsRetriever, + logger, +}: GetMatchPrebuiltRuleNodeParams): GraphNode => { + return async (state) => { const query = state.semantic_query; const techniqueIds = state.original_rule.annotations?.mitre_attack || []; const prebuiltRules = await ruleMigrationsRetriever.prebuiltRules.getRules( @@ -32,7 +37,7 @@ export const getMatchPrebuiltRuleNode = ); const outputParser = new JsonOutputParser(); - const matchPrebuiltRule = MATCH_PREBUILT_RULE_PROMPT.pipe(model).pipe(outputParser); + const mostRelevantRule = MATCH_PREBUILT_RULE_PROMPT.pipe(model).pipe(outputParser); const elasticSecurityRules = prebuiltRules.map((rule) => { return { @@ -41,9 +46,17 @@ export const getMatchPrebuiltRuleNode = }; }); - const response = (await matchPrebuiltRule.invoke({ + const splunkRule = { + title: state.original_rule.title, + description: state.original_rule.description, + }; + + /* + * Takes the most relevant rule from the array of rule(s) returned by the semantic query, returns either the most relevant or none. + */ + const response = (await mostRelevantRule.invoke({ rules: JSON.stringify(elasticSecurityRules, null, 2), - ruleTitle: state.original_rule.title, + splunk_rule: JSON.stringify(splunkRule, null, 2), })) as GetMatchedRuleResponse; if (response.match) { const matchedRule = prebuiltRules.find((r) => r.name === response.match); @@ -59,5 +72,16 @@ export const getMatchPrebuiltRuleNode = }; } } + const lookupTypes = ['inputlookup', 'outputlookup']; + if ( + state.original_rule?.query && + lookupTypes.some((type) => state.original_rule.query.includes(type)) + ) { + logger.debug( + `Rule: ${state.original_rule?.title} did not match any prebuilt rule, but contains inputlookup, dropping` + ); + return { translation_result: SiemMigrationRuleTranslationResult.UNTRANSLATABLE }; + } 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 60fea54250bb3..12fb7ec70febf 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 @@ -23,21 +23,22 @@ Here are some context for you to reference for your task, read it carefully as y [ 'human', `See the below description of the relevant splunk rule and try to match it with any of the elastic detection rules with similar names. - -{ruleTitle} - + +{splunk_rule} + - Always reply with a JSON object with the key "match" and the value being the most relevant matched elastic detection rule name. Do not reply with anything else. - Only reply with exact matches, if you are unsure or do not find a very confident match, always reply with an empty string value in the match key, do not guess or reply with anything else. -- If there is one Elastic rule in the list that covers the same threat, set the name of the matching rule as a value of the match key. Do not reply with anything else. -- If there are multiple rules in the list that cover the same threat, answer with the most specific of them, for example: "Linux User Account Creation" is more specific than "User Account Creation". +- If there is one Elastic rule in the list that covers the same usecase, set the name of the matching rule as a value of the match key. Do not reply with anything else. +- If there are multiple rules in the list that cover the same usecase, answer with the most specific of them, for example: "Linux User Account Creation" is more specific than "User Account Creation". -U: -Linux Auditd Add User Account Type - +U: +Title: Linux Auditd Add User Account Type +Description: The following analytic detects the suspicious add user account type. + A: Please find the match JSON object below: \`\`\`json {{"match": "Linux User Account Creation"}} 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 edd33e2ec69b6..a9047c9dc5439 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,7 +13,6 @@ import type { OriginalRule, RuleMigration, } from '../../../../../../common/siem_migrations/model/rule_migration.gen'; -import type { Integration } from '../../types'; export const migrateRuleState = Annotation.Root({ messages: Annotation({ @@ -32,10 +31,6 @@ export const migrateRuleState = Annotation.Root({ 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 267a5bb0dd520..463de671552c1 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 @@ -8,6 +8,8 @@ import { END, START, StateGraph } from '@langchain/langgraph'; import { isEmpty } from 'lodash/fp'; import { SiemMigrationRuleTranslationResult } from '../../../../../../../../common/siem_migrations/constants'; +import { getEcsMappingNode } from './nodes/ecs_mapping'; +import { getFilterIndexPatternsNode } from './nodes/filter_index_patterns'; import { getFixQueryErrorsNode } from './nodes/fix_query_errors'; import { getRetrieveIntegrationsNode } from './nodes/retrieve_integrations'; import { getTranslateRuleNode } from './nodes/translate_rule'; @@ -19,6 +21,7 @@ import type { TranslateRuleGraphParams, TranslateRuleState } from './types'; const MAX_VALIDATION_ITERATIONS = 3; export function getTranslateRuleGraph({ + model, inferenceClient, connectorId, ruleMigrationsRetriever, @@ -31,7 +34,9 @@ export function getTranslateRuleGraph({ }); const validationNode = getValidationNode({ logger }); const fixQueryErrorsNode = getFixQueryErrorsNode({ inferenceClient, connectorId, logger }); - const retrieveIntegrationsNode = getRetrieveIntegrationsNode({ ruleMigrationsRetriever }); + const retrieveIntegrationsNode = getRetrieveIntegrationsNode({ model, ruleMigrationsRetriever }); + const ecsMappingNode = getEcsMappingNode({ inferenceClient, connectorId, logger }); + const filterIndexPatternsNode = getFilterIndexPatternsNode({ logger }); const translateRuleGraph = new StateGraph(translateRuleState) // Nodes @@ -39,12 +44,20 @@ export function getTranslateRuleGraph({ .addNode('validation', validationNode) .addNode('fixQueryErrors', fixQueryErrorsNode) .addNode('retrieveIntegrations', retrieveIntegrationsNode) + .addNode('ecsMapping', ecsMappingNode) + .addNode('filterIndexPatterns', filterIndexPatternsNode) // Edges .addEdge(START, 'retrieveIntegrations') .addEdge('retrieveIntegrations', 'translateRule') .addEdge('translateRule', 'validation') .addEdge('fixQueryErrors', 'validation') - .addConditionalEdges('validation', validationRouter, ['fixQueryErrors', END]); + .addEdge('ecsMapping', 'validation') + .addConditionalEdges('validation', validationRouter, [ + 'fixQueryErrors', + 'ecsMapping', + 'filterIndexPatterns', + ]) + .addEdge('filterIndexPatterns', END); const graph = translateRuleGraph.compile(); graph.name = 'Translate Rule Graph'; @@ -59,6 +72,9 @@ const validationRouter = (state: TranslateRuleState) => { if (!isEmpty(state.validation_errors?.esql_errors)) { return 'fixQueryErrors'; } + if (!state.translation_finalized) { + return 'ecsMapping'; + } } - return END; + return 'filterIndexPatterns'; }; diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/ecs_mapping/cim_ecs_map.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/ecs_mapping/cim_ecs_map.ts new file mode 100644 index 0000000000000..3bafaf2fc6518 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/ecs_mapping/cim_ecs_map.ts @@ -0,0 +1,181 @@ +/* + * 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 const SIEM_RULE_MIGRATION_CIM_ECS_MAP = ` +datamodel,object,source_field,ecs_field,data_type +Application_State,All_Application_State,dest,service.node.name,string +Application_State,All_Application_State,process,process.title,string +Application_State,All_Application_State,user,user.name,string +Application_State,Ports,dest_port,destination.port,number +Application_State,Ports,transport,network.transport,string +Application_State,Ports,transport_dest_port,destination.port,string +Application_State,Services,service,service.name,string +Application_State,Services,service_id,service.id,string +Application_State,Services,status,service.state,string +Authentication,Authentication,action,event.action,string +Authentication,Authentication,app,process.name,string +Authentication,Authentication,dest,host.name,string +Authentication,Authentication,duration,event.duration,number +Authentication,Authentication,signature,event.code,string +Authentication,Authentication,signature_id,event.reason,string +Authentication,Authentication,src,source.address,string +Authentication,Authentication,src_nt_domain,source.domain,string +Authentication,Authentication,user,user.name,string +Certificates,All_Certificates,dest_port,destination.port,number +Certificates,All_Certificates,duration,event.duration,number +Certificates,All_Certificates,src,source.address,string +Certificates,All_Certificates,src_port,source.port,number +Certificates,All_Certificates,transport,network.protocol,string +Certificates,SSL,ssl_end_time,tls.server.not_after,time +Certificates,SSL,ssl_hash,tls.server.hash,string +Certificates,SSL,ssl_issuer_common_name,tls.server.issuer,string +Certificates,SSL,ssl_issuer_locality,x509.issuer.locality,string +Certificates,SSL,ssl_issuer_organization,x509.issuer.organization,string +Certificates,SSL,ssl_issuer_state,x509.issuer.state_or_province,string +Certificates,SSL,ssl_issuer_unit,x509.issuer.organizational_unit,string +Certificates,SSL,ssl_publickey_algorithm,x509.public_key_algorithm,string +Certificates,SSL,ssl_serial,x509.serial_number,string +Certificates,SSL,ssl_signature_algorithm,x509.signature_algorithm,string +Certificates,SSL,ssl_start_time,x509.not_before,time +Certificates,SSL,ssl_subject,x509.subject.distinguished_name,string +Certificates,SSL,ssl_subject_common_name,x509.subject.common_name,string +Certificates,SSL,ssl_subject_locality,x509.subject.locality,string +Certificates,SSL,ssl_subject_organization,x509.subject.organization,string +Certificates,SSL,ssl_subject_state,x509.subject.state_or_province,string +Certificates,SSL,ssl_subject_unit,x509.subject.organizational_unit,string +Certificates,SSL,ssl_version,tls.version,string +Change,All_Changes,action,event.action,string +Change,Account_Management,dest_nt_domain,destination.domain,string +Change,Account_Management,src_nt_domain,source.domain,string +Change,Account_Management,src_user,source.user,string +Intrusion_Detection,IDS_Attacks,action,event.action,string +Intrusion_Detection,IDS_Attacks,dest,destination.address,string +Intrusion_Detection,IDS_Attacks,dest_port,destination.port,number +Intrusion_Detection,IDS_Attacks,dvc,observer.hostname,string +Intrusion_Detection,IDS_Attacks,severity,event.severity,string +Intrusion_Detection,IDS_Attacks,src,source.ip,string +Intrusion_Detection,IDS_Attacks,user,source.user,string +JVM,OS,os,host.os.name,string +JVM,OS,os_architecture,host.architecture,string +JVM,OS,os_version,host.os.version,string +Malware,Malware_Attacks,action,event.action,string +Malware,Malware_Attacks,date,event.created,string +Malware,Malware_Attacks,dest,host.hostname,string +Malware,Malware_Attacks,file_hash,file.hash.*,string +Malware,Malware_Attacks,file_name,file.name,string +Malware,Malware_Attacks,file_path,file.path,string +Malware,Malware_Attacks,Sender,source.user.email,string +Malware,Malware_Attacks,src,source.ip,string +Malware,Malware_Attacks,user,related.user,string +Malware,Malware_Attacks,url,rule.reference,string +Network_Resolution,DNS,answer,dns.answers,string +Network_Resolution,DNS,dest,destination.address,string +Network_Resolution,DNS,dest_port,destination.port,number +Network_Resolution,DNS,duration,event.duration,number +Network_Resolution,DNS,message_type,dns.type,string +Network_Resolution,DNS,name,dns.question.name,string +Network_Resolution,DNS,query,dns.question.name,string +Network_Resolution,DNS,query_type,dns.op_code,string +Network_Resolution,DNS,record_type,dns.question.type,string +Network_Resolution,DNS,reply_code,dns.response_code,string +Network_Resolution,DNS,reply_code_id,dns.id,number +Network_Resolution,DNS,response_time,event.duration,number +Network_Resolution,DNS,src,source.address,string +Network_Resolution,DNS,src_port,source.port,number +Network_Resolution,DNS,transaction_id,dns.id,number +Network_Resolution,DNS,transport,network.transport,string +Network_Resolution,DNS,ttl,dns.answers.ttl,number +Network_Sessions,All_Sessions,action,event.action,string +Network_Sessions,All_Sessions,dest_ip,destination.ip,string +Network_Sessions,All_Sessions,dest_mac,destination.mac,string +Network_Sessions,All_Sessions,duration,event.duration,number +Network_Sessions,All_Sessions,src_dns,source.registered_domain,string +Network_Sessions,All_Sessions,src_ip,source.ip,string +Network_Sessions,All_Sessions,src_mac,source.mac,string +Network_Sessions,All_Sessions,user,user.name,string +Network_Traffic,All_Traffic,action,event.action,string +Network_Traffic,All_Traffic,app,network.protocol,string +Network_Traffic,All_Traffic,bytes,network.bytes,number +Network_Traffic,All_Traffic,dest,destination.ip,string +Network_Traffic,All_Traffic,dest_ip,destination.ip,string +Network_Traffic,All_Traffic,dest_mac,destination.mac,string +Network_Traffic,All_Traffic,dest_port,destination.port,number +Network_Traffic,All_Traffic,dest_translated_ip,destination.nat.ip,string +Network_Traffic,All_Traffic,dest_translated_port,destination.nat.port,number +Network_Traffic,All_Traffic,direction,network.direction,string +Network_Traffic,All_Traffic,duration,event.duration,number +Network_Traffic,All_Traffic,dvc,observer.name,string +Network_Traffic,All_Traffic,dvc_ip,observer.ip,string +Network_Traffic,All_Traffic,dvc_mac,observer.mac,string +Network_Traffic,All_Traffic,dvc_zone,observer.egress.zone,string +Network_Traffic,All_Traffic,packets,network.packets,number +Network_Traffic,All_Traffic,packets_in,source.packets,number +Network_Traffic,All_Traffic,packets_out,destination.packets,number +Network_Traffic,All_Traffic,protocol,network.protocol,string +Network_Traffic,All_Traffic,rule,rule.name,string +Network_Traffic,All_Traffic,src,source.address,string +Network_Traffic,All_Traffic,src_ip,source.ip,string +Network_Traffic,All_Traffic,src_mac,source.mac,string +Network_Traffic,All_Traffic,src_port,source.port,number +Network_Traffic,All_Traffic,src_translated_ip,source.nat.ip,string +Network_Traffic,All_Traffic,src_translated_port,source.nat.port,number +Network_Traffic,All_Traffic,transport,network.transport,string +Network_Traffic,All_Traffic,vlan,vlan.name,string +Vulnerabilities,Vulnerabilities,category,vulnerability.category,string +Vulnerabilities,Vulnerabilities,cve,vulnerability.id,string +Vulnerabilities,Vulnerabilities,cvss,vulnerability.score.base,number +Vulnerabilities,Vulnerabilities,dest,host.name,string +Vulnerabilities,Vulnerabilities,dvc,vulnerability.scanner.vendor,string +Vulnerabilities,Vulnerabilities,severity,vulnerability.severity,string +Vulnerabilities,Vulnerabilities,url,vulnerability.reference,string +Vulnerabilities,Vulnerabilities,user,related.user,string +Vulnerabilities,Vulnerabilities,vendor_product,vulnerability.scanner.vendor,string +Endpoint,Ports,creation_time,@timestamp,timestamp +Endpoint,Ports,dest_port,destination.port,number +Endpoint,Ports,process_id,process.pid,string +Endpoint,Ports,transport,network.transport,string +Endpoint,Ports,transport_dest_port,destination.port,string +Endpoint,Processes,action,event.action,string +Endpoint,Processes,os,os.full,string +Endpoint,Processes,parent_process_exec,process.parent.name,string +Endpoint,Processes,parent_process_id,process.ppid,number +Endpoint,Processes,parent_process_guid,process.parent.entity_id,string +Endpoint,Processes,parent_process_path,process.parent.executable,string +Endpoint,Processes,process_current_directory,process.parent.working_directory, +Endpoint,Processes,process_exec,process.name,string +Endpoint,Processes,process_hash,process.hash.*,string +Endpoint,Processes,process_guid,process.entity_id,string +Endpoint,Processes,process_id,process.pid,number +Endpoint,Processes,process_path,process.executable,string +Endpoint,Processes,user_id,related.user,string +Endpoint,Services,description,service.name,string +Endpoint,Services,process_id,service.id,string +Endpoint,Services,service_dll,dll.name,string +Endpoint,Services,service_dll_path,dll.path,string +Endpoint,Services,service_dll_hash,dll.hash.*,string +Endpoint,Services,service_dll_signature_exists,dll.code_signature.exists,boolean +Endpoint,Services,service_dll_signature_verified,dll.code_signature.valid,boolean +Endpoint,Services,service_exec,service.name,string +Endpoint,Services,service_hash,hash.*,string +Endpoint,Filesystem,file_access_time,file.accessed,timestamp +Endpoint,Filesystem,file_create_time,file.created,timestamp +Endpoint,Filesystem,file_modify_time,file.mtime,timestamp +Endpoint,Filesystem,process_id,process.pid,string +Endpoint,Registry,process_id,process.id,string +Web,Web,action,event.action,string +Web,Web,app,observer.product,string +Web,Web,bytes_in,http.request.bytes,number +Web,Web,bytes_out,http.response.bytes,number +Web,Web,dest,destination.ip,string +Web,Web,duration,event.duration,number +Web,Web,http_method,http.request.method,string +Web,Web,http_referrer,http.request.referrer,string +Web,Web,http_user_agent,user_agent.name,string +Web,Web,status,http.response.status_code,string +Web,Web,url,url.full,string +Web,Web,user,url.username,string +Web,Web,vendor_product,observer.product,string`; diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/ecs_mapping/ecs_mapping.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/ecs_mapping/ecs_mapping.ts new file mode 100644 index 0000000000000..ac7f6db7236d1 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/ecs_mapping/ecs_mapping.ts @@ -0,0 +1,66 @@ +/* + * 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 { Logger } from '@kbn/core/server'; +import type { InferenceClient } from '@kbn/inference-plugin/server'; +import { SiemMigrationRuleTranslationResult } from '../../../../../../../../../../common/siem_migrations/constants'; +import { getEsqlKnowledgeBase } from '../../../../../util/esql_knowledge_base_caller'; +import type { GraphNode } from '../../types'; +import { SIEM_RULE_MIGRATION_CIM_ECS_MAP } from './cim_ecs_map'; +import { ESQL_TRANSLATE_ECS_MAPPING_PROMPT } from './prompts'; + +interface GetEcsMappingNodeParams { + inferenceClient: InferenceClient; + connectorId: string; + logger: Logger; +} + +export const getEcsMappingNode = ({ + inferenceClient, + connectorId, + logger, +}: GetEcsMappingNodeParams): GraphNode => { + const esqlKnowledgeBaseCaller = getEsqlKnowledgeBase({ inferenceClient, connectorId, logger }); + return async (state) => { + const elasticRule = { + title: state.elastic_rule.title, + description: state.elastic_rule.description, + query: state.elastic_rule.query, + }; + + const prompt = await ESQL_TRANSLATE_ECS_MAPPING_PROMPT.format({ + field_mapping: SIEM_RULE_MIGRATION_CIM_ECS_MAP, + splunk_query: state.inline_query, + elastic_rule: JSON.stringify(elasticRule, null, 2), + }); + + const response = await esqlKnowledgeBaseCaller(prompt); + + const updatedQuery = response.match(/```esql\n([\s\S]*?)\n```/)?.[1] ?? ''; + const ecsSummary = response.match(/## Field Mapping Summary[\s\S]*$/)?.[0] ?? ''; + + const translationResult = getTranslationResult(updatedQuery); + + return { + response, + comments: [ecsSummary], + translation_finalized: true, + translation_result: translationResult, + elastic_rule: { + ...state.elastic_rule, + query: updatedQuery, + }, + }; + }; +}; + +const getTranslationResult = (esqlQuery: string): SiemMigrationRuleTranslationResult => { + if (esqlQuery.match(/\[(macro|lookup):[\s\S]*\]/)) { + return SiemMigrationRuleTranslationResult.PARTIAL; + } + return SiemMigrationRuleTranslationResult.FULL; +}; diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/ecs_mapping/index.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/ecs_mapping/index.ts new file mode 100644 index 0000000000000..339e6d3dd8e7a --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/ecs_mapping/index.ts @@ -0,0 +1,7 @@ +/* + * 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 { getEcsMappingNode } from './ecs_mapping'; diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/ecs_mapping/prompts.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/ecs_mapping/prompts.ts new file mode 100644 index 0000000000000..1e89cda884ca0 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/ecs_mapping/prompts.ts @@ -0,0 +1,46 @@ +/* + * 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 { ChatPromptTemplate } from '@langchain/core/prompts'; + +export const ESQL_TRANSLATE_ECS_MAPPING_PROMPT = + ChatPromptTemplate.fromTemplate(`You are a helpful cybersecurity (SIEM) expert agent. Your task is to migrate "detection rules" from Splunk SPL to Elasticsearch ESQL. +Your task is to look at the new ESQL query already generated from its initial Splunk SPL query and translate the Splunk CIM field names to the Elastic Common Schema (ECS) fields. +Below is the relevant context used when deciding which Elastic Common Schema field to use when translating from Splunk CIM fields: + + + +{field_mapping} + + +{splunk_query} + + +{elastic_rule} + + + +Go through the current esql query above and translate the current field names that originated from a Splunk CIM/SPL query to the equivalent Elastic Common Schema (ECS) fields by following these steps: +- Analyze all the information about the related esql rule especially the query and try to determine the intent of the rule, which should help in choosing which fields to map to ECS. +- Try to determine if and which datamodel is being used by looking at the initial splunk query, the query usually contains the datamodel name, its object and related fields. +- Go through each part of the ESQL query, if a part is determined to be related to a splunk CIM field or datamodel use the cim to ecs map above to determine the equivalent ECS field. +- If a field is not in the cim to ecs map, or no datamodel is used, try to use your existing knowledge about Elastic Common Schema to determine if and which ECS field to use. +- Do not reuse the same ECS field name for multiple Splunk CIM fields, always try to find the most appropriate ECS field. +- If you are uncertain about a field mapping, leave it as is and mention it in the summary. + + +- If a field is found in the CIM to ECS map, replace the field name with the ECS field name. If not, try to determine if and what equivalent ECS field can be used. +- Only translate the field names, do not modify the structure of the query. +- Only translate when you are certain about the field mapping, if uncertain, leave the field as is and mention it in the summary. +- Only use and modify the current ESQL query, do not create a new one or modify any other part of the rule. + + + +- First, the updated ES|QL query inside an \`\`\`esql code block. +- At the end, the summary of the the field mapping process followed in markdown, starting with "## Field Mapping Summary". This would include the reason, original field names, the target ECS field name and any fields that were left as is. + +`); diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/filter_index_patterns/filter_index_patterns.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/filter_index_patterns/filter_index_patterns.ts new file mode 100644 index 0000000000000..bb1e086bf4937 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/filter_index_patterns/filter_index_patterns.ts @@ -0,0 +1,40 @@ +/* + * 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 { Logger } from '@kbn/core/server'; +import { SiemMigrationRuleTranslationResult } from '../../../../../../../../../../common/siem_migrations/constants'; +import type { GraphNode } from '../../types'; + +interface GetFilterIndexPatternsNodeParams { + logger: Logger; +} + +/** + * When rule translation happens without any related integrations found we reuse the logs-* pattern to make validation easier. + * However we want to replace this with a value to notify the end user that it needs to be replaced. + */ +export const getFilterIndexPatternsNode = ({ + logger, +}: GetFilterIndexPatternsNodeParams): GraphNode => { + return async (state) => { + const query = state.elastic_rule?.query; + + if (query && query.includes('logs-*')) { + logger.debug('Replacing logs-* with a placeholder value'); + const newQuery = query.replace('logs-*', '[indexPattern:logs-*]'); + return { + elastic_rule: { + ...state.elastic_rule, + query: newQuery, + translation_result: SiemMigrationRuleTranslationResult.PARTIAL, + }, + }; + } + + return {}; + }; +}; diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/filter_index_patterns/index.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/filter_index_patterns/index.ts new file mode 100644 index 0000000000000..6e7762633b7fd --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/filter_index_patterns/index.ts @@ -0,0 +1,7 @@ +/* + * 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 { getFilterIndexPatternsNode } from './filter_index_patterns'; 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/sub_graphs/translate_rule/nodes/retrieve_integrations/prompts.ts new file mode 100644 index 0000000000000..d562bb31b1628 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/retrieve_integrations/prompts.ts @@ -0,0 +1,49 @@ +/* + * 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 { ChatPromptTemplate } from '@langchain/core/prompts'; +export const MATCH_INTEGRATION_PROMPT = ChatPromptTemplate.fromMessages([ + [ + 'system', + `You are an expert assistant in Cybersecurity, your task is to help migrating a SIEM detection rule, from Splunk Security to Elastic Security. +You will be provided with a Splunk Detection Rule name by the user, your goal is to try to find the most relevant Elastic Integration from the integration list below if any, and return either the most relevant. If none seems relevant you should always return empty. +Here are some context for you to reference for your task, read it carefully as you will get questions about it later: + + + +{integrations} + + +`, + ], + [ + 'human', + `See the below description of the relevant splunk rule and try to match it with any of the Elastic Integrations from before, do not guess or reply with anything else, only reply with the most relevant Elastic Integration if any. + +{splunk_rule} + + + +- Always reply with a JSON object with the key "match" and the value being the most relevant matched integration title. Do not reply with anything else. +- Only reply with exact matches, if you are unsure or do not find a very confident match, always reply with an empty string value in the match key, do not guess or reply with anything else. +- If there is one elastic integration in the list that covers the relevant usecase, set the title of the matching integration as a value of the match key. Do not reply with anything else. +- If there are multiple elastic integrations in the list that cover the same usecase, answer with the most specific of them, for example if the rule is related to "Sysmon" then the Sysmon integration is more specific than Windows. + + + +U: +Linux Auditd Add User Account Type + +A: Please find the match JSON object below: +\`\`\`json +{{"match": "auditd_manager"}} +\`\`\` + +`, + ], + ['ai', 'Please find the match JSON object below:'], +]); 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 fa5b761806b5d..74c9055bd7665 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,22 +5,57 @@ * 2.0. */ +import { JsonOutputParser } from '@langchain/core/output_parsers'; import type { RuleMigrationsRetriever } from '../../../../../retrievers'; +import type { ChatModel } from '../../../../../util/actions_client_chat'; import type { GraphNode } from '../../types'; +import { MATCH_INTEGRATION_PROMPT } from './prompts'; interface GetRetrieveIntegrationsNodeParams { + model: ChatModel; ruleMigrationsRetriever: RuleMigrationsRetriever; } +interface GetMatchedIntegrationResponse { + match: string; +} + export const getRetrieveIntegrationsNode = ({ + model, ruleMigrationsRetriever, }: GetRetrieveIntegrationsNodeParams): GraphNode => { return async (state) => { const query = state.semantic_query; const integrations = await ruleMigrationsRetriever.integrations.getIntegrations(query); - return { - integrations, + + const outputParser = new JsonOutputParser(); + const mostRelevantIntegration = MATCH_INTEGRATION_PROMPT.pipe(model).pipe(outputParser); + + const elasticSecurityIntegrations = integrations.map((integration) => { + return { + title: integration.title, + description: integration.description, + }; + }); + const splunkRule = { + title: state.original_rule.title, + description: state.original_rule.description, }; + + /* + * Takes the most relevant integration from the array of integration(s) returned by the semantic query, returns either the most relevant or none. + */ + const response = (await mostRelevantIntegration.invoke({ + integrations: JSON.stringify(elasticSecurityIntegrations, null, 2), + splunk_rule: JSON.stringify(splunkRule, null, 2), + })) as GetMatchedIntegrationResponse; + if (response.match) { + const matchedIntegration = integrations.find((r) => r.title === response.match); + if (matchedIntegration) { + return { integration: matchedIntegration }; + } + } + return {}; }; }; diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/translate_rule/prompts.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/translate_rule/prompts.ts index 9749dfd96efba..1ea8295c7402f 100644 --- a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/translate_rule/prompts.ts +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/translate_rule/prompts.ts @@ -7,46 +7,39 @@ import { ChatPromptTemplate } from '@langchain/core/prompts'; -export const ESQL_TRANSLATION_PROMPT = - ChatPromptTemplate.fromTemplate(`You are a helpful cybersecurity (SIEM) expert agent. Your task is to migrate "detection rules" from Splunk to Elastic Security. -Your goal is to translate the SPL query into an equivalent Elastic Security Query Language (ES|QL) query. -Below is the relevant context used when deciding which Elastic Common Schema field to use when translating from Splunk CIM fields: +export const ESQL_SYNTAX_TRANSLATION_PROMPT = + ChatPromptTemplate.fromTemplate(`You are a helpful cybersecurity (SIEM) expert agent. Your task is to migrate "detection rules" from Splunk SPL to Elasticsearch ES|QL. +Your goal is to translate the SPL query syntax into an equivalent Elastic Search Query Language (ES|QL) query without changing any of the field names and focusing only on translating the syntax and structure. +Here are some context for you to reference for your task, read it carefully as you will get questions about it later: - -{field_mapping} - + +{splunk_rule} + + +If, in the SPL query, you find a lookup list or macro call, mention it in the summary and add a placeholder in the query with the format [macro:(argumentCount)] or [lookup:] including the [] keys, + Examples: + - \`get_duration(firstDate,secondDate)\` -> [macro:get_duration(2)] + - lookup dns_domains.csv -> [lookup:dns_domains.csv]. + -## Splunk rule Information provided: -- Below you will find Splunk rule information: the title (<>), the description (<<DESCRIPTION>>), and the SPL (Search Processing Language) query (<<SPL_QUERY>>). -- Use all the information to analyze the intent of the rule, in order to translate into an equivalent ES|QL rule. -- The fields in the Splunk query may not be the same as in the Elastic Common Schema (ECS), so you may need to map them accordingly. +Go through each step and part of the splunk rule and query while following the below guide to produce the resulting ES|QL query: +- Analyze all the information about the related splunk rule and try to determine the intent of the rule, in order to translate into an equivalent ES|QL rule. +- Go through each part of the SPL query and determine the steps required to produce the same end results using ES|QL. Only focus on translating the structure without modifying any of the field names. +- Do NOT map any of the fields to the Elastic Common Schema (ECS), this will happen in a later step. +- Always remember to replace any lookup list or macro call with the appropriate placeholder as defined in the context. -## Guidelines: + +<guidelines> - Analyze the SPL query and identify the key components. -- Translate the SPL query into an equivalent ES|QL query using ECS (Elastic Common Schema) field names. -- Always start the generated ES|QL query by filtering FROM using these index patterns in the translated query: {indexPatterns}. -- If, in the SPL query, you find a lookup list or macro call, mention it in the summary and add a placeholder in the query with the format [macro:<macro_name>(argumentCount)] or [lookup:<lookup_name>] including the [] keys, - - Examples: - - \`get_duration(firstDate,secondDate)\` -> [macro:get_duration(2)] - - lookup dns_domains.csv -> [lookup:dns_domains.csv]. +- Do NOT translate the field names of the SPL query. +- Always start the resulting ES|QL query by filtering using FROM and with these index patterns: {indexPatterns}. +- Remember to always replace any lookup list or macro call with the appropriate placeholder as defined in the context. +</guidelines> -## The output will be parsed and must contain: +<expected_output> - First, the ES|QL query inside an \`\`\`esql code block. -- At the end, the summary of the translation process followed in markdown, starting with "## Migration Summary". - -Find the Splunk rule information below: - -<<TITLE>> -{title} -<> - -<> -{description} -<> - -<> -{inline_query} -<> +- At the end, the summary of the translation process followed in markdown, starting with "## Translation Summary". + `); 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 85f5e7279d2b9..09f9bca474004 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 @@ -10,8 +10,7 @@ import type { InferenceClient } from '@kbn/inference-plugin/server'; import { SiemMigrationRuleTranslationResult } from '../../../../../../../../../../common/siem_migrations/constants'; import { getEsqlKnowledgeBase } from '../../../../../util/esql_knowledge_base_caller'; import type { GraphNode } from '../../types'; -import { SIEM_RULE_MIGRATION_CIM_ECS_MAP } from './cim_ecs_map'; -import { ESQL_TRANSLATION_PROMPT } from './prompts'; +import { ESQL_SYNTAX_TRANSLATION_PROMPT } from './prompts'; interface GetTranslateRuleNodeParams { inferenceClient: InferenceClient; @@ -26,34 +25,35 @@ export const getTranslateRuleNode = ({ }: GetTranslateRuleNodeParams): GraphNode => { const esqlKnowledgeBaseCaller = getEsqlKnowledgeBase({ inferenceClient, connectorId, logger }); return async (state) => { - const indexPatterns = state.integrations - .flatMap((integration) => - integration.data_streams.map((dataStream) => dataStream.index_pattern) - ) - .join(','); - const integrationIds = state.integrations.map((integration) => integration.id); + const indexPatterns = + state.integration?.data_streams?.map((dataStream) => dataStream.index_pattern).join(',') || + 'logs-*'; + const integrationId = state.integration?.id || ''; - const prompt = await ESQL_TRANSLATION_PROMPT.format({ + const splunkRule = { title: state.original_rule.title, description: state.original_rule.description, - field_mapping: SIEM_RULE_MIGRATION_CIM_ECS_MAP, inline_query: state.inline_query, + }; + + const prompt = await ESQL_SYNTAX_TRANSLATION_PROMPT.format({ + splunk_rule: JSON.stringify(splunkRule, null, 2), indexPatterns, }); const response = await esqlKnowledgeBaseCaller(prompt); const esqlQuery = response.match(/```esql\n([\s\S]*?)\n```/)?.[1] ?? ''; - const summary = response.match(/## Migration Summary[\s\S]*$/)?.[0] ?? ''; + const translationSummary = response.match(/## Translation Summary[\s\S]*$/)?.[0] ?? ''; const translationResult = getTranslationResult(esqlQuery); return { response, - comments: [summary], + comments: [translationSummary], translation_result: translationResult, elastic_rule: { title: state.original_rule.title, - integration_ids: integrationIds, + integration_id: integrationId, description: state.original_rule.description, severity: 'low', query: esqlQuery, 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 ac8799cb09d74..ea46238002178 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 @@ -22,9 +22,13 @@ export const translateRuleState = Annotation.Root({ default: () => [], }), original_rule: Annotation(), - integrations: Annotation({ + integration: Annotation({ reducer: (current, value) => value ?? current, - default: () => [], + default: () => ({} as Integration), + }), + translation_finalized: Annotation({ + reducer: (current, value) => value ?? current, + default: () => false, }), inline_query: Annotation({ reducer: (current, value) => value ?? current, 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 eddc415f23392..0a3435d496736 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 @@ -8,12 +8,14 @@ import type { Logger } from '@kbn/core/server'; import type { InferenceClient } from '@kbn/inference-plugin/server'; import type { RuleMigrationsRetriever } from '../../../retrievers'; +import type { ChatModel } from '../../../util/actions_client_chat'; import type { translateRuleState } from './state'; export type TranslateRuleState = typeof translateRuleState.State; export type GraphNode = (state: TranslateRuleState) => Promise>; export interface TranslateRuleGraphParams { + model: ChatModel; inferenceClient: InferenceClient; connectorId: string; ruleMigrationsRetriever: RuleMigrationsRetriever; 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 fe3e01fe84925..1edd1b449070c 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 @@ -27,7 +27,7 @@ import type { } from './types'; import { ActionsClientChat } from './util/actions_client_chat'; -const ITERATION_BATCH_SIZE = 50 as const; +const ITERATION_BATCH_SIZE = 15 as const; const ITERATION_SLEEP_SECONDS = 10 as const; type MigrationsRunning = Map;