diff --git a/packages/kbn-rule-data-utils/src/technical_field_names.ts b/packages/kbn-rule-data-utils/src/technical_field_names.ts index 6c45403fc0a1368..d83321cb43d0b5d 100644 --- a/packages/kbn-rule-data-utils/src/technical_field_names.ts +++ b/packages/kbn-rule-data-utils/src/technical_field_names.ts @@ -30,6 +30,7 @@ const ALERT_SEVERITY_VALUE = `${ALERT_NAMESPACE}.severity.value` as const; const ALERT_STATUS = `${ALERT_NAMESPACE}.status` as const; const ALERT_EVALUATION_THRESHOLD = `${ALERT_NAMESPACE}.evaluation.threshold` as const; const ALERT_EVALUATION_VALUE = `${ALERT_NAMESPACE}.evaluation.value` as const; +const VERSION = '_version' as const; const fields = { TIMESTAMP, @@ -52,6 +53,7 @@ const fields = { ALERT_STATUS, ALERT_EVALUATION_THRESHOLD, ALERT_EVALUATION_VALUE, + VERSION, }; export { @@ -75,6 +77,7 @@ export { ALERT_STATUS, ALERT_EVALUATION_THRESHOLD, ALERT_EVALUATION_VALUE, + VERSION, }; export type TechnicalRuleDataFieldName = ValuesType; diff --git a/x-pack/plugins/lists/server/services/utils/decode_version.ts b/packages/kbn-securitysolution-es-utils/src/decode_version/index.ts similarity index 85% rename from x-pack/plugins/lists/server/services/utils/decode_version.ts rename to packages/kbn-securitysolution-es-utils/src/decode_version/index.ts index 8ed934204ed982a..d58c7add67a27f2 100644 --- a/x-pack/plugins/lists/server/services/utils/decode_version.ts +++ b/packages/kbn-securitysolution-es-utils/src/decode_version/index.ts @@ -1,8 +1,9 @@ /* * 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. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ // Similar to the src/core/server/saved_objects/version/decode_version.ts diff --git a/x-pack/plugins/lists/server/services/utils/encode_hit_version.ts b/packages/kbn-securitysolution-es-utils/src/encode_hit_version/index.ts similarity index 84% rename from x-pack/plugins/lists/server/services/utils/encode_hit_version.ts rename to packages/kbn-securitysolution-es-utils/src/encode_hit_version/index.ts index 4c55d858d283b4b..29b5a18f7c303b1 100644 --- a/x-pack/plugins/lists/server/services/utils/encode_hit_version.ts +++ b/packages/kbn-securitysolution-es-utils/src/encode_hit_version/index.ts @@ -1,8 +1,9 @@ /* * 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. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ /** diff --git a/packages/kbn-securitysolution-es-utils/src/index.ts b/packages/kbn-securitysolution-es-utils/src/index.ts index cfa6820e9aac526..8dead7f899ba2d6 100644 --- a/packages/kbn-securitysolution-es-utils/src/index.ts +++ b/packages/kbn-securitysolution-es-utils/src/index.ts @@ -8,10 +8,12 @@ export * from './bad_request_error'; export * from './create_boostrap_index'; +export * from './decode_version'; export * from './delete_all_index'; export * from './delete_policy'; export * from './delete_template'; export * from './elasticsearch_client'; +export * from './encode_hit_version'; export * from './get_index_aliases'; export * from './get_index_count'; export * from './get_index_exists'; diff --git a/src/core/server/ui_settings/routes/delete.ts b/src/core/server/ui_settings/routes/delete.ts index f8ab4d5d0c2c4c6..5da69e043a5e1e4 100644 --- a/src/core/server/ui_settings/routes/delete.ts +++ b/src/core/server/ui_settings/routes/delete.ts @@ -34,10 +34,10 @@ export function registerDeleteRoute(router: IRouter) { }); } catch (error) { if (SavedObjectsErrorHelpers.isSavedObjectsClientError(error)) { - return response.customError({ + return { body: error, statusCode: error.output.statusCode, - }); + }; } if (error instanceof CannotOverrideError) { diff --git a/x-pack/plugins/lists/server/services/items/create_list_item.ts b/x-pack/plugins/lists/server/services/items/create_list_item.ts index b4203f000b7b975..ccdb8ab4779b69c 100644 --- a/x-pack/plugins/lists/server/services/items/create_list_item.ts +++ b/x-pack/plugins/lists/server/services/items/create_list_item.ts @@ -15,9 +15,9 @@ import { SerializerOrUndefined, Type, } from '@kbn/securitysolution-io-ts-list-types'; +import { encodeHitVersion } from '@kbn/securitysolution-es-utils'; import { transformListItemToElasticQuery } from '../utils'; -import { encodeHitVersion } from '../utils/encode_hit_version'; import { IndexEsListItemSchema } from '../../schemas/elastic_query'; export interface CreateListItemOptions { diff --git a/x-pack/plugins/lists/server/services/items/update_list_item.ts b/x-pack/plugins/lists/server/services/items/update_list_item.ts index c73149019f41681..78651bb83d73b0a 100644 --- a/x-pack/plugins/lists/server/services/items/update_list_item.ts +++ b/x-pack/plugins/lists/server/services/items/update_list_item.ts @@ -12,10 +12,9 @@ import type { MetaOrUndefined, _VersionOrUndefined, } from '@kbn/securitysolution-io-ts-list-types'; +import { decodeVersion, encodeHitVersion } from '@kbn/securitysolution-es-utils'; import { transformListItemToElasticQuery } from '../utils'; -import { decodeVersion } from '../utils/decode_version'; -import { encodeHitVersion } from '../utils/encode_hit_version'; import { UpdateEsListItemSchema } from '../../schemas/elastic_query'; import { getListItem } from './get_list_item'; diff --git a/x-pack/plugins/lists/server/services/lists/create_list.ts b/x-pack/plugins/lists/server/services/lists/create_list.ts index 6c7081d7c701ec2..521a38a51d6eb00 100644 --- a/x-pack/plugins/lists/server/services/lists/create_list.ts +++ b/x-pack/plugins/lists/server/services/lists/create_list.ts @@ -19,8 +19,8 @@ import type { Type, } from '@kbn/securitysolution-io-ts-list-types'; import type { Version } from '@kbn/securitysolution-io-ts-types'; +import { encodeHitVersion } from '@kbn/securitysolution-es-utils'; -import { encodeHitVersion } from '../utils/encode_hit_version'; import { IndexEsListSchema } from '../../schemas/elastic_query'; export interface CreateListOptions { diff --git a/x-pack/plugins/lists/server/services/lists/update_list.ts b/x-pack/plugins/lists/server/services/lists/update_list.ts index 22235341ca07552..11868a6187bbf88 100644 --- a/x-pack/plugins/lists/server/services/lists/update_list.ts +++ b/x-pack/plugins/lists/server/services/lists/update_list.ts @@ -15,9 +15,8 @@ import type { _VersionOrUndefined, } from '@kbn/securitysolution-io-ts-list-types'; import { VersionOrUndefined } from '@kbn/securitysolution-io-ts-types'; +import { decodeVersion, encodeHitVersion } from '@kbn/securitysolution-es-utils'; -import { decodeVersion } from '../utils/decode_version'; -import { encodeHitVersion } from '../utils/encode_hit_version'; import { UpdateEsListSchema } from '../../schemas/elastic_query'; import { getList } from '.'; diff --git a/x-pack/plugins/lists/server/services/utils/index.ts b/x-pack/plugins/lists/server/services/utils/index.ts index 0cd2720bd199b8e..64e7c50d0e7b0e8 100644 --- a/x-pack/plugins/lists/server/services/utils/index.ts +++ b/x-pack/plugins/lists/server/services/utils/index.ts @@ -6,9 +6,7 @@ */ export * from './calculate_scroll_math'; -export * from './decode_version'; export * from './encode_decode_cursor'; -export * from './encode_hit_version'; export * from './escape_query'; export * from './find_source_type'; export * from './find_source_value'; diff --git a/x-pack/plugins/lists/server/services/utils/transform_elastic_to_list.ts b/x-pack/plugins/lists/server/services/utils/transform_elastic_to_list.ts index 19177c1c2785ff6..5b0949d7b79b7ae 100644 --- a/x-pack/plugins/lists/server/services/utils/transform_elastic_to_list.ts +++ b/x-pack/plugins/lists/server/services/utils/transform_elastic_to_list.ts @@ -7,11 +7,10 @@ import type { estypes } from '@elastic/elasticsearch'; import type { ListArraySchema } from '@kbn/securitysolution-io-ts-list-types'; +import { encodeHitVersion } from '@kbn/securitysolution-es-utils'; import { SearchEsListSchema } from '../../schemas/elastic_response'; -import { encodeHitVersion } from './encode_hit_version'; - export interface TransformElasticToListOptions { response: estypes.SearchResponse; } diff --git a/x-pack/plugins/lists/server/services/utils/transform_elastic_to_list_item.ts b/x-pack/plugins/lists/server/services/utils/transform_elastic_to_list_item.ts index 79db56f9a7fe9e2..65392f8c379d957 100644 --- a/x-pack/plugins/lists/server/services/utils/transform_elastic_to_list_item.ts +++ b/x-pack/plugins/lists/server/services/utils/transform_elastic_to_list_item.ts @@ -7,11 +7,11 @@ import type { estypes } from '@elastic/elasticsearch'; import type { ListItemArraySchema, Type } from '@kbn/securitysolution-io-ts-list-types'; +import { encodeHitVersion } from '@kbn/securitysolution-es-utils'; import { ErrorWithStatusCode } from '../../error_with_status_code'; import { SearchEsListItemSchema } from '../../schemas/elastic_response'; -import { encodeHitVersion } from './encode_hit_version'; import { findSourceValue } from './find_source_value'; export interface TransformElasticToListItemOptions { diff --git a/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.ts b/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.ts index 6d70c581802c136..690b09c414fc99d 100644 --- a/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.ts +++ b/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.ts @@ -26,6 +26,7 @@ import { RULE_UUID, TAGS, TIMESTAMP, + VERSION, } from '../../../common/technical_rule_data_field_names'; import { ecsFieldMap } from './ecs_field_map'; @@ -41,6 +42,7 @@ export const technicalRuleFieldMap = { RULE_CATEGORY, TAGS ), + [VERSION]: { type: 'number' }, [OWNER]: { type: 'keyword' }, [PRODUCER]: { type: 'keyword' }, [ALERT_UUID]: { type: 'keyword' }, diff --git a/x-pack/plugins/rule_registry/common/constants.ts b/x-pack/plugins/rule_registry/common/constants.ts index d9aba65e4373e87..72793b1087e7b3e 100644 --- a/x-pack/plugins/rule_registry/common/constants.ts +++ b/x-pack/plugins/rule_registry/common/constants.ts @@ -5,4 +5,4 @@ * 2.0. */ -export const BASE_RAC_ALERTS_API_PATH = '/api/rac/alerts'; +export const BASE_RAC_ALERTS_API_PATH = '/internal/rac/alerts'; 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 9af5662ab2d397c..e36e97b7a252a09 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 @@ -5,6 +5,7 @@ * 2.0. */ import { PublicMethodsOf } from '@kbn/utility-types'; +import { decodeVersion, encodeHitVersion } from '@kbn/securitysolution-es-utils'; import { AlertTypeParams } from '../../../alerting/server'; import { ReadOperations, @@ -35,9 +36,8 @@ export interface ConstructorOptions { export interface UpdateOptions { id: string; - data: { - status: string; - }; + status: string; + _version: string | undefined; index: string; } @@ -82,7 +82,9 @@ export class AlertsClient { // result in a big performance hit. If the client already knows which index the alert // belongs to, passing in the index will speed things up index: index ?? '.alerts-*', + ignore_unavailable: true, body: { query: { term: { _id: id } } }, + seq_no_primary_term: true, }); if (!isValidAlert(result.body.hits.hits[0]._source)) { @@ -91,7 +93,10 @@ export class AlertsClient { throw new Error(errorMessage); } - return result.body.hits.hits[0]._source; + return { + ...result.body.hits.hits[0]._source, + _version: encodeHitVersion(result.body.hits.hits[0]), + }; } catch (error) { const errorMessage = `Unable to retrieve alert with id of "${id}".`; this.logger.debug(errorMessage); @@ -139,7 +144,8 @@ export class AlertsClient { public async update({ id, - data, + status, + _version, index, }: UpdateOptions) { try { @@ -155,19 +161,17 @@ export class AlertsClient { entity: AlertingAuthorizationEntity.Alert, }); - const updateParameters = { + const { body: response } = await this.esClient.update({ + ...decodeVersion(_version), id, index, body: { doc: { - [ALERT_STATUS]: data.status, + [ALERT_STATUS]: status, }, }, - }; - - const res = await this.esClient.update( - updateParameters - ); + refresh: 'wait_for', + }); this.auditLogger?.log( alertAuditEvent({ @@ -176,7 +180,10 @@ export class AlertsClient { }) ); - return res.body; + return { + ...response, + _version: encodeHitVersion(response), + }; } catch (error) { this.auditLogger?.log( alertAuditEvent({ 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 c870f8b18fdce70..897c17a82b9821d 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 @@ -52,6 +52,9 @@ describe('get()', () => { _type: 'alert', _index: '.alerts-observability-apm', _id: 'NoxgpHkBqbdrfX07MqXV', + _version: 1, + _seq_no: 362, + _primary_term: 2, _source: { 'rule.id': 'apm.error_rate', message: 'hello world 1', @@ -67,6 +70,7 @@ describe('get()', () => { const result = await alertsClient.get({ id: '1', index: '.alerts-observability-apm' }); expect(result).toMatchInlineSnapshot(` Object { + "_version": "WzM2MiwyXQ==", "kibana.rac.alert.owner": "apm", "kibana.rac.alert.status": "open", "message": "hello world 1", @@ -84,7 +88,9 @@ describe('get()', () => { }, }, }, + "ignore_unavailable": true, "index": ".alerts-observability-apm", + "seq_no_primary_term": true, }, ] `); @@ -112,6 +118,9 @@ describe('get()', () => { _type: 'alert', _index: '.alerts-observability-apm', _id: 'NoxgpHkBqbdrfX07MqXV', + _version: 1, + _seq_no: 362, + _primary_term: 2, _source: { 'rule.id': 'apm.error_rate', message: 'hello world 1', @@ -170,6 +179,9 @@ describe('get()', () => { _type: 'alert', _index: '.alerts-observability-apm', _id: 'NoxgpHkBqbdrfX07MqXV', + _version: 1, + _seq_no: 362, + _primary_term: 2, _source: { 'rule.id': 'apm.error_rate', message: 'hello world 1', @@ -196,6 +208,7 @@ describe('get()', () => { }); expect(result).toMatchInlineSnapshot(` Object { + "_version": "WzM2MiwyXQ==", "kibana.rac.alert.owner": "apm", "kibana.rac.alert.status": "open", "message": "hello world 1", 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 10bf31e441839ca..720ad769da8ea5c 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 @@ -79,7 +79,8 @@ describe('update()', () => { ); const result = await alertsClient.update({ id: '1', - data: { status: 'closed' }, + status: 'closed', + _version: undefined, index: '.alerts-observability-apm', }); expect(result).toMatchInlineSnapshot(` @@ -93,7 +94,7 @@ describe('update()', () => { "successful": 1, "total": 2, }, - "_version": 2, + "_version": "WzEsMV0=", "result": "updated", } `); @@ -108,6 +109,7 @@ describe('update()', () => { }, "id": "1", "index": ".alerts-observability-apm", + "refresh": "wait_for", }, ] `); @@ -162,7 +164,8 @@ describe('update()', () => { ); await alertsClient.update({ id: '1', - data: { status: 'closed' }, + status: 'closed', + _version: undefined, index: '.alerts-observability-apm', }); @@ -186,7 +189,8 @@ describe('update()', () => { await expect( alertsClient.update({ id: '1', - data: { status: 'closed' }, + status: 'closed', + _version: undefined, index: '.alerts-observability-apm', }) ).rejects.toThrowErrorMatchingInlineSnapshot(`"something went wrong on get"`); @@ -242,7 +246,8 @@ describe('update()', () => { await expect( alertsClient.update({ id: '1', - data: { status: 'closed' }, + status: 'closed', + _version: undefined, index: '.alerts-observability-apm', }) ).rejects.toThrowErrorMatchingInlineSnapshot(`"something went wrong on update"`); @@ -280,6 +285,9 @@ describe('update()', () => { _type: 'alert', _index: '.alerts-observability-apm', _id: 'NoxgpHkBqbdrfX07MqXV', + _version: 2, + _seq_no: 362, + _primary_term: 2, _source: { 'rule.id': 'apm.error_rate', message: 'hello world 1', @@ -312,7 +320,8 @@ describe('update()', () => { const alertsClient = new AlertsClient(alertsClientParams); const result = await alertsClient.update({ id: '1', - data: { status: 'closed' }, + status: 'closed', + _version: undefined, index: '.alerts-observability-apm', }); @@ -333,7 +342,7 @@ describe('update()', () => { "successful": 1, "total": 2, }, - "_version": 2, + "_version": "WzEsMV0=", "result": "updated", } `); @@ -348,7 +357,8 @@ describe('update()', () => { await expect( alertsClient.update({ id: '1', - data: { status: 'closed' }, + status: 'closed', + _version: undefined, index: '.alerts-observability-apm', }) ).rejects.toMatchInlineSnapshot( diff --git a/x-pack/plugins/rule_registry/server/event_log/elasticsearch/index_writer.ts b/x-pack/plugins/rule_registry/server/event_log/elasticsearch/index_writer.ts index 7f83421ec80d85b..6fd1c954d8c14c1 100644 --- a/x-pack/plugins/rule_registry/server/event_log/elasticsearch/index_writer.ts +++ b/x-pack/plugins/rule_registry/server/event_log/elasticsearch/index_writer.ts @@ -72,7 +72,7 @@ export class IndexWriter { for (const item of items) { if (item.doc === undefined) continue; - bulkBody.push({ create: { _index: item.index } }); + bulkBody.push({ create: { _index: item.index, version: 1 } }); bulkBody.push(item.doc); } diff --git a/x-pack/plugins/rule_registry/server/plugin.ts b/x-pack/plugins/rule_registry/server/plugin.ts index 3b903191ca76e0a..84cda0fc39ce721 100644 --- a/x-pack/plugins/rule_registry/server/plugin.ts +++ b/x-pack/plugins/rule_registry/server/plugin.ts @@ -148,10 +148,7 @@ export class RuleRegistryPlugin private createRouteHandlerContext = (): IContextProvider => { const { alertsClientFactory } = this; - return async function alertsRouteHandlerContext( - context, - request - ): Promise { + return function alertsRouteHandlerContext(context, request): RacApiRequestHandlerContext { return { getAlertsClient: async () => { const createdClient = alertsClientFactory.create(request); diff --git a/x-pack/plugins/rule_registry/server/routes/__mocks__/response_adapters.ts b/x-pack/plugins/rule_registry/server/routes/__mocks__/response_adapters.ts index 45c86c90be723cf..7952b33dcf9b103 100644 --- a/x-pack/plugins/rule_registry/server/routes/__mocks__/response_adapters.ts +++ b/x-pack/plugins/rule_registry/server/routes/__mocks__/response_adapters.ts @@ -34,10 +34,10 @@ const buildResponses = (method: Method, calls: MockCall[]): ResponseCall[] => { switch (method) { case 'ok': return calls.map(([call]) => ({ status: 200, body: call.body })); - case 'custom': + case 'customError': return calls.map(([call]) => ({ status: call.statusCode, - body: JSON.parse(call.body), + body: call.body, })); default: throw new Error(`Encountered unexpected call to response.${method}`); diff --git a/x-pack/plugins/rule_registry/server/routes/get_alert_by_id.test.ts b/x-pack/plugins/rule_registry/server/routes/get_alert_by_id.test.ts index b87202aac2c4a0f..0de1e6c585a17f4 100644 --- a/x-pack/plugins/rule_registry/server/routes/get_alert_by_id.test.ts +++ b/x-pack/plugins/rule_registry/server/routes/get_alert_by_id.test.ts @@ -90,6 +90,9 @@ describe('getAlertByIdRoute', () => { const response = await server.inject(getReadRequest(), context); expect(response.status).toEqual(500); - expect(response.body).toEqual({ message: 'Unable to get alert', status_code: 500 }); + expect(response.body).toEqual({ + attributes: { success: false }, + message: 'Unable to get alert', + }); }); }); diff --git a/x-pack/plugins/rule_registry/server/routes/get_alert_by_id.ts b/x-pack/plugins/rule_registry/server/routes/get_alert_by_id.ts index de4519512415e7f..67b040cebc4b9d9 100644 --- a/x-pack/plugins/rule_registry/server/routes/get_alert_by_id.ts +++ b/x-pack/plugins/rule_registry/server/routes/get_alert_by_id.ts @@ -55,15 +55,15 @@ export const getAlertByIdRoute = (router: IRouter) => ...contentType, }; - return response.custom({ + return response.customError({ headers: defaultedHeaders, statusCode: err.statusCode, - body: Buffer.from( - JSON.stringify({ - message: err.message, - status_code: err.statusCode, - }) - ), + body: { + message: err.message, + attributes: { + success: false, + }, + }, }); } } diff --git a/x-pack/plugins/rule_registry/server/routes/update_alert_by_id.test.ts b/x-pack/plugins/rule_registry/server/routes/update_alert_by_id.test.ts index 0ab2abf138e487c..7ec699491ca83e7 100644 --- a/x-pack/plugins/rule_registry/server/routes/update_alert_by_id.test.ts +++ b/x-pack/plugins/rule_registry/server/routes/update_alert_by_id.test.ts @@ -22,7 +22,7 @@ describe('updateAlertByIdRoute', () => { clients.rac.update.mockResolvedValue({ _index: '.alerts-observability-apm', _id: 'NoxgpHkBqbdrfX07MqXV', - _version: 2, + _version: 'WzM2MiwyXQ==', result: 'updated', _shards: { total: 2, successful: 1, failed: 0 }, _seq_no: 1, @@ -37,15 +37,13 @@ describe('updateAlertByIdRoute', () => { expect(response.status).toEqual(200); expect(response.body).toEqual({ - alerts: { - _index: '.alerts-observability-apm', - _id: 'NoxgpHkBqbdrfX07MqXV', - _version: 2, - result: 'updated', - _shards: { total: 2, successful: 1, failed: 0 }, - _seq_no: 1, - _primary_term: 1, - }, + _index: '.alerts-observability-apm', + _id: 'NoxgpHkBqbdrfX07MqXV', + _version: 'WzM2MiwyXQ==', + result: 'updated', + _shards: { total: 2, successful: 1, failed: 0 }, + _seq_no: 1, + _primary_term: 1, success: true, }); }); @@ -95,6 +93,9 @@ describe('updateAlertByIdRoute', () => { const response = await server.inject(getUpdateRequest(), context); expect(response.status).toEqual(500); - expect(response.body).toEqual({ message: 'Unable to update alert', status_code: 500 }); + expect(response.body).toEqual({ + attributes: { success: false }, + message: 'Unable to update alert', + }); }); }); diff --git a/x-pack/plugins/rule_registry/server/routes/update_alert_by_id.ts b/x-pack/plugins/rule_registry/server/routes/update_alert_by_id.ts index 5ebfe536a2f975d..7cd3441ea4e6e74 100644 --- a/x-pack/plugins/rule_registry/server/routes/update_alert_by_id.ts +++ b/x-pack/plugins/rule_registry/server/routes/update_alert_by_id.ts @@ -20,13 +20,20 @@ export const updateAlertByIdRoute = (router: IRouter) path: BASE_RAC_ALERTS_API_PATH, validate: { body: buildRouteValidation( - t.exact( - t.type({ - status: t.string, - ids: t.array(t.string), - index: t.string, - }) - ) + t.intersection([ + t.exact( + t.type({ + status: t.string, + ids: t.array(t.string), + index: t.string, + }) + ), + t.exact( + t.partial({ + _version: t.string, + }) + ), + ]) ), }, options: { @@ -36,14 +43,15 @@ export const updateAlertByIdRoute = (router: IRouter) async (context, req, response) => { try { const alertsClient = await context.rac.getAlertsClient(); - const { status, ids, index } = req.body; + const { status, ids, index, _version } = req.body; const updatedAlert = await alertsClient.update({ id: ids[0], - data: { status }, + status, + _version, index, }); - return response.ok({ body: { success: true, alerts: updatedAlert } }); + return response.ok({ body: { success: true, ...updatedAlert } }); } catch (exc) { const err = transformError(exc); @@ -54,15 +62,15 @@ export const updateAlertByIdRoute = (router: IRouter) ...contentType, }; - return response.custom({ + return response.customError({ headers: defaultedHeaders, statusCode: err.statusCode, - body: Buffer.from( - JSON.stringify({ - message: err.message, - status_code: err.statusCode, - }) - ), + body: { + message: err.message, + attributes: { + success: false, + }, + }, }); } } diff --git a/x-pack/plugins/rule_registry/server/scripts/get_alerts_index.sh b/x-pack/plugins/rule_registry/server/scripts/get_alerts_index.sh index e6963a9d535a27e..bfa74aa016f02ed 100755 --- a/x-pack/plugins/rule_registry/server/scripts/get_alerts_index.sh +++ b/x-pack/plugins/rule_registry/server/scripts/get_alerts_index.sh @@ -18,6 +18,6 @@ cd .. # Example: ./find_rules.sh curl -v -k \ -u $USER:changeme \ - -X GET "${KIBANA_URL}${SPACE_URL}/api/rac/alerts/index" | jq . + -X GET "${KIBANA_URL}${SPACE_URL}/internal/rac/alerts/index" | jq . # -X GET "${KIBANA_URL}${SPACE_URL}/api/apm/settings/apm-alerts-as-data-indices" | jq . diff --git a/x-pack/plugins/rule_registry/server/scripts/get_observability_alert.sh b/x-pack/plugins/rule_registry/server/scripts/get_observability_alert.sh index 41bc27e1d7655d4..6fbd0eb3dc816a5 100755 --- a/x-pack/plugins/rule_registry/server/scripts/get_observability_alert.sh +++ b/x-pack/plugins/rule_registry/server/scripts/get_observability_alert.sh @@ -19,4 +19,4 @@ cd .. # Example: ./get_observability_alert.sh hunter curl -v -k \ -u $USER:changeme \ - -X GET "${KIBANA_URL}${SPACE_URL}/api/rac/alerts?id=$ID&index=.alerts-observability-apm" | jq . + -X GET "${KIBANA_URL}${SPACE_URL}/internal/rac/alerts?id=$ID&index=.alerts-observability-apm" | jq . diff --git a/x-pack/plugins/rule_registry/server/scripts/update_observability_alert.sh b/x-pack/plugins/rule_registry/server/scripts/update_observability_alert.sh index f5c7ad716ed1b83..f61fcf2662aa37f 100755 --- a/x-pack/plugins/rule_registry/server/scripts/update_observability_alert.sh +++ b/x-pack/plugins/rule_registry/server/scripts/update_observability_alert.sh @@ -24,5 +24,5 @@ curl -s -k \ -H 'Content-Type: application/json' \ -H 'kbn-xsrf: 123' \ -u observer:changeme \ - -X POST ${KIBANA_URL}${SPACE_URL}/api/rac/alerts \ + -X POST ${KIBANA_URL}${SPACE_URL}/internal/rac/alerts \ -d "{\"ids\": $IDS, \"status\":\"$STATUS\", \"index\":\".alerts-observability-apm\"}" | jq . 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 763d158e2d74529..0c1befb54e109e2 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 @@ -3,6 +3,9 @@ "value": { "index": ".alerts-observability-apm", "id": "NoxgpHkBqbdrfX07MqXV", + "_version": 1, + "_seq_no": 1, + "_primary_term": 1, "source": { "@timestamp": "2020-12-16T15:16:18.570Z", "rule.id": "apm.error_rate", @@ -18,6 +21,9 @@ "value": { "index": ".alerts-security-solution", "id": "020202", + "_version": 1, + "_seq_no": 1, + "_primary_term": 1, "source": { "@timestamp": "2020-12-16T15:16:18.570Z", "rule.id": "siem.signals", diff --git a/x-pack/test/rule_registry/security_and_spaces/tests/basic/get_alerts.ts b/x-pack/test/rule_registry/security_and_spaces/tests/basic/get_alerts.ts index 27f4c00875bc989..d632448b2bd385b 100644 --- a/x-pack/test/rule_registry/security_and_spaces/tests/basic/get_alerts.ts +++ b/x-pack/test/rule_registry/security_and_spaces/tests/basic/get_alerts.ts @@ -30,7 +30,7 @@ export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); - const TEST_URL = '/api/rac/alerts'; + const TEST_URL = '/internal/rac/alerts'; const ALERTS_INDEX_URL = `${TEST_URL}/index`; const SPACE1 = 'space1'; const SPACE2 = 'space2'; diff --git a/x-pack/test/rule_registry/security_and_spaces/tests/basic/update_alert.ts b/x-pack/test/rule_registry/security_and_spaces/tests/basic/update_alert.ts index b8d8b78aaf45e8b..f04cb8c8f467ebc 100644 --- a/x-pack/test/rule_registry/security_and_spaces/tests/basic/update_alert.ts +++ b/x-pack/test/rule_registry/security_and_spaces/tests/basic/update_alert.ts @@ -23,7 +23,7 @@ export default ({ getService }: FtrProviderContext) => { const supertestWithoutAuth = getService('supertestWithoutAuth'); const esArchiver = getService('esArchiver'); - const TEST_URL = '/api/rac/alerts'; + const TEST_URL = '/internal/rac/alerts'; const ALERTS_INDEX_URL = `${TEST_URL}/index`; const SPACE1 = 'space1'; @@ -56,38 +56,101 @@ export default ({ getService }: FtrProviderContext) => { .post(`${getSpaceUrlPrefix(SPACE1)}${TEST_URL}`) .auth(superUser.username, superUser.password) .set('kbn-xsrf', 'true') - .send({ ids: ['NoxgpHkBqbdrfX07MqXV'], status: 'closed', index: apmIndex }) + .send({ + ids: ['NoxgpHkBqbdrfX07MqXV'], + status: 'closed', + index: apmIndex, + _version: Buffer.from(JSON.stringify([0, 1]), 'utf8').toString('base64'), + }) .expect(200); }); + it(`${superUser.username} should receive a 409 if trying to update an old alert document version`, async () => { + const apmIndex = await getAPMIndexName(superUser); + await supertestWithoutAuth + .post(`${getSpaceUrlPrefix(SPACE1)}${TEST_URL}`) + .auth(superUser.username, superUser.password) + .set('kbn-xsrf', 'true') + .send({ + ids: ['NoxgpHkBqbdrfX07MqXV'], + status: 'closed', + index: apmIndex, + _version: Buffer.from(JSON.stringify([0, 1]), 'utf8').toString('base64'), + }) + .expect(200); + await supertestWithoutAuth + .post(`${getSpaceUrlPrefix(SPACE1)}${TEST_URL}`) + .auth(superUser.username, superUser.password) + .set('kbn-xsrf', 'true') + .send({ + ids: ['NoxgpHkBqbdrfX07MqXV'], + status: 'closed', + index: apmIndex, + _version: Buffer.from(JSON.stringify([999, 999]), 'utf8').toString('base64'), + }) + .expect(409); + }); it(`${obsOnlySpacesAll.username} should be able to update the APM alert in ${SPACE1}`, async () => { const apmIndex = await getAPMIndexName(superUser); const res = await supertestWithoutAuth .post(`${getSpaceUrlPrefix(SPACE1)}${TEST_URL}`) .auth(obsOnlySpacesAll.username, obsOnlySpacesAll.password) .set('kbn-xsrf', 'true') - .send({ ids: ['NoxgpHkBqbdrfX07MqXV'], status: 'closed', index: apmIndex }) - .expect(200); + .send({ + ids: ['NoxgpHkBqbdrfX07MqXV'], + status: 'closed', + index: apmIndex, + _version: Buffer.from(JSON.stringify([0, 1]), 'utf8').toString('base64'), + }); expect(res.body).to.eql({ success: true, - alerts: { - _index: '.alerts-observability-apm', - _id: 'NoxgpHkBqbdrfX07MqXV', - _version: 2, - result: 'updated', - _shards: { total: 2, successful: 1, failed: 0 }, - _seq_no: 1, - _primary_term: 1, - }, + _index: '.alerts-observability-apm', + _id: 'NoxgpHkBqbdrfX07MqXV', + result: 'updated', + _shards: { total: 2, successful: 1, failed: 0 }, + _version: 'WzEsMV0=', + _seq_no: 1, + _primary_term: 1, }); }); + it(`${obsOnlySpacesAll.username} should receive a 409 if trying to update an old alert document version`, async () => { + const apmIndex = await getAPMIndexName(superUser); + await supertestWithoutAuth + .post(`${getSpaceUrlPrefix(SPACE1)}${TEST_URL}`) + .auth(obsOnlySpacesAll.username, obsOnlySpacesAll.password) + .set('kbn-xsrf', 'true') + .send({ + ids: ['NoxgpHkBqbdrfX07MqXV'], + status: 'closed', + index: apmIndex, + _version: Buffer.from(JSON.stringify([0, 1]), 'utf8').toString('base64'), + }) + .expect(200); + + await supertestWithoutAuth + .post(`${getSpaceUrlPrefix(SPACE1)}${TEST_URL}`) + .auth(obsOnlySpacesAll.username, obsOnlySpacesAll.password) + .set('kbn-xsrf', 'true') + .send({ + ids: ['NoxgpHkBqbdrfX07MqXV'], + status: 'closed', + index: apmIndex, + _version: Buffer.from(JSON.stringify([999, 999]), 'utf8').toString('base64'), + }) + .expect(409); + }); it(`${obsOnlyReadSpacesAll.username} should NOT be able to update the APM alert in ${SPACE1}`, async () => { const apmIndex = await getAPMIndexName(superUser); await supertestWithoutAuth .post(`${getSpaceUrlPrefix(SPACE1)}${TEST_URL}`) .auth(obsOnlyReadSpacesAll.username, obsOnlyReadSpacesAll.password) .set('kbn-xsrf', 'true') - .send({ ids: ['NoxgpHkBqbdrfX07MqXV'], status: 'closed', index: apmIndex }) + .send({ + ids: ['NoxgpHkBqbdrfX07MqXV'], + status: 'closed', + index: apmIndex, + _version: Buffer.from(JSON.stringify([0, 1]), 'utf8').toString('base64'), + }) .expect(403); }); @@ -112,6 +175,7 @@ export default ({ getService }: FtrProviderContext) => { ids: ['NoxgpHkBqbdrfX07MqXV'], status: 'closed', index: apmIndex, + _version: Buffer.from(JSON.stringify([0, 1]), 'utf8').toString('base64'), }) .expect(403); }); diff --git a/x-pack/test/rule_registry/security_and_spaces/tests/trial/get_alerts.ts b/x-pack/test/rule_registry/security_and_spaces/tests/trial/get_alerts.ts index 245aab4b3311dd1..cf21a8a148ed3dc 100644 --- a/x-pack/test/rule_registry/security_and_spaces/tests/trial/get_alerts.ts +++ b/x-pack/test/rule_registry/security_and_spaces/tests/trial/get_alerts.ts @@ -26,7 +26,7 @@ export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); - const TEST_URL = '/api/rac/alerts'; + const TEST_URL = '/internal/rac/alerts'; const ALERTS_INDEX_URL = `${TEST_URL}/index`; const SPACE1 = 'space1'; const SPACE2 = 'space2'; diff --git a/x-pack/test/rule_registry/security_and_spaces/tests/trial/update_alert.ts b/x-pack/test/rule_registry/security_and_spaces/tests/trial/update_alert.ts index 293f479eab1bd99..70071ccdb3cf587 100644 --- a/x-pack/test/rule_registry/security_and_spaces/tests/trial/update_alert.ts +++ b/x-pack/test/rule_registry/security_and_spaces/tests/trial/update_alert.ts @@ -24,7 +24,7 @@ export default ({ getService }: FtrProviderContext) => { const supertestWithoutAuth = getService('supertestWithoutAuth'); const esArchiver = getService('esArchiver'); - const TEST_URL = '/api/rac/alerts'; + const TEST_URL = '/internal/rac/alerts'; const ALERTS_INDEX_URL = `${TEST_URL}/index`; const SPACE1 = 'space1'; @@ -57,31 +57,91 @@ export default ({ getService }: FtrProviderContext) => { .post(`${getSpaceUrlPrefix(SPACE1)}${TEST_URL}`) .auth(superUser.username, superUser.password) .set('kbn-xsrf', 'true') - .send({ ids: ['NoxgpHkBqbdrfX07MqXV'], status: 'closed', index: apmIndex }) + .send({ + ids: ['NoxgpHkBqbdrfX07MqXV'], + status: 'closed', + index: apmIndex, + _version: Buffer.from(JSON.stringify([0, 1]), 'utf8').toString('base64'), + }) .expect(200); }); + it(`${superUser.username} should receive a 409 if trying to update an old alert document version`, async () => { + const apmIndex = await getAPMIndexName(superUser); + await supertestWithoutAuth + .post(`${getSpaceUrlPrefix(SPACE1)}${TEST_URL}`) + .auth(superUser.username, superUser.password) + .set('kbn-xsrf', 'true') + .send({ + ids: ['NoxgpHkBqbdrfX07MqXV'], + status: 'closed', + index: apmIndex, + _version: Buffer.from(JSON.stringify([0, 1]), 'utf8').toString('base64'), + }) + .expect(200); + + await supertestWithoutAuth + .post(`${getSpaceUrlPrefix(SPACE1)}${TEST_URL}`) + .auth(superUser.username, superUser.password) + .set('kbn-xsrf', 'true') + .send({ + ids: ['NoxgpHkBqbdrfX07MqXV'], + status: 'closed', + index: apmIndex, + _version: Buffer.from(JSON.stringify([999, 999]), 'utf8').toString('base64'), + }) + .expect(409); + }); + it(`${obsMinReadAlertsAllSpacesAll.username} should be able to update the APM alert in ${SPACE1}`, async () => { const apmIndex = await getAPMIndexName(superUser); const res = await supertestWithoutAuth .post(`${getSpaceUrlPrefix(SPACE1)}${TEST_URL}`) .auth(obsMinReadAlertsAllSpacesAll.username, obsMinReadAlertsAllSpacesAll.password) .set('kbn-xsrf', 'true') - .send({ ids: ['NoxgpHkBqbdrfX07MqXV'], status: 'closed', index: apmIndex }) + .send({ + ids: ['NoxgpHkBqbdrfX07MqXV'], + status: 'closed', + index: apmIndex, + _version: Buffer.from(JSON.stringify([0, 1]), 'utf8').toString('base64'), + }) .expect(200); expect(res.body).to.eql({ success: true, - alerts: { - _index: '.alerts-observability-apm', - _id: 'NoxgpHkBqbdrfX07MqXV', - _version: 2, - result: 'updated', - _shards: { total: 2, successful: 1, failed: 0 }, - _seq_no: 1, - _primary_term: 1, - }, + _index: '.alerts-observability-apm', + _id: 'NoxgpHkBqbdrfX07MqXV', + result: 'updated', + _shards: { total: 2, successful: 1, failed: 0 }, + _version: 'WzEsMV0=', + _seq_no: 1, + _primary_term: 1, }); }); + it(`${obsMinReadAlertsAllSpacesAll.username} should receive a 409 if trying to update an old alert document version`, async () => { + const apmIndex = await getAPMIndexName(superUser); + await supertestWithoutAuth + .post(`${getSpaceUrlPrefix(SPACE1)}${TEST_URL}`) + .auth(obsMinReadAlertsAllSpacesAll.username, obsMinReadAlertsAllSpacesAll.password) + .set('kbn-xsrf', 'true') + .send({ + ids: ['NoxgpHkBqbdrfX07MqXV'], + status: 'closed', + index: apmIndex, + _version: Buffer.from(JSON.stringify([0, 1]), 'utf8').toString('base64'), + }) + .expect(200); + await supertestWithoutAuth + .post(`${getSpaceUrlPrefix(SPACE1)}${TEST_URL}`) + .auth(obsMinReadAlertsAllSpacesAll.username, obsMinReadAlertsAllSpacesAll.password) + .set('kbn-xsrf', 'true') + .send({ + ids: ['NoxgpHkBqbdrfX07MqXV'], + status: 'closed', + index: apmIndex, + _version: Buffer.from(JSON.stringify([999, 999]), 'utf8').toString('base64'), + }) + .expect(409); + }); it(`${obsMinReadAlertsAll.username} should be able to update the APM alert in ${SPACE1}`, async () => { const apmIndex = await getAPMIndexName(superUser); @@ -89,7 +149,12 @@ export default ({ getService }: FtrProviderContext) => { .post(`${getSpaceUrlPrefix(SPACE1)}${TEST_URL}`) .auth(obsMinReadAlertsAll.username, obsMinReadAlertsAll.password) .set('kbn-xsrf', 'true') - .send({ ids: ['NoxgpHkBqbdrfX07MqXV'], status: 'closed', index: apmIndex }) + .send({ + ids: ['NoxgpHkBqbdrfX07MqXV'], + status: 'closed', + index: apmIndex, + _version: Buffer.from(JSON.stringify([0, 1]), 'utf8').toString('base64'), + }) .expect(200); }); it(`${obsMinAll.username} should NOT be able to update the APM alert in ${SPACE1}`, async () => { @@ -98,7 +163,12 @@ export default ({ getService }: FtrProviderContext) => { .post(`${getSpaceUrlPrefix(SPACE1)}${TEST_URL}`) .auth(obsMinAll.username, obsMinAll.password) .set('kbn-xsrf', 'true') - .send({ ids: ['NoxgpHkBqbdrfX07MqXV'], status: 'closed', index: apmIndex }) + .send({ + ids: ['NoxgpHkBqbdrfX07MqXV'], + status: 'closed', + index: apmIndex, + _version: Buffer.from(JSON.stringify([0, 1]), 'utf8').toString('base64'), + }) .expect(403); }); @@ -108,7 +178,12 @@ export default ({ getService }: FtrProviderContext) => { .post(`${getSpaceUrlPrefix(SPACE1)}${TEST_URL}`) .auth(obsMinAllSpacesAll.username, obsMinAllSpacesAll.password) .set('kbn-xsrf', 'true') - .send({ ids: ['NoxgpHkBqbdrfX07MqXV'], status: 'closed', index: apmIndex }) + .send({ + ids: ['NoxgpHkBqbdrfX07MqXV'], + status: 'closed', + index: apmIndex, + _version: Buffer.from(JSON.stringify([0, 1]), 'utf8').toString('base64'), + }) .expect(403); }); @@ -118,7 +193,12 @@ export default ({ getService }: FtrProviderContext) => { .post(`${getSpaceUrlPrefix(SPACE1)}${TEST_URL}`) .auth(obsAlertsAll.username, obsAlertsAll.password) .set('kbn-xsrf', 'true') - .send({ ids: ['NoxgpHkBqbdrfX07MqXV'], status: 'closed', index: apmIndex }) + .send({ + ids: ['NoxgpHkBqbdrfX07MqXV'], + status: 'closed', + index: apmIndex, + _version: Buffer.from(JSON.stringify([0, 1]), 'utf8').toString('base64'), + }) .expect(403); }); @@ -128,7 +208,12 @@ export default ({ getService }: FtrProviderContext) => { .post(`${getSpaceUrlPrefix(SPACE1)}${TEST_URL}`) .auth(obsAlertsAllSpacesAll.username, obsAlertsAllSpacesAll.password) .set('kbn-xsrf', 'true') - .send({ ids: ['NoxgpHkBqbdrfX07MqXV'], status: 'closed', index: apmIndex }) + .send({ + ids: ['NoxgpHkBqbdrfX07MqXV'], + status: 'closed', + index: apmIndex, + _version: Buffer.from(JSON.stringify([0, 1]), 'utf8').toString('base64'), + }) .expect(403); }); });