From 4ccb92c4b0468e44270d6068b6aa3135e8cb6d0f Mon Sep 17 00:00:00 2001 From: Kevin Qualters <56408403+kqualters-elastic@users.noreply.github.com> Date: Tue, 4 Oct 2022 09:09:55 -0400 Subject: [PATCH] [Security Solution][Analyzer] Make all analyzer apis have time range as optional (#142536) (cherry picked from commit 890bf7430cd9a62ee1d2f46c16f486bfb6aebd59) --- .../common/endpoint/schema/resolver.ts | 10 ++- .../e2e/detection_alerts/resolver.cy.ts | 4 +- .../resolver/data_access_layer/factory.ts | 75 +++++++++---------- .../data_access_layer/mocks/generator_tree.ts | 8 +- .../mocks/no_ancestors_two_children.ts | 6 +- ..._children_in_index_called_awesome_index.ts | 6 +- ..._children_with_related_events_on_origin.ts | 6 +- .../one_node_with_paginated_related_events.ts | 6 +- .../current_related_event_fetcher.ts | 5 +- .../store/middleware/node_data_fetcher.ts | 4 +- .../middleware/related_events_fetcher.ts | 4 +- .../store/middleware/resolver_tree_fetcher.ts | 6 +- .../public/resolver/types.ts | 8 +- .../routes/resolver/queries/events.ts | 55 +++----------- .../routes/resolver/tree/queries/base.ts | 16 ++-- 15 files changed, 98 insertions(+), 121 deletions(-) diff --git a/x-pack/plugins/security_solution/common/endpoint/schema/resolver.ts b/x-pack/plugins/security_solution/common/endpoint/schema/resolver.ts index 15c89c8cd9c28..6de81d3e95a55 100644 --- a/x-pack/plugins/security_solution/common/endpoint/schema/resolver.ts +++ b/x-pack/plugins/security_solution/common/endpoint/schema/resolver.ts @@ -58,10 +58,12 @@ export const validateEvents = { afterEvent: schema.maybe(schema.string()), }), body: schema.object({ - timeRange: schema.object({ - from: schema.string(), - to: schema.string(), - }), + timeRange: schema.maybe( + schema.object({ + from: schema.string(), + to: schema.string(), + }) + ), indexPatterns: schema.arrayOf(schema.string()), filter: schema.maybe(schema.string()), entityType: schema.maybe(schema.string()), diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/resolver.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/resolver.cy.ts index aa2263b9b518c..c2436f3f2de9a 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/resolver.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/resolver.cy.ts @@ -28,12 +28,12 @@ describe('Analyze events view for alerts', () => { waitForAlertsToPopulate(); }); - it('should render analyzer when button is clicked', () => { + it('should render when button is clicked', () => { openAnalyzerForFirstAlertInTimeline(); cy.get(ANALYZER_NODE).first().should('be.visible'); }); - it(`should render an analyzer view and display + it(`should display a toast indicating the date range of found events when a time range has 0 events in it`, () => { const dateContainingZeroEvents = 'Jul 27, 2022 @ 00:00:00.000'; setStartDate(dateContainingZeroEvents); diff --git a/x-pack/plugins/security_solution/public/resolver/data_access_layer/factory.ts b/x-pack/plugins/security_solution/public/resolver/data_access_layer/factory.ts index 04e694b2cedbb..719fdedb73546 100644 --- a/x-pack/plugins/security_solution/public/resolver/data_access_layer/factory.ts +++ b/x-pack/plugins/security_solution/public/resolver/data_access_layer/factory.ts @@ -17,6 +17,17 @@ import type { ResolverSchema, } from '../../../common/endpoint/types'; +function getRangeFilter(timeRange: TimeRange | undefined) { + return timeRange + ? { + timeRange: { + from: timeRange.from, + to: timeRange.to, + }, + } + : []; +} + /** * The data access layer for resolver. All communication with the Kibana server is done through this object. This object is provided to Resolver. In tests, a mock data access layer can be used instead. */ @@ -34,7 +45,7 @@ export function dataAccessLayerFactory( indexPatterns, }: { entityID: string; - timeRange: TimeRange; + timeRange?: TimeRange; indexPatterns: string[]; }): Promise { const response: ResolverPaginatedEvents = await context.services.http.post( @@ -43,10 +54,7 @@ export function dataAccessLayerFactory( query: {}, body: JSON.stringify({ indexPatterns, - timeRange: { - from: timeRange.from, - to: timeRange.to, - }, + ...getRangeFilter(timeRange), filter: JSON.stringify({ bool: { filter: [ @@ -76,16 +84,13 @@ export function dataAccessLayerFactory( entityID: string; category: string; after?: string; - timeRange: TimeRange; + timeRange?: TimeRange; indexPatterns: string[]; }): Promise { const commonFields = { query: { afterEvent: after, limit: 25 }, body: { - timeRange: { - from: timeRange.from, - to: timeRange.to, - }, + ...getRangeFilter(timeRange), indexPatterns, }, }; @@ -127,30 +132,28 @@ export function dataAccessLayerFactory( limit, }: { ids: string[]; - timeRange: TimeRange; + timeRange?: TimeRange; indexPatterns: string[]; limit: number; }): Promise { - const response: ResolverPaginatedEvents = await context.services.http.post( - '/api/endpoint/resolver/events', - { - query: { limit }, - body: JSON.stringify({ - timeRange: { - from: timeRange.from, - to: timeRange.to, + const query = { + query: { limit }, + body: JSON.stringify({ + indexPatterns, + ...getRangeFilter(timeRange), + filter: JSON.stringify({ + bool: { + filter: [ + { terms: { 'process.entity_id': ids } }, + { term: { 'event.category': 'process' } }, + ], }, - indexPatterns, - filter: JSON.stringify({ - bool: { - filter: [ - { terms: { 'process.entity_id': ids } }, - { term: { 'event.category': 'process' } }, - ], - }, - }), }), - } + }), + }; + const response: ResolverPaginatedEvents = await context.services.http.post( + '/api/endpoint/resolver/events', + query ); return response.events; }, @@ -172,7 +175,7 @@ export function dataAccessLayerFactory( eventTimestamp: string; eventID?: string | number; winlogRecordID: string; - timeRange: TimeRange; + timeRange?: TimeRange; indexPatterns: string[]; }): Promise { /** @description - eventID isn't provided by winlog. This can be removed once runtime fields are available */ @@ -200,10 +203,7 @@ export function dataAccessLayerFactory( query: { limit: 1 }, body: JSON.stringify({ indexPatterns, - timeRange: { - from: timeRange.from, - to: timeRange.to, - }, + ...getRangeFilter(timeRange), filter: JSON.stringify(filter), }), } @@ -217,10 +217,7 @@ export function dataAccessLayerFactory( query: { limit: 1 }, body: JSON.stringify({ indexPatterns, - timeRange: { - from: timeRange.from, - to: timeRange.to, - }, + ...getRangeFilter(timeRange), entityType: 'alertDetail', eventID, }), @@ -250,7 +247,7 @@ export function dataAccessLayerFactory( }: { dataId: string; schema: ResolverSchema; - timeRange: TimeRange; + timeRange?: TimeRange; indices: string[]; ancestors: number; descendants: number; diff --git a/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/generator_tree.ts b/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/generator_tree.ts index 130b81c5622b2..6b833c93704b4 100644 --- a/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/generator_tree.ts +++ b/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/generator_tree.ts @@ -63,7 +63,7 @@ export function generateTreeWithDAL( indexPatterns, }: { entityID: string; - timeRange: TimeRange; + timeRange?: TimeRange; indexPatterns: string[]; }): Promise { const node = allNodes.get(entityID); @@ -88,7 +88,7 @@ export function generateTreeWithDAL( entityID: string; category: string; after?: string; - timeRange: TimeRange; + timeRange?: TimeRange; indexPatterns: string[]; }): Promise<{ events: SafeResolverEvent[]; nextEvent: string | null }> { const node = allNodes.get(entityID); @@ -119,7 +119,7 @@ export function generateTreeWithDAL( eventCategory: string[]; eventTimestamp: string; eventID?: string | number; - timeRange: TimeRange; + timeRange?: TimeRange; indexPatterns: string[]; }): Promise { return null; @@ -135,7 +135,7 @@ export function generateTreeWithDAL( limit, }: { ids: string[]; - timeRange: TimeRange; + timeRange?: TimeRange; indexPatterns: string[]; limit: number; }): Promise { diff --git a/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/no_ancestors_two_children.ts b/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/no_ancestors_two_children.ts index 000d08b4e15c7..e883a96b162e8 100644 --- a/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/no_ancestors_two_children.ts +++ b/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/no_ancestors_two_children.ts @@ -59,7 +59,7 @@ export function noAncestorsTwoChildren(): { dataAccessLayer: DataAccessLayer; me indexPatterns, }: { entityID: string; - timeRange: TimeRange; + timeRange?: TimeRange; indexPatterns: string[]; }): Promise { return Promise.resolve({ @@ -83,7 +83,7 @@ export function noAncestorsTwoChildren(): { dataAccessLayer: DataAccessLayer; me entityID: string; category: string; after?: string; - timeRange: TimeRange; + timeRange?: TimeRange; indexPatterns: string[]; }): Promise<{ events: SafeResolverEvent[]; @@ -110,7 +110,7 @@ export function noAncestorsTwoChildren(): { dataAccessLayer: DataAccessLayer; me eventTimestamp: string; eventID?: string | number; winlogRecordID: string; - timeRange: TimeRange; + timeRange?: TimeRange; indexPatterns: string[]; }): Promise { return null; diff --git a/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/no_ancestors_two_children_in_index_called_awesome_index.ts b/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/no_ancestors_two_children_in_index_called_awesome_index.ts index 808c4463f3a89..c4c7fda097e8f 100644 --- a/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/no_ancestors_two_children_in_index_called_awesome_index.ts +++ b/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/no_ancestors_two_children_in_index_called_awesome_index.ts @@ -64,7 +64,7 @@ export function noAncestorsTwoChildenInIndexCalledAwesomeIndex(): { indexPatterns, }: { entityID: string; - timeRange: TimeRange; + timeRange?: TimeRange; indexPatterns: string[]; }): Promise { return Promise.resolve({ @@ -90,7 +90,7 @@ export function noAncestorsTwoChildenInIndexCalledAwesomeIndex(): { entityID: string; category: string; after?: string; - timeRange: TimeRange; + timeRange?: TimeRange; indexPatterns: string[]; }): Promise<{ events: SafeResolverEvent[]; @@ -121,7 +121,7 @@ export function noAncestorsTwoChildenInIndexCalledAwesomeIndex(): { eventTimestamp: string; eventID?: string | number; winlogRecordID: string; - timeRange: TimeRange; + timeRange?: TimeRange; indexPatterns: string[]; }): Promise { return mockEndpointEvent({ diff --git a/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/no_ancestors_two_children_with_related_events_on_origin.ts b/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/no_ancestors_two_children_with_related_events_on_origin.ts index 774111baf165d..30f7e07bf041a 100644 --- a/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/no_ancestors_two_children_with_related_events_on_origin.ts +++ b/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/no_ancestors_two_children_with_related_events_on_origin.ts @@ -67,7 +67,7 @@ export function noAncestorsTwoChildrenWithRelatedEventsOnOrigin(): { indexPatterns, }: { entityID: string; - timeRange: TimeRange; + timeRange?: TimeRange; indexPatterns: string[]; }): Promise { /** @@ -97,7 +97,7 @@ export function noAncestorsTwoChildrenWithRelatedEventsOnOrigin(): { entityID: string; category: string; after?: string; - timeRange: TimeRange; + timeRange?: TimeRange; indexPatterns: string[]; }): Promise<{ events: SafeResolverEvent[]; nextEvent: string | null }> { const events = @@ -129,7 +129,7 @@ export function noAncestorsTwoChildrenWithRelatedEventsOnOrigin(): { eventTimestamp: string; eventID?: string | number; winlogRecordID: string; - timeRange: TimeRange; + timeRange?: TimeRange; indexPatterns: string[]; }): Promise { return relatedEvents.events.find((event) => eventModel.eventID(event) === eventID) ?? null; diff --git a/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/one_node_with_paginated_related_events.ts b/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/one_node_with_paginated_related_events.ts index 7eb8c28a433e3..dc7031acdbd91 100644 --- a/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/one_node_with_paginated_related_events.ts +++ b/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/one_node_with_paginated_related_events.ts @@ -58,7 +58,7 @@ export function oneNodeWithPaginatedEvents(): { indexPatterns, }: { entityID: string; - timeRange: TimeRange; + timeRange?: TimeRange; indexPatterns: string[]; }): Promise { /** @@ -86,7 +86,7 @@ export function oneNodeWithPaginatedEvents(): { entityID: string; category: string; after?: string; - timeRange: TimeRange; + timeRange?: TimeRange; indexPatterns: string[]; }): Promise<{ events: SafeResolverEvent[]; nextEvent: string | null }> { let events: SafeResolverEvent[] = []; @@ -121,7 +121,7 @@ export function oneNodeWithPaginatedEvents(): { eventTimestamp: string; eventID?: string | number; winlogRecordID: string; - timeRange: TimeRange; + timeRange?: TimeRange; indexPatterns: string[]; }): Promise { return mockTree.events.find((event) => eventModel.eventID(event) === eventID) ?? null; diff --git a/x-pack/plugins/security_solution/public/resolver/store/middleware/current_related_event_fetcher.ts b/x-pack/plugins/security_solution/public/resolver/store/middleware/current_related_event_fetcher.ts index 6b58dd4e8e62e..cd4119f9569e7 100644 --- a/x-pack/plugins/security_solution/public/resolver/store/middleware/current_related_event_fetcher.ts +++ b/x-pack/plugins/security_solution/public/resolver/store/middleware/current_related_event_fetcher.ts @@ -48,8 +48,9 @@ export function CurrentRelatedEventFetcher( api.dispatch({ type: 'appRequestedCurrentRelatedEventData', }); - const timeRangeFilters = selectors.timeRangeFilters(state); - + const detectedBounds = selectors.detectedBounds(state); + const timeRangeFilters = + detectedBounds !== undefined ? undefined : selectors.timeRangeFilters(state); let result: SafeResolverEvent | null = null; try { result = await dataAccessLayer.event({ diff --git a/x-pack/plugins/security_solution/public/resolver/store/middleware/node_data_fetcher.ts b/x-pack/plugins/security_solution/public/resolver/store/middleware/node_data_fetcher.ts index c3173b3238737..9a3a9eb3450fd 100644 --- a/x-pack/plugins/security_solution/public/resolver/store/middleware/node_data_fetcher.ts +++ b/x-pack/plugins/security_solution/public/resolver/store/middleware/node_data_fetcher.ts @@ -60,7 +60,9 @@ export function NodeDataFetcher( let results: SafeResolverEvent[] | undefined; try { - const timeRangeFilters = selectors.timeRangeFilters(state); + const detectedBounds = selectors.detectedBounds(state); + const timeRangeFilters = + detectedBounds !== undefined ? undefined : selectors.timeRangeFilters(state); results = await dataAccessLayer.nodeData({ ids: Array.from(newIDsToRequest), timeRange: timeRangeFilters, diff --git a/x-pack/plugins/security_solution/public/resolver/store/middleware/related_events_fetcher.ts b/x-pack/plugins/security_solution/public/resolver/store/middleware/related_events_fetcher.ts index ec0f068b5425c..ab8f71940104e 100644 --- a/x-pack/plugins/security_solution/public/resolver/store/middleware/related_events_fetcher.ts +++ b/x-pack/plugins/security_solution/public/resolver/store/middleware/related_events_fetcher.ts @@ -30,7 +30,9 @@ export function RelatedEventsFetcher( const indices = selectors.eventIndices(state); const oldParams = last; - const timeRangeFilters = selectors.timeRangeFilters(state); + const detectedBounds = selectors.detectedBounds(state); + const timeRangeFilters = + detectedBounds !== undefined ? undefined : selectors.timeRangeFilters(state); // Update this each time before fetching data (or even if we don't fetch data) so that subsequent actions that call this (concurrently) will have up to date info. last = newParams; diff --git a/x-pack/plugins/security_solution/public/resolver/store/middleware/resolver_tree_fetcher.ts b/x-pack/plugins/security_solution/public/resolver/store/middleware/resolver_tree_fetcher.ts index e4da1af5f4d79..61319158fccc2 100644 --- a/x-pack/plugins/security_solution/public/resolver/store/middleware/resolver_tree_fetcher.ts +++ b/x-pack/plugins/security_solution/public/resolver/store/middleware/resolver_tree_fetcher.ts @@ -93,9 +93,9 @@ export function ResolverTreeFetcher( descendants: descendantsRequestAmount(), }); if (unboundedTree.length > 0) { - const timestamps = unboundedTree.map((event) => - firstNonNullValue(event.data['@timestamp']) - ); + const timestamps = unboundedTree + .map((event) => firstNonNullValue(event.data['@timestamp'])) + .sort(); const oldestTimestamp = timestamps[0]; const newestTimestamp = timestamps.slice(-1); api.dispatch({ diff --git a/x-pack/plugins/security_solution/public/resolver/types.ts b/x-pack/plugins/security_solution/public/resolver/types.ts index 00ecd995176eb..88e97f416dc49 100644 --- a/x-pack/plugins/security_solution/public/resolver/types.ts +++ b/x-pack/plugins/security_solution/public/resolver/types.ts @@ -692,7 +692,7 @@ export interface DataAccessLayer { indexPatterns, }: { entityID: string; - timeRange: TimeRange; + timeRange?: TimeRange; indexPatterns: string[]; }) => Promise; @@ -710,7 +710,7 @@ export interface DataAccessLayer { entityID: string; category: string; after?: string; - timeRange: TimeRange; + timeRange?: TimeRange; indexPatterns: string[]; }) => Promise; @@ -725,7 +725,7 @@ export interface DataAccessLayer { limit, }: { ids: string[]; - timeRange: TimeRange; + timeRange?: TimeRange; indexPatterns: string[]; limit: number; }): Promise; @@ -747,7 +747,7 @@ export interface DataAccessLayer { eventTimestamp: string; eventID?: string | number; winlogRecordID: string; - timeRange: TimeRange; + timeRange?: TimeRange; indexPatterns: string[]; }) => Promise; diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/queries/events.ts b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/queries/events.ts index ba4f682423670..869ae911ad890 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/queries/events.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/queries/events.ts @@ -11,31 +11,22 @@ import type { JsonObject, JsonValue } from '@kbn/utility-types'; import { parseFilterQuery } from '../../../../utils/serialized_query'; import type { SafeResolverEvent } from '../../../../../common/endpoint/types'; import type { PaginationBuilder } from '../utils/pagination'; - -interface TimeRange { - from: string; - to: string; -} +import { BaseResolverQuery } from '../tree/queries/base'; +import type { ResolverQueryParams } from '../tree/queries/base'; /** * Builds a query for retrieving events. */ -export class EventsQuery { - private readonly pagination: PaginationBuilder; - private readonly indexPatterns: string | string[]; - private readonly timeRange: TimeRange; +export class EventsQuery extends BaseResolverQuery { + readonly pagination: PaginationBuilder; constructor({ - pagination, indexPatterns, timeRange, - }: { - pagination: PaginationBuilder; - indexPatterns: string | string[]; - timeRange: TimeRange; - }) { + isInternalRequest, + pagination, + }: ResolverQueryParams & { pagination: PaginationBuilder }) { + super({ indexPatterns, timeRange, isInternalRequest }); this.pagination = pagination; - this.indexPatterns = indexPatterns; - this.timeRange = timeRange; } private query(filters: JsonObject[]): JsonObject { @@ -44,15 +35,7 @@ export class EventsQuery { bool: { filter: [ ...filters, - { - range: { - '@timestamp': { - gte: this.timeRange.from, - lte: this.timeRange.to, - format: 'strict_date_optional_time', - }, - }, - }, + ...this.getRangeFilter(), { term: { 'event.kind': 'event' }, }, @@ -71,15 +54,7 @@ export class EventsQuery { { term: { 'event.id': id }, }, - { - range: { - '@timestamp': { - gte: this.timeRange.from, - lte: this.timeRange.to, - format: 'strict_date_optional_time', - }, - }, - }, + ...this.getRangeFilter(), ], }, }, @@ -97,15 +72,7 @@ export class EventsQuery { { term: { 'process.entity_id': id }, }, - { - range: { - '@timestamp': { - gte: this.timeRange.from, - lte: this.timeRange.to, - format: 'strict_date_optional_time', - }, - }, - }, + ...this.getRangeFilter(), ], }, }, diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/queries/base.ts b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/queries/base.ts index 6637e7931b056..256f2b58b6864 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/queries/base.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/queries/base.ts @@ -11,10 +11,10 @@ import type { TimeRange } from '../utils'; import { resolverFields } from '../utils'; export interface ResolverQueryParams { - readonly schema: ResolverSchema; + readonly schema?: ResolverSchema; readonly indexPatterns: string | string[]; readonly timeRange: TimeRange | undefined; - readonly isInternalRequest: boolean; + readonly isInternalRequest?: boolean; readonly resolverFields?: JsonValue[]; getRangeFilter?: () => Array<{ range: { '@timestamp': { gte: string; lte: string; format: string } }; @@ -25,12 +25,18 @@ export class BaseResolverQuery implements ResolverQueryParams { readonly schema: ResolverSchema; readonly indexPatterns: string | string[]; readonly timeRange: TimeRange | undefined; - readonly isInternalRequest: boolean; + readonly isInternalRequest?: boolean; readonly resolverFields?: JsonValue[]; constructor({ schema, indexPatterns, timeRange, isInternalRequest }: ResolverQueryParams) { - this.resolverFields = resolverFields(schema); - this.schema = schema; + const schemaOrDefault = schema + ? schema + : { + id: 'process.entity_id', + parent: 'process.parent.entity_id', + }; + this.resolverFields = resolverFields(schemaOrDefault); + this.schema = schemaOrDefault; this.indexPatterns = indexPatterns; this.timeRange = timeRange; this.isInternalRequest = isInternalRequest;