mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 01:13:23 -04:00
[Bug][Investigations] - Fix slow timeline queries (#176838)
## Summary **Version Affected: 8.11.x, 8.12.0, 8.12.1** ### Background The ID field necessary to track long running timeline search strategy queries was no longer being passed to ES search after work in 8.11. This led to what looked like long running timeline queries, but in reality were queries being repeated due to the ID not being tracked. This pr re-introduces the ID field necessary for long running timeline search strategies in security solution **Views Affected:** - Timeline tabs (query, correlation, pinned) - Explore events tables (hosts, users, network) - Rule preview table Pre-fix: Observer the changing ID's for the `timelineSearchStrategy` `eventsAll` queries.5731d310
-d3ed-452d-8c34-783b2cfe76e1 Post-fix: Observer the same ID for the `timelineSearchStrategy` `eventsAll` queries.a20d4b28
-2748-4475-a257-96133bb8efc7 --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
e2dfb09ed3
commit
68bdd7cb27
8 changed files with 434 additions and 0 deletions
|
@ -0,0 +1,109 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { timelineEqlRequestOptionsSchema } from './eql';
|
||||
import { mockBaseTimelineRequest } from './mocks/base_timeline_request';
|
||||
|
||||
const mockEqlRequestOptions = {
|
||||
...mockBaseTimelineRequest,
|
||||
filterQuery: 'sequence\n[any where true]\n[any where true]',
|
||||
eventCategoryField: 'event.category',
|
||||
tiebreakerField: '',
|
||||
fieldRequested: [
|
||||
'@timestamp',
|
||||
'message',
|
||||
'event.category',
|
||||
'event.action',
|
||||
'host.name',
|
||||
'source.ip',
|
||||
'destination.ip',
|
||||
'user.name',
|
||||
'@timestamp',
|
||||
'kibana.alert.workflow_status',
|
||||
'kibana.alert.workflow_tags',
|
||||
'kibana.alert.workflow_assignee_ids',
|
||||
'kibana.alert.group.id',
|
||||
'kibana.alert.original_time',
|
||||
'kibana.alert.building_block_type',
|
||||
'kibana.alert.rule.from',
|
||||
'kibana.alert.rule.name',
|
||||
'kibana.alert.rule.to',
|
||||
'kibana.alert.rule.uuid',
|
||||
'kibana.alert.rule.rule_id',
|
||||
'kibana.alert.rule.type',
|
||||
'kibana.alert.suppression.docs_count',
|
||||
'kibana.alert.original_event.kind',
|
||||
'kibana.alert.original_event.module',
|
||||
'file.path',
|
||||
'file.Ext.code_signature.subject_name',
|
||||
'file.Ext.code_signature.trusted',
|
||||
'file.hash.sha256',
|
||||
'host.os.family',
|
||||
'event.code',
|
||||
'process.entry_leader.entity_id',
|
||||
],
|
||||
language: 'eql',
|
||||
pagination: {
|
||||
activePage: 0,
|
||||
querySize: 25,
|
||||
},
|
||||
runtimeMappings: {},
|
||||
size: 100,
|
||||
sort: [
|
||||
{
|
||||
direction: 'asc',
|
||||
esTypes: ['date'],
|
||||
field: '@timestamp',
|
||||
type: 'date',
|
||||
},
|
||||
],
|
||||
timerange: {
|
||||
from: '2018-02-12T20:39:22.229Z',
|
||||
interval: '12h',
|
||||
to: '2024-02-13T20:39:22.229Z',
|
||||
},
|
||||
timestampField: '@timestamp',
|
||||
};
|
||||
|
||||
describe('timelineEqlRequestOptionsSchema', () => {
|
||||
it('should correctly parse the last eql request object without unknown fields', () => {
|
||||
expect(timelineEqlRequestOptionsSchema.parse(mockEqlRequestOptions)).toEqual(
|
||||
mockEqlRequestOptions
|
||||
);
|
||||
});
|
||||
|
||||
it('should correctly parse the last eql request object and remove unknown fields', () => {
|
||||
const invalidEqlRequest = {
|
||||
...mockEqlRequestOptions,
|
||||
unknownField: 'should-be-removed',
|
||||
};
|
||||
expect(timelineEqlRequestOptionsSchema.parse(invalidEqlRequest)).toEqual(mockEqlRequestOptions);
|
||||
});
|
||||
|
||||
it('should correctly error if an incorrect field type is provided for a schema key', () => {
|
||||
const invalidEqlRequest = {
|
||||
...mockEqlRequestOptions,
|
||||
fieldRequested: 123,
|
||||
};
|
||||
|
||||
expect(() => {
|
||||
timelineEqlRequestOptionsSchema.parse(invalidEqlRequest);
|
||||
}).toThrowErrorMatchingInlineSnapshot(`
|
||||
"[
|
||||
{
|
||||
\\"code\\": \\"invalid_type\\",
|
||||
\\"expected\\": \\"array\\",
|
||||
\\"received\\": \\"number\\",
|
||||
\\"path\\": [
|
||||
\\"fieldRequested\\"
|
||||
],
|
||||
\\"message\\": \\"Expected array, received number\\"
|
||||
}
|
||||
]"
|
||||
`);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { timelineEventsAllSchema } from './events_all';
|
||||
import { mockBaseTimelineRequest } from './mocks/base_timeline_request';
|
||||
|
||||
const mockEventsAllRequest = {
|
||||
...mockBaseTimelineRequest,
|
||||
factoryQueryType: 'eventsAll',
|
||||
excludeEcsData: false,
|
||||
pagination: { activePage: 0, querySize: 25 },
|
||||
fieldRequested: [
|
||||
'@timestamp',
|
||||
'_index',
|
||||
'message',
|
||||
'host.name',
|
||||
'event.module',
|
||||
'agent.type',
|
||||
'event.dataset',
|
||||
'event.action',
|
||||
'user.name',
|
||||
'source.ip',
|
||||
'destination.ip',
|
||||
],
|
||||
sort: [
|
||||
{
|
||||
field: '@timestamp',
|
||||
type: 'date',
|
||||
direction: 'desc',
|
||||
esTypes: [],
|
||||
},
|
||||
],
|
||||
fields: [],
|
||||
language: 'kuery',
|
||||
};
|
||||
|
||||
describe('timelineEventsAllSchema', () => {
|
||||
it('should correctly parse the events request object', () => {
|
||||
expect(timelineEventsAllSchema.parse(mockEventsAllRequest)).toEqual(mockEventsAllRequest);
|
||||
});
|
||||
|
||||
it('should correctly parse the events request object and remove unknown fields', () => {
|
||||
const invalidEventsRequest = {
|
||||
...mockEventsAllRequest,
|
||||
unknownField: 'shouldBeRemoved',
|
||||
};
|
||||
expect(timelineEventsAllSchema.parse(invalidEventsRequest)).toEqual(mockEventsAllRequest);
|
||||
});
|
||||
|
||||
it('should correctly error if an incorrect field type is provided for a schema key', () => {
|
||||
const invalidEventsRequest = {
|
||||
...mockEventsAllRequest,
|
||||
excludeEcsData: 'notABoolean',
|
||||
};
|
||||
|
||||
expect(() => {
|
||||
timelineEventsAllSchema.parse(invalidEventsRequest);
|
||||
}).toThrowErrorMatchingInlineSnapshot(`
|
||||
"[
|
||||
{
|
||||
\\"code\\": \\"invalid_type\\",
|
||||
\\"expected\\": \\"boolean\\",
|
||||
\\"received\\": \\"string\\",
|
||||
\\"path\\": [
|
||||
\\"excludeEcsData\\"
|
||||
],
|
||||
\\"message\\": \\"Expected boolean, received string\\"
|
||||
}
|
||||
]"
|
||||
`);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { timelineEventsDetailsSchema } from './events_details';
|
||||
|
||||
const mockEventsDetails = {
|
||||
entityType: 'events',
|
||||
indexName: 'test-large-index',
|
||||
eventId: 'enfXnY0Byt9Ce9tO1aWh',
|
||||
factoryQueryType: 'eventsDetails',
|
||||
runtimeMappings: {},
|
||||
};
|
||||
|
||||
describe('timelineEventsDetailsSchema', () => {
|
||||
it('should correctly parse the event details request schema', () => {
|
||||
expect(timelineEventsDetailsSchema.parse(mockEventsDetails)).toEqual(mockEventsDetails);
|
||||
});
|
||||
|
||||
it('should correctly parse the event details request schema and remove unknown fields', () => {
|
||||
const invalidEventsDetailsRequest = {
|
||||
...mockEventsDetails,
|
||||
unknownField: 'should-be-removed',
|
||||
};
|
||||
expect(timelineEventsDetailsSchema.parse(invalidEventsDetailsRequest)).toEqual(
|
||||
mockEventsDetails
|
||||
);
|
||||
});
|
||||
|
||||
it('should correctly error if an incorrect field type is provided for a schema key', () => {
|
||||
const invalidEventsDetailsRequest = {
|
||||
...mockEventsDetails,
|
||||
indexName: 123,
|
||||
};
|
||||
|
||||
expect(() => {
|
||||
timelineEventsDetailsSchema.parse(invalidEventsDetailsRequest);
|
||||
}).toThrowErrorMatchingInlineSnapshot(`
|
||||
"[
|
||||
{
|
||||
\\"code\\": \\"invalid_type\\",
|
||||
\\"expected\\": \\"string\\",
|
||||
\\"received\\": \\"number\\",
|
||||
\\"path\\": [
|
||||
\\"indexName\\"
|
||||
],
|
||||
\\"message\\": \\"Expected string, received number\\"
|
||||
}
|
||||
]"
|
||||
`);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { timelineEventsLastEventTimeRequestSchema } from './events_last_event_time';
|
||||
import { mockBaseTimelineRequest } from './mocks/base_timeline_request';
|
||||
|
||||
const mockEventsLastEventTimeRequest = {
|
||||
...mockBaseTimelineRequest,
|
||||
// Remove fields that are omitted in the schema
|
||||
runtimeMappings: undefined,
|
||||
filterQuery: undefined,
|
||||
timerange: undefined,
|
||||
// Add eventsLastEventTime specific fields
|
||||
factoryQueryType: 'eventsLastEventTime',
|
||||
indexKey: 'hosts',
|
||||
details: {},
|
||||
};
|
||||
|
||||
describe('timelineEventsLastEventTimeRequestSchema', () => {
|
||||
it('should correctly parse the last event time request object without unknown fields', () => {
|
||||
expect(timelineEventsLastEventTimeRequestSchema.parse(mockEventsLastEventTimeRequest)).toEqual(
|
||||
mockEventsLastEventTimeRequest
|
||||
);
|
||||
});
|
||||
|
||||
it('should correctly parse the last event time request object and remove unknown fields', () => {
|
||||
const invalidEventsDetailsRequest = {
|
||||
...mockEventsLastEventTimeRequest,
|
||||
unknownField: 'should-be-removed',
|
||||
};
|
||||
expect(timelineEventsLastEventTimeRequestSchema.parse(invalidEventsDetailsRequest)).toEqual(
|
||||
mockEventsLastEventTimeRequest
|
||||
);
|
||||
});
|
||||
|
||||
it('should correctly error if an incorrect field type is provided for a schema key', () => {
|
||||
const invalidEventsDetailsRequest = {
|
||||
...mockEventsLastEventTimeRequest,
|
||||
indexKey: 'unknown-key',
|
||||
};
|
||||
|
||||
expect(() => {
|
||||
timelineEventsLastEventTimeRequestSchema.parse(invalidEventsDetailsRequest);
|
||||
}).toThrowErrorMatchingInlineSnapshot(`
|
||||
"[
|
||||
{
|
||||
\\"received\\": \\"unknown-key\\",
|
||||
\\"code\\": \\"invalid_enum_value\\",
|
||||
\\"options\\": [
|
||||
\\"hostDetails\\",
|
||||
\\"hosts\\",
|
||||
\\"users\\",
|
||||
\\"userDetails\\",
|
||||
\\"ipDetails\\",
|
||||
\\"network\\"
|
||||
],
|
||||
\\"path\\": [
|
||||
\\"indexKey\\"
|
||||
],
|
||||
\\"message\\": \\"Invalid enum value. Expected 'hostDetails' | 'hosts' | 'users' | 'userDetails' | 'ipDetails' | 'network', received 'unknown-key'\\"
|
||||
}
|
||||
]"
|
||||
`);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { timelineKpiRequestOptionsSchema } from './kpi';
|
||||
import { mockBaseTimelineRequest } from './mocks/base_timeline_request';
|
||||
|
||||
const mockKpiRequest = {
|
||||
...mockBaseTimelineRequest,
|
||||
factoryQueryType: 'eventsKpi',
|
||||
};
|
||||
|
||||
describe('timelineKpiRequestOptionsSchema', () => {
|
||||
it('should correctly parse the events kpi request object', () => {
|
||||
expect(timelineKpiRequestOptionsSchema.parse(mockKpiRequest)).toEqual(mockKpiRequest);
|
||||
});
|
||||
|
||||
it('should correctly parse the events kpi request object and remove unknown fields', () => {
|
||||
const invalidKpiRequest = {
|
||||
...mockKpiRequest,
|
||||
unknownField: 'shouldBeRemoved',
|
||||
};
|
||||
expect(timelineKpiRequestOptionsSchema.parse(invalidKpiRequest)).toEqual(mockKpiRequest);
|
||||
});
|
||||
|
||||
it('should correctly error if an incorrect field type is provided for a schema key', () => {
|
||||
const invalidKpiRequest = {
|
||||
...mockKpiRequest,
|
||||
factoryQueryType: 'someOtherType',
|
||||
};
|
||||
|
||||
expect(() => {
|
||||
timelineKpiRequestOptionsSchema.parse(invalidKpiRequest);
|
||||
}).toThrowErrorMatchingInlineSnapshot(`
|
||||
"[
|
||||
{
|
||||
\\"received\\": \\"someOtherType\\",
|
||||
\\"code\\": \\"invalid_literal\\",
|
||||
\\"expected\\": \\"eventsKpi\\",
|
||||
\\"path\\": [
|
||||
\\"factoryQueryType\\"
|
||||
],
|
||||
\\"message\\": \\"Invalid literal value, expected \\\\\\"eventsKpi\\\\\\"\\"
|
||||
}
|
||||
]"
|
||||
`);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
export const mockBaseTimelineRequest = {
|
||||
id: 'Fnh1dVQ4SDRTUldtRXpUcDEwZXliWHcdZXdlWVBFWkVSWHVIdzY4a19JbFRvUTozMzgzNzk=',
|
||||
defaultIndex: ['*-large-index'],
|
||||
filterQuery:
|
||||
'{"bool":{"must":[],"filter":[{"bool":{"should":[{"exists":{"field":"host.name"}}],"minimum_should_match":1}},{"range":{"@timestamp":{"gte":"2019-02-13T15:39:10.392Z","lt":"2024-02-14T04:59:59.999Z","format":"strict_date_optional_time"}}}],"should":[],"must_not":[]}}',
|
||||
runtimeMappings: {},
|
||||
timerange: {
|
||||
interval: '12h',
|
||||
from: '2019-02-13T15:39:10.392Z',
|
||||
to: '2024-02-14T04:59:59.999Z',
|
||||
},
|
||||
entityType: 'events',
|
||||
};
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { timelineRequestBasicOptionsSchema } from './request_basic';
|
||||
import { mockBaseTimelineRequest } from './mocks/base_timeline_request';
|
||||
|
||||
describe('timelineRequestBasicOptionsSchema', () => {
|
||||
it('should correctly parse the base timeline request object', () => {
|
||||
expect(timelineRequestBasicOptionsSchema.parse(mockBaseTimelineRequest)).toEqual(
|
||||
mockBaseTimelineRequest
|
||||
);
|
||||
});
|
||||
|
||||
it('should correctly parse the base timeline request object and remove unknown fields', () => {
|
||||
const invalidBaseTimelineRequest = {
|
||||
...mockBaseTimelineRequest,
|
||||
iAmNotAllowed: 'butWhy?',
|
||||
};
|
||||
expect(timelineRequestBasicOptionsSchema.parse(invalidBaseTimelineRequest)).toEqual(
|
||||
mockBaseTimelineRequest
|
||||
);
|
||||
});
|
||||
|
||||
it('should correctly error if an incorrect field type is provided for a schema key', () => {
|
||||
const invalidBaseTimelineRequest = {
|
||||
...mockBaseTimelineRequest,
|
||||
entityType: 'notAValidEntityType',
|
||||
};
|
||||
|
||||
expect(() => {
|
||||
timelineRequestBasicOptionsSchema.parse(invalidBaseTimelineRequest);
|
||||
}).toThrowErrorMatchingInlineSnapshot(`
|
||||
"[
|
||||
{
|
||||
\\"received\\": \\"notAValidEntityType\\",
|
||||
\\"code\\": \\"invalid_enum_value\\",
|
||||
\\"options\\": [
|
||||
\\"events\\",
|
||||
\\"sessions\\"
|
||||
],
|
||||
\\"path\\": [
|
||||
\\"entityType\\"
|
||||
],
|
||||
\\"message\\": \\"Invalid enum value. Expected 'events' | 'sessions', received 'notAValidEntityType'\\"
|
||||
}
|
||||
]"
|
||||
`);
|
||||
});
|
||||
});
|
|
@ -12,6 +12,7 @@ import { timerange } from '../model/timerange';
|
|||
|
||||
export const timelineRequestBasicOptionsSchema = z.object({
|
||||
indexType: z.string().optional(),
|
||||
id: z.string().optional(),
|
||||
timerange: timerange.optional(),
|
||||
filterQuery,
|
||||
defaultIndex: z.array(z.string()).optional(),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue