mirror of
https://github.com/elastic/kibana.git
synced 2025-06-27 18:51:07 -04:00
XSOAR Connector (#212049)
## Summary XSOAR action connector, enabling users to send alerts generated by the rule detection engine to Palo Alto XSOAR for automation and remediation. ### **create connector**  ### **test connector** 1. **test page**  2. **select playbook**  ### Checklist Check the PR satisfies following conditions. Reviewers should verify this PR satisfies this list as well. - [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) - [ ] [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 - [x] 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) ### For maintainers - [ ] This was checked for breaking API changes and was [labeled appropriately](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: Sergi Massaneda <sergi.massaneda@elastic.co> Co-authored-by: Nastasha Solomon <79124755+nastasha-solomon@users.noreply.github.com> Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
parent
31fe87ae06
commit
3fcdc062fa
38 changed files with 3368 additions and 3 deletions
|
@ -54,6 +54,7 @@ subs:
|
||||||
bedrock: "Amazon Bedrock"
|
bedrock: "Amazon Bedrock"
|
||||||
gemini: "Google Gemini"
|
gemini: "Google Gemini"
|
||||||
hive: "TheHive"
|
hive: "TheHive"
|
||||||
|
xsoar: "XSOAR"
|
||||||
report-features: "reporting features"
|
report-features: "reporting features"
|
||||||
ml: "machine learning"
|
ml: "machine learning"
|
||||||
ccs: "cross-cluster search"
|
ccs: "cross-cluster search"
|
||||||
|
|
|
@ -41,6 +41,7 @@ Actions are instantiations of a connector that are linked to rules and run as ba
|
||||||
* [{{webhook}}](/reference/connectors-kibana/webhook-action-type.md): Send a request to a web service.
|
* [{{webhook}}](/reference/connectors-kibana/webhook-action-type.md): Send a request to a web service.
|
||||||
* [{{webhook-cm}}](/reference/connectors-kibana/cases-webhook-action-type.md): Send a request to a Case Management web service.
|
* [{{webhook-cm}}](/reference/connectors-kibana/cases-webhook-action-type.md): Send a request to a Case Management web service.
|
||||||
* [xMatters](/reference/connectors-kibana/xmatters-action-type.md): Send actionable alerts to on-call xMatters resources.
|
* [xMatters](/reference/connectors-kibana/xmatters-action-type.md): Send actionable alerts to on-call xMatters resources.
|
||||||
|
* [{{xsoar}}](/reference/connectors-kibana/xsoar-action-type.md): Create an incident in Cortex {{xsoar}}.
|
||||||
|
|
||||||
::::{note}
|
::::{note}
|
||||||
Some connector types are paid commercial features, while others are free. For a comparison of the Elastic subscription levels, go to [the subscription page](https://www.elastic.co/subscriptions).
|
Some connector types are paid commercial features, while others are free. For a comparison of the Elastic subscription levels, go to [the subscription page](https://www.elastic.co/subscriptions).
|
||||||
|
|
80
docs/reference/connectors-kibana/xsoar-action-type.md
Normal file
80
docs/reference/connectors-kibana/xsoar-action-type.md
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
---
|
||||||
|
navigation_title: "{{xsoar}}"
|
||||||
|
mapped_pages:
|
||||||
|
- https://www.elastic.co/guide/en/kibana/current/xsoar-action-type.html
|
||||||
|
---
|
||||||
|
|
||||||
|
# {{xsoar}} connector and action [xsoar-action-type]
|
||||||
|
|
||||||
|
|
||||||
|
{{xsoar}} connector uses the [{{xsoar}} REST API](https://cortex-panw.stoplight.io/docs/cortex-xsoar-8/m0qlgh9inh4vk-create-or-update-an-incident) to create Cortex {{xsoar}} incidents.
|
||||||
|
|
||||||
|
|
||||||
|
## Create connectors in {{kib}} [define-xsoar-ui]
|
||||||
|
|
||||||
|
You can create connectors in **{{stack-manage-app}} > {{connectors-ui}}** or as needed when you’re creating a rule. For example:
|
||||||
|
|
||||||
|
% TO DO: Use `:class: screenshot`
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
### Connector configuration [xsoar-connector-configuration]
|
||||||
|
|
||||||
|
{{xsoar}} connectors have the following configuration properties:
|
||||||
|
|
||||||
|
Name
|
||||||
|
: The name of the connector.
|
||||||
|
|
||||||
|
URL
|
||||||
|
: The {{xsoar}} instance URL.
|
||||||
|
|
||||||
|
API key
|
||||||
|
: The {{xsoar}} API key for authentication.
|
||||||
|
|
||||||
|
::::{note}
|
||||||
|
If you do not have an API key, refer to [Create a new API key](https://cortex-panw.stoplight.io/docs/cortex-xsoar-8/t09y7hrb5d14m-create-a-new-api-key) to make one for your {{xsoar}} instance.
|
||||||
|
::::
|
||||||
|
|
||||||
|
API key id
|
||||||
|
: The {{xsoar}} API key ID for authentication. (Mandatory for cloud instance users.)
|
||||||
|
|
||||||
|
|
||||||
|
## Test connectors [xsoar-action-configuration]
|
||||||
|
|
||||||
|
You can test connectors as you’re creating or editing the connector in {{kib}}. For example:
|
||||||
|
|
||||||
|
% TO DO: Use `:class: screenshot`
|
||||||
|

|
||||||
|
|
||||||
|
{{xsoar}} actions have the following configuration properties.
|
||||||
|
|
||||||
|
Name
|
||||||
|
: The incident name.
|
||||||
|
|
||||||
|
Playbook
|
||||||
|
: The playbook to associate with the incident.
|
||||||
|
|
||||||
|
Start investigation
|
||||||
|
: If turned on, will automatically start the investigation process after the incident is created.
|
||||||
|
|
||||||
|
Severity
|
||||||
|
: The severity of the incident. Can be `Unknown`, `Informational`, `Low`, `Medium`, `High` or `Critical`.
|
||||||
|
|
||||||
|
::::{note}
|
||||||
|
Turn on `Keep severity from rule` to create an incident that inherits the rule's severity.
|
||||||
|
::::
|
||||||
|
|
||||||
|
Body
|
||||||
|
: A JSON payload that includes additional parameters to be included in the API request.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"details": "This is an example incident",
|
||||||
|
"type": "Unclassified"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Connector networking configuration [xsoar-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.
|
BIN
docs/reference/images/xsoar-connector.png
Normal file
BIN
docs/reference/images/xsoar-connector.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 76 KiB |
BIN
docs/reference/images/xsoar-params-test.png
Normal file
BIN
docs/reference/images/xsoar-params-test.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 46 KiB |
|
@ -58,6 +58,7 @@ toc:
|
||||||
- file: connectors-kibana/webhook-action-type.md
|
- file: connectors-kibana/webhook-action-type.md
|
||||||
- file: connectors-kibana/cases-webhook-action-type.md
|
- file: connectors-kibana/cases-webhook-action-type.md
|
||||||
- file: connectors-kibana/xmatters-action-type.md
|
- file: connectors-kibana/xmatters-action-type.md
|
||||||
|
- file: connectors-kibana/xsoar-action-type.md
|
||||||
- file: connectors-kibana/pre-configured-connectors.md
|
- file: connectors-kibana/pre-configured-connectors.md
|
||||||
- file: kibana-plugins.md
|
- file: kibana-plugins.md
|
||||||
- file: commands.md
|
- file: commands.md
|
||||||
|
@ -65,4 +66,4 @@ toc:
|
||||||
- file: commands/kibana-encryption-keys.md
|
- file: commands/kibana-encryption-keys.md
|
||||||
- file: commands/kibana-verification-code.md
|
- file: commands/kibana-verification-code.md
|
||||||
- file: osquery-exported-fields.md
|
- file: osquery-exported-fields.md
|
||||||
- file: osquery-manager-prebuilt-packs.md
|
- file: osquery-manager-prebuilt-packs.md
|
||||||
|
|
|
@ -39559,3 +39559,294 @@ Object {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`Connector type config checks detect connector type changes for: .xsoar 1`] = `
|
||||||
|
Object {
|
||||||
|
"flags": Object {
|
||||||
|
"default": Object {
|
||||||
|
"special": "deep",
|
||||||
|
},
|
||||||
|
"error": [Function],
|
||||||
|
"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",
|
||||||
|
},
|
||||||
|
"createInvestigation": Object {
|
||||||
|
"flags": Object {
|
||||||
|
"error": [Function],
|
||||||
|
},
|
||||||
|
"type": "boolean",
|
||||||
|
},
|
||||||
|
"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",
|
||||||
|
},
|
||||||
|
"name": Object {
|
||||||
|
"flags": Object {
|
||||||
|
"error": [Function],
|
||||||
|
},
|
||||||
|
"rules": Array [
|
||||||
|
Object {
|
||||||
|
"args": Object {
|
||||||
|
"method": [Function],
|
||||||
|
},
|
||||||
|
"name": "custom",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
|
"playbookId": 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",
|
||||||
|
},
|
||||||
|
"severity": Object {
|
||||||
|
"flags": Object {
|
||||||
|
"error": [Function],
|
||||||
|
},
|
||||||
|
"type": "number",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"type": "object",
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Connector type config checks detect connector type changes for: .xsoar 2`] = `
|
||||||
|
Object {
|
||||||
|
"flags": Object {
|
||||||
|
"default": Object {
|
||||||
|
"special": "deep",
|
||||||
|
},
|
||||||
|
"error": [Function],
|
||||||
|
"presence": "optional",
|
||||||
|
},
|
||||||
|
"keys": Object {
|
||||||
|
"url": Object {
|
||||||
|
"flags": Object {
|
||||||
|
"error": [Function],
|
||||||
|
},
|
||||||
|
"rules": Array [
|
||||||
|
Object {
|
||||||
|
"args": Object {
|
||||||
|
"method": [Function],
|
||||||
|
},
|
||||||
|
"name": "custom",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"type": "object",
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Connector type config checks detect connector type changes for: .xsoar 3`] = `
|
||||||
|
Object {
|
||||||
|
"flags": Object {
|
||||||
|
"default": Object {
|
||||||
|
"special": "deep",
|
||||||
|
},
|
||||||
|
"error": [Function],
|
||||||
|
"presence": "optional",
|
||||||
|
},
|
||||||
|
"keys": Object {
|
||||||
|
"apiKey": Object {
|
||||||
|
"flags": Object {
|
||||||
|
"error": [Function],
|
||||||
|
},
|
||||||
|
"rules": Array [
|
||||||
|
Object {
|
||||||
|
"args": Object {
|
||||||
|
"method": [Function],
|
||||||
|
},
|
||||||
|
"name": "custom",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
|
"apiKeyID": 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",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"type": "object",
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Connector type config checks detect connector type changes for: .xsoar 4`] = `
|
||||||
|
Object {
|
||||||
|
"flags": Object {
|
||||||
|
"default": Object {
|
||||||
|
"special": "deep",
|
||||||
|
},
|
||||||
|
"error": [Function],
|
||||||
|
"presence": "optional",
|
||||||
|
},
|
||||||
|
"keys": Object {
|
||||||
|
"subAction": Object {
|
||||||
|
"flags": Object {
|
||||||
|
"error": [Function],
|
||||||
|
},
|
||||||
|
"rules": Array [
|
||||||
|
Object {
|
||||||
|
"args": Object {
|
||||||
|
"method": [Function],
|
||||||
|
},
|
||||||
|
"name": "custom",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
|
"subActionParams": Object {
|
||||||
|
"flags": Object {
|
||||||
|
"default": Object {
|
||||||
|
"special": "deep",
|
||||||
|
},
|
||||||
|
"error": [Function],
|
||||||
|
"presence": "optional",
|
||||||
|
"unknown": true,
|
||||||
|
},
|
||||||
|
"keys": Object {},
|
||||||
|
"preferences": Object {
|
||||||
|
"stripUnknown": Object {
|
||||||
|
"objects": false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"type": "object",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"type": "object",
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
|
@ -30,6 +30,7 @@ export const connectorTypes: string[] = [
|
||||||
'.d3security',
|
'.d3security',
|
||||||
'.resilient',
|
'.resilient',
|
||||||
'.thehive',
|
'.thehive',
|
||||||
|
'.xsoar',
|
||||||
'.sentinelone',
|
'.sentinelone',
|
||||||
'.crowdstrike',
|
'.crowdstrike',
|
||||||
'.inference',
|
'.inference',
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
/*
|
||||||
|
* 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 { i18n } from '@kbn/i18n';
|
||||||
|
|
||||||
|
export const XSOAR_TITLE = i18n.translate(
|
||||||
|
'xpack.stackConnectors.components.xsoar.connectorTypeTitle',
|
||||||
|
{
|
||||||
|
defaultMessage: 'XSOAR',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
export const XSOAR_CONNECTOR_ID = '.xsoar';
|
||||||
|
export enum SUB_ACTION {
|
||||||
|
PLAYBOOKS = 'getPlaybooks',
|
||||||
|
RUN = 'run',
|
||||||
|
}
|
||||||
|
export enum XSOARSeverity {
|
||||||
|
INFORMATIONAL = 0.5,
|
||||||
|
UNKNOWN = 0,
|
||||||
|
LOW = 1,
|
||||||
|
MEDIUM = 2,
|
||||||
|
HIGH = 3,
|
||||||
|
CRITICAL = 4,
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
/*
|
||||||
|
* 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 { schema } from '@kbn/config-schema';
|
||||||
|
import { SUB_ACTION } from './constants';
|
||||||
|
|
||||||
|
export const ConfigSchema = schema.object({
|
||||||
|
url: schema.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const SecretsSchema = schema.object({
|
||||||
|
apiKey: schema.string(),
|
||||||
|
apiKeyID: schema.nullable(schema.string()),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const XSOARPlaybooksActionParamsSchema = null;
|
||||||
|
export const XSOARPlaybooksObjectSchema = schema.object(
|
||||||
|
{
|
||||||
|
id: schema.string(),
|
||||||
|
name: schema.string(),
|
||||||
|
},
|
||||||
|
{ unknowns: 'ignore' }
|
||||||
|
);
|
||||||
|
export const XSOARPlaybooksActionResponseSchema = schema.object(
|
||||||
|
{
|
||||||
|
playbooks: schema.arrayOf(XSOARPlaybooksObjectSchema),
|
||||||
|
},
|
||||||
|
{ unknowns: 'ignore' }
|
||||||
|
);
|
||||||
|
|
||||||
|
export const XSOARRunActionParamsSchema = schema.object({
|
||||||
|
name: schema.string(),
|
||||||
|
playbookId: schema.nullable(schema.string()),
|
||||||
|
createInvestigation: schema.boolean(),
|
||||||
|
severity: schema.number(),
|
||||||
|
isRuleSeverity: schema.nullable(schema.boolean({ defaultValue: false })),
|
||||||
|
body: schema.nullable(schema.string()),
|
||||||
|
});
|
||||||
|
export const XSOARRunActionResponseSchema = schema.object({}, { unknowns: 'ignore' });
|
||||||
|
|
||||||
|
export const ExecutorParamsSchema = schema.oneOf([
|
||||||
|
schema.object({
|
||||||
|
subAction: schema.literal(SUB_ACTION.PLAYBOOKS),
|
||||||
|
subActionParams: schema.literal(null), // this subaction not required any value as params
|
||||||
|
}),
|
||||||
|
schema.object({
|
||||||
|
subAction: schema.literal(SUB_ACTION.RUN),
|
||||||
|
subActionParams: XSOARRunActionParamsSchema,
|
||||||
|
}),
|
||||||
|
]);
|
|
@ -0,0 +1,26 @@
|
||||||
|
/*
|
||||||
|
* 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 { TypeOf } from '@kbn/config-schema';
|
||||||
|
import type {
|
||||||
|
ConfigSchema,
|
||||||
|
SecretsSchema,
|
||||||
|
XSOARRunActionParamsSchema,
|
||||||
|
XSOARRunActionResponseSchema,
|
||||||
|
XSOARPlaybooksObjectSchema,
|
||||||
|
XSOARPlaybooksActionResponseSchema,
|
||||||
|
ExecutorParamsSchema,
|
||||||
|
} from './schema';
|
||||||
|
|
||||||
|
export type Config = TypeOf<typeof ConfigSchema>;
|
||||||
|
export type Secrets = TypeOf<typeof SecretsSchema>;
|
||||||
|
export type XSOARRunActionParams = TypeOf<typeof XSOARRunActionParamsSchema>;
|
||||||
|
export type XSOARRunActionResponse = TypeOf<typeof XSOARRunActionResponseSchema>;
|
||||||
|
export type XSOARPlaybooksActionParams = void;
|
||||||
|
export type XSOARPlaybooksObject = TypeOf<typeof XSOARPlaybooksObjectSchema>;
|
||||||
|
export type XSOARPlaybooksActionResponse = TypeOf<typeof XSOARPlaybooksActionResponseSchema>;
|
||||||
|
export type ExecutorParams = TypeOf<typeof ExecutorParamsSchema>;
|
|
@ -36,6 +36,7 @@ import { ExperimentalFeaturesService } from '../common/experimental_features_ser
|
||||||
import { getSentinelOneConnectorType } from './sentinelone';
|
import { getSentinelOneConnectorType } from './sentinelone';
|
||||||
import { getTheHiveConnectorType } from './thehive';
|
import { getTheHiveConnectorType } from './thehive';
|
||||||
import { getCrowdStrikeConnectorType } from './crowdstrike';
|
import { getCrowdStrikeConnectorType } from './crowdstrike';
|
||||||
|
import { getXSOARConnectorType } from './xsoar';
|
||||||
|
|
||||||
export interface RegistrationServices {
|
export interface RegistrationServices {
|
||||||
validateEmailAddresses: (
|
validateEmailAddresses: (
|
||||||
|
@ -75,6 +76,7 @@ export function registerConnectorTypes({
|
||||||
connectorTypeRegistry.register(getTinesConnectorType());
|
connectorTypeRegistry.register(getTinesConnectorType());
|
||||||
connectorTypeRegistry.register(getD3SecurityConnectorType());
|
connectorTypeRegistry.register(getD3SecurityConnectorType());
|
||||||
connectorTypeRegistry.register(getTheHiveConnectorType());
|
connectorTypeRegistry.register(getTheHiveConnectorType());
|
||||||
|
connectorTypeRegistry.register(getXSOARConnectorType());
|
||||||
|
|
||||||
if (ExperimentalFeaturesService.get().sentinelOneConnectorOn) {
|
if (ExperimentalFeaturesService.get().sentinelOneConnectorOn) {
|
||||||
connectorTypeRegistry.register(getSentinelOneConnectorType());
|
connectorTypeRegistry.register(getSentinelOneConnectorType());
|
||||||
|
|
|
@ -0,0 +1,113 @@
|
||||||
|
/*
|
||||||
|
* 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 React from 'react';
|
||||||
|
import XSOARConnectorFields from './connector';
|
||||||
|
import { ConnectorFormTestProvider } from '../lib/test_utils';
|
||||||
|
import { act, render, waitFor } from '@testing-library/react';
|
||||||
|
import userEvent from '@testing-library/user-event';
|
||||||
|
|
||||||
|
jest.mock('@kbn/triggers-actions-ui-plugin/public/common/lib/kibana');
|
||||||
|
|
||||||
|
describe('XSOARActionConnectorFields renders', () => {
|
||||||
|
const actionConnector = {
|
||||||
|
actionTypeId: '.xsoar',
|
||||||
|
name: 'XSOAR',
|
||||||
|
config: {
|
||||||
|
url: 'https://test.com',
|
||||||
|
},
|
||||||
|
secrets: {
|
||||||
|
apiKey: 'apiKey',
|
||||||
|
},
|
||||||
|
isDeprecated: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
it('XSOAR connector fields are rendered', () => {
|
||||||
|
const { getByTestId } = render(
|
||||||
|
<ConnectorFormTestProvider connector={actionConnector}>
|
||||||
|
<XSOARConnectorFields
|
||||||
|
readOnly={false}
|
||||||
|
isEdit={false}
|
||||||
|
registerPreSubmitValidator={() => {}}
|
||||||
|
/>
|
||||||
|
</ConnectorFormTestProvider>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(getByTestId('config.url-input')).toBeInTheDocument();
|
||||||
|
expect(getByTestId('secrets.apiKey-input')).toBeInTheDocument();
|
||||||
|
expect(getByTestId('secrets.apiKeyID-input')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Validation', () => {
|
||||||
|
const onSubmit = jest.fn();
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
const tests: Array<[string, string]> = [
|
||||||
|
['config.url-input', 'not-valid'],
|
||||||
|
['secrets.apiKey-input', ''],
|
||||||
|
];
|
||||||
|
|
||||||
|
it('connector validation succeeds when connector config is valid', async () => {
|
||||||
|
const { getByTestId } = render(
|
||||||
|
<ConnectorFormTestProvider connector={actionConnector} onSubmit={onSubmit}>
|
||||||
|
<XSOARConnectorFields
|
||||||
|
readOnly={false}
|
||||||
|
isEdit={false}
|
||||||
|
registerPreSubmitValidator={() => {}}
|
||||||
|
/>
|
||||||
|
</ConnectorFormTestProvider>
|
||||||
|
);
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
await userEvent.click(getByTestId('form-test-provide-submit'));
|
||||||
|
});
|
||||||
|
|
||||||
|
waitFor(() => {
|
||||||
|
expect(onSubmit).toBeCalledWith({
|
||||||
|
data: {
|
||||||
|
actionTypeId: '.xsoar',
|
||||||
|
name: 'XSOAR',
|
||||||
|
config: {
|
||||||
|
url: 'https://test.com',
|
||||||
|
},
|
||||||
|
secrets: {
|
||||||
|
apiKey: 'apiKey',
|
||||||
|
},
|
||||||
|
isDeprecated: false,
|
||||||
|
},
|
||||||
|
isValid: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each(tests)('validates correctly %p', async (field, value) => {
|
||||||
|
const res = render(
|
||||||
|
<ConnectorFormTestProvider connector={actionConnector} onSubmit={onSubmit}>
|
||||||
|
<XSOARConnectorFields
|
||||||
|
readOnly={false}
|
||||||
|
isEdit={false}
|
||||||
|
registerPreSubmitValidator={() => {}}
|
||||||
|
/>
|
||||||
|
</ConnectorFormTestProvider>
|
||||||
|
);
|
||||||
|
|
||||||
|
await userEvent.clear(res.getByTestId(field));
|
||||||
|
if (value !== '') {
|
||||||
|
await userEvent.type(res.getByTestId(field), value, {
|
||||||
|
delay: 10,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await userEvent.click(res.getByTestId('form-test-provide-submit'));
|
||||||
|
|
||||||
|
expect(onSubmit).toHaveBeenCalledWith({ data: {}, isValid: false });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,45 @@
|
||||||
|
/*
|
||||||
|
* 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 React from 'react';
|
||||||
|
import { ActionConnectorFieldsProps } from '@kbn/triggers-actions-ui-plugin/public';
|
||||||
|
import {
|
||||||
|
ConfigFieldSchema,
|
||||||
|
SimpleConnectorForm,
|
||||||
|
SecretsFieldSchema,
|
||||||
|
} from '@kbn/triggers-actions-ui-plugin/public';
|
||||||
|
|
||||||
|
import { URL_LABEL, API_KEY_LABEL, API_KEY_ID_LABEL, API_KEY_ID_HELP_TEXT } from './translations';
|
||||||
|
|
||||||
|
const configFormSchema: ConfigFieldSchema[] = [{ id: 'url', label: URL_LABEL, isUrlField: true }];
|
||||||
|
|
||||||
|
const secretsFormSchema: SecretsFieldSchema[] = [
|
||||||
|
{ id: 'apiKey', label: API_KEY_LABEL, isPasswordField: true },
|
||||||
|
{
|
||||||
|
id: 'apiKeyID',
|
||||||
|
label: API_KEY_ID_LABEL,
|
||||||
|
isPasswordField: true,
|
||||||
|
isRequired: false,
|
||||||
|
helpText: API_KEY_ID_HELP_TEXT,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const XSOARConnectorFields: React.FC<ActionConnectorFieldsProps> = ({ readOnly, isEdit }) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SimpleConnectorForm
|
||||||
|
isEdit={isEdit}
|
||||||
|
readOnly={readOnly}
|
||||||
|
configFormSchema={configFormSchema}
|
||||||
|
secretsFormSchema={secretsFormSchema}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// eslint-disable-next-line import/no-default-export
|
||||||
|
export { XSOARConnectorFields as default };
|
|
@ -0,0 +1,66 @@
|
||||||
|
/*
|
||||||
|
* 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 { i18n } from '@kbn/i18n';
|
||||||
|
import { XSOARSeverity } from '../../../common/xsoar/constants';
|
||||||
|
|
||||||
|
export const severityOptions = [
|
||||||
|
{
|
||||||
|
value: XSOARSeverity.UNKNOWN,
|
||||||
|
text: i18n.translate(
|
||||||
|
'xpack.stackConnectors.components.xsoar.eventSelectSeverityUnknownOptionLabel',
|
||||||
|
{
|
||||||
|
defaultMessage: 'Unknown',
|
||||||
|
}
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: XSOARSeverity.INFORMATIONAL,
|
||||||
|
text: i18n.translate(
|
||||||
|
'xpack.stackConnectors.components.xsoar.eventSelectSeverityInformationalOptionLabel',
|
||||||
|
{
|
||||||
|
defaultMessage: 'Informational',
|
||||||
|
}
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: XSOARSeverity.LOW,
|
||||||
|
text: i18n.translate(
|
||||||
|
'xpack.stackConnectors.components.xsoar.eventSelectSeverityLowOptionLabel',
|
||||||
|
{
|
||||||
|
defaultMessage: 'Low',
|
||||||
|
}
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: XSOARSeverity.MEDIUM,
|
||||||
|
text: i18n.translate(
|
||||||
|
'xpack.stackConnectors.components.xsoar.eventSelectSeverityMediumOptionLabel',
|
||||||
|
{
|
||||||
|
defaultMessage: 'Medium',
|
||||||
|
}
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: XSOARSeverity.HIGH,
|
||||||
|
text: i18n.translate(
|
||||||
|
'xpack.stackConnectors.components.xsoar.eventSelectSeverityHighOptionLabel',
|
||||||
|
{
|
||||||
|
defaultMessage: 'High',
|
||||||
|
}
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: XSOARSeverity.CRITICAL,
|
||||||
|
text: i18n.translate(
|
||||||
|
'xpack.stackConnectors.components.xsoar.eventSelectSeverityCriticalOptionLabel',
|
||||||
|
{
|
||||||
|
defaultMessage: 'Critical',
|
||||||
|
}
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
|
@ -0,0 +1,8 @@
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export { getConnectorType as getXSOARConnectorType } from './xsoar';
|
|
@ -0,0 +1,45 @@
|
||||||
|
/*
|
||||||
|
* 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 React from 'react';
|
||||||
|
import { LogoProps } from '../types';
|
||||||
|
|
||||||
|
const Logo = (props: LogoProps) => (
|
||||||
|
<svg
|
||||||
|
version="1.1"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="180"
|
||||||
|
height="180"
|
||||||
|
viewBox="0 0 180 180"
|
||||||
|
fill="none"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M0 0 C59.4 0 118.8 0 180 0 C180 59.4 180 118.8 180 180 C120.6 180 61.2 180 0 180 C0 120.6 0 61.2 0 0 Z "
|
||||||
|
fill="#FEFEFE"
|
||||||
|
transform="translate(0,0)"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M0 0 C0 11.88 0 23.76 0 36 C5.445 36.495 5.445 36.495 11 37 C23.89312642 40.73692232 34.87162395 48.36931836 41.76171875 59.96484375 C48.07437887 72.08576258 50.43971101 84.47923625 46.5637207 97.89599609 C42.26551236 111.05035692 34.17899054 121.54046247 21.75 127.875 C14.04399747 131.47617112 9.1053914 132.24121738 0 133 C0 144.88 0 156.76 0 169 C-24.23522933 169 -43.35264863 160.25811545 -60.8125 143.5625 C-76.84026319 126.78388665 -84.872243 104.0210434 -84.51928711 80.99316406 C-83.71001346 60.47708128 -75.31748211 42.39007039 -62 27 C-61.42765625 26.32710937 -60.8553125 25.65421875 -60.265625 24.9609375 C-44.93602384 8.34122139 -22.06892026 0 0 0 Z "
|
||||||
|
fill="#02CC67"
|
||||||
|
transform="translate(108,5)"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M0 0 C10.52864676 8.69370494 16.43235973 20.33764021 17.97265625 33.8515625 C18.50961099 47.3884742 14.58471229 59.93950836 5.55859375 70.21875 C-4.4347226 80.75174265 -16.58867412 85.97534344 -31.00390625 86.53125 C-45.1565789 86.31948931 -56.9777179 80.70659268 -66.85546875 70.796875 C-78.06743615 57.6112654 -79.64663933 43.88512024 -78.44140625 27.21875 C-77.64997359 23.15304735 -76.436914 19.8367734 -74.44140625 16.21875 C-74.01730469 15.44789062 -73.59320313 14.67703125 -73.15625 13.8828125 C-65.71409762 1.73489804 -54.89874847 -6.13554065 -41.31640625 -10.21875 C-25.93977477 -12.45084167 -12.56035967 -9.16403842 0 0 Z "
|
||||||
|
fill="#FBFEFC"
|
||||||
|
transform="translate(138.44140625,51.78125)"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M0 0 C13.74396568 0 23.98108874 3.58656823 34.12109375 12.9609375 C44.49350337 23.82353661 48.60984892 36.24760551 48.375 50.984375 C47.48222761 64.08262134 41.08555096 75.39512537 31.6875 84.3125 C22.17264545 92.36353078 12.39116947 94.96740254 0 96 C0 64.32 0 32.64 0 0 Z "
|
||||||
|
fill="#01CC66"
|
||||||
|
transform="translate(108,42)"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
|
||||||
|
// eslint-disable-next-line import/no-default-export
|
||||||
|
export { Logo as default };
|
|
@ -0,0 +1,364 @@
|
||||||
|
/*
|
||||||
|
* 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 React from 'react';
|
||||||
|
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
||||||
|
import userEvent from '@testing-library/user-event';
|
||||||
|
import { ActionConnector, ActionConnectorMode } from '@kbn/triggers-actions-ui-plugin/public/types';
|
||||||
|
import XSOARParamsFields from './params';
|
||||||
|
import type { UseSubActionParams } from '@kbn/triggers-actions-ui-plugin/public/application/hooks/use_sub_action';
|
||||||
|
import { SUB_ACTION } from '../../../common/xsoar/constants';
|
||||||
|
import { ExecutorParams, XSOARRunActionParams } from '../../../common/xsoar/types';
|
||||||
|
import * as translations from './translations';
|
||||||
|
|
||||||
|
interface Result {
|
||||||
|
isLoading: boolean;
|
||||||
|
response: Record<string, unknown>;
|
||||||
|
error: null | Error;
|
||||||
|
}
|
||||||
|
|
||||||
|
const triggersActionsPath = '@kbn/triggers-actions-ui-plugin/public';
|
||||||
|
|
||||||
|
const response = {
|
||||||
|
playbooks: [
|
||||||
|
{
|
||||||
|
id: '8db0105c-f674-4d83-8095-f95a9f61e77a',
|
||||||
|
version: 4,
|
||||||
|
cacheVersn: 0,
|
||||||
|
sequenceNumber: 33831652,
|
||||||
|
primaryTerm: 11,
|
||||||
|
modified: '2023-12-12T13:51:15.668021556Z',
|
||||||
|
sizeInBytes: 0,
|
||||||
|
packID: '',
|
||||||
|
packName: '',
|
||||||
|
itemVersion: '',
|
||||||
|
fromServerVersion: '',
|
||||||
|
toServerVersion: '',
|
||||||
|
propagationLabels: ['all'],
|
||||||
|
definitionId: '',
|
||||||
|
vcShouldIgnore: false,
|
||||||
|
vcShouldKeepItemLegacyProdMachine: false,
|
||||||
|
commitMessage: '',
|
||||||
|
shouldCommit: false,
|
||||||
|
name: 'playbook0',
|
||||||
|
nameRaw: 'playbook0',
|
||||||
|
prevName: 'aaa',
|
||||||
|
startTaskId: '0',
|
||||||
|
tasks: {
|
||||||
|
'0': {
|
||||||
|
id: '0',
|
||||||
|
taskId: 'e228a044-2ad5-4ab0-873a-d5bb94a5c1b4',
|
||||||
|
type: 'start',
|
||||||
|
task: {
|
||||||
|
id: 'e228a044-2ad5-4ab0-873a-d5bb94a5c1b4',
|
||||||
|
version: 1,
|
||||||
|
cacheVersn: 0,
|
||||||
|
sequenceNumber: 13431901,
|
||||||
|
primaryTerm: 8,
|
||||||
|
modified: '2023-05-23T07:16:19.930125981Z',
|
||||||
|
sizeInBytes: 0,
|
||||||
|
},
|
||||||
|
nextTasks: {
|
||||||
|
'#none#': ['1'],
|
||||||
|
},
|
||||||
|
continueOnErrorType: '',
|
||||||
|
view: {
|
||||||
|
position: {
|
||||||
|
x: 450,
|
||||||
|
y: 50,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
evidenceData: {},
|
||||||
|
},
|
||||||
|
'1': {
|
||||||
|
id: '1',
|
||||||
|
taskId: 'c28b63d3-c860-4e16-82b4-6db6b58bdee3',
|
||||||
|
type: 'regular',
|
||||||
|
task: {
|
||||||
|
id: 'c28b63d3-c860-4e16-82b4-6db6b58bdee3',
|
||||||
|
version: 1,
|
||||||
|
cacheVersn: 0,
|
||||||
|
sequenceNumber: 33831651,
|
||||||
|
primaryTerm: 11,
|
||||||
|
modified: '2023-12-12T13:51:15.604271789Z',
|
||||||
|
sizeInBytes: 0,
|
||||||
|
name: 'Untitled Task 1',
|
||||||
|
description: 'commands.local.cmd.set.incident',
|
||||||
|
scriptId: 'Builtin|||setIncident',
|
||||||
|
type: 'regular',
|
||||||
|
isCommand: true,
|
||||||
|
brand: 'Builtin',
|
||||||
|
},
|
||||||
|
scriptArguments: {
|
||||||
|
severity: {
|
||||||
|
simple: '1',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
continueOnErrorType: '',
|
||||||
|
view: {
|
||||||
|
position: {
|
||||||
|
x: 450,
|
||||||
|
y: 200,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
evidenceData: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
taskIds: ['e228a044-2ad5-4ab0-873a-d5bb94a5c1b4', 'c28b63d3-c860-4e16-82b4-6db6b58bdee3'],
|
||||||
|
scriptIds: [],
|
||||||
|
commands: ['setIncident'],
|
||||||
|
brands: ['Builtin'],
|
||||||
|
missingScriptsIds: ['Builtin|||setIncident'],
|
||||||
|
view: {
|
||||||
|
linkLabelsPosition: {},
|
||||||
|
paper: {
|
||||||
|
dimensions: {
|
||||||
|
height: 245,
|
||||||
|
width: 380,
|
||||||
|
x: 450,
|
||||||
|
y: 50,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
inputs: null,
|
||||||
|
outputs: null,
|
||||||
|
quiet: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
tags: [
|
||||||
|
'Phishing',
|
||||||
|
'Sandbox',
|
||||||
|
'Severity',
|
||||||
|
'Malware',
|
||||||
|
'Remediation',
|
||||||
|
'Job',
|
||||||
|
'Sinkhole',
|
||||||
|
'TIM',
|
||||||
|
'PAN-OS',
|
||||||
|
],
|
||||||
|
total: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockUseSubActionPlaybooks = jest.fn().mockImplementation(() => ({
|
||||||
|
isLoading: false,
|
||||||
|
response,
|
||||||
|
error: null,
|
||||||
|
}));
|
||||||
|
const mockUseSubAction = jest.fn<Result, [UseSubActionParams<unknown>]>(mockUseSubActionPlaybooks);
|
||||||
|
|
||||||
|
const mockToasts = { danger: jest.fn(), warning: jest.fn() };
|
||||||
|
jest.mock(triggersActionsPath, () => {
|
||||||
|
const original = jest.requireActual(triggersActionsPath);
|
||||||
|
return {
|
||||||
|
...original,
|
||||||
|
useSubAction: (params: UseSubActionParams<unknown>) => mockUseSubAction(params),
|
||||||
|
useKibana: () => ({
|
||||||
|
...original.useKibana(),
|
||||||
|
notifications: { toasts: mockToasts },
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('XSOARParamsFields renders', () => {
|
||||||
|
const subActionParams: XSOARRunActionParams = {
|
||||||
|
name: 'new incident',
|
||||||
|
playbookId: '8db0105c-f674-4d83-8095-f95a9f61e77a',
|
||||||
|
createInvestigation: false,
|
||||||
|
severity: 2,
|
||||||
|
isRuleSeverity: false,
|
||||||
|
body: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
const actionParams: ExecutorParams = {
|
||||||
|
subAction: SUB_ACTION.RUN,
|
||||||
|
subActionParams,
|
||||||
|
};
|
||||||
|
const connector: ActionConnector = {
|
||||||
|
secrets: {},
|
||||||
|
config: {},
|
||||||
|
id: 'test',
|
||||||
|
actionTypeId: '.test',
|
||||||
|
name: 'Test',
|
||||||
|
isPreconfigured: false,
|
||||||
|
isDeprecated: false,
|
||||||
|
isSystemAction: false as const,
|
||||||
|
};
|
||||||
|
|
||||||
|
const editAction = jest.fn();
|
||||||
|
const defaultProps = {
|
||||||
|
actionConnector: connector,
|
||||||
|
actionParams,
|
||||||
|
editAction,
|
||||||
|
errors: { name: [] },
|
||||||
|
index: 0,
|
||||||
|
messageVariables: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('New connector', () => {
|
||||||
|
it('should render empty run form', () => {
|
||||||
|
const props = { ...defaultProps, actionParams: {} };
|
||||||
|
const { getByTestId } = render(<XSOARParamsFields {...props} />);
|
||||||
|
|
||||||
|
expect(getByTestId('nameInput')).toBeInTheDocument();
|
||||||
|
expect(getByTestId('xsoar-playbookSelector')).toBeInTheDocument();
|
||||||
|
expect(getByTestId('rule-severity-toggle')).toBeInTheDocument();
|
||||||
|
expect(getByTestId('bodyJsonEditor')).toBeInTheDocument();
|
||||||
|
|
||||||
|
expect(getByTestId('rule-severity-toggle')).not.toBeChecked();
|
||||||
|
expect(getByTestId('bodyJsonEditor')).toHaveProperty('value', '');
|
||||||
|
|
||||||
|
expect(editAction).toHaveBeenCalledWith('subAction', SUB_ACTION.RUN, 0);
|
||||||
|
expect(editAction).toHaveBeenCalledWith(
|
||||||
|
'subActionParams',
|
||||||
|
{ createInvestigation: false, severity: 0 },
|
||||||
|
0
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render empty test form', () => {
|
||||||
|
const props = { ...defaultProps, actionParams: {}, executionMode: ActionConnectorMode.Test };
|
||||||
|
const { getByTestId } = render(<XSOARParamsFields {...props} />);
|
||||||
|
|
||||||
|
expect(getByTestId('nameInput')).toBeInTheDocument();
|
||||||
|
expect(getByTestId('xsoar-playbookSelector')).toBeInTheDocument();
|
||||||
|
expect(getByTestId('severitySelectInput')).toBeInTheDocument();
|
||||||
|
expect(getByTestId('bodyJsonEditor')).toBeInTheDocument();
|
||||||
|
|
||||||
|
expect(getByTestId('severitySelectInput')).toHaveValue('0');
|
||||||
|
expect(getByTestId('bodyJsonEditor')).toHaveProperty('value', '');
|
||||||
|
|
||||||
|
expect(editAction).toHaveBeenCalledWith('subAction', SUB_ACTION.RUN, 0);
|
||||||
|
expect(editAction).toHaveBeenCalledWith(
|
||||||
|
'subActionParams',
|
||||||
|
{ createInvestigation: false, severity: 0 },
|
||||||
|
0
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should renders playbook selector and start investigation toggle after playbook selection', async () => {
|
||||||
|
const props = { ...defaultProps, actionParams: {} };
|
||||||
|
render(<XSOARParamsFields {...props} />);
|
||||||
|
|
||||||
|
expect(mockUseSubActionPlaybooks).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({ subAction: 'getPlaybooks' })
|
||||||
|
);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByTestId('comboBoxSearchInput')).not.toBeDisabled();
|
||||||
|
});
|
||||||
|
|
||||||
|
await userEvent.click(screen.getByTestId('comboBoxSearchInput'));
|
||||||
|
expect(screen.getByText('playbook0')).toBeInTheDocument();
|
||||||
|
await userEvent.click(screen.getByText('playbook0'), { pointerEventsCheck: 0 });
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(editAction).toHaveBeenCalledTimes(3);
|
||||||
|
expect(editAction).toHaveBeenCalledWith('subAction', SUB_ACTION.RUN, 0);
|
||||||
|
expect(editAction).toHaveBeenCalledWith(
|
||||||
|
'subActionParams',
|
||||||
|
{ createInvestigation: false, severity: 0 },
|
||||||
|
0
|
||||||
|
);
|
||||||
|
expect(editAction).toHaveBeenCalledWith(
|
||||||
|
'subActionParams',
|
||||||
|
{
|
||||||
|
createInvestigation: false,
|
||||||
|
playbookId: '8db0105c-f674-4d83-8095-f95a9f61e77a',
|
||||||
|
severity: 0,
|
||||||
|
},
|
||||||
|
0
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(screen.getByTestId('createInvestigation-toggle')).toBeInTheDocument();
|
||||||
|
expect(screen.getByTestId('createInvestigation-toggle')).not.toBeChecked();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Edit connector', () => {
|
||||||
|
it('all Params fields is rendered', () => {
|
||||||
|
const { getByTestId } = render(<XSOARParamsFields {...defaultProps} />);
|
||||||
|
|
||||||
|
expect(mockUseSubActionPlaybooks).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({ subAction: 'getPlaybooks' })
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(getByTestId('nameInput')).toBeInTheDocument();
|
||||||
|
expect(getByTestId('xsoar-playbookSelector')).toBeInTheDocument();
|
||||||
|
expect(getByTestId('createInvestigation-toggle')).toBeInTheDocument();
|
||||||
|
expect(getByTestId('rule-severity-toggle')).toBeInTheDocument();
|
||||||
|
expect(getByTestId('severitySelectInput')).toBeInTheDocument();
|
||||||
|
expect(getByTestId('bodyJsonEditor')).toBeInTheDocument();
|
||||||
|
|
||||||
|
expect(getByTestId('nameInput')).toHaveValue('new incident');
|
||||||
|
expect(getByTestId('comboBoxSearchInput')).toHaveProperty('value', 'playbook0');
|
||||||
|
expect(getByTestId('createInvestigation-toggle')).not.toBeChecked();
|
||||||
|
expect(getByTestId('rule-severity-toggle')).not.toBeChecked();
|
||||||
|
expect(getByTestId('severitySelectInput')).toHaveValue('2');
|
||||||
|
expect(getByTestId('bodyJsonEditor')).toHaveProperty('value', '');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('hides the severity select input when rule severity is enabled', () => {
|
||||||
|
const { getByTestId } = render(<XSOARParamsFields {...defaultProps} />);
|
||||||
|
const ruleSeverityToggleEl = getByTestId('rule-severity-toggle');
|
||||||
|
|
||||||
|
fireEvent.click(ruleSeverityToggleEl);
|
||||||
|
expect(getByTestId('rule-severity-toggle')).toBeEnabled();
|
||||||
|
expect(editAction).toHaveBeenCalledWith(
|
||||||
|
'subActionParams',
|
||||||
|
{ ...subActionParams, severity: 2, isRuleSeverity: true },
|
||||||
|
0
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.queryByTestId('severitySelectInput')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show warning if playbook not found', () => {
|
||||||
|
const props = {
|
||||||
|
...defaultProps,
|
||||||
|
actionParams: { subActionParams: { ...subActionParams, playbookId: 'wrong-playbookId' } },
|
||||||
|
};
|
||||||
|
render(<XSOARParamsFields {...props} />);
|
||||||
|
|
||||||
|
expect(mockToasts.warning).toHaveBeenCalledWith({
|
||||||
|
title: translations.PLAYBOOK_NOT_FOUND_WARNING,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show error when playbooks subAction has error', () => {
|
||||||
|
const errorMessage = 'something broke';
|
||||||
|
mockUseSubActionPlaybooks.mockReturnValueOnce({
|
||||||
|
isLoading: false,
|
||||||
|
response,
|
||||||
|
error: new Error(errorMessage),
|
||||||
|
});
|
||||||
|
|
||||||
|
render(<XSOARParamsFields {...defaultProps} />);
|
||||||
|
|
||||||
|
expect(mockToasts.danger).toHaveBeenCalledWith({
|
||||||
|
title: translations.PLAYBOOKS_ERROR,
|
||||||
|
body: errorMessage,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles the case when subAction is undefined', () => {
|
||||||
|
const props = {
|
||||||
|
...defaultProps,
|
||||||
|
actionParams: {
|
||||||
|
...actionParams,
|
||||||
|
subAction: undefined,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
render(<XSOARParamsFields {...props} />);
|
||||||
|
expect(editAction).toHaveBeenCalledWith('subAction', SUB_ACTION.RUN, 0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,298 @@
|
||||||
|
/*
|
||||||
|
* 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 React, { useState, useEffect, useMemo, useCallback } from 'react';
|
||||||
|
import {
|
||||||
|
useSubAction,
|
||||||
|
useKibana,
|
||||||
|
ActionParamsProps,
|
||||||
|
JsonEditorWithMessageVariables,
|
||||||
|
TextFieldWithMessageVariables,
|
||||||
|
ActionConnectorMode,
|
||||||
|
} from '@kbn/triggers-actions-ui-plugin/public';
|
||||||
|
import {
|
||||||
|
EuiFormRow,
|
||||||
|
EuiComboBoxOptionOption,
|
||||||
|
EuiComboBox,
|
||||||
|
EuiFlexGroup,
|
||||||
|
EuiFlexItem,
|
||||||
|
EuiHighlight,
|
||||||
|
EuiSwitch,
|
||||||
|
EuiSelect,
|
||||||
|
} from '@elastic/eui';
|
||||||
|
import { SUB_ACTION, XSOARSeverity } from '../../../common/xsoar/constants';
|
||||||
|
import {
|
||||||
|
ExecutorParams,
|
||||||
|
XSOARRunActionParams,
|
||||||
|
XSOARPlaybooksActionResponse,
|
||||||
|
XSOARPlaybooksActionParams,
|
||||||
|
XSOARPlaybooksObject,
|
||||||
|
} from '../../../common/xsoar/types';
|
||||||
|
import * as translations from './translations';
|
||||||
|
import { severityOptions } from './constants';
|
||||||
|
|
||||||
|
type PlaybookOption = EuiComboBoxOptionOption<XSOARPlaybooksObject>;
|
||||||
|
|
||||||
|
const createOption = (playbook: XSOARPlaybooksObject): PlaybookOption => ({
|
||||||
|
key: playbook.id,
|
||||||
|
label: playbook.name,
|
||||||
|
});
|
||||||
|
|
||||||
|
const renderPlaybook = (
|
||||||
|
{ label }: PlaybookOption,
|
||||||
|
searchValue: string,
|
||||||
|
contentClassName: string
|
||||||
|
) => (
|
||||||
|
<EuiFlexGroup className={contentClassName} direction="row" alignItems="center">
|
||||||
|
<EuiFlexItem grow={false}>
|
||||||
|
<EuiHighlight search={searchValue}>{label}</EuiHighlight>
|
||||||
|
</EuiFlexItem>
|
||||||
|
</EuiFlexGroup>
|
||||||
|
);
|
||||||
|
|
||||||
|
const XSOARParamsFields: React.FunctionComponent<ActionParamsProps<ExecutorParams>> = ({
|
||||||
|
actionConnector,
|
||||||
|
actionParams,
|
||||||
|
editAction,
|
||||||
|
index,
|
||||||
|
errors,
|
||||||
|
messageVariables,
|
||||||
|
executionMode,
|
||||||
|
}) => {
|
||||||
|
const { toasts } = useKibana().notifications;
|
||||||
|
const isTest = executionMode === ActionConnectorMode.Test;
|
||||||
|
const incident = useMemo(
|
||||||
|
() =>
|
||||||
|
(actionParams.subActionParams as XSOARRunActionParams) ??
|
||||||
|
({
|
||||||
|
severity: XSOARSeverity.UNKNOWN,
|
||||||
|
createInvestigation: false,
|
||||||
|
} as unknown as XSOARRunActionParams),
|
||||||
|
|
||||||
|
[actionParams.subActionParams]
|
||||||
|
);
|
||||||
|
|
||||||
|
const [connectorId, setConnectorId] = useState<string | undefined>(actionConnector?.id);
|
||||||
|
const [selectedPlaybookOption, setSelectedPlaybookOption] = useState<
|
||||||
|
PlaybookOption | null | undefined
|
||||||
|
>();
|
||||||
|
const [isRuleSeverity, setIsRuleSeverity] = useState<boolean>(Boolean(incident.isRuleSeverity));
|
||||||
|
const [playbooks, setPlaybooks] = useState<XSOARPlaybooksObject[]>();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (actionConnector != null && connectorId !== actionConnector.id) {
|
||||||
|
setConnectorId(actionConnector?.id);
|
||||||
|
setSelectedPlaybookOption(null);
|
||||||
|
setIsRuleSeverity(isTest ? false : true);
|
||||||
|
editAction(
|
||||||
|
'subActionParams',
|
||||||
|
{
|
||||||
|
severity: XSOARSeverity.UNKNOWN,
|
||||||
|
createInvestigation: false,
|
||||||
|
},
|
||||||
|
index
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [actionConnector]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!actionParams.subAction) {
|
||||||
|
editAction('subAction', SUB_ACTION.RUN, index);
|
||||||
|
}
|
||||||
|
if (!actionParams.subActionParams) {
|
||||||
|
editAction('subActionParams', incident, index);
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [actionParams]);
|
||||||
|
|
||||||
|
const {
|
||||||
|
response: { playbooks: fetchedPlaybooks } = {},
|
||||||
|
isLoading: isLoadingPlaybooks,
|
||||||
|
error: playbooksError,
|
||||||
|
} = useSubAction<XSOARPlaybooksActionParams, XSOARPlaybooksActionResponse>({
|
||||||
|
connectorId,
|
||||||
|
subAction: 'getPlaybooks',
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (playbooksError) {
|
||||||
|
toasts.danger({ title: translations.PLAYBOOKS_ERROR, body: playbooksError.message });
|
||||||
|
setPlaybooks([]);
|
||||||
|
} else {
|
||||||
|
setPlaybooks(fetchedPlaybooks);
|
||||||
|
}
|
||||||
|
}, [toasts, playbooksError, fetchedPlaybooks]);
|
||||||
|
|
||||||
|
const playbooksOptions = useMemo(() => playbooks?.map(createOption) ?? [], [playbooks]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (selectedPlaybookOption === undefined && incident.playbookId && playbooks !== undefined) {
|
||||||
|
const selectedPlaybook = playbooks.find(({ id }) => id === incident.playbookId);
|
||||||
|
if (selectedPlaybook) {
|
||||||
|
setSelectedPlaybookOption(createOption(selectedPlaybook));
|
||||||
|
} else {
|
||||||
|
toasts.warning({ title: translations.PLAYBOOK_NOT_FOUND_WARNING });
|
||||||
|
editAction(
|
||||||
|
'subActionParams',
|
||||||
|
{ ...incident, playbookId: undefined, createInvestigation: false },
|
||||||
|
index
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
selectedPlaybookOption !== undefined &&
|
||||||
|
selectedPlaybookOption?.key !== incident.playbookId
|
||||||
|
) {
|
||||||
|
editAction(
|
||||||
|
'subActionParams',
|
||||||
|
{
|
||||||
|
...incident,
|
||||||
|
playbookId: selectedPlaybookOption?.key,
|
||||||
|
createInvestigation:
|
||||||
|
selectedPlaybookOption === null ? false : incident.createInvestigation,
|
||||||
|
},
|
||||||
|
index
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [selectedPlaybookOption, incident.playbookId, playbooks, toasts, editAction, index]);
|
||||||
|
|
||||||
|
const selectedPlaybookOptions = useMemo(
|
||||||
|
() => (selectedPlaybookOption ? [selectedPlaybookOption] : []),
|
||||||
|
[selectedPlaybookOption]
|
||||||
|
);
|
||||||
|
|
||||||
|
const onChangePlaybook = useCallback(([selected]: PlaybookOption[]) => {
|
||||||
|
setSelectedPlaybookOption(selected ?? null);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<TextFieldWithMessageVariables
|
||||||
|
index={index}
|
||||||
|
editAction={(key, value) => {
|
||||||
|
editAction('subActionParams', { ...incident, [key]: value }, index);
|
||||||
|
}}
|
||||||
|
messageVariables={messageVariables}
|
||||||
|
paramsProperty={'name'}
|
||||||
|
inputTargetValue={incident.name}
|
||||||
|
wrapField={true}
|
||||||
|
formRowProps={{
|
||||||
|
label: translations.NAME_LABEL,
|
||||||
|
fullWidth: true,
|
||||||
|
helpText: '',
|
||||||
|
isInvalid:
|
||||||
|
errors.name !== undefined &&
|
||||||
|
Number(errors.name.length) > 0 &&
|
||||||
|
incident.name !== undefined,
|
||||||
|
error: errors.name as string,
|
||||||
|
}}
|
||||||
|
errors={errors.name as string[]}
|
||||||
|
/>
|
||||||
|
<EuiFormRow
|
||||||
|
fullWidth
|
||||||
|
error={errors.playbook as string[]}
|
||||||
|
isInvalid={!!errors.playbook?.length && selectedPlaybookOption !== undefined}
|
||||||
|
label={translations.PLAYBOOK_LABEL}
|
||||||
|
helpText={translations.PLAYBOOK_HELP}
|
||||||
|
>
|
||||||
|
<EuiComboBox
|
||||||
|
aria-label={translations.PLAYBOOK_ARIA_LABEL}
|
||||||
|
placeholder={translations.PLAYBOOK_PLACEHOLDER}
|
||||||
|
singleSelection={{ asPlainText: true }}
|
||||||
|
options={playbooksOptions}
|
||||||
|
selectedOptions={selectedPlaybookOptions}
|
||||||
|
onChange={onChangePlaybook}
|
||||||
|
isDisabled={isLoadingPlaybooks}
|
||||||
|
isLoading={isLoadingPlaybooks}
|
||||||
|
renderOption={renderPlaybook}
|
||||||
|
fullWidth
|
||||||
|
data-test-subj="xsoar-playbookSelector"
|
||||||
|
/>
|
||||||
|
</EuiFormRow>
|
||||||
|
{selectedPlaybookOption && (
|
||||||
|
<EuiFormRow fullWidth>
|
||||||
|
<EuiSwitch
|
||||||
|
label={translations.START_INVESTIGATION_LABEL}
|
||||||
|
checked={incident.createInvestigation}
|
||||||
|
data-test-subj="createInvestigation-toggle"
|
||||||
|
onChange={(e) => {
|
||||||
|
editAction(
|
||||||
|
'subActionParams',
|
||||||
|
{
|
||||||
|
...incident,
|
||||||
|
createInvestigation: e.target.checked,
|
||||||
|
},
|
||||||
|
index
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</EuiFormRow>
|
||||||
|
)}
|
||||||
|
{!isTest && (
|
||||||
|
<EuiFormRow fullWidth>
|
||||||
|
<EuiSwitch
|
||||||
|
label={translations.IS_RULE_SEVERITY_LABEL}
|
||||||
|
checked={Boolean(isRuleSeverity)}
|
||||||
|
data-test-subj="rule-severity-toggle"
|
||||||
|
onChange={(e) => {
|
||||||
|
setIsRuleSeverity(e.target.checked);
|
||||||
|
editAction(
|
||||||
|
'subActionParams',
|
||||||
|
{
|
||||||
|
...incident,
|
||||||
|
isRuleSeverity: e.target.checked,
|
||||||
|
},
|
||||||
|
index
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</EuiFormRow>
|
||||||
|
)}
|
||||||
|
{!Boolean(isRuleSeverity) && (
|
||||||
|
<EuiFormRow fullWidth label={translations.SEVERITY_LABEL}>
|
||||||
|
<EuiSelect
|
||||||
|
fullWidth
|
||||||
|
data-test-subj="severitySelectInput"
|
||||||
|
disabled={Boolean(isRuleSeverity)}
|
||||||
|
value={incident.severity ?? severityOptions[0].value}
|
||||||
|
options={severityOptions}
|
||||||
|
onChange={(e) => {
|
||||||
|
editAction(
|
||||||
|
'subActionParams',
|
||||||
|
{ ...incident, severity: parseFloat(e.target.value) },
|
||||||
|
index
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</EuiFormRow>
|
||||||
|
)}
|
||||||
|
<JsonEditorWithMessageVariables
|
||||||
|
key={connectorId}
|
||||||
|
messageVariables={messageVariables}
|
||||||
|
paramsProperty={'body'}
|
||||||
|
inputTargetValue={incident.body}
|
||||||
|
label={translations.BODY_LABEL}
|
||||||
|
ariaLabel={translations.BODY_DESCRIPTION}
|
||||||
|
onDocumentsChange={(json: string) =>
|
||||||
|
editAction('subActionParams', { ...incident, body: json }, index)
|
||||||
|
}
|
||||||
|
dataTestSubj="xsoar-body"
|
||||||
|
onBlur={() => {
|
||||||
|
if (!incident.body) {
|
||||||
|
editAction('subActionParams', { ...incident, body: null }, index);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// eslint-disable-next-line import/no-default-export
|
||||||
|
export { XSOARParamsFields as default };
|
|
@ -0,0 +1,139 @@
|
||||||
|
/*
|
||||||
|
* 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 { i18n } from '@kbn/i18n';
|
||||||
|
|
||||||
|
export const URL_LABEL = i18n.translate('xpack.stackConnectors.components.xsoar.urlFieldLabel', {
|
||||||
|
defaultMessage: 'URL',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const SELECT_MESSAGE = i18n.translate(
|
||||||
|
'xpack.stackConnectors.components.xsoar.selectMessageText',
|
||||||
|
{
|
||||||
|
defaultMessage: 'Create an incident in XSOAR',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const API_KEY_LABEL = i18n.translate(
|
||||||
|
'xpack.stackConnectors.components.xsoar.apiKeyFieldLabel',
|
||||||
|
{
|
||||||
|
defaultMessage: 'API key',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const API_KEY_ID_LABEL = i18n.translate(
|
||||||
|
'xpack.stackConnectors.components.xsoar.apiKeyIDFieldLabel',
|
||||||
|
{
|
||||||
|
defaultMessage: 'API key ID',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const API_KEY_ID_HELP_TEXT = i18n.translate(
|
||||||
|
'xpack.stackConnectors.components.xsoar.apiKeyIDFieldHelpText',
|
||||||
|
{
|
||||||
|
defaultMessage:
|
||||||
|
'Enter the API key ID (the unique serial number for your API key) to authenticate with your XSOAR cloud instance.',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const NAME_LABEL = i18n.translate(
|
||||||
|
'xpack.stackConnectors.components.xsoar.params.nameFieldLabel',
|
||||||
|
{
|
||||||
|
defaultMessage: 'Name',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const NAME_REQUIRED = i18n.translate(
|
||||||
|
'xpack.stackConnectors.components.xsoar.params.error.requiredNameText',
|
||||||
|
{
|
||||||
|
defaultMessage: 'Incident name is required.',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const BODY_REQUIRED = i18n.translate(
|
||||||
|
'xpack.stackConnectors.components.xsoar.params.error.requiredBodyText',
|
||||||
|
{
|
||||||
|
defaultMessage: 'Body is required.',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const START_INVESTIGATION_LABEL = i18n.translate(
|
||||||
|
'xpack.stackConnectors.components.xsoar.params.startInvestigationToggleLabel',
|
||||||
|
{
|
||||||
|
defaultMessage: 'Start investigation',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const SEVERITY_LABEL = i18n.translate(
|
||||||
|
'xpack.stackConnectors.components.xsoar.params.severitySelectInputLabel',
|
||||||
|
{
|
||||||
|
defaultMessage: 'Severity',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const IS_RULE_SEVERITY_LABEL = i18n.translate(
|
||||||
|
'xpack.stackConnectors.components.xsoar.params.isRuleSeverityToggleLabel',
|
||||||
|
{
|
||||||
|
defaultMessage: 'Use severity assigned to the rule',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const PLAYBOOKS_ERROR = i18n.translate(
|
||||||
|
'xpack.stackConnectors.components.xsoar.params.componentError.playbooksRequestFailed',
|
||||||
|
{
|
||||||
|
defaultMessage: 'Unable to retrieve playbooks from XSOAR.',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const PLAYBOOK_NOT_FOUND_WARNING = i18n.translate(
|
||||||
|
'xpack.stackConnectors.components.xsoar.params.componentWarning.playbookNotFound',
|
||||||
|
{
|
||||||
|
defaultMessage: 'Could not find the selected playbook. Choose a different one.',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const BODY_LABEL = i18n.translate(
|
||||||
|
'xpack.stackConnectors.components.xsoar.params.bodyFieldLabel',
|
||||||
|
{
|
||||||
|
defaultMessage: 'Body',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const BODY_DESCRIPTION = i18n.translate(
|
||||||
|
'xpack.stackConnectors.components.xsoar.params.bodyCodeEditorAriaLabel',
|
||||||
|
{
|
||||||
|
defaultMessage: 'Code editor',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const PLAYBOOK_LABEL = i18n.translate(
|
||||||
|
'xpack.stackConnectors.components.xsoar.params.playbookFieldLabel',
|
||||||
|
{
|
||||||
|
defaultMessage: 'XSOAR playbooks',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const PLAYBOOK_HELP = i18n.translate(
|
||||||
|
'xpack.stackConnectors.components.xsoar.params.playbookHelp',
|
||||||
|
{
|
||||||
|
defaultMessage: 'The XSOAR playbook to associate with incident',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const PLAYBOOK_PLACEHOLDER = i18n.translate(
|
||||||
|
'xpack.stackConnectors.components.xsoar.params.playbookPlaceholder',
|
||||||
|
{
|
||||||
|
defaultMessage: 'Select a playbook',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const PLAYBOOK_ARIA_LABEL = i18n.translate(
|
||||||
|
'xpack.stackConnectors.components.xsoar.params.playbookFieldAriaLabel',
|
||||||
|
{
|
||||||
|
defaultMessage: 'Select an XSOAR playbook.',
|
||||||
|
}
|
||||||
|
);
|
|
@ -0,0 +1,11 @@
|
||||||
|
/*
|
||||||
|
* 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 { ActionTypeModel as ConnectorTypeModel } from '@kbn/triggers-actions-ui-plugin/public';
|
||||||
|
import type { Config, Secrets, ExecutorParams } from '../../../common/xsoar/types';
|
||||||
|
|
||||||
|
export type XSOARConnector = ConnectorTypeModel<Config, Secrets, ExecutorParams>;
|
|
@ -0,0 +1,72 @@
|
||||||
|
/*
|
||||||
|
* 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 { TypeRegistry } from '@kbn/triggers-actions-ui-plugin/public/application/type_registry';
|
||||||
|
import { registerConnectorTypes } from '..';
|
||||||
|
import { ActionTypeModel as ConnectorTypeModel } from '@kbn/triggers-actions-ui-plugin/public/types';
|
||||||
|
import { experimentalFeaturesMock, registrationServicesMock } from '../../mocks';
|
||||||
|
import { SUB_ACTION } from '../../../common/xsoar/constants';
|
||||||
|
import { ExperimentalFeaturesService } from '../../common/experimental_features_service';
|
||||||
|
import * as translations from './translations';
|
||||||
|
|
||||||
|
const CONNECTOR_TYPE_ID = '.xsoar';
|
||||||
|
let connectorTypeModel: ConnectorTypeModel;
|
||||||
|
beforeAll(() => {
|
||||||
|
const connectorTypeRegistry = new TypeRegistry<ConnectorTypeModel>();
|
||||||
|
ExperimentalFeaturesService.init({ experimentalFeatures: experimentalFeaturesMock });
|
||||||
|
registerConnectorTypes({ connectorTypeRegistry, services: registrationServicesMock });
|
||||||
|
const getResult = connectorTypeRegistry.get(CONNECTOR_TYPE_ID);
|
||||||
|
if (getResult !== null) {
|
||||||
|
connectorTypeModel = getResult;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('actionTypeRegistry.get() works', () => {
|
||||||
|
test('action type static data is as expected', () => {
|
||||||
|
expect(connectorTypeModel.id).toEqual(CONNECTOR_TYPE_ID);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('XSOAR RUN action params validation', () => {
|
||||||
|
test('RUN action params validation succeeds when action params is valid', async () => {
|
||||||
|
const actionParams = {
|
||||||
|
subAction: SUB_ACTION.RUN,
|
||||||
|
subActionParams: {
|
||||||
|
name: 'new incident',
|
||||||
|
playbookId: 'playbook0',
|
||||||
|
createInvestigation: false,
|
||||||
|
severity: 1,
|
||||||
|
body: '',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(await connectorTypeModel.validateParams(actionParams)).toEqual({
|
||||||
|
errors: {
|
||||||
|
name: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('RUN action params validation fails when required fields is not valid', async () => {
|
||||||
|
const actionParams = {
|
||||||
|
subAction: SUB_ACTION.RUN,
|
||||||
|
subActionParams: {
|
||||||
|
name: '',
|
||||||
|
playbookId: 'playbook0',
|
||||||
|
createInvestigation: false,
|
||||||
|
severity: 1,
|
||||||
|
body: '',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(await connectorTypeModel.validateParams(actionParams)).toEqual({
|
||||||
|
errors: {
|
||||||
|
name: [translations.NAME_REQUIRED],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,45 @@
|
||||||
|
/*
|
||||||
|
* 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 { lazy } from 'react';
|
||||||
|
import { GenericValidationResult } from '@kbn/triggers-actions-ui-plugin/public/types';
|
||||||
|
import { XSOARConnector } from './types';
|
||||||
|
import { XSOAR_CONNECTOR_ID, SUB_ACTION, XSOAR_TITLE } from '../../../common/xsoar/constants';
|
||||||
|
import { ExecutorParams } from '../../../common/xsoar/types';
|
||||||
|
import * as i18n from './translations';
|
||||||
|
|
||||||
|
interface ValidationErrors {
|
||||||
|
name: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getConnectorType(): XSOARConnector {
|
||||||
|
return {
|
||||||
|
id: XSOAR_CONNECTOR_ID,
|
||||||
|
hideInUi: true,
|
||||||
|
iconClass: lazy(() => import('./logo')),
|
||||||
|
selectMessage: i18n.SELECT_MESSAGE,
|
||||||
|
actionTypeTitle: XSOAR_TITLE,
|
||||||
|
validateParams: async (
|
||||||
|
actionParams: ExecutorParams
|
||||||
|
): Promise<GenericValidationResult<ValidationErrors>> => {
|
||||||
|
const translations = await import('./translations');
|
||||||
|
const errors: ValidationErrors = {
|
||||||
|
name: [],
|
||||||
|
};
|
||||||
|
const { subAction, subActionParams } = actionParams;
|
||||||
|
|
||||||
|
if (subAction === SUB_ACTION.RUN) {
|
||||||
|
if (!subActionParams?.name?.length) {
|
||||||
|
errors.name.push(translations.NAME_REQUIRED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { errors };
|
||||||
|
},
|
||||||
|
actionConnectorFields: lazy(() => import('./connector')),
|
||||||
|
actionParamsFields: lazy(() => import('./params')),
|
||||||
|
};
|
||||||
|
}
|
|
@ -32,6 +32,7 @@ import { getConnectorType as getXmattersConnectorType } from './xmatters';
|
||||||
import { getConnectorType as getTeamsConnectorType } from './teams';
|
import { getConnectorType as getTeamsConnectorType } from './teams';
|
||||||
import { getConnectorType as getD3SecurityConnectorType } from './d3security';
|
import { getConnectorType as getD3SecurityConnectorType } from './d3security';
|
||||||
import { getConnectorType as getTheHiveConnectorType } from './thehive';
|
import { getConnectorType as getTheHiveConnectorType } from './thehive';
|
||||||
|
import { getConnectorType as getXSOARConnectorType } from './xsoar';
|
||||||
import { getOpsgenieConnectorType } from './opsgenie';
|
import { getOpsgenieConnectorType } from './opsgenie';
|
||||||
import type { ActionParamsType as ServiceNowITSMActionParams } from './servicenow_itsm';
|
import type { ActionParamsType as ServiceNowITSMActionParams } from './servicenow_itsm';
|
||||||
import type { ActionParamsType as ServiceNowSIRActionParams } from './servicenow_sir';
|
import type { ActionParamsType as ServiceNowSIRActionParams } from './servicenow_sir';
|
||||||
|
@ -114,6 +115,7 @@ export function registerConnectorTypes({
|
||||||
actions.registerSubActionConnectorType(getD3SecurityConnectorType());
|
actions.registerSubActionConnectorType(getD3SecurityConnectorType());
|
||||||
actions.registerSubActionConnectorType(getResilientConnectorType());
|
actions.registerSubActionConnectorType(getResilientConnectorType());
|
||||||
actions.registerSubActionConnectorType(getTheHiveConnectorType());
|
actions.registerSubActionConnectorType(getTheHiveConnectorType());
|
||||||
|
actions.registerSubActionConnectorType(getXSOARConnectorType());
|
||||||
|
|
||||||
if (experimentalFeatures.sentinelOneConnectorOn) {
|
if (experimentalFeatures.sentinelOneConnectorOn) {
|
||||||
actions.registerSubActionConnectorType(getSentinelOneConnectorType());
|
actions.registerSubActionConnectorType(getSentinelOneConnectorType());
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
/*
|
||||||
|
* 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 { XSOARConnectorType } from '.';
|
||||||
|
import { getConnectorType } from '.';
|
||||||
|
|
||||||
|
let connectorType: XSOARConnectorType;
|
||||||
|
|
||||||
|
describe('XSOAR Connector', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
connectorType = getConnectorType();
|
||||||
|
});
|
||||||
|
test('exposes the connector as `XSOAR` with id `.xsoar`', () => {
|
||||||
|
expect(connectorType.id).toEqual('.xsoar');
|
||||||
|
expect(connectorType.name).toEqual('XSOAR');
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,34 @@
|
||||||
|
/*
|
||||||
|
* 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 { SubActionConnectorType } from '@kbn/actions-plugin/server/sub_action_framework/types';
|
||||||
|
import { ValidatorType } from '@kbn/actions-plugin/server/sub_action_framework/types';
|
||||||
|
import { SecurityConnectorFeatureId } from '@kbn/actions-plugin/common';
|
||||||
|
import { urlAllowListValidator } from '@kbn/actions-plugin/server';
|
||||||
|
import { XSOAR_CONNECTOR_ID, XSOAR_TITLE } from '../../../common/xsoar/constants';
|
||||||
|
import { ConfigSchema, SecretsSchema } from '../../../common/xsoar/schema';
|
||||||
|
import type { Config, Secrets } from '../../../common/xsoar/types';
|
||||||
|
import { XSOARConnector } from './xsoar';
|
||||||
|
import { renderParameterTemplates } from './render';
|
||||||
|
|
||||||
|
export type XSOARConnectorType = SubActionConnectorType<Config, Secrets>;
|
||||||
|
|
||||||
|
export function getConnectorType(): XSOARConnectorType {
|
||||||
|
return {
|
||||||
|
id: XSOAR_CONNECTOR_ID,
|
||||||
|
minimumLicenseRequired: 'platinum',
|
||||||
|
name: XSOAR_TITLE,
|
||||||
|
getService: (params) => new XSOARConnector(params),
|
||||||
|
supportedFeatureIds: [SecurityConnectorFeatureId],
|
||||||
|
schema: {
|
||||||
|
config: ConfigSchema,
|
||||||
|
secrets: SecretsSchema,
|
||||||
|
},
|
||||||
|
renderParameterTemplates,
|
||||||
|
validators: [{ type: ValidatorType.CONFIG, validator: urlAllowListValidator('url') }],
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
/*
|
||||||
|
* 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/xsoar/constants';
|
||||||
|
import Mustache from 'mustache';
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
subAction: SUB_ACTION.RUN,
|
||||||
|
subActionParams: {
|
||||||
|
name: 'new incident - {{alert.uuid}}',
|
||||||
|
playbookId: 'playbook0',
|
||||||
|
createInvestigation: true,
|
||||||
|
severity: 0,
|
||||||
|
isRuleSeverity: true,
|
||||||
|
body: '',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const variables = {
|
||||||
|
url: 'https://example.com',
|
||||||
|
context: { rule: { severity: 'medium' } },
|
||||||
|
alert: { uuid: 'test123' },
|
||||||
|
};
|
||||||
|
const logger = loggingSystemMock.createLogger();
|
||||||
|
|
||||||
|
describe('XSOAR - renderParameterTemplates', () => {
|
||||||
|
it('should rendered subActionParams with variables', () => {
|
||||||
|
const result = renderParameterTemplates(logger, params, variables);
|
||||||
|
|
||||||
|
expect(result.subActionParams).toEqual({
|
||||||
|
name: `new incident - ${variables.alert.uuid}`,
|
||||||
|
playbookId: 'playbook0',
|
||||||
|
createInvestigation: true,
|
||||||
|
severity: 2,
|
||||||
|
isRuleSeverity: true,
|
||||||
|
body: '',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
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({
|
||||||
|
name: `new incident - ${variables.alert.uuid}`,
|
||||||
|
playbookId: 'playbook0',
|
||||||
|
createInvestigation: true,
|
||||||
|
severity: 0,
|
||||||
|
isRuleSeverity: false,
|
||||||
|
body: '',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
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 "": test error',
|
||||||
|
createInvestigation: true,
|
||||||
|
name: 'error rendering mustache template "new incident - {{alert.uuid}}": test error',
|
||||||
|
playbookId: 'error rendering mustache template "playbook0": test error',
|
||||||
|
severity: 0,
|
||||||
|
isRuleSeverity: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,47 @@
|
||||||
|
/*
|
||||||
|
* 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';
|
||||||
|
|
||||||
|
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 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const renderParameterTemplates: RenderParameterTemplates<ExecutorParams> = (
|
||||||
|
logger,
|
||||||
|
params,
|
||||||
|
variables
|
||||||
|
) => {
|
||||||
|
return {
|
||||||
|
...params,
|
||||||
|
subActionParams: {
|
||||||
|
...renderMustacheObject(logger, params.subActionParams, variables),
|
||||||
|
severity:
|
||||||
|
params.subActionParams.isRuleSeverity === true
|
||||||
|
? mapSeverity(
|
||||||
|
renderMustacheString(logger, '{{context.rule.severity}}', variables, 'json')
|
||||||
|
)
|
||||||
|
: params.subActionParams.severity,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
|
@ -0,0 +1,527 @@
|
||||||
|
/*
|
||||||
|
* 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 { XSOARConnector } from './xsoar';
|
||||||
|
import { actionsConfigMock } from '@kbn/actions-plugin/server/actions_config.mock';
|
||||||
|
import { XSOAR_CONNECTOR_ID } from '../../../common/xsoar/constants';
|
||||||
|
import { loggingSystemMock } from '@kbn/core-logging-server-mocks';
|
||||||
|
import { actionsMock } from '@kbn/actions-plugin/server/mocks';
|
||||||
|
import {
|
||||||
|
XSOARRunActionResponseSchema,
|
||||||
|
XSOARPlaybooksActionResponseSchema,
|
||||||
|
} from '../../../common/xsoar/schema';
|
||||||
|
import type { XSOARRunActionParams } from '../../../common/xsoar/types';
|
||||||
|
import { ConnectorUsageCollector } from '@kbn/actions-plugin/server/types';
|
||||||
|
|
||||||
|
const mockTime = new Date('2025-02-20T10:10:30.000');
|
||||||
|
|
||||||
|
describe('XSOARConnector', () => {
|
||||||
|
const logger = loggingSystemMock.createLogger();
|
||||||
|
|
||||||
|
const connector = new XSOARConnector({
|
||||||
|
configurationUtilities: actionsConfigMock.create(),
|
||||||
|
connector: { id: '1', type: XSOAR_CONNECTOR_ID },
|
||||||
|
config: { url: 'https://example.com' },
|
||||||
|
secrets: { apiKey: 'test123', apiKeyID: null },
|
||||||
|
logger,
|
||||||
|
services: actionsMock.createServices(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const cloudConnector = new XSOARConnector({
|
||||||
|
configurationUtilities: actionsConfigMock.create(),
|
||||||
|
connector: { id: '2', type: XSOAR_CONNECTOR_ID },
|
||||||
|
config: { url: 'https://test.com' },
|
||||||
|
secrets: { apiKey: 'test123', apiKeyID: '123' },
|
||||||
|
logger,
|
||||||
|
services: actionsMock.createServices(),
|
||||||
|
});
|
||||||
|
|
||||||
|
let mockRequest: jest.Mock;
|
||||||
|
let mockCloudRequest: jest.Mock;
|
||||||
|
let mockError: jest.Mock;
|
||||||
|
let connectorUsageCollector: ConnectorUsageCollector;
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
jest.useFakeTimers();
|
||||||
|
jest.setSystemTime(mockTime);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
jest.useRealTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
mockError = jest.fn().mockImplementation(() => {
|
||||||
|
throw new Error('API Error');
|
||||||
|
});
|
||||||
|
jest.clearAllMocks();
|
||||||
|
connectorUsageCollector = new ConnectorUsageCollector({
|
||||||
|
logger,
|
||||||
|
connectorId: 'test-connector-id',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getPlaybooks', () => {
|
||||||
|
const mockResponse = {
|
||||||
|
data: {
|
||||||
|
playbooks: [
|
||||||
|
{
|
||||||
|
id: '8db0105c-f674-4d83-8095-f95a9f61e77a',
|
||||||
|
version: 4,
|
||||||
|
cacheVersn: 0,
|
||||||
|
sequenceNumber: 33831652,
|
||||||
|
primaryTerm: 11,
|
||||||
|
modified: '2023-12-12T13:51:15.668021556Z',
|
||||||
|
sizeInBytes: 0,
|
||||||
|
packID: '',
|
||||||
|
packName: '',
|
||||||
|
itemVersion: '',
|
||||||
|
fromServerVersion: '',
|
||||||
|
toServerVersion: '',
|
||||||
|
propagationLabels: ['all'],
|
||||||
|
definitionId: '',
|
||||||
|
vcShouldIgnore: false,
|
||||||
|
vcShouldKeepItemLegacyProdMachine: false,
|
||||||
|
commitMessage: '',
|
||||||
|
shouldCommit: false,
|
||||||
|
name: 'aaa',
|
||||||
|
nameRaw: 'aaa',
|
||||||
|
prevName: 'aaa',
|
||||||
|
startTaskId: '0',
|
||||||
|
tasks: {
|
||||||
|
'0': {
|
||||||
|
id: '0',
|
||||||
|
taskId: 'e228a044-2ad5-4ab0-873a-d5bb94a5c1b4',
|
||||||
|
type: 'start',
|
||||||
|
task: {
|
||||||
|
id: 'e228a044-2ad5-4ab0-873a-d5bb94a5c1b4',
|
||||||
|
version: 1,
|
||||||
|
cacheVersn: 0,
|
||||||
|
sequenceNumber: 13431901,
|
||||||
|
primaryTerm: 8,
|
||||||
|
modified: '2023-05-23T07:16:19.930125981Z',
|
||||||
|
sizeInBytes: 0,
|
||||||
|
},
|
||||||
|
nextTasks: {
|
||||||
|
'#none#': ['1'],
|
||||||
|
},
|
||||||
|
continueOnErrorType: '',
|
||||||
|
view: {
|
||||||
|
position: {
|
||||||
|
x: 450,
|
||||||
|
y: 50,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
evidenceData: {},
|
||||||
|
},
|
||||||
|
'1': {
|
||||||
|
id: '1',
|
||||||
|
taskId: 'c28b63d3-c860-4e16-82b4-6db6b58bdee3',
|
||||||
|
type: 'regular',
|
||||||
|
task: {
|
||||||
|
id: 'c28b63d3-c860-4e16-82b4-6db6b58bdee3',
|
||||||
|
version: 1,
|
||||||
|
cacheVersn: 0,
|
||||||
|
sequenceNumber: 33831651,
|
||||||
|
primaryTerm: 11,
|
||||||
|
modified: '2023-12-12T13:51:15.604271789Z',
|
||||||
|
sizeInBytes: 0,
|
||||||
|
name: 'Untitled Task 1',
|
||||||
|
description: 'commands.local.cmd.set.incident',
|
||||||
|
scriptId: 'Builtin|||setIncident',
|
||||||
|
type: 'regular',
|
||||||
|
isCommand: true,
|
||||||
|
brand: 'Builtin',
|
||||||
|
},
|
||||||
|
scriptArguments: {
|
||||||
|
severity: {
|
||||||
|
simple: '1',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
continueOnErrorType: '',
|
||||||
|
view: {
|
||||||
|
position: {
|
||||||
|
x: 450,
|
||||||
|
y: 200,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
evidenceData: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
taskIds: [
|
||||||
|
'e228a044-2ad5-4ab0-873a-d5bb94a5c1b4',
|
||||||
|
'c28b63d3-c860-4e16-82b4-6db6b58bdee3',
|
||||||
|
],
|
||||||
|
scriptIds: [],
|
||||||
|
commands: ['setIncident'],
|
||||||
|
brands: ['Builtin'],
|
||||||
|
missingScriptsIds: ['Builtin|||setIncident'],
|
||||||
|
view: {
|
||||||
|
linkLabelsPosition: {},
|
||||||
|
paper: {
|
||||||
|
dimensions: {
|
||||||
|
height: 245,
|
||||||
|
width: 380,
|
||||||
|
x: 450,
|
||||||
|
y: 50,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
inputs: null,
|
||||||
|
outputs: null,
|
||||||
|
quiet: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
tags: [
|
||||||
|
'Endpoint',
|
||||||
|
'ITDR',
|
||||||
|
'Automated',
|
||||||
|
'Phishing',
|
||||||
|
'Sandbox',
|
||||||
|
'Joe Security',
|
||||||
|
'Severity',
|
||||||
|
'Malware',
|
||||||
|
'Sumo Logic',
|
||||||
|
'Remediation',
|
||||||
|
'Job',
|
||||||
|
'Code42 Incydr',
|
||||||
|
'Sinkhole',
|
||||||
|
'XDR',
|
||||||
|
'TIM',
|
||||||
|
'PAN-OS',
|
||||||
|
'Vulnerability',
|
||||||
|
'Virus',
|
||||||
|
'Domaintools',
|
||||||
|
],
|
||||||
|
total: 1,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
mockRequest = jest.fn().mockResolvedValue(mockResponse);
|
||||||
|
mockCloudRequest = jest.fn().mockResolvedValue(mockResponse);
|
||||||
|
// @ts-ignore
|
||||||
|
connector.request = mockRequest;
|
||||||
|
// @ts-ignore
|
||||||
|
cloudConnector.request = mockCloudRequest;
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('XSOAR API call is successful with correct parameters', async () => {
|
||||||
|
const response = await connector.getPlaybooks(undefined, connectorUsageCollector);
|
||||||
|
expect(mockRequest).toBeCalledTimes(1);
|
||||||
|
expect(mockRequest).toHaveBeenCalledWith(
|
||||||
|
{
|
||||||
|
method: 'post',
|
||||||
|
url: 'https://example.com/playbook/search',
|
||||||
|
data: {},
|
||||||
|
responseSchema: XSOARPlaybooksActionResponseSchema,
|
||||||
|
headers: {
|
||||||
|
Authorization: 'test123',
|
||||||
|
},
|
||||||
|
timeout: 15000,
|
||||||
|
},
|
||||||
|
connectorUsageCollector
|
||||||
|
);
|
||||||
|
expect(response).toEqual(mockResponse.data);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Auth headers are correctly set for cloud instance', async () => {
|
||||||
|
const response = await cloudConnector.getPlaybooks(undefined, connectorUsageCollector);
|
||||||
|
expect(mockCloudRequest).toBeCalledTimes(1);
|
||||||
|
expect(mockCloudRequest).toHaveBeenCalledWith(
|
||||||
|
{
|
||||||
|
method: 'post',
|
||||||
|
url: 'https://test.com/xsoar/public/v1/playbook/search',
|
||||||
|
data: {},
|
||||||
|
responseSchema: XSOARPlaybooksActionResponseSchema,
|
||||||
|
headers: {
|
||||||
|
Authorization: 'test123',
|
||||||
|
'x-xdr-auth-id': '123',
|
||||||
|
},
|
||||||
|
timeout: 15000,
|
||||||
|
},
|
||||||
|
connectorUsageCollector
|
||||||
|
);
|
||||||
|
expect(response).toEqual(mockResponse.data);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('errors during API calls are properly handled', async () => {
|
||||||
|
// @ts-ignore
|
||||||
|
connector.request = mockError;
|
||||||
|
|
||||||
|
await expect(connector.getPlaybooks(undefined, connectorUsageCollector)).rejects.toThrow(
|
||||||
|
'API Error'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('run', () => {
|
||||||
|
const mockResponse = {
|
||||||
|
data: {
|
||||||
|
id: '178791',
|
||||||
|
version: 0,
|
||||||
|
cacheVersn: 0,
|
||||||
|
modified: '1970-01-01T00:00:00Z',
|
||||||
|
sizeInBytes: 0,
|
||||||
|
CustomFields: {
|
||||||
|
bmcassignee: [{}],
|
||||||
|
bmccustomer: [{}],
|
||||||
|
bmcrequester: [{}],
|
||||||
|
containmentsla: {
|
||||||
|
accumulatedPause: 0,
|
||||||
|
breachTriggered: false,
|
||||||
|
dueDate: '0001-01-01T00:00:00Z',
|
||||||
|
endDate: '0001-01-01T00:00:00Z',
|
||||||
|
lastPauseDate: '0001-01-01T00:00:00Z',
|
||||||
|
runStatus: 'idle',
|
||||||
|
sla: 30,
|
||||||
|
slaStatus: -1,
|
||||||
|
startDate: '0001-01-01T00:00:00Z',
|
||||||
|
totalDuration: 0,
|
||||||
|
},
|
||||||
|
crowdstrikefalconbehaviourpatterndispositiondetails: [{}, {}, {}],
|
||||||
|
datadogcloudsiem: [{}, {}, {}],
|
||||||
|
dataminrpulserelatedterms: [{}, {}, {}],
|
||||||
|
decyfirdatadetails: [{}, {}, {}],
|
||||||
|
detectionsla: {
|
||||||
|
accumulatedPause: 0,
|
||||||
|
breachTriggered: false,
|
||||||
|
dueDate: '0001-01-01T00:00:00Z',
|
||||||
|
endDate: '0001-01-01T00:00:00Z',
|
||||||
|
lastPauseDate: '0001-01-01T00:00:00Z',
|
||||||
|
runStatus: 'idle',
|
||||||
|
sla: 20,
|
||||||
|
slaStatus: -1,
|
||||||
|
startDate: '0001-01-01T00:00:00Z',
|
||||||
|
totalDuration: 0,
|
||||||
|
},
|
||||||
|
domaintoolsirisdetect: [{}, {}, {}],
|
||||||
|
endpoint: [{}],
|
||||||
|
externalid: '178791',
|
||||||
|
extrahoprevealxdetectiondevices: [{}, {}, {}],
|
||||||
|
extrahoprevealxmitretechniques: [{}, {}, {}],
|
||||||
|
filerelationships: [{}, {}, {}],
|
||||||
|
fortisiemattacktactics: [{}, {}],
|
||||||
|
fortisiemevents: [{}],
|
||||||
|
incidentduration: {
|
||||||
|
accumulatedPause: 0,
|
||||||
|
breachTriggered: false,
|
||||||
|
dueDate: '0001-01-01T00:00:00Z',
|
||||||
|
endDate: '0001-01-01T00:00:00Z',
|
||||||
|
lastPauseDate: '0001-01-01T00:00:00Z',
|
||||||
|
runStatus: 'idle',
|
||||||
|
sla: 0,
|
||||||
|
slaStatus: -1,
|
||||||
|
startDate: '0001-01-01T00:00:00Z',
|
||||||
|
totalDuration: 0,
|
||||||
|
},
|
||||||
|
incidentrdpachehuntingstringssimilarity: [{}, {}, {}],
|
||||||
|
incidentrdpcachehuntingstringsifter: [{}, {}, {}],
|
||||||
|
inventasource: [{}],
|
||||||
|
microsoftsentinelowner: [],
|
||||||
|
qintelqwatchexposures: [{}, {}, {}],
|
||||||
|
remediationsla: {
|
||||||
|
accumulatedPause: 0,
|
||||||
|
breachTriggered: false,
|
||||||
|
dueDate: '0001-01-01T00:00:00Z',
|
||||||
|
endDate: '0001-01-01T00:00:00Z',
|
||||||
|
lastPauseDate: '0001-01-01T00:00:00Z',
|
||||||
|
runStatus: 'idle',
|
||||||
|
sla: 7200,
|
||||||
|
slaStatus: -1,
|
||||||
|
startDate: '0001-01-01T00:00:00Z',
|
||||||
|
totalDuration: 0,
|
||||||
|
},
|
||||||
|
rsametasevents: [],
|
||||||
|
rsarawlogslist: [],
|
||||||
|
securitypolicymatch: [{}],
|
||||||
|
similarincidentsdbot: [{}],
|
||||||
|
spycloudcompassdevicedata: [{}, {}, {}],
|
||||||
|
suspiciousexecutions: [{}, {}, {}],
|
||||||
|
timetoassignment: {
|
||||||
|
accumulatedPause: 0,
|
||||||
|
breachTriggered: false,
|
||||||
|
dueDate: '0001-01-01T00:00:00Z',
|
||||||
|
endDate: '0001-01-01T00:00:00Z',
|
||||||
|
lastPauseDate: '0001-01-01T00:00:00Z',
|
||||||
|
runStatus: 'idle',
|
||||||
|
sla: 0,
|
||||||
|
slaStatus: -1,
|
||||||
|
startDate: '0001-01-01T00:00:00Z',
|
||||||
|
totalDuration: 0,
|
||||||
|
},
|
||||||
|
triagesla: {
|
||||||
|
accumulatedPause: 0,
|
||||||
|
breachTriggered: false,
|
||||||
|
dueDate: '0001-01-01T00:00:00Z',
|
||||||
|
endDate: '0001-01-01T00:00:00Z',
|
||||||
|
lastPauseDate: '0001-01-01T00:00:00Z',
|
||||||
|
runStatus: 'idle',
|
||||||
|
sla: 30,
|
||||||
|
slaStatus: -1,
|
||||||
|
startDate: '0001-01-01T00:00:00Z',
|
||||||
|
totalDuration: 0,
|
||||||
|
},
|
||||||
|
urlsslverification: [],
|
||||||
|
xdralertsearchresults: [{}, {}, {}],
|
||||||
|
xdrinvestigationresults: [
|
||||||
|
{},
|
||||||
|
{},
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
columnheader1: '',
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
columnheader1: '',
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
{},
|
||||||
|
],
|
||||||
|
xpanseserviceclassifications: [{}, {}, {}],
|
||||||
|
xpanseservicevalidation: [
|
||||||
|
{
|
||||||
|
columnheader1: '',
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
{},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
account: '',
|
||||||
|
autime: 1713700028107000000,
|
||||||
|
type: 'Unclassified',
|
||||||
|
rawType: 'Unclassified',
|
||||||
|
name: 'My test incident',
|
||||||
|
rawName: 'My test incident',
|
||||||
|
status: 0,
|
||||||
|
custom_status: '',
|
||||||
|
resolution_status: '',
|
||||||
|
reason: '',
|
||||||
|
created: '2024-04-21T11:47:08.107Z',
|
||||||
|
occurred: '2024-04-21T11:47:08.107982676Z',
|
||||||
|
closed: '0001-01-01T00:00:00Z',
|
||||||
|
sla: 0,
|
||||||
|
severity: 2,
|
||||||
|
investigationId: '',
|
||||||
|
labels: [
|
||||||
|
{
|
||||||
|
value: '',
|
||||||
|
type: 'Instance',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'Manual',
|
||||||
|
type: 'Brand',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
attachment: null,
|
||||||
|
details: 'My test incident',
|
||||||
|
openDuration: 0,
|
||||||
|
lastOpen: '0001-01-01T00:00:00Z',
|
||||||
|
closingUserId: '',
|
||||||
|
owner: '',
|
||||||
|
activated: '0001-01-01T00:00:00Z',
|
||||||
|
closeReason: '',
|
||||||
|
rawCloseReason: '',
|
||||||
|
closeNotes: '',
|
||||||
|
playbookId: 'playbook0',
|
||||||
|
dueDate: '2024-05-01T11:47:08.107988742Z',
|
||||||
|
reminder: '0001-01-01T00:00:00Z',
|
||||||
|
runStatus: '',
|
||||||
|
notifyTime: '0001-01-01T00:00:00Z',
|
||||||
|
phase: '',
|
||||||
|
rawPhase: '',
|
||||||
|
isPlayground: false,
|
||||||
|
rawJSON: '',
|
||||||
|
parent: '',
|
||||||
|
parentXDRIncident: '',
|
||||||
|
retained: false,
|
||||||
|
category: '',
|
||||||
|
rawCategory: '',
|
||||||
|
linkedIncidents: null,
|
||||||
|
linkedCount: 0,
|
||||||
|
droppedCount: 0,
|
||||||
|
sourceInstance: '',
|
||||||
|
sourceBrand: 'Manual',
|
||||||
|
canvases: null,
|
||||||
|
lastJobRunTime: '0001-01-01T00:00:00Z',
|
||||||
|
feedBased: false,
|
||||||
|
dbotMirrorId: '',
|
||||||
|
dbotMirrorInstance: '',
|
||||||
|
dbotMirrorDirection: '',
|
||||||
|
dbotDirtyFields: null,
|
||||||
|
dbotCurrentDirtyFields: null,
|
||||||
|
dbotMirrorTags: null,
|
||||||
|
dbotMirrorLastSync: '0001-01-01T00:00:00Z',
|
||||||
|
isDebug: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
mockRequest = jest.fn().mockResolvedValue(mockResponse);
|
||||||
|
mockCloudRequest = jest.fn().mockResolvedValue(mockResponse);
|
||||||
|
// @ts-ignore
|
||||||
|
connector.request = mockRequest;
|
||||||
|
// @ts-ignore
|
||||||
|
cloudConnector.request = mockCloudRequest;
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
const incident: XSOARRunActionParams = {
|
||||||
|
name: 'My test incident',
|
||||||
|
playbookId: 'playbook0',
|
||||||
|
createInvestigation: false,
|
||||||
|
severity: 2,
|
||||||
|
isRuleSeverity: false,
|
||||||
|
body: JSON.stringify({}),
|
||||||
|
};
|
||||||
|
|
||||||
|
const malformedIncident: XSOARRunActionParams = {
|
||||||
|
name: 'My test incident 2',
|
||||||
|
playbookId: 'playbook0',
|
||||||
|
createInvestigation: false,
|
||||||
|
isRuleSeverity: false,
|
||||||
|
severity: 2,
|
||||||
|
body: '{',
|
||||||
|
};
|
||||||
|
|
||||||
|
const { body, isRuleSeverity, ...incidentWithoutBody } = incident;
|
||||||
|
const expectedIncident = { ...JSON.parse(body || '{}'), ...incidentWithoutBody };
|
||||||
|
|
||||||
|
it('XSOAR API call is successful with correct parameters', async () => {
|
||||||
|
await connector.run(incident, connectorUsageCollector);
|
||||||
|
expect(mockRequest).toBeCalledTimes(1);
|
||||||
|
expect(mockRequest).toHaveBeenCalledWith(
|
||||||
|
{
|
||||||
|
url: 'https://example.com/incident',
|
||||||
|
method: 'post',
|
||||||
|
responseSchema: XSOARRunActionResponseSchema,
|
||||||
|
data: expectedIncident,
|
||||||
|
headers: {
|
||||||
|
Authorization: 'test123',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
connectorUsageCollector
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('errors during API calls are properly handled', async () => {
|
||||||
|
// @ts-ignore
|
||||||
|
connector.request = mockError;
|
||||||
|
|
||||||
|
await expect(connector.run(expectedIncident, connectorUsageCollector)).rejects.toThrow(
|
||||||
|
'API Error'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('error when malformed incident is passed', async () => {
|
||||||
|
await expect(connector.run(malformedIncident, connectorUsageCollector)).rejects.toThrowError(
|
||||||
|
`Error parsing Body: SyntaxError: Expected property name or '}' in JSON at position 1`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,143 @@
|
||||||
|
/*
|
||||||
|
* 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 { i18n } from '@kbn/i18n';
|
||||||
|
import type { ServiceParams } from '@kbn/actions-plugin/server';
|
||||||
|
import { SubActionConnector } from '@kbn/actions-plugin/server';
|
||||||
|
import type { ConnectorUsageCollector } from '@kbn/actions-plugin/server/types';
|
||||||
|
import type { AxiosError } from 'axios';
|
||||||
|
|
||||||
|
import type {
|
||||||
|
Config,
|
||||||
|
Secrets,
|
||||||
|
XSOARRunActionParams,
|
||||||
|
XSOARPlaybooksActionResponse,
|
||||||
|
} from '../../../common/xsoar/types';
|
||||||
|
import {
|
||||||
|
XSOARRunActionResponseSchema,
|
||||||
|
XSOARPlaybooksActionResponseSchema,
|
||||||
|
XSOARPlaybooksActionParamsSchema,
|
||||||
|
XSOARRunActionParamsSchema,
|
||||||
|
} from '../../../common/xsoar/schema';
|
||||||
|
import { SUB_ACTION } from '../../../common/xsoar/constants';
|
||||||
|
|
||||||
|
export const CLOUD_API_PATH = '/xsoar/public/v1';
|
||||||
|
export const INCIDENT_PATH = '/incident';
|
||||||
|
export const PLAYBOOKS_PATH = '/playbook/search';
|
||||||
|
|
||||||
|
export class XSOARConnector extends SubActionConnector<Config, Secrets> {
|
||||||
|
private urls: {
|
||||||
|
playbooks: string;
|
||||||
|
incident: string;
|
||||||
|
};
|
||||||
|
private isCloud: boolean;
|
||||||
|
private ConnectorId: string;
|
||||||
|
|
||||||
|
constructor(params: ServiceParams<Config, Secrets>) {
|
||||||
|
super(params);
|
||||||
|
|
||||||
|
this.isCloud = this.secrets.apiKeyID !== null && this.secrets.apiKeyID !== '';
|
||||||
|
this.urls = {
|
||||||
|
playbooks: this.isCloud
|
||||||
|
? `${this.config.url}${CLOUD_API_PATH}${PLAYBOOKS_PATH}`
|
||||||
|
: `${this.config.url}${PLAYBOOKS_PATH}`,
|
||||||
|
incident: this.isCloud
|
||||||
|
? `${this.config.url}${CLOUD_API_PATH}${INCIDENT_PATH}`
|
||||||
|
: `${this.config.url}${INCIDENT_PATH}`,
|
||||||
|
};
|
||||||
|
this.ConnectorId = params.connector.id;
|
||||||
|
this.registerSubActions();
|
||||||
|
}
|
||||||
|
|
||||||
|
private registerSubActions() {
|
||||||
|
this.registerSubAction({
|
||||||
|
name: SUB_ACTION.PLAYBOOKS,
|
||||||
|
method: 'getPlaybooks',
|
||||||
|
schema: XSOARPlaybooksActionParamsSchema,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.registerSubAction({
|
||||||
|
name: SUB_ACTION.RUN,
|
||||||
|
method: 'run',
|
||||||
|
schema: XSOARRunActionParamsSchema,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private getAuthHeaders() {
|
||||||
|
return this.isCloud
|
||||||
|
? { Authorization: this.secrets.apiKey, 'x-xdr-auth-id': this.secrets.apiKeyID }
|
||||||
|
: { Authorization: this.secrets.apiKey };
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getResponseErrorMessage(error: AxiosError): string {
|
||||||
|
if (error.response?.statusText) {
|
||||||
|
return `API Error: ${error.response?.statusText}`;
|
||||||
|
}
|
||||||
|
return error.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private formatIncidentBody(incident: XSOARRunActionParams) {
|
||||||
|
try {
|
||||||
|
const { body, isRuleSeverity, ...incidentWithoutBody } = incident;
|
||||||
|
const bodyJson = JSON.parse(body || '{}');
|
||||||
|
const mergedIncident = { ...bodyJson, ...incidentWithoutBody };
|
||||||
|
|
||||||
|
return mergedIncident;
|
||||||
|
} catch (err) {
|
||||||
|
const errMessage = i18n.translate('xpack.stackConnectors.xsoar.BodyParsingErrorMessage', {
|
||||||
|
defaultMessage: 'error triggering XSOAR workflow, parsing body',
|
||||||
|
});
|
||||||
|
|
||||||
|
this.logger.error(`error on ${this.ConnectorId} XSOAR event: ${errMessage}: ${err.message}`);
|
||||||
|
|
||||||
|
throw new Error(
|
||||||
|
i18n.translate('xpack.stackConnectors.xsoar.incidentBodyParsingError', {
|
||||||
|
defaultMessage: 'Error parsing Body: {err}',
|
||||||
|
values: {
|
||||||
|
err: err.toString(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async run(
|
||||||
|
incident: XSOARRunActionParams,
|
||||||
|
connectorUsageCollector: ConnectorUsageCollector
|
||||||
|
) {
|
||||||
|
const mergedIncident = this.formatIncidentBody(incident);
|
||||||
|
await this.request(
|
||||||
|
{
|
||||||
|
method: 'post',
|
||||||
|
url: `${this.urls.incident}`,
|
||||||
|
data: mergedIncident,
|
||||||
|
headers: this.getAuthHeaders(),
|
||||||
|
responseSchema: XSOARRunActionResponseSchema,
|
||||||
|
},
|
||||||
|
connectorUsageCollector
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getPlaybooks(
|
||||||
|
params: unknown,
|
||||||
|
connectorUsageCollector: ConnectorUsageCollector
|
||||||
|
): Promise<XSOARPlaybooksActionResponse> {
|
||||||
|
const res = await this.request(
|
||||||
|
{
|
||||||
|
method: 'post',
|
||||||
|
url: `${this.urls.playbooks}`,
|
||||||
|
data: {},
|
||||||
|
headers: this.getAuthHeaders(),
|
||||||
|
responseSchema: XSOARPlaybooksActionResponseSchema,
|
||||||
|
timeout: 15000,
|
||||||
|
},
|
||||||
|
connectorUsageCollector
|
||||||
|
);
|
||||||
|
|
||||||
|
return res.data;
|
||||||
|
}
|
||||||
|
}
|
|
@ -141,7 +141,7 @@ describe('Stack Connectors Plugin', () => {
|
||||||
name: 'Torq',
|
name: 'Torq',
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
expect(actionsSetup.registerSubActionConnectorType).toHaveBeenCalledTimes(12);
|
expect(actionsSetup.registerSubActionConnectorType).toHaveBeenCalledTimes(13);
|
||||||
expect(actionsSetup.registerSubActionConnectorType).toHaveBeenNthCalledWith(
|
expect(actionsSetup.registerSubActionConnectorType).toHaveBeenNthCalledWith(
|
||||||
1,
|
1,
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
|
@ -200,13 +200,20 @@ describe('Stack Connectors Plugin', () => {
|
||||||
);
|
);
|
||||||
expect(actionsSetup.registerSubActionConnectorType).toHaveBeenNthCalledWith(
|
expect(actionsSetup.registerSubActionConnectorType).toHaveBeenNthCalledWith(
|
||||||
9,
|
9,
|
||||||
|
expect.objectContaining({
|
||||||
|
id: '.xsoar',
|
||||||
|
name: 'XSOAR',
|
||||||
|
})
|
||||||
|
);
|
||||||
|
expect(actionsSetup.registerSubActionConnectorType).toHaveBeenNthCalledWith(
|
||||||
|
10,
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
id: '.sentinelone',
|
id: '.sentinelone',
|
||||||
name: 'Sentinel One',
|
name: 'Sentinel One',
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
expect(actionsSetup.registerSubActionConnectorType).toHaveBeenNthCalledWith(
|
expect(actionsSetup.registerSubActionConnectorType).toHaveBeenNthCalledWith(
|
||||||
10,
|
11,
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
id: '.crowdstrike',
|
id: '.crowdstrike',
|
||||||
name: 'CrowdStrike',
|
name: 'CrowdStrike',
|
||||||
|
|
|
@ -122,6 +122,10 @@ Array [
|
||||||
"cost": 1,
|
"cost": 1,
|
||||||
"taskType": "actions:.xmatters",
|
"taskType": "actions:.xmatters",
|
||||||
},
|
},
|
||||||
|
Object {
|
||||||
|
"cost": 1,
|
||||||
|
"taskType": "actions:.xsoar",
|
||||||
|
},
|
||||||
Object {
|
Object {
|
||||||
"cost": 10,
|
"cost": 10,
|
||||||
"taskType": "alerting:siem.indicatorRule",
|
"taskType": "alerting:siem.indicatorRule",
|
||||||
|
|
|
@ -68,6 +68,7 @@ const enabledActionTypes = [
|
||||||
'.tines',
|
'.tines',
|
||||||
'.webhook',
|
'.webhook',
|
||||||
'.xmatters',
|
'.xmatters',
|
||||||
|
'.xsoar',
|
||||||
'.torq',
|
'.torq',
|
||||||
'test.sub-action-connector',
|
'test.sub-action-connector',
|
||||||
'test.sub-action-connector-without-sub-actions',
|
'test.sub-action-connector-without-sub-actions',
|
||||||
|
|
|
@ -0,0 +1,423 @@
|
||||||
|
/*
|
||||||
|
* 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 http from 'http';
|
||||||
|
import type {
|
||||||
|
RequestHandlerContext,
|
||||||
|
KibanaRequest,
|
||||||
|
KibanaResponseFactory,
|
||||||
|
IKibanaResponse,
|
||||||
|
IRouter,
|
||||||
|
} from '@kbn/core/server';
|
||||||
|
import type { ProxyArgs } from './simulator';
|
||||||
|
import { Simulator } from './simulator';
|
||||||
|
|
||||||
|
export const XSOARPlaybook0 = {
|
||||||
|
id: '8db0105c-f674-4d83-8095-f95a9f61e77a',
|
||||||
|
version: 4,
|
||||||
|
cacheVersn: 0,
|
||||||
|
sequenceNumber: 33831652,
|
||||||
|
primaryTerm: 11,
|
||||||
|
modified: '2023-12-12T13:51:15.668021556Z',
|
||||||
|
sizeInBytes: 0,
|
||||||
|
packID: '',
|
||||||
|
packName: '',
|
||||||
|
itemVersion: '',
|
||||||
|
fromServerVersion: '',
|
||||||
|
toServerVersion: '',
|
||||||
|
propagationLabels: ['all'],
|
||||||
|
definitionId: '',
|
||||||
|
vcShouldIgnore: false,
|
||||||
|
vcShouldKeepItemLegacyProdMachine: false,
|
||||||
|
commitMessage: '',
|
||||||
|
shouldCommit: false,
|
||||||
|
name: 'playbook0',
|
||||||
|
nameRaw: 'playbook0',
|
||||||
|
prevName: 'aaa',
|
||||||
|
startTaskId: '0',
|
||||||
|
tasks: {
|
||||||
|
'0': {
|
||||||
|
id: '0',
|
||||||
|
taskId: 'e228a044-2ad5-4ab0-873a-d5bb94a5c1b4',
|
||||||
|
type: 'start',
|
||||||
|
task: {
|
||||||
|
id: 'e228a044-2ad5-4ab0-873a-d5bb94a5c1b4',
|
||||||
|
version: 1,
|
||||||
|
cacheVersn: 0,
|
||||||
|
sequenceNumber: 13431901,
|
||||||
|
primaryTerm: 8,
|
||||||
|
modified: '2023-05-23T07:16:19.930125981Z',
|
||||||
|
sizeInBytes: 0,
|
||||||
|
},
|
||||||
|
nextTasks: {
|
||||||
|
'#none#': ['1'],
|
||||||
|
},
|
||||||
|
continueOnErrorType: '',
|
||||||
|
view: {
|
||||||
|
position: {
|
||||||
|
x: 450,
|
||||||
|
y: 50,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
evidenceData: {},
|
||||||
|
},
|
||||||
|
'1': {
|
||||||
|
id: '1',
|
||||||
|
taskId: 'c28b63d3-c860-4e16-82b4-6db6b58bdee3',
|
||||||
|
type: 'regular',
|
||||||
|
task: {
|
||||||
|
id: 'c28b63d3-c860-4e16-82b4-6db6b58bdee3',
|
||||||
|
version: 1,
|
||||||
|
cacheVersn: 0,
|
||||||
|
sequenceNumber: 33831651,
|
||||||
|
primaryTerm: 11,
|
||||||
|
modified: '2023-12-12T13:51:15.604271789Z',
|
||||||
|
sizeInBytes: 0,
|
||||||
|
name: 'Untitled Task 1',
|
||||||
|
description: 'commands.local.cmd.set.incident',
|
||||||
|
scriptId: 'Builtin|||setIncident',
|
||||||
|
type: 'regular',
|
||||||
|
isCommand: true,
|
||||||
|
brand: 'Builtin',
|
||||||
|
},
|
||||||
|
scriptArguments: {
|
||||||
|
severity: {
|
||||||
|
simple: '1',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
continueOnErrorType: '',
|
||||||
|
view: {
|
||||||
|
position: {
|
||||||
|
x: 450,
|
||||||
|
y: 200,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
evidenceData: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
taskIds: ['e228a044-2ad5-4ab0-873a-d5bb94a5c1b4', 'c28b63d3-c860-4e16-82b4-6db6b58bdee3'],
|
||||||
|
scriptIds: [],
|
||||||
|
commands: ['setIncident'],
|
||||||
|
brands: ['Builtin'],
|
||||||
|
missingScriptsIds: ['Builtin|||setIncident'],
|
||||||
|
view: {
|
||||||
|
linkLabelsPosition: {},
|
||||||
|
paper: {
|
||||||
|
dimensions: {
|
||||||
|
height: 245,
|
||||||
|
width: 380,
|
||||||
|
x: 450,
|
||||||
|
y: 50,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
inputs: null,
|
||||||
|
outputs: null,
|
||||||
|
quiet: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const XSOARPlaybooksResponse = {
|
||||||
|
playbooks: [XSOARPlaybook0],
|
||||||
|
tags: [
|
||||||
|
'Phishing',
|
||||||
|
'Sandbox',
|
||||||
|
'Severity',
|
||||||
|
'Malware',
|
||||||
|
'Remediation',
|
||||||
|
'Job',
|
||||||
|
'Sinkhole',
|
||||||
|
'TIM',
|
||||||
|
'PAN-OS',
|
||||||
|
],
|
||||||
|
total: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const XSOARRunSuccessResponse = {
|
||||||
|
id: '178791',
|
||||||
|
version: 0,
|
||||||
|
cacheVersn: 0,
|
||||||
|
modified: '1970-01-01T00:00:00Z',
|
||||||
|
sizeInBytes: 0,
|
||||||
|
CustomFields: {
|
||||||
|
bmcassignee: [{}],
|
||||||
|
bmccustomer: [{}],
|
||||||
|
bmcrequester: [{}],
|
||||||
|
containmentsla: {
|
||||||
|
accumulatedPause: 0,
|
||||||
|
breachTriggered: false,
|
||||||
|
dueDate: '0001-01-01T00:00:00Z',
|
||||||
|
endDate: '0001-01-01T00:00:00Z',
|
||||||
|
lastPauseDate: '0001-01-01T00:00:00Z',
|
||||||
|
runStatus: 'idle',
|
||||||
|
sla: 30,
|
||||||
|
slaStatus: -1,
|
||||||
|
startDate: '0001-01-01T00:00:00Z',
|
||||||
|
totalDuration: 0,
|
||||||
|
},
|
||||||
|
crowdstrikefalconbehaviourpatterndispositiondetails: [{}, {}, {}],
|
||||||
|
datadogcloudsiem: [{}, {}, {}],
|
||||||
|
dataminrpulserelatedterms: [{}, {}, {}],
|
||||||
|
decyfirdatadetails: [{}, {}, {}],
|
||||||
|
detectionsla: {
|
||||||
|
accumulatedPause: 0,
|
||||||
|
breachTriggered: false,
|
||||||
|
dueDate: '0001-01-01T00:00:00Z',
|
||||||
|
endDate: '0001-01-01T00:00:00Z',
|
||||||
|
lastPauseDate: '0001-01-01T00:00:00Z',
|
||||||
|
runStatus: 'idle',
|
||||||
|
sla: 20,
|
||||||
|
slaStatus: -1,
|
||||||
|
startDate: '0001-01-01T00:00:00Z',
|
||||||
|
totalDuration: 0,
|
||||||
|
},
|
||||||
|
domaintoolsirisdetect: [{}, {}, {}],
|
||||||
|
endpoint: [{}],
|
||||||
|
externalid: '178791',
|
||||||
|
extrahoprevealxdetectiondevices: [{}, {}, {}],
|
||||||
|
extrahoprevealxmitretechniques: [{}, {}, {}],
|
||||||
|
filerelationships: [{}, {}, {}],
|
||||||
|
fortisiemattacktactics: [{}, {}],
|
||||||
|
fortisiemevents: [{}],
|
||||||
|
incidentduration: {
|
||||||
|
accumulatedPause: 0,
|
||||||
|
breachTriggered: false,
|
||||||
|
dueDate: '0001-01-01T00:00:00Z',
|
||||||
|
endDate: '0001-01-01T00:00:00Z',
|
||||||
|
lastPauseDate: '0001-01-01T00:00:00Z',
|
||||||
|
runStatus: 'idle',
|
||||||
|
sla: 0,
|
||||||
|
slaStatus: -1,
|
||||||
|
startDate: '0001-01-01T00:00:00Z',
|
||||||
|
totalDuration: 0,
|
||||||
|
},
|
||||||
|
incidentrdpachehuntingstringssimilarity: [{}, {}, {}],
|
||||||
|
incidentrdpcachehuntingstringsifter: [{}, {}, {}],
|
||||||
|
inventasource: [{}],
|
||||||
|
microsoftsentinelowner: [],
|
||||||
|
qintelqwatchexposures: [{}, {}, {}],
|
||||||
|
remediationsla: {
|
||||||
|
accumulatedPause: 0,
|
||||||
|
breachTriggered: false,
|
||||||
|
dueDate: '0001-01-01T00:00:00Z',
|
||||||
|
endDate: '0001-01-01T00:00:00Z',
|
||||||
|
lastPauseDate: '0001-01-01T00:00:00Z',
|
||||||
|
runStatus: 'idle',
|
||||||
|
sla: 7200,
|
||||||
|
slaStatus: -1,
|
||||||
|
startDate: '0001-01-01T00:00:00Z',
|
||||||
|
totalDuration: 0,
|
||||||
|
},
|
||||||
|
rsametasevents: [],
|
||||||
|
rsarawlogslist: [],
|
||||||
|
securitypolicymatch: [{}],
|
||||||
|
similarincidentsdbot: [{}],
|
||||||
|
spycloudcompassdevicedata: [{}, {}, {}],
|
||||||
|
suspiciousexecutions: [{}, {}, {}],
|
||||||
|
timetoassignment: {
|
||||||
|
accumulatedPause: 0,
|
||||||
|
breachTriggered: false,
|
||||||
|
dueDate: '0001-01-01T00:00:00Z',
|
||||||
|
endDate: '0001-01-01T00:00:00Z',
|
||||||
|
lastPauseDate: '0001-01-01T00:00:00Z',
|
||||||
|
runStatus: 'idle',
|
||||||
|
sla: 0,
|
||||||
|
slaStatus: -1,
|
||||||
|
startDate: '0001-01-01T00:00:00Z',
|
||||||
|
totalDuration: 0,
|
||||||
|
},
|
||||||
|
triagesla: {
|
||||||
|
accumulatedPause: 0,
|
||||||
|
breachTriggered: false,
|
||||||
|
dueDate: '0001-01-01T00:00:00Z',
|
||||||
|
endDate: '0001-01-01T00:00:00Z',
|
||||||
|
lastPauseDate: '0001-01-01T00:00:00Z',
|
||||||
|
runStatus: 'idle',
|
||||||
|
sla: 30,
|
||||||
|
slaStatus: -1,
|
||||||
|
startDate: '0001-01-01T00:00:00Z',
|
||||||
|
totalDuration: 0,
|
||||||
|
},
|
||||||
|
urlsslverification: [],
|
||||||
|
xdralertsearchresults: [{}, {}, {}],
|
||||||
|
xdrinvestigationresults: [
|
||||||
|
{},
|
||||||
|
{},
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
columnheader1: '',
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
columnheader1: '',
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
{},
|
||||||
|
],
|
||||||
|
xpanseserviceclassifications: [{}, {}, {}],
|
||||||
|
xpanseservicevalidation: [
|
||||||
|
{
|
||||||
|
columnheader1: '',
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
{},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
account: '',
|
||||||
|
autime: 1713700028107000000,
|
||||||
|
type: 'Unclassified',
|
||||||
|
rawType: 'Unclassified',
|
||||||
|
name: 'My test incident',
|
||||||
|
rawName: 'My test incident',
|
||||||
|
status: 0,
|
||||||
|
custom_status: '',
|
||||||
|
resolution_status: '',
|
||||||
|
reason: '',
|
||||||
|
created: '2024-04-21T11:47:08.107Z',
|
||||||
|
occurred: '2024-04-21T11:47:08.107982676Z',
|
||||||
|
closed: '0001-01-01T00:00:00Z',
|
||||||
|
sla: 0,
|
||||||
|
severity: 2,
|
||||||
|
investigationId: '',
|
||||||
|
labels: [
|
||||||
|
{
|
||||||
|
value: '',
|
||||||
|
type: 'Instance',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'Manual',
|
||||||
|
type: 'Brand',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
attachment: null,
|
||||||
|
details: 'My test incident',
|
||||||
|
openDuration: 0,
|
||||||
|
lastOpen: '0001-01-01T00:00:00Z',
|
||||||
|
closingUserId: '',
|
||||||
|
owner: '',
|
||||||
|
activated: '0001-01-01T00:00:00Z',
|
||||||
|
closeReason: '',
|
||||||
|
rawCloseReason: '',
|
||||||
|
closeNotes: '',
|
||||||
|
playbookId: '8db0105c-f674-4d83-8095-f95a9f61e77a',
|
||||||
|
dueDate: '2024-05-01T11:47:08.107988742Z',
|
||||||
|
reminder: '0001-01-01T00:00:00Z',
|
||||||
|
runStatus: '',
|
||||||
|
notifyTime: '0001-01-01T00:00:00Z',
|
||||||
|
phase: '',
|
||||||
|
rawPhase: '',
|
||||||
|
isPlayground: false,
|
||||||
|
rawJSON: '',
|
||||||
|
parent: '',
|
||||||
|
parentXDRIncident: '',
|
||||||
|
retained: false,
|
||||||
|
category: '',
|
||||||
|
rawCategory: '',
|
||||||
|
linkedIncidents: null,
|
||||||
|
linkedCount: 0,
|
||||||
|
droppedCount: 0,
|
||||||
|
sourceInstance: '',
|
||||||
|
sourceBrand: 'Manual',
|
||||||
|
canvases: null,
|
||||||
|
lastJobRunTime: '0001-01-01T00:00:00Z',
|
||||||
|
feedBased: false,
|
||||||
|
dbotMirrorId: '',
|
||||||
|
dbotMirrorInstance: '',
|
||||||
|
dbotMirrorDirection: '',
|
||||||
|
dbotDirtyFields: null,
|
||||||
|
dbotCurrentDirtyFields: null,
|
||||||
|
dbotMirrorTags: null,
|
||||||
|
dbotMirrorLastSync: '0001-01-01T00:00:00Z',
|
||||||
|
isDebug: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const XSOARFailedResponse = {
|
||||||
|
id: 'incCreateErr',
|
||||||
|
status: 400,
|
||||||
|
title: 'Failed creating incident',
|
||||||
|
detail: 'Failed creating incident',
|
||||||
|
error: '',
|
||||||
|
encrypted: false,
|
||||||
|
multires: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
export class XSOARSimulator extends Simulator {
|
||||||
|
private readonly returnError: boolean;
|
||||||
|
|
||||||
|
constructor({ returnError = false, proxy }: { returnError?: boolean; proxy?: ProxyArgs }) {
|
||||||
|
super(proxy);
|
||||||
|
|
||||||
|
this.returnError = returnError;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async handler(
|
||||||
|
request: http.IncomingMessage,
|
||||||
|
response: http.ServerResponse,
|
||||||
|
data: Record<string, unknown>
|
||||||
|
) {
|
||||||
|
if (this.returnError) {
|
||||||
|
return XSOARSimulator.sendErrorResponse(response);
|
||||||
|
}
|
||||||
|
return XSOARSimulator.sendResponse(request, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static sendResponse(request: http.IncomingMessage, response: http.ServerResponse) {
|
||||||
|
response.setHeader('Content-Type', 'application/json');
|
||||||
|
let body;
|
||||||
|
if (request.url?.match('/incident')) {
|
||||||
|
response.statusCode = 201;
|
||||||
|
body = XSOARRunSuccessResponse;
|
||||||
|
} else if (request.url?.match('/playbook/search')) {
|
||||||
|
response.statusCode = 200;
|
||||||
|
body = XSOARPlaybooksResponse;
|
||||||
|
}
|
||||||
|
response.end(JSON.stringify(body, null, 4));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static sendErrorResponse(response: http.ServerResponse) {
|
||||||
|
response.statusCode = 400;
|
||||||
|
response.setHeader('Content-Type', 'application/json');
|
||||||
|
response.end(JSON.stringify(XSOARFailedResponse, null, 4));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function initPlugin(router: IRouter, path: string) {
|
||||||
|
router.post(
|
||||||
|
{
|
||||||
|
path: `${path}/playbook/search`,
|
||||||
|
options: {
|
||||||
|
authRequired: false,
|
||||||
|
},
|
||||||
|
validate: {},
|
||||||
|
security: { authz: { enabled: false, reason: 'This route is opted out from authorization' } },
|
||||||
|
},
|
||||||
|
async function (
|
||||||
|
context: RequestHandlerContext,
|
||||||
|
req: KibanaRequest<any, any, any, any>,
|
||||||
|
res: KibanaResponseFactory
|
||||||
|
): Promise<IKibanaResponse<any>> {
|
||||||
|
return res.ok({ body: XSOARPlaybooksResponse });
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
router.post(
|
||||||
|
{
|
||||||
|
path: `${path}/incident`,
|
||||||
|
options: {
|
||||||
|
authRequired: false,
|
||||||
|
},
|
||||||
|
validate: {},
|
||||||
|
security: { authz: { enabled: false, reason: 'This route is opted out from authorization' } },
|
||||||
|
},
|
||||||
|
async function (
|
||||||
|
context: RequestHandlerContext,
|
||||||
|
req: KibanaRequest<any, any, any, any>,
|
||||||
|
res: KibanaResponseFactory
|
||||||
|
): Promise<IKibanaResponse<any>> {
|
||||||
|
return res.ok({ body: XSOARRunSuccessResponse });
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,384 @@
|
||||||
|
/*
|
||||||
|
* 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 expect from '@kbn/expect';
|
||||||
|
|
||||||
|
import {
|
||||||
|
XSOARSimulator,
|
||||||
|
XSOARPlaybooksResponse,
|
||||||
|
} from '@kbn/actions-simulators-plugin/server/xsoar_simulation';
|
||||||
|
import { TaskErrorSource } from '@kbn/task-manager-plugin/common';
|
||||||
|
import type { FtrProviderContext } from '../../../../../common/ftr_provider_context';
|
||||||
|
|
||||||
|
const connectorTypeId = '.xsoar';
|
||||||
|
const name = 'XSOAR action';
|
||||||
|
const secrets = {
|
||||||
|
apiKey: 'apiKey',
|
||||||
|
};
|
||||||
|
|
||||||
|
// eslint-disable-next-line import/no-default-export
|
||||||
|
export default function xsoarTest({ getService }: FtrProviderContext) {
|
||||||
|
const supertest = getService('supertest');
|
||||||
|
const configService = getService('config');
|
||||||
|
|
||||||
|
const createConnector = async (url: string) => {
|
||||||
|
const { body } = await supertest
|
||||||
|
.post('/api/actions/connector')
|
||||||
|
.set('kbn-xsrf', 'foo')
|
||||||
|
.send({
|
||||||
|
name,
|
||||||
|
connector_type_id: connectorTypeId,
|
||||||
|
config: { url },
|
||||||
|
secrets,
|
||||||
|
})
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
return body.id;
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('XSOAR', () => {
|
||||||
|
describe('action creation', () => {
|
||||||
|
const simulator = new XSOARSimulator({
|
||||||
|
returnError: false,
|
||||||
|
proxy: {
|
||||||
|
config: configService.get('kbnTestServer.serverArgs'),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const config = { url: '' };
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
config.url = await simulator.start();
|
||||||
|
});
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
simulator.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return 200 when creating the connector', async () => {
|
||||||
|
const { body: createdAction } = await supertest
|
||||||
|
.post('/api/actions/connector')
|
||||||
|
.set('kbn-xsrf', 'foo')
|
||||||
|
.send({
|
||||||
|
name,
|
||||||
|
connector_type_id: connectorTypeId,
|
||||||
|
config,
|
||||||
|
secrets,
|
||||||
|
})
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
expect(createdAction).to.eql({
|
||||||
|
id: createdAction.id,
|
||||||
|
is_preconfigured: false,
|
||||||
|
is_system_action: false,
|
||||||
|
is_deprecated: false,
|
||||||
|
name,
|
||||||
|
connector_type_id: connectorTypeId,
|
||||||
|
is_missing_secrets: false,
|
||||||
|
config,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return 400 Bad Request when creating the connector without the url', async () => {
|
||||||
|
await supertest
|
||||||
|
.post('/api/actions/connector')
|
||||||
|
.set('kbn-xsrf', 'foo')
|
||||||
|
.send({
|
||||||
|
name,
|
||||||
|
connector_type_id: connectorTypeId,
|
||||||
|
config: {},
|
||||||
|
secrets,
|
||||||
|
})
|
||||||
|
.expect(400)
|
||||||
|
.then((resp: any) => {
|
||||||
|
expect(resp.body).to.eql({
|
||||||
|
statusCode: 400,
|
||||||
|
error: 'Bad Request',
|
||||||
|
message:
|
||||||
|
'error validating action type config: [url]: expected value of type [string] but got [undefined]',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return 400 Bad Request when creating the connector with a url that is not allowed', async () => {
|
||||||
|
await supertest
|
||||||
|
.post('/api/actions/connector')
|
||||||
|
.set('kbn-xsrf', 'foo')
|
||||||
|
.send({
|
||||||
|
name,
|
||||||
|
connector_type_id: connectorTypeId,
|
||||||
|
config: {
|
||||||
|
url: 'http://xsoar.mynonexistent.com',
|
||||||
|
},
|
||||||
|
secrets,
|
||||||
|
})
|
||||||
|
.expect(400)
|
||||||
|
.then((resp: any) => {
|
||||||
|
expect(resp.body).to.eql({
|
||||||
|
statusCode: 400,
|
||||||
|
error: 'Bad Request',
|
||||||
|
message:
|
||||||
|
'error validating action type config: error validating url: target url "http://xsoar.mynonexistent.com" is not added to the Kibana config xpack.actions.allowedHosts',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return 400 Bad Request when creating the connector without secrets', async () => {
|
||||||
|
await supertest
|
||||||
|
.post('/api/actions/connector')
|
||||||
|
.set('kbn-xsrf', 'foo')
|
||||||
|
.send({
|
||||||
|
name,
|
||||||
|
connector_type_id: connectorTypeId,
|
||||||
|
config,
|
||||||
|
})
|
||||||
|
.expect(400)
|
||||||
|
.then((resp: any) => {
|
||||||
|
expect(resp.body).to.eql({
|
||||||
|
statusCode: 400,
|
||||||
|
error: 'Bad Request',
|
||||||
|
message:
|
||||||
|
'error validating action type secrets: [apiKey]: expected value of type [string] but got [undefined]',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('executor', () => {
|
||||||
|
describe('validation', () => {
|
||||||
|
const simulator = new XSOARSimulator({
|
||||||
|
proxy: {
|
||||||
|
config: configService.get('kbnTestServer.serverArgs'),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
let xsoarActionId: string;
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
const url = await simulator.start();
|
||||||
|
xsoarActionId = await createConnector(url);
|
||||||
|
});
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
simulator.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fail when the params is empty', async () => {
|
||||||
|
const { body } = await supertest
|
||||||
|
.post(`/api/actions/connector/${xsoarActionId}/_execute`)
|
||||||
|
.set('kbn-xsrf', 'foo')
|
||||||
|
.send({
|
||||||
|
params: {},
|
||||||
|
});
|
||||||
|
expect(200);
|
||||||
|
|
||||||
|
expect(Object.keys(body).sort()).to.eql([
|
||||||
|
'connector_id',
|
||||||
|
'errorSource',
|
||||||
|
'message',
|
||||||
|
'retry',
|
||||||
|
'status',
|
||||||
|
]);
|
||||||
|
expect(body.connector_id).to.eql(xsoarActionId);
|
||||||
|
expect(body.status).to.eql('error');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fail when the subAction is invalid', async () => {
|
||||||
|
const { body } = await supertest
|
||||||
|
.post(`/api/actions/connector/${xsoarActionId}/_execute`)
|
||||||
|
.set('kbn-xsrf', 'foo')
|
||||||
|
.send({
|
||||||
|
params: { subAction: 'invalidAction' },
|
||||||
|
})
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
expect(body).to.eql({
|
||||||
|
connector_id: xsoarActionId,
|
||||||
|
status: 'error',
|
||||||
|
retry: true,
|
||||||
|
message: 'an error occurred while running the action',
|
||||||
|
errorSource: TaskErrorSource.FRAMEWORK,
|
||||||
|
service_message: `Sub action "invalidAction" is not registered. Connector id: ${xsoarActionId}. Connector name: XSOAR. Connector type: ${connectorTypeId}`,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should fail to run when the name parameter isn't included", async () => {
|
||||||
|
const { body } = await supertest
|
||||||
|
.post(`/api/actions/connector/${xsoarActionId}/_execute`)
|
||||||
|
.set('kbn-xsrf', 'foo')
|
||||||
|
.send({
|
||||||
|
params: {
|
||||||
|
subAction: 'run',
|
||||||
|
subActionParams: {
|
||||||
|
severity: 1,
|
||||||
|
createInvestigation: false,
|
||||||
|
body: '',
|
||||||
|
isRuleSeverity: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
expect(body).to.eql({
|
||||||
|
connector_id: xsoarActionId,
|
||||||
|
status: 'error',
|
||||||
|
retry: true,
|
||||||
|
message: 'an error occurred while running the action',
|
||||||
|
errorSource: TaskErrorSource.USER,
|
||||||
|
service_message:
|
||||||
|
'Request validation failed (Error: [name]: expected value of type [string] but got [undefined])',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('execution', () => {
|
||||||
|
describe('successful response simulator', () => {
|
||||||
|
const simulator = new XSOARSimulator({
|
||||||
|
proxy: {
|
||||||
|
config: configService.get('kbnTestServer.serverArgs'),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
let url: string;
|
||||||
|
let xsoarActionId: string;
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
url = await simulator.start();
|
||||||
|
xsoarActionId = await createConnector(url);
|
||||||
|
});
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
simulator.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get playbooks', async () => {
|
||||||
|
const { body } = await supertest
|
||||||
|
.post(`/api/actions/connector/${xsoarActionId}/_execute`)
|
||||||
|
.set('kbn-xsrf', 'foo')
|
||||||
|
.send({
|
||||||
|
params: { subAction: 'getPlaybooks', subActionParams: {} },
|
||||||
|
})
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
expect(simulator.requestUrl).to.eql(`${url}/playbook/search`);
|
||||||
|
expect(body).to.eql({
|
||||||
|
status: 'ok',
|
||||||
|
connector_id: xsoarActionId,
|
||||||
|
data: XSOARPlaybooksResponse,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create incident', async () => {
|
||||||
|
const { body } = await supertest
|
||||||
|
.post(`/api/actions/connector/${xsoarActionId}/_execute`)
|
||||||
|
.set('kbn-xsrf', 'foo')
|
||||||
|
.send({
|
||||||
|
params: {
|
||||||
|
subAction: 'run',
|
||||||
|
subActionParams: {
|
||||||
|
name: 'My test incident',
|
||||||
|
severity: 2,
|
||||||
|
playbookId: '8db0105c-f674-4d83-8095-f95a9f61e77a',
|
||||||
|
createInvestigation: false,
|
||||||
|
body: null,
|
||||||
|
isRuleSeverity: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
expect(simulator.requestData).to.eql({
|
||||||
|
name: 'My test incident',
|
||||||
|
severity: 2,
|
||||||
|
playbookId: '8db0105c-f674-4d83-8095-f95a9f61e77a',
|
||||||
|
createInvestigation: false,
|
||||||
|
});
|
||||||
|
expect(simulator.requestUrl).to.eql(`${url}/incident`);
|
||||||
|
expect(body).to.eql({
|
||||||
|
status: 'ok',
|
||||||
|
connector_id: xsoarActionId,
|
||||||
|
data: {},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('error response simulator', () => {
|
||||||
|
const simulator = new XSOARSimulator({
|
||||||
|
returnError: true,
|
||||||
|
proxy: {
|
||||||
|
config: configService.get('kbnTestServer.serverArgs'),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
let xsoarActionId: string;
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
const url = await simulator.start();
|
||||||
|
xsoarActionId = await createConnector(url);
|
||||||
|
});
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
simulator.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return a failure when attempting to get playbooks', async () => {
|
||||||
|
const { body } = await supertest
|
||||||
|
.post(`/api/actions/connector/${xsoarActionId}/_execute`)
|
||||||
|
.set('kbn-xsrf', 'foo')
|
||||||
|
.send({
|
||||||
|
params: {
|
||||||
|
subAction: 'getPlaybooks',
|
||||||
|
subActionParams: {},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
expect(body).to.eql({
|
||||||
|
status: 'error',
|
||||||
|
message: 'an error occurred while running the action',
|
||||||
|
retry: true,
|
||||||
|
connector_id: xsoarActionId,
|
||||||
|
errorSource: TaskErrorSource.FRAMEWORK,
|
||||||
|
service_message: 'Status code: 400. Message: API Error: Bad Request',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return a failure when attempting to run', async () => {
|
||||||
|
const { body } = await supertest
|
||||||
|
.post(`/api/actions/connector/${xsoarActionId}/_execute`)
|
||||||
|
.set('kbn-xsrf', 'foo')
|
||||||
|
.send({
|
||||||
|
params: {
|
||||||
|
subAction: 'run',
|
||||||
|
subActionParams: {
|
||||||
|
name: 'My test incident',
|
||||||
|
playbookId: '8db0105c-f674-4d83-8095-f95a9f61e77a',
|
||||||
|
severity: 1,
|
||||||
|
isRuleSeverity: false,
|
||||||
|
createInvestigation: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
expect(simulator.requestData).to.eql({
|
||||||
|
name: 'My test incident',
|
||||||
|
playbookId: '8db0105c-f674-4d83-8095-f95a9f61e77a',
|
||||||
|
severity: 1,
|
||||||
|
createInvestigation: false,
|
||||||
|
});
|
||||||
|
expect(body).to.eql({
|
||||||
|
status: 'error',
|
||||||
|
message: 'an error occurred while running the action',
|
||||||
|
retry: true,
|
||||||
|
connector_id: xsoarActionId,
|
||||||
|
errorSource: TaskErrorSource.FRAMEWORK,
|
||||||
|
service_message: 'Status code: 400. Message: API Error: Bad Request',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
|
@ -44,6 +44,7 @@ export default function connectorsTests({ loadTestFile, getService }: FtrProvide
|
||||||
loadTestFile(require.resolve('./connector_types/thehive'));
|
loadTestFile(require.resolve('./connector_types/thehive'));
|
||||||
loadTestFile(require.resolve('./connector_types/bedrock'));
|
loadTestFile(require.resolve('./connector_types/bedrock'));
|
||||||
loadTestFile(require.resolve('./connector_types/gemini'));
|
loadTestFile(require.resolve('./connector_types/gemini'));
|
||||||
|
loadTestFile(require.resolve('./connector_types/xsoar'));
|
||||||
loadTestFile(require.resolve('./create'));
|
loadTestFile(require.resolve('./create'));
|
||||||
loadTestFile(require.resolve('./delete'));
|
loadTestFile(require.resolve('./delete'));
|
||||||
loadTestFile(require.resolve('./execute'));
|
loadTestFile(require.resolve('./execute'));
|
||||||
|
|
|
@ -58,6 +58,7 @@ export default function createRegisteredConnectorTypeTests({ getService }: FtrPr
|
||||||
'.cases',
|
'.cases',
|
||||||
'.crowdstrike',
|
'.crowdstrike',
|
||||||
'.microsoft_defender_endpoint',
|
'.microsoft_defender_endpoint',
|
||||||
|
'.xsoar',
|
||||||
].sort()
|
].sort()
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -88,6 +88,7 @@ export default function ({ getService }: FtrProviderContext) {
|
||||||
'actions:.torq',
|
'actions:.torq',
|
||||||
'actions:.webhook',
|
'actions:.webhook',
|
||||||
'actions:.xmatters',
|
'actions:.xmatters',
|
||||||
|
'actions:.xsoar',
|
||||||
'actions:connector_usage_reporting',
|
'actions:connector_usage_reporting',
|
||||||
'actions_telemetry',
|
'actions_telemetry',
|
||||||
'ad_hoc_run-backfill',
|
'ad_hoc_run-backfill',
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue