Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Security Solution] Webhook - Case Management Connector #131762

Merged
merged 122 commits into from
Jul 26, 2022
Merged
Show file tree
Hide file tree
Changes from 96 commits
Commits
Show all changes
122 commits
Select commit Hold shift + click to select a range
2fed291
bootstrap cases webhook
stephmilovic Apr 12, 2022
d981f51
Merge branch 'main' into cases_webhook
stephmilovic Apr 18, 2022
2c6a233
Merge branch 'main' into cases_webhook
stephmilovic Apr 19, 2022
78907b6
wip
stephmilovic Apr 20, 2022
b18270e
Merge branch 'main' into cases_webhook
stephmilovic May 4, 2022
094ff9b
wip'
stephmilovic May 4, 2022
74361d3
Merge branch 'main' into cases_webhook
stephmilovic May 5, 2022
ff959a4
wip
stephmilovic May 6, 2022
85e3d97
[CI] Auto-commit changed files from 'node scripts/eslint --no-cache -…
kibanamachine May 6, 2022
b63647a
Merge branch 'main' into cases_webhook
stephmilovic May 16, 2022
9b6bf56
make work in cases
stephmilovic May 16, 2022
0cd8127
[CI] Auto-commit changed files from 'node scripts/eslint --no-cache -…
kibanamachine May 16, 2022
6bd36f7
Merge branch 'main' into cases_webhook
stephmilovic May 18, 2022
959e689
werk better
stephmilovic May 18, 2022
b84e705
add api file
stephmilovic May 18, 2022
823c1fa
type cleanup
stephmilovic May 19, 2022
ef6e12e
fix types more
stephmilovic May 19, 2022
4017aa8
add all fields to form
stephmilovic May 19, 2022
8067c34
add in new fields
stephmilovic May 19, 2022
c958685
fix cases push
stephmilovic May 19, 2022
2e8a899
add patch to fe
stephmilovic May 19, 2022
ba1ac12
merge in main
stephmilovic May 20, 2022
fe108a5
make link back url work
stephmilovic May 20, 2022
db286dd
type cleaning
stephmilovic May 20, 2022
53a09fa
Merge branch 'main' into cases_webhook
stephmilovic May 23, 2022
9de1167
add update and create comment
stephmilovic May 23, 2022
211c24b
support jira
stephmilovic May 23, 2022
5709c8c
labels
stephmilovic May 24, 2022
490e4bb
Merge branch 'main' into cases_webhook
stephmilovic May 24, 2022
06a8326
rm console logs and improve validators
stephmilovic May 25, 2022
33adfe5
fix type
stephmilovic May 25, 2022
b016221
fix triggers actions ui tests
stephmilovic May 25, 2022
9d2e57c
cleanup
stephmilovic May 26, 2022
96368bf
fix translations
stephmilovic May 26, 2022
4a2db85
Merge branch 'main' into cases_webhook
stephmilovic May 26, 2022
c41aca0
fix types
stephmilovic May 26, 2022
2bc71a9
Merge branch 'main' into cases_webhook
stephmilovic May 31, 2022
d651513
Merge branch 'main' into cases_webhook
stephmilovic Jun 21, 2022
93f68a9
update to new format
stephmilovic Jun 21, 2022
4966881
fix webhook tests
stephmilovic Jun 22, 2022
f1a8cf3
fix type
stephmilovic Jun 22, 2022
6abc85d
use mustache
stephmilovic Jun 29, 2022
c429071
fix!
stephmilovic Jun 29, 2022
2e982c5
add mustache to urls
stephmilovic Jun 30, 2022
6fb6da2
better form validation for mustache variables
stephmilovic Jun 30, 2022
4a23a68
Merge branch 'main' into cases_webhook
stephmilovic Jul 5, 2022
4f5c051
create comment optional
stephmilovic Jul 6, 2022
1760fd5
add tags and comment to test form
stephmilovic Jul 6, 2022
ac980a4
make steps
stephmilovic Jul 7, 2022
9790eaf
Merge branch 'main' into cases_webhook
stephmilovic Jul 7, 2022
8486691
hide cases webhook from rule actions
stephmilovic Jul 8, 2022
afaf444
fix i18n
stephmilovic Jul 8, 2022
35d1288
monina changes
stephmilovic Jul 8, 2022
c60ec73
test fixing
stephmilovic Jul 11, 2022
e7cfced
Merge branch 'main' into cases_webhook
stephmilovic Jul 12, 2022
98b876c
fix text field input
stephmilovic Jul 12, 2022
5cd8eb7
tests wip
stephmilovic Jul 12, 2022
60d5be3
finish webhook connectors test
stephmilovic Jul 12, 2022
18dd905
fix comment verbiage
stephmilovic Jul 12, 2022
eb15ea4
api tests
stephmilovic Jul 12, 2022
c35d2c2
action service tests
stephmilovic Jul 13, 2022
8d14410
starting api tests
stephmilovic Jul 13, 2022
1dd846e
api tests
stephmilovic Jul 14, 2022
36d35f4
ues
stephmilovic Jul 14, 2022
8a6d34a
add case connectors api tests
stephmilovic Jul 14, 2022
5f4d5fe
Merge branch 'main' into cases_webhook
stephmilovic Jul 14, 2022
a4df495
uncomment test files
stephmilovic Jul 14, 2022
a3a5a22
fix translations
stephmilovic Jul 14, 2022
2aa44a7
couple pr fixes
stephmilovic Jul 14, 2022
17507e9
shorter step titles
stephmilovic Jul 18, 2022
54ea733
Merge branch 'main' into cases_webhook
stephmilovic Jul 18, 2022
ffc0e80
move makeCaseStringy
stephmilovic Jul 18, 2022
a6aba99
more fix whoops
stephmilovic Jul 18, 2022
3061484
tests
stephmilovic Jul 18, 2022
fd478d7
pr changes
stephmilovic Jul 19, 2022
a71ddf7
pr changes 3
stephmilovic Jul 19, 2022
0902c49
fix type
stephmilovic Jul 19, 2022
7d322a0
pr changes 4
stephmilovic Jul 19, 2022
18db51d
remove only
stephmilovic Jul 20, 2022
73cabbf
Merge branch 'main' into cases_webhook
stephmilovic Jul 20, 2022
4ec23a4
fix func test
stephmilovic Jul 20, 2022
b0d344f
fix
stephmilovic Jul 20, 2022
bee21e7
incident > case
stephmilovic Jul 20, 2022
d870bec
Merge branch 'main' into cases_webhook
stephmilovic Jul 20, 2022
81595e8
update allows external.service.id in json, not required in url
stephmilovic Jul 21, 2022
ae487b1
response id to string
stephmilovic Jul 22, 2022
050ea48
fix user
stephmilovic Jul 22, 2022
c537288
getObjectValueByKeyAsString + url manipulation fix
stephmilovic Jul 22, 2022
008bdcb
add technical preview badge
stephmilovic Jul 22, 2022
90d69d1
fix test
stephmilovic Jul 22, 2022
cd86d5d
no async submit validation
stephmilovic Jul 22, 2022
a81c540
type fixes
stephmilovic Jul 22, 2022
7064dc6
fix merge
stephmilovic Jul 22, 2022
2dc98c3
fix i18n
stephmilovic Jul 22, 2022
f581b1d
translations and headers
stephmilovic Jul 22, 2022
3222259
fix user/pw validation
stephmilovic Jul 22, 2022
953e235
fix translations and tests
stephmilovic Jul 22, 2022
8aecdc3
fix type
stephmilovic Jul 22, 2022
3dc8e3e
Props to own interface
stephmilovic Jul 22, 2022
4b8b1e3
better error messaging
stephmilovic Jul 23, 2022
df21227
fix tests
stephmilovic Jul 23, 2022
f70e80e
betaBadgeProps > isExperimental
stephmilovic Jul 25, 2022
d675a89
rm console.logs
stephmilovic Jul 25, 2022
48be85f
resolve merge
stephmilovic Jul 25, 2022
2d22adc
fix badge
stephmilovic Jul 25, 2022
8693147
better errs
stephmilovic Jul 25, 2022
3d4fcc4
better url err
stephmilovic Jul 25, 2022
7a22fe5
err msg
stephmilovic Jul 25, 2022
9de3498
fix tests and nullable
stephmilovic Jul 25, 2022
da17ac1
add tests for bad url and bad protocol
stephmilovic Jul 25, 2022
31f1303
add test for showButtonTitle prop
stephmilovic Jul 25, 2022
73efd75
rm url err
stephmilovic Jul 25, 2022
2036e82
rm SecurityConnectorFeatureId
stephmilovic Jul 25, 2022
7d71f3a
revert escape mustache vars in url
stephmilovic Jul 25, 2022
9aee3f3
better esc
stephmilovic Jul 25, 2022
485e86f
revert triple to double
stephmilovic Jul 25, 2022
219018c
one more test
stephmilovic Jul 25, 2022
c416363
fix jest
stephmilovic Jul 25, 2022
0fd363f
Merge branch 'main' into cases_webhook
stephmilovic Jul 25, 2022
3dc2aaf
Merge branch 'main' into cases_webhook
kibanamachine Jul 25, 2022
92672bf
Merge branch 'main' into cases_webhook
kibanamachine Jul 26, 2022
b9a23fa
Merge branch 'main' into cases_webhook
kibanamachine Jul 26, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
/*
* 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.
*/

import { Logger } from '@kbn/core/server';
import { externalServiceMock, apiParams } from './mock';
import { ExternalService } from './types';
import { api } from './api';
let mockedLogger: jest.Mocked<Logger>;

describe('api', () => {
let externalService: jest.Mocked<ExternalService>;

beforeEach(() => {
externalService = externalServiceMock.create();
});

describe('create incident - cases', () => {
test('it creates an incident', async () => {
const params = { ...apiParams, externalId: null };
const res = await api.pushToService({
externalService,
params,
logger: mockedLogger,
});

expect(res).toEqual({
id: 'incident-1',
title: 'CK-1',
pushedDate: '2020-04-27T10:59:46.202Z',
url: 'https://siem-kibana.atlassian.net/browse/CK-1',
comments: [
{
commentId: 'case-comment-1',
pushedDate: '2020-04-27T10:59:46.202Z',
},
{
commentId: 'case-comment-2',
pushedDate: '2020-04-27T10:59:46.202Z',
},
],
});
});

test('it creates an incident without comments', async () => {
const params = { ...apiParams, externalId: null, comments: [] };
const res = await api.pushToService({
externalService,
params,
logger: mockedLogger,
});

expect(res).toEqual({
id: 'incident-1',
title: 'CK-1',
pushedDate: '2020-04-27T10:59:46.202Z',
url: 'https://siem-kibana.atlassian.net/browse/CK-1',
});
});

test('it calls createIncident correctly', async () => {
const params = { ...apiParams, incident: { ...apiParams.incident, externalId: null } };
await api.pushToService({ externalService, params, logger: mockedLogger });

expect(externalService.createIncident).toHaveBeenCalledWith({
incident: {
tags: ['kibana', 'elastic'],
description: 'Incident description',
title: 'Incident title',
},
});
expect(externalService.updateIncident).not.toHaveBeenCalled();
});

test('it calls createComment correctly', async () => {
const params = { ...apiParams, incident: { ...apiParams.incident, externalId: null } };
await api.pushToService({ externalService, params, logger: mockedLogger });
expect(externalService.createComment).toHaveBeenCalledTimes(2);
expect(externalService.createComment).toHaveBeenNthCalledWith(1, {
incidentId: 'incident-1',
comment: {
commentId: 'case-comment-1',
comment: 'A comment',
},
});

expect(externalService.createComment).toHaveBeenNthCalledWith(2, {
incidentId: 'incident-1',
comment: {
commentId: 'case-comment-2',
comment: 'Another comment',
},
});
});
});

describe('update incident', () => {
test('it updates an incident', async () => {
const res = await api.pushToService({
externalService,
params: apiParams,
logger: mockedLogger,
});

expect(res).toEqual({
id: 'incident-1',
title: 'CK-1',
pushedDate: '2020-04-27T10:59:46.202Z',
url: 'https://siem-kibana.atlassian.net/browse/CK-1',
comments: [
{
commentId: 'case-comment-1',
pushedDate: '2020-04-27T10:59:46.202Z',
},
{
commentId: 'case-comment-2',
pushedDate: '2020-04-27T10:59:46.202Z',
},
],
});
});

test('it updates an incident without comments', async () => {
const params = { ...apiParams, comments: [] };
const res = await api.pushToService({
externalService,
params,
logger: mockedLogger,
});

expect(res).toEqual({
id: 'incident-1',
title: 'CK-1',
pushedDate: '2020-04-27T10:59:46.202Z',
url: 'https://siem-kibana.atlassian.net/browse/CK-1',
});
});

test('it calls updateIncident correctly', async () => {
const params = { ...apiParams };
await api.pushToService({ externalService, params, logger: mockedLogger });

expect(externalService.updateIncident).toHaveBeenCalledWith({
incidentId: 'incident-3',
incident: {
tags: ['kibana', 'elastic'],
description: 'Incident description',
title: 'Incident title',
},
});
expect(externalService.createIncident).not.toHaveBeenCalled();
});

test('it calls updateIncident correctly without mapping', async () => {
const params = { ...apiParams };
await api.pushToService({ externalService, params, logger: mockedLogger });

expect(externalService.updateIncident).toHaveBeenCalledWith({
incidentId: 'incident-3',
incident: {
description: 'Incident description',
title: 'Incident title',
tags: ['kibana', 'elastic'],
},
});
expect(externalService.createIncident).not.toHaveBeenCalled();
});

test('it calls createComment correctly', async () => {
const params = { ...apiParams };
await api.pushToService({ externalService, params, logger: mockedLogger });
expect(externalService.createComment).toHaveBeenCalledTimes(2);
expect(externalService.createComment).toHaveBeenNthCalledWith(1, {
incidentId: 'incident-1',
comment: {
commentId: 'case-comment-1',
comment: 'A comment',
},
});

expect(externalService.createComment).toHaveBeenNthCalledWith(2, {
incidentId: 'incident-1',
comment: {
commentId: 'case-comment-2',
comment: 'Another comment',
},
});
});

test('it calls createComment correctly without mapping', async () => {
const params = { ...apiParams };
await api.pushToService({ externalService, params, logger: mockedLogger });
expect(externalService.createComment).toHaveBeenCalledTimes(2);
expect(externalService.createComment).toHaveBeenNthCalledWith(1, {
incidentId: 'incident-1',
comment: {
commentId: 'case-comment-1',
comment: 'A comment',
},
});

expect(externalService.createComment).toHaveBeenNthCalledWith(2, {
incidentId: 'incident-1',
comment: {
commentId: 'case-comment-2',
comment: 'Another comment',
},
});
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* 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.
*/

import {
ExternalServiceApi,
Incident,
PushToServiceApiHandlerArgs,
PushToServiceResponse,
} from './types';

const pushToServiceHandler = async ({
externalService,
params,
}: PushToServiceApiHandlerArgs): Promise<PushToServiceResponse> => {
const {
incident: { externalId, ...rest },
comments,
} = params;
const incident: Incident = rest;
let res: PushToServiceResponse;

if (externalId != null) {
res = await externalService.updateIncident({
incidentId: externalId,
incident,
});
} else {
res = await externalService.createIncident({
incident,
});
}

if (comments && Array.isArray(comments) && comments.length > 0) {
res.comments = [];
for (const currentComment of comments) {
if (!currentComment.comment) {
continue;
}
await externalService.createComment({
incidentId: res.id,
comment: currentComment,
});
res.comments = [
...(res.comments ?? []),
{
commentId: currentComment.commentId,
pushedDate: res.pushedDate,
},
];
}
}

return res;
};
export const api: ExternalServiceApi = {
pushToService: pushToServiceHandler,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/*
* 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.
*/

import { curry } from 'lodash';
import { schema } from '@kbn/config-schema';
import { Logger } from '@kbn/core/server';
import {
CasesWebhookActionParamsType,
CasesWebhookExecutorResultData,
CasesWebhookPublicConfigurationType,
CasesWebhookSecretConfigurationType,
ExecutorParams,
ExecutorSubActionPushParams,
} from './types';
import { ActionType, ActionTypeExecutorOptions, ActionTypeExecutorResult } from '../../types';
import { ActionsConfigurationUtilities } from '../../actions_config';
import { createExternalService } from './service';
import {
ExecutorParamsSchema,
ExternalIncidentServiceConfiguration,
ExternalIncidentServiceSecretConfiguration,
} from './schema';
import { api } from './api';
import { validate } from './validators';
import * as i18n from './translations';

const supportedSubActions: string[] = ['pushToService'];
export type ActionParamsType = CasesWebhookActionParamsType;
export const ActionTypeId = '.cases-webhook';
// action type definition
export function getActionType({
logger,
configurationUtilities,
}: {
logger: Logger;
configurationUtilities: ActionsConfigurationUtilities;
}): ActionType<
CasesWebhookPublicConfigurationType,
CasesWebhookSecretConfigurationType,
ExecutorParams,
CasesWebhookExecutorResultData
> {
return {
id: ActionTypeId,
minimumLicenseRequired: 'gold',
name: i18n.NAME,
validate: {
config: schema.object(ExternalIncidentServiceConfiguration, {
validate: curry(validate.config)(configurationUtilities),
}),
secrets: schema.object(ExternalIncidentServiceSecretConfiguration, {
validate: curry(validate.secrets),
}),
params: ExecutorParamsSchema,
connector: validate.connector,
},
executor: curry(executor)({ logger, configurationUtilities }),
};
}

// action executor
export async function executor(
{
logger,
configurationUtilities,
}: { logger: Logger; configurationUtilities: ActionsConfigurationUtilities },
execOptions: ActionTypeExecutorOptions<
CasesWebhookPublicConfigurationType,
CasesWebhookSecretConfigurationType,
CasesWebhookActionParamsType
>
): Promise<ActionTypeExecutorResult<CasesWebhookExecutorResultData>> {
const actionId = execOptions.actionId;
const { subAction, subActionParams } = execOptions.params;
let data: CasesWebhookExecutorResultData | undefined;

const externalService = createExternalService(
actionId,
{
config: execOptions.config,
secrets: execOptions.secrets,
},
logger,
configurationUtilities
);

if (!api[subAction]) {
ymao1 marked this conversation as resolved.
Show resolved Hide resolved
const errorMessage = `[Action][ExternalService] Unsupported subAction type ${subAction}.`;
logger.error(errorMessage);
throw new Error(errorMessage);
}

if (!supportedSubActions.includes(subAction)) {
const errorMessage = `[Action][ExternalService] subAction ${subAction} not implemented.`;
logger.error(errorMessage);
throw new Error(errorMessage);
}

if (subAction === 'pushToService') {
const pushToServiceParams = subActionParams as ExecutorSubActionPushParams;
data = await api.pushToService({
externalService,
params: pushToServiceParams,
logger,
});

logger.debug(`response push to service for case id: ${data.id}`);
}

return { status: 'ok', data, actionId };
}
Loading