diff --git a/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts b/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts index a67c03abe8b32..64a6be3aac8d8 100644 --- a/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts +++ b/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts @@ -35,7 +35,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, @@ -80,7 +80,7 @@ export interface BulkUpdateOptions { ids: string[] | undefined | null; status: STATUS_VALUES; index: string; - query: string | undefined | null; + query: object | string | undefined | null; } interface GetAlertParams { @@ -90,7 +90,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 | undefined; @@ -315,7 +315,11 @@ export class AlertsClient { }, }, { - doc: { [ALERT_STATUS]: status }, + doc: { + [item?._source?.[ALERT_WORKFLOW_STATUS] == null + ? 'signal.status' + : ALERT_WORKFLOW_STATUS]: status, + }, }, ]); @@ -330,7 +334,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 +349,28 @@ export class AlertsClient { }, operation ); - return buildEsQuery( + let esQuery; + if (id != null) { + esQuery = { query: `_id:${id}`, language: 'kuery' }; + } 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') { + // @ts-expect-error + builtQuery.bool.must.push(query); + } + return builtQuery; } catch (exc) { this.logger.error(exc); throw Boom.expectationFailed( @@ -373,7 +390,7 @@ export class AlertsClient { operation, }: { index: string; - query: string; + query: object | string; operation: WriteOperations.Update | ReadOperations.Find | ReadOperations.Get; }) { let lastSortIds; @@ -436,7 +453,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, @@ -483,7 +500,9 @@ export class AlertsClient { index, body: { doc: { - [ALERT_STATUS]: status, + [alert?.hits.hits[0]._source?.[ALERT_WORKFLOW_STATUS] == null + ? 'signal.status' + : ALERT_WORKFLOW_STATUS]: status, }, }, refresh: 'wait_for', @@ -535,8 +554,8 @@ 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}' diff --git a/x-pack/plugins/rule_registry/server/alert_data_client/tests/get.test.ts b/x-pack/plugins/rule_registry/server/alert_data_client/tests/get.test.ts index c8d0d18dfd37e..33958f584988d 100644 --- a/x-pack/plugins/rule_registry/server/alert_data_client/tests/get.test.ts +++ b/x-pack/plugins/rule_registry/server/alert_data_client/tests/get.test.ts @@ -254,7 +254,7 @@ describe('get()', () => { await expect(alertsClient.get({ id: fakeAlertId, index: '.alerts-observability-apm' })).rejects .toThrowErrorMatchingInlineSnapshot(` - "Unable to retrieve alert details for alert with id of \\"myfakeid1\\" or with query \\"null\\" and operation get + "Unable to retrieve alert details for alert with id of \\"myfakeid1\\" or with query \\"undefined\\" and operation get Error: Error: Unauthorized for fake.rule and apm" `); @@ -281,7 +281,7 @@ describe('get()', () => { await expect( alertsClient.get({ id: 'NoxgpHkBqbdrfX07MqXV', index: '.alerts-observability-apm' }) ).rejects.toThrowErrorMatchingInlineSnapshot(` - "Unable to retrieve alert details for alert with id of \\"NoxgpHkBqbdrfX07MqXV\\" or with query \\"null\\" and operation get + "Unable to retrieve alert details for alert with id of \\"NoxgpHkBqbdrfX07MqXV\\" or with query \\"undefined\\" and operation get Error: Error: something went wrong" `); }); diff --git a/x-pack/plugins/rule_registry/server/alert_data_client/tests/update.test.ts b/x-pack/plugins/rule_registry/server/alert_data_client/tests/update.test.ts index 0aaab20052716..7c2e985c269a8 100644 --- a/x-pack/plugins/rule_registry/server/alert_data_client/tests/update.test.ts +++ b/x-pack/plugins/rule_registry/server/alert_data_client/tests/update.test.ts @@ -7,7 +7,7 @@ import { ALERT_RULE_CONSUMER, - ALERT_STATUS, + ALERT_WORKFLOW_STATUS, SPACE_IDS, ALERT_RULE_TYPE_ID, } from '@kbn/rule-data-utils'; @@ -89,8 +89,8 @@ describe('update()', () => { _source: { [ALERT_RULE_TYPE_ID]: 'apm.error_rate', message: 'hello world 1', + [ALERT_WORKFLOW_STATUS]: 'open', [ALERT_RULE_CONSUMER]: 'apm', - [ALERT_STATUS]: 'open', [SPACE_IDS]: [DEFAULT_SPACE], }, }, @@ -139,7 +139,7 @@ describe('update()', () => { Object { "body": Object { "doc": Object { - "${ALERT_STATUS}": "closed", + "kibana.alert.workflow_status": "closed", }, }, "id": "1", @@ -175,8 +175,8 @@ describe('update()', () => { _source: { [ALERT_RULE_TYPE_ID]: 'apm.error_rate', message: 'hello world 1', + [ALERT_WORKFLOW_STATUS]: 'open', [ALERT_RULE_CONSUMER]: 'apm', - [ALERT_STATUS]: 'open', [SPACE_IDS]: [DEFAULT_SPACE], }, }, @@ -249,7 +249,7 @@ describe('update()', () => { _source: { [ALERT_RULE_TYPE_ID]: fakeRuleTypeId, [ALERT_RULE_CONSUMER]: 'apm', - [ALERT_STATUS]: 'open', + [ALERT_WORKFLOW_STATUS]: 'open', [SPACE_IDS]: [DEFAULT_SPACE], }, }, @@ -330,8 +330,8 @@ describe('update()', () => { _source: { [ALERT_RULE_TYPE_ID]: 'apm.error_rate', message: 'hello world 1', + [ALERT_WORKFLOW_STATUS]: 'open', [ALERT_RULE_CONSUMER]: 'apm', - [ALERT_STATUS]: 'open', [SPACE_IDS]: [DEFAULT_SPACE], }, }, @@ -391,7 +391,7 @@ describe('update()', () => { [ALERT_RULE_TYPE_ID]: 'apm.error_rate', message: 'hello world 1', [ALERT_RULE_CONSUMER]: 'apm', - [ALERT_STATUS]: 'open', + [ALERT_WORKFLOW_STATUS]: 'open', [SPACE_IDS]: [DEFAULT_SPACE], }, }, diff --git a/x-pack/plugins/rule_registry/server/routes/bulk_update_alerts.ts b/x-pack/plugins/rule_registry/server/routes/bulk_update_alerts.ts index 40f27f7eb7e23..9a29774219270 100644 --- a/x-pack/plugins/rule_registry/server/routes/bulk_update_alerts.ts +++ b/x-pack/plugins/rule_registry/server/routes/bulk_update_alerts.ts @@ -31,7 +31,7 @@ export const bulkUpdateAlertsRoute = (router: IRouter) status: t.union([t.literal('open'), t.literal('closed')]), index: t.string, ids: t.undefined, - query: t.string, + query: t.union([t.object, t.string]), }), ]) ), diff --git a/x-pack/test/functional/es_archives/rule_registry/alerts/data.json b/x-pack/test/functional/es_archives/rule_registry/alerts/data.json index 81ff007903368..baea934440e13 100644 --- a/x-pack/test/functional/es_archives/rule_registry/alerts/data.json +++ b/x-pack/test/functional/es_archives/rule_registry/alerts/data.json @@ -1,116 +1,117 @@ { - "type": "doc", - "value": { - "index": ".alerts-observability-apm", - "id": "NoxgpHkBqbdrfX07MqXV", - "source": { - "event.kind" : "signal", - "@timestamp": "2020-12-16T15:16:18.570Z", - "kibana.alert.rule.rule_type_id": "apm.error_rate", - "message": "hello world 1", - "kibana.alert.rule.consumer": "apm", - "kibana.alert.status": "open", - "kibana.space_ids": ["space1", "space2"] + "type": "doc", + "value": { + "index": ".alerts-observability-apm", + "id": "NoxgpHkBqbdrfX07MqXV", + "source": { + "event.kind" : "signal", + "@timestamp": "2020-12-16T15:16:18.570Z", + "kibana.alert.rule.rule_type_id": "apm.error_rate", + "message": "hello world 1", + "kibana.alert.workflow_status": "open", + "kibana.alert.rule.consumer": "apm", + "kibana.space_ids": ["space1", "space2"] + } } } -} - -{ - "type": "doc", - "value": { - "index": ".alerts-observability-apm", - "id": "space1alert", - "source": { - "event.kind" : "signal", - "@timestamp": "2020-12-16T15:16:18.570Z", - "kibana.alert.rule.rule_type_id": "apm.error_rate", - "message": "hello world 1", - "kibana.alert.rule.consumer": "apm", - "kibana.alert.status": "open", - "kibana.space_ids": ["space1"] + + { + "type": "doc", + "value": { + "index": ".alerts-observability-apm", + "id": "space1alert", + "source": { + "event.kind" : "signal", + "@timestamp": "2020-12-16T15:16:18.570Z", + "kibana.alert.rule.rule_type_id": "apm.error_rate", + "message": "hello world 1", + "kibana.alert.workflow_status": "open", + "kibana.alert.rule.consumer": "apm", + "kibana.space_ids": ["space1"] + } } } -} - -{ - "type": "doc", - "value": { - "index": ".alerts-observability-apm", - "id": "space2alert", - "source": { - "event.kind" : "signal", - "@timestamp": "2020-12-16T15:16:18.570Z", - "kibana.alert.rule.rule_type_id": "apm.error_rate", - "message": "hello world 1", - "kibana.alert.rule.consumer": "apm", - "kibana.alert.status": "open", - "kibana.space_ids": ["space2"] + + { + "type": "doc", + "value": { + "index": ".alerts-observability-apm", + "id": "space2alert", + "source": { + "event.kind" : "signal", + "@timestamp": "2020-12-16T15:16:18.570Z", + "kibana.alert.rule.rule_type_id": "apm.error_rate", + "message": "hello world 1", + "kibana.alert.workflow_status": "open", + "kibana.alert.rule.consumer": "apm", + "kibana.space_ids": ["space2"] + } } } -} - -{ - "type": "doc", - "value": { - "index": ".alerts-security.alerts", - "id": "020202", - "source": { - "event.kind" : "signal", - "@timestamp": "2020-12-16T15:16:18.570Z", - "kibana.alert.rule.rule_type_id": "siem.signals", - "message": "hello world security", - "kibana.alert.rule.consumer": "siem", - "kibana.alert.status": "open", - "kibana.space_ids": ["space1", "space2"] + + { + "type": "doc", + "value": { + "index": ".alerts-security.alerts", + "id": "020202", + "source": { + "event.kind" : "signal", + "@timestamp": "2020-12-16T15:16:18.570Z", + "kibana.alert.rule.rule_type_id": "siem.signals", + "message": "hello world security", + "kibana.alert.workflow_status": "open", + "kibana.alert.rule.consumer": "siem", + "kibana.space_ids": ["space1", "space2"] + } } } -} - -{ - "type": "doc", - "value": { - "index": ".alerts-security.alerts", - "id": "020204", - "source": { - "event.kind" : "signal", - "@timestamp": "2020-12-16T15:16:18.570Z", - "kibana.alert.rule.rule_type_id": "siem.customRule", - "message": "hello world security", - "kibana.alert.rule.consumer": "siem", - "kibana.alert.status": "open", - "kibana.space_ids": ["space1", "space2"] + + { + "type": "doc", + "value": { + "index": ".alerts-security.alerts", + "id": "020204", + "source": { + "event.kind" : "signal", + "@timestamp": "2020-12-16T15:16:18.570Z", + "kibana.alert.rule.rule_type_id": "siem.customRule", + "message": "hello world security", + "kibana.alert.workflow_status": "open", + "kibana.alert.rule.consumer": "siem", + "kibana.space_ids": ["space1", "space2"] + } } } -} - -{ - "type": "doc", - "value": { - "index": ".alerts-security.alerts", - "id": "space1securityalert", - "source": { - "@timestamp": "2020-12-16T15:16:18.570Z", - "kibana.alert.rule.rule_type_id": "siem.signals", - "message": "hello world security", - "kibana.alert.rule.consumer": "siem", - "kibana.alert.status": "open", - "kibana.space_ids": ["space1"] + + { + "type": "doc", + "value": { + "index": ".alerts-security.alerts", + "id": "space1securityalert", + "source": { + "@timestamp": "2020-12-16T15:16:18.570Z", + "kibana.alert.rule.rule_type_id": "siem.signals", + "message": "hello world security", + "kibana.alert.workflow_status": "open", + "kibana.alert.rule.consumer": "siem", + "kibana.space_ids": ["space1"] + } } } -} - -{ - "type": "doc", - "value": { - "index": ".alerts-security.alerts", - "id": "space2securityalert", - "source": { - "@timestamp": "2020-12-16T15:16:18.570Z", - "kibana.alert.rule.rule_type_id": "siem.signals", - "message": "hello world security", - "kibana.alert.rule.consumer": "siem", - "kibana.alert.status": "open", - "kibana.space_ids": ["space2"] + + { + "type": "doc", + "value": { + "index": ".alerts-security.alerts", + "id": "space2securityalert", + "source": { + "@timestamp": "2020-12-16T15:16:18.570Z", + "kibana.alert.rule.rule_type_id": "siem.signals", + "message": "hello world security", + "kibana.alert.workflow_status": "open", + "kibana.alert.rule.consumer": "siem", + "kibana.space_ids": ["space2"] + } } } -} + \ No newline at end of file diff --git a/x-pack/test/rule_registry/security_and_spaces/tests/basic/bulk_update_alerts.ts b/x-pack/test/rule_registry/security_and_spaces/tests/basic/bulk_update_alerts.ts index aa8eb6bc8fcaa..a033e634dc6b3 100644 --- a/x-pack/test/rule_registry/security_and_spaces/tests/basic/bulk_update_alerts.ts +++ b/x-pack/test/rule_registry/security_and_spaces/tests/basic/bulk_update_alerts.ts @@ -6,6 +6,7 @@ */ import expect from '@kbn/expect'; +import { ALERT_WORKFLOW_STATUS } from '@kbn/rule-data-utils'; import { superUser, globalRead, @@ -26,6 +27,7 @@ import { secOnlySpacesAll, noKibanaPrivileges, } from '../../../common/lib/authentication/users'; + import type { User } from '../../../common/lib/authentication/types'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; import { getSpaceUrlPrefix } from '../../../common/lib/authentication/spaces'; @@ -119,14 +121,30 @@ export default ({ getService }: FtrProviderContext) => { items.map((item) => expect(item.update.result).to.eql('updated')); }); - it(`${username} should bulk update alerts which match query in ${space}/${index}`, async () => { + it(`${username} should bulk update alerts which match KQL query string in ${space}/${index}`, async () => { + await esArchiver.load('x-pack/test/functional/es_archives/rule_registry/alerts'); // since this is a success case, reload the test data immediately beforehand + const { body: updated } = await supertestWithoutAuth + .post(`${getSpaceUrlPrefix(space)}${TEST_URL}/bulk_update`) + .auth(username, password) + .set('kbn-xsrf', 'true') + .send({ + status: 'closed', + query: `${ALERT_WORKFLOW_STATUS}: open`, + index, + }); + expect(updated.statusCode).to.eql(200); + expect(updated.body.updated).to.greaterThan(0); + }); + + it(`${username} should bulk update alerts which match query in DSL in ${space}/${index}`, async () => { + await esArchiver.load('x-pack/test/functional/es_archives/rule_registry/alerts'); // since this is a success case, reload the test data immediately beforehand const { body: updated } = await supertestWithoutAuth .post(`${getSpaceUrlPrefix(space)}${TEST_URL}/bulk_update`) .auth(username, password) .set('kbn-xsrf', 'true') .send({ status: 'closed', - query: 'kibana.alert.status: open', + query: { match: { [ALERT_WORKFLOW_STATUS]: 'open' } }, index, }); expect(updated.statusCode).to.eql(200);