diff --git a/packages/kbn-securitysolution-utils/src/transform_data_to_ndjson/index.test.ts b/packages/kbn-securitysolution-utils/src/transform_data_to_ndjson/index.test.ts index 14f6201cc828..32300a2e66c9 100644 --- a/packages/kbn-securitysolution-utils/src/transform_data_to_ndjson/index.test.ts +++ b/packages/kbn-securitysolution-utils/src/transform_data_to_ndjson/index.test.ts @@ -20,6 +20,7 @@ const getRulesSchemaMock = (anchorDate: string = ANCHOR_DATE) => ({ enabled: true, false_positives: ['false positive 1', 'false positive 2'], from: 'now-6m', + investigation_fields: ['custom.field1', 'custom.field2'], immutable: false, name: 'Query with a rule id', query: 'user.name: root or user.name: admin', diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/common_attributes.ts b/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/common_attributes.ts index 9c3288614fa9..db46af9091be 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/common_attributes.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/common_attributes.ts @@ -55,6 +55,14 @@ export const RuleAuthorArray = t.array(t.string); // should be non-empty strings export type RuleFalsePositiveArray = t.TypeOf; export const RuleFalsePositiveArray = t.array(t.string); // should be non-empty strings? +/** + * User defined fields to display in areas such as alert details and exceptions auto-populate + * Field added in PR - https://github.com/elastic/kibana/pull/163235 + * @example const investigationFields: RuleCustomHighlightedFieldArray = ['host.os.name'] + */ +export type RuleCustomHighlightedFieldArray = t.TypeOf; +export const RuleCustomHighlightedFieldArray = t.array(NonEmptyString); + export type RuleReferenceArray = t.TypeOf; export const RuleReferenceArray = t.array(t.string); // should be non-empty strings? diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_request_schema.test.ts b/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_request_schema.test.ts index a49112a98f81..9d15c355df60 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_request_schema.test.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_request_schema.test.ts @@ -1289,6 +1289,36 @@ describe('rules schema', () => { expect(message.schema).toEqual({}); expect(getPaths(left(message.errors))).toEqual(['invalid keys "data_view_id"']); }); + + test('You can optionally send in an array of investigation_fields', () => { + const payload: RuleCreateProps = { + ...getCreateRulesSchemaMock(), + investigation_fields: ['field1', 'field2'], + }; + + const decoded = RuleCreateProps.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('You cannot send in an array of investigation_fields that are numbers', () => { + const payload = { + ...getCreateRulesSchemaMock(), + investigation_fields: [0, 1, 2], + }; + + const decoded = RuleCreateProps.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "0" supplied to "investigation_fields"', + 'Invalid value "1" supplied to "investigation_fields"', + 'Invalid value "2" supplied to "investigation_fields"', + ]); + expect(message.schema).toEqual({}); + }); }); describe('response', () => { diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_response_schema.mock.ts b/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_response_schema.mock.ts index 521e9918a652..20d2c1104251 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_response_schema.mock.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_response_schema.mock.ts @@ -64,6 +64,7 @@ const getResponseBaseParams = (anchorDate: string = ANCHOR_DATE): SharedResponse timestamp_override: undefined, timestamp_override_fallback_disabled: undefined, namespace: undefined, + investigation_fields: undefined, }); export const getRulesSchemaMock = (anchorDate: string = ANCHOR_DATE): QueryRule => ({ @@ -77,6 +78,7 @@ export const getRulesSchemaMock = (anchorDate: string = ANCHOR_DATE): QueryRule saved_id: undefined, response_actions: undefined, alert_suppression: undefined, + investigation_fields: undefined, }); export const getSavedQuerySchemaMock = (anchorDate: string = ANCHOR_DATE): SavedQueryRule => ({ diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_response_schema.test.ts b/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_response_schema.test.ts index 0032ee60267c..cd1562b1c4c4 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_response_schema.test.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_response_schema.test.ts @@ -232,4 +232,65 @@ describe('Rule response schema', () => { expect(message.schema).toEqual({}); }); }); + + describe('investigation_fields', () => { + test('it should validate rule with empty array for "investigation_fields"', () => { + const payload = getRulesSchemaMock(); + payload.investigation_fields = []; + + const decoded = RuleResponse.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + const expected = { ...getRulesSchemaMock(), investigation_fields: [] }; + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(expected); + }); + + test('it should validate rule with "investigation_fields"', () => { + const payload = getRulesSchemaMock(); + payload.investigation_fields = ['foo', 'bar']; + + const decoded = RuleResponse.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + const expected = { ...getRulesSchemaMock(), investigation_fields: ['foo', 'bar'] }; + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(expected); + }); + + test('it should validate undefined for "investigation_fields"', () => { + const payload: RuleResponse = { + ...getRulesSchemaMock(), + investigation_fields: undefined, + }; + + const decoded = RuleResponse.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + const expected = { ...getRulesSchemaMock(), investigation_fields: undefined }; + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(expected); + }); + + test('it should NOT validate a string for "investigation_fields"', () => { + const payload: Omit & { + investigation_fields: string; + } = { + ...getRulesSchemaMock(), + investigation_fields: 'foo', + }; + + const decoded = RuleResponse.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "foo" supplied to "investigation_fields"', + ]); + expect(message.schema).toEqual({}); + }); + }); }); diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_schemas.ts b/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_schemas.ts index 834607906b2e..24badba560b8 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_schemas.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema/rule_schemas.ts @@ -53,6 +53,7 @@ import { RelatedIntegrationArray, RequiredFieldArray, RuleAuthorArray, + RuleCustomHighlightedFieldArray, RuleDescription, RuleFalsePositiveArray, RuleFilterArray, @@ -116,6 +117,7 @@ export const baseSchema = buildRuleSchemas({ output_index: AlertsIndex, namespace: AlertsIndexNamespace, meta: RuleMetadata, + investigation_fields: RuleCustomHighlightedFieldArray, // Throttle throttle: RuleActionThrottle, }, diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.test.tsx index c6a022ff0998..a92ec9901d7e 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.test.tsx @@ -141,6 +141,45 @@ describe('AlertSummaryView', () => { }); }); }); + test('User specified investigation fields appear in summary rows', async () => { + const mockData = mockAlertDetailsData.map((item) => { + if (item.category === 'event' && item.field === 'event.category') { + return { + ...item, + values: ['network'], + originalValue: ['network'], + }; + } + return item; + }); + const renderProps = { + ...props, + investigationFields: ['custom.field'], + data: [ + ...mockData, + { category: 'custom', field: 'custom.field', values: ['blob'], originalValue: 'blob' }, + ] as TimelineEventsDetailsItem[], + }; + await act(async () => { + const { getByText } = render( + + + + ); + + [ + 'custom.field', + 'host.name', + 'user.name', + 'destination.address', + 'source.address', + 'source.port', + 'process.name', + ].forEach((fieldId) => { + expect(getByText(fieldId)); + }); + }); + }); test('Network event renders the correct summary rows', async () => { const renderProps = { ...props, diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.tsx index 5704eab502ed..4eb81ddf5770 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.tsx @@ -21,10 +21,30 @@ const AlertSummaryViewComponent: React.FC<{ title: string; goToTable: () => void; isReadOnly?: boolean; -}> = ({ browserFields, data, eventId, isDraggable, scopeId, title, goToTable, isReadOnly }) => { + investigationFields?: string[]; +}> = ({ + browserFields, + data, + eventId, + isDraggable, + scopeId, + title, + goToTable, + isReadOnly, + investigationFields, +}) => { const summaryRows = useMemo( - () => getSummaryRows({ browserFields, data, eventId, isDraggable, scopeId, isReadOnly }), - [browserFields, data, eventId, isDraggable, scopeId, isReadOnly] + () => + getSummaryRows({ + browserFields, + data, + eventId, + isDraggable, + scopeId, + isReadOnly, + investigationFields, + }), + [browserFields, data, eventId, isDraggable, scopeId, isReadOnly, investigationFields] ); return ( diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx index ff5d44d2b9eb..87f450ecb43b 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx @@ -21,6 +21,8 @@ import styled from 'styled-components'; import { isEmpty } from 'lodash'; import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs'; +import { useBasicDataFromDetailsData } from '../../../timelines/components/side_panel/event_details/helpers'; +import { useRuleWithFallback } from '../../../detection_engine/rule_management/logic/use_rule_with_fallback'; import type { RawEventData } from '../../../../common/types/response_actions'; import { useResponseActionsView } from './response_actions_view'; import { useIsExperimentalFeatureEnabled } from '../../hooks/use_experimental_features'; @@ -169,6 +171,8 @@ const EventDetailsComponent: React.FC = ({ const goToTableTab = useCallback(() => setSelectedTabId(EventsViewType.tableView), []); const eventFields = useMemo(() => getEnrichmentFields(data), [data]); + const { ruleId } = useBasicDataFromDetailsData(data); + const { rule: maybeRule } = useRuleWithFallback(ruleId); const existingEnrichments = useMemo( () => isAlert @@ -284,6 +288,7 @@ const EventDetailsComponent: React.FC = ({ isReadOnly, }} goToTable={goToTableTab} + investigationFields={maybeRule?.investigation_fields ?? []} /> = ({ userRisk, allEnrichments, isEnrichmentsLoading, + maybeRule, ] ); diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/get_alert_summary_rows.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/get_alert_summary_rows.tsx index 82be0711b75c..160a91a9874c 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/get_alert_summary_rows.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/get_alert_summary_rows.tsx @@ -215,6 +215,15 @@ function getFieldsByRuleType(ruleType?: string): EventSummaryField[] { } } +/** + * Gets the fields to display based on custom rules and configuration + * @param customs The list of custom-defined fields to display + * @returns The list of custom-defined fields to display + */ +function getHighlightedFieldsOverride(customs: string[]): EventSummaryField[] { + return customs.map((field) => ({ id: field })); +} + /** This function is exported because it is used in the Exception Component to populate the conditions with the Highlighted Fields. Additionally, the new @@ -229,12 +238,15 @@ export function getEventFieldsToDisplay({ eventCategories, eventCode, eventRuleType, + highlightedFieldsOverride, }: { eventCategories: EventCategories; eventCode?: string; eventRuleType?: string; + highlightedFieldsOverride: string[]; }): EventSummaryField[] { const fields = [ + ...getHighlightedFieldsOverride(highlightedFieldsOverride), ...alwaysDisplayedFields, ...getFieldsByCategory(eventCategories), ...getFieldsByEventCode(eventCode, eventCategories), @@ -281,11 +293,13 @@ export const getSummaryRows = ({ eventId, isDraggable = false, isReadOnly = false, + investigationFields, }: { data: TimelineEventsDetailsItem[]; browserFields: BrowserFields; scopeId: string; eventId: string; + investigationFields?: string[]; isDraggable?: boolean; isReadOnly?: boolean; }) => { @@ -306,6 +320,7 @@ export const getSummaryRows = ({ eventCategories, eventCode, eventRuleType, + highlightedFieldsOverride: investigationFields ?? [], }); return data != null diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/helpers.test.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/helpers.test.ts index 80a7971aedfc..5f1a36f93102 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/helpers.test.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/helpers.test.ts @@ -555,6 +555,7 @@ describe('helpers', () => { severity_mapping: [], tags: ['tag1', 'tag2'], threat: getThreatMock(), + investigation_fields: ['foo', 'bar'], }; expect(result).toEqual(expected); @@ -635,6 +636,7 @@ describe('helpers', () => { severity_mapping: [], tags: ['tag1', 'tag2'], threat: getThreatMock(), + investigation_fields: ['foo', 'bar'], }; expect(result).toEqual(expected); @@ -659,6 +661,7 @@ describe('helpers', () => { severity_mapping: [], tags: ['tag1', 'tag2'], threat: getThreatMock(), + investigation_fields: ['foo', 'bar'], }; expect(result).toEqual(expected); @@ -702,6 +705,7 @@ describe('helpers', () => { severity_mapping: [], tags: ['tag1', 'tag2'], threat: getThreatMock(), + investigation_fields: ['foo', 'bar'], }; expect(result).toEqual(expected); @@ -754,6 +758,7 @@ describe('helpers', () => { ], }, ], + investigation_fields: ['foo', 'bar'], }; expect(result).toEqual(expected); @@ -782,6 +787,95 @@ describe('helpers', () => { threat: getThreatMock(), timestamp_override: 'event.ingest', timestamp_override_fallback_disabled: true, + investigation_fields: ['foo', 'bar'], + }; + + expect(result).toEqual(expected); + }); + + test('returns formatted object if investigation_fields is empty array', () => { + const mockStepData: AboutStepRule = { + ...mockData, + investigationFields: [], + }; + const result = formatAboutStepData(mockStepData); + const expected: AboutStepRuleJson = { + author: ['Elastic'], + description: '24/7', + false_positives: ['test'], + license: 'Elastic License', + name: 'Query with rule-id', + note: '# this is some markdown documentation', + references: ['www.test.co'], + risk_score: 21, + risk_score_mapping: [], + severity: 'low', + severity_mapping: [], + tags: ['tag1', 'tag2'], + rule_name_override: undefined, + threat_indicator_path: undefined, + timestamp_override: undefined, + timestamp_override_fallback_disabled: undefined, + threat: getThreatMock(), + investigation_fields: [], + }; + + expect(result).toEqual(expected); + }); + + test('returns formatted object with investigation_fields', () => { + const mockStepData: AboutStepRule = { + ...mockData, + investigationFields: ['foo', 'bar'], + }; + const result = formatAboutStepData(mockStepData); + const expected: AboutStepRuleJson = { + author: ['Elastic'], + description: '24/7', + false_positives: ['test'], + license: 'Elastic License', + name: 'Query with rule-id', + note: '# this is some markdown documentation', + references: ['www.test.co'], + risk_score: 21, + risk_score_mapping: [], + severity: 'low', + severity_mapping: [], + tags: ['tag1', 'tag2'], + threat: getThreatMock(), + investigation_fields: ['foo', 'bar'], + threat_indicator_path: undefined, + timestamp_override: undefined, + timestamp_override_fallback_disabled: undefined, + }; + + expect(result).toEqual(expected); + }); + + test('returns formatted object if investigation_fields includes empty string', () => { + const mockStepData: AboutStepRule = { + ...mockData, + investigationFields: [' '], + }; + const result = formatAboutStepData(mockStepData); + const expected: AboutStepRuleJson = { + author: ['Elastic'], + description: '24/7', + false_positives: ['test'], + license: 'Elastic License', + name: 'Query with rule-id', + note: '# this is some markdown documentation', + references: ['www.test.co'], + risk_score: 21, + risk_score_mapping: [], + severity: 'low', + severity_mapping: [], + tags: ['tag1', 'tag2'], + threat: getThreatMock(), + investigation_fields: [], + threat_indicator_path: undefined, + timestamp_override: undefined, + timestamp_override_fallback_disabled: undefined, }; expect(result).toEqual(expected); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/helpers.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/helpers.ts index ea466771bf8a..c3cb11e907a1 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/helpers.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/pages/rule_creation/helpers.ts @@ -485,6 +485,7 @@ export const formatAboutStepData = ( const { author, falsePositives, + investigationFields, references, riskScore, severity, @@ -524,6 +525,7 @@ export const formatAboutStepData = ( : {}), false_positives: falsePositives.filter((item) => !isEmpty(item)), references: references.filter((item) => !isEmpty(item)), + investigation_fields: investigationFields.filter((item) => !isEmpty(item.trim())), risk_score: riskScore.value, risk_score_mapping: riskScore.isMappingChecked ? riskScore.mapping.filter((m) => m.field != null && m.field !== '') diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/add_exception_flyout/index.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/add_exception_flyout/index.test.tsx index 2e173812b010..6150cb3b6d0e 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/add_exception_flyout/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/add_exception_flyout/index.test.tsx @@ -662,6 +662,42 @@ describe('When the add exception modal is opened', () => { expect(getByTestId('entryType')).toHaveTextContent('match'); expect(getByTestId('entryValue')).toHaveTextContent('test/path'); }); + + it('should include rule defined custom highlighted fields', () => { + const wrapper = render( + (() => ( + + + + ))() + ); + const { getByTestId, getAllByTestId } = wrapper; + expect(getByTestId('alertExceptionBuilder')).toBeInTheDocument(); + expect(getAllByTestId('entryField')[0]).toHaveTextContent('foo.bar'); + expect(getAllByTestId('entryOperator')[0]).toHaveTextContent('included'); + expect(getAllByTestId('entryType')[0]).toHaveTextContent('match'); + expect(getAllByTestId('entryValue')[0]).toHaveTextContent('blob'); + expect(getAllByTestId('entryField')[1]).toHaveTextContent('file.path'); + expect(getAllByTestId('entryOperator')[1]).toHaveTextContent('included'); + expect(getAllByTestId('entryType')[1]).toHaveTextContent('match'); + expect(getAllByTestId('entryValue')[1]).toHaveTextContent('test/path'); + }); }); describe('bulk closeable alert data is passed in', () => { let wrapper: ReactWrapper; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/add_exception_flyout/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/add_exception_flyout/index.tsx index 41bcefebeb19..d7081f195fef 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/add_exception_flyout/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/add_exception_flyout/index.tsx @@ -346,6 +346,9 @@ export const AddExceptionFlyout = memo(function AddExceptionFlyout({ const populatedException = getPrepopulatedRuleExceptionWithHighlightFields({ alertData, exceptionItemName, + // With "rule_default" type, there is only ever one rule associated. + // That is why it's ok to pull just the first item from rules array here. + ruleCustomHighlightedFields: rules?.[0]?.investigation_fields ?? [], }); if (populatedException) { setComment(i18n.ADD_RULE_EXCEPTION_FROM_ALERT_COMMENT(alertData._id)); @@ -354,7 +357,7 @@ export const AddExceptionFlyout = memo(function AddExceptionFlyout({ } } } - }, [listType, exceptionItemName, alertData, setInitialExceptionItems, setComment]); + }, [listType, exceptionItemName, alertData, rules, setInitialExceptionItems, setComment]); const osTypesSelection = useMemo((): OsTypeArray => { return hasAlertData ? retrieveAlertOsTypes(alertData) : selectedOs ? [...selectedOs] : []; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/utils/helpers.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/utils/helpers.test.tsx index 33b1a53ad1b1..e7a3d40dd9ad 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/utils/helpers.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/utils/helpers.test.tsx @@ -1560,6 +1560,27 @@ describe('Exception helpers', () => { }, { field: 'process.name', operator: 'included', type: 'match', value: 'malware writer' }, ]; + const expectedExceptionEntriesWithCustomHighlightedFields = [ + { + field: 'event.type', + operator: 'included', + type: 'match', + value: 'creation', + }, + { + field: 'agent.id', + operator: 'included', + type: 'match', + value: 'f4f86e7c-29bd-4655-b7d0-a3d08ad0c322', + }, + { + field: 'process.executable', + operator: 'included', + type: 'match', + value: 'C:/malware.exe', + }, + { field: 'process.name', operator: 'included', type: 'match', value: 'malware writer' }, + ]; const entriesWithMatchAny = { field: 'Endpoint.capabilities', operator, @@ -1739,12 +1760,12 @@ describe('Exception helpers', () => { }, ]; it('should return the highlighted fields correctly when eventCode, eventCategory and RuleType are in the alertData', () => { - const res = getAlertHighlightedFields(alertData); + const res = getAlertHighlightedFields(alertData, []); expect(res).toEqual(allHighlightFields); }); it('should return highlighted fields without the file.Ext.quarantine_path when "event.code" is not in the alertData', () => { const alertDataWithoutEventCode = { ...alertData, 'event.code': null }; - const res = getAlertHighlightedFields(alertDataWithoutEventCode); + const res = getAlertHighlightedFields(alertDataWithoutEventCode, []); expect(res).toEqual([ ...baseGeneratedAlertHighlightedFields, { @@ -1763,7 +1784,7 @@ describe('Exception helpers', () => { }); it('should return highlighted fields without the file and process props when "event.category" is not in the alertData', () => { const alertDataWithoutEventCategory = { ...alertData, 'event.category': null }; - const res = getAlertHighlightedFields(alertDataWithoutEventCategory); + const res = getAlertHighlightedFields(alertDataWithoutEventCategory, []); expect(res).toEqual([ ...baseGeneratedAlertHighlightedFields, { @@ -1775,7 +1796,7 @@ describe('Exception helpers', () => { }); it('should return the process highlighted fields correctly when eventCategory is an array', () => { const alertDataEventCategoryProcessArray = { ...alertData, 'event.category': ['process'] }; - const res = getAlertHighlightedFields(alertDataEventCategoryProcessArray); + const res = getAlertHighlightedFields(alertDataEventCategoryProcessArray, []); expect(res).not.toEqual( expect.arrayContaining([ { id: 'file.name' }, @@ -1793,20 +1814,20 @@ describe('Exception helpers', () => { }); it('should return all highlighted fields even when the "kibana.alert.rule.type" is not in the alertData', () => { const alertDataWithoutEventCategory = { ...alertData, 'kibana.alert.rule.type': null }; - const res = getAlertHighlightedFields(alertDataWithoutEventCategory); + const res = getAlertHighlightedFields(alertDataWithoutEventCategory, []); expect(res).toEqual(allHighlightFields); }); it('should return all highlighted fields when there are no fields to be filtered out', () => { jest.mock('./highlighted_fields_config', () => ({ highlightedFieldsPrefixToExclude: [] })); - const res = getAlertHighlightedFields(alertData); + const res = getAlertHighlightedFields(alertData, []); expect(res).toEqual(allHighlightFields); }); it('should exclude the "agent.id" from highlighted fields when agent.type is not "endpoint"', () => { jest.mock('./highlighted_fields_config', () => ({ highlightedFieldsPrefixToExclude: [] })); const alertDataWithoutAgentType = { ...alertData, agent: { ...alertData.agent, type: '' } }; - const res = getAlertHighlightedFields(alertDataWithoutAgentType); + const res = getAlertHighlightedFields(alertDataWithoutAgentType, []); expect(res).toEqual(allHighlightFields.filter((field) => field.id !== AGENT_ID)); }); @@ -1814,10 +1835,14 @@ describe('Exception helpers', () => { jest.mock('./highlighted_fields_config', () => ({ highlightedFieldsPrefixToExclude: [] })); const alertDataWithoutRuleUUID = { ...alertData, 'kibana.alert.rule.uuid': '' }; - const res = getAlertHighlightedFields(alertDataWithoutRuleUUID); + const res = getAlertHighlightedFields(alertDataWithoutRuleUUID, []); expect(res).toEqual(allHighlightFields.filter((field) => field.id !== AGENT_ID)); }); + it('should include custom highlighted fields', () => { + const res = getAlertHighlightedFields(alertData, ['event.type']); + expect(res).toEqual([{ id: 'event.type' }, ...allHighlightFields]); + }); }); describe('getPrepopulatedRuleExceptionWithHighlightFields', () => { it('should not create any exception and return null if there are no highlighted fields', () => { @@ -1826,6 +1851,7 @@ describe('Exception helpers', () => { const res = getPrepopulatedRuleExceptionWithHighlightFields({ alertData: defaultAlertData, exceptionItemName: '', + ruleCustomHighlightedFields: [], }); expect(res).toBe(null); }); @@ -1835,6 +1861,7 @@ describe('Exception helpers', () => { const res = getPrepopulatedRuleExceptionWithHighlightFields({ alertData: defaultAlertData, exceptionItemName: '', + ruleCustomHighlightedFields: [], }); expect(res).toBe(null); }); @@ -1842,6 +1869,7 @@ describe('Exception helpers', () => { const exception = getPrepopulatedRuleExceptionWithHighlightFields({ alertData, exceptionItemName: name, + ruleCustomHighlightedFields: [], }); expect(exception?.entries).toEqual( @@ -1849,6 +1877,21 @@ describe('Exception helpers', () => { ); expect(exception?.name).toEqual(name); }); + it('should create a new exception and populate its entries with the custom highlighted fields', () => { + const exception = getPrepopulatedRuleExceptionWithHighlightFields({ + alertData, + exceptionItemName: name, + ruleCustomHighlightedFields: ['event.type'], + }); + + expect(exception?.entries).toEqual( + expectedExceptionEntriesWithCustomHighlightedFields.map((entry) => ({ + ...entry, + id: '123', + })) + ); + expect(exception?.name).toEqual(name); + }); }); }); }); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/utils/helpers.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/utils/helpers.tsx index 3235276e650a..617dd2901363 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/utils/helpers.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/utils/helpers.tsx @@ -908,11 +908,13 @@ export const buildExceptionEntriesFromAlertFields = ({ export const getPrepopulatedRuleExceptionWithHighlightFields = ({ alertData, exceptionItemName, + ruleCustomHighlightedFields, }: { alertData: AlertData; exceptionItemName: string; + ruleCustomHighlightedFields: string[]; }): ExceptionsBuilderExceptionItem | null => { - const highlightedFields = getAlertHighlightedFields(alertData); + const highlightedFields = getAlertHighlightedFields(alertData, ruleCustomHighlightedFields); if (!highlightedFields.length) return null; const exceptionEntries = buildExceptionEntriesFromAlertFields({ highlightedFields, alertData }); @@ -951,11 +953,13 @@ export const filterHighlightedFields = ( * * Alert field ids filters * @param alertData The Alert data object */ -export const getAlertHighlightedFields = (alertData: AlertData): EventSummaryField[] => { +export const getAlertHighlightedFields = ( + alertData: AlertData, + ruleCustomHighlightedFields: string[] +): EventSummaryField[] => { const eventCategory = get(alertData, EVENT_CATEGORY); const eventCode = get(alertData, EVENT_CODE); const eventRuleType = get(alertData, KIBANA_ALERT_RULE_TYPE); - const eventCategories = { primaryEventCategory: Array.isArray(eventCategory) ? eventCategory[0] : eventCategory, allEventCategories: [eventCategory], @@ -965,6 +969,7 @@ export const getAlertHighlightedFields = (alertData: AlertData): EventSummaryFie eventCategories, eventCode, eventRuleType, + highlightedFieldsOverride: ruleCustomHighlightedFields, }); return filterHighlightedFields(fieldsToDisplay, highlightedFieldsPrefixToExclude, alertData); }; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/types.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/types.ts index 35441d926402..63a6b70356ea 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/types.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/logic/types.ts @@ -73,6 +73,7 @@ import { TimestampField, TimestampOverride, TimestampOverrideFallbackDisabled, + RuleCustomHighlightedFieldArray, } from '../../../../common/api/detection_engine/model/rule_schema'; import type { @@ -201,6 +202,7 @@ export const RuleSchema = t.intersection([ version: RuleVersion, execution_summary: RuleExecutionSummary, alert_suppression: AlertSuppression, + investigation_fields: RuleCustomHighlightedFieldArray, }), ]); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/__mocks__/mock.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/__mocks__/mock.ts index 487052fcbf2e..d525a894a3af 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/__mocks__/mock.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/__mocks__/mock.ts @@ -194,6 +194,7 @@ export const mockAboutStepRule = (): AboutStepRule => ({ tags: ['tag1', 'tag2'], threat: getThreatMock(), note: '# this is some markdown documentation', + investigationFields: ['foo', 'bar'], }); export const mockActionsStepRule = (enabled = false): ActionsStepRule => ({ diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx index c2d477d5a87c..0e1b32c3c77d 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx @@ -15,6 +15,7 @@ import { ExceptionListTypeEnum } from '@kbn/securitysolution-io-ts-list-types'; import { get } from 'lodash/fp'; import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs'; import { TableId } from '@kbn/securitysolution-data-table'; +import { useRuleWithFallback } from '../../../../detection_engine/rule_management/logic/use_rule_with_fallback'; import { DEFAULT_ACTION_BUTTON_WIDTH } from '../../../../common/components/header_actions'; import { isActiveTimeline } from '../../../../helpers'; import { useOsqueryContextActionItem } from '../../osquery/use_osquery_context_action_item'; @@ -384,6 +385,7 @@ export const AddExceptionFlyoutWrapper: React.FC alertStatus, }) => { const { loading: isSignalIndexLoading, signalIndexName } = useSignalIndex(); + const { rule: maybeRule, loading: isRuleLoading } = useRuleWithFallback(ruleId); const { loading: isLoadingAlertData, data } = useQueryAlerts({ query: buildGetAlertByIdQuery(eventId), @@ -429,32 +431,13 @@ export const AddExceptionFlyoutWrapper: React.FC return ruleDataViewId; }, [enrichedAlert, ruleDataViewId]); - // TODO: Do we want to notify user when they are working off of an older version of a rule - // if they select to add an exception from an alert referencing an older rule version? const memoRule = useMemo(() => { - if (enrichedAlert != null && enrichedAlert['kibana.alert.rule.parameters'] != null) { - return [ - { - ...enrichedAlert['kibana.alert.rule.parameters'], - id: ruleId, - rule_id: ruleRuleId, - name: ruleName, - index: memoRuleIndices, - data_view_id: memoDataViewId, - }, - ] as Rule[]; + if (maybeRule) { + return [maybeRule]; } - return [ - { - id: ruleId, - rule_id: ruleRuleId, - name: ruleName, - index: memoRuleIndices, - data_view_id: memoDataViewId, - }, - ] as Rule[]; - }, [enrichedAlert, memoDataViewId, memoRuleIndices, ruleId, ruleName, ruleRuleId]); + return null; + }, [maybeRule]); const isLoading = (isLoadingAlertData && isSignalIndexLoading) || @@ -466,7 +449,7 @@ export const AddExceptionFlyoutWrapper: React.FC rules={memoRule} isEndpointItem={exceptionListType === ExceptionListTypeEnum.ENDPOINT} alertData={enrichedAlert} - isAlertDataLoading={isLoading} + isAlertDataLoading={isLoading || isRuleLoading} alertStatus={alertStatus} isBulkAction={false} showAlertCloseOptions diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/helpers.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/helpers.test.tsx index 8ea4e21509f5..ecfcb5c98123 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/helpers.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/helpers.test.tsx @@ -27,6 +27,7 @@ import { buildUrlsDescription, buildNoteDescription, buildRuleTypeDescription, + buildHighlightedFieldsOverrideDescription, } from './helpers'; import type { ListItems } from './types'; @@ -508,4 +509,28 @@ describe('helpers', () => { expect(result.description).toEqual('Indicator Match'); }); }); + + describe('buildHighlightedFieldsOverrideDescription', () => { + test('returns ListItem with passed in label and custom highlighted fields', () => { + const result: ListItems[] = buildHighlightedFieldsOverrideDescription('Test label', [ + 'foo', + 'bar', + ]); + const wrapper = shallow(result[0].description as React.ReactElement); + const element = wrapper.find( + '[data-test-subj="customHighlightedFieldsStringArrayDescriptionBadgeItem"]' + ); + + expect(result[0].title).toEqual('Test label'); + expect(element.exists()).toBeTruthy(); + expect(element.at(0).text()).toEqual('foo'); + expect(element.at(1).text()).toEqual('bar'); + }); + + test('returns empty array if passed in note is empty string', () => { + const result: ListItems[] = buildHighlightedFieldsOverrideDescription('Test label', []); + + expect(result).toHaveLength(0); + }); + }); }); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/helpers.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/helpers.tsx index d3d79b76b1d0..57fe4f72fd19 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/helpers.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/helpers.tsx @@ -27,15 +27,13 @@ import { FieldIcon } from '@kbn/react-field'; import type { ThreatMapping, Type } from '@kbn/securitysolution-io-ts-alerting-types'; import { FilterBadgeGroup } from '@kbn/unified-search-plugin/public'; +import type { RequiredFieldArray } from '../../../../../common/api/detection_engine/model/rule_schema/common_attributes'; import { MATCHES, AND, OR } from '../../../../common/components/threat_match/translations'; import type { EqlOptionsSelected } from '../../../../../common/search_strategy'; import { assertUnreachable } from '../../../../../common/utility_types'; import * as i18nSeverity from '../severity_mapping/translations'; import * as i18nRiskScore from '../risk_score_mapping/translations'; -import type { - RequiredFieldArray, - Threshold, -} from '../../../../../common/api/detection_engine/model/rule_schema'; +import type { Threshold } from '../../../../../common/api/detection_engine/model/rule_schema'; import * as i18n from './translations'; import type { BuildQueryBarDescription, BuildThreatDescription, ListItems } from './types'; @@ -201,6 +199,38 @@ export const buildUnorderedListArrayDescription = ( return []; }; +export const buildHighlightedFieldsOverrideDescription = ( + label: string, + values: string[] +): ListItems[] => { + if (isEmpty(values)) { + return []; + } + const description = ( + + {values.map((val: string) => + isEmpty(val) ? null : ( + + + {val} + + + ) + )} + + ); + + return [ + { + title: label, + description, + }, + ]; +}; + export const buildStringArrayDescription = ( label: string, field: string, diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/index.test.tsx index a2206c9c562e..5ee4bb4bb1f4 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/index.test.tsx @@ -262,7 +262,7 @@ describe('description_step', () => { mockLicenseService ); - expect(result.length).toEqual(11); + expect(result.length).toEqual(12); }); }); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/index.tsx index d4f7c026d369..8d7c21e386e4 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/description_step/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/description_step/index.tsx @@ -14,11 +14,11 @@ import type { ThreatMapping, Threats, Type } from '@kbn/securitysolution-io-ts-a import type { DataViewBase, Filter } from '@kbn/es-query'; import { FilterStateStore } from '@kbn/es-query'; import { FilterManager } from '@kbn/data-plugin/public'; -import { buildRelatedIntegrationsDescription } from '../related_integrations/integrations_description'; import type { RelatedIntegrationArray, RequiredFieldArray, -} from '../../../../../common/api/detection_engine/model/rule_schema'; +} from '../../../../../common/api/detection_engine/model/rule_schema/common_attributes'; +import { buildRelatedIntegrationsDescription } from '../related_integrations/integrations_description'; import { DEFAULT_TIMELINE_TITLE } from '../../../../timelines/components/timeline/translations'; import type { EqlOptionsSelected } from '../../../../../common/search_strategy'; import { useKibana } from '../../../../common/lib/kibana'; @@ -47,6 +47,7 @@ import { buildAlertSuppressionDescription, buildAlertSuppressionWindowDescription, buildAlertSuppressionMissingFieldsDescription, + buildHighlightedFieldsOverrideDescription, } from './helpers'; import * as i18n from './translations'; import { buildMlJobsDescription } from './build_ml_jobs_description'; @@ -261,6 +262,9 @@ export const getDescriptionItem = ( } else if (field === 'falsePositives') { const values: string[] = get(field, data); return buildUnorderedListArrayDescription(label, field, values); + } else if (field === 'investigationFields') { + const values: string[] = get(field, data); + return buildHighlightedFieldsOverrideDescription(label, values); } else if (field === 'riskScore') { const values: AboutStepRiskScore = get(field, data); return buildRiskScoreDescription(values); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/group_by_fields/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/multi_select_fields/index.tsx similarity index 62% rename from x-pack/plugins/security_solution/public/detections/components/rules/group_by_fields/index.tsx rename to x-pack/plugins/security_solution/public/detections/components/rules/multi_select_fields/index.tsx index 579167c73f4c..9dbd49a2f4f0 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/group_by_fields/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/multi_select_fields/index.tsx @@ -11,40 +11,44 @@ import { EuiToolTip } from '@elastic/eui'; import type { DataViewFieldBase } from '@kbn/es-query'; import type { FieldHook } from '../../../../shared_imports'; import { Field } from '../../../../shared_imports'; -import { GROUP_BY_FIELD_PLACEHOLDER, GROUP_BY_FIELD_LICENSE_WARNING } from './translations'; +import { FIELD_PLACEHOLDER } from './translations'; -interface GroupByFieldsProps { +interface MultiSelectAutocompleteProps { browserFields: DataViewFieldBase[]; isDisabled: boolean; field: FieldHook; + fullWidth?: boolean; + disabledText?: string; } const FIELD_COMBO_BOX_WIDTH = 410; -const fieldDescribedByIds = 'detectionEngineStepDefineRuleGroupByField'; +const fieldDescribedByIds = 'detectionEngineMultiSelectAutocompleteField'; -export const GroupByComponent: React.FC = ({ +export const MultiSelectAutocompleteComponent: React.FC = ({ browserFields, + disabledText, isDisabled, field, -}: GroupByFieldsProps) => { + fullWidth = false, +}: MultiSelectAutocompleteProps) => { const fieldEuiFieldProps = useMemo( () => ({ fullWidth: true, noSuggestions: false, options: browserFields.map((browserField) => ({ label: browserField.name })), - placeholder: GROUP_BY_FIELD_PLACEHOLDER, + placeholder: FIELD_PLACEHOLDER, onCreateOption: undefined, - style: { width: `${FIELD_COMBO_BOX_WIDTH}px` }, + ...(fullWidth ? {} : { style: { width: `${FIELD_COMBO_BOX_WIDTH}px` } }), isDisabled, }), - [browserFields, isDisabled] + [browserFields, isDisabled, fullWidth] ); const fieldComponent = ( ); return isDisabled ? ( - + {fieldComponent} ) : ( @@ -52,4 +56,4 @@ export const GroupByComponent: React.FC = ({ ); }; -export const GroupByFields = React.memo(GroupByComponent); +export const MultiSelectFieldsAutocomplete = React.memo(MultiSelectAutocompleteComponent); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/group_by_fields/translations.ts b/x-pack/plugins/security_solution/public/detections/components/rules/multi_select_fields/translations.ts similarity index 54% rename from x-pack/plugins/security_solution/public/detections/components/rules/group_by_fields/translations.ts rename to x-pack/plugins/security_solution/public/detections/components/rules/multi_select_fields/translations.ts index d0df6a732001..4dd83c607ef0 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/group_by_fields/translations.ts +++ b/x-pack/plugins/security_solution/public/detections/components/rules/multi_select_fields/translations.ts @@ -7,16 +7,9 @@ import { i18n } from '@kbn/i18n'; -export const GROUP_BY_FIELD_PLACEHOLDER = i18n.translate( - 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.groupBy.placeholderText', +export const FIELD_PLACEHOLDER = i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.multiSelectFields.placeholderText', { defaultMessage: 'Select a field', } ); - -export const GROUP_BY_FIELD_LICENSE_WARNING = i18n.translate( - 'xpack.securitySolution.detectionEngine.createRule.stepDefineRule.groupBy.licenseWarning', - { - defaultMessage: 'Alert suppression is enabled with Platinum license or above', - } -); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/default_value.ts b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/default_value.ts index 3ae5441d060d..f8025537f3f1 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/default_value.ts +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/default_value.ts @@ -26,6 +26,7 @@ export const stepAboutDefaultValue: AboutStepRule = { riskScore: { value: 21, mapping: [], isMappingChecked: false }, references: [''], falsePositives: [''], + investigationFields: [], license: '', ruleNameOverride: '', tags: [], diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.test.tsx index 096de7b836a0..a9805bf71d3c 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.test.tsx @@ -274,6 +274,7 @@ describe('StepAboutRuleComponent', () => { technique: [], }, ], + investigationFields: [], }; await act(async () => { @@ -333,6 +334,7 @@ describe('StepAboutRuleComponent', () => { technique: [], }, ], + investigationFields: [], }; await act(async () => { diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.tsx index 7052c29ce788..622153160c9f 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.tsx @@ -33,6 +33,7 @@ import { useFetchIndex } from '../../../../common/containers/source'; import { DEFAULT_INDICATOR_SOURCE_PATH } from '../../../../../common/constants'; import { useKibana } from '../../../../common/lib/kibana'; import { useRuleIndices } from '../../../../detection_engine/rule_management/logic/use_rule_indices'; +import { MultiSelectFieldsAutocomplete } from '../multi_select_fields'; const CommonUseField = getUseField({ component: Field }); @@ -237,6 +238,16 @@ const StepAboutRuleComponent: FC = ({ }} /> + + = { ), labelAppend: OptionalFieldLabel, }, + investigationFields: { + type: FIELD_TYPES.COMBO_BOX, + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldCustomHighlightedFieldsLabel', + { + defaultMessage: 'Custom highlighted fields', + } + ), + labelAppend: OptionalFieldLabel, + }, license: { type: FIELD_TYPES.TEXT, label: i18n.translate( diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/translations.ts b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/translations.ts index f1841430f03f..007cf4d9dd4c 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/translations.ts +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/translations.ts @@ -28,6 +28,13 @@ export const ADD_FALSE_POSITIVE = i18n.translate( } ); +export const ADD_CUSTOM_HIGHLIGHTED_FIELD = i18n.translate( + 'xpack.securitySolution.detectionEngine.createRule.stepAboutRuleForm.addCustomHighlightedFieldDescription', + { + defaultMessage: 'Add a custom highlighted field', + } +); + export const GLOBAL_ENDPOINT_EXCEPTION_LIST = i18n.translate( 'xpack.securitySolution.detectionEngine.createRule.stepAboutRuleForm.endpointExceptionListLabel', { diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx index 779fe5f35a82..a0da91e66056 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx @@ -75,7 +75,7 @@ import { NewTermsFields } from '../new_terms_fields'; import { ScheduleItem } from '../schedule_item_form'; import { DocLink } from '../../../../common/components/links_to_docs/doc_link'; import { defaultCustomQuery } from '../../../pages/detection_engine/rules/utils'; -import { GroupByFields } from '../group_by_fields'; +import { MultiSelectFieldsAutocomplete } from '../multi_select_fields'; import { useLicense } from '../../../../common/hooks/use_license'; import { minimumLicenseForSuppression, @@ -752,9 +752,10 @@ const StepDefineRuleComponent: FC = ({ > { threat: getThreatMock(), timestampOverride: 'event.ingested', timestampOverrideFallbackDisabled: false, + investigationFields: [], }; const scheduleRuleStepData = { from: '0s', interval: '5m' }; const ruleActionsStepData = { @@ -181,6 +182,14 @@ describe('rule helpers', () => { expect(result.note).toEqual(''); }); + + test('returns customHighlightedField as empty array if property does not exist on rule', () => { + const mockedRule = mockRuleWithEverything('test-id'); + delete mockedRule.investigation_fields; + const result: AboutStepRule = getAboutStepsData(mockedRule, false); + + expect(result.investigationFields).toEqual([]); + }); }); describe('determineDetailsValue', () => { diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.tsx index e80ec9591c30..7ef797d64eeb 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.tsx @@ -200,6 +200,7 @@ export const getAboutStepsData = (rule: Rule, detailsView: boolean): AboutStepRu severity, false_positives: falsePositives, risk_score: riskScore, + investigation_fields: investigationFields, tags, threat, threat_indicator_path: threatIndicatorPath, @@ -230,6 +231,7 @@ export const getAboutStepsData = (rule: Rule, detailsView: boolean): AboutStepRu isMappingChecked: riskScoreMapping.length > 0, }, falsePositives, + investigationFields: investigationFields ?? [], threat: threat as Threats, threatIndicatorPath, }; @@ -343,6 +345,7 @@ const commonRuleParamsKeys = [ 'name', 'description', 'false_positives', + 'investigation_fields', 'rule_id', 'max_signals', 'risk_score', diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/types.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/types.ts index 4232481eee86..c5b98b1bfd39 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/types.ts +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/types.ts @@ -89,6 +89,7 @@ export interface AboutStepRule { riskScore: AboutStepRiskScore; references: string[]; falsePositives: string[]; + investigationFields: string[]; license: string; ruleNameOverride: string; tags: string[]; @@ -238,6 +239,7 @@ export interface AboutStepRuleJson { timestamp_override?: TimestampOverride; timestamp_override_fallback_disabled?: boolean; note?: string; + investigation_fields?: string[]; } export interface ScheduleStepRuleJson { diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts index 791d8ae32226..5f54eea162c7 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts @@ -83,6 +83,7 @@ export const stepAboutDefaultValue: AboutStepRule = { isBuildingBlock: false, severity: { value: 'low', mapping: fillEmptySeverityMappings([]), isMappingChecked: false }, riskScore: { value: 21, mapping: [], isMappingChecked: false }, + investigationFields: [], references: [''], falsePositives: [''], license: '', diff --git a/x-pack/plugins/security_solution/public/flyout/left/components/correlations_details.test.tsx b/x-pack/plugins/security_solution/public/flyout/left/components/correlations_details.test.tsx index ee641e5c6918..6b52bcf921b0 100644 --- a/x-pack/plugins/security_solution/public/flyout/left/components/correlations_details.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/left/components/correlations_details.test.tsx @@ -55,6 +55,7 @@ const contextValue: LeftPanelContext = { scopeId: '', browserFields: null, searchHit: undefined, + investigationFields: [], }; const renderCorrelationDetails = () => { diff --git a/x-pack/plugins/security_solution/public/flyout/left/components/prevalence_details.tsx b/x-pack/plugins/security_solution/public/flyout/left/components/prevalence_details.tsx index 70fc7554e64c..504d2d2c9232 100644 --- a/x-pack/plugins/security_solution/public/flyout/left/components/prevalence_details.tsx +++ b/x-pack/plugins/security_solution/public/flyout/left/components/prevalence_details.tsx @@ -108,7 +108,8 @@ const columns: Array> = [ * Prevalence table displayed in the document details expandable flyout left section under the Insights tab */ export const PrevalenceDetails: React.FC = () => { - const { browserFields, dataFormattedForFieldBrowser, eventId, scopeId } = useLeftPanelContext(); + const { browserFields, dataFormattedForFieldBrowser, eventId, scopeId, investigationFields } = + useLeftPanelContext(); const data = useMemo(() => { const summaryRows = getSummaryRows({ @@ -116,6 +117,7 @@ export const PrevalenceDetails: React.FC = () => { data: dataFormattedForFieldBrowser || [], eventId, scopeId, + investigationFields, isReadOnly: false, }); @@ -137,7 +139,7 @@ export const PrevalenceDetails: React.FC = () => { userPrevalence: fields, }; }); - }, [browserFields, dataFormattedForFieldBrowser, eventId, scopeId]); + }, [browserFields, investigationFields, dataFormattedForFieldBrowser, eventId, scopeId]); if (!eventId || !dataFormattedForFieldBrowser || !browserFields || !data || data.length === 0) { return ( diff --git a/x-pack/plugins/security_solution/public/flyout/left/context.tsx b/x-pack/plugins/security_solution/public/flyout/left/context.tsx index b552a830fc26..b5c4f340d548 100644 --- a/x-pack/plugins/security_solution/public/flyout/left/context.tsx +++ b/x-pack/plugins/security_solution/public/flyout/left/context.tsx @@ -15,12 +15,16 @@ import type { LeftPanelProps } from '.'; import type { GetFieldsData } from '../../common/hooks/use_get_fields_data'; import { useGetFieldsData } from '../../common/hooks/use_get_fields_data'; import { useTimelineEventsDetails } from '../../timelines/containers/details'; -import { getAlertIndexAlias } from '../../timelines/components/side_panel/event_details/helpers'; +import { + getAlertIndexAlias, + useBasicDataFromDetailsData, +} from '../../timelines/components/side_panel/event_details/helpers'; import { useSpaceId } from '../../common/hooks/use_space_id'; import { useRouteSpy } from '../../common/utils/route/use_route_spy'; import { SecurityPageName } from '../../../common/constants'; import { SourcererScopeName } from '../../common/store/sourcerer/model'; import { useSourcererDataView } from '../../common/containers/sourcerer'; +import { useRuleWithFallback } from '../../detection_engine/rule_management/logic/use_rule_with_fallback'; export interface LeftPanelContext { /** @@ -51,6 +55,10 @@ export interface LeftPanelContext { * The actual raw document object */ searchHit: SearchHit | undefined; + /** + * User defined fields to highlight (defined on the rule) + */ + investigationFields: string[]; /** * Retrieves searchHit values for the provided field */ @@ -83,6 +91,8 @@ export const LeftPanelProvider = ({ id, indexName, scopeId, children }: LeftPane skip: !id, }); const getFieldsData = useGetFieldsData(searchHit?.fields); + const { ruleId } = useBasicDataFromDetailsData(dataFormattedForFieldBrowser); + const { rule: maybeRule } = useRuleWithFallback(ruleId); const contextValue = useMemo( () => @@ -95,6 +105,7 @@ export const LeftPanelProvider = ({ id, indexName, scopeId, children }: LeftPane dataAsNestedObject, dataFormattedForFieldBrowser, searchHit, + investigationFields: maybeRule?.investigation_fields ?? [], getFieldsData, } : undefined, @@ -103,10 +114,11 @@ export const LeftPanelProvider = ({ id, indexName, scopeId, children }: LeftPane indexName, scopeId, sourcererDataView.browserFields, - dataFormattedForFieldBrowser, - getFieldsData, dataAsNestedObject, + dataFormattedForFieldBrowser, searchHit, + maybeRule?.investigation_fields, + getFieldsData, ] ); diff --git a/x-pack/plugins/security_solution/public/flyout/left/hooks/use_threat_intelligence_details.test.ts b/x-pack/plugins/security_solution/public/flyout/left/hooks/use_threat_intelligence_details.test.ts index 1a6387d4eb3f..1e92ff0a6bd4 100644 --- a/x-pack/plugins/security_solution/public/flyout/left/hooks/use_threat_intelligence_details.test.ts +++ b/x-pack/plugins/security_solution/public/flyout/left/hooks/use_threat_intelligence_details.test.ts @@ -75,6 +75,7 @@ describe('useThreatIntelligenceDetails', () => { _index: 'testIndex', }, dataAsNestedObject: null, + investigationFields: [], }); }); diff --git a/x-pack/plugins/security_solution/public/flyout/left/mocks/mock_context.ts b/x-pack/plugins/security_solution/public/flyout/left/mocks/mock_context.ts index 99bfc24bab50..356957056898 100644 --- a/x-pack/plugins/security_solution/public/flyout/left/mocks/mock_context.ts +++ b/x-pack/plugins/security_solution/public/flyout/left/mocks/mock_context.ts @@ -47,4 +47,5 @@ export const mockContextValue: LeftPanelContext = { dataAsNestedObject: { _id: 'testId', }, + investigationFields: [], }; diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/highlighted_fields.test.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/highlighted_fields.test.tsx index b156006a906e..ed9d2cd62370 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/highlighted_fields.test.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/highlighted_fields.test.tsx @@ -13,10 +13,16 @@ import { HighlightedFields } from './highlighted_fields'; import { mockDataFormattedForFieldBrowser } from '../mocks/mock_context'; import { useHighlightedFields } from '../hooks/use_highlighted_fields'; import { TestProviders } from '../../../common/mock'; +import { useRuleWithFallback } from '../../../detection_engine/rule_management/logic/use_rule_with_fallback'; jest.mock('../hooks/use_highlighted_fields'); +jest.mock('../../../detection_engine/rule_management/logic/use_rule_with_fallback'); describe('', () => { + beforeEach(() => { + (useRuleWithFallback as jest.Mock).mockReturnValue({ investigation_fields: [] }); + }); + it('should render the component', () => { const panelContextValue = { dataFormattedForFieldBrowser: mockDataFormattedForFieldBrowser, diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/highlighted_fields.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/highlighted_fields.tsx index a0ef290b11ea..f02682721ee5 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/highlighted_fields.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/highlighted_fields.tsx @@ -9,6 +9,8 @@ import type { FC } from 'react'; import React from 'react'; import type { EuiBasicTableColumn } from '@elastic/eui'; import { EuiFlexGroup, EuiFlexItem, EuiInMemoryTable, EuiPanel, EuiTitle } from '@elastic/eui'; +import { useRuleWithFallback } from '../../../detection_engine/rule_management/logic/use_rule_with_fallback'; +import { useBasicDataFromDetailsData } from '../../../timelines/components/side_panel/event_details/helpers'; import { HighlightedFieldsCell } from './highlighted_fields_cell'; import { CellActionsMode, @@ -57,8 +59,13 @@ const columns: Array> = [ */ export const HighlightedFields: FC = () => { const { dataFormattedForFieldBrowser } = useRightPanelContext(); + const { ruleId } = useBasicDataFromDetailsData(dataFormattedForFieldBrowser); + const { rule: maybeRule } = useRuleWithFallback(ruleId); - const highlightedFields = useHighlightedFields({ dataFormattedForFieldBrowser }); + const highlightedFields = useHighlightedFields({ + dataFormattedForFieldBrowser, + investigationFields: maybeRule?.investigation_fields ?? [], + }); if (!dataFormattedForFieldBrowser || highlightedFields.length === 0) { return null; diff --git a/x-pack/plugins/security_solution/public/flyout/right/components/prevalence_overview.tsx b/x-pack/plugins/security_solution/public/flyout/right/components/prevalence_overview.tsx index a98246c3eaa0..b017b4b9225a 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/components/prevalence_overview.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/components/prevalence_overview.tsx @@ -23,8 +23,14 @@ import { PREVALENCE_TAB_ID } from '../../left/components/prevalence_details'; * and the SummaryPanel component for data rendering. */ export const PrevalenceOverview: FC = () => { - const { eventId, indexName, browserFields, dataFormattedForFieldBrowser, scopeId } = - useRightPanelContext(); + const { + eventId, + indexName, + browserFields, + dataFormattedForFieldBrowser, + scopeId, + investigationFields, + } = useRightPanelContext(); const { openLeftPanel } = useExpandableFlyoutContext(); const goToCorrelationsTab = useCallback(() => { @@ -46,6 +52,7 @@ export const PrevalenceOverview: FC = () => { eventId, browserFields, dataFormattedForFieldBrowser, + investigationFields, scopeId, }); diff --git a/x-pack/plugins/security_solution/public/flyout/right/context.tsx b/x-pack/plugins/security_solution/public/flyout/right/context.tsx index 7b12fc3a3a6c..31eec77707d2 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/context.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/context.tsx @@ -10,9 +10,13 @@ import { css } from '@emotion/react'; import React, { createContext, useContext, useMemo } from 'react'; import { EuiFlexItem, EuiLoadingSpinner } from '@elastic/eui'; import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs'; + import type { SearchHit } from '../../../common/search_strategy'; import { useTimelineEventsDetails } from '../../timelines/containers/details'; -import { getAlertIndexAlias } from '../../timelines/components/side_panel/event_details/helpers'; +import { + getAlertIndexAlias, + useBasicDataFromDetailsData, +} from '../../timelines/components/side_panel/event_details/helpers'; import { useSpaceId } from '../../common/hooks/use_space_id'; import { useRouteSpy } from '../../common/utils/route/use_route_spy'; import { SecurityPageName } from '../../../common/constants'; @@ -21,6 +25,7 @@ import { useSourcererDataView } from '../../common/containers/sourcerer'; import type { RightPanelProps } from '.'; import type { GetFieldsData } from '../../common/hooks/use_get_fields_data'; import { useGetFieldsData } from '../../common/hooks/use_get_fields_data'; +import { useRuleWithFallback } from '../../detection_engine/rule_management/logic/use_rule_with_fallback'; export interface RightPanelContext { /** @@ -51,6 +56,10 @@ export interface RightPanelContext { * The actual raw document object */ searchHit: SearchHit | undefined; + /** + * User defined fields to highlight (defined on the rule) + */ + investigationFields: string[]; /** * Promise to trigger a data refresh */ @@ -94,6 +103,8 @@ export const RightPanelProvider = ({ skip: !id, }); const getFieldsData = useGetFieldsData(searchHit?.fields); + const { ruleId } = useBasicDataFromDetailsData(dataFormattedForFieldBrowser); + const { rule: maybeRule } = useRuleWithFallback(ruleId); const contextValue = useMemo( () => @@ -106,12 +117,14 @@ export const RightPanelProvider = ({ dataAsNestedObject, dataFormattedForFieldBrowser, searchHit, + investigationFields: maybeRule?.investigation_fields ?? [], refetchFlyoutData, getFieldsData, } : undefined, [ id, + maybeRule, indexName, scopeId, sourcererDataView.browserFields, diff --git a/x-pack/plugins/security_solution/public/flyout/right/hooks/use_highlighted_fields.ts b/x-pack/plugins/security_solution/public/flyout/right/hooks/use_highlighted_fields.ts index 20e036145a6c..b9377a6ca85a 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/hooks/use_highlighted_fields.ts +++ b/x-pack/plugins/security_solution/public/flyout/right/hooks/use_highlighted_fields.ts @@ -19,6 +19,10 @@ export interface UseHighlightedFieldsParams { * An array of field objects with category and value */ dataFormattedForFieldBrowser: TimelineEventsDetailsItem[] | null; + /** + * An array of fields user has selected to highlight, defined on rule + */ + investigationFields?: string[]; } export interface UseHighlightedFieldsResult { @@ -43,6 +47,7 @@ export interface UseHighlightedFieldsResult { */ export const useHighlightedFields = ({ dataFormattedForFieldBrowser, + investigationFields, }: UseHighlightedFieldsParams): UseHighlightedFieldsResult[] => { if (!dataFormattedForFieldBrowser) return []; @@ -61,6 +66,7 @@ export const useHighlightedFields = ({ { category: 'kibana', field: ALERT_RULE_TYPE }, dataFormattedForFieldBrowser ); + const eventRuleType = Array.isArray(eventRuleTypeField?.originalValue) ? eventRuleTypeField?.originalValue?.[0] : eventRuleTypeField?.originalValue; @@ -69,6 +75,7 @@ export const useHighlightedFields = ({ eventCategories, eventCode, eventRuleType, + highlightedFieldsOverride: investigationFields ?? [], }); return tableFields.reduce((acc, field) => { diff --git a/x-pack/plugins/security_solution/public/flyout/right/hooks/use_prevalence.tsx b/x-pack/plugins/security_solution/public/flyout/right/hooks/use_prevalence.tsx index 162bc8bc851a..5121e166e9a7 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/hooks/use_prevalence.tsx +++ b/x-pack/plugins/security_solution/public/flyout/right/hooks/use_prevalence.tsx @@ -29,6 +29,10 @@ export interface UsePrevalenceParams { * Maintain backwards compatibility // TODO remove when possible */ scopeId: string; + /** + * User defined fields to highlight (defined on rule) + */ + investigationFields?: string[]; } /** @@ -41,6 +45,7 @@ export const usePrevalence = ({ eventId, browserFields, dataFormattedForFieldBrowser, + investigationFields, scopeId, }: UsePrevalenceParams): ReactElement[] => { // retrieves the highlighted fields @@ -49,11 +54,12 @@ export const usePrevalence = ({ getSummaryRows({ browserFields: browserFields || {}, data: dataFormattedForFieldBrowser || [], + investigationFields: investigationFields || [], eventId, scopeId, isReadOnly: false, }), - [browserFields, dataFormattedForFieldBrowser, eventId, scopeId] + [browserFields, investigationFields, dataFormattedForFieldBrowser, eventId, scopeId] ); return useMemo( diff --git a/x-pack/plugins/security_solution/public/flyout/right/mocks/mock_right_panel_context.ts b/x-pack/plugins/security_solution/public/flyout/right/mocks/mock_right_panel_context.ts index 95c986df4378..e7593b1eea9e 100644 --- a/x-pack/plugins/security_solution/public/flyout/right/mocks/mock_right_panel_context.ts +++ b/x-pack/plugins/security_solution/public/flyout/right/mocks/mock_right_panel_context.ts @@ -20,5 +20,6 @@ export const mockContextValue: RightPanelContext = { browserFields: null, dataAsNestedObject: null, searchHit: undefined, + investigationFields: [], refetchFlyoutData: jest.fn(), }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/utils.ts index 7c6d7926f2fa..003e55fa6f1b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/__mocks__/utils.ts @@ -100,4 +100,5 @@ export const getOutputRuleAlertForRest = (): RuleResponse => ({ namespace: undefined, data_view_id: undefined, alert_suppression: undefined, + investigation_fields: [], }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/notifications/schedule_notification_actions.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/notifications/schedule_notification_actions.test.ts index b4c1a794929c..02e162c0d7ff 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/notifications/schedule_notification_actions.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/notifications/schedule_notification_actions.test.ts @@ -44,6 +44,7 @@ describe('schedule_notification_actions', () => { responseActions: [], riskScore: 80, riskScoreMapping: [], + investigationFields: [], ruleNameOverride: undefined, dataViewId: undefined, outputIndex: 'output-1', diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/notifications/schedule_throttle_notification_actions.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/notifications/schedule_throttle_notification_actions.test.ts index 701da673efcd..a5381504e98f 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/notifications/schedule_throttle_notification_actions.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_actions_legacy/logic/notifications/schedule_throttle_notification_actions.test.ts @@ -64,6 +64,7 @@ describe('schedule_throttle_notification_actions', () => { requiredFields: [], setup: '', alertSuppression: undefined, + investigationFields: undefined, }; }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/actions/duplicate_rule.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/actions/duplicate_rule.test.ts index 08a53c007dc0..7223b920c7bd 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/actions/duplicate_rule.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/actions/duplicate_rule.test.ts @@ -62,6 +62,7 @@ describe('duplicateRule', () => { timestampOverrideFallbackDisabled: undefined, dataViewId: undefined, alertSuppression: undefined, + investigationFields: undefined, }, schedule: { interval: '5m', diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/crud/update_rules.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/crud/update_rules.ts index 66a4531d1c2e..5248e7f06938 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/crud/update_rules.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/crud/update_rules.ts @@ -45,6 +45,7 @@ export const updateRules = async ({ ruleId: existingRule.params.ruleId, falsePositives: ruleUpdate.false_positives ?? [], from: ruleUpdate.from ?? 'now-6m', + investigationFields: ruleUpdate.investigation_fields ?? [], // Unlike the create route, immutable comes from the existing rule here immutable: existingRule.params.immutable, license: ruleUpdate.license, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/export/get_export_all.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/export/get_export_all.test.ts index 60e4d5627833..662085c95d62 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/export/get_export_all.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/export/get_export_all.test.ts @@ -136,6 +136,7 @@ describe('getExportAll', () => { note: '# Investigative notes', version: 1, exceptions_list: getListArrayMock(), + investigation_fields: [], }); expect(detailsJson).toEqual({ exported_exception_list_count: 1, @@ -319,6 +320,7 @@ describe('getExportAll', () => { version: 1, revision: 0, exceptions_list: getListArrayMock(), + investigation_fields: [], }); expect(detailsJson).toEqual({ exported_exception_list_count: 1, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/export/get_export_by_object_ids.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/export/get_export_by_object_ids.test.ts index cf1c9db355bd..b8f27c0d16a5 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/export/get_export_by_object_ids.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/export/get_export_by_object_ids.test.ts @@ -132,6 +132,7 @@ describe('get_export_by_object_ids', () => { note: '# Investigative notes', version: 1, exceptions_list: getListArrayMock(), + investigation_fields: [], }, exportDetails: { exported_exception_list_count: 0, @@ -327,6 +328,7 @@ describe('get_export_by_object_ids', () => { version: 1, revision: 0, exceptions_list: getListArrayMock(), + investigation_fields: [], }); expect(detailsJson).toEqual({ exported_exception_list_count: 0, @@ -523,6 +525,7 @@ describe('get_export_by_object_ids', () => { namespace: undefined, data_view_id: undefined, alert_suppression: undefined, + investigation_fields: [], }, ], }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/normalization/rule_converters.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/normalization/rule_converters.ts index 300d58ecc2e1..ecb379a1dcc2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/normalization/rule_converters.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/normalization/rule_converters.ts @@ -409,6 +409,7 @@ export const convertPatchAPIToInternalSchema = ( description: nextParams.description ?? existingParams.description, ruleId: existingParams.ruleId, falsePositives: nextParams.false_positives ?? existingParams.falsePositives, + investigationFields: nextParams.investigation_fields ?? existingParams.investigationFields, from: nextParams.from ?? existingParams.from, immutable: existingParams.immutable, license: nextParams.license ?? existingParams.license, @@ -470,6 +471,7 @@ export const convertCreateAPIToInternalSchema = ( description: input.description, ruleId: newRuleId, falsePositives: input.false_positives ?? [], + investigationFields: input.investigation_fields ?? [], from: input.from ?? 'now-6m', immutable, license: input.license, @@ -619,6 +621,7 @@ export const commonParamsCamelToSnake = (params: BaseRuleParams) => { rule_name_override: params.ruleNameOverride, timestamp_override: params.timestampOverride, timestamp_override_fallback_disabled: params.timestampOverrideFallbackDisabled, + investigation_fields: params.investigationFields, author: params.author, false_positives: params.falsePositives, from: params.from, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/validate.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/validate.test.ts index ce60bf34d2ed..d24bcaa5a7b9 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/validate.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/utils/validate.test.ts @@ -78,6 +78,7 @@ export const ruleOutput = (): RuleResponse => ({ data_view_id: undefined, saved_id: undefined, alert_suppression: undefined, + investigation_fields: [], }); describe('validate', () => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_schema/model/rule_schemas.mock.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_schema/model/rule_schemas.mock.ts index 44fa44ee0189..a6e6ee5282e8 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_schema/model/rule_schemas.mock.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_schema/model/rule_schemas.mock.ts @@ -48,6 +48,7 @@ const getBaseRuleParams = (): BaseRuleParams => { timelineTitle: 'some-timeline-title', timestampOverride: undefined, timestampOverrideFallbackDisabled: undefined, + investigationFields: [], meta: { someMeta: 'someField', }, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_schema/model/rule_schemas.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_schema/model/rule_schemas.ts index c851ed9288ea..d3e66cf49148 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_schema/model/rule_schemas.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_schema/model/rule_schemas.ts @@ -57,6 +57,7 @@ import { RuleAuthorArray, RuleDescription, RuleFalsePositiveArray, + RuleCustomHighlightedFieldArray, RuleFilterArray, RuleLicense, RuleMetadata, @@ -97,6 +98,7 @@ export const baseRuleParams = t.exact( falsePositives: RuleFalsePositiveArray, from: RuleIntervalFrom, ruleId: RuleSignatureId, + investigationFields: t.union([RuleCustomHighlightedFieldArray, t.undefined]), immutable: IsRuleImmutable, license: t.union([RuleLicense, t.undefined]), outputIndex: AlertsIndex, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/__mocks__/es_results.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/__mocks__/es_results.ts index 93c93eb631fa..6a522193558a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/__mocks__/es_results.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/__mocks__/es_results.ts @@ -523,6 +523,7 @@ export const sampleSignalHit = (): SignalHit => ({ filters: undefined, saved_id: undefined, alert_suppression: undefined, + investigation_fields: undefined, }, depth: 1, }, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert.test.ts index 06b0dc5b9051..8e06156bdc91 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/factories/utils/build_alert.test.ts @@ -165,6 +165,7 @@ describe('buildAlert', () => { index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], query: 'user.name: root or user.name: admin', filters: [{ query: { match_phrase: { 'host.name': 'some-host' } } }], + investigation_fields: [], }, [ALERT_RULE_INDICES]: completeRule.ruleParams.index, ...flattenWithPrefix(ALERT_RULE_NAMESPACE, { @@ -358,6 +359,7 @@ describe('buildAlert', () => { index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], query: 'user.name: root or user.name: admin', filters: [{ query: { match_phrase: { 'host.name': 'some-host' } } }], + investigation_fields: [], }, ...flattenWithPrefix(ALERT_RULE_NAMESPACE, { actions: [], diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 74a27cd6d3a1..d5fbbabf7a4c 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -31106,7 +31106,6 @@ "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.fieldThresholdFieldCardinalityFieldHelpText": "Sélectionner un champ pour vérifier la cardinalité", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.fieldThresholdLabel": "Seuil", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.groupBy.licenseWarning": "La suppression d'alertes est activée avec la licence Platinum ou supérieure", - "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.groupBy.placeholderText": "Sélectionner un champ", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.groupByDurationValueLabel": "Supprimer les alertes pour", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.groupByFieldsLabel": "Supprimer les alertes par", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.groupByFieldsLabelAppend": "Facultatif (version d'évaluation technique)", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 2a38ff3908f8..fd71d9e3dfc3 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -31105,7 +31105,6 @@ "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.fieldThresholdFieldCardinalityFieldHelpText": "カーディナリティを確認するフィールドを選択します", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.fieldThresholdLabel": "しきい値", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.groupBy.licenseWarning": "アラートの非表示は、プラチナライセンス以上で有効です", - "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.groupBy.placeholderText": "フィールドを選択", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.groupByDurationValueLabel": "アラートを非表示", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.groupByFieldsLabel": "アラートを非表示", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.groupByFieldsLabelAppend": "任意(テクニカルプレビュー)", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index fbac616ba95e..046b0c6873ff 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -31101,7 +31101,6 @@ "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.fieldThresholdFieldCardinalityFieldHelpText": "选择字段以检查基数", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.fieldThresholdLabel": "阈值", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.groupBy.licenseWarning": "告警阻止通过白金级或更高级许可证启用", - "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.groupBy.placeholderText": "选择字段", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.groupByDurationValueLabel": "阻止以下项的告警", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.groupByFieldsLabel": "阻止告警的依据", "xpack.securitySolution.detectionEngine.createRule.stepDefineRule.groupByFieldsLabelAppend": "可选(技术预览)", diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/create_rules.ts b/x-pack/test/detection_engine_api_integration/basic/tests/create_rules.ts index 66e075792b6f..57c9651bec55 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/create_rules.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/create_rules.ts @@ -76,6 +76,7 @@ export default ({ getService }: FtrProviderContext) => { author: [], created_by: 'elastic', description: 'Simple Rule Query', + investigation_fields: [], enabled: true, false_positives: [], from: 'now-6m', diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/create_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/create_rules.ts index fe17a9fb6200..b624cd95787a 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/create_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/create_rules.ts @@ -207,6 +207,7 @@ export default ({ getService }: FtrProviderContext) => { risk_score_mapping: [], name: 'Simple Rule Query', query: 'user.name: root or user.name: admin', + investigation_fields: [], references: [], related_integrations: [], required_fields: [], diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/export_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/export_rules.ts index 49cf3cc5107a..cb47021ba3d5 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/export_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/export_rules.ts @@ -742,5 +742,6 @@ function expectToMatchRuleSchema(obj: RuleResponse): void { index: expect.arrayContaining([]), query: expect.any(String), actions: expect.arrayContaining([]), + investigation_fields: expect.arrayContaining([]), }); } diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/patch_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/patch_rules.ts index b671c2ce39d4..9a55755f2e93 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/patch_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/patch_rules.ts @@ -426,6 +426,28 @@ export default ({ getService }: FtrProviderContext) => { message: 'rule_id: "fake_id" not found', }); }); + + describe('investigation_fields', () => { + it('should overwrite investigation_fields value on update - non additive', async () => { + await createRule(supertest, log, { + ...getSimpleRule('rule-1'), + investigation_fields: ['blob', 'boop'], + }); + + const rulePatch = { + rule_id: 'rule-1', + investigation_fields: ['foo', 'bar'], + }; + + const { body } = await supertest + .patch(DETECTION_ENGINE_RULES_URL) + .set('kbn-xsrf', 'true') + .send(rulePatch) + .expect(200); + + expect(body.investigation_fields).to.eql(['foo', 'bar']); + }); + }); }); describe('patch per-action frequencies', () => { diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/update_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/update_rules.ts index 7b2dfca1f46f..0f26e2b396db 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/update_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/update_rules.ts @@ -841,6 +841,28 @@ export default ({ getService }: FtrProviderContext) => { }); }); }); + + describe('investigation_fields', () => { + it('should overwrite investigation_fields value on update - non additive', async () => { + await createRule(supertest, log, { + ...getSimpleRule('rule-1'), + investigation_fields: ['blob', 'boop'], + }); + + const ruleUpdate = { + ...getSimpleRuleUpdate('rule-1'), + investigation_fields: ['foo', 'bar'], + }; + + const { body } = await supertest + .put(DETECTION_ENGINE_RULES_URL) + .set('kbn-xsrf', 'true') + .send(ruleUpdate) + .expect(200); + + expect(body.investigation_fields).to.eql(['foo', 'bar']); + }); + }); }); }); }; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group6/alerts/alerts_compatibility.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group6/alerts/alerts_compatibility.ts index 9e7b6265a2b9..143556528648 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group6/alerts/alerts_compatibility.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group6/alerts/alerts_compatibility.ts @@ -337,6 +337,7 @@ export default ({ getService }: FtrProviderContext) => { max_signals: 100, risk_score_mapping: [], severity_mapping: [], + investigation_fields: [], threat: [], to: 'now', references: [], @@ -512,6 +513,7 @@ export default ({ getService }: FtrProviderContext) => { related_integrations: [], required_fields: [], setup: '', + investigation_fields: [], }, 'kibana.alert.rule.actions': [], 'kibana.alert.rule.created_by': 'elastic', diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/machine_learning.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/machine_learning.ts index 792fcb30b664..b9f6b8caed95 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/machine_learning.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/machine_learning.ts @@ -146,6 +146,7 @@ export default ({ getService }: FtrProviderContext) => { to: 'now', type: 'machine_learning', version: 1, + investigation_fields: [], }, [ALERT_DEPTH]: 1, [ALERT_REASON]: `event with process store, by root on mothra created critical alert Test ML rule.`, diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/new_terms.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/new_terms.ts index 0ac86c991015..72a71185065d 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/new_terms.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/rule_execution_logic/new_terms.ts @@ -198,6 +198,7 @@ export default ({ getService }: FtrProviderContext) => { history_window_start: '2019-01-19T20:42:00.000Z', index: ['auditbeat-*'], language: 'kuery', + investigation_fields: [], }, 'kibana.alert.rule.actions': [], 'kibana.alert.rule.author': [], diff --git a/x-pack/test/detection_engine_api_integration/utils/get_complex_rule_output.ts b/x-pack/test/detection_engine_api_integration/utils/get_complex_rule_output.ts index 0115b00c4b46..9c35d6652935 100644 --- a/x-pack/test/detection_engine_api_integration/utils/get_complex_rule_output.ts +++ b/x-pack/test/detection_engine_api_integration/utils/get_complex_rule_output.ts @@ -102,4 +102,5 @@ export const getComplexRuleOutput = (ruleId = 'rule-1'): Partial = related_integrations: [], required_fields: [], setup: '', + investigation_fields: [], }); diff --git a/x-pack/test/detection_engine_api_integration/utils/get_simple_rule_output.ts b/x-pack/test/detection_engine_api_integration/utils/get_simple_rule_output.ts index 7c51faf2b884..92f427876e35 100644 --- a/x-pack/test/detection_engine_api_integration/utils/get_simple_rule_output.ts +++ b/x-pack/test/detection_engine_api_integration/utils/get_simple_rule_output.ts @@ -60,6 +60,7 @@ export const getMockSharedResponseSchema = ( timestamp_override: undefined, timestamp_override_fallback_disabled: undefined, namespace: undefined, + investigation_fields: [], }); const getQueryRuleOutput = (ruleId = 'rule-1', enabled = false): RuleResponse => ({ diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/alerts_table_flow/endpoint_exceptions.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/alerts_table_flow/endpoint_exceptions.cy.ts index 3e29f3a08cb7..bef92ab69444 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/alerts_table_flow/endpoint_exceptions.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/alerts_table_flow/endpoint_exceptions.cy.ts @@ -42,7 +42,8 @@ import { } from '../../../screens/exceptions'; import { goToEndpointExceptionsTab } from '../../../tasks/rule_details'; -describe( +// See https://github.com/elastic/kibana/issues/163967 +describe.skip( 'Endpoint Exceptions workflows from Alert', { tags: [tag.ESS, tag.BROKEN_IN_SERVERLESS] }, () => { diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/alerts_table_flow/rule_exceptions/auto_populate_with_alert_data.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/alerts_table_flow/rule_exceptions/auto_populate_with_alert_data.cy.ts index 6a7df890aec0..5a2451d42d86 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/alerts_table_flow/rule_exceptions/auto_populate_with_alert_data.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/alerts_table_flow/rule_exceptions/auto_populate_with_alert_data.cy.ts @@ -39,7 +39,8 @@ import { } from '../../../../screens/exceptions'; import { waitForAlertsToPopulate } from '../../../../tasks/create_new_rule'; -describe( +// See https://github.com/elastic/kibana/issues/163967 +describe.skip( 'Auto populate exception with Alert data', { tags: [tag.ESS, tag.BROKEN_IN_SERVERLESS] }, () => { diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/alerts_table_flow/rule_exceptions/closing_all_matching_alerts.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/alerts_table_flow/rule_exceptions/closing_all_matching_alerts.cy.ts index 96bf48fa2793..ea905a777412 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/alerts_table_flow/rule_exceptions/closing_all_matching_alerts.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/alerts_table_flow/rule_exceptions/closing_all_matching_alerts.cy.ts @@ -4,13 +4,13 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import { waitForAlertsToPopulate } from '../../../../tasks/create_new_rule'; import { addExceptionFromFirstAlert, goToClosedAlertsOnRuleDetailsPage, waitForAlerts, } from '../../../../tasks/alerts'; import { deleteAlertsAndRules, postDataView } from '../../../../tasks/common'; -import { waitForAlertsToPopulate } from '../../../../tasks/create_new_rule'; import { login, visitWithoutDateRange } from '../../../../tasks/login'; import { DETECTIONS_RULE_MANAGEMENT_URL } from '../../../../urls/navigation'; import { goToRuleDetails } from '../../../../tasks/alerts_detection_rules'; @@ -27,6 +27,7 @@ import { submitNewExceptionItem, } from '../../../../tasks/exceptions'; +// See https://github.com/elastic/kibana/issues/163967 describe('Close matching Alerts ', () => { const newRule = getNewRule(); const ITEM_NAME = 'Sample Exception Item'; diff --git a/x-pack/test/security_solution_cypress/cypress/objects/rule.ts b/x-pack/test/security_solution_cypress/cypress/objects/rule.ts index e2920f497547..586a4188b84b 100644 --- a/x-pack/test/security_solution_cypress/cypress/objects/rule.ts +++ b/x-pack/test/security_solution_cypress/cypress/objects/rule.ts @@ -532,6 +532,7 @@ export const expectedExportedRule = (ruleResponse: Cypress.Response