-
Notifications
You must be signed in to change notification settings - Fork 8.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Security_Solution][Endpoint] Leveraging msearch and ancestry array for resolver #70134
Changes from all commits
1f0e448
f0ee76a
c95132f
e4b8a13
407db62
8947b43
1287379
54f1a3f
92005dd
6cdf961
40f5537
f54a462
1eac63f
79464ed
5a872da
99acb58
3f9f3eb
e75f7bf
caa8f8f
544e1f3
c99773b
d274a07
200ce25
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,10 +14,12 @@ import { MSearchQuery } from './multi_searcher'; | |
/** | ||
* ResolverQuery provides the base structure for queries to retrieve events when building a resolver graph. | ||
* | ||
* @param T the structured return type of a resolver query. This represents the type that is returned when translating | ||
* Elasticsearch's SearchResponse<ResolverEvent> response. | ||
* @param T the structured return type of a resolver query. This represents the final return type of the query after handling | ||
* any aggregations. | ||
* @param R the is the type after transforming ES's response. Making this definable let's us set whether it is a resolver event | ||
* or something else. | ||
*/ | ||
export abstract class ResolverQuery<T> implements MSearchQuery { | ||
export abstract class ResolverQuery<T, R = ResolverEvent> implements MSearchQuery { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||
/** | ||
* | ||
* @param indexPattern the index pattern to use in the query for finding indices with documents in ES. | ||
|
@@ -50,7 +52,7 @@ export abstract class ResolverQuery<T> implements MSearchQuery { | |
}; | ||
} | ||
|
||
protected static getResults(response: SearchResponse<ResolverEvent>): ResolverEvent[] { | ||
protected getResults(response: SearchResponse<R>): R[] { | ||
return response.hits.hits.map((hit) => hit._source); | ||
} | ||
|
||
|
@@ -68,19 +70,26 @@ export abstract class ResolverQuery<T> implements MSearchQuery { | |
} | ||
|
||
/** | ||
* Searches ES for the specified ids. | ||
* Searches ES for the specified ids and format the response. | ||
* | ||
* @param client a client for searching ES | ||
* @param ids a single more multiple unique node ids (e.g. entity_id or unique_pid) | ||
*/ | ||
async search(client: ILegacyScopedClusterClient, ids: string | string[]): Promise<T> { | ||
const res: SearchResponse<ResolverEvent> = await client.callAsCurrentUser( | ||
'search', | ||
this.buildSearch(ids) | ||
); | ||
async searchAndFormat(client: ILegacyScopedClusterClient, ids: string | string[]): Promise<T> { | ||
const res: SearchResponse<ResolverEvent> = await this.search(client, ids); | ||
return this.formatResponse(res); | ||
} | ||
|
||
/** | ||
* Searches ES for the specified ids but do not format the response. | ||
* | ||
* @param client a client for searching ES | ||
* @param ids a single more multiple unique node ids (e.g. entity_id or unique_pid) | ||
*/ | ||
async search(client: ILegacyScopedClusterClient, ids: string | string[]) { | ||
return client.callAsCurrentUser('search', this.buildSearch(ids)); | ||
} | ||
|
||
/** | ||
* Builds a query to search the legacy data format. | ||
* | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,13 +6,13 @@ | |
import { SearchResponse } from 'elasticsearch'; | ||
import { ResolverEvent } from '../../../../../common/endpoint/types'; | ||
import { ResolverQuery } from './base'; | ||
import { PaginationBuilder, PaginatedResults } from '../utils/pagination'; | ||
import { PaginationBuilder } from '../utils/pagination'; | ||
import { JsonObject } from '../../../../../../../../src/plugins/kibana_utils/common'; | ||
|
||
/** | ||
* Builds a query for retrieving descendants of a node. | ||
*/ | ||
export class ChildrenQuery extends ResolverQuery<PaginatedResults> { | ||
export class ChildrenQuery extends ResolverQuery<ResolverEvent[]> { | ||
constructor( | ||
private readonly pagination: PaginationBuilder, | ||
indexPattern: string | string[], | ||
|
@@ -53,11 +53,7 @@ export class ChildrenQuery extends ResolverQuery<PaginatedResults> { | |
], | ||
}, | ||
}, | ||
...this.pagination.buildQueryFields( | ||
uniquePIDs.length, | ||
'endgame.serial_event_id', | ||
'endgame.unique_ppid' | ||
), | ||
...this.pagination.buildQueryFields('endgame.serial_event_id'), | ||
}; | ||
} | ||
|
||
|
@@ -67,7 +63,16 @@ export class ChildrenQuery extends ResolverQuery<PaginatedResults> { | |
bool: { | ||
filter: [ | ||
{ | ||
terms: { 'process.parent.entity_id': entityIDs }, | ||
bool: { | ||
should: [ | ||
{ | ||
terms: { 'process.parent.entity_id': entityIDs }, | ||
}, | ||
{ | ||
terms: { 'process.Ext.ancestry': entityIDs }, | ||
}, | ||
], | ||
}, | ||
}, | ||
{ | ||
term: { 'event.category': 'process' }, | ||
|
@@ -81,14 +86,11 @@ export class ChildrenQuery extends ResolverQuery<PaginatedResults> { | |
], | ||
}, | ||
}, | ||
...this.pagination.buildQueryFields(entityIDs.length, 'event.id', 'process.parent.entity_id'), | ||
...this.pagination.buildQueryFields('event.id'), | ||
}; | ||
} | ||
|
||
formatResponse(response: SearchResponse<ResolverEvent>): PaginatedResults { | ||
return { | ||
results: ResolverQuery.getResults(response), | ||
totals: PaginationBuilder.getTotals(response.aggregations), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Totals are inferred based on the response of ES while using the ancestry array. |
||
}; | ||
formatResponse(response: SearchResponse<ResolverEvent>): ResolverEvent[] { | ||
return this.getResults(response); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,7 +5,7 @@ | |
*/ | ||
|
||
import { ILegacyScopedClusterClient } from 'kibana/server'; | ||
import { MSearchResponse } from 'elasticsearch'; | ||
import { MSearchResponse, SearchResponse } from 'elasticsearch'; | ||
import { ResolverEvent } from '../../../../../common/endpoint/types'; | ||
import { JsonObject } from '../../../../../../../../src/plugins/kibana_utils/common'; | ||
|
||
|
@@ -34,6 +34,10 @@ export interface QueryInfo { | |
* one or many unique identifiers to be searched for in this query | ||
*/ | ||
ids: string | string[]; | ||
/** | ||
* a function to handle the response | ||
*/ | ||
handler: (response: SearchResponse<ResolverEvent>) => void; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This calls into the class (e.g. RelatedEventsQueryHandler etc) for it to handle the response from ES. |
||
} | ||
|
||
/** | ||
|
@@ -57,10 +61,10 @@ export class MultiSearcher { | |
throw new Error('No queries provided to MultiSearcher'); | ||
} | ||
|
||
let searchQuery: JsonObject[] = []; | ||
queries.forEach( | ||
(info) => (searchQuery = [...searchQuery, ...info.query.buildMSearch(info.ids)]) | ||
); | ||
const searchQuery: JsonObject[] = []; | ||
for (const info of queries) { | ||
searchQuery.push(...info.query.buildMSearch(info.ids)); | ||
} | ||
const res: MSearchResponse<ResolverEvent> = await this.client.callAsCurrentUser('msearch', { | ||
body: searchQuery, | ||
}); | ||
|
@@ -72,6 +76,8 @@ export class MultiSearcher { | |
if (res.responses.length !== queries.length) { | ||
throw new Error(`Responses length was: ${res.responses.length} expected ${queries.length}`); | ||
} | ||
return res.responses; | ||
for (let i = 0; i < queries.length; i++) { | ||
queries[i].handler(res.responses[i]); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a thought, I understand what this is saying, especially after reading through your write up, but one of @oatkiller's famous diagrams may be useful here, you could probs just copy paste one he's already done. Not necessary, but I think it could be helpful
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @michaelolo24 do you mean an ascii diagram of a tree? Or a table?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ascii I think
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think what I'll probably do is put a link to the relevant section in the docs that I'll be adding as a follow up or add on to this PR.