From fb89b868a5e8634d4316fdff43b361843ed89fcb Mon Sep 17 00:00:00 2001 From: Christos Nasikas Date: Tue, 15 Mar 2022 11:00:30 +0200 Subject: [PATCH] [Cases] Close alerts if closure options is set to automatic (#127494) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../plugins/cases/server/client/cases/push.ts | 39 ++++++- .../tests/trial/cases/push_case.ts | 105 +++++++++++++++++- 2 files changed, 142 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/cases/server/client/cases/push.ts b/x-pack/plugins/cases/server/client/cases/push.ts index 06b0922cacc47..b19a5ebdb0e5c 100644 --- a/x-pack/plugins/cases/server/client/cases/push.ts +++ b/x-pack/plugins/cases/server/client/cases/push.ts @@ -6,6 +6,7 @@ */ import Boom from '@hapi/boom'; +import { nodeBuilder } from '@kbn/es-query'; import { SavedObjectsFindResponse } from 'kibana/server'; import { @@ -17,11 +18,18 @@ import { CasesConfigureAttributes, ActionTypes, OWNER_FIELD, + CommentType, + CommentRequestAlertType, } from '../../../common/api'; +import { CASE_COMMENT_SAVED_OBJECT } from '../../../common/constants'; import { createIncident, getCommentContextFromAttributes } from './utils'; import { createCaseError } from '../../common/error'; -import { flattenCaseSavedObject, getAlertInfoFromComments } from '../../common/utils'; +import { + createAlertUpdateRequest, + flattenCaseSavedObject, + getAlertInfoFromComments, +} from '../../common/utils'; import { CasesClient, CasesClientArgs, CasesClientInternal } from '..'; import { Operations } from '../../authorization'; import { casesConnectors } from '../../connectors'; @@ -40,6 +48,30 @@ function shouldCloseByPush( ); } +const changeAlertsStatusToClose = async ( + caseId: string, + caseService: CasesClientArgs['caseService'], + alertsService: CasesClientArgs['alertsService'] +) => { + const alertAttachments = (await caseService.getAllCaseComments({ + id: [caseId], + options: { + filter: nodeBuilder.is(`${CASE_COMMENT_SAVED_OBJECT}.attributes.type`, CommentType.alert), + }, + })) as SavedObjectsFindResponse; + + const alerts = alertAttachments.saved_objects + .map((attachment) => + createAlertUpdateRequest({ + comment: attachment.attributes, + status: CaseStatuses.closed, + }) + ) + .flat(); + + await alertsService.updateAlertsStatus(alerts); +}; + /** * Parameters for pushing a case to an external system */ @@ -71,6 +103,7 @@ export const push = async ( caseService, caseConfigureService, userActionService, + alertsService, actionsClient, user, logger, @@ -224,6 +257,10 @@ export const push = async ( caseId, owner: myCase.attributes.owner, }); + + if (myCase.attributes.settings.syncAlerts) { + await changeAlertsStatusToClose(myCase.id, caseService, alertsService); + } } await userActionService.createUserAction({ diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/trial/cases/push_case.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/trial/cases/push_case.ts index 2eb066d110f51..193621a422726 100644 --- a/x-pack/test/cases_api_integration/security_and_spaces/tests/trial/cases/push_case.ts +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/trial/cases/push_case.ts @@ -34,8 +34,13 @@ import { getCase, getServiceNowSimulationServer, createConfiguration, + getSignalsWithES, } from '../../../../common/lib/utils'; -import { CaseConnector, CaseStatuses } from '../../../../../../plugins/cases/common/api'; +import { + CaseConnector, + CaseStatuses, + CommentType, +} from '../../../../../../plugins/cases/common/api'; import { globalRead, noKibanaPrivileges, @@ -50,6 +55,7 @@ import { export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); const es = getService('es'); + const esArchiver = getService('esArchiver'); describe('push_case', () => { const actionsRemover = new ActionsRemover(supertest); @@ -295,6 +301,103 @@ export default ({ getService }: FtrProviderContext): void => { }); }); + describe('alerts', () => { + const defaultSignalsIndex = '.siem-signals-default-000001'; + const signalID = '4679431ee0ba3209b6fcd60a255a696886fe0a7d18f5375de510ff5b68fa6b78'; + const signalID2 = '1023bcfea939643c5e51fd8df53797e0ea693cee547db579ab56d96402365c1e'; + + beforeEach(async () => { + await esArchiver.load('x-pack/test/functional/es_archives/cases/signals/default'); + }); + + afterEach(async () => { + await esArchiver.unload('x-pack/test/functional/es_archives/cases/signals/default'); + await deleteAllCaseItems(es); + }); + + const attachAlertsAndPush = async ({ syncAlerts = true }: { syncAlerts?: boolean } = {}) => { + const { postedCase, connector } = await createCaseWithConnector({ + createCaseReq: { ...getPostCaseRequest(), settings: { syncAlerts } }, + configureReq: { + closure_type: 'close-by-pushing', + }, + supertest, + serviceNowSimulatorURL, + actionsRemover, + }); + + await createComment({ + supertest, + caseId: postedCase.id, + params: { + alertId: signalID, + index: defaultSignalsIndex, + rule: { id: 'test-rule-id', name: 'test-index-id' }, + type: CommentType.alert, + owner: 'securitySolutionFixture', + }, + }); + + await createComment({ + supertest, + caseId: postedCase.id, + params: { + alertId: signalID2, + index: defaultSignalsIndex, + rule: { id: 'test-rule-id', name: 'test-index-id' }, + type: CommentType.alert, + owner: 'securitySolutionFixture', + }, + }); + + await pushCase({ + supertest, + caseId: postedCase.id, + connectorId: connector.id, + }); + + await es.indices.refresh({ index: defaultSignalsIndex }); + + const signals = await getSignalsWithES({ + es, + indices: defaultSignalsIndex, + ids: [signalID, signalID2], + }); + + return signals; + }; + + it('should change the status of all alerts attached to a case to closed when closure_type: close-by-pushing and syncAlerts: true', async () => { + const signals = await attachAlertsAndPush(); + /** + * The status of the alerts should be changed to closed when pushing a case and the + * closure_type is set to close-by-pushing + */ + expect(signals.get(defaultSignalsIndex)?.get(signalID)?._source?.signal?.status).to.be( + CaseStatuses.closed + ); + + expect(signals.get(defaultSignalsIndex)?.get(signalID2)?._source?.signal?.status).to.be( + CaseStatuses.closed + ); + }); + + it('should NOT change the status of all alerts attached to a case to closed when closure_type: close-by-pushing and syncAlerts: false', async () => { + const signals = await attachAlertsAndPush({ syncAlerts: false }); + /** + * The status of the alerts should NOT be changed to closed when pushing a case and the + * closure_type is set to close-by-pushing and syncAlert is set to false + */ + expect(signals.get(defaultSignalsIndex)?.get(signalID)?._source?.signal?.status).to.be( + CaseStatuses.open + ); + + expect(signals.get(defaultSignalsIndex)?.get(signalID2)?._source?.signal?.status).to.be( + CaseStatuses.open + ); + }); + }); + describe('rbac', () => { const supertestWithoutAuth = getService('supertestWithoutAuth');