From fc4c0e1d94135694f733076140277f140543de84 Mon Sep 17 00:00:00 2001 From: Garrett Spong Date: Tue, 14 Jul 2020 00:17:43 -0600 Subject: [PATCH] Adds rule execution logic for timestampOverride, integrates autocomplete for severityOverride, and fixes rule details description rollup --- .../autocomplete/field_value_match.tsx | 3 + .../components/alerts_table/translations.ts | 6 +- .../rules/description_step/helpers.test.tsx | 17 +++- .../rules/description_step/helpers.tsx | 75 ++++++++++++++- .../rules/description_step/index.test.tsx | 4 +- .../rules/description_step/index.tsx | 15 +-- .../rules/risk_score_mapping/index.tsx | 34 +++---- .../rules/risk_score_mapping/translations.tsx | 7 ++ .../rules/severity_mapping/index.tsx | 94 ++++++++++++++----- .../rules/create/helpers.test.ts | 8 -- .../detection_engine/rules/create/helpers.ts | 4 +- .../pages/detection_engine/rules/types.ts | 4 +- .../signals/build_events_query.test.ts | 4 + .../signals/build_events_query.ts | 11 ++- .../signals/search_after_bulk_create.ts | 1 + .../signals/single_search_after.test.ts | 3 + .../signals/single_search_after.ts | 4 + 17 files changed, 218 insertions(+), 76 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_match.tsx b/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_match.tsx index 4d96d6638132..32a82af114ba 100644 --- a/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_match.tsx +++ b/x-pack/plugins/security_solution/public/common/components/autocomplete/field_value_match.tsx @@ -22,6 +22,7 @@ interface AutocompleteFieldMatchProps { isLoading: boolean; isDisabled: boolean; isClearable: boolean; + fieldInputWidth?: number; onChange: (arg: string) => void; } @@ -33,6 +34,7 @@ export const AutocompleteFieldMatchComponent: React.FC { const [isLoadingSuggestions, suggestions, updateSuggestions] = useFieldValueAutocomplete({ @@ -97,6 +99,7 @@ export const AutocompleteFieldMatchComponent: React.FC diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/translations.ts b/x-pack/plugins/security_solution/public/detections/components/alerts_table/translations.ts index 0f55469bbfda..e5e8635b9e79 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/translations.ts +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/translations.ts @@ -20,21 +20,21 @@ export const ALERTS_DOCUMENT_TYPE = i18n.translate( export const OPEN_ALERTS = i18n.translate( 'xpack.securitySolution.detectionEngine.alerts.openAlertsTitle', { - defaultMessage: 'Open alerts', + defaultMessage: 'Open', } ); export const CLOSED_ALERTS = i18n.translate( 'xpack.securitySolution.detectionEngine.alerts.closedAlertsTitle', { - defaultMessage: 'Closed alerts', + defaultMessage: 'Closed', } ); export const IN_PROGRESS_ALERTS = i18n.translate( 'xpack.securitySolution.detectionEngine.alerts.inProgressAlertsTitle', { - defaultMessage: 'In progress alerts', + defaultMessage: 'In progress', } ); 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 b82d1c0a36ab..6759cbac2ddc 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 @@ -5,7 +5,7 @@ */ import React from 'react'; -import { shallow } from 'enzyme'; +import { mount, shallow } from 'enzyme'; import { EuiLoadingSpinner } from '@elastic/eui'; import { coreMock } from '../../../../../../../../src/core/public/mocks'; @@ -328,10 +328,19 @@ describe('helpers', () => { describe('buildSeverityDescription', () => { test('returns ListItem with passed in label and SeverityBadge component', () => { - const result: ListItems[] = buildSeverityDescription('Test label', 'Test description value'); + const result: ListItems[] = buildSeverityDescription({ + value: 'low', + mapping: [{ field: 'host.name', operator: 'equals', value: 'hello', severity: 'high' }], + }); - expect(result[0].title).toEqual('Test label'); - expect(result[0].description).toEqual(); + expect(result[0].title).toEqual('Severity'); + expect(result[0].description).toEqual(); + expect(result[1].title).toEqual('Severity override'); + + const wrapper = mount(result[1].description as React.ReactElement); + expect(wrapper.find('[data-test-subj="severityOverrideSeverity0"]').first().text()).toEqual( + 'High' + ); }); }); 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 a0d43c3abf5c..206c54255edd 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 @@ -13,12 +13,16 @@ import { EuiSpacer, EuiLink, EuiText, + EuiIcon, + EuiToolTip, } from '@elastic/eui'; import { isEmpty } from 'lodash/fp'; import React from 'react'; import styled from 'styled-components'; +import * as i18nSeverity from '../severity_mapping/translations'; +import * as i18nRiskScore from '../risk_score_mapping/translations'; import { RuleType } from '../../../../../common/detection_engine/types'; import { esFilters } from '../../../../../../../../src/plugins/data/public'; @@ -29,6 +33,7 @@ import { BuildQueryBarDescription, BuildThreatDescription, ListItems } from './t import { SeverityBadge } from '../severity_badge'; import ListTreeIcon from './assets/list_tree_icon.svg'; import { assertUnreachable } from '../../../../common/lib/helpers'; +import { AboutStepRiskScore, AboutStepSeverity } from '../../../pages/detection_engine/rules/types'; const NoteDescriptionContainer = styled(EuiFlexItem)` height: 105px; @@ -218,11 +223,75 @@ export const buildStringArrayDescription = ( return []; }; -export const buildSeverityDescription = (label: string, value: string): ListItems[] => [ +const OverrideColumn = styled(EuiFlexItem)` + width: 125px; + max-width: 125px; + overflow: hidden; + text-overflow: ellipsis; +`; + +export const buildSeverityDescription = (severity: AboutStepSeverity): ListItems[] => [ + { + title: i18nSeverity.DEFAULT_SEVERITY, + description: , + }, + ...severity.mapping.map((severityItem, index) => { + return { + title: index === 0 ? i18nSeverity.SEVERITY_MAPPING : '', + description: ( + + + + <>{severityItem.field} + + + + <>{severityItem.value} + + + + + + + + + ), + }; + }), +]; + +export const buildRiskScoreDescription = (riskScore: AboutStepRiskScore): ListItems[] => [ { - title: label, - description: , + title: i18nRiskScore.RISK_SCORE, + description: riskScore.value, }, + ...riskScore.mapping.map((riskScoreItem, index) => { + return { + title: index === 0 ? i18nRiskScore.RISK_SCORE_MAPPING : '', + description: ( + + + + <>{riskScoreItem.field} + + + + + + {'signal.rule.risk_score'} + + ), + }; + }), ]; const MyRefUrlLink = styled(EuiLink)` 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 0a7e666d65ae..762ea07d9186 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 @@ -404,7 +404,7 @@ describe('description_step', () => { mockFilterManager ); - expect(result[0].title).toEqual('Severity label'); + expect(result[0].title).toEqual('Severity'); expect(React.isValidElement(result[0].description)).toBeTruthy(); }); }); @@ -418,7 +418,7 @@ describe('description_step', () => { mockFilterManager ); - expect(result[0].title).toEqual('Risk score label'); + expect(result[0].title).toEqual('Risk score'); expect(result[0].description).toEqual(21); }); }); 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 8f3a76c6aea5..080fa871830d 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 @@ -35,6 +35,7 @@ import { buildUrlsDescription, buildNoteDescription, buildRuleTypeDescription, + buildRiskScoreDescription, } from './helpers'; import { useSiemJobs } from '../../../../common/components/ml_popover/hooks/use_siem_jobs'; import { buildMlJobDescription } from './ml_job_description'; @@ -188,18 +189,12 @@ export const getDescriptionItem = ( } else if (Array.isArray(get(field, data))) { const values: string[] = get(field, data); return buildStringArrayDescription(label, field, values); - // TODO: Add custom UI for Risk/Severity Mappings (and fix missing label) } else if (field === 'riskScore') { - const val: AboutStepRiskScore = get(field, data); - return [ - { - title: label, - description: val.value, - }, - ]; + const values: AboutStepRiskScore = get(field, data); + return buildRiskScoreDescription(values); } else if (field === 'severity') { - const val: AboutStepSeverity = get(field, data); - return buildSeverityDescription(label, val.value); + const values: AboutStepSeverity = get(field, data); + return buildSeverityDescription(values); } else if (field === 'timeline') { const timeline = get(field, data) as FieldValueTimeline; return [ diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/risk_score_mapping/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/risk_score_mapping/index.tsx index 334dde9abe7d..c9e2cb1a8ca2 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/risk_score_mapping/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/risk_score_mapping/index.tsx @@ -51,25 +51,25 @@ export const RiskScoreField = ({ indices, placeholder, }: RiskScoreFieldProps) => { - const [isRiskScoreMappingSelected, setIsRiskScoreMappingSelected] = useState(false); + const [isRiskScoreMappingChecked, setIsRiskScoreMappingChecked] = useState(false); const [initialFieldCheck, setInitialFieldCheck] = useState(true); const fieldTypeFilter = useMemo(() => ['number'], []); useEffect(() => { if ( - !isRiskScoreMappingSelected && + !isRiskScoreMappingChecked && initialFieldCheck && (field.value as AboutStepRiskScore).mapping?.length > 0 ) { - setIsRiskScoreMappingSelected(true); + setIsRiskScoreMappingChecked(true); setInitialFieldCheck(false); } }, [ field, initialFieldCheck, - isRiskScoreMappingSelected, - setIsRiskScoreMappingSelected, + isRiskScoreMappingChecked, + setIsRiskScoreMappingChecked, setInitialFieldCheck, ]); @@ -90,10 +90,6 @@ export const RiskScoreField = ({ [field] ); - const handleRiskScoreMappingSelected = useCallback(() => { - setIsRiskScoreMappingSelected(!isRiskScoreMappingSelected); - }, [isRiskScoreMappingSelected, setIsRiskScoreMappingSelected]); - const selectedField = useMemo(() => { const existingField = (field.value as AboutStepRiskScore).mapping?.[0]?.field ?? ''; const [newSelectedField] = indices.fields.filter( @@ -102,11 +98,15 @@ export const RiskScoreField = ({ return newSelectedField; }, [field.value, indices]); + const handleRiskScoreMappingChecked = useCallback(() => { + setIsRiskScoreMappingChecked(!isRiskScoreMappingChecked); + }, [isRiskScoreMappingChecked, setIsRiskScoreMappingChecked]); + const riskScoreLabel = useMemo(() => { return (
- {i18n.RISK_SCORE} + {i18n.DEFAULT_RISK_SCORE} {i18n.RISK_SCORE_DESCRIPTION} @@ -117,12 +117,12 @@ export const RiskScoreField = ({ const riskScoreMappingLabel = useMemo(() => { return (
- + {i18n.RISK_SCORE_MAPPING} @@ -133,7 +133,7 @@ export const RiskScoreField = ({
); - }, [handleRiskScoreMappingSelected, isRiskScoreMappingSelected]); + }, [handleRiskScoreMappingChecked, isRiskScoreMappingChecked]); return ( @@ -170,7 +170,7 @@ export const RiskScoreField = ({ label={riskScoreMappingLabel} labelAppend={field.labelAppend} helpText={ - isRiskScoreMappingSelected ? ( + isRiskScoreMappingChecked ? ( {i18n.RISK_SCORE_MAPPING_DETAILS} ) : ( '' @@ -184,7 +184,7 @@ export const RiskScoreField = ({ > - {isRiskScoreMappingSelected && ( + {isRiskScoreMappingChecked && ( @@ -193,7 +193,7 @@ export const RiskScoreField = ({ - {i18n.RISK_SCORE} + {i18n.DEFAULT_RISK_SCORE} diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/risk_score_mapping/translations.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/risk_score_mapping/translations.tsx index a75bf19b5b3c..24e82a8f95a6 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/risk_score_mapping/translations.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/risk_score_mapping/translations.tsx @@ -8,6 +8,13 @@ import { i18n } from '@kbn/i18n'; export const RISK_SCORE = i18n.translate( 'xpack.securitySolution.alerts.riskScoreMapping.riskScoreTitle', + { + defaultMessage: 'Risk score', + } +); + +export const DEFAULT_RISK_SCORE = i18n.translate( + 'xpack.securitySolution.alerts.riskScoreMapping.defaultRiskScoreTitle', { defaultMessage: 'Default risk score', } diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/severity_mapping/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/severity_mapping/index.tsx index cc884c1338f3..579c60579b32 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/severity_mapping/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/severity_mapping/index.tsx @@ -6,7 +6,6 @@ import { EuiFormRow, - EuiFieldText, EuiCheckbox, EuiText, EuiFlexGroup, @@ -22,8 +21,16 @@ import { FieldHook } from '../../../../../../../../src/plugins/es_ui_shared/stat import { SeverityOptionItem } from '../step_about_rule/data'; import { CommonUseField } from '../../../../cases/components/create'; import { AboutStepSeverity } from '../../../pages/detection_engine/rules/types'; -import { IIndexPattern } from '../../../../../../../../src/plugins/data/common/index_patterns'; +import { + IFieldType, + IIndexPattern, +} from '../../../../../../../../src/plugins/data/common/index_patterns'; +import { FieldComponent } from '../../../../common/components/autocomplete/field'; +import { AutocompleteFieldMatchComponent } from '../../../../common/components/autocomplete/field_value_match'; +const SeverityMappingParentContainer = styled(EuiFlexItem)` + max-width: 471px; +`; const NestedContent = styled.div` margin-left: 24px; `; @@ -53,6 +60,7 @@ export const SeverityField = ({ }: SeverityFieldProps) => { const [isSeverityMappingChecked, setIsSeverityMappingChecked] = useState(false); const [initialFieldCheck, setInitialFieldCheck] = useState(true); + const fieldValueInputWidth = 160; useEffect(() => { if ( @@ -71,8 +79,28 @@ export const SeverityField = ({ setInitialFieldCheck, ]); - const updateSeverityMapping = useCallback( - (index: number, severity: string, mappingField: string, event) => { + const handleFieldChange = useCallback( + (index: number, severity: string, [newField]: IFieldType[]): void => { + const values = field.value as AboutStepSeverity; + field.setValue({ + value: values.value, + mapping: [ + ...values.mapping.slice(0, index), + { + ...values.mapping[index], + field: newField?.name ?? '', + operator: 'equals', + severity, + }, + ...values.mapping.slice(index + 1), + ], + }); + }, + [field] + ); + + const handleFieldMatchValueChange = useCallback( + (index: number, severity: string, newMatchValue: string): void => { const values = field.value as AboutStepSeverity; field.setValue({ value: values.value, @@ -80,7 +108,7 @@ export const SeverityField = ({ ...values.mapping.slice(0, index), { ...values.mapping[index], - [mappingField]: event.target.value, + value: newMatchValue, operator: 'equals', severity, }, @@ -91,6 +119,21 @@ export const SeverityField = ({ [field] ); + const selectedState = useMemo(() => { + return ( + (field.value as AboutStepSeverity).mapping?.map((mapping) => { + const [newSelectedField] = indices.fields.filter( + ({ name }) => mapping.field != null && mapping.field === name + ); + return { field: newSelectedField, value: mapping.value }; + }) ?? [] + ); + }, [field.value, indices]); + + const handleSeverityMappingSelected = useCallback(() => { + setIsSeverityMappingChecked(!isSeverityMappingChecked); + }, [isSeverityMappingChecked, setIsSeverityMappingChecked]); + const severityLabel = useMemo(() => { return (
@@ -106,16 +149,12 @@ export const SeverityField = ({ const severityMappingLabel = useMemo(() => { return (
- setIsSeverityMappingChecked(!isSeverityMappingChecked)} - > + setIsSeverityMappingChecked(e.target.checked)} + onChange={handleSeverityMappingSelected} /> {i18n.SEVERITY_MAPPING} @@ -126,7 +165,7 @@ export const SeverityField = ({
); - }, [isSeverityMappingChecked, setIsSeverityMappingChecked]); + }, [handleSeverityMappingSelected, isSeverityMappingChecked]); return ( @@ -156,7 +195,7 @@ export const SeverityField = ({ - + - - @@ -227,7 +277,7 @@ export const SeverityField = ({ )} - + ); }; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.test.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.test.ts index f402303c4c62..745518b90df0 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.test.ts +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.test.ts @@ -348,7 +348,6 @@ describe('helpers', () => { references: ['www.test.co'], risk_score: 21, risk_score_mapping: [], - rule_name_override: '', severity: 'low', severity_mapping: [], tags: ['tag1', 'tag2'], @@ -369,7 +368,6 @@ describe('helpers', () => { ], }, ], - timestamp_override: '', }; expect(result).toEqual(expected); @@ -392,7 +390,6 @@ describe('helpers', () => { references: ['www.test.co'], risk_score: 21, risk_score_mapping: [], - rule_name_override: '', severity: 'low', severity_mapping: [], tags: ['tag1', 'tag2'], @@ -413,7 +410,6 @@ describe('helpers', () => { ], }, ], - timestamp_override: '', }; expect(result).toEqual(expected); @@ -434,7 +430,6 @@ describe('helpers', () => { references: ['www.test.co'], risk_score: 21, risk_score_mapping: [], - rule_name_override: '', severity: 'low', severity_mapping: [], tags: ['tag1', 'tag2'], @@ -455,7 +450,6 @@ describe('helpers', () => { ], }, ], - timestamp_override: '', }; expect(result).toEqual(expected); @@ -508,7 +502,6 @@ describe('helpers', () => { references: ['www.test.co'], risk_score: 21, risk_score_mapping: [], - rule_name_override: '', severity: 'low', severity_mapping: [], tags: ['tag1', 'tag2'], @@ -519,7 +512,6 @@ describe('helpers', () => { technique: [{ id: '456', name: 'technique1', reference: 'technique reference' }], }, ], - timestamp_override: '', }; expect(result).toEqual(expected); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.ts index 8331346b19ac..b97c675ddec1 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.ts +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.ts @@ -143,7 +143,7 @@ export const formatAboutStepData = (aboutStepData: AboutStepRule): AboutStepRule references: references.filter((item) => !isEmpty(item)), risk_score: riskScore.value, risk_score_mapping: riskScore.mapping, - rule_name_override: ruleNameOverride, + rule_name_override: ruleNameOverride !== '' ? ruleNameOverride : undefined, severity: severity.value, severity_mapping: severity.mapping, threat: threat @@ -156,7 +156,7 @@ export const formatAboutStepData = (aboutStepData: AboutStepRule): AboutStepRule return { id, name, reference }; }), })), - timestamp_override: timestampOverride, + timestamp_override: timestampOverride !== '' ? timestampOverride : undefined, ...(!isEmpty(note) ? { note } : {}), ...rest, }; 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 f453b5a95994..d4b64cec3285 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 @@ -139,10 +139,10 @@ export interface AboutStepRuleJson { risk_score_mapping: RiskScoreMapping; references: string[]; false_positives: string[]; - rule_name_override: RuleNameOverride; + rule_name_override?: RuleNameOverride; tags: string[]; threat: IMitreEnterpriseAttack[]; - timestamp_override: TimestampOverride; + timestamp_override?: TimestampOverride; note?: string; } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_events_query.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_events_query.test.ts index b368c8fe3605..f764329290e6 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_events_query.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_events_query.test.ts @@ -15,6 +15,7 @@ describe('create_signals', () => { filter: {}, size: 100, searchAfterSortId: undefined, + timestampOverride: undefined, }); expect(query).toEqual({ allowNoIndices: true, @@ -85,6 +86,7 @@ describe('create_signals', () => { filter: {}, size: 100, searchAfterSortId: '', + timestampOverride: undefined, }); expect(query).toEqual({ allowNoIndices: true, @@ -156,6 +158,7 @@ describe('create_signals', () => { filter: {}, size: 100, searchAfterSortId: fakeSortId, + timestampOverride: undefined, }); expect(query).toEqual({ allowNoIndices: true, @@ -228,6 +231,7 @@ describe('create_signals', () => { filter: {}, size: 100, searchAfterSortId: fakeSortIdNumber, + timestampOverride: undefined, }); expect(query).toEqual({ allowNoIndices: true, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_events_query.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_events_query.ts index c75dddf896fd..2b78a2b10b87 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_events_query.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/build_events_query.ts @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +import { TimestampOverrideOrUndefined } from '../../../../common/detection_engine/schemas/common/schemas'; + interface BuildEventsSearchQuery { index: string[]; from: string; @@ -11,6 +13,7 @@ interface BuildEventsSearchQuery { filter: unknown; size: number; searchAfterSortId: string | number | undefined; + timestampOverride: TimestampOverrideOrUndefined; } export const buildEventsSearchQuery = ({ @@ -20,7 +23,9 @@ export const buildEventsSearchQuery = ({ filter, size, searchAfterSortId, + timestampOverride, }: BuildEventsSearchQuery) => { + const timestamp = timestampOverride ?? '@timestamp'; const filterWithTime = [ filter, { @@ -31,7 +36,7 @@ export const buildEventsSearchQuery = ({ should: [ { range: { - '@timestamp': { + [timestamp]: { gte: from, }, }, @@ -45,7 +50,7 @@ export const buildEventsSearchQuery = ({ should: [ { range: { - '@timestamp': { + [timestamp]: { lte: to, }, }, @@ -76,7 +81,7 @@ export const buildEventsSearchQuery = ({ }, sort: [ { - '@timestamp': { + [timestamp]: { order: 'asc', }, }, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.ts index f3025ead69a0..2a0e39cbbf23 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.ts @@ -144,6 +144,7 @@ export const searchAfterAndBulkCreate = async ({ logger, filter, pageSize: tuple.maxSignals < pageSize ? Math.ceil(tuple.maxSignals) : pageSize, // maximum number of docs to receive per search result. + timestampOverride: ruleParams.timestampOverride, } ); toReturn.searchAfterTimes.push(searchDuration); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_search_after.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_search_after.test.ts index 50b0cb27990f..250b891eb1f2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_search_after.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_search_after.test.ts @@ -31,6 +31,7 @@ describe('singleSearchAfter', () => { logger: mockLogger, pageSize: 1, filter: undefined, + timestampOverride: undefined, }); expect(searchResult).toEqual(sampleDocSearchResultsNoSortId); }); @@ -46,6 +47,7 @@ describe('singleSearchAfter', () => { logger: mockLogger, pageSize: 1, filter: undefined, + timestampOverride: undefined, }); expect(searchResult).toEqual(sampleDocSearchResultsWithSortId); }); @@ -64,6 +66,7 @@ describe('singleSearchAfter', () => { logger: mockLogger, pageSize: 1, filter: undefined, + timestampOverride: undefined, }) ).rejects.toThrow('Fake Error'); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_search_after.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_search_after.ts index 409f374d7df1..f3dcc505b8fe 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_search_after.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/single_search_after.ts @@ -10,6 +10,7 @@ import { Logger } from '../../../../../../../src/core/server'; import { SignalSearchResponse } from './types'; import { buildEventsSearchQuery } from './build_events_query'; import { makeFloatString } from './utils'; +import { TimestampOverrideOrUndefined } from '../../../../common/detection_engine/schemas/common/schemas'; interface SingleSearchAfterParams { searchAfterSortId: string | undefined; @@ -20,6 +21,7 @@ interface SingleSearchAfterParams { logger: Logger; pageSize: number; filter: unknown; + timestampOverride: TimestampOverrideOrUndefined; } // utilize search_after for paging results into bulk. @@ -32,6 +34,7 @@ export const singleSearchAfter = async ({ filter, logger, pageSize, + timestampOverride, }: SingleSearchAfterParams): Promise<{ searchResult: SignalSearchResponse; searchDuration: string; @@ -44,6 +47,7 @@ export const singleSearchAfter = async ({ filter, size: pageSize, searchAfterSortId, + timestampOverride, }); const start = performance.now(); const nextSearchAfterResult: SignalSearchResponse = await services.callCluster(