Skip to content

Commit

Permalink
[8.18] [Security Solution] - Feat Add Severity and risk_score to …
Browse files Browse the repository at this point in the history
…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)](#211202)

<!--- Backport version: 9.6.6 -->

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

<!--BACKPORT [{"author":{"name":"Jatin
Kathuria","email":"jatin.kathuria@elastic.co"},"sourceCommit":{"committedDate":"2025-02-21T16:24:44Z","message":"[Security
Solution] - Feat Add `Severity` and `risk_score` to the Siem migrations
(#211202)\n\n## Summary\n\nHandles below Features:\n-
https://github.com/elastic/security-team/issues/11837\n\n\nThis PR adds
`risk_score` and `severity` based on below 3 rules\n- `Rule Severity`
should be mapped to Splunk's `alert.severity`.\n- `Rule Severity` values
should be mapped as mentioned in below section\nMapping Elastic Security
Rule's Severity with Splunk's Severity\n\n> \n> |Splunk's Severity|
Elastic Rule Severity |\n> |---|---|\n> |1- Info|Low|\n> |2-Low|Low|\n>
|3-Medium|Medium|\n> |4-High|High|\n> |5-Critical|Critical|\n\n- Elastic
Security Rule's `Risk Score` derived from the `Severity` of\nthe Rulet
based on below
mapping(\n[Source](https://www.elastic.co/guide/en/security/current/rules-ui-create.html#rule-ui-basic-params)\n)\n\n\n![Image](https://github.com/user-attachments/assets/d88acd1f-9b73-467f-bf16-0dbecadec465)\n\n\n##
Desk
Testing\n\n\n[splunk_rules_test_severity.json](https://github.com/user-attachments/files/18825855/splunk_rules_test_severity.json)\n\n\n1.
Use the above attached test file which has the
`alert.severity`\nexported from Splunk.\n2. Check the Severity of the
translated rule should match the mapping\ngiven above. Expect results
like below :\n\n<img width=\"1474\" alt=\"Screenshot 2025-02-17 at 14 19
23\"\nsrc=\"https://github.com/user-attachments/assets/a8459c71-3208-480e-8049-05293a0a3d2a\"\n/>\n\n\n\n###
Checklist\n\nCheck the PR satisfies following conditions. \n\nReviewers
should verify this PR satisfies this list as well.\n\n- [x] Any text
added follows [EUI's
writing\nguidelines](https://elastic.github.io/eui/#/guidelines/writing),
uses\nsentence case text and includes
[i18n\nsupport](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md)\n-
[x] [Unit or
functional\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\nwere
updated or added to match the most common scenarios\n\n\n### Identify
risks\n\nDoes this PR introduce any risks? For example, consider risks
like hard\nto test bugs, performance regression, potential of data
loss.\n\nDescribe the risk, its severity, and mitigation for each
identified\nrisk. Invite stakeholders and evaluate how to proceed before
merging.\n\n- [ ] [See some
risk\nexamples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx)\n-
[ ] ...\n\n---------\n\nCo-authored-by: kibanamachine
<42973632+kibanamachine@users.noreply.github.com>","sha":"74ef9fcdee2086bf2c48a35c5c15fb0997fc41dd","branchLabelMapping":{"^v9.1.0$":"main","^v8.19.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","v9.0.0","Team:Threat
Hunting","backport:version","v8.18.0","v9.1.0","v8.19.0"],"title":"[Security
Solution] - Feat Add `Severity` and `risk_score` to the Siem
migrations","number":211202,"url":"https://github.com/elastic/kibana/pull/211202","mergeCommit":{"message":"[Security
Solution] - Feat Add `Severity` and `risk_score` to the Siem migrations
(#211202)\n\n## Summary\n\nHandles below Features:\n-
https://github.com/elastic/security-team/issues/11837\n\n\nThis PR adds
`risk_score` and `severity` based on below 3 rules\n- `Rule Severity`
should be mapped to Splunk's `alert.severity`.\n- `Rule Severity` values
should be mapped as mentioned in below section\nMapping Elastic Security
Rule's Severity with Splunk's Severity\n\n> \n> |Splunk's Severity|
Elastic Rule Severity |\n> |---|---|\n> |1- Info|Low|\n> |2-Low|Low|\n>
|3-Medium|Medium|\n> |4-High|High|\n> |5-Critical|Critical|\n\n- Elastic
Security Rule's `Risk Score` derived from the `Severity` of\nthe Rulet
based on below
mapping(\n[Source](https://www.elastic.co/guide/en/security/current/rules-ui-create.html#rule-ui-basic-params)\n)\n\n\n![Image](https://github.com/user-attachments/assets/d88acd1f-9b73-467f-bf16-0dbecadec465)\n\n\n##
Desk
Testing\n\n\n[splunk_rules_test_severity.json](https://github.com/user-attachments/files/18825855/splunk_rules_test_severity.json)\n\n\n1.
Use the above attached test file which has the
`alert.severity`\nexported from Splunk.\n2. Check the Severity of the
translated rule should match the mapping\ngiven above. Expect results
like below :\n\n<img width=\"1474\" alt=\"Screenshot 2025-02-17 at 14 19
23\"\nsrc=\"https://github.com/user-attachments/assets/a8459c71-3208-480e-8049-05293a0a3d2a\"\n/>\n\n\n\n###
Checklist\n\nCheck the PR satisfies following conditions. \n\nReviewers
should verify this PR satisfies this list as well.\n\n- [x] Any text
added follows [EUI's
writing\nguidelines](https://elastic.github.io/eui/#/guidelines/writing),
uses\nsentence case text and includes
[i18n\nsupport](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md)\n-
[x] [Unit or
functional\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\nwere
updated or added to match the most common scenarios\n\n\n### Identify
risks\n\nDoes this PR introduce any risks? For example, consider risks
like hard\nto test bugs, performance regression, potential of data
loss.\n\nDescribe the risk, its severity, and mitigation for each
identified\nrisk. Invite stakeholders and evaluate how to proceed before
merging.\n\n- [ ] [See some
risk\nexamples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx)\n-
[ ] ...\n\n---------\n\nCo-authored-by: kibanamachine
<42973632+kibanamachine@users.noreply.github.com>","sha":"74ef9fcdee2086bf2c48a35c5c15fb0997fc41dd"}},"sourceBranch":"main","suggestedTargetBranches":["9.0","8.18","8.x"],"targetPullRequestStates":[{"branch":"9.0","label":"v9.0.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"8.18","label":"v8.18.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"main","label":"v9.1.0","branchLabelMappingKey":"^v9.1.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/211202","number":211202,"mergeCommit":{"message":"[Security
Solution] - Feat Add `Severity` and `risk_score` to the Siem migrations
(#211202)\n\n## Summary\n\nHandles below Features:\n-
https://github.com/elastic/security-team/issues/11837\n\n\nThis PR adds
`risk_score` and `severity` based on below 3 rules\n- `Rule Severity`
should be mapped to Splunk's `alert.severity`.\n- `Rule Severity` values
should be mapped as mentioned in below section\nMapping Elastic Security
Rule's Severity with Splunk's Severity\n\n> \n> |Splunk's Severity|
Elastic Rule Severity |\n> |---|---|\n> |1- Info|Low|\n> |2-Low|Low|\n>
|3-Medium|Medium|\n> |4-High|High|\n> |5-Critical|Critical|\n\n- Elastic
Security Rule's `Risk Score` derived from the `Severity` of\nthe Rulet
based on below
mapping(\n[Source](https://www.elastic.co/guide/en/security/current/rules-ui-create.html#rule-ui-basic-params)\n)\n\n\n![Image](https://github.com/user-attachments/assets/d88acd1f-9b73-467f-bf16-0dbecadec465)\n\n\n##
Desk
Testing\n\n\n[splunk_rules_test_severity.json](https://github.com/user-attachments/files/18825855/splunk_rules_test_severity.json)\n\n\n1.
Use the above attached test file which has the
`alert.severity`\nexported from Splunk.\n2. Check the Severity of the
translated rule should match the mapping\ngiven above. Expect results
like below :\n\n<img width=\"1474\" alt=\"Screenshot 2025-02-17 at 14 19
23\"\nsrc=\"https://github.com/user-attachments/assets/a8459c71-3208-480e-8049-05293a0a3d2a\"\n/>\n\n\n\n###
Checklist\n\nCheck the PR satisfies following conditions. \n\nReviewers
should verify this PR satisfies this list as well.\n\n- [x] Any text
added follows [EUI's
writing\nguidelines](https://elastic.github.io/eui/#/guidelines/writing),
uses\nsentence case text and includes
[i18n\nsupport](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md)\n-
[x] [Unit or
functional\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\nwere
updated or added to match the most common scenarios\n\n\n### Identify
risks\n\nDoes this PR introduce any risks? For example, consider risks
like hard\nto test bugs, performance regression, potential of data
loss.\n\nDescribe the risk, its severity, and mitigation for each
identified\nrisk. Invite stakeholders and evaluate how to proceed before
merging.\n\n- [ ] [See some
risk\nexamples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx)\n-
[ ] ...\n\n---------\n\nCo-authored-by: kibanamachine
<42973632+kibanamachine@users.noreply.github.com>","sha":"74ef9fcdee2086bf2c48a35c5c15fb0997fc41dd"}},{"branch":"8.x","label":"v8.19.0","branchLabelMappingKey":"^v8.19.0$","isSourceBranch":false,"state":"NOT_CREATED"}]}]
BACKPORT-->

Co-authored-by: Jatin Kathuria <jatin.kathuria@elastic.co>
  • Loading branch information
kibanamachine and logeekal authored Feb 21, 2025
1 parent 516382f commit 65065aa
Show file tree
Hide file tree
Showing 22 changed files with 521 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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<Severity, number> = {
low: RISK_SCORE_LOW,
medium: RISK_SCORE_MEDIUM,
high: RISK_SCORE_HIGH,
critical: RISK_SCORE_CRITICAL,
};
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
});

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -64,10 +58,3 @@ export const useSeverityOptions = () => {

return severityOptions;
};

export const defaultRiskScoreBySeverity: Record<Severity, number> = {
low: RISK_SCORE_LOW,
medium: RISK_SCORE_MEDIUM,
high: RISK_SCORE_HIGH,
critical: RISK_SCORE_CRITICAL,
};
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export const DATA_INPUT_FILE_UPLOAD_BUTTON = i18n.translate(

export const UploadFileButton = React.memo<PropsForButton<EuiButtonProps>>((props) => {
return (
<EuiButton color="success" {...props}>
<EuiButton data-test-subj="uploadFileButton" color="success" {...props}>
{DATA_INPUT_FILE_UPLOAD_BUTTON}
</EuiButton>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -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<RulesFileUploadProps> = {}) => {
const finalProps = {
...defaultProps,
...props,
};
render(
<I18nProvider>
<RulesFileUpload {...finalProps} />
</I18nProvider>
);
};

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();
});
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ const formatRuleRow = (row: SplunkRow<SplunkRulesResult>): 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']) {
Expand Down
Loading

0 comments on commit 65065aa

Please sign in to comment.