Skip to content

Commit

Permalink
[Security Solution] Detection rules for case UI (#91434)
Browse files Browse the repository at this point in the history
* Adding type field to client

* Removing context and adding association type

* Handle alerts from multiple indices

* Adding flow for adding a sub case

* Making progress on creating alerts from rules

* Refactored add comment to handle case and sub case

* Starting sub case API and refactoring of case client

* Fleshing out find cases

* Finished the find cases api

* Filtering comments by association type

* Fixing tests and types

* Updating snapshots

* Cleaning up comment references

* Working unit tests

* Fixing integration tests and got ES to work

* Unit tests and api integration test working

* Refactoring find and get_status

* Starting patch, and update

* script for sub cases

* Removing converted_by and fixing type errors

* Adding docs for script

* Removing converted_by and fixing integration test

* init expanded rows

* Adding sub case id to comment routes

* Removing stringify comparison

* styling

* clean up

* add status column

* styling

* hide actions if it has sub-cases

* Adding delete api and tests

* generated alert

* Updating license

* missed license files

* Integration tests passing

* Adding more tests for sub cases

* wip

* Find int tests, scoped client, patch sub user actions

* fixing types and call cluster

* fixing get sub case param issue

* Adding user actions for sub cases

* Preventing alerts on collections and refactoring user

* Allowing type to be updated for ind cases

* subcases attached to api

* combine enum on UI for simplification

* Refactoring and writing tests

* Fixing sub case status filtering

* add alerts count

* Adding more tests not allowing gen alerts patch

* Working unit tests

* Push to connector gets all sub case comments

* Writing more tests and cleaning up

* Updating push functionality for generated alerts and sub cases

* Adding comment about updating collection sync

* use CaseType to check if it is a sub-case

* fix types and disable selection if it has subcases

* isEmpty

* Detection rule correctly adding alerts to sub case

* update api and functionality to accept sub case

* integration part I

* fix integration with case connector

* Fix manual attach

* Fix types

* Fix bug when updating

* Fix bug with user actions

* Fix react key error

* Fix bug when pushing a lot of alerts

* fix lint error

* Fix limit

* fix title on sub case

* fix unit tests

* rm bazel

* fix unit tests and cypress test

* enable delete case icon

* revert change

* review

* Fix the scripts alerts generation code

* temp work

* Fix rule types and add migration

* fix types

* fix types error

* Remove query alerts

* Fix rules

* fix types

* fix lint error

* fix types

* delete a sub case

* rm unused i18n

* fix delete cases

* fix unit tests

* fix unit test

* update Case type

* fix types

* fix unit test

* final integration between rule and case

* fix integration test

* fix unit test + bring back connector in action of rule

Co-authored-by: Jonathan Buttner <jonathan.buttner@elastic.co>
Co-authored-by: Xavier Mouligneau <189600+XavierM@users.noreply.github.com>
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Christos Nasikas <christos.nasikas@elastic.co>
  • Loading branch information
5 people committed Feb 18, 2021
1 parent 9180ed1 commit 97d391a
Show file tree
Hide file tree
Showing 95 changed files with 1,916 additions and 541 deletions.
1 change: 1 addition & 0 deletions x-pack/plugins/case/common/api/cases/case.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ export const CaseResponseRt = rt.intersection([
version: rt.string,
}),
rt.partial({
subCaseIds: rt.array(rt.string),
subCases: rt.array(SubCaseResponseRt),
comments: rt.array(CommentResponseRt),
}),
Expand Down
7 changes: 6 additions & 1 deletion x-pack/plugins/case/common/api/cases/comment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,11 @@ export const ContextTypeUserRt = rt.type({
export const AlertCommentRequestRt = rt.type({
type: rt.union([rt.literal(CommentType.generatedAlert), rt.literal(CommentType.alert)]),
alertId: rt.union([rt.array(rt.string), rt.string]),
index: rt.string,
index: rt.union([rt.array(rt.string), rt.string]),
rule: rt.type({
id: rt.union([rt.string, rt.null]),
name: rt.union([rt.string, rt.null]),
}),
});

const AttributesTypeUserRt = rt.intersection([ContextTypeUserRt, CommentAttributesBasicRt]);
Expand Down Expand Up @@ -108,6 +112,7 @@ export const CommentsResponseRt = rt.type({

export const AllCommentsResponseRt = rt.array(CommentResponseRt);

export type AttributesTypeAlerts = rt.TypeOf<typeof AttributesTypeAlertsRt>;
export type CommentAttributes = rt.TypeOf<typeof CommentAttributesRt>;
export type CommentRequest = rt.TypeOf<typeof CommentRequestRt>;
export type CommentResponse = rt.TypeOf<typeof CommentResponseRt>;
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/case/common/api/cases/user_actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ const CaseUserActionResponseRT = rt.intersection([
case_id: rt.string,
comment_id: rt.union([rt.string, rt.null]),
}),
rt.partial({ sub_case_id: rt.string }),
]);

export const CaseUserActionAttributesRt = CaseUserActionBasicRT;
Expand Down
6 changes: 6 additions & 0 deletions x-pack/plugins/case/common/api/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
SUB_CASE_DETAILS_URL,
SUB_CASES_URL,
CASE_PUSH_URL,
SUB_CASE_USER_ACTIONS_URL,
} from '../constants';

export const getCaseDetailsUrl = (id: string): string => {
Expand All @@ -38,6 +39,11 @@ export const getCaseCommentDetailsUrl = (caseId: string, commentId: string): str
export const getCaseUserActionUrl = (id: string): string => {
return CASE_USER_ACTIONS_URL.replace('{case_id}', id);
};

export const getSubCaseUserActionUrl = (caseID: string, subCaseID: string): string => {
return SUB_CASE_USER_ACTIONS_URL.replace('{case_id}', caseID).replace('{sub_case_id}', subCaseID);
};

export const getCasePushUrl = (caseId: string, connectorId: string): string => {
return CASE_PUSH_URL.replace('{case_id}', caseId).replace('{connector_id}', connectorId);
};
10 changes: 10 additions & 0 deletions x-pack/plugins/case/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
* 2.0.
*/

import { DEFAULT_MAX_SIGNALS } from '../../security_solution/common/constants';

export const APP_ID = 'case';

/**
Expand All @@ -19,6 +21,7 @@ export const CASE_CONFIGURE_CONNECTORS_URL = `${CASE_CONFIGURE_URL}/connectors`;
export const SUB_CASES_PATCH_DEL_URL = `${CASES_URL}/sub_cases`;
export const SUB_CASES_URL = `${CASE_DETAILS_URL}/sub_cases`;
export const SUB_CASE_DETAILS_URL = `${CASE_DETAILS_URL}/sub_cases/{sub_case_id}`;
export const SUB_CASE_USER_ACTIONS_URL = `${SUB_CASE_DETAILS_URL}/user_actions`;

export const CASE_COMMENTS_URL = `${CASE_DETAILS_URL}/comments`;
export const CASE_COMMENT_DETAILS_URL = `${CASE_DETAILS_URL}/comments/{comment_id}`;
Expand All @@ -45,3 +48,10 @@ export const SUPPORTED_CONNECTORS = [
JIRA_ACTION_TYPE_ID,
RESILIENT_ACTION_TYPE_ID,
];

/**
* Alerts
*/

export const MAX_ALERTS_PER_SUB_CASE = 5000;
export const MAX_GENERATED_ALERTS_PER_SUB_CASE = MAX_ALERTS_PER_SUB_CASE / DEFAULT_MAX_SIGNALS;
3 changes: 3 additions & 0 deletions x-pack/plugins/case/server/client/cases/create.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ describe('create', () => {
"syncAlerts": true,
},
"status": "open",
"subCaseIds": undefined,
"subCases": undefined,
"tags": Array [
"defacement",
Expand Down Expand Up @@ -174,6 +175,7 @@ describe('create', () => {
"syncAlerts": true,
},
"status": "open",
"subCaseIds": undefined,
"subCases": undefined,
"tags": Array [
"defacement",
Expand Down Expand Up @@ -239,6 +241,7 @@ describe('create', () => {
"syncAlerts": true,
},
"status": "open",
"subCaseIds": undefined,
"subCases": undefined,
"tags": Array [
"defacement",
Expand Down
16 changes: 11 additions & 5 deletions x-pack/plugins/case/server/client/cases/get.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,24 @@ export const get = async ({
includeComments = false,
includeSubCaseComments = false,
}: GetParams): Promise<CaseResponse> => {
const theCase = await caseService.getCase({
client: savedObjectsClient,
id,
});
const [theCase, subCasesForCaseId] = await Promise.all([
caseService.getCase({
client: savedObjectsClient,
id,
}),
caseService.findSubCasesByCaseId({ client: savedObjectsClient, ids: [id] }),
]);

const subCaseIds = subCasesForCaseId.saved_objects.map((so) => so.id);

if (!includeComments) {
return CaseResponseRt.encode(
flattenCaseSavedObject({
savedObject: theCase,
subCaseIds,
})
);
}

const theComments = await caseService.getAllCaseComments({
client: savedObjectsClient,
id,
Expand All @@ -53,6 +58,7 @@ export const get = async ({
flattenCaseSavedObject({
savedObject: theCase,
comments: theComments.saved_objects,
subCaseIds,
totalComment: theComments.total,
totalAlerts: countAlertsForID({ comments: theComments, id }),
})
Expand Down
4 changes: 4 additions & 0 deletions x-pack/plugins/case/server/client/cases/mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ export const commentAlert: CommentResponse = {
id: 'mock-comment-1',
alertId: 'alert-id-1',
index: 'alert-index-1',
rule: {
id: 'rule-id-1',
name: 'rule-name-1',
},
type: CommentType.alert as const,
created_at: '2019-11-25T21:55:00.177Z',
created_by: {
Expand Down
5 changes: 5 additions & 0 deletions x-pack/plugins/case/server/client/cases/update.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ describe('update', () => {
"syncAlerts": true,
},
"status": "closed",
"subCaseIds": undefined,
"subCases": undefined,
"tags": Array [
"defacement",
Expand Down Expand Up @@ -166,6 +167,7 @@ describe('update', () => {
"syncAlerts": true,
},
"status": "open",
"subCaseIds": undefined,
"subCases": undefined,
"tags": Array [
"defacement",
Expand Down Expand Up @@ -233,6 +235,7 @@ describe('update', () => {
"syncAlerts": true,
},
"status": "in-progress",
"subCaseIds": undefined,
"subCases": undefined,
"tags": Array [
"LOLBins",
Expand Down Expand Up @@ -300,6 +303,7 @@ describe('update', () => {
"syncAlerts": true,
},
"status": "closed",
"subCaseIds": undefined,
"subCases": undefined,
"tags": Array [
"defacement",
Expand Down Expand Up @@ -371,6 +375,7 @@ describe('update', () => {
"syncAlerts": true,
},
"status": "open",
"subCaseIds": undefined,
"subCases": undefined,
"tags": Array [
"LOLBins",
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/case/server/client/cases/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,7 @@ export const getCommentContextFromAttributes = (
type: attributes.type,
alertId: attributes.alertId,
index: attributes.index,
rule: attributes.rule,
};
default:
return {
Expand Down
24 changes: 24 additions & 0 deletions x-pack/plugins/case/server/client/comments/add.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@ describe('addComment', () => {
type: CommentType.alert,
alertId: 'test-id',
index: 'test-index',
rule: {
id: 'test-rule1',
name: 'test-rule',
},
},
});

Expand All @@ -94,6 +98,10 @@ describe('addComment', () => {
"index": "test-index",
"pushed_at": null,
"pushed_by": null,
"rule": Object {
"id": "test-rule1",
"name": "test-rule",
},
"type": "alert",
"updated_at": null,
"updated_by": null,
Expand Down Expand Up @@ -231,6 +239,10 @@ describe('addComment', () => {
type: CommentType.alert,
alertId: 'test-alert',
index: 'test-index',
rule: {
id: 'test-rule1',
name: 'test-rule',
},
},
});

Expand Down Expand Up @@ -265,6 +277,10 @@ describe('addComment', () => {
type: CommentType.alert,
alertId: 'test-alert',
index: 'test-index',
rule: {
id: 'test-rule1',
name: 'test-rule',
},
},
});

Expand Down Expand Up @@ -406,6 +422,10 @@ describe('addComment', () => {
type: CommentType.alert,
index: 'test-index',
alertId: 'test-id',
rule: {
id: 'test-rule1',
name: 'test-rule',
},
},
})
.catch((e) => {
Expand Down Expand Up @@ -478,6 +498,10 @@ describe('addComment', () => {
type: CommentType.alert,
alertId: 'test-alert',
index: 'test-index',
rule: {
id: 'test-rule1',
name: 'test-rule',
},
},
})
.catch((e) => {
Expand Down
29 changes: 26 additions & 3 deletions x-pack/plugins/case/server/client/comments/add.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ import {
import { CaseServiceSetup, CaseUserActionServiceSetup } from '../../services';
import { CommentableCase } from '../../common';
import { CaseClientHandler } from '..';
import { CASE_COMMENT_SAVED_OBJECT } from '../../saved_object_types';
import { MAX_GENERATED_ALERTS_PER_SUB_CASE } from '../../../common/constants';

async function getSubCase({
caseService,
Expand All @@ -56,7 +58,20 @@ async function getSubCase({
}): Promise<SavedObject<SubCaseAttributes>> {
const mostRecentSubCase = await caseService.getMostRecentSubCase(savedObjectsClient, caseId);
if (mostRecentSubCase && mostRecentSubCase.attributes.status !== CaseStatuses.closed) {
return mostRecentSubCase;
const subCaseAlertsAttachement = await caseService.getAllSubCaseComments({
client: savedObjectsClient,
id: mostRecentSubCase.id,
options: {
fields: [],
filter: `${CASE_COMMENT_SAVED_OBJECT}.attributes.type: ${CommentType.generatedAlert}`,
page: 1,
perPage: 1,
},
});

if (subCaseAlertsAttachement.total <= MAX_GENERATED_ALERTS_PER_SUB_CASE) {
return mostRecentSubCase;
}
}

const newSubCase = await caseService.createSubCase({
Expand Down Expand Up @@ -160,7 +175,11 @@ const addGeneratedAlerts = async ({
await caseClient.updateAlertsStatus({
ids,
status: subCase.attributes.status,
indices: new Set([newComment.attributes.index]),
indices: new Set([
...(Array.isArray(newComment.attributes.index)
? newComment.attributes.index
: [newComment.attributes.index]),
]),
});
}

Expand Down Expand Up @@ -282,7 +301,11 @@ export const addComment = async ({
await caseClient.updateAlertsStatus({
ids,
status: updatedCase.status,
indices: new Set([newComment.attributes.index]),
indices: new Set([
...(Array.isArray(newComment.attributes.index)
? newComment.attributes.index
: [newComment.attributes.index]),
]),
});
}

Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/case/server/client/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ export interface CaseClientGetAlerts {

export interface CaseClientGetUserActions {
caseId: string;
subCaseId?: string;
}

export interface MappingsClient {
Expand Down
30 changes: 23 additions & 7 deletions x-pack/plugins/case/server/client/user_actions/get.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,32 +6,48 @@
*/

import { SavedObjectsClientContract } from 'kibana/server';
import { CASE_SAVED_OBJECT, CASE_COMMENT_SAVED_OBJECT } from '../../saved_object_types';
import {
CASE_SAVED_OBJECT,
CASE_COMMENT_SAVED_OBJECT,
SUB_CASE_SAVED_OBJECT,
} from '../../saved_object_types';
import { CaseUserActionsResponseRt, CaseUserActionsResponse } from '../../../common/api';
import { CaseUserActionServiceSetup } from '../../services';

interface GetParams {
savedObjectsClient: SavedObjectsClientContract;
userActionService: CaseUserActionServiceSetup;
caseId: string;
subCaseId?: string;
}

export const get = async ({
savedObjectsClient,
userActionService,
caseId,
subCaseId,
}: GetParams): Promise<CaseUserActionsResponse> => {
const userActions = await userActionService.getUserActions({
client: savedObjectsClient,
caseId,
subCaseId,
});

return CaseUserActionsResponseRt.encode(
userActions.saved_objects.map((ua) => ({
...ua.attributes,
action_id: ua.id,
case_id: ua.references.find((r) => r.type === CASE_SAVED_OBJECT)?.id ?? '',
comment_id: ua.references.find((r) => r.type === CASE_COMMENT_SAVED_OBJECT)?.id ?? null,
}))
userActions.saved_objects.reduce<CaseUserActionsResponse>((acc, ua) => {
if (subCaseId == null && ua.references.some((uar) => uar.type === SUB_CASE_SAVED_OBJECT)) {
return acc;
}
return [
...acc,
{
...ua.attributes,
action_id: ua.id,
case_id: ua.references.find((r) => r.type === CASE_SAVED_OBJECT)?.id ?? '',
comment_id: ua.references.find((r) => r.type === CASE_COMMENT_SAVED_OBJECT)?.id ?? null,
sub_case_id: ua.references.find((r) => r.type === SUB_CASE_SAVED_OBJECT)?.id ?? '',
},
];
}, [])
);
};
Loading

0 comments on commit 97d391a

Please sign in to comment.