-
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
[TGrid] Alerts status update use RBAC api #108092
Changes from 23 commits
49ebd28
74e64b1
73b97e3
d6094df
1a7cfd4
ec8ada3
f82720b
18e1f8b
f91de3f
cae0960
918c4bd
7cbec10
dab6006
120029e
7ef80cf
df080e2
1710bf3
22a85ea
8138fb6
23e77f3
ca20b19
63e033c
d5c29fa
5a09258
36680b4
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 |
---|---|---|
|
@@ -5,6 +5,7 @@ | |
* 2.0. | ||
*/ | ||
import Boom from '@hapi/boom'; | ||
import { estypes } from '@elastic/elasticsearch'; | ||
import { PublicMethodsOf } from '@kbn/utility-types'; | ||
import { Filter, buildEsQuery, EsQueryConfig } from '@kbn/es-query'; | ||
import { decodeVersion, encodeHitVersion } from '@kbn/securitysolution-es-utils'; | ||
|
@@ -35,7 +36,7 @@ import { Logger, ElasticsearchClient, EcsEventOutcome } from '../../../../../src | |
import { alertAuditEvent, operationAlertAuditActionMap } from './audit_events'; | ||
import { AuditLogger } from '../../../security/server'; | ||
import { | ||
ALERT_STATUS, | ||
ALERT_WORKFLOW_STATUS, | ||
ALERT_RULE_CONSUMER, | ||
ALERT_RULE_TYPE_ID, | ||
SPACE_IDS, | ||
|
@@ -50,16 +51,19 @@ const mapConsumerToIndexName: typeof mapConsumerToIndexNameTyped = mapConsumerTo | |
// TODO: Fix typings https://github.com/elastic/kibana/issues/101776 | ||
type NonNullableProps<Obj extends {}, Props extends keyof Obj> = Omit<Obj, Props> & | ||
{ [K in Props]-?: NonNullable<Obj[K]> }; | ||
type AlertType = NonNullableProps< | ||
type AlertType = { _index: string; _id: string } & NonNullableProps< | ||
ParsedTechnicalFields, | ||
typeof ALERT_RULE_TYPE_ID | typeof ALERT_RULE_CONSUMER | typeof SPACE_IDS | ||
>; | ||
|
||
const isValidAlert = (source?: ParsedTechnicalFields): source is AlertType => { | ||
const isValidAlert = (source?: estypes.SearchHit<any>): source is AlertType => { | ||
return ( | ||
source?.[ALERT_RULE_TYPE_ID] != null && | ||
source?.[ALERT_RULE_CONSUMER] != null && | ||
source?.[SPACE_IDS] != null | ||
(source?._source?.[ALERT_RULE_TYPE_ID] != null && | ||
source?._source?.[ALERT_RULE_CONSUMER] != null && | ||
source?._source?.[SPACE_IDS] != null) || | ||
(source?.fields?.[ALERT_RULE_TYPE_ID][0] != null && | ||
source?.fields?.[ALERT_RULE_CONSUMER][0] != null && | ||
source?.fields?.[SPACE_IDS][0] != null) | ||
); | ||
}; | ||
export interface ConstructorOptions { | ||
|
@@ -80,7 +84,7 @@ export interface BulkUpdateOptions<Params extends AlertTypeParams> { | |
ids: string[] | undefined | null; | ||
status: STATUS_VALUES; | ||
index: string; | ||
query: string | undefined | null; | ||
query: object | string | undefined | null; | ||
} | ||
|
||
interface GetAlertParams { | ||
|
@@ -90,7 +94,7 @@ interface GetAlertParams { | |
|
||
interface SingleSearchAfterAndAudit { | ||
id: string | null | undefined; | ||
query: string | null | undefined; | ||
query: object | string | null | undefined; | ||
index?: string; | ||
operation: WriteOperations.Update | ReadOperations.Find | ReadOperations.Get; | ||
lastSortIds: Array<string | number> | undefined; | ||
|
@@ -126,6 +130,15 @@ export class AlertsClient { | |
}; | ||
} | ||
|
||
private getAlertStatusFieldUpdate( | ||
source: ParsedTechnicalFields | undefined, | ||
status: STATUS_VALUES | ||
) { | ||
return source?.[ALERT_WORKFLOW_STATUS] == null | ||
? { signal: { status } } | ||
: { [ALERT_WORKFLOW_STATUS]: status }; | ||
} | ||
|
||
/** | ||
* Accepts an array of ES documents and executes ensureAuthorized for the given operation | ||
* @param items | ||
|
@@ -218,6 +231,7 @@ export class AlertsClient { | |
const config = getEsQueryConfig(); | ||
|
||
let queryBody = { | ||
fields: [ALERT_RULE_TYPE_ID, ALERT_RULE_CONSUMER, ALERT_WORKFLOW_STATUS, SPACE_IDS], | ||
query: await this.buildEsQueryWithAuthz(query, id, alertSpaceId, operation, config), | ||
sort: [ | ||
{ | ||
|
@@ -245,7 +259,7 @@ export class AlertsClient { | |
seq_no_primary_term: true, | ||
}); | ||
|
||
if (!result?.body.hits.hits.every((hit) => isValidAlert(hit._source))) { | ||
if (!result?.body.hits.hits.every((hit) => isValidAlert(hit))) { | ||
const errorMessage = `Invalid alert found with id of "${id}" or with query "${query}" and operation ${operation}`; | ||
this.logger.error(errorMessage); | ||
throw Boom.badData(errorMessage); | ||
|
@@ -307,19 +321,25 @@ export class AlertsClient { | |
); | ||
} | ||
|
||
const bulkUpdateRequest = mgetRes.body.docs.flatMap((item) => [ | ||
{ | ||
update: { | ||
_index: item._index, | ||
_id: item._id, | ||
const bulkUpdateRequest = mgetRes.body.docs.flatMap((item) => { | ||
const fieldToUpdate = this.getAlertStatusFieldUpdate(item?._source, status); | ||
return [ | ||
{ | ||
update: { | ||
_index: item._index, | ||
_id: item._id, | ||
}, | ||
}, | ||
}, | ||
{ | ||
doc: { [ALERT_STATUS]: status }, | ||
}, | ||
]); | ||
{ | ||
doc: { | ||
...fieldToUpdate, | ||
}, | ||
}, | ||
]; | ||
}); | ||
|
||
const bulkUpdateResponse = await this.esClient.bulk({ | ||
refresh: 'wait_for', | ||
body: bulkUpdateRequest, | ||
}); | ||
return bulkUpdateResponse; | ||
|
@@ -330,7 +350,7 @@ export class AlertsClient { | |
} | ||
|
||
private async buildEsQueryWithAuthz( | ||
query: string | null | undefined, | ||
query: object | string | null | undefined, | ||
id: string | null | undefined, | ||
alertSpaceId: string, | ||
operation: WriteOperations.Update | ReadOperations.Get | ReadOperations.Find, | ||
|
@@ -345,15 +365,33 @@ export class AlertsClient { | |
}, | ||
operation | ||
); | ||
return buildEsQuery( | ||
let esQuery; | ||
if (id != null) { | ||
esQuery = { query: `_id:${id}`, language: 'kuery' }; | ||
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. It could be helpful to have a comment here describing how these different scenarios happen |
||
} else if (typeof query === 'string') { | ||
esQuery = { query, language: 'kuery' }; | ||
} else if (query != null && typeof query === 'object') { | ||
esQuery = []; | ||
} | ||
const builtQuery = buildEsQuery( | ||
undefined, | ||
{ query: query == null ? `_id:${id}` : query, language: 'kuery' }, | ||
esQuery == null ? { query: ``, language: 'kuery' } : esQuery, | ||
[ | ||
(authzFilter as unknown) as Filter, | ||
({ term: { [SPACE_IDS]: alertSpaceId } } as unknown) as Filter, | ||
], | ||
config | ||
); | ||
if (query != null && typeof query === 'object') { | ||
return { | ||
...builtQuery, | ||
bool: { | ||
...builtQuery.bool, | ||
must: [...builtQuery.bool.must, query], | ||
}, | ||
}; | ||
} | ||
return builtQuery; | ||
} catch (exc) { | ||
this.logger.error(exc); | ||
throw Boom.expectationFailed( | ||
|
@@ -373,7 +411,7 @@ export class AlertsClient { | |
operation, | ||
}: { | ||
index: string; | ||
query: string; | ||
query: object | string; | ||
operation: WriteOperations.Update | ReadOperations.Find | ReadOperations.Get; | ||
}) { | ||
let lastSortIds; | ||
|
@@ -436,7 +474,7 @@ export class AlertsClient { | |
// first search for the alert by id, then use the alert info to check if user has access to it | ||
const alert = await this.singleSearchAfterAndAudit({ | ||
id, | ||
query: null, | ||
query: undefined, | ||
index, | ||
operation: ReadOperations.Get, | ||
lastSortIds: undefined, | ||
|
@@ -476,14 +514,17 @@ export class AlertsClient { | |
this.logger.error(errorMessage); | ||
throw Boom.notFound(errorMessage); | ||
} | ||
|
||
const fieldToUpdate = this.getAlertStatusFieldUpdate( | ||
alert?.hits.hits[0]._source, | ||
status as STATUS_VALUES | ||
); | ||
const { body: response } = await this.esClient.update<ParsedTechnicalFields>({ | ||
...decodeVersion(_version), | ||
id, | ||
index, | ||
body: { | ||
doc: { | ||
[ALERT_STATUS]: status, | ||
...fieldToUpdate, | ||
}, | ||
}, | ||
refresh: 'wait_for', | ||
|
@@ -535,11 +576,11 @@ export class AlertsClient { | |
refresh: true, | ||
body: { | ||
script: { | ||
source: `if (ctx._source['${ALERT_STATUS}'] != null) { | ||
ctx._source['${ALERT_STATUS}'] = '${status}' | ||
source: `if (ctx._source['${ALERT_WORKFLOW_STATUS}'] != null) { | ||
ctx._source['${ALERT_WORKFLOW_STATUS}'] = '${status}' | ||
} | ||
if (ctx._source['signal.status'] != null) { | ||
ctx._source['signal.status'] = '${status}' | ||
if (ctx._source.signal != null && ctx._source.signal.status != null) { | ||
ctx._source.signal.status = '${status}' | ||
}`, | ||
lang: 'painless', | ||
} as InlineScript, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,16 +22,26 @@ export const bulkUpdateAlertsRoute = (router: IRouter<RacRequestHandlerContext>) | |
body: buildRouteValidation( | ||
t.union([ | ||
t.strict({ | ||
status: t.union([t.literal('open'), t.literal('closed')]), | ||
status: t.union([ | ||
t.literal('open'), | ||
t.literal('closed'), | ||
t.literal('in-progress'), // TODO: remove after migration to acknowledged | ||
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. |
||
t.literal('acknowledged'), | ||
]), | ||
index: t.string, | ||
ids: t.array(t.string), | ||
query: t.undefined, | ||
}), | ||
t.strict({ | ||
status: t.union([t.literal('open'), t.literal('closed')]), | ||
status: t.union([ | ||
t.literal('open'), | ||
t.literal('closed'), | ||
t.literal('in-progress'), // TODO: remove after migration to acknowledged | ||
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. |
||
t.literal('acknowledged'), | ||
]), | ||
index: t.string, | ||
ids: t.undefined, | ||
query: t.string, | ||
query: t.union([t.object, t.string]), | ||
}), | ||
]) | ||
), | ||
|
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.
@dplumlee - just pinging you here since you're working on these changes