diff --git a/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/delete_asset_criticality.gen.ts b/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/delete_asset_criticality.gen.ts index 22f3f682ae98a..4b747b6a48674 100644 --- a/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/delete_asset_criticality.gen.ts +++ b/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/delete_asset_criticality.gen.ts @@ -16,7 +16,7 @@ import { z } from 'zod'; -import { IdField } from './common.gen'; +import { IdField, AssetCriticalityRecord } from './common.gen'; export type DeleteAssetCriticalityRecordRequestQuery = z.infer< typeof DeleteAssetCriticalityRecordRequestQuery @@ -38,3 +38,14 @@ export const DeleteAssetCriticalityRecordRequestQuery = z.object({ export type DeleteAssetCriticalityRecordRequestQueryInput = z.input< typeof DeleteAssetCriticalityRecordRequestQuery >; + +export type DeleteAssetCriticalityRecordResponse = z.infer< + typeof DeleteAssetCriticalityRecordResponse +>; +export const DeleteAssetCriticalityRecordResponse = z.object({ + /** + * If the record was deleted. If false the record did not exist. + */ + deleted: z.boolean(), + record: AssetCriticalityRecord.optional(), +}); diff --git a/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/delete_asset_criticality.schema.yaml b/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/delete_asset_criticality.schema.yaml index 2b4f05528da0c..521cacd51406b 100644 --- a/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/delete_asset_criticality.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/entity_analytics/asset_criticality/delete_asset_criticality.schema.yaml @@ -33,5 +33,17 @@ paths: responses: '200': description: Successful response + content: + application/json: + schema: + type: object + properties: + deleted: + type: boolean + description: If the record was deleted. If false the record did not exist. + record: + $ref: './common.schema.yaml#/components/schemas/AssetCriticalityRecord' + required: + - deleted '400': description: Invalid request diff --git a/x-pack/plugins/security_solution/docs/openapi/ess/security_solution_entity_analytics_api_2023_10_31.bundled.schema.yaml b/x-pack/plugins/security_solution/docs/openapi/ess/security_solution_entity_analytics_api_2023_10_31.bundled.schema.yaml index bef21b7133bee..35346afa0f120 100644 --- a/x-pack/plugins/security_solution/docs/openapi/ess/security_solution_entity_analytics_api_2023_10_31.bundled.schema.yaml +++ b/x-pack/plugins/security_solution/docs/openapi/ess/security_solution_entity_analytics_api_2023_10_31.bundled.schema.yaml @@ -38,6 +38,20 @@ paths: type: string responses: '200': + content: + application/json: + schema: + type: object + properties: + deleted: + description: >- + If the record was deleted. If false the record did not + exist. + type: boolean + record: + $ref: '#/components/schemas/AssetCriticalityRecord' + required: + - deleted description: Successful response '400': description: Invalid request diff --git a/x-pack/plugins/security_solution/docs/openapi/serverless/security_solution_entity_analytics_api_2023_10_31.bundled.schema.yaml b/x-pack/plugins/security_solution/docs/openapi/serverless/security_solution_entity_analytics_api_2023_10_31.bundled.schema.yaml index 4d6b8f0c5aa78..79df809b600c2 100644 --- a/x-pack/plugins/security_solution/docs/openapi/serverless/security_solution_entity_analytics_api_2023_10_31.bundled.schema.yaml +++ b/x-pack/plugins/security_solution/docs/openapi/serverless/security_solution_entity_analytics_api_2023_10_31.bundled.schema.yaml @@ -38,6 +38,20 @@ paths: type: string responses: '200': + content: + application/json: + schema: + type: object + properties: + deleted: + description: >- + If the record was deleted. If false the record did not + exist. + type: boolean + record: + $ref: '#/components/schemas/AssetCriticalityRecord' + required: + - deleted description: Successful response '400': description: Invalid request diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/asset_criticality_data_client.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/asset_criticality_data_client.ts index 4770d051f2e99..39473c69c5a2e 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/asset_criticality_data_client.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/asset_criticality_data_client.ts @@ -272,12 +272,37 @@ export class AssetCriticalityDataClient { return { errors, stats }; }; - public async delete(idParts: AssetCriticalityIdParts, refresh = 'wait_for' as const) { - await this.options.esClient.delete({ - id: createId(idParts), - index: this.getIndex(), - refresh: refresh ?? false, - }); + public async delete( + idParts: AssetCriticalityIdParts, + refresh = 'wait_for' as const + ): Promise { + let record: AssetCriticalityRecord | undefined; + try { + record = await this.get(idParts); + } catch (err) { + if (err.statusCode === 404) { + return undefined; + } else { + throw err; + } + } + + if (!record) { + return undefined; + } + + try { + await this.options.esClient.delete({ + id: createId(idParts), + index: this.getIndex(), + refresh: refresh ?? false, + }); + } catch (err) { + this.options.logger.error(`Failed to delete asset criticality record: ${err.message}`); + throw err; + } + + return record; } public formatSearchResponse(response: SearchResponse): { diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/delete.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/delete.ts index 4da6d719e701e..4e0692f631718 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/delete.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/asset_criticality/routes/delete.ts @@ -4,10 +4,11 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import type { Logger } from '@kbn/core/server'; +import type { IKibanaResponse, Logger } from '@kbn/core/server'; import { buildSiemResponse } from '@kbn/lists-plugin/server/routes/utils'; import { transformError } from '@kbn/securitysolution-es-utils'; import { buildRouteValidationWithZod } from '@kbn/zod-helpers'; +import type { DeleteAssetCriticalityRecordResponse } from '../../../../../common/api/entity_analytics'; import { DeleteAssetCriticalityRecordRequestQuery } from '../../../../../common/api/entity_analytics'; import { ASSET_CRITICALITY_PUBLIC_URL, @@ -42,7 +43,11 @@ export const assetCriticalityPublicDeleteRoute = ( }, }, }, - async (context, request, response) => { + async ( + context, + request, + response + ): Promise> => { const securitySolution = await context.securitySolution; securitySolution.getAuditLogger()?.log({ @@ -61,7 +66,7 @@ export const assetCriticalityPublicDeleteRoute = ( await checkAndInitAssetCriticalityResources(context, logger); const assetCriticalityClient = securitySolution.getAssetCriticalityDataClient(); - await assetCriticalityClient.delete( + const deletedRecord = await assetCriticalityClient.delete( { idField: request.query.id_field, idValue: request.query.id_value, @@ -69,7 +74,12 @@ export const assetCriticalityPublicDeleteRoute = ( request.query.refresh ); - return response.ok(); + return response.ok({ + body: { + deleted: deletedRecord !== undefined, + record: deletedRecord, + }, + }); } catch (e) { const error = transformError(e); diff --git a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/asset_criticality.ts b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/asset_criticality.ts index b0eee842b47f6..29bf401412af4 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/asset_criticality.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/asset_criticality.ts @@ -442,7 +442,7 @@ export default ({ getService }: FtrProviderContext) => { }); describe('delete', () => { - it('should correctly delete asset criticality', async () => { + it('should correctly delete asset criticality if it exists', async () => { const assetCriticality = { id_field: 'host.name', id_value: 'delete-me', @@ -451,7 +451,10 @@ export default ({ getService }: FtrProviderContext) => { await assetCriticalityRoutes.upsert(assetCriticality); - await assetCriticalityRoutes.delete('host.name', 'delete-me'); + const res = await assetCriticalityRoutes.delete('host.name', 'delete-me'); + + expect(res.body.deleted).to.eql(true); + expect(_.omit(res.body.record, '@timestamp')).to.eql(assetCriticality); const doc = await getAssetCriticalityDoc({ idField: 'host.name', idValue: 'delete-me', @@ -461,6 +464,13 @@ export default ({ getService }: FtrProviderContext) => { expect(doc).to.eql(undefined); }); + it('should not return 404 if the asset criticality does not exist', async () => { + const res = await assetCriticalityRoutes.delete('host.name', 'doesnt-exist'); + + expect(res.body.deleted).to.eql(false); + expect(res.body.record).to.eql(undefined); + }); + it('should return 403 if the advanced setting is disabled', async () => { await disableAssetCriticalityAdvancedSetting(kibanaServer, log);