mirror of
https://github.com/elastic/kibana.git
synced 2025-06-27 18:51:07 -04:00
Implement functionality to add observables, procedures and custom fields to alerts for TheHive (#207255)
## Summary - Added a toggle to retain the severity from the rule. When enabled, alerts generated from the rule will inherit its severity; otherwise, users must manually select a severity level from the dropdown. - Added a template selection menu with predefined basic templates. These templates come with preset configurations, including observables and procedures, which automatically populate the Body field upon selection. Users also have the option to modify an existing template or create a custom one using the `Custom Template` option. ## Screenshots   ### Checklist - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md) - [x] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [x] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [x] This was checked for breaking HTTP API changes, and any breaking changes have been approved by the breaking-change committee. The `release_note:breaking` label should be applied in these situations. - [x] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [ ] The PR description includes the appropriate Release Notes section, and the correct `release_note:*` label is applied per the [guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
parent
f857b34115
commit
884e51ae49
14 changed files with 413 additions and 27 deletions
|
@ -70,6 +70,10 @@ Description
|
|||
Severity
|
||||
: The severity of the incident: `LOW`, `MEDIUM`, `HIGH` or `CRITICAL`.
|
||||
|
||||
::::{note}
|
||||
While creating an alert, use the Keep severity from rule toggle to create an alert with the rule's severity. If the rule does not have a defined severity, the alert will have the default MEDIUM severity.
|
||||
::::
|
||||
|
||||
TLP
|
||||
: The traffic light protocol designation for the incident: `CLEAR`, `GREEN`, `AMBER`, `AMBER+STRICT` or `RED`.
|
||||
|
||||
|
@ -88,6 +92,27 @@ Source
|
|||
Source reference
|
||||
: A source reference for the alert.
|
||||
|
||||
Body
|
||||
: A Json payload specifying additional parameter, such as observables and procedures. It can be populated using a predefined template or customized using the `Custom Template` option. For example:
|
||||
|
||||
```json
|
||||
{
|
||||
"observables": [
|
||||
{
|
||||
"dataType": "url",
|
||||
"data": "http://example.org"
|
||||
}
|
||||
],
|
||||
"procedures": [
|
||||
{
|
||||
"patternId": "TA0001",
|
||||
"occurDate": 1640000000000,
|
||||
"tactic": "tactic-name"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Connector networking configuration [thehive-connector-networking-configuration]
|
||||
|
||||
Use the [Action configuration settings](/reference/configuration-reference/alerting-settings.md#action-settings) to customize connector networking configurations, such as proxies, certificates, or TLS settings. You can set configurations that apply to all your connectors or use `xpack.actions.customHostSettings` to set per-host configurations.
|
||||
|
|
|
@ -37801,6 +37801,44 @@ Object {
|
|||
"presence": "optional",
|
||||
},
|
||||
"keys": Object {
|
||||
"body": Object {
|
||||
"flags": Object {
|
||||
"default": null,
|
||||
"error": [Function],
|
||||
"presence": "optional",
|
||||
},
|
||||
"matches": Array [
|
||||
Object {
|
||||
"schema": Object {
|
||||
"flags": Object {
|
||||
"error": [Function],
|
||||
},
|
||||
"rules": Array [
|
||||
Object {
|
||||
"args": Object {
|
||||
"method": [Function],
|
||||
},
|
||||
"name": "custom",
|
||||
},
|
||||
],
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"schema": Object {
|
||||
"allow": Array [
|
||||
null,
|
||||
],
|
||||
"flags": Object {
|
||||
"error": [Function],
|
||||
"only": true,
|
||||
},
|
||||
"type": "any",
|
||||
},
|
||||
},
|
||||
],
|
||||
"type": "alternatives",
|
||||
},
|
||||
"description": Object {
|
||||
"flags": Object {
|
||||
"error": [Function],
|
||||
|
@ -37815,6 +37853,38 @@ Object {
|
|||
],
|
||||
"type": "string",
|
||||
},
|
||||
"isRuleSeverity": Object {
|
||||
"flags": Object {
|
||||
"default": null,
|
||||
"error": [Function],
|
||||
"presence": "optional",
|
||||
},
|
||||
"matches": Array [
|
||||
Object {
|
||||
"schema": Object {
|
||||
"flags": Object {
|
||||
"default": false,
|
||||
"error": [Function],
|
||||
"presence": "optional",
|
||||
},
|
||||
"type": "boolean",
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"schema": Object {
|
||||
"allow": Array [
|
||||
null,
|
||||
],
|
||||
"flags": Object {
|
||||
"error": [Function],
|
||||
"only": true,
|
||||
},
|
||||
"type": "any",
|
||||
},
|
||||
},
|
||||
],
|
||||
"type": "alternatives",
|
||||
},
|
||||
"severity": Object {
|
||||
"flags": Object {
|
||||
"default": null,
|
||||
|
|
|
@ -55,8 +55,10 @@ export const ExecutorSubActionCreateAlertParamsSchema = schema.object({
|
|||
source: schema.string(),
|
||||
sourceRef: schema.string(),
|
||||
severity: schema.nullable(schema.number({ defaultValue: TheHiveSeverity.MEDIUM })),
|
||||
isRuleSeverity: schema.nullable(schema.boolean({ defaultValue: false })),
|
||||
tlp: schema.nullable(schema.number({ defaultValue: TheHiveTLP.AMBER })),
|
||||
tags: schema.nullable(schema.arrayOf(schema.string())),
|
||||
body: schema.nullable(schema.string()),
|
||||
});
|
||||
|
||||
export const ExecutorParamsSchema = schema.oneOf([
|
||||
|
|
|
@ -9,7 +9,7 @@ import React from 'react';
|
|||
import { fireEvent, render } from '@testing-library/react';
|
||||
import { ActionConnector } from '@kbn/triggers-actions-ui-plugin/public/types';
|
||||
import TheHiveParamsFields from './params';
|
||||
import { SUB_ACTION } from '../../../common/thehive/constants';
|
||||
import { SUB_ACTION, TheHiveSeverity } from '../../../common/thehive/constants';
|
||||
import { ExecutorParams, ExecutorSubActionPushParams } from '../../../common/thehive/types';
|
||||
|
||||
describe('TheHiveParamsFields renders', () => {
|
||||
|
@ -69,7 +69,7 @@ describe('TheHiveParamsFields renders', () => {
|
|||
'subActionParams',
|
||||
{
|
||||
tlp: 2,
|
||||
severity: 2,
|
||||
severity: TheHiveSeverity.MEDIUM,
|
||||
tags: [],
|
||||
sourceRef: '{{alert.uuid}}',
|
||||
},
|
||||
|
|
|
@ -9,7 +9,7 @@ import React, { useState, useEffect, useRef, useMemo } from 'react';
|
|||
import { ActionParamsProps, ActionConnectorMode } from '@kbn/triggers-actions-ui-plugin/public';
|
||||
import { EuiFormRow, EuiSelect } from '@elastic/eui';
|
||||
import { eventActionOptions } from './constants';
|
||||
import { SUB_ACTION } from '../../../common/thehive/constants';
|
||||
import { SUB_ACTION, TheHiveSeverity } from '../../../common/thehive/constants';
|
||||
import { ExecutorParams } from '../../../common/thehive/types';
|
||||
import { TheHiveParamsAlertFields } from './params_alert';
|
||||
import { TheHiveParamsCaseFields } from './params_case';
|
||||
|
@ -80,7 +80,7 @@ const TheHiveParamsFields: React.FunctionComponent<ActionParamsProps<ExecutorPar
|
|||
eventActionType === SUB_ACTION.CREATE_ALERT
|
||||
? {
|
||||
tlp: 2,
|
||||
severity: 2,
|
||||
severity: TheHiveSeverity.MEDIUM,
|
||||
tags: [],
|
||||
sourceRef: isTest ? undefined : '{{alert.uuid}}',
|
||||
}
|
||||
|
@ -123,6 +123,7 @@ const TheHiveParamsFields: React.FunctionComponent<ActionParamsProps<ExecutorPar
|
|||
index={index}
|
||||
errors={errors}
|
||||
messageVariables={messageVariables}
|
||||
executionMode={executionMode}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
|
|
@ -18,10 +18,12 @@ describe('TheHiveParamsFields renders', () => {
|
|||
description: 'description test',
|
||||
tlp: 2,
|
||||
severity: 2,
|
||||
isRuleSeverity: false,
|
||||
tags: ['test1'],
|
||||
source: 'source test',
|
||||
type: 'sourceType test',
|
||||
sourceRef: 'sourceRef test',
|
||||
body: null,
|
||||
};
|
||||
const actionParams: ExecutorParams = {
|
||||
subAction: SUB_ACTION.CREATE_ALERT,
|
||||
|
|
|
@ -10,8 +10,10 @@ import {
|
|||
TextFieldWithMessageVariables,
|
||||
TextAreaWithMessageVariables,
|
||||
ActionParamsProps,
|
||||
JsonEditorWithMessageVariables,
|
||||
ActionConnectorMode,
|
||||
} from '@kbn/triggers-actions-ui-plugin/public';
|
||||
import { EuiFormRow, EuiSelect, EuiComboBox } from '@elastic/eui';
|
||||
import { EuiFormRow, EuiSelect, EuiComboBox, EuiSwitch } from '@elastic/eui';
|
||||
import { ExecutorParams, ExecutorSubActionCreateAlertParams } from '../../../common/thehive/types';
|
||||
import { severityOptions, tlpOptions } from './constants';
|
||||
import * as translations from './translations';
|
||||
|
@ -22,6 +24,7 @@ export const TheHiveParamsAlertFields: React.FC<ActionParamsProps<ExecutorParams
|
|||
index,
|
||||
errors,
|
||||
messageVariables,
|
||||
executionMode,
|
||||
}) => {
|
||||
const alert = useMemo(
|
||||
() =>
|
||||
|
@ -33,12 +36,14 @@ export const TheHiveParamsAlertFields: React.FC<ActionParamsProps<ExecutorParams
|
|||
} as unknown as ExecutorSubActionCreateAlertParams),
|
||||
[actionParams.subActionParams]
|
||||
);
|
||||
const isTest = executionMode === ActionConnectorMode.Test;
|
||||
|
||||
const [severity, setSeverity] = useState(alert.severity ?? severityOptions[1].value);
|
||||
const [tlp, setTlp] = useState(alert.tlp ?? tlpOptions[2].value);
|
||||
const [selectedOptions, setSelected] = useState<Array<{ label: string }>>(
|
||||
alert.tags?.map((tag) => ({ label: tag })) ?? []
|
||||
);
|
||||
const [isRuleSeverity, setIsRuleSeverity] = useState<boolean>(Boolean(alert.isRuleSeverity));
|
||||
|
||||
const onCreateOption = (searchValue: string) => {
|
||||
setSelected([...selectedOptions, { label: searchValue }]);
|
||||
|
@ -149,22 +154,46 @@ export const TheHiveParamsAlertFields: React.FC<ActionParamsProps<ExecutorParams
|
|||
}}
|
||||
errors={errors['createAlertParam.sourceRef'] as string[]}
|
||||
/>
|
||||
<EuiFormRow fullWidth label={translations.SEVERITY_LABEL}>
|
||||
<EuiSelect
|
||||
fullWidth
|
||||
data-test-subj="severitySelectInput"
|
||||
value={severity}
|
||||
options={severityOptions}
|
||||
onChange={(e) => {
|
||||
editAction(
|
||||
'subActionParams',
|
||||
{ ...alert, severity: parseInt(e.target.value, 10) },
|
||||
index
|
||||
);
|
||||
setSeverity(parseInt(e.target.value, 10));
|
||||
}}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
{!isTest && Boolean(isRuleSeverity) && (
|
||||
<EuiFormRow fullWidth>
|
||||
<EuiSwitch
|
||||
label={translations.IS_RULE_SEVERITY_LABEL}
|
||||
checked={Boolean(isRuleSeverity)}
|
||||
compressed={true}
|
||||
data-test-subj="rule-severity-toggle"
|
||||
onChange={(e) => {
|
||||
setIsRuleSeverity(e.target.checked);
|
||||
editAction(
|
||||
'subActionParams',
|
||||
{
|
||||
...alert,
|
||||
isRuleSeverity: e.target.checked,
|
||||
},
|
||||
index
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
)}
|
||||
{!Boolean(isRuleSeverity) && (
|
||||
<EuiFormRow fullWidth label={translations.SEVERITY_LABEL}>
|
||||
<EuiSelect
|
||||
fullWidth
|
||||
data-test-subj="severitySelectInput"
|
||||
disabled={isRuleSeverity}
|
||||
value={severity}
|
||||
options={severityOptions}
|
||||
onChange={(e) => {
|
||||
editAction(
|
||||
'subActionParams',
|
||||
{ ...alert, severity: parseInt(e.target.value, 10) },
|
||||
index
|
||||
);
|
||||
setSeverity(parseInt(e.target.value, 10));
|
||||
}}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
)}
|
||||
<EuiFormRow fullWidth label={translations.TLP_LABEL}>
|
||||
<EuiSelect
|
||||
fullWidth
|
||||
|
@ -187,6 +216,26 @@ export const TheHiveParamsAlertFields: React.FC<ActionParamsProps<ExecutorParams
|
|||
noSuggestions
|
||||
/>
|
||||
</EuiFormRow>
|
||||
{alert.body != null && (
|
||||
<JsonEditorWithMessageVariables
|
||||
messageVariables={messageVariables}
|
||||
paramsProperty={'body'}
|
||||
inputTargetValue={alert.body}
|
||||
label={translations.BODY_LABEL}
|
||||
ariaLabel={translations.BODY_DESCRIPTION}
|
||||
errors={errors.body as string[]}
|
||||
onDocumentsChange={(json: string) =>
|
||||
editAction('subActionParams', { ...alert, body: json }, index)
|
||||
}
|
||||
dataTestSubj="thehive-body"
|
||||
onBlur={() => {
|
||||
if (!alert.body) {
|
||||
editAction('subActionParams', { ...alert, body: null }, index);
|
||||
}
|
||||
}}
|
||||
isOptionalField
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -92,6 +92,19 @@ describe('thehive createAlert action params validation', () => {
|
|||
type: 'type test',
|
||||
source: 'source test',
|
||||
sourceRef: 'source reference test',
|
||||
body: JSON.stringify(
|
||||
{
|
||||
observables: [
|
||||
{
|
||||
dataType: 'ip',
|
||||
data: '127.0.0.1',
|
||||
tags: ['source.ip'],
|
||||
},
|
||||
],
|
||||
},
|
||||
null,
|
||||
2
|
||||
),
|
||||
},
|
||||
comments: [],
|
||||
};
|
||||
|
|
|
@ -60,6 +60,13 @@ export const TLP_LABEL = i18n.translate(
|
|||
}
|
||||
);
|
||||
|
||||
export const IS_RULE_SEVERITY_LABEL = i18n.translate(
|
||||
'xpack.stackConnectors.components.thehive.isRuleSeverityToggleLabel',
|
||||
{
|
||||
defaultMessage: 'Use severity assigned to the rule',
|
||||
}
|
||||
);
|
||||
|
||||
export const SEVERITY_LABEL = i18n.translate(
|
||||
'xpack.stackConnectors.components.thehive.severitySelectFieldLabel',
|
||||
{
|
||||
|
@ -102,6 +109,34 @@ export const SOURCE_REF_LABEL = i18n.translate(
|
|||
}
|
||||
);
|
||||
|
||||
export const TEMPLATE_LABEL = i18n.translate(
|
||||
'xpack.stackConnectors.components.thehive.templateFieldLabel',
|
||||
{
|
||||
defaultMessage: 'Template',
|
||||
}
|
||||
);
|
||||
|
||||
export const BODY_LABEL = i18n.translate(
|
||||
'xpack.stackConnectors.components.thehive.bodyFieldLabel',
|
||||
{
|
||||
defaultMessage: 'Body',
|
||||
}
|
||||
);
|
||||
|
||||
export const BODY_DESCRIPTION = i18n.translate(
|
||||
'xpack.stackConnectors.components.thehive.bodyFieldDescription',
|
||||
{
|
||||
defaultMessage: 'Code Editor',
|
||||
}
|
||||
);
|
||||
|
||||
export const SELECT_BODY_TEMPLATE_POPOVER_BUTTON = i18n.translate(
|
||||
'xpack.stackConnectors.components.thehive.selectBodyTemplatePopoverButton',
|
||||
{
|
||||
defaultMessage: 'Select body template',
|
||||
}
|
||||
);
|
||||
|
||||
export const TITLE_REQUIRED = i18n.translate(
|
||||
'xpack.stackConnectors.components.thehive.requiredTitleText',
|
||||
{
|
||||
|
|
|
@ -22,6 +22,7 @@ import {
|
|||
} from '../../../common/thehive/schema';
|
||||
import { THEHIVE_CONNECTOR_ID, THEHIVE_TITLE } from '../../../common/thehive/constants';
|
||||
import type { TheHiveConfig, TheHiveSecrets } from '../../../common/thehive/types';
|
||||
import { renderParameterTemplates } from './render';
|
||||
|
||||
export type TheHiveConnectorType = SubActionConnectorType<TheHiveConfig, TheHiveSecrets>;
|
||||
|
||||
|
@ -41,6 +42,7 @@ export function getConnectorType(): TheHiveConnectorType {
|
|||
config: TheHiveConfigSchema,
|
||||
secrets: TheHiveSecretsSchema,
|
||||
},
|
||||
renderParameterTemplates,
|
||||
validators: [{ type: ValidatorType.CONFIG, validator: urlAllowListValidator('url') }],
|
||||
};
|
||||
}
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* 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 { loggingSystemMock } from '@kbn/core/server/mocks';
|
||||
import { renderParameterTemplates } from './render';
|
||||
import { SUB_ACTION } from '../../../common/thehive/constants';
|
||||
import Mustache from 'mustache';
|
||||
|
||||
const params = {
|
||||
subAction: SUB_ACTION.CREATE_ALERT,
|
||||
subActionParams: {
|
||||
title: 'title',
|
||||
description: 'description',
|
||||
type: 'type',
|
||||
source: 'source',
|
||||
sourceRef: '{{alert.uuid}}',
|
||||
tlp: 2,
|
||||
severity: 1,
|
||||
isRuleSeverity: true,
|
||||
body: '{"observables":[{"datatype":"url","data":"{{url}}"}],"tags":["test"]}',
|
||||
},
|
||||
};
|
||||
|
||||
const variables = {
|
||||
url: 'https://example.com',
|
||||
context: { rule: { severity: 'high' } },
|
||||
alert: { uuid: 'test123' },
|
||||
};
|
||||
const logger = loggingSystemMock.createLogger();
|
||||
|
||||
describe('TheHive - renderParameterTemplates', () => {
|
||||
it('should rendered subActionParams with variables', () => {
|
||||
const result = renderParameterTemplates(logger, params, variables);
|
||||
|
||||
expect(result.subActionParams).toEqual({
|
||||
title: 'title',
|
||||
description: 'description',
|
||||
type: 'type',
|
||||
source: 'source',
|
||||
sourceRef: variables.alert.uuid,
|
||||
tlp: 2,
|
||||
severity: 3,
|
||||
isRuleSeverity: true,
|
||||
body: `{"observables":[{"datatype":"url","data":"${variables.url}"}],"tags":["test"]}`,
|
||||
});
|
||||
});
|
||||
|
||||
it('should not use rule severity if isRuleSeverity is false', () => {
|
||||
const paramswithoutRuleSeverity = {
|
||||
...params,
|
||||
subActionParams: { ...params.subActionParams, isRuleSeverity: false },
|
||||
};
|
||||
const result = renderParameterTemplates(logger, paramswithoutRuleSeverity, variables);
|
||||
|
||||
expect(result.subActionParams).toEqual({
|
||||
title: 'title',
|
||||
description: 'description',
|
||||
type: 'type',
|
||||
source: 'source',
|
||||
sourceRef: variables.alert.uuid,
|
||||
tlp: 2,
|
||||
severity: 1,
|
||||
isRuleSeverity: false,
|
||||
body: `{"observables":[{"datatype":"url","data":"${variables.url}"}],"tags":["test"]}`,
|
||||
});
|
||||
});
|
||||
|
||||
it('should render error body', () => {
|
||||
const errorMessage = 'test error';
|
||||
jest.spyOn(Mustache, 'render').mockImplementation(() => {
|
||||
throw new Error(errorMessage);
|
||||
});
|
||||
const result = renderParameterTemplates(logger, params, variables);
|
||||
expect(result.subActionParams).toEqual({
|
||||
body: 'error rendering mustache template "{"observables":[{"datatype":"url","data":"{{url}}"}],"tags":["test"]}": test error',
|
||||
description: 'error rendering mustache template "description": test error',
|
||||
severity: 2,
|
||||
isRuleSeverity: true,
|
||||
source: 'error rendering mustache template "source": test error',
|
||||
sourceRef: 'error rendering mustache template "{{alert.uuid}}": test error',
|
||||
title: 'error rendering mustache template "title": test error',
|
||||
tlp: 2,
|
||||
type: 'error rendering mustache template "type": test error',
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* 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 type { ExecutorParams } from '@kbn/actions-plugin/server/sub_action_framework/types';
|
||||
import {
|
||||
renderMustacheObject,
|
||||
renderMustacheString,
|
||||
} from '@kbn/actions-plugin/server/lib/mustache_renderer';
|
||||
import type { RenderParameterTemplates } from '@kbn/actions-plugin/server/types';
|
||||
import { SUB_ACTION } from '../../../common/thehive/constants';
|
||||
|
||||
function mapSeverity(severity: string): number {
|
||||
switch (severity) {
|
||||
case 'low':
|
||||
return 1;
|
||||
case 'medium':
|
||||
return 2;
|
||||
case 'high':
|
||||
return 3;
|
||||
case 'critical':
|
||||
return 4;
|
||||
default:
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
export const renderParameterTemplates: RenderParameterTemplates<ExecutorParams> = (
|
||||
logger,
|
||||
params,
|
||||
variables
|
||||
) => {
|
||||
if (params?.subAction === SUB_ACTION.PUSH_TO_SERVICE) {
|
||||
return renderMustacheObject(logger, params, variables);
|
||||
} else {
|
||||
return {
|
||||
...params,
|
||||
subActionParams: {
|
||||
...renderMustacheObject(logger, params.subActionParams, variables),
|
||||
severity:
|
||||
params.subActionParams.isRuleSeverity === true
|
||||
? mapSeverity(
|
||||
renderMustacheString(logger, '{{context.rule.severity}}', variables, 'json')
|
||||
)
|
||||
: params.subActionParams.severity,
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
|
@ -390,7 +390,7 @@ describe('TheHiveConnector', () => {
|
|||
papLabel: 'AMBER',
|
||||
follow: true,
|
||||
customFields: [],
|
||||
observableCount: 0,
|
||||
observableCount: 1,
|
||||
status: 'New',
|
||||
stage: 'New',
|
||||
extraData: {},
|
||||
|
@ -413,10 +413,34 @@ describe('TheHiveConnector', () => {
|
|||
source: 'alert source',
|
||||
sourceRef: 'test123',
|
||||
severity: 1,
|
||||
isRuleSeverity: false,
|
||||
tlp: 2,
|
||||
tags: ['tag1', 'tag2'],
|
||||
body: JSON.stringify(
|
||||
{
|
||||
observables: [
|
||||
{
|
||||
dataType: 'url',
|
||||
data: 'http://example.com',
|
||||
tags: ['url'],
|
||||
},
|
||||
],
|
||||
procedures: [
|
||||
{
|
||||
patternId: 'T1132',
|
||||
occurDate: 1640000000000,
|
||||
tactic: 'command-and-control',
|
||||
},
|
||||
],
|
||||
},
|
||||
null,
|
||||
2
|
||||
),
|
||||
};
|
||||
|
||||
const { body, isRuleSeverity, ...restOfAlert } = alert;
|
||||
const expectedAlertBody = { ...JSON.parse(body || '{}'), ...restOfAlert };
|
||||
|
||||
it('TheHive API call is successful with correct parameters', async () => {
|
||||
await connector.createAlert(alert, connectorUsageCollector);
|
||||
expect(mockRequest).toBeCalledTimes(1);
|
||||
|
@ -425,7 +449,7 @@ describe('TheHiveConnector', () => {
|
|||
url: 'https://example.com/api/v1/alert',
|
||||
method: 'post',
|
||||
responseSchema: TheHiveCreateAlertResponseSchema,
|
||||
data: alert,
|
||||
data: expectedAlertBody,
|
||||
headers: {
|
||||
Authorization: 'Bearer test123',
|
||||
'X-Organisation': null,
|
||||
|
@ -439,9 +463,9 @@ describe('TheHiveConnector', () => {
|
|||
// @ts-ignore
|
||||
connector.request = mockError;
|
||||
|
||||
await expect(connector.createAlert(alert, connectorUsageCollector)).rejects.toThrow(
|
||||
'API Error'
|
||||
);
|
||||
await expect(
|
||||
connector.createAlert(expectedAlertBody, connectorUsageCollector)
|
||||
).rejects.toThrow('API Error');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import type { ServiceParams } from '@kbn/actions-plugin/server';
|
||||
import { CaseConnector } from '@kbn/actions-plugin/server';
|
||||
import type { AxiosError } from 'axios';
|
||||
|
@ -154,15 +155,35 @@ export class TheHiveConnector extends CaseConnector<
|
|||
return res.data;
|
||||
}
|
||||
|
||||
private formatAlertBody(alert: ExecutorSubActionCreateAlertParams) {
|
||||
try {
|
||||
const { body, isRuleSeverity, ...restOfAlert } = alert;
|
||||
const bodyJson = JSON.parse(body || '{}');
|
||||
const mergedAlertBody = { ...bodyJson, ...restOfAlert };
|
||||
|
||||
return mergedAlertBody;
|
||||
} catch (err) {
|
||||
throw new Error(
|
||||
i18n.translate('xpack.stackConnectors.thehive.alertBodyParsingError', {
|
||||
defaultMessage: 'Error parsing alert body for thehive: {err}',
|
||||
values: {
|
||||
err: err.toString(),
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public async createAlert(
|
||||
alert: ExecutorSubActionCreateAlertParams,
|
||||
connectorUsageCollector: ConnectorUsageCollector
|
||||
) {
|
||||
const mergedAlertBody = this.formatAlertBody(alert);
|
||||
await this.request(
|
||||
{
|
||||
method: 'post',
|
||||
url: `${this.url}/api/${API_VERSION}/alert`,
|
||||
data: alert,
|
||||
data: mergedAlertBody,
|
||||
headers: this.getAuthHeaders(),
|
||||
responseSchema: TheHiveCreateAlertResponseSchema,
|
||||
},
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue