From 65065aa22358633cf7dd1299426b58a8eb344c18 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Sat, 22 Feb 2025 05:51:43 +1100 Subject: [PATCH] [8.18] [Security Solution] - Feat Add `Severity` and `risk_score` to the Siem migrations (#211202) (#212116) # Backport This will backport the following commits from `main` to `8.18`: - [[Security Solution] - Feat Add `Severity` and `risk_score` to the Siem migrations (#211202)](https://github.com/elastic/kibana/pull/211202) ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sorenlouv/backport) Co-authored-by: Jatin Kathuria --- .../common/detection_engine/constants.ts | 14 +- .../common/siem_migrations/constants.ts | 5 - .../model/rule_migration.gen.ts | 4 + .../model/rule_migration.schema.yaml | 3 + .../public/common/constants.ts | 5 - .../components/step_about_rule/data.tsx | 13 -- .../components/step_about_rule/index.tsx | 2 +- .../lib/chart_palette/index.test.ts | 2 +- .../alerts_treemap/lib/chart_palette/index.ts | 2 +- .../components/data_input_flyout/constants.ts | 1 + .../steps/common/upload_file_button.tsx | 2 +- .../rules_file_upload.test.tsx | 208 ++++++++++++++++++ .../rules_file_upload/rules_file_upload.tsx | 1 + .../splunk_rules.test.data.ts | 66 ++++++ .../lib/siem_migrations/rules/constants.ts | 25 +++ .../rules/data/rule_migrations_field_maps.ts | 1 + .../match_prebuilt_rule.ts | 10 +- .../nodes/translate_rule/severity.test.ts | 127 +++++++++++ .../nodes/translate_rule/severity.ts | 40 ++++ .../nodes/translate_rule/translate_rule.ts | 6 + .../translation_result/translation_result.ts | 4 +- .../server/lib/siem_migrations/rules/types.ts | 15 ++ 22 files changed, 521 insertions(+), 35 deletions(-) create mode 100644 x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/sub_steps/rules_file_upload/rules_file_upload.test.tsx create mode 100644 x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/sub_steps/rules_file_upload/splunk_rules.test.data.ts create mode 100644 x-pack/solutions/security/plugins/security_solution/server/lib/siem_migrations/rules/constants.ts create mode 100644 x-pack/solutions/security/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/translate_rule/severity.test.ts create mode 100644 x-pack/solutions/security/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/translate_rule/severity.ts diff --git a/x-pack/solutions/security/plugins/security_solution/common/detection_engine/constants.ts b/x-pack/solutions/security/plugins/security_solution/common/detection_engine/constants.ts index 270af1a91cf46..e3acea3b1d613 100644 --- a/x-pack/solutions/security/plugins/security_solution/common/detection_engine/constants.ts +++ b/x-pack/solutions/security/plugins/security_solution/common/detection_engine/constants.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { Type } from '@kbn/securitysolution-io-ts-alerting-types'; +import type { Severity, Type } from '@kbn/securitysolution-io-ts-alerting-types'; export enum RULE_PREVIEW_INVOCATION_COUNT { HOUR = 12, @@ -60,3 +60,15 @@ export const SUPPRESSIBLE_ALERT_RULES_GA: Type[] = [ 'threat_match', 'machine_learning', ]; + +export const RISK_SCORE_LOW = 21; +export const RISK_SCORE_MEDIUM = 47; +export const RISK_SCORE_HIGH = 73; +export const RISK_SCORE_CRITICAL = 99; + +export const defaultRiskScoreBySeverity: Record = { + low: RISK_SCORE_LOW, + medium: RISK_SCORE_MEDIUM, + high: RISK_SCORE_HIGH, + critical: RISK_SCORE_CRITICAL, +}; diff --git a/x-pack/solutions/security/plugins/security_solution/common/siem_migrations/constants.ts b/x-pack/solutions/security/plugins/security_solution/common/siem_migrations/constants.ts index e04ca9a2ae2aa..a717c465618c9 100644 --- a/x-pack/solutions/security/plugins/security_solution/common/siem_migrations/constants.ts +++ b/x-pack/solutions/security/plugins/security_solution/common/siem_migrations/constants.ts @@ -5,8 +5,6 @@ * 2.0. */ -import type { Severity } from '@kbn/securitysolution-io-ts-alerting-types'; - export const SIEM_MIGRATIONS_ASSISTANT_USER = 'assistant'; export const SIEM_MIGRATIONS_PATH = '/internal/siem_migrations' as const; @@ -61,9 +59,6 @@ export enum RuleTranslationResult { UNTRANSLATABLE = 'untranslatable', } -export const DEFAULT_TRANSLATION_RISK_SCORE = 21; -export const DEFAULT_TRANSLATION_SEVERITY: Severity = 'low'; - export const DEFAULT_TRANSLATION_FIELDS = { from: 'now-360s', to: 'now', diff --git a/x-pack/solutions/security/plugins/security_solution/common/siem_migrations/model/rule_migration.gen.ts b/x-pack/solutions/security/plugins/security_solution/common/siem_migrations/model/rule_migration.gen.ts index f7195ef9b14c4..3ce88d0d90184 100644 --- a/x-pack/solutions/security/plugins/security_solution/common/siem_migrations/model/rule_migration.gen.ts +++ b/x-pack/solutions/security/plugins/security_solution/common/siem_migrations/model/rule_migration.gen.ts @@ -71,6 +71,10 @@ export const OriginalRule = z.object({ * The original rule annotations containing additional information. */ annotations: OriginalRuleAnnotations.optional(), + /** + * The original rule's severity or some representation of it. + */ + severity: z.string().optional(), }); /** diff --git a/x-pack/solutions/security/plugins/security_solution/common/siem_migrations/model/rule_migration.schema.yaml b/x-pack/solutions/security/plugins/security_solution/common/siem_migrations/model/rule_migration.schema.yaml index 7c5e44236f053..a2bed4aa15642 100644 --- a/x-pack/solutions/security/plugins/security_solution/common/siem_migrations/model/rule_migration.schema.yaml +++ b/x-pack/solutions/security/plugins/security_solution/common/siem_migrations/model/rule_migration.schema.yaml @@ -56,6 +56,9 @@ components: annotations: description: The original rule annotations containing additional information. $ref: '#/components/schemas/OriginalRuleAnnotations' + severity: + type: string + description: The original rule's severity or some representation of it. ElasticRule: type: object diff --git a/x-pack/solutions/security/plugins/security_solution/public/common/constants.ts b/x-pack/solutions/security/plugins/security_solution/public/common/constants.ts index 3b5cd00f25a0c..4fc5a7eba2246 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/common/constants.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/common/constants.ts @@ -22,11 +22,6 @@ export const RISK_COLOR_HIGH = euiThemeVars.euiColorVis9_behindText; */ export const RISK_COLOR_CRITICAL = euiThemeVars.euiColorDanger; -export const RISK_SCORE_LOW = 21; -export const RISK_SCORE_MEDIUM = 47; -export const RISK_SCORE_HIGH = 73; -export const RISK_SCORE_CRITICAL = 99; - export const ONBOARDING_VIDEO_SOURCE = '//play.vidyard.com/K6kKDBbP9SpXife9s2tHNP.html?'; export const DEFAULT_HISTORY_WINDOW_SIZE = '7d'; diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_about_rule/data.tsx b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_about_rule/data.tsx index 57e824d7d400d..2458782bc1a7d 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_about_rule/data.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_about_rule/data.tsx @@ -12,12 +12,6 @@ import { EuiHealth, useEuiTheme } from '@elastic/eui'; import type { Severity } from '@kbn/securitysolution-io-ts-alerting-types'; import * as I18n from './translations'; -import { - RISK_SCORE_LOW, - RISK_SCORE_MEDIUM, - RISK_SCORE_HIGH, - RISK_SCORE_CRITICAL, -} from '../../../../common/constants'; import { getRiskSeverityColors } from '../../../../common/utils/risk_color_palette'; export interface SeverityOptionItem { @@ -64,10 +58,3 @@ export const useSeverityOptions = () => { return severityOptions; }; - -export const defaultRiskScoreBySeverity: Record = { - low: RISK_SCORE_LOW, - medium: RISK_SCORE_MEDIUM, - high: RISK_SCORE_HIGH, - critical: RISK_SCORE_CRITICAL, -}; diff --git a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_about_rule/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_about_rule/index.tsx index 3accb1dce4396..8e8a65286991c 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_about_rule/index.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/detection_engine/rule_creation_ui/components/step_about_rule/index.tsx @@ -13,6 +13,7 @@ import styled from 'styled-components'; import type { DataViewBase } from '@kbn/es-query'; import type { Severity, Type } from '@kbn/securitysolution-io-ts-alerting-types'; +import { defaultRiskScoreBySeverity } from '../../../../../common/detection_engine/constants'; import type { RuleSource } from '../../../../../common/api/detection_engine'; import { isThreatMatchRule, isEsqlRule } from '../../../../../common/detection_engine/utils'; import type { @@ -25,7 +26,6 @@ import { AddMitreAttackThreat } from '../mitre'; import type { FieldHook, FormHook } from '../../../../shared_imports'; import { Field, Form, getUseField, UseField } from '../../../../shared_imports'; -import { defaultRiskScoreBySeverity } from './data'; import { isUrlInvalid } from '../../../../common/utils/validators'; import { schema as defaultSchema } from './schema'; import * as I18n from './translations'; diff --git a/x-pack/solutions/security/plugins/security_solution/public/detections/components/alerts_kpis/alerts_treemap_panel/alerts_treemap/lib/chart_palette/index.test.ts b/x-pack/solutions/security/plugins/security_solution/public/detections/components/alerts_kpis/alerts_treemap_panel/alerts_treemap/lib/chart_palette/index.test.ts index d7974ac8f4a51..ce3d2504a36f4 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detections/components/alerts_kpis/alerts_treemap_panel/alerts_treemap/lib/chart_palette/index.test.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/detections/components/alerts_kpis/alerts_treemap_panel/alerts_treemap/lib/chart_palette/index.test.ts @@ -10,7 +10,7 @@ import { RISK_SCORE_MEDIUM, RISK_SCORE_HIGH, RISK_SCORE_CRITICAL, -} from '../../../../../../../common/constants'; +} from '../../../../../../../../common/detection_engine/constants'; import { getFillColor, getRiskScorePalette, RISK_SCORE_STEPS } from '.'; import { renderHook } from '@testing-library/react'; import { getRiskSeverityColors } from '../../../../../../../common/utils/risk_color_palette'; diff --git a/x-pack/solutions/security/plugins/security_solution/public/detections/components/alerts_kpis/alerts_treemap_panel/alerts_treemap/lib/chart_palette/index.ts b/x-pack/solutions/security/plugins/security_solution/public/detections/components/alerts_kpis/alerts_treemap_panel/alerts_treemap/lib/chart_palette/index.ts index fdbeb989632e3..9a12d88d8cd2a 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/detections/components/alerts_kpis/alerts_treemap_panel/alerts_treemap/lib/chart_palette/index.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/detections/components/alerts_kpis/alerts_treemap_panel/alerts_treemap/lib/chart_palette/index.ts @@ -12,7 +12,7 @@ import { RISK_SCORE_MEDIUM, RISK_SCORE_HIGH, RISK_SCORE_CRITICAL, -} from '../../../../../../../common/constants'; +} from '../../../../../../../../common/detection_engine/constants'; import { getRiskSeverityColors } from '../../../../../../../common/utils/risk_color_palette'; /** diff --git a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/constants.ts b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/constants.ts index d390b395ed98f..2544f8acc1e42 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/constants.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/constants.ts @@ -12,6 +12,7 @@ export const SPLUNK_RULES_COLUMNS = [ 'description', 'action.escu.eli5', 'action.correlationsearch.annotations', + 'alert.severity', ] as const; export const RULES_SPLUNK_QUERY = `| rest /servicesNS/-/-/saved/searches diff --git a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/common/upload_file_button.tsx b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/common/upload_file_button.tsx index 1be66ac0a1e76..e76b2810049c1 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/common/upload_file_button.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/common/upload_file_button.tsx @@ -16,7 +16,7 @@ export const DATA_INPUT_FILE_UPLOAD_BUTTON = i18n.translate( export const UploadFileButton = React.memo>((props) => { return ( - + {DATA_INPUT_FILE_UPLOAD_BUTTON} ); diff --git a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/sub_steps/rules_file_upload/rules_file_upload.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/sub_steps/rules_file_upload/rules_file_upload.test.tsx new file mode 100644 index 0000000000000..f3ba48b3efea0 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/sub_steps/rules_file_upload/rules_file_upload.test.tsx @@ -0,0 +1,208 @@ +/* + * 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 React from 'react'; +import { act, fireEvent, render, waitFor } from '@testing-library/react'; +import type { RulesFileUploadProps } from './rules_file_upload'; +import { RulesFileUpload } from './rules_file_upload'; +import type { CreateMigration } from '../../../../../../service/hooks/use_create_migration'; +import { screen } from '@elastic/eui/lib/test/rtl'; +import { I18nProvider } from '@kbn/i18n-react'; +// eslint-disable-next-line import/no-nodejs-modules +import path from 'path'; +// eslint-disable-next-line import/no-nodejs-modules +import os from 'os'; +import { splunkTestRules } from './splunk_rules.test.data'; +import type { OriginalRule } from '../../../../../../../../../common/siem_migrations/model/rule_migration.gen'; + +const mockCreateMigration: CreateMigration = jest.fn(); +const mockApiError = 'Some Mock API Error'; + +const defaultProps: RulesFileUploadProps = { + createMigration: mockCreateMigration, + apiError: undefined, + isLoading: false, + isCreated: false, +}; + +const renderTestComponent = (props: Partial = {}) => { + const finalProps = { + ...defaultProps, + ...props, + }; + render( + + + + ); +}; + +const getTestDir = () => os.tmpdir(); + +const createRulesFileFromRulesData = ( + data: string, + destinationDirectory: string, + fileName: string +) => { + const filePath = path.join(destinationDirectory, fileName); + const file = new File([data], filePath, { + type: 'application/x-ndjson', + }); + return file; +}; + +describe('RulesFileUpload', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should render the upload button', () => { + renderTestComponent(); + + expect(screen.getByTestId('rulesFilePicker')).toBeInTheDocument(); + expect(screen.getByTestId('uploadFileButton')).toBeDisabled(); + }); + + it('should be able to upload correct file type', async () => { + const fileName = 'splunk_rules.test.data.json'; + const ndJSONString = splunkTestRules.map((obj) => JSON.stringify(obj)).join('\n'); + const testFile = createRulesFileFromRulesData(ndJSONString, getTestDir(), fileName); + + renderTestComponent(); + + const filePicker = screen.getByTestId('rulesFilePicker'); + + act(() => { + fireEvent.change(filePicker, { + target: { + files: [testFile], + }, + }); + }); + + await waitFor(() => { + expect(filePicker).toHaveAttribute('data-loading', 'true'); + }); + + await waitFor(() => { + expect(filePicker).toHaveAttribute('data-loading', 'false'); + }); + + await act(async () => { + fireEvent.click(screen.getByTestId('uploadFileButton')); + }); + + await waitFor(() => { + expect(mockCreateMigration).toHaveBeenCalled(); + }); + + const rulesToExpect: OriginalRule[] = splunkTestRules.map(({ result: rule }) => ({ + id: rule.id, + vendor: 'splunk', + title: rule.title, + query: rule.search, + query_language: 'spl', + description: rule.description, + severity: rule['alert.severity'] as OriginalRule['severity'], + })); + + expect(mockCreateMigration).toHaveBeenNthCalledWith(1, rulesToExpect); + }); + + describe('Error Handling', () => { + const scenarios = [ + { + subject: 'Non Object Entries', + fileContent: '["asdadsada"]', + errorMessage: 'The file contains non-object entries', + }, + { + subject: 'Non parsable JSON or ND-JSON', + fileContent: '[{"testArray"}])', + errorMessage: 'Cannot parse the file as either a JSON file or NDJSON file', + }, + { + subject: 'Empty File', + fileContent: '', + errorMessage: 'The file is empty', + }, + ]; + + it('should not be able to upload on API Error', async () => { + renderTestComponent({ + apiError: mockApiError, + }); + + const fileName = 'splunk_rules.test.data.json'; + const ndJSONString = splunkTestRules.map((obj) => JSON.stringify(obj)).join('\n'); + const testFile = createRulesFileFromRulesData(ndJSONString, getTestDir(), fileName); + + const filePicker = screen.getByTestId('rulesFilePicker'); + + act(() => { + fireEvent.change(filePicker, { + target: { + files: [testFile], + }, + }); + }); + + await waitFor(() => { + expect(filePicker).toHaveAttribute('data-loading', 'true'); + }); + + await waitFor(() => { + expect(filePicker).toHaveAttribute('data-loading', 'false'); + }); + + await act(async () => { + fireEvent.click(screen.getByTestId('uploadFileButton')); + }); + + await waitFor(() => { + expect(screen.getByText(mockApiError)).toBeVisible(); + expect(screen.getByTestId('uploadFileButton')).toBeDisabled(); + }); + }); + scenarios.forEach((scenario, _idx) => { + it(`should not be able to upload when file has - ${scenario.subject}`, async () => { + const fileName = 'invalid_rule_file.json'; + const testFile = createRulesFileFromRulesData(scenario.fileContent, getTestDir(), fileName); + + renderTestComponent({ + apiError: undefined, + }); + + const filePicker = screen.getByTestId('rulesFilePicker'); + + act(() => { + fireEvent.change(filePicker, { + target: { + files: [testFile], + }, + }); + }); + + await waitFor(() => { + expect(filePicker).toHaveAttribute('data-loading', 'true'); + }); + + await waitFor(() => { + expect(filePicker).toHaveAttribute('data-loading', 'false'); + }); + + await waitFor(() => { + expect(screen.getByText(scenario.errorMessage)).toBeVisible(); + }); + + await waitFor(() => { + expect(screen.getByTestId('uploadFileButton')).toBeDisabled(); + }); + }); + }); + }); +}); diff --git a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/sub_steps/rules_file_upload/rules_file_upload.tsx b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/sub_steps/rules_file_upload/rules_file_upload.tsx index dfe11234f726b..f8a70d0bf1365 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/sub_steps/rules_file_upload/rules_file_upload.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/sub_steps/rules_file_upload/rules_file_upload.tsx @@ -138,6 +138,7 @@ const formatRuleRow = (row: SplunkRow): OriginalRule => { query: row.result.search, query_language: 'spl', description: row.result['action.escu.eli5']?.trim() || row.result.description, + severity: row.result['alert.severity'] as OriginalRule['severity'], }; if (row.result['action.correlationsearch.annotations']) { diff --git a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/sub_steps/rules_file_upload/splunk_rules.test.data.ts b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/sub_steps/rules_file_upload/splunk_rules.test.data.ts new file mode 100644 index 0000000000000..0d40d903d96d0 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/sub_steps/rules_file_upload/splunk_rules.test.data.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. + */ + +export const splunkTestRules = [ + { + preview: false, + result: { + id: 'some_id1', + title: 'Alert with IP Method and URI Filters with Default Severity', + search: + 'source="testing_data.zip:*" clientip="198.35.1.75" method=POST uri_path="/cart/error.do"', + description: '', + 'alert.severity': '3', + }, + }, + { + preview: false, + result: { + id: 'some_id2', + title: 'New Alert with Index filter', + search: 'source="testing_data.zip:*" | search server="MacBookPro.fritz.box" index=main', + description: 'Tutorial data based on host name', + 'alert.severity': '5', + }, + }, + { + preview: false, + result: { + id: 'some_id3', + title: 'Sample Alert in Essentials', + search: 'source="testing_file.zip:*"', + description: '', + 'alert.severity': '3', + }, + }, + { + preview: false, + lastrow: true, + result: { + id: 'some_id4', + title: 'Tutorial data based on host name', + search: 'source="testing_file.zip:*" \n| search host=vendor_sales', + description: 'Tutorial data based on host name', + 'alert.severity': '5', + }, + }, +]; + +export const invalidTestRulesFormat = [ + { + result: { + some: 'key', + }, + }, + { + result: { + description: { + some: 'key', + }, + }, + }, +]; diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/siem_migrations/rules/constants.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/siem_migrations/rules/constants.ts new file mode 100644 index 0000000000000..8ede8e1c610bd --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/siem_migrations/rules/constants.ts @@ -0,0 +1,25 @@ +/* + * 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 { Severity } from '@kbn/securitysolution-io-ts-alerting-types'; +import { defaultRiskScoreBySeverity } from '../../../../common/detection_engine/constants'; +import type { SplunkSeverity } from './types'; + +export const SPLUNK_ELASTIC_ALERT_SEVERITY_MAP: Record = { + '1': 'low', + '2': 'low', + '3': 'medium', + '4': 'high', + '5': 'critical', +} as const; + +export const ELASTIC_SEVERITY_TO_RISK_SCORE_MAP = defaultRiskScoreBySeverity; + +export const DEFAULT_TRANSLATION_SEVERITY: Severity = 'low'; + +export const DEFAULT_TRANSLATION_RISK_SCORE = + ELASTIC_SEVERITY_TO_RISK_SCORE_MAP[DEFAULT_TRANSLATION_SEVERITY]; diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/siem_migrations/rules/data/rule_migrations_field_maps.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/siem_migrations/rules/data/rule_migrations_field_maps.ts index 491f14597220c..0bac373877cef 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/siem_migrations/rules/data/rule_migrations_field_maps.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/siem_migrations/rules/data/rule_migrations_field_maps.ts @@ -26,6 +26,7 @@ export const ruleMigrationsFieldMap: FieldMap { + describe('getElasticRiskScoreFromOriginalRule', () => { + describe('splunk', () => { + describe('when there is a vendor match', () => { + it('should return the correct risk score', () => { + const riskScore = getElasticRiskScoreFromOriginalRule(defaultSplunkRule); + expect(riskScore).toEqual(47); + }); + }); + describe('when there is no vendor match', () => { + it('should return default risk score', () => { + expect( + getElasticRiskScoreFromOriginalRule({ + ...defaultSplunkRule, + /* @ts-expect-error because vendor type is "splunk" which raises error below */ + vendor: 'not_splunk', + query_language: 'not_spl', + }) + ).toEqual(21); + }); + }); + }); + }); + + describe('mapSplunkSeverityToElasticSeverity', () => { + describe('when there is a match', () => { + const tests: Array<{ + input: SplunkSeverity; + expected: string; + }> = [ + { + input: '1', + expected: 'low', + }, + { + input: '2', + expected: 'low', + }, + { + input: '3', + expected: 'medium', + }, + { + input: '4', + expected: 'high', + }, + { + input: '5', + expected: 'critical', + }, + ]; + + tests.forEach((test) => { + it(`should maps severity ${test.input} to ${test.expected}`, () => { + expect(mapSplunkSeverityToElasticSeverity(test.input)).toEqual(test.expected); + }); + }); + }); + describe('when there is no match', () => { + it('should return default severity when there is no match', () => { + expect( + mapSplunkSeverityToElasticSeverity('an_invalid_severity' as unknown as SplunkSeverity) + ).toEqual('low'); + }); + + it('should return default severity when there is no severity', () => { + expect(mapSplunkSeverityToElasticSeverity()).toEqual('low'); + }); + }); + }); + + describe('getElasticSeverityFromOriginalRule', () => { + describe('splunk', () => { + describe('when there is a vendor match', () => { + it('should call the correct function with the correct severity', () => { + expect(getElasticSeverityFromOriginalRule(defaultSplunkRule)).toBe('medium'); + }); + }); + describe('when there is no vendor match', () => { + it('should return default severity when there is no match', () => { + expect( + getElasticSeverityFromOriginalRule({ + ...defaultSplunkRule, + /* @ts-expect-error because vendor type is "splunk" which raises error below */ + vendor: undefined, + query_language: 'not_spl', + }) + ).toEqual('low'); + }); + + it('should return default severity when there is no severity', () => { + expect( + getElasticSeverityFromOriginalRule({ + ...defaultSplunkRule, + severity: undefined, + }) + ).toBe('low'); + }); + }); + }); + }); +}); diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/translate_rule/severity.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/translate_rule/severity.ts new file mode 100644 index 0000000000000..b09577a5f2afe --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/translate_rule/severity.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 { Severity } from '@kbn/securitysolution-io-ts-alerting-types'; +import type { OriginalRule } from '../../../../../../../../../../common/siem_migrations/model/rule_migration.gen'; +import type { SplunkSeverity } from '../../../../../../types'; +import { + DEFAULT_TRANSLATION_SEVERITY, + ELASTIC_SEVERITY_TO_RISK_SCORE_MAP, + SPLUNK_ELASTIC_ALERT_SEVERITY_MAP, +} from '../../../../../../constants'; + +export const mapSplunkSeverityToElasticSeverity = (splunkSeverity?: SplunkSeverity): Severity => { + if (!splunkSeverity) { + return DEFAULT_TRANSLATION_SEVERITY; + } + return SPLUNK_ELASTIC_ALERT_SEVERITY_MAP[splunkSeverity] || DEFAULT_TRANSLATION_SEVERITY; +}; + +export const getElasticSeverityFromOriginalRule = (originalRule: OriginalRule) => { + return originalRule.query_language === 'spl' || originalRule.vendor === 'splunk' + ? mapSplunkSeverityToElasticSeverity(originalRule.severity as SplunkSeverity) + : DEFAULT_TRANSLATION_SEVERITY; +}; + +export const getElasticRiskScoreFromElasticSeverity = (elasticSeverity: Severity) => { + return ELASTIC_SEVERITY_TO_RISK_SCORE_MAP[elasticSeverity]; +}; + +export const getElasticRiskScoreFromOriginalRule = (originalRule: OriginalRule) => { + if (originalRule.vendor === 'splunk') { + const elasticSeverity = getElasticSeverityFromOriginalRule(originalRule); + return getElasticRiskScoreFromElasticSeverity(elasticSeverity); + } + return ELASTIC_SEVERITY_TO_RISK_SCORE_MAP[DEFAULT_TRANSLATION_SEVERITY]; +}; diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/translate_rule/translate_rule.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/translate_rule/translate_rule.ts index e25a0378f6499..7be5e96d84bb8 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/translate_rule/translate_rule.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/translate_rule/translate_rule.ts @@ -10,6 +10,10 @@ import { cleanMarkdown, generateAssistantComment } from '../../../../../util/com import type { EsqlKnowledgeBase } from '../../../../../util/esql_knowledge_base'; import type { GraphNode } from '../../types'; import { ESQL_SYNTAX_TRANSLATION_PROMPT } from './prompts'; +import { + getElasticRiskScoreFromOriginalRule, + getElasticSeverityFromOriginalRule, +} from './severity'; interface GetTranslateRuleNodeParams { esqlKnowledgeBase: EsqlKnowledgeBase; @@ -48,6 +52,8 @@ export const getTranslateRuleNode = ({ integration_ids: [integrationId], query: esqlQuery, query_language: 'esql', + risk_score: getElasticRiskScoreFromOriginalRule(state.original_rule), + severity: getElasticSeverityFromOriginalRule(state.original_rule), }, }; }; diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/translation_result/translation_result.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/translation_result/translation_result.ts index b38dd3ed45ae3..2ee45c9f9219e 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/translation_result/translation_result.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/siem_migrations/rules/task/agent/sub_graphs/translate_rule/nodes/translation_result/translation_result.ts @@ -8,8 +8,8 @@ import { DEFAULT_TRANSLATION_RISK_SCORE, DEFAULT_TRANSLATION_SEVERITY, - RuleTranslationResult, -} from '../../../../../../../../../../common/siem_migrations/constants'; +} from '../../../../../../constants'; +import { RuleTranslationResult } from '../../../../../../../../../../common/siem_migrations/constants'; import type { GraphNode } from '../../types'; export const getTranslationResultNode = (): GraphNode => { diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/siem_migrations/rules/types.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/siem_migrations/rules/types.ts index 521c03a9e38c4..cb01f39978c7c 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/siem_migrations/rules/types.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/siem_migrations/rules/types.ts @@ -56,3 +56,18 @@ export type RuleSemanticSearchResult = RuleMigrationPrebuiltRule & RuleVersions; export type InternalUpdateRuleMigrationData = UpdateRuleMigrationData & { translation_result?: RuleMigrationTranslationResult; }; + +/** + * + * Based on the severity levels defined in the Splunk Common Information Model (CIM) documentation + * + * https://docs.splunk.com/Documentation/CIM/6.0.2/User/Alerts + * + * '1': 'INFO'; + * '2': 'LOW'; + * '3': 'MEDIUM'; + * '4': 'HIGH'; + * '5': 'CRITICAL'; + * + **/ +export type SplunkSeverity = '1' | '2' | '3' | '4' | '5';