diff --git a/docs/management/connectors/action-types/thehive.asciidoc b/docs/management/connectors/action-types/thehive.asciidoc index e847d0b621eca..fdd6f975816ba 100644 --- a/docs/management/connectors/action-types/thehive.asciidoc +++ b/docs/management/connectors/action-types/thehive.asciidoc @@ -65,6 +65,7 @@ Additional comments:: Additional information about the incident. Type:: The type of alert. Source:: The source of the alert. Source reference:: A source reference for the alert. +Template:: Templates provide predefined configurations that include observables and procedures. When you select a template, its corresponding values will automatically populate the `Body` field. You can also use the `Build Your Own` option to create a custom template. [float] [[thehive-connector-networking-configuration]] diff --git a/x-pack/platform/plugins/shared/stack_connectors/common/thehive/schema.ts b/x-pack/platform/plugins/shared/stack_connectors/common/thehive/schema.ts index e880ca900591a..0e9ad5d81ad53 100644 --- a/x-pack/platform/plugins/shared/stack_connectors/common/thehive/schema.ts +++ b/x-pack/platform/plugins/shared/stack_connectors/common/thehive/schema.ts @@ -57,6 +57,8 @@ export const ExecutorSubActionCreateAlertParamsSchema = schema.object({ severity: schema.nullable(schema.number({ defaultValue: TheHiveSeverity.MEDIUM })), tlp: schema.nullable(schema.number({ defaultValue: TheHiveTLP.AMBER })), tags: schema.nullable(schema.arrayOf(schema.string())), + template: schema.number(), + body: schema.nullable(schema.string()), }); export const ExecutorParamsSchema = schema.oneOf([ diff --git a/x-pack/platform/plugins/shared/stack_connectors/public/connector_types/thehive/constants.ts b/x-pack/platform/plugins/shared/stack_connectors/public/connector_types/thehive/constants.ts index 6ab86cdbe7f1b..6c542b675efce 100644 --- a/x-pack/platform/plugins/shared/stack_connectors/public/connector_types/thehive/constants.ts +++ b/x-pack/platform/plugins/shared/stack_connectors/public/connector_types/thehive/constants.ts @@ -112,3 +112,117 @@ export const tlpOptions = [ }), }, ]; + +export const templateOptions = [ + { + value: 0, + text: i18n.translate( + 'xpack.stackConnectors.components.thehive.eventSelectTemplate1OptionLabel', + { + defaultMessage: 'Build Your Own', + } + ), + }, + { + value: 1, + text: i18n.translate( + 'xpack.stackConnectors.components.thehive.eventSelectTemplate2OptionLabel', + { + defaultMessage: 'Compromised User Account Investigation', + } + ), + }, + { + value: 2, + text: i18n.translate( + 'xpack.stackConnectors.components.thehive.eventSelectTemplate3OptionLabel', + { + defaultMessage: 'Malicious File Analysis', + } + ), + }, + { + value: 3, + text: i18n.translate( + 'xpack.stackConnectors.components.thehive.eventSelectTemplate4OptionLabel', + { + defaultMessage: 'Suspicious Network Activity', + } + ), + }, +]; + +export const bodyOptions = [ + '{}', + '{\r\n "observables": [\r\n {\r\n "dataType": "mail",\r\n "data": "{{#context.alerts}}{{user.email}}{{/context.alerts}}",\r\n "tags": [\r\n "phishing",\r\n "targeted-user"\r\n ]\r\n },\r\n {\r\n "dataType": "other",\r\n "data": "{{#context.alerts}}{{user.name}}{{/context.alerts}}",\r\n "tags": [\r\n "username",\r\n "compromised-account",\r\n "unauthorized-access"\r\n ]\r\n }\r\n ],\r\n "procedures": [\r\n {\r\n "patternId": "{{#context.alerts}}{{threat.technique.id}}{{/context.alerts}}",\r\n "occurDate": {{#context.alerts}}{{#signal.original_time}}{{#FormatDate}} {{{signal.original_time}}} ; ; x {{/FormatDate}}{{/signal.original_time}}{{^signal.original_time}}1640000000000{{/signal.original_time}}{{/context.alerts}}\r\n }\r\n ]\r\n}', + '{\r\n "observables": [\r\n {\r\n "dataType": "hash",\r\n "data": "{{#context.alerts}}{{file.hash.md5}}{{/context.alerts}}",\r\n "tags": ["malware", "file-analysis"]\r\n },\r\n {\r\n "dataType": "hash",\r\n "data": "{{#context.alerts}}{{file.hash.sha256}}{{/context.alerts}}",\r\n "tags": ["malware", "suspicious-file"]\r\n }\r\n ]\r\n}', + '{\r\n "observables":\r\n [\r\n {\r\n "dataType": "ip",\r\n "data": "{{#context.alerts}}{{threat.indicator.ip}}{{/context.alerts}}",\r\n "tags": ["source", "malicious-activity"]\r\n }\r\n ],\r\n "procedures":\r\n [\r\n {\r\n "patternId": "{{#context.alerts}}{{threat.technique.id}}{{/context.alerts}}",\r\n "occurDate": {{#context.alerts}}{{#signal.original_time}}{{#FormatDate}} {{{signal.original_time}}} ; ; x {{/FormatDate}}{{/signal.original_time}}{{^signal.original_time}}1640000000000{{/signal.original_time}}{{/context.alerts}}\r\n }\r\n ]\r\n}\r\n', +]; + +export const testBodyOptions = [ + '{}', + JSON.stringify( + { + observables: [ + { + dataType: 'mail', + data: 'john@example.com', + tags: ['iam-user'], + }, + { + dataType: 'other', + data: 'john', + tags: ['username'], + }, + ], + procedures: [ + { + patternId: 'T1132', + occurDate: 1737103254000, + }, + ], + }, + null, + 2 + ), + JSON.stringify( + { + observables: [ + { + dataType: 'hash', + data: '5d41402abc4b2a76b9719d911017c592', + tags: ['md5'], + }, + ], + procedures: [ + { + patternId: 'T1612', + occurDate: 1737107904000, + tactic: 'Defense Evasion', + }, + ], + }, + null, + 2 + ), + JSON.stringify( + { + observables: [ + { + dataType: 'ip', + data: '127.0.0.1', + tags: ['source'], + }, + ], + procedures: [ + { + patternId: 'T1132', + occurDate: 1737105104000, + tactic: 'command-and-control', + }, + ], + }, + null, + 2 + ), +]; diff --git a/x-pack/platform/plugins/shared/stack_connectors/public/connector_types/thehive/params.test.tsx b/x-pack/platform/plugins/shared/stack_connectors/public/connector_types/thehive/params.test.tsx index d69080938fc26..7eeddac659020 100644 --- a/x-pack/platform/plugins/shared/stack_connectors/public/connector_types/thehive/params.test.tsx +++ b/x-pack/platform/plugins/shared/stack_connectors/public/connector_types/thehive/params.test.tsx @@ -72,6 +72,8 @@ describe('TheHiveParamsFields renders', () => { severity: 2, tags: [], sourceRef: '{{alert.uuid}}', + template: 0, + body: '{}', }, 0 ); diff --git a/x-pack/platform/plugins/shared/stack_connectors/public/connector_types/thehive/params.tsx b/x-pack/platform/plugins/shared/stack_connectors/public/connector_types/thehive/params.tsx index f0221ce7a460b..42900a9f3dacf 100644 --- a/x-pack/platform/plugins/shared/stack_connectors/public/connector_types/thehive/params.tsx +++ b/x-pack/platform/plugins/shared/stack_connectors/public/connector_types/thehive/params.tsx @@ -83,6 +83,8 @@ const TheHiveParamsFields: React.FunctionComponent )} diff --git a/x-pack/platform/plugins/shared/stack_connectors/public/connector_types/thehive/params_alert.test.tsx b/x-pack/platform/plugins/shared/stack_connectors/public/connector_types/thehive/params_alert.test.tsx index 138595bd52690..4467a4d894dc8 100644 --- a/x-pack/platform/plugins/shared/stack_connectors/public/connector_types/thehive/params_alert.test.tsx +++ b/x-pack/platform/plugins/shared/stack_connectors/public/connector_types/thehive/params_alert.test.tsx @@ -6,11 +6,12 @@ */ import React from 'react'; -import { render } from '@testing-library/react'; +import { fireEvent, render } from '@testing-library/react'; import { ActionConnector } from '@kbn/triggers-actions-ui-plugin/public/types'; import { TheHiveParamsAlertFields } from './params_alert'; import { SUB_ACTION } from '../../../common/thehive/constants'; import { ExecutorParams, ExecutorSubActionCreateAlertParams } from '../../../common/thehive/types'; +import { bodyOptions } from './constants'; describe('TheHiveParamsFields renders', () => { const subActionParams: ExecutorSubActionCreateAlertParams = { @@ -22,6 +23,8 @@ describe('TheHiveParamsFields renders', () => { source: 'source test', type: 'sourceType test', sourceRef: 'sourceRef test', + template: 0, + body: null, }; const actionParams: ExecutorParams = { subAction: SUB_ACTION.CREATE_ALERT, @@ -63,8 +66,31 @@ describe('TheHiveParamsFields renders', () => { expect(getByTestId('typeInput')).toBeInTheDocument(); expect(getByTestId('sourceInput')).toBeInTheDocument(); expect(getByTestId('sourceRefInput')).toBeInTheDocument(); + expect(getByTestId('templateSelectInput')).toBeInTheDocument(); expect(getByTestId('severitySelectInput')).toHaveValue('2'); expect(getByTestId('tlpSelectInput')).toHaveValue('2'); + expect(getByTestId('templateSelectInput')).toHaveValue('0'); + }); + + it('changes the content of json editor when template is changed', () => { + const { getByTestId } = render(); + const templateSelectEl = getByTestId('templateSelectInput'); + + fireEvent.change(templateSelectEl, { target: { value: 1 } }); + expect(editAction).toHaveBeenNthCalledWith( + 1, + 'subActionParams', + { ...subActionParams, body: bodyOptions[1], template: 1 }, + 0 + ); + + fireEvent.change(templateSelectEl, { target: { value: 2 } }); + expect(editAction).toHaveBeenNthCalledWith( + 2, + 'subActionParams', + { ...subActionParams, body: bodyOptions[2], template: 2 }, + 0 + ); }); }); diff --git a/x-pack/platform/plugins/shared/stack_connectors/public/connector_types/thehive/params_alert.tsx b/x-pack/platform/plugins/shared/stack_connectors/public/connector_types/thehive/params_alert.tsx index 868298ef98a7c..c2d7a6dde0925 100644 --- a/x-pack/platform/plugins/shared/stack_connectors/public/connector_types/thehive/params_alert.tsx +++ b/x-pack/platform/plugins/shared/stack_connectors/public/connector_types/thehive/params_alert.tsx @@ -10,10 +10,18 @@ import { TextFieldWithMessageVariables, TextAreaWithMessageVariables, ActionParamsProps, + JsonEditorWithMessageVariables, + ActionConnectorMode, } from '@kbn/triggers-actions-ui-plugin/public'; -import { EuiFormRow, EuiSelect, EuiComboBox } from '@elastic/eui'; +import { EuiFormRow, EuiSelect, EuiComboBox, EuiIconTip } from '@elastic/eui'; import { ExecutorParams, ExecutorSubActionCreateAlertParams } from '../../../common/thehive/types'; -import { severityOptions, tlpOptions } from './constants'; +import { + bodyOptions, + severityOptions, + templateOptions, + testBodyOptions, + tlpOptions, +} from './constants'; import * as translations from './translations'; export const TheHiveParamsAlertFields: React.FC> = ({ @@ -22,6 +30,7 @@ export const TheHiveParamsAlertFields: React.FC { const alert = useMemo( () => @@ -30,9 +39,12 @@ export const TheHiveParamsAlertFields: React.FC executionMode === ActionConnectorMode.Test, [executionMode]); const [severity, setSeverity] = useState(alert.severity ?? severityOptions[1].value); const [tlp, setTlp] = useState(alert.tlp ?? tlpOptions[2].value); @@ -187,6 +199,57 @@ export const TheHiveParamsAlertFields: React.FC + + { + editAction( + 'subActionParams', + { + ...alert, + body: isTest + ? testBodyOptions[parseInt(e.target.value, 10)] + : bodyOptions[parseInt(e.target.value, 10)], + template: parseInt(e.target.value, 10), + }, + index + ); + }} + /> + + + {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: '{}' }, index); + } + }} + /> ); }; diff --git a/x-pack/platform/plugins/shared/stack_connectors/public/connector_types/thehive/thehive.test.tsx b/x-pack/platform/plugins/shared/stack_connectors/public/connector_types/thehive/thehive.test.tsx index 654324d22e153..7923da52afbd7 100644 --- a/x-pack/platform/plugins/shared/stack_connectors/public/connector_types/thehive/thehive.test.tsx +++ b/x-pack/platform/plugins/shared/stack_connectors/public/connector_types/thehive/thehive.test.tsx @@ -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: [], }; diff --git a/x-pack/platform/plugins/shared/stack_connectors/public/connector_types/thehive/translations.ts b/x-pack/platform/plugins/shared/stack_connectors/public/connector_types/thehive/translations.ts index cd2c1ffcf9a63..67fdac461835a 100644 --- a/x-pack/platform/plugins/shared/stack_connectors/public/connector_types/thehive/translations.ts +++ b/x-pack/platform/plugins/shared/stack_connectors/public/connector_types/thehive/translations.ts @@ -102,6 +102,42 @@ 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.bodyFieldLabel', + { + defaultMessage: 'Code Editor', + } +); + +export const BODY_HELP_LABEL = i18n.translate( + 'xpack.stackConnectors.components.thehive.bodyFieldHelpText', + { + defaultMessage: 'Body Help', + } +); + +export const BODY_HELP_TEXT = i18n.translate( + 'xpack.stackConnectors.components.thehive.bodyFieldHelpText', + { + defaultMessage: + 'Additional body parameters such as observables, procedures (TTPs), and custom fields to include in the API request.', + } +); + export const TITLE_REQUIRED = i18n.translate( 'xpack.stackConnectors.components.thehive.requiredTitleText', { diff --git a/x-pack/platform/plugins/shared/stack_connectors/server/connector_types/thehive/thehive.test.ts b/x-pack/platform/plugins/shared/stack_connectors/server/connector_types/thehive/thehive.test.ts index 5972d5da570ef..fdeb0ca07d0c0 100644 --- a/x-pack/platform/plugins/shared/stack_connectors/server/connector_types/thehive/thehive.test.ts +++ b/x-pack/platform/plugins/shared/stack_connectors/server/connector_types/thehive/thehive.test.ts @@ -390,7 +390,7 @@ describe('TheHiveConnector', () => { papLabel: 'AMBER', follow: true, customFields: [], - observableCount: 0, + observableCount: 1, status: 'New', stage: 'New', extraData: {}, @@ -415,8 +415,32 @@ describe('TheHiveConnector', () => { severity: 1, tlp: 2, tags: ['tag1', 'tag2'], + template: 0, + 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, template, ...restOfAlert } = alert; + const expectedAlertBody = { ...restOfAlert, ...JSON.parse(body ?? '{}') }; + 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, diff --git a/x-pack/platform/plugins/shared/stack_connectors/server/connector_types/thehive/thehive.ts b/x-pack/platform/plugins/shared/stack_connectors/server/connector_types/thehive/thehive.ts index 623a9b8ee73d7..d291308c858af 100644 --- a/x-pack/platform/plugins/shared/stack_connectors/server/connector_types/thehive/thehive.ts +++ b/x-pack/platform/plugins/shared/stack_connectors/server/connector_types/thehive/thehive.ts @@ -157,11 +157,15 @@ export class TheHiveConnector extends CaseConnector< alert: ExecutorSubActionCreateAlertParams, connectorUsageCollector: ConnectorUsageCollector ) { + const { body, template, ...restOfAlert } = alert; + const bodyJson = JSON.parse(body ?? '{}'); + const mergedAlertBody = { ...restOfAlert, ...bodyJson }; + await this.request( { method: 'post', url: `${this.url}/api/${API_VERSION}/alert`, - data: alert, + data: mergedAlertBody, headers: this.getAuthHeaders(), responseSchema: TheHiveCreateAlertResponseSchema, }, diff --git a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/components/json_editor_with_message_variables.tsx b/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/components/json_editor_with_message_variables.tsx index dd761d42037b2..aa19bc264e7fe 100644 --- a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/components/json_editor_with_message_variables.tsx +++ b/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/components/json_editor_with_message_variables.tsx @@ -77,7 +77,7 @@ export const JsonEditorWithMessageVariables: React.FunctionComponent = ({ const { convertToJson, setXJson, xJson } = useXJsonMode(inputTargetValue ?? null); useEffect(() => { - if (!xJson && inputTargetValue) { + if ((!xJson && inputTargetValue) || (xJson && inputTargetValue && inputTargetValue !== xJson)) { setXJson(inputTargetValue); } // eslint-disable-next-line react-hooks/exhaustive-deps