From 0f5da1a70ea1f74a01d1b8ee47a34241eedcf470 Mon Sep 17 00:00:00 2001 From: sharadcrest Date: Mon, 13 Jan 2025 15:45:12 +0530 Subject: [PATCH 1/7] add json editor to suppport observables and procedures --- .../connectors/action-types/thehive.asciidoc | 1 + .../stack_connectors/common/thehive/schema.ts | 1 + .../connector_types/thehive/constants.ts | 24 +++++++++++++ .../connector_types/thehive/params.test.tsx | 20 +++++++++++ .../public/connector_types/thehive/params.tsx | 3 +- .../thehive/params_alert.test.tsx | 23 ++++++++++++ .../connector_types/thehive/params_alert.tsx | 36 +++++++++++++++++-- .../connector_types/thehive/translations.ts | 29 +++++++++++++++ .../connector_types/thehive/thehive.test.ts | 22 +++++++++++- .../server/connector_types/thehive/thehive.ts | 6 +++- 10 files changed, 160 insertions(+), 5 deletions(-) diff --git a/docs/management/connectors/action-types/thehive.asciidoc b/docs/management/connectors/action-types/thehive.asciidoc index e847d0b621eca..41beb14d93396 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. +Body:: The additional body parameters, such as observables, procedures (TTPs), custom fields, etc., to include in the API call request. [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..96c5089de21a3 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,7 @@ 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())), + body: 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..3b4ce03d9b004 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,27 @@ export const tlpOptions = [ }), }, ]; + +export const body = + '{"observables":[{"dataType":"ip","data":"{{#context.alerts}}{{source.ip}}{{/context.alerts}}","tags":["source.ip"]},{"dataType":"hostname","data":"{{#context.alerts}}{{host.hostname}}{{/context.alerts}}","tags":["Fortigate-FW"]}],"procedures":[{{#context.rule.threat}}{"patternId":"{{technique.0.id}}","occurDate":"{{#context.alerts}}{{#signal.original_time}}{{#FormatDate}} {{{signal.original_time}}} ; ; x {{/FormatDate}}{{/signal.original_time}}{{^signal.original_time}}1640000000000{{/signal.original_time}}{{/context.alerts}}","tactic":"{{tactic.name}}"}{{#technique.0.subtechnique}},{"patternId":"{{id}}","occurDate":"{{#context.alerts}}{{#signal.original_time}}{{#FormatDate}} {{{signal.original_time}}} ; ; x {{/FormatDate}}{{/signal.original_time}}{{^signal.original_time}}1640000000000{{/signal.original_time}}{{/context.alerts}}","tactic":"{{tactic.name}}"}{{/technique.0.subtechnique}}{{^technique.0.subtechnique}}{{^@last}},{{/@last}}{{/technique.0.subtechnique}}{{/context.rule.threat}}]}'; + +export const testBody = JSON.stringify( + { + observables: [ + { + dataType: 'ip', + data: '127.0.0.1', + tags: ['source.ip'], + }, + ], + procedures: [ + { + patternId: 'T1132', + occurDate: 1640000000000, + 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..aaf0274bf991e 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,26 @@ describe('TheHiveParamsFields renders', () => { severity: 2, tags: [], sourceRef: '{{alert.uuid}}', + otherBody: JSON.stringify( + { + observables: [ + { + dataType: 'ip', + data: '127.0.0.1', + tags: ['source.ip'], + }, + ], + procedures: [ + { + patternId: 'T1132', + occurDate: 1640000000000, + tactic: 'command-and-control', + }, + ], + }, + null, + 2 + ), }, 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..244b643a77462 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 @@ -8,7 +8,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 { body, eventActionOptions, testBody } from './constants'; import { SUB_ACTION } from '../../../common/thehive/constants'; import { ExecutorParams } from '../../../common/thehive/types'; import { TheHiveParamsAlertFields } from './params_alert'; @@ -83,6 +83,7 @@ const TheHiveParamsFields: React.FunctionComponent { source: 'source test', type: 'sourceType test', sourceRef: 'sourceRef test', + body: JSON.stringify( + { + observables: [ + { + dataType: 'ip', + data: '127.0.0.1', + tags: ['source.ip'], + }, + ], + procedures: [ + { + patternId: 'T1132', + occurDate: 1640000000000, + tactic: 'command-and-control', + }, + ], + }, + null, + 2 + ), }; const actionParams: ExecutorParams = { subAction: SUB_ACTION.CREATE_ALERT, @@ -63,8 +83,11 @@ describe('TheHiveParamsFields renders', () => { expect(getByTestId('typeInput')).toBeInTheDocument(); expect(getByTestId('sourceInput')).toBeInTheDocument(); expect(getByTestId('sourceRefInput')).toBeInTheDocument(); + expect(getByTestId('bodyJsonEditor')).toBeInTheDocument(); + expect(getByTestId('templateSelectInput')).toBeInTheDocument(); expect(getByTestId('severitySelectInput')).toHaveValue('2'); expect(getByTestId('tlpSelectInput')).toHaveValue('2'); + expect(getByTestId('templateSelectInput')).toHaveValue('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..4777e60ee006b 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,11 @@ import { TextFieldWithMessageVariables, TextAreaWithMessageVariables, ActionParamsProps, + JsonEditorWithMessageVariables, } 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 { severityOptions, testBody, tlpOptions } from './constants'; import * as translations from './translations'; export const TheHiveParamsAlertFields: React.FC> = ({ @@ -30,6 +31,7 @@ export const TheHiveParamsAlertFields: React.FC + + {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/translations.ts b/x-pack/platform/plugins/shared/stack_connectors/public/connector_types/thehive/translations.ts index cd2c1ffcf9a63..a3809dac88ba5 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,35 @@ export const SOURCE_REF_LABEL = i18n.translate( } ); +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..d53f35fc3fb03 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,6 +415,26 @@ describe('TheHiveConnector', () => { severity: 1, 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 + ), }; it('TheHive API call is successful with correct parameters', async () => { 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..f61a93dad0c81 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, ...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, }, From 5f696207ea4fca5df5ab60a74ae9c6dee1e847f8 Mon Sep 17 00:00:00 2001 From: sharadcrest Date: Mon, 13 Jan 2025 22:32:53 +0530 Subject: [PATCH 2/7] Add json editor and templates for observables and procedures --- .../stack_connectors/common/thehive/schema.ts | 1 + .../connector_types/thehive/constants.ts | 89 +++++++++++++++++++ .../connector_types/thehive/params.test.tsx | 20 +++++ .../public/connector_types/thehive/params.tsx | 4 +- .../thehive/params_alert.test.tsx | 23 +++++ .../connector_types/thehive/params_alert.tsx | 67 +++++++++++++- .../connector_types/thehive/translations.ts | 36 ++++++++ .../connector_types/thehive/thehive.test.ts | 22 ++++- .../server/connector_types/thehive/thehive.ts | 6 +- .../json_editor_with_message_variables.tsx | 2 +- 10 files changed, 264 insertions(+), 6 deletions(-) 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..37c6efef247f3 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,7 @@ 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())), + 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..0ec974bd549b9 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,92 @@ export const tlpOptions = [ }), }, ]; + +export const templateOptions = [ + { + value: 0, + text: i18n.translate( + 'xpack.stackConnectors.components.thehive.eventSelectTemplate1OptionLabel', + { + defaultMessage: 'IP Observables', // change this + } + ), + }, + { + value: 1, + text: i18n.translate( + 'xpack.stackConnectors.components.thehive.eventSelectTemplate2OptionLabel', + { + defaultMessage: 'Hostname Observables', // change this + } + ), + }, +]; + +export const bodyOptions = [ + '{"observables":[{"dataType":"ip","data":"{{#context.alerts}}{{source.ip}}{{/context.alerts}}","tags":["source.ip"]},{"dataType":"hostname","data":"{{#context.alerts}}{{host.hostname}}{{/context.alerts}}","tags":["Fortigate-FW"]}],"procedures":[{{#context.rule.threat}}{"patternId":"{{technique.0.id}}","occurDate":"{{#context.alerts}}{{#signal.original_time}}{{#FormatDate}} {{{signal.original_time}}} ; ; x {{/FormatDate}}{{/signal.original_time}}{{^signal.original_time}}1640000000000{{/signal.original_time}}{{/context.alerts}}","tactic":"{{tactic.name}}"}{{#technique.0.subtechnique}},{"patternId":"{{id}}","occurDate":"{{#context.alerts}}{{#signal.original_time}}{{#FormatDate}} {{{signal.original_time}}} ; ; x {{/FormatDate}}{{/signal.original_time}}{{^signal.original_time}}1640000000000{{/signal.original_time}}{{/context.alerts}}","tactic":"{{tactic.name}}"}{{/technique.0.subtechnique}}{{^technique.0.subtechnique}}{{^@last}},{{/@last}}{{/technique.0.subtechnique}}{{/context.rule.threat}}]}', + // change this template + JSON.stringify( + { + observables: [ + { + dataType: 'ip', + data: '127.0.0.1', + tags: ['source.ip'], + }, + ], + procedures: [ + { + patternId: 'T1132', + occurDate: 1640000000000, + tactic: 'command-and-control', + }, + ], + }, + null, + 2 + ), +]; + +export const testBodyOptions = [ + JSON.stringify( + { + observables: [ + { + dataType: 'ip', + data: '127.0.0.1', + tags: ['source.ip'], + }, + ], + procedures: [ + { + patternId: 'T1132', + occurDate: 1640000000000, + tactic: 'command-and-control', + }, + ], + }, + null, + 2 + ), + JSON.stringify( + { + observables: [ + { + dataType: 'hostname', + data: 'test-host.example.com', + tags: ['hostname'], + }, + ], + procedures: [ + { + patternId: 'T1132', + occurDate: 1640000000000, + 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..ba1643bae8c70 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,26 @@ describe('TheHiveParamsFields renders', () => { severity: 2, tags: [], sourceRef: '{{alert.uuid}}', + body: JSON.stringify( + { + observables: [ + { + dataType: 'ip', + data: '127.0.0.1', + tags: ['source.ip'], + }, + ], + procedures: [ + { + patternId: 'T1132', + occurDate: 1640000000000, + tactic: 'command-and-control', + }, + ], + }, + null, + 2 + ), }, 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..6dd97a50be246 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 @@ -8,7 +8,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 { bodyOptions, eventActionOptions, testBodyOptions } from './constants'; import { SUB_ACTION } from '../../../common/thehive/constants'; import { ExecutorParams } from '../../../common/thehive/types'; import { TheHiveParamsAlertFields } from './params_alert'; @@ -83,6 +83,7 @@ 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..4eba3227a3f17 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 @@ -22,6 +22,26 @@ describe('TheHiveParamsFields renders', () => { source: 'source test', type: 'sourceType test', sourceRef: 'sourceRef test', + body: JSON.stringify( + { + observables: [ + { + dataType: 'ip', + data: '127.0.0.1', + tags: ['source.ip'], + }, + ], + procedures: [ + { + patternId: 'T1132', + occurDate: 1640000000000, + tactic: 'command-and-control', + }, + ], + }, + null, + 2 + ), }; const actionParams: ExecutorParams = { subAction: SUB_ACTION.CREATE_ALERT, @@ -63,8 +83,11 @@ describe('TheHiveParamsFields renders', () => { expect(getByTestId('typeInput')).toBeInTheDocument(); expect(getByTestId('sourceInput')).toBeInTheDocument(); expect(getByTestId('sourceRefInput')).toBeInTheDocument(); + expect(getByTestId('bodyJsonEditor')).toBeInTheDocument(); + expect(getByTestId('templateSelectInput')).toBeInTheDocument(); expect(getByTestId('severitySelectInput')).toHaveValue('2'); expect(getByTestId('tlpSelectInput')).toHaveValue('2'); + expect(getByTestId('templateSelectInput')).toHaveValue('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..805e86dd538bc 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,15 +39,18 @@ 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); const [selectedOptions, setSelected] = useState>( alert.tags?.map((tag) => ({ label: tag })) ?? [] ); + const [template, setTemplate] = useState(templateOptions[0].value); const onCreateOption = (searchValue: string) => { setSelected([...selectedOptions, { label: searchValue }]); @@ -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)], + }, + index + ); + setTemplate(parseInt(e.target.value, 10)); + }} + /> + + + {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/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..d53f35fc3fb03 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,6 +415,26 @@ describe('TheHiveConnector', () => { severity: 1, 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 + ), }; it('TheHive API call is successful with correct parameters', async () => { 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..f61a93dad0c81 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, ...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 From 8c39d536b4cbb3aed07b0260abccbca1d3fdd73f Mon Sep 17 00:00:00 2001 From: sharadcrest Date: Thu, 16 Jan 2025 09:48:27 +0530 Subject: [PATCH 3/7] update unit test --- .../connector_types/thehive/params.test.tsx | 21 +------------------ .../connector_types/thehive/thehive.test.tsx | 13 ++++++++++++ 2 files changed, 14 insertions(+), 20 deletions(-) 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 ba1643bae8c70..8e6b48183bf27 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,26 +72,7 @@ describe('TheHiveParamsFields renders', () => { severity: 2, tags: [], sourceRef: '{{alert.uuid}}', - body: JSON.stringify( - { - observables: [ - { - dataType: 'ip', - data: '127.0.0.1', - tags: ['source.ip'], - }, - ], - procedures: [ - { - patternId: 'T1132', - occurDate: 1640000000000, - tactic: 'command-and-control', - }, - ], - }, - null, - 2 - ), + body: '{"observables":[{"dataType":"ip","data":"{{#context.alerts}}{{source.ip}}{{/context.alerts}}","tags":["source.ip"]},{"dataType":"hostname","data":"{{#context.alerts}}{{host.hostname}}{{/context.alerts}}","tags":["Fortigate-FW"]}],"procedures":[{{#context.rule.threat}}{"patternId":"{{technique.0.id}}","occurDate":"{{#context.alerts}}{{#signal.original_time}}{{#FormatDate}} {{{signal.original_time}}} ; ; x {{/FormatDate}}{{/signal.original_time}}{{^signal.original_time}}1640000000000{{/signal.original_time}}{{/context.alerts}}","tactic":"{{tactic.name}}"}{{#technique.0.subtechnique}},{"patternId":"{{id}}","occurDate":"{{#context.alerts}}{{#signal.original_time}}{{#FormatDate}} {{{signal.original_time}}} ; ; x {{/FormatDate}}{{/signal.original_time}}{{^signal.original_time}}1640000000000{{/signal.original_time}}{{/context.alerts}}","tactic":"{{tactic.name}}"}{{/technique.0.subtechnique}}{{^technique.0.subtechnique}}{{^@last}},{{/@last}}{{/technique.0.subtechnique}}{{/context.rule.threat}}]}', }, 0 ); 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: [], }; From 277c5f0eb2d6fb7b9c7a0a30075f6032921364c7 Mon Sep 17 00:00:00 2001 From: sharadcrest Date: Thu, 16 Jan 2025 17:12:54 +0530 Subject: [PATCH 4/7] add templates --- .../connector_types/thehive/constants.ts | 225 +++++++++++++++++- .../connector_types/thehive/thehive.test.ts | 5 +- 2 files changed, 221 insertions(+), 9 deletions(-) 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 0ec974bd549b9..b6ee16a2eb38a 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 @@ -119,7 +119,7 @@ export const templateOptions = [ text: i18n.translate( 'xpack.stackConnectors.components.thehive.eventSelectTemplate1OptionLabel', { - defaultMessage: 'IP Observables', // change this + defaultMessage: 'Fortigate Firewall Threat Detection', } ), }, @@ -128,7 +128,34 @@ export const templateOptions = [ text: i18n.translate( 'xpack.stackConnectors.components.thehive.eventSelectTemplate2OptionLabel', { - defaultMessage: 'Hostname Observables', // change this + defaultMessage: 'Suspicious Network Activity', + } + ), + }, + { + value: 2, + text: i18n.translate( + 'xpack.stackConnectors.components.thehive.eventSelectTemplate3OptionLabel', + { + defaultMessage: 'Compromised User Account Investigation', + } + ), + }, + { + value: 3, + text: i18n.translate( + 'xpack.stackConnectors.components.thehive.eventSelectTemplate4OptionLabel', + { + defaultMessage: 'Malicious File Analysis', + } + ), + }, + { + value: 4, + text: i18n.translate( + 'xpack.stackConnectors.components.thehive.eventSelectTemplate5OptionLabel', + { + defaultMessage: 'Data Exfiltration Monitoring', } ), }, @@ -136,21 +163,130 @@ export const templateOptions = [ export const bodyOptions = [ '{"observables":[{"dataType":"ip","data":"{{#context.alerts}}{{source.ip}}{{/context.alerts}}","tags":["source.ip"]},{"dataType":"hostname","data":"{{#context.alerts}}{{host.hostname}}{{/context.alerts}}","tags":["Fortigate-FW"]}],"procedures":[{{#context.rule.threat}}{"patternId":"{{technique.0.id}}","occurDate":"{{#context.alerts}}{{#signal.original_time}}{{#FormatDate}} {{{signal.original_time}}} ; ; x {{/FormatDate}}{{/signal.original_time}}{{^signal.original_time}}1640000000000{{/signal.original_time}}{{/context.alerts}}","tactic":"{{tactic.name}}"}{{#technique.0.subtechnique}},{"patternId":"{{id}}","occurDate":"{{#context.alerts}}{{#signal.original_time}}{{#FormatDate}} {{{signal.original_time}}} ; ; x {{/FormatDate}}{{/signal.original_time}}{{^signal.original_time}}1640000000000{{/signal.original_time}}{{/context.alerts}}","tactic":"{{tactic.name}}"}{{/technique.0.subtechnique}}{{^technique.0.subtechnique}}{{^@last}},{{/@last}}{{/technique.0.subtechnique}}{{/context.rule.threat}}]}', - // change this template JSON.stringify( { observables: [ { dataType: 'ip', - data: '127.0.0.1', - tags: ['source.ip'], + data: '{{source.ip}}', + tags: ['source', 'malicious-activity'], + }, + { + dataType: 'hostname', + data: '{{host.hostname}}', + tags: ['endpoint', 'suspicious'], + }, + { + dataType: 'url', + data: '{{network.url}}', + tags: ['malware-distribution', 'phishing-site'], }, ], procedures: [ { - patternId: 'T1132', - occurDate: 1640000000000, - tactic: 'command-and-control', + patternId: '{{technique.id}}', + occurDate: '{{timestamp}}', + tactic: 'Initial Access', + }, + { + patternId: '{{technique.id}}', + occurDate: '{{timestamp}}', + tactic: 'Command and Control', + }, + ], + }, + null, + 2 + ), + JSON.stringify( + { + observables: [ + { + dataType: 'email', + data: '{{user.email}}', + tags: ['phishing', 'targeted-user'], + }, + { + dataType: 'username', + data: '{{user.name}}', + tags: ['compromised-account', 'unauthorized-access'], + }, + ], + procedures: [ + { + patternId: '{{technique.id}}', + occurDate: '{{timestamp}}', + tactic: 'Credential Access', + }, + { + patternId: '{{technique.id}}', + occurDate: '{{timestamp}}', + tactic: 'Privilege Escalation', + }, + ], + }, + null, + 2 + ), + JSON.stringify( + { + observables: [ + { + dataType: 'hash', + data: '{{file.hash.md5}}', + tags: ['malware', 'file-analysis'], + }, + { + dataType: 'hash', + data: '{{file.hash.sha256}}', + tags: ['malware', 'suspicious-file'], + }, + ], + procedures: [ + { + patternId: '{{technique.id}}', + occurDate: '{{timestamp}}', + tactic: 'Defense Evasion', + }, + { + patternId: '{{technique.id}}', + occurDate: '{{timestamp}}', + tactic: 'Execution', + }, + ], + }, + null, + 2 + ), + JSON.stringify( + { + observables: [ + { + dataType: 'ip', + data: '{{destination.ip}}', + tags: ['exfiltration', 'suspicious'], + }, + { + dataType: 'hostname', + data: '{{destination.hostname}}', + tags: ['data-leakage', 'endpoint'], + }, + { + dataType: 'url', + data: '{{destination.url}}', + tags: ['data-exfiltration', 'command-and-control'], + }, + ], + procedures: [ + { + patternId: '{{technique.id}}', + occurDate: '{{timestamp}}', + tactic: 'Exfiltration', + }, + { + patternId: '{{technique.id}}', + occurDate: '{{timestamp}}', + tactic: 'Command and Control', }, ], }, @@ -200,4 +336,77 @@ export const testBodyOptions = [ null, 2 ), + JSON.stringify( + { + observables: [ + { + dataType: 'email', + data: 'john@ex.com', + tags: ['phishing', 'targeted-user'], + }, + { + dataType: 'username', + data: 'user1', + tags: ['compromised-account', 'unauthorized-access'], + }, + ], + procedures: [ + { + patternId: 'T1132', + occurDate: '1640000000000', + tactic: 'Credential Access', + }, + ], + }, + null, + 2 + ), + JSON.stringify( + { + observables: [ + { + dataType: 'hash', + data: '5d41402abc4b2a76b9719d911017c592', + tags: ['malware', 'file-analysis'], + }, + ], + }, + null, + 2 + ), + JSON.stringify( + { + observables: [ + { + dataType: 'ip', + data: '127.0.0.1', + tags: ['exfiltration', 'suspicious'], + }, + { + dataType: 'hostname', + data: 'www.example.com', + tags: ['data-leakage', 'endpoint'], + }, + { + dataType: 'url', + data: 'https://www.example.com', + tags: ['data-exfiltration', 'command-and-control'], + }, + ], + procedures: [ + { + patternId: 'TA0002', + occurDate: '1736976000000', + tactic: 'Exfiltration', + }, + { + patternId: 'TA0003', + occurDate: '1704067199000', + tactic: 'Command and Control', + }, + ], + }, + null, + 2 + ), ]; 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 d53f35fc3fb03..1489007016490 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 @@ -437,6 +437,9 @@ describe('TheHiveConnector', () => { ), }; + const { body, ...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); @@ -445,7 +448,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, From e4fbcf4b3a0a63e29a3963b8aab11a06a6c27b74 Mon Sep 17 00:00:00 2001 From: sharadcrest Date: Thu, 16 Jan 2025 17:22:33 +0530 Subject: [PATCH 5/7] update readme --- docs/management/connectors/action-types/thehive.asciidoc | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/management/connectors/action-types/thehive.asciidoc b/docs/management/connectors/action-types/thehive.asciidoc index e847d0b621eca..be8b0a34c9b56 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. +Body:: The additional body parameters, such as observables, procedures (TTPs), and custom fields, to include in the API call request. [float] [[thehive-connector-networking-configuration]] From 79efb8287d1f592888da7819321d4bf254610033 Mon Sep 17 00:00:00 2001 From: Sharad <154420354+sharadcrest@users.noreply.github.com> Date: Mon, 20 Jan 2025 10:54:14 +0530 Subject: [PATCH 6/7] Thehive update template (#19) * update templates * update test template and add unit test * change template option and store template in action params * update 2nd template with context condition --- .../stack_connectors/common/thehive/schema.ts | 1 + .../connector_types/thehive/constants.ts | 223 ++---------------- .../connector_types/thehive/params.test.tsx | 3 +- .../public/connector_types/thehive/params.tsx | 5 +- .../thehive/params_alert.test.tsx | 47 ++-- .../connector_types/thehive/params_alert.tsx | 70 +++--- .../connector_types/thehive/thehive.test.ts | 3 +- .../server/connector_types/thehive/thehive.ts | 2 +- 8 files changed, 91 insertions(+), 263 deletions(-) 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 37c6efef247f3..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,7 @@ 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()), }); 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 23096b17ffd46..05d2f88334470 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,13 +112,14 @@ export const tlpOptions = [ }), }, ]; + export const templateOptions = [ { value: 0, text: i18n.translate( 'xpack.stackConnectors.components.thehive.eventSelectTemplate1OptionLabel', { - defaultMessage: 'Fortigate Firewall Threat Detection', + defaultMessage: 'none', } ), }, @@ -149,165 +150,30 @@ export const templateOptions = [ } ), }, - { - value: 4, - text: i18n.translate( - 'xpack.stackConnectors.components.thehive.eventSelectTemplate5OptionLabel', - { - defaultMessage: 'Data Exfiltration Monitoring', - } - ), - }, ]; export const bodyOptions = [ - '{"observables":[{"dataType":"ip","data":"{{#context.alerts}}{{source.ip}}{{/context.alerts}}","tags":["source.ip"]},{"dataType":"hostname","data":"{{#context.alerts}}{{host.hostname}}{{/context.alerts}}","tags":["Fortigate-FW"]}],"procedures":[{{#context.rule.threat}}{"patternId":"{{technique.0.id}}","occurDate":"{{#context.alerts}}{{#signal.original_time}}{{#FormatDate}} {{{signal.original_time}}} ; ; x {{/FormatDate}}{{/signal.original_time}}{{^signal.original_time}}1640000000000{{/signal.original_time}}{{/context.alerts}}","tactic":"{{tactic.name}}"}{{#technique.0.subtechnique}},{"patternId":"{{id}}","occurDate":"{{#context.alerts}}{{#signal.original_time}}{{#FormatDate}} {{{signal.original_time}}} ; ; x {{/FormatDate}}{{/signal.original_time}}{{^signal.original_time}}1640000000000{{/signal.original_time}}{{/context.alerts}}","tactic":"{{tactic.name}}"}{{/technique.0.subtechnique}}{{^technique.0.subtechnique}}{{^@last}},{{/@last}}{{/technique.0.subtechnique}}{{/context.rule.threat}}]}', - JSON.stringify( - { - observables: [ - { - dataType: 'ip', - data: '{{source.ip}}', - tags: ['source', 'malicious-activity'], - }, - { - dataType: 'hostname', - data: '{{host.hostname}}', - tags: ['endpoint', 'suspicious'], - }, - { - dataType: 'url', - data: '{{network.url}}', - tags: ['malware-distribution', 'phishing-site'], - }, - ], - procedures: [ - { - patternId: '{{technique.id}}', - occurDate: '{{timestamp}}', - tactic: 'Initial Access', - }, - { - patternId: '{{technique.id}}', - occurDate: '{{timestamp}}', - tactic: 'Command and Control', - }, - ], - }, - null, - 2 - ), - JSON.stringify( - { - observables: [ - { - dataType: 'email', - data: '{{user.email}}', - tags: ['phishing', 'targeted-user'], - }, - { - dataType: 'username', - data: '{{user.name}}', - tags: ['compromised-account', 'unauthorized-access'], - }, - ], - procedures: [ - { - patternId: '{{technique.id}}', - occurDate: '{{timestamp}}', - tactic: 'Credential Access', - }, - { - patternId: '{{technique.id}}', - occurDate: '{{timestamp}}', - tactic: 'Privilege Escalation', - }, - ], - }, - null, - 2 - ), - JSON.stringify( - { - observables: [ - { - dataType: 'hash', - data: '{{file.hash.md5}}', - tags: ['malware', 'file-analysis'], - }, - { - dataType: 'hash', - data: '{{file.hash.sha256}}', - tags: ['malware', 'suspicious-file'], - }, - ], - procedures: [ - { - patternId: '{{technique.id}}', - occurDate: '{{timestamp}}', - tactic: 'Defense Evasion', - }, - { - patternId: '{{technique.id}}', - occurDate: '{{timestamp}}', - tactic: 'Execution', - }, - ], - }, - null, - 2 - ), - JSON.stringify( - { - observables: [ - { - dataType: 'ip', - data: '{{destination.ip}}', - tags: ['exfiltration', 'suspicious'], - }, - { - dataType: 'hostname', - data: '{{destination.hostname}}', - tags: ['data-leakage', 'endpoint'], - }, - { - dataType: 'url', - data: '{{destination.url}}', - tags: ['data-exfiltration', 'command-and-control'], - }, - ], - procedures: [ - { - patternId: '{{technique.id}}', - occurDate: '{{timestamp}}', - tactic: 'Exfiltration', - }, - { - patternId: '{{technique.id}}', - occurDate: '{{timestamp}}', - tactic: 'Command and Control', - }, - ], - }, - null, - 2 - ), + null, + '{\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', + '{\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}', ]; export const testBodyOptions = [ + null, JSON.stringify( { observables: [ { dataType: 'ip', data: '127.0.0.1', - tags: ['source.ip'], + tags: ['source'], }, ], procedures: [ { patternId: 'T1132', - occurDate: 1640000000000, + occurDate: 1737105104000, tactic: 'command-and-control', }, ], @@ -319,41 +185,20 @@ export const testBodyOptions = [ { observables: [ { - dataType: 'hostname', - data: 'test-host.example.com', - tags: ['hostname'], + dataType: 'mail', + data: 'john@example.com', + tags: ['iam-user'], }, - ], - procedures: [ { - patternId: 'T1132', - occurDate: 1640000000000, - tactic: 'command-and-control', - }, - ], - }, - null, - 2 - ), - JSON.stringify( - { - observables: [ - { - dataType: 'email', - data: 'john@ex.com', - tags: ['phishing', 'targeted-user'], - }, - { - dataType: 'username', - data: 'user1', - tags: ['compromised-account', 'unauthorized-access'], + dataType: 'other', + data: 'john', + tags: ['username'], }, ], procedures: [ { patternId: 'T1132', - occurDate: '1640000000000', - tactic: 'Credential Access', + occurDate: 1737103254000, }, ], }, @@ -366,42 +211,14 @@ export const testBodyOptions = [ { dataType: 'hash', data: '5d41402abc4b2a76b9719d911017c592', - tags: ['malware', 'file-analysis'], - }, - ], - }, - null, - 2 - ), - JSON.stringify( - { - observables: [ - { - dataType: 'ip', - data: '127.0.0.1', - tags: ['exfiltration', 'suspicious'], - }, - { - dataType: 'hostname', - data: 'www.example.com', - tags: ['data-leakage', 'endpoint'], - }, - { - dataType: 'url', - data: 'https://www.example.com', - tags: ['data-exfiltration', 'command-and-control'], + tags: ['md5'], }, ], procedures: [ { - patternId: 'TA0002', - occurDate: '1736976000000', - tactic: 'Exfiltration', - }, - { - patternId: 'TA0003', - occurDate: '1704067199000', - tactic: 'Command and Control', + patternId: 'T1612', + occurDate: 1737107904000, + tactic: 'Defense Evasion', }, ], }, 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 8e6b48183bf27..9e568bad56d0b 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,7 +72,8 @@ describe('TheHiveParamsFields renders', () => { severity: 2, tags: [], sourceRef: '{{alert.uuid}}', - body: '{"observables":[{"dataType":"ip","data":"{{#context.alerts}}{{source.ip}}{{/context.alerts}}","tags":["source.ip"]},{"dataType":"hostname","data":"{{#context.alerts}}{{host.hostname}}{{/context.alerts}}","tags":["Fortigate-FW"]}],"procedures":[{{#context.rule.threat}}{"patternId":"{{technique.0.id}}","occurDate":"{{#context.alerts}}{{#signal.original_time}}{{#FormatDate}} {{{signal.original_time}}} ; ; x {{/FormatDate}}{{/signal.original_time}}{{^signal.original_time}}1640000000000{{/signal.original_time}}{{/context.alerts}}","tactic":"{{tactic.name}}"}{{#technique.0.subtechnique}},{"patternId":"{{id}}","occurDate":"{{#context.alerts}}{{#signal.original_time}}{{#FormatDate}} {{{signal.original_time}}} ; ; x {{/FormatDate}}{{/signal.original_time}}{{^signal.original_time}}1640000000000{{/signal.original_time}}{{/context.alerts}}","tactic":"{{tactic.name}}"}{{/technique.0.subtechnique}}{{^technique.0.subtechnique}}{{^@last}},{{/@last}}{{/technique.0.subtechnique}}{{/context.rule.threat}}]}', + template: 0, + body: null, }, 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 6dd97a50be246..39d25a452f534 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 @@ -8,7 +8,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 { bodyOptions, eventActionOptions, testBodyOptions } from './constants'; +import { eventActionOptions } from './constants'; import { SUB_ACTION } from '../../../common/thehive/constants'; import { ExecutorParams } from '../../../common/thehive/types'; import { TheHiveParamsAlertFields } from './params_alert'; @@ -83,7 +83,8 @@ const TheHiveParamsFields: React.FunctionComponent { const subActionParams: ExecutorSubActionCreateAlertParams = { @@ -22,26 +23,8 @@ describe('TheHiveParamsFields renders', () => { source: 'source test', type: 'sourceType test', sourceRef: 'sourceRef test', - body: JSON.stringify( - { - observables: [ - { - dataType: 'ip', - data: '127.0.0.1', - tags: ['source.ip'], - }, - ], - procedures: [ - { - patternId: 'T1132', - occurDate: 1640000000000, - tactic: 'command-and-control', - }, - ], - }, - null, - 2 - ), + template: 0, + body: null, }; const actionParams: ExecutorParams = { subAction: SUB_ACTION.CREATE_ALERT, @@ -83,11 +66,31 @@ describe('TheHiveParamsFields renders', () => { expect(getByTestId('typeInput')).toBeInTheDocument(); expect(getByTestId('sourceInput')).toBeInTheDocument(); expect(getByTestId('sourceRefInput')).toBeInTheDocument(); - expect(getByTestId('bodyJsonEditor')).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 805e86dd538bc..16a5385dd83b0 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 @@ -39,7 +39,8 @@ export const TheHiveParamsAlertFields: React.FC>( alert.tags?.map((tag) => ({ label: tag })) ?? [] ); - const [template, setTemplate] = useState(templateOptions[0].value); const onCreateOption = (searchValue: string) => { setSelected([...selectedOptions, { label: searchValue }]); @@ -203,7 +203,7 @@ export const TheHiveParamsAlertFields: React.FC { editAction( @@ -213,43 +213,47 @@ export const TheHiveParamsAlertFields: React.FC - - {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); + {alert.body !== null ? ( + + {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); + } + }} + /> + ) : ( + <> + )} ); }; 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 1489007016490..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 @@ -415,6 +415,7 @@ describe('TheHiveConnector', () => { severity: 1, tlp: 2, tags: ['tag1', 'tag2'], + template: 0, body: JSON.stringify( { observables: [ @@ -437,7 +438,7 @@ describe('TheHiveConnector', () => { ), }; - const { body, ...restOfAlert } = alert; + const { body, template, ...restOfAlert } = alert; const expectedAlertBody = { ...restOfAlert, ...JSON.parse(body ?? '{}') }; it('TheHive API call is successful with correct parameters', async () => { 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 f61a93dad0c81..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,7 +157,7 @@ export class TheHiveConnector extends CaseConnector< alert: ExecutorSubActionCreateAlertParams, connectorUsageCollector: ConnectorUsageCollector ) { - const { body, ...restOfAlert } = alert; + const { body, template, ...restOfAlert } = alert; const bodyJson = JSON.parse(body ?? '{}'); const mergedAlertBody = { ...restOfAlert, ...bodyJson }; From f93b8f79df4c131eb1cd20c37882432acf0ef91c Mon Sep 17 00:00:00 2001 From: Sharad <154420354+sharadcrest@users.noreply.github.com> Date: Mon, 20 Jan 2025 14:53:15 +0530 Subject: [PATCH 7/7] Add build your own default template (#20) * Add build your own default template * update readme --- .../connectors/action-types/thehive.asciidoc | 2 +- .../connector_types/thehive/constants.ts | 54 ++++++++-------- .../connector_types/thehive/params.test.tsx | 2 +- .../public/connector_types/thehive/params.tsx | 2 +- .../connector_types/thehive/params_alert.tsx | 64 +++++++++---------- 5 files changed, 60 insertions(+), 64 deletions(-) diff --git a/docs/management/connectors/action-types/thehive.asciidoc b/docs/management/connectors/action-types/thehive.asciidoc index 41beb14d93396..fdd6f975816ba 100644 --- a/docs/management/connectors/action-types/thehive.asciidoc +++ b/docs/management/connectors/action-types/thehive.asciidoc @@ -65,7 +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. -Body:: The additional body parameters, such as observables, procedures (TTPs), custom fields, etc., to include in the API call request. +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/public/connector_types/thehive/constants.ts b/x-pack/platform/plugins/shared/stack_connectors/public/connector_types/thehive/constants.ts index 05d2f88334470..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 @@ -119,7 +119,7 @@ export const templateOptions = [ text: i18n.translate( 'xpack.stackConnectors.components.thehive.eventSelectTemplate1OptionLabel', { - defaultMessage: 'none', + defaultMessage: 'Build Your Own', } ), }, @@ -128,7 +128,7 @@ export const templateOptions = [ text: i18n.translate( 'xpack.stackConnectors.components.thehive.eventSelectTemplate2OptionLabel', { - defaultMessage: 'Suspicious Network Activity', + defaultMessage: 'Compromised User Account Investigation', } ), }, @@ -137,7 +137,7 @@ export const templateOptions = [ text: i18n.translate( 'xpack.stackConnectors.components.thehive.eventSelectTemplate3OptionLabel', { - defaultMessage: 'Compromised User Account Investigation', + defaultMessage: 'Malicious File Analysis', } ), }, @@ -146,41 +146,21 @@ export const templateOptions = [ text: i18n.translate( 'xpack.stackConnectors.components.thehive.eventSelectTemplate4OptionLabel', { - defaultMessage: 'Malicious File Analysis', + defaultMessage: 'Suspicious Network Activity', } ), }, ]; export const bodyOptions = [ - null, - '{\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', + '{}', '{\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 = [ - null, - JSON.stringify( - { - observables: [ - { - dataType: 'ip', - data: '127.0.0.1', - tags: ['source'], - }, - ], - procedures: [ - { - patternId: 'T1132', - occurDate: 1737105104000, - tactic: 'command-and-control', - }, - ], - }, - null, - 2 - ), + '{}', JSON.stringify( { observables: [ @@ -225,4 +205,24 @@ export const testBodyOptions = [ 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 9e568bad56d0b..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 @@ -73,7 +73,7 @@ describe('TheHiveParamsFields renders', () => { tags: [], sourceRef: '{{alert.uuid}}', template: 0, - body: null, + 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 39d25a452f534..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 @@ -84,7 +84,7 @@ const TheHiveParamsFields: React.FunctionComponent - {alert.body !== null ? ( - - {translations.BODY_LABEL} - - - } - ariaLabel={translations.BODY_DESCRIPTION} - errors={errors.body as string[]} - onDocumentsChange={(json: string) => - editAction('subActionParams', { ...alert, body: json }, 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); } - dataTestSubj="thehive-body" - onBlur={() => { - if (!alert.body) { - editAction('subActionParams', { ...alert, body: null }, index); - } - }} - /> - ) : ( - <> - )} + }} + /> ); };