diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 06e0d2478429f..0ba3f7fdcc32a 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -1146,6 +1146,11 @@ x-pack/plugins/cloud_integrations/cloud_full_story/server/config.ts @elastic/kib
/x-pack/plugins/stack_connectors/server/connector_types/gen_ai @elastic/security-threat-hunting-explore
/x-pack/plugins/stack_connectors/common/gen_ai @elastic/security-threat-hunting-explore
+## Defend Workflows owner connectors
+/x-pack/plugins/stack_connectors/public/connector_types/sentinelone @elastic/security-defend-workflows
+/x-pack/plugins/stack_connectors/server/connector_types/sentinelone @elastic/security-defend-workflows
+/x-pack/plugins/stack_connectors/common/sentinelone @elastic/security-defend-workflows
+
## Security Solution sub teams - Detection Rule Management
/x-pack/plugins/security_solution/common/api/detection_engine/model/rule_schema @elastic/security-detection-rule-management @elastic/security-detection-engine
/x-pack/plugins/security_solution/common/api/detection_engine/fleet_integrations @elastic/security-detection-rule-management
diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml
index b08ff89b5dd09..d63b19f67f783 100644
--- a/packages/kbn-optimizer/limits.yml
+++ b/packages/kbn-optimizer/limits.yml
@@ -129,7 +129,7 @@ pageLoadAssetSize:
snapshotRestore: 79032
spaces: 57868
stackAlerts: 58316
- stackConnectors: 36314
+ stackConnectors: 52131
synthetics: 40958
telemetry: 51957
telemetryManagementSection: 38586
diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/use_sub_action.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/use_sub_action.tsx
new file mode 100644
index 0000000000000..44f9099a5b816
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/use_sub_action.tsx
@@ -0,0 +1,43 @@
+/*
+ * 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 { executeAction } from '@kbn/triggers-actions-ui-plugin/public';
+import { useQuery } from '@tanstack/react-query';
+import { useKibana } from '../../../../../common/lib/kibana/kibana_react';
+
+export interface UseSubActionParams
{
+ connectorId: string;
+ subAction: string;
+ subActionParams?: P;
+ disabled?: boolean;
+}
+
+export const useSubAction =
({
+ connectorId,
+ subAction,
+ subActionParams,
+ disabled = false,
+ ...rest
+}: UseSubActionParams
) => {
+ const { http } = useKibana().services;
+
+ return useQuery({
+ queryKey: ['useSubAction', connectorId, subAction, subActionParams],
+ queryFn: ({ signal }) =>
+ executeAction({
+ id: connectorId,
+ params: {
+ subAction,
+ subActionParams,
+ },
+ http,
+ signal,
+ }),
+ enabled: !disabled && !!connectorId && !!subAction,
+ ...rest,
+ });
+};
diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/use_sub_action_mutation.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/use_sub_action_mutation.tsx
new file mode 100644
index 0000000000000..78c48ccca1491
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/use_sub_action_mutation.tsx
@@ -0,0 +1,38 @@
+/*
+ * 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 { executeAction } from '@kbn/triggers-actions-ui-plugin/public';
+import { useMutation } from '@tanstack/react-query';
+import { useKibana } from '../../../../../common/lib/kibana/kibana_react';
+
+export interface UseSubActionParams {
+ connectorId: string;
+ subAction: string;
+ subActionParams?: P;
+ disabled?: boolean;
+}
+
+export const useSubActionMutation =
({
+ connectorId,
+ subAction,
+ subActionParams,
+ disabled = false,
+}: UseSubActionParams
) => {
+ const { http } = useKibana().services;
+
+ return useMutation({
+ mutationFn: () =>
+ executeAction({
+ id: connectorId,
+ params: {
+ subAction,
+ subActionParams,
+ },
+ http,
+ }),
+ });
+};
diff --git a/x-pack/plugins/stack_connectors/common/sentinelone/constants.ts b/x-pack/plugins/stack_connectors/common/sentinelone/constants.ts
new file mode 100644
index 0000000000000..a77e070a71056
--- /dev/null
+++ b/x-pack/plugins/stack_connectors/common/sentinelone/constants.ts
@@ -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.
+ */
+
+export const SENTINELONE_TITLE = 'Sentinel One';
+export const SENTINELONE_CONNECTOR_ID = '.sentinelone';
+export const API_MAX_RESULTS = 1000;
+
+export enum SUB_ACTION {
+ KILL_PROCESS = 'killProcess',
+ EXECUTE_SCRIPT = 'executeScript',
+ GET_AGENTS = 'getAgents',
+ ISOLATE_AGENT = 'isolateAgent',
+ RELEASE_AGENT = 'releaseAgent',
+ GET_REMOTE_SCRIPTS = 'getRemoteScripts',
+ GET_REMOTE_SCRIPT_STATUS = 'getRemoteScriptStatus',
+ GET_REMOTE_SCRIPT_RESULTS = 'getRemoteScriptResults',
+}
diff --git a/x-pack/plugins/stack_connectors/common/sentinelone/schema.ts b/x-pack/plugins/stack_connectors/common/sentinelone/schema.ts
new file mode 100644
index 0000000000000..f475a9e6a83f6
--- /dev/null
+++ b/x-pack/plugins/stack_connectors/common/sentinelone/schema.ts
@@ -0,0 +1,495 @@
+/*
+ * 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.
+ */
+
+/* eslint-disable @typescript-eslint/naming-convention */
+
+import { schema } from '@kbn/config-schema';
+import { SUB_ACTION } from './constants';
+
+// Connector schema
+export const SentinelOneConfigSchema = schema.object({ url: schema.string() });
+export const SentinelOneSecretsSchema = schema.object({
+ token: schema.string(),
+});
+
+export const SentinelOneBaseApiResponseSchema = schema.object({}, { unknowns: 'allow' });
+
+export const SentinelOneGetAgentsResponseSchema = schema.object({
+ pagination: schema.object({
+ totalItems: schema.number(),
+ nextCursor: schema.nullable(schema.string()),
+ }),
+ errors: schema.nullable(schema.arrayOf(schema.string())),
+ data: schema.arrayOf(
+ schema.object({
+ modelName: schema.string(),
+ firewallEnabled: schema.boolean(),
+ totalMemory: schema.number(),
+ osName: schema.string(),
+ cloudProviders: schema.recordOf(schema.string(), schema.any()),
+ siteName: schema.string(),
+ cpuId: schema.string(),
+ isPendingUninstall: schema.boolean(),
+ isUpToDate: schema.boolean(),
+ osArch: schema.string(),
+ accountId: schema.string(),
+ locationEnabled: schema.boolean(),
+ consoleMigrationStatus: schema.string(),
+ scanFinishedAt: schema.nullable(schema.string()),
+ operationalStateExpiration: schema.nullable(schema.string()),
+ agentVersion: schema.string(),
+ isActive: schema.boolean(),
+ locationType: schema.string(),
+ activeThreats: schema.number(),
+ inRemoteShellSession: schema.boolean(),
+ allowRemoteShell: schema.boolean(),
+ serialNumber: schema.nullable(schema.string()),
+ updatedAt: schema.string(),
+ lastActiveDate: schema.string(),
+ firstFullModeTime: schema.nullable(schema.string()),
+ operationalState: schema.string(),
+ externalId: schema.string(),
+ mitigationModeSuspicious: schema.string(),
+ licenseKey: schema.string(),
+ cpuCount: schema.number(),
+ mitigationMode: schema.string(),
+ networkStatus: schema.string(),
+ installerType: schema.string(),
+ uuid: schema.string(),
+ detectionState: schema.nullable(schema.string()),
+ infected: schema.boolean(),
+ registeredAt: schema.string(),
+ lastIpToMgmt: schema.string(),
+ storageName: schema.nullable(schema.string()),
+ osUsername: schema.string(),
+ groupIp: schema.string(),
+ createdAt: schema.string(),
+ remoteProfilingState: schema.string(),
+ groupUpdatedAt: schema.nullable(schema.string()),
+ scanAbortedAt: schema.nullable(schema.string()),
+ isUninstalled: schema.boolean(),
+ networkQuarantineEnabled: schema.boolean(),
+ tags: schema.object({
+ sentinelone: schema.arrayOf(
+ schema.object({
+ assignedBy: schema.string(),
+ assignedAt: schema.string(),
+ assignedById: schema.string(),
+ key: schema.string(),
+ value: schema.string(),
+ id: schema.string(),
+ })
+ ),
+ }),
+ externalIp: schema.string(),
+ siteId: schema.string(),
+ machineType: schema.string(),
+ domain: schema.string(),
+ scanStatus: schema.string(),
+ osStartTime: schema.string(),
+ accountName: schema.string(),
+ lastLoggedInUserName: schema.string(),
+ showAlertIcon: schema.boolean(),
+ rangerStatus: schema.string(),
+ groupName: schema.string(),
+ threatRebootRequired: schema.boolean(),
+ remoteProfilingStateExpiration: schema.nullable(schema.string()),
+ policyUpdatedAt: schema.nullable(schema.string()),
+ activeDirectory: schema.object({
+ userPrincipalName: schema.nullable(schema.string()),
+ lastUserDistinguishedName: schema.nullable(schema.string()),
+ computerMemberOf: schema.arrayOf(schema.object({ type: schema.string() })),
+ lastUserMemberOf: schema.arrayOf(schema.object({ type: schema.string() })),
+ mail: schema.nullable(schema.string()),
+ computerDistinguishedName: schema.nullable(schema.string()),
+ }),
+ isDecommissioned: schema.boolean(),
+ rangerVersion: schema.string(),
+ userActionsNeeded: schema.arrayOf(
+ schema.object({
+ type: schema.string(),
+ example: schema.string(),
+ enum: schema.arrayOf(schema.string()),
+ })
+ ),
+ locations: schema.nullable(
+ schema.arrayOf(
+ schema.object({ name: schema.string(), scope: schema.string(), id: schema.string() })
+ )
+ ),
+ id: schema.string(),
+ coreCount: schema.number(),
+ osRevision: schema.string(),
+ osType: schema.string(),
+ groupId: schema.string(),
+ computerName: schema.string(),
+ scanStartedAt: schema.string(),
+ encryptedApplications: schema.boolean(),
+ storageType: schema.nullable(schema.string()),
+ networkInterfaces: schema.arrayOf(
+ schema.object({
+ gatewayMacAddress: schema.nullable(schema.string()),
+ inet6: schema.arrayOf(schema.string()),
+ name: schema.string(),
+ inet: schema.arrayOf(schema.string()),
+ physical: schema.string(),
+ gatewayIp: schema.nullable(schema.string()),
+ id: schema.string(),
+ })
+ ),
+ fullDiskScanLastUpdatedAt: schema.string(),
+ appsVulnerabilityStatus: schema.string(),
+ })
+ ),
+});
+
+export const SentinelOneIsolateAgentResponseSchema = schema.object({
+ errors: schema.nullable(schema.arrayOf(schema.string())),
+ data: schema.object({
+ affected: schema.number(),
+ }),
+});
+
+export const SentinelOneGetRemoteScriptsParamsSchema = schema.object({
+ query: schema.nullable(schema.string()),
+ osTypes: schema.nullable(schema.arrayOf(schema.string())),
+});
+
+export const SentinelOneGetRemoteScriptsResponseSchema = schema.object({
+ errors: schema.nullable(schema.arrayOf(schema.string())),
+ pagination: schema.object({
+ nextCursor: schema.nullable(schema.string()),
+ totalItems: schema.number(),
+ }),
+ data: schema.arrayOf(
+ schema.object({
+ id: schema.string(),
+ updater: schema.nullable(schema.string()),
+ isAvailableForLite: schema.boolean(),
+ isAvailableForArs: schema.boolean(),
+ fileSize: schema.number(),
+ mgmtId: schema.number(),
+ scopeLevel: schema.string(),
+ shortFileName: schema.string(),
+ scriptName: schema.string(),
+ creator: schema.string(),
+ package: schema.nullable(
+ schema.object({
+ id: schema.string(),
+ bucketName: schema.string(),
+ endpointExpiration: schema.string(),
+ fileName: schema.string(),
+ endpointExpirationSeconds: schema.nullable(schema.number()),
+ fileSize: schema.number(),
+ signatureType: schema.string(),
+ signature: schema.string(),
+ })
+ ),
+ bucketName: schema.string(),
+ inputRequired: schema.boolean(),
+ fileName: schema.string(),
+ supportedDestinations: schema.nullable(schema.arrayOf(schema.string())),
+ scopeName: schema.nullable(schema.string()),
+ signatureType: schema.string(),
+ outputFilePaths: schema.nullable(schema.arrayOf(schema.string())),
+ scriptDescription: schema.nullable(schema.string()),
+ createdByUserId: schema.string(),
+ scopeId: schema.string(),
+ updatedAt: schema.string(),
+ scriptType: schema.string(),
+ scopePath: schema.string(),
+ creatorId: schema.string(),
+ osTypes: schema.arrayOf(schema.string()),
+ scriptRuntimeTimeoutSeconds: schema.number(),
+ version: schema.string(),
+ updaterId: schema.nullable(schema.string()),
+ createdAt: schema.string(),
+ inputExample: schema.nullable(schema.string()),
+ inputInstructions: schema.nullable(schema.string()),
+ signature: schema.string(),
+ createdByUser: schema.string(),
+ requiresApproval: schema.maybe(schema.boolean()),
+ })
+ ),
+});
+
+export const SentinelOneExecuteScriptParamsSchema = schema.object({
+ computerName: schema.maybe(schema.string()),
+ script: schema.object({
+ scriptId: schema.string(),
+ scriptName: schema.maybe(schema.string()),
+ apiKey: schema.maybe(schema.string()),
+ outputDirectory: schema.maybe(schema.string()),
+ requiresApproval: schema.maybe(schema.boolean()),
+ taskDescription: schema.maybe(schema.string()),
+ singularityxdrUrl: schema.maybe(schema.string()),
+ inputParams: schema.maybe(schema.string()),
+ singularityxdrKeyword: schema.maybe(schema.string()),
+ scriptRuntimeTimeoutSeconds: schema.maybe(schema.number()),
+ passwordFromScope: schema.maybe(
+ schema.object({
+ scopeLevel: schema.maybe(schema.string()),
+ scopeId: schema.maybe(schema.string()),
+ })
+ ),
+ password: schema.maybe(schema.string()),
+ }),
+});
+
+export const SentinelOneGetRemoteScriptStatusParamsSchema = schema.object(
+ {
+ parentTaskId: schema.string(),
+ },
+ { unknowns: 'allow' }
+);
+
+export const SentinelOneGetRemoteScriptStatusResponseSchema = schema.object({
+ pagination: schema.object({
+ totalItems: schema.number(),
+ nextCursor: schema.nullable(schema.string()),
+ }),
+ errors: schema.arrayOf(schema.object({ type: schema.string() })),
+ data: schema.arrayOf(
+ schema.object({
+ agentIsDecommissioned: schema.boolean(),
+ agentComputerName: schema.string(),
+ status: schema.string(),
+ groupName: schema.string(),
+ initiatedById: schema.string(),
+ parentTaskId: schema.string(),
+ updatedAt: schema.string(),
+ createdAt: schema.string(),
+ agentIsActive: schema.boolean(),
+ agentOsType: schema.string(),
+ agentMachineType: schema.string(),
+ id: schema.string(),
+ siteName: schema.string(),
+ detailedStatus: schema.string(),
+ siteId: schema.string(),
+ scriptResultsSignature: schema.nullable(schema.string()),
+ initiatedBy: schema.string(),
+ accountName: schema.string(),
+ groupId: schema.string(),
+ statusDescription: schema.object({
+ readOnly: schema.boolean(),
+ description: schema.string(),
+ }),
+ agentUuid: schema.string(),
+ accountId: schema.string(),
+ type: schema.string(),
+ scriptResultsPath: schema.string(),
+ scriptResultsBucket: schema.string(),
+ description: schema.string(),
+ agentId: schema.string(),
+ })
+ ),
+});
+
+export const SentinelOneBaseFilterSchema = schema.object({
+ K8SNodeName__contains: schema.nullable(schema.string()),
+ coreCount__lt: schema.nullable(schema.string()),
+ rangerStatuses: schema.nullable(schema.string()),
+ adUserQuery__contains: schema.nullable(schema.string()),
+ rangerVersionsNin: schema.nullable(schema.string()),
+ rangerStatusesNin: schema.nullable(schema.string()),
+ coreCount__gte: schema.nullable(schema.string()),
+ threatCreatedAt__gte: schema.nullable(schema.string()),
+ decommissionedAt__lte: schema.nullable(schema.string()),
+ operationalStatesNin: schema.nullable(schema.string()),
+ appsVulnerabilityStatusesNin: schema.nullable(schema.string()),
+ mitigationMode: schema.nullable(schema.string()),
+ createdAt__gte: schema.nullable(schema.string()),
+ gatewayIp: schema.nullable(schema.string()),
+ cloudImage__contains: schema.nullable(schema.string()),
+ registeredAt__between: schema.nullable(schema.string()),
+ threatMitigationStatus: schema.nullable(schema.string()),
+ installerTypesNin: schema.nullable(schema.string()),
+ appsVulnerabilityStatuses: schema.nullable(schema.string()),
+ threatResolved: schema.nullable(schema.string()),
+ mitigationModeSuspicious: schema.nullable(schema.string()),
+ isUpToDate: schema.nullable(schema.string()),
+ adComputerQuery__contains: schema.nullable(schema.string()),
+ updatedAt__gte: schema.nullable(schema.string()),
+ azureResourceGroup__contains: schema.nullable(schema.string()),
+ scanStatus: schema.nullable(schema.string()),
+ threatContentHash: schema.nullable(schema.string()),
+ osTypesNin: schema.nullable(schema.string()),
+ threatRebootRequired: schema.nullable(schema.string()),
+ totalMemory__between: schema.nullable(schema.string()),
+ firewallEnabled: schema.nullable(schema.string()),
+ gcpServiceAccount__contains: schema.nullable(schema.string()),
+ updatedAt__gt: schema.nullable(schema.string()),
+ remoteProfilingStates: schema.nullable(schema.string()),
+ filteredGroupIds: schema.nullable(schema.string()),
+ agentVersions: schema.nullable(schema.string()),
+ activeThreats: schema.nullable(schema.string()),
+ machineTypesNin: schema.nullable(schema.string()),
+ lastActiveDate__gt: schema.nullable(schema.string()),
+ awsSubnetIds__contains: schema.nullable(schema.string()),
+ installerTypes: schema.nullable(schema.string()),
+ registeredAt__gte: schema.nullable(schema.string()),
+ migrationStatus: schema.nullable(schema.string()),
+ cloudTags__contains: schema.nullable(schema.string()),
+ totalMemory__gte: schema.nullable(schema.string()),
+ decommissionedAt__lt: schema.nullable(schema.string()),
+ threatCreatedAt__lt: schema.nullable(schema.string()),
+ updatedAt__lte: schema.nullable(schema.string()),
+ osArch: schema.nullable(schema.string()),
+ registeredAt__gt: schema.nullable(schema.string()),
+ registeredAt__lt: schema.nullable(schema.string()),
+ siteIds: schema.nullable(schema.string()),
+ networkInterfaceInet__contains: schema.nullable(schema.string()),
+ groupIds: schema.nullable(schema.string()),
+ uuids: schema.nullable(schema.string()),
+ accountIds: schema.nullable(schema.string()),
+ scanStatusesNin: schema.nullable(schema.string()),
+ cpuCount__lte: schema.nullable(schema.string()),
+ locationIds: schema.nullable(schema.string()),
+ awsSecurityGroups__contains: schema.nullable(schema.string()),
+ networkStatusesNin: schema.nullable(schema.string()),
+ activeThreats__gt: schema.nullable(schema.string()),
+ infected: schema.nullable(schema.string()),
+ osVersion__contains: schema.nullable(schema.string()),
+ machineTypes: schema.nullable(schema.string()),
+ agentPodName__contains: schema.nullable(schema.string()),
+ computerName__like: schema.nullable(schema.string()),
+ threatCreatedAt__gt: schema.nullable(schema.string()),
+ consoleMigrationStatusesNin: schema.nullable(schema.string()),
+ computerName: schema.nullable(schema.string()),
+ decommissionedAt__between: schema.nullable(schema.string()),
+ cloudInstanceId__contains: schema.nullable(schema.string()),
+ createdAt__lte: schema.nullable(schema.string()),
+ coreCount__between: schema.nullable(schema.string()),
+ totalMemory__lte: schema.nullable(schema.string()),
+ remoteProfilingStatesNin: schema.nullable(schema.string()),
+ adComputerMember__contains: schema.nullable(schema.string()),
+ threatCreatedAt__between: schema.nullable(schema.string()),
+ totalMemory__gt: schema.nullable(schema.string()),
+ ids: schema.nullable(schema.string()),
+ agentVersionsNin: schema.nullable(schema.string()),
+ updatedAt__between: schema.nullable(schema.string()),
+ locationEnabled: schema.nullable(schema.string()),
+ locationIdsNin: schema.nullable(schema.string()),
+ osTypes: schema.nullable(schema.string()),
+ encryptedApplications: schema.nullable(schema.string()),
+ filterId: schema.nullable(schema.string()),
+ decommissionedAt__gt: schema.nullable(schema.string()),
+ adUserMember__contains: schema.nullable(schema.string()),
+ uuid: schema.nullable(schema.string()),
+ coreCount__lte: schema.nullable(schema.string()),
+ coreCount__gt: schema.nullable(schema.string()),
+ cloudNetwork__contains: schema.nullable(schema.string()),
+ clusterName__contains: schema.nullable(schema.string()),
+ cpuCount__gte: schema.nullable(schema.string()),
+ query: schema.nullable(schema.string()),
+ lastActiveDate__between: schema.nullable(schema.string()),
+ rangerStatus: schema.nullable(schema.string()),
+ domains: schema.nullable(schema.string()),
+ cloudProvider: schema.nullable(schema.string()),
+ lastActiveDate__lt: schema.nullable(schema.string()),
+ scanStatuses: schema.nullable(schema.string()),
+ hasLocalConfiguration: schema.nullable(schema.string()),
+ networkStatuses: schema.nullable(schema.string()),
+ isPendingUninstall: schema.nullable(schema.string()),
+ createdAt__gt: schema.nullable(schema.string()),
+ cpuCount__lt: schema.nullable(schema.string()),
+ consoleMigrationStatuses: schema.nullable(schema.string()),
+ adQuery: schema.nullable(schema.string()),
+ updatedAt__lt: schema.nullable(schema.string()),
+ createdAt__lt: schema.nullable(schema.string()),
+ adComputerName__contains: schema.nullable(schema.string()),
+ cloudInstanceSize__contains: schema.nullable(schema.string()),
+ registeredAt__lte: schema.nullable(schema.string()),
+ networkQuarantineEnabled: schema.nullable(schema.string()),
+ cloudAccount__contains: schema.nullable(schema.string()),
+ cloudLocation__contains: schema.nullable(schema.string()),
+ rangerVersions: schema.nullable(schema.string()),
+ networkInterfaceGatewayMacAddress__contains: schema.nullable(schema.string()),
+ uuid__contains: schema.nullable(schema.string()),
+ agentNamespace__contains: schema.nullable(schema.string()),
+ K8SNodeLabels__contains: schema.nullable(schema.string()),
+ adQuery__contains: schema.nullable(schema.string()),
+ K8SType__contains: schema.nullable(schema.string()),
+ countsFor: schema.nullable(schema.string()),
+ totalMemory__lt: schema.nullable(schema.string()),
+ externalId__contains: schema.nullable(schema.string()),
+ filteredSiteIds: schema.nullable(schema.string()),
+ decommissionedAt__gte: schema.nullable(schema.string()),
+ cpuCount__gt: schema.nullable(schema.string()),
+ threatHidden: schema.nullable(schema.string()),
+ isUninstalled: schema.nullable(schema.string()),
+ computerName__contains: schema.nullable(schema.string()),
+ lastActiveDate__lte: schema.nullable(schema.string()),
+ adUserName__contains: schema.nullable(schema.string()),
+ isActive: schema.nullable(schema.string()),
+ userActionsNeeded: schema.nullable(schema.string()),
+ threatCreatedAt__lte: schema.nullable(schema.string()),
+ domainsNin: schema.nullable(schema.string()),
+ operationalStates: schema.nullable(schema.string()),
+ externalIp__contains: schema.nullable(schema.string()),
+ isDecommissioned: schema.nullable(schema.string()),
+ networkInterfacePhysical__contains: schema.nullable(schema.string()),
+ lastActiveDate__gte: schema.nullable(schema.string()),
+ createdAt__between: schema.nullable(schema.string()),
+ cpuCount__between: schema.nullable(schema.string()),
+ lastLoggedInUserName__contains: schema.nullable(schema.string()),
+ awsRole__contains: schema.nullable(schema.string()),
+ K8SVersion__contains: schema.nullable(schema.string()),
+});
+
+export const SentinelOneKillProcessParamsSchema = SentinelOneBaseFilterSchema.extends({
+ processName: schema.string(),
+});
+
+export const SentinelOneIsolateAgentParamsSchema = SentinelOneBaseFilterSchema;
+
+export const SentinelOneGetAgentsParamsSchema = SentinelOneBaseFilterSchema;
+
+export const SentinelOneGetRemoteScriptsStatusParams = schema.object({
+ parentTaskId: schema.string(),
+});
+
+export const SentinelOneExecuteScriptResponseSchema = schema.object({
+ errors: schema.nullable(schema.arrayOf(schema.object({}, { unknowns: 'allow' }))),
+ data: schema.nullable(
+ schema.object({
+ pendingExecutionId: schema.nullable(schema.string()),
+ affected: schema.nullable(schema.number()),
+ parentTaskId: schema.nullable(schema.string()),
+ pending: schema.nullable(schema.boolean()),
+ })
+ ),
+});
+
+export const SentinelOneKillProcessResponseSchema = SentinelOneExecuteScriptResponseSchema;
+
+export const SentinelOneKillProcessSchema = schema.object({
+ subAction: schema.literal(SUB_ACTION.KILL_PROCESS),
+ subActionParams: SentinelOneKillProcessParamsSchema,
+});
+
+export const SentinelOneIsolateAgentSchema = schema.object({
+ subAction: schema.literal(SUB_ACTION.ISOLATE_AGENT),
+ subActionParams: SentinelOneIsolateAgentParamsSchema,
+});
+
+export const SentinelOneReleaseAgentSchema = schema.object({
+ subAction: schema.literal(SUB_ACTION.RELEASE_AGENT),
+ subActionParams: SentinelOneIsolateAgentParamsSchema,
+});
+
+export const SentinelOneExecuteScriptSchema = schema.object({
+ subAction: schema.literal(SUB_ACTION.EXECUTE_SCRIPT),
+ subActionParams: SentinelOneExecuteScriptParamsSchema,
+});
+
+export const SentinelOneActionParamsSchema = schema.oneOf([
+ SentinelOneKillProcessSchema,
+ SentinelOneIsolateAgentSchema,
+ SentinelOneReleaseAgentSchema,
+ SentinelOneExecuteScriptSchema,
+]);
diff --git a/x-pack/plugins/stack_connectors/common/sentinelone/types.ts b/x-pack/plugins/stack_connectors/common/sentinelone/types.ts
new file mode 100644
index 0000000000000..ab50e316d03f7
--- /dev/null
+++ b/x-pack/plugins/stack_connectors/common/sentinelone/types.ts
@@ -0,0 +1,50 @@
+/*
+ * 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 { TypeOf } from '@kbn/config-schema';
+import {
+ SentinelOneBaseApiResponseSchema,
+ SentinelOneConfigSchema,
+ SentinelOneExecuteScriptParamsSchema,
+ SentinelOneGetAgentsParamsSchema,
+ SentinelOneGetAgentsResponseSchema,
+ SentinelOneGetRemoteScriptsParamsSchema,
+ SentinelOneGetRemoteScriptsResponseSchema,
+ SentinelOneGetRemoteScriptsStatusParams,
+ SentinelOneIsolateAgentParamsSchema,
+ SentinelOneKillProcessParamsSchema,
+ SentinelOneSecretsSchema,
+ SentinelOneActionParamsSchema,
+} from './schema';
+
+export type SentinelOneConfig = TypeOf;
+export type SentinelOneSecrets = TypeOf;
+
+export type SentinelOneBaseApiResponse = TypeOf;
+
+export type SentinelOneGetAgentsParams = TypeOf;
+export type SentinelOneGetAgentsResponse = TypeOf;
+
+export type SentinelOneKillProcessParams = TypeOf;
+
+export type SentinelOneExecuteScriptParams = TypeOf;
+
+export type SentinelOneGetRemoteScriptStatusParams = TypeOf<
+ typeof SentinelOneGetRemoteScriptsStatusParams
+>;
+
+export type SentinelOneGetRemoteScriptsParams = TypeOf<
+ typeof SentinelOneGetRemoteScriptsParamsSchema
+>;
+
+export type SentinelOneGetRemoteScriptsResponse = TypeOf<
+ typeof SentinelOneGetRemoteScriptsResponseSchema
+>;
+
+export type SentinelOneIsolateAgentParams = TypeOf;
+
+export type SentinelOneActionParams = TypeOf;
diff --git a/x-pack/plugins/stack_connectors/public/connector_types/sentinelone/index.ts b/x-pack/plugins/stack_connectors/public/connector_types/sentinelone/index.ts
new file mode 100644
index 0000000000000..7bb5159f87525
--- /dev/null
+++ b/x-pack/plugins/stack_connectors/public/connector_types/sentinelone/index.ts
@@ -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 getSentinelOneConnectorType } from './sentinelone';
diff --git a/x-pack/plugins/stack_connectors/public/connector_types/sentinelone/logo.tsx b/x-pack/plugins/stack_connectors/public/connector_types/sentinelone/logo.tsx
new file mode 100644
index 0000000000000..656e75d07d67c
--- /dev/null
+++ b/x-pack/plugins/stack_connectors/public/connector_types/sentinelone/logo.tsx
@@ -0,0 +1,84 @@
+/*
+ * 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';
+
+const Logo = () => (
+
+);
+
+// eslint-disable-next-line import/no-default-export
+export { Logo as default };
diff --git a/x-pack/plugins/stack_connectors/public/connector_types/sentinelone/sentinelone.ts b/x-pack/plugins/stack_connectors/public/connector_types/sentinelone/sentinelone.ts
new file mode 100644
index 0000000000000..469613621bf05
--- /dev/null
+++ b/x-pack/plugins/stack_connectors/public/connector_types/sentinelone/sentinelone.ts
@@ -0,0 +1,64 @@
+/*
+ * 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 { i18n } from '@kbn/i18n';
+import type {
+ ActionTypeModel as ConnectorTypeModel,
+ GenericValidationResult,
+} from '@kbn/triggers-actions-ui-plugin/public';
+import {
+ SENTINELONE_CONNECTOR_ID,
+ SENTINELONE_TITLE,
+ SUB_ACTION,
+} from '../../../common/sentinelone/constants';
+import type {
+ SentinelOneConfig,
+ SentinelOneSecrets,
+ SentinelOneActionParams,
+} from '../../../common/sentinelone/types';
+
+interface ValidationErrors {
+ subAction: string[];
+}
+
+export function getConnectorType(): ConnectorTypeModel<
+ SentinelOneConfig,
+ SentinelOneSecrets,
+ SentinelOneActionParams
+> {
+ return {
+ id: SENTINELONE_CONNECTOR_ID,
+ actionTypeTitle: SENTINELONE_TITLE,
+ iconClass: lazy(() => import('./logo')),
+ selectMessage: i18n.translate(
+ 'xpack.stackConnectors.security.sentinelone.config.selectMessageText',
+ {
+ defaultMessage: 'Execute SentinelOne scripts',
+ }
+ ),
+ validateParams: async (
+ actionParams: SentinelOneActionParams
+ ): Promise> => {
+ const translations = await import('./translations');
+ const errors: ValidationErrors = {
+ subAction: [],
+ };
+ const { subAction } = actionParams;
+
+ // The internal "subAction" param should always be valid, ensure it is only if "subActionParams" are valid
+ if (!subAction) {
+ errors.subAction.push(translations.ACTION_REQUIRED);
+ } else if (!(subAction in SUB_ACTION)) {
+ errors.subAction.push(translations.INVALID_ACTION);
+ }
+ return { errors };
+ },
+ actionConnectorFields: lazy(() => import('./sentinelone_connector')),
+ actionParamsFields: lazy(() => import('./sentinelone_params')),
+ };
+}
diff --git a/x-pack/plugins/stack_connectors/public/connector_types/sentinelone/sentinelone_connector.tsx b/x-pack/plugins/stack_connectors/public/connector_types/sentinelone/sentinelone_connector.tsx
new file mode 100644
index 0000000000000..785dc5d05832d
--- /dev/null
+++ b/x-pack/plugins/stack_connectors/public/connector_types/sentinelone/sentinelone_connector.tsx
@@ -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 React from 'react';
+
+import {
+ ActionConnectorFieldsProps,
+ ConfigFieldSchema,
+ SecretsFieldSchema,
+ SimpleConnectorForm,
+} from '@kbn/triggers-actions-ui-plugin/public';
+import * as i18n from './translations';
+
+const configFormSchema: ConfigFieldSchema[] = [
+ {
+ id: 'url',
+ label: i18n.URL_LABEL,
+ isUrlField: true,
+ },
+];
+
+const secretsFormSchema: SecretsFieldSchema[] = [
+ {
+ id: 'token',
+ label: i18n.TOKEN_LABEL,
+ isPasswordField: true,
+ },
+];
+
+const SentinelOneActionConnectorFields: React.FunctionComponent = ({
+ readOnly,
+ isEdit,
+}) => (
+
+);
+
+// eslint-disable-next-line import/no-default-export
+export { SentinelOneActionConnectorFields as default };
diff --git a/x-pack/plugins/stack_connectors/public/connector_types/sentinelone/sentinelone_params.tsx b/x-pack/plugins/stack_connectors/public/connector_types/sentinelone/sentinelone_params.tsx
new file mode 100644
index 0000000000000..f74e1c897b97b
--- /dev/null
+++ b/x-pack/plugins/stack_connectors/public/connector_types/sentinelone/sentinelone_params.tsx
@@ -0,0 +1,333 @@
+/*
+ * 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, { useCallback, useEffect, useMemo, useState, ReactNode } from 'react';
+import { reduce } from 'lodash';
+import {
+ EuiButtonIcon,
+ EuiComboBox,
+ EuiFieldText,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiFormRow,
+ EuiInMemoryTable,
+ EuiSuperSelect,
+} from '@elastic/eui';
+import {
+ ActionConnectorMode,
+ ActionParamsProps,
+ TextAreaWithMessageVariables,
+} from '@kbn/triggers-actions-ui-plugin/public';
+import { useSubAction, useKibana } from '@kbn/triggers-actions-ui-plugin/public';
+import { EuiBasicTableColumn, EuiSearchBarProps, EuiLink } from '@elastic/eui';
+import { SUB_ACTION } from '../../../common/sentinelone/constants';
+import type {
+ SentinelOneGetAgentsParams,
+ SentinelOneGetAgentsResponse,
+ SentinelOneGetRemoteScriptsParams,
+ SentinelOneGetRemoteScriptsResponse,
+ SentinelOneActionParams,
+} from '../../../common/sentinelone/types';
+import type { SentinelOneExecuteSubActionParams } from './types';
+import * as i18n from './translations';
+
+type ScriptOption = SentinelOneGetRemoteScriptsResponse['data'][0];
+
+const SentinelOneParamsFields: React.FunctionComponent<
+ ActionParamsProps
+> = ({ actionConnector, actionParams, editAction, index, executionMode, errors, ...rest }) => {
+ const { toasts } = useKibana().notifications;
+ const { subAction, subActionParams } = actionParams;
+ const [selectedScript, setSelectedScript] = useState();
+
+ const [selectedAgent, setSelectedAgent] = useState>(() => {
+ if (subActionParams?.computerName) {
+ return [{ label: subActionParams?.computerName }];
+ }
+ return [];
+ });
+ const [connectorId] = useState(actionConnector?.id);
+
+ const isTest = useMemo(() => executionMode === ActionConnectorMode.Test, [executionMode]);
+
+ const editSubActionParams = useCallback(
+ (params: Partial) => {
+ editAction('subActionParams', { ...subActionParams, ...params }, index);
+ },
+ [editAction, index, subActionParams]
+ );
+
+ const {
+ response: { data: agents } = {},
+ isLoading: isLoadingAgents,
+ error: agentsError,
+ } = useSubAction({
+ connectorId,
+ subAction: SUB_ACTION.GET_AGENTS,
+ disabled: isTest,
+ });
+
+ const agentOptions = useMemo(
+ () =>
+ reduce(
+ agents,
+ (acc, item) => {
+ acc.push({
+ label: item.computerName,
+ });
+ return acc;
+ },
+ [] as Array<{ label: string }>
+ ),
+ [agents]
+ );
+
+ const {
+ response: { data: remoteScripts } = {},
+ isLoading: isLoadingScripts,
+ error: scriptsError,
+ } = useSubAction({
+ connectorId,
+ subAction: SUB_ACTION.GET_REMOTE_SCRIPTS,
+ });
+
+ useEffect(() => {
+ if (agentsError) {
+ toasts.danger({ title: i18n.AGENTS_ERROR, body: agentsError.message });
+ }
+ if (scriptsError) {
+ toasts.danger({ title: i18n.REMOTE_SCRIPTS_ERROR, body: scriptsError.message });
+ }
+ }, [toasts, scriptsError, agentsError]);
+
+ const pagination = {
+ initialPageSize: 10,
+ pageSizeOptions: [10, 20, 50],
+ };
+
+ const search: EuiSearchBarProps = {
+ defaultQuery: 'scriptType:action',
+ box: {
+ incremental: true,
+ },
+ filters: [
+ {
+ type: 'field_value_selection',
+ field: 'scriptType',
+ name: i18n.SCRIPT_TYPE_FILTER_LABEL,
+ multiSelect: true,
+ options: [
+ {
+ value: 'action',
+ },
+ { value: 'dataCollection' },
+ ],
+ },
+ {
+ type: 'field_value_selection',
+ field: 'osTypes',
+ name: i18n.OS_TYPES_FILTER_LABEL,
+ multiSelect: true,
+ options: [
+ {
+ value: 'Windows',
+ },
+ {
+ value: 'macos',
+ },
+ {
+ value: 'linux',
+ },
+ ],
+ },
+ ],
+ };
+
+ const [itemIdToExpandedRowMap, setItemIdToExpandedRowMap] = useState>(
+ {}
+ );
+
+ const toggleDetails = (script: ScriptOption) => {
+ const itemIdToExpandedRowMapValues = { ...itemIdToExpandedRowMap };
+
+ if (script.id) {
+ if (itemIdToExpandedRowMapValues[script.id]) {
+ delete itemIdToExpandedRowMapValues[script.id];
+ } else {
+ itemIdToExpandedRowMapValues[script.id] = <>More details true>;
+ }
+ }
+ setItemIdToExpandedRowMap(itemIdToExpandedRowMapValues);
+ };
+
+ const columns: Array> = [
+ {
+ field: 'scriptName',
+ name: 'Script name',
+ },
+ {
+ field: 'scriptType',
+ name: 'Script type',
+ },
+ {
+ field: 'osTypes',
+ name: 'OS types',
+ },
+ {
+ actions: [
+ {
+ name: 'Choose',
+ description: 'Choose this script',
+ isPrimary: true,
+ onClick: (item) => {
+ setSelectedScript(item);
+ editSubActionParams({
+ script: {
+ scriptId: item.id,
+ scriptRuntimeTimeoutSeconds: 3600,
+ taskDescription: item.scriptName,
+ requiresApproval: item.requiresApproval ?? false,
+ },
+ });
+ },
+ },
+ ],
+ },
+ {
+ align: 'right',
+ width: '40px',
+ isExpander: true,
+ render: (script: ScriptOption) => {
+ const itemIdToExpandedRowMapValues = { ...itemIdToExpandedRowMap };
+
+ return (
+ toggleDetails(script)}
+ aria-label={itemIdToExpandedRowMapValues[script.id] ? 'Collapse' : 'Expand'}
+ iconType={itemIdToExpandedRowMapValues[script.id] ? 'arrowDown' : 'arrowRight'}
+ />
+ );
+ },
+ },
+ ];
+
+ const actionTypeOptions = [
+ {
+ value: SUB_ACTION.KILL_PROCESS,
+ inputDisplay: i18n.KILL_PROCESS_ACTION_LABEL,
+ },
+ {
+ value: SUB_ACTION.ISOLATE_AGENT,
+ inputDisplay: i18n.ISOLATE_AGENT_ACTION_LABEL,
+ },
+ {
+ value: SUB_ACTION.RELEASE_AGENT,
+ inputDisplay: i18n.RELEASE_AGENT_ACTION_LABEL,
+ },
+ ];
+
+ const handleEditSubAction = useCallback(
+ (payload) => {
+ if (subAction !== payload) {
+ editSubActionParams({});
+ editAction('subAction', payload, index);
+ }
+ },
+ [editAction, editSubActionParams, index, subAction]
+ );
+
+ return (
+
+ {isTest && (
+
+
+ {
+ setSelectedAgent(item);
+ editSubActionParams({ computerName: item[0].label });
+ }}
+ isDisabled={isLoadingAgents}
+ />
+
+
+ )}
+
+
+
+
+
+ {subAction === SUB_ACTION.EXECUTE_SCRIPT && (
+ <>
+
+ setSelectedScript(undefined)}>
+ {i18n.CHANGE_ACTION_LABEL}
+
+ ) : null
+ }
+ >
+ {selectedScript?.scriptName ? (
+
+ ) : (
+
+ items={remoteScripts ?? []}
+ itemId="scriptId"
+ loading={isLoadingScripts}
+ columns={columns}
+ search={search}
+ pagination={pagination}
+ sorting
+ hasActions
+ itemIdToExpandedRowMap={itemIdToExpandedRowMap}
+ />
+ )}
+
+
+
+ <>
+ {selectedScript && (
+
+
+
+ )}
+ >
+ >
+ )}
+
+ );
+};
+
+// eslint-disable-next-line import/no-default-export
+export { SentinelOneParamsFields as default };
diff --git a/x-pack/plugins/stack_connectors/public/connector_types/sentinelone/translations.ts b/x-pack/plugins/stack_connectors/public/connector_types/sentinelone/translations.ts
new file mode 100644
index 0000000000000..a5b9a274857c3
--- /dev/null
+++ b/x-pack/plugins/stack_connectors/public/connector_types/sentinelone/translations.ts
@@ -0,0 +1,270 @@
+/*
+ * 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 { API_MAX_RESULTS } from '../../../common/sentinelone/constants';
+
+// config form
+export const URL_LABEL = i18n.translate(
+ 'xpack.stackConnectors.security.sentinelone.config.urlTextFieldLabel',
+ {
+ defaultMessage: 'SentinelOne tenant URL',
+ }
+);
+
+export const TOKEN_LABEL = i18n.translate(
+ 'xpack.stackConnectors.security.sentinelone.config.tokenTextFieldLabel',
+ {
+ defaultMessage: 'API token',
+ }
+);
+
+// params form
+export const ASC = i18n.translate(
+ 'xpack.stackConnectors.security.sentinelone.params.storyFieldLabel',
+ {
+ defaultMessage: 'SentinelOne Script',
+ }
+);
+
+export const SCRIPT_TYPE_FILTER_LABEL = i18n.translate(
+ 'xpack.stackConnectors.security.sentinelone.params.scriptTypeFilterLabel',
+ {
+ defaultMessage: 'Script type',
+ }
+);
+
+export const OS_TYPES_FILTER_LABEL = i18n.translate(
+ 'xpack.stackConnectors.security.sentinelone.params.osTypesFilterLabel',
+ {
+ defaultMessage: 'OS',
+ }
+);
+
+export const STORY_ARIA_LABEL = i18n.translate(
+ 'xpack.stackConnectors.security.sentinelone.params.storyFieldAriaLabel',
+ {
+ defaultMessage: 'Select a SentinelOne script',
+ }
+);
+
+export const KILL_PROCESS_ACTION_LABEL = i18n.translate(
+ 'xpack.stackConnectors.security.sentinelone.params.killProcessActionLabel',
+ {
+ defaultMessage: 'Kill process',
+ }
+);
+
+export const ISOLATE_AGENT_ACTION_LABEL = i18n.translate(
+ 'xpack.stackConnectors.security.sentinelone.params.isolateAgentActionLabel',
+ {
+ defaultMessage: 'Isolate agent',
+ }
+);
+
+export const RELEASE_AGENT_ACTION_LABEL = i18n.translate(
+ 'xpack.stackConnectors.security.sentinelone.params.releaseAgentActionLabel',
+ {
+ defaultMessage: 'Release agent',
+ }
+);
+
+export const AGENTS_FIELD_LABEL = i18n.translate(
+ 'xpack.stackConnectors.security.sentinelone.params.agentsFieldLabel',
+ {
+ defaultMessage: 'SentinelOne agent',
+ }
+);
+
+export const AGENTS_FIELD_PLACEHOLDER = i18n.translate(
+ 'xpack.stackConnectors.security.sentinelone.params.agentsFieldPlaceholder',
+ {
+ defaultMessage: 'Select a single agent',
+ }
+);
+
+export const ACTION_TYPE_LABEL = i18n.translate(
+ 'xpack.stackConnectors.security.sentinelone.params.actionTypeFieldLabel',
+ {
+ defaultMessage: 'Action Type',
+ }
+);
+
+export const COMMAND_LABEL = i18n.translate(
+ 'xpack.stackConnectors.security.sentinelone.params.commandFieldLabel',
+ {
+ defaultMessage: 'Command',
+ }
+);
+
+export const CHANGE_ACTION_LABEL = i18n.translate(
+ 'xpack.stackConnectors.security.sentinelone.params.changeActionButton',
+ {
+ defaultMessage: 'Change action',
+ }
+);
+
+export const WEBHOOK_LABEL = i18n.translate(
+ 'xpack.stackConnectors.security.sentinelone.params.webhookFieldLabel',
+ {
+ defaultMessage: 'SentinelOne Webhook action',
+ }
+);
+export const WEBHOOK_HELP = i18n.translate(
+ 'xpack.stackConnectors.security.sentinelone.params.webhookHelp',
+ {
+ defaultMessage: 'The data entry action in the story',
+ }
+);
+export const WEBHOOK_PLACEHOLDER = i18n.translate(
+ 'xpack.stackConnectors.security.sentinelone.params.webhookPlaceholder',
+ {
+ defaultMessage: 'Select a webhook action',
+ }
+);
+export const WEBHOOK_DISABLED_PLACEHOLDER = i18n.translate(
+ 'xpack.stackConnectors.security.sentinelone.params.webhookDisabledPlaceholder',
+ {
+ defaultMessage: 'Select a story first',
+ }
+);
+export const WEBHOOK_ARIA_LABEL = i18n.translate(
+ 'xpack.stackConnectors.security.sentinelone.params.webhookFieldAriaLabel',
+ {
+ defaultMessage: 'Select a SentinelOne webhook action',
+ }
+);
+
+export const WEBHOOK_URL_LABEL = i18n.translate(
+ 'xpack.stackConnectors.security.sentinelone.params.webhookUrlFieldLabel',
+ {
+ defaultMessage: 'Webhook URL',
+ }
+);
+export const WEBHOOK_URL_FALLBACK_TITLE = i18n.translate(
+ 'xpack.stackConnectors.security.sentinelone.params.webhookUrlFallbackTitle',
+ {
+ defaultMessage: 'SentinelOne API results limit reached',
+ }
+);
+export const WEBHOOK_URL_FALLBACK_TEXT = (entity: 'Story' | 'Webhook') =>
+ i18n.translate('xpack.stackConnectors.security.sentinelone.params.webhookUrlFallbackText', {
+ values: { entity, limit: API_MAX_RESULTS },
+ defaultMessage: `Not possible to retrieve more than {limit} results from the SentinelOne {entity} API. If your {entity} does not appear in the list, please fill the Webhook URL below`,
+ });
+export const WEBHOOK_URL_HELP = i18n.translate(
+ 'xpack.stackConnectors.security.sentinelone.params.webhookUrlHelp',
+ {
+ defaultMessage: 'The Story and Webhook selectors will be ignored if the Webhook URL is defined',
+ }
+);
+export const WEBHOOK_URL_PLACEHOLDER = i18n.translate(
+ 'xpack.stackConnectors.security.sentinelone.params.webhookUrlPlaceholder',
+ {
+ defaultMessage: 'Paste the Webhook URL here',
+ }
+);
+export const DISABLED_BY_WEBHOOK_URL_PLACEHOLDER = i18n.translate(
+ 'xpack.stackConnectors.security.sentinelone.params.disabledByWebhookUrlPlaceholder',
+ {
+ defaultMessage: 'Remove the Webhook URL to use this selector',
+ }
+);
+
+export const BODY_LABEL = i18n.translate(
+ 'xpack.stackConnectors.security.sentinelone.params.bodyFieldLabel',
+ {
+ defaultMessage: 'Body',
+ }
+);
+export const AGENTS_ERROR = i18n.translate(
+ 'xpack.stackConnectors.security.sentinelone.params.componentError.storiesRequestFailed',
+ {
+ defaultMessage: 'Error retrieving agent from SentinelOne',
+ }
+);
+
+export const REMOTE_SCRIPTS_ERROR = i18n.translate(
+ 'xpack.stackConnectors.security.sentinelone.params.componentError.remoteScriptsRequestFailed',
+ {
+ defaultMessage: 'Error retrieving remote scripts from SentinelOne',
+ }
+);
+
+export const WEBHOOKS_ERROR = i18n.translate(
+ 'xpack.stackConnectors.security.sentinelone.params.componentError.webhooksRequestFailed',
+ {
+ defaultMessage: 'Error retrieving webhook actions from SentinelOne',
+ }
+);
+
+export const STORY_NOT_FOUND_WARNING = i18n.translate(
+ 'xpack.stackConnectors.security.sentinelone.params.componentWarning.storyNotFound',
+ {
+ defaultMessage: 'Cannot find the saved story. Please select a valid story from the selector',
+ }
+);
+export const WEBHOOK_NOT_FOUND_WARNING = i18n.translate(
+ 'xpack.stackConnectors.security.sentinelone.params.componentWarning.webhookNotFound',
+ {
+ defaultMessage:
+ 'Cannot find the saved webhook. Please select a valid webhook from the selector',
+ }
+);
+
+export const ACTION_REQUIRED = i18n.translate(
+ 'xpack.stackConnectors.security.sentinelone.params.error.requiredActionText',
+ {
+ defaultMessage: 'Action is required.',
+ }
+);
+
+export const INVALID_ACTION = i18n.translate(
+ 'xpack.stackConnectors.security.sentinelone.params.error.invalidActionText',
+ {
+ defaultMessage: 'Invalid action name.',
+ }
+);
+
+export const BODY_REQUIRED = i18n.translate(
+ 'xpack.stackConnectors.security.sentinelone.params.error.requiredBodyText',
+ {
+ defaultMessage: 'Body is required.',
+ }
+);
+
+export const BODY_INVALID = i18n.translate(
+ 'xpack.stackConnectors.security.sentinelone.params.error.invalidBodyText',
+ {
+ defaultMessage: 'Body does not have a valid JSON format.',
+ }
+);
+
+export const STORY_REQUIRED = i18n.translate(
+ 'xpack.stackConnectors.security.sentinelone.params.error.requiredStoryText',
+ {
+ defaultMessage: 'Story is required.',
+ }
+);
+export const WEBHOOK_REQUIRED = i18n.translate(
+ 'xpack.stackConnectors.security.sentinelone.params.error.requiredWebhookText',
+ {
+ defaultMessage: 'Webhook is required.',
+ }
+);
+export const WEBHOOK_PATH_REQUIRED = i18n.translate(
+ 'xpack.stackConnectors.security.sentinelone.params.error.requiredWebhookPathText',
+ {
+ defaultMessage: 'Webhook action path is missing.',
+ }
+);
+export const WEBHOOK_SECRET_REQUIRED = i18n.translate(
+ 'xpack.stackConnectors.security.sentinelone.params.error.requiredWebhookSecretText',
+ {
+ defaultMessage: 'Webhook action secret is missing.',
+ }
+);
diff --git a/x-pack/plugins/stack_connectors/public/connector_types/sentinelone/types.ts b/x-pack/plugins/stack_connectors/public/connector_types/sentinelone/types.ts
new file mode 100644
index 0000000000000..ac97bdaf224f4
--- /dev/null
+++ b/x-pack/plugins/stack_connectors/public/connector_types/sentinelone/types.ts
@@ -0,0 +1,23 @@
+/*
+ * 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 {
+ SentinelOneKillProcessParams,
+ SentinelOneExecuteScriptParams,
+ SentinelOneIsolateAgentParams,
+} from '../../../common/sentinelone/types';
+import type { SUB_ACTION } from '../../../common/sentinelone/constants';
+
+export type SentinelOneExecuteSubActionParams =
+ | SentinelOneKillProcessParams
+ | SentinelOneExecuteScriptParams
+ | SentinelOneIsolateAgentParams;
+
+export interface SentinelOneExecuteActionParams {
+ subAction: SUB_ACTION;
+ subActionParams: SentinelOneExecuteSubActionParams;
+}
diff --git a/x-pack/plugins/stack_connectors/server/connector_types/sentinelone/index.ts b/x-pack/plugins/stack_connectors/server/connector_types/sentinelone/index.ts
new file mode 100644
index 0000000000000..1ce534079e829
--- /dev/null
+++ b/x-pack/plugins/stack_connectors/server/connector_types/sentinelone/index.ts
@@ -0,0 +1,38 @@
+/*
+ * 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 {
+ SubActionConnectorType,
+ 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 { SENTINELONE_CONNECTOR_ID, SENTINELONE_TITLE } from '../../../common/sentinelone/constants';
+import {
+ SentinelOneConfigSchema,
+ SentinelOneSecretsSchema,
+} from '../../../common/sentinelone/schema';
+import { SentinelOneConfig, SentinelOneSecrets } from '../../../common/sentinelone/types';
+import { SentinelOneConnector } from './sentinelone';
+import { renderParameterTemplates } from './render';
+
+export const getSentinelOneConnectorType = (): SubActionConnectorType<
+ SentinelOneConfig,
+ SentinelOneSecrets
+> => ({
+ id: SENTINELONE_CONNECTOR_ID,
+ name: SENTINELONE_TITLE,
+ Service: SentinelOneConnector,
+ schema: {
+ config: SentinelOneConfigSchema,
+ secrets: SentinelOneSecretsSchema,
+ },
+ validators: [{ type: ValidatorType.CONFIG, validator: urlAllowListValidator('url') }],
+ supportedFeatureIds: [SecurityConnectorFeatureId],
+ minimumLicenseRequired: 'enterprise' as const,
+ renderParameterTemplates,
+});
diff --git a/x-pack/plugins/stack_connectors/server/connector_types/sentinelone/render.ts b/x-pack/plugins/stack_connectors/server/connector_types/sentinelone/render.ts
new file mode 100644
index 0000000000000..9d852510ea7b7
--- /dev/null
+++ b/x-pack/plugins/stack_connectors/server/connector_types/sentinelone/render.ts
@@ -0,0 +1,79 @@
+/*
+ * 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 { set } from '@kbn/safer-lodash-set/fp';
+import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs';
+import { ExecutorParams } from '@kbn/actions-plugin/server/sub_action_framework/types';
+import { SUB_ACTION } from '../../../common/sentinelone/constants';
+
+interface Context {
+ alerts: Ecs[];
+}
+
+export const renderParameterTemplates = (
+ params: ExecutorParams,
+ variables: Record
+) => {
+ const context = variables?.context as Context;
+ if (params?.subAction === SUB_ACTION.KILL_PROCESS) {
+ return {
+ subAction: SUB_ACTION.KILL_PROCESS,
+ subActionParams: {
+ processName: context.alerts[0].process?.name,
+ computerName: context.alerts[0].host?.name,
+ },
+ };
+ }
+
+ if (params?.subAction === SUB_ACTION.ISOLATE_AGENT) {
+ return {
+ subAction: SUB_ACTION.ISOLATE_AGENT,
+ subActionParams: {
+ computerName: context.alerts[0].host?.name,
+ },
+ };
+ }
+
+ if (params?.subAction === SUB_ACTION.RELEASE_AGENT) {
+ return {
+ subAction: SUB_ACTION.RELEASE_AGENT,
+ subActionParams: {
+ computerName: context.alerts[0].host?.name,
+ },
+ };
+ }
+
+ if (params?.subAction === SUB_ACTION.EXECUTE_SCRIPT) {
+ return {
+ subAction: SUB_ACTION.EXECUTE_SCRIPT,
+ subActionParams: {
+ computerName: context.alerts[0].host?.name,
+ ...params.subActionParams,
+ },
+ };
+ }
+
+ let body: string;
+ try {
+ let bodyObject;
+ const alerts = context.alerts;
+ if (alerts) {
+ // Remove the "kibana" entry from all alerts to reduce weight, the same data can be found in other parts of the alert object.
+ bodyObject = set(
+ 'context.alerts',
+ alerts.map(({ kibana, ...alert }) => alert),
+ variables
+ );
+ } else {
+ bodyObject = variables;
+ }
+ body = JSON.stringify(bodyObject);
+ } catch (err) {
+ body = JSON.stringify({ error: { message: err.message } });
+ }
+ return set('subActionParams.body', body, params);
+};
diff --git a/x-pack/plugins/stack_connectors/server/connector_types/sentinelone/sentinelone.ts b/x-pack/plugins/stack_connectors/server/connector_types/sentinelone/sentinelone.ts
new file mode 100644
index 0000000000000..d27f7dcc5588b
--- /dev/null
+++ b/x-pack/plugins/stack_connectors/server/connector_types/sentinelone/sentinelone.ts
@@ -0,0 +1,276 @@
+/*
+ * 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 { ServiceParams, SubActionConnector } from '@kbn/actions-plugin/server';
+import type { AxiosError } from 'axios';
+import { SubActionRequestParams } from '@kbn/actions-plugin/server/sub_action_framework/types';
+import type {
+ SentinelOneConfig,
+ SentinelOneSecrets,
+ SentinelOneGetAgentsResponse,
+ SentinelOneGetAgentsParams,
+ SentinelOneGetRemoteScriptStatusParams,
+ SentinelOneBaseApiResponse,
+ SentinelOneGetRemoteScriptsParams,
+ SentinelOneGetRemoteScriptsResponse,
+ SentinelOneIsolateAgentParams,
+ SentinelOneKillProcessParams,
+ SentinelOneExecuteScriptParams,
+} from '../../../common/sentinelone/types';
+import {
+ SentinelOneKillProcessResponseSchema,
+ SentinelOneExecuteScriptParamsSchema,
+ SentinelOneGetRemoteScriptsParamsSchema,
+ SentinelOneGetRemoteScriptsResponseSchema,
+ SentinelOneGetAgentsResponseSchema,
+ SentinelOneIsolateAgentResponseSchema,
+ SentinelOneIsolateAgentParamsSchema,
+ SentinelOneGetRemoteScriptStatusParamsSchema,
+ SentinelOneGetRemoteScriptStatusResponseSchema,
+ SentinelOneGetAgentsParamsSchema,
+ SentinelOneExecuteScriptResponseSchema,
+} from '../../../common/sentinelone/schema';
+import { SUB_ACTION } from '../../../common/sentinelone/constants';
+
+export const API_MAX_RESULTS = 1000;
+export const API_PATH = '/web/api/v2.1';
+
+export class SentinelOneConnector extends SubActionConnector<
+ SentinelOneConfig,
+ SentinelOneSecrets
+> {
+ private urls: {
+ agents: string;
+ isolateAgent: string;
+ releaseAgent: string;
+ remoteScripts: string;
+ remoteScriptStatus: string;
+ remoteScriptsExecute: string;
+ };
+
+ constructor(params: ServiceParams) {
+ super(params);
+
+ this.urls = {
+ isolateAgent: `${this.config.url}${API_PATH}/agents/actions/disconnect`,
+ releaseAgent: `${this.config.url}${API_PATH}/agents/actions/connect`,
+ remoteScripts: `${this.config.url}${API_PATH}/remote-scripts`,
+ remoteScriptStatus: `${this.config.url}${API_PATH}/remote-scripts/status`,
+ remoteScriptsExecute: `${this.config.url}${API_PATH}/remote-scripts/execute`,
+ agents: `${this.config.url}${API_PATH}/agents`,
+ };
+
+ this.registerSubActions();
+ }
+
+ private registerSubActions() {
+ this.registerSubAction({
+ name: SUB_ACTION.GET_REMOTE_SCRIPTS,
+ method: 'getRemoteScripts',
+ schema: SentinelOneGetRemoteScriptsParamsSchema,
+ });
+
+ this.registerSubAction({
+ name: SUB_ACTION.GET_REMOTE_SCRIPT_STATUS,
+ method: 'getRemoteScriptStatus',
+ schema: SentinelOneGetRemoteScriptStatusParamsSchema,
+ });
+
+ this.registerSubAction({
+ name: SUB_ACTION.GET_AGENTS,
+ method: 'getAgents',
+ schema: SentinelOneGetAgentsParamsSchema,
+ });
+
+ this.registerSubAction({
+ name: SUB_ACTION.ISOLATE_AGENT,
+ method: 'isolateAgent',
+ schema: SentinelOneIsolateAgentParamsSchema,
+ });
+
+ this.registerSubAction({
+ name: SUB_ACTION.RELEASE_AGENT,
+ method: 'releaseAgent',
+ schema: SentinelOneIsolateAgentParamsSchema,
+ });
+
+ this.registerSubAction({
+ name: SUB_ACTION.KILL_PROCESS,
+ method: 'killProcess',
+ schema: SentinelOneKillProcessResponseSchema,
+ });
+
+ this.registerSubAction({
+ name: SUB_ACTION.EXECUTE_SCRIPT,
+ method: 'executeScript',
+ schema: SentinelOneExecuteScriptParamsSchema,
+ });
+ }
+
+ public async executeScript(payload: SentinelOneExecuteScriptParams) {
+ return this.sentinelOneApiRequest({
+ url: this.urls.remoteScriptsExecute,
+ method: 'post',
+ data: {
+ data: {
+ outputDestination: 'SentinelCloud',
+ ...payload.script,
+ },
+ filter: {
+ computerName: payload.computerName,
+ },
+ },
+ responseSchema: SentinelOneExecuteScriptResponseSchema,
+ });
+ }
+
+ public async killProcess({ processName, ...payload }: SentinelOneKillProcessParams) {
+ const agentData = await this.getAgents(payload);
+
+ const agentId = agentData.data[0]?.id;
+
+ if (!agentId) {
+ throw new Error(`No agent found for filter ${JSON.stringify(payload)}`);
+ }
+
+ const terminateScriptResponse = await this.getRemoteScripts({
+ query: 'terminate',
+ osTypes: [agentData?.data[0]?.osType],
+ });
+
+ if (!processName) {
+ throw new Error('No process name provided');
+ }
+
+ return this.sentinelOneApiRequest({
+ url: this.urls.remoteScriptsExecute,
+ method: 'post',
+ data: {
+ data: {
+ outputDestination: 'SentinelCloud',
+ scriptId: terminateScriptResponse.data[0].id,
+ scriptRuntimeTimeoutSeconds: terminateScriptResponse.data[0].scriptRuntimeTimeoutSeconds,
+ taskDescription: terminateScriptResponse.data[0].scriptName,
+ inputParams: `--terminate --processes ${processName}`,
+ },
+ filter: {
+ ids: agentId,
+ },
+ },
+ responseSchema: SentinelOneKillProcessResponseSchema,
+ });
+ }
+
+ public async isolateAgent(payload: SentinelOneIsolateAgentParams) {
+ const response = await this.getAgents(payload);
+
+ if (response.data.length === 0) {
+ throw new Error('No agents found');
+ }
+
+ if (response.data[0].networkStatus === 'disconnected') {
+ throw new Error('Agent already isolated');
+ }
+
+ const agentId = response.data[0].id;
+
+ return this.sentinelOneApiRequest({
+ url: this.urls.isolateAgent,
+ method: 'post',
+ data: {
+ filter: {
+ ids: agentId,
+ },
+ },
+ responseSchema: SentinelOneIsolateAgentResponseSchema,
+ });
+ }
+
+ public async releaseAgent(payload: SentinelOneIsolateAgentParams) {
+ const response = await this.getAgents(payload);
+
+ if (response.data.length === 0) {
+ throw new Error('No agents found');
+ }
+
+ if (response.data[0].networkStatus !== 'disconnected') {
+ throw new Error('Agent not isolated');
+ }
+
+ const agentId = response.data[0].id;
+
+ return this.sentinelOneApiRequest({
+ url: this.urls.releaseAgent,
+ method: 'post',
+ data: {
+ filter: {
+ ids: agentId,
+ },
+ },
+ responseSchema: SentinelOneIsolateAgentResponseSchema,
+ });
+ }
+
+ public async getAgents(
+ payload: SentinelOneGetAgentsParams
+ ): Promise {
+ return this.sentinelOneApiRequest({
+ url: this.urls.agents,
+ params: {
+ ...payload,
+ },
+ responseSchema: SentinelOneGetAgentsResponseSchema,
+ });
+ }
+
+ public async getRemoteScriptStatus(payload: SentinelOneGetRemoteScriptStatusParams) {
+ return this.sentinelOneApiRequest({
+ url: this.urls.remoteScriptStatus,
+ params: {
+ parent_task_id: payload.parentTaskId,
+ },
+ responseSchema: SentinelOneGetRemoteScriptStatusResponseSchema,
+ });
+ }
+
+ private async sentinelOneApiRequest(
+ req: SubActionRequestParams
+ ): Promise {
+ const response = await this.request({
+ ...req,
+ params: {
+ ...req.params,
+ APIToken: this.secrets.token,
+ },
+ });
+
+ return response.data;
+ }
+
+ protected getResponseErrorMessage(error: AxiosError): string {
+ if (!error.response?.status) {
+ return 'Unknown API Error';
+ }
+ if (error.response.status === 401) {
+ return 'Unauthorized API Error';
+ }
+ return `API Error: ${error.response?.statusText}`;
+ }
+
+ public async getRemoteScripts(
+ payload: SentinelOneGetRemoteScriptsParams
+ ): Promise {
+ return this.sentinelOneApiRequest({
+ url: this.urls.remoteScripts,
+ params: {
+ limit: API_MAX_RESULTS,
+ ...payload,
+ },
+ responseSchema: SentinelOneGetRemoteScriptsResponseSchema,
+ });
+ }
+}
diff --git a/x-pack/plugins/stack_connectors/tsconfig.json b/x-pack/plugins/stack_connectors/tsconfig.json
index 7cc6696f04368..f18dfaea77cca 100644
--- a/x-pack/plugins/stack_connectors/tsconfig.json
+++ b/x-pack/plugins/stack_connectors/tsconfig.json
@@ -33,6 +33,7 @@
"@kbn/core-saved-objects-common",
"@kbn/core-http-browser-mocks",
"@kbn/core-saved-objects-api-server-mocks",
+ "@kbn/securitysolution-ecs",
],
"exclude": [
"target/**/*",
diff --git a/x-pack/plugins/task_manager/server/queries/oldest_idle_action_task.ts b/x-pack/plugins/task_manager/server/queries/oldest_idle_action_task.ts
index 49e7bc1e8d9e3..69947cb08fc8d 100644
--- a/x-pack/plugins/task_manager/server/queries/oldest_idle_action_task.ts
+++ b/x-pack/plugins/task_manager/server/queries/oldest_idle_action_task.ts
@@ -44,6 +44,7 @@ export const getOldestIdleActionTask = async (
'actions:.jira',
'actions:.resilient',
'actions:.teams',
+ 'actions:.sentinelone',
],
},
},
diff --git a/x-pack/plugins/task_manager/server/saved_objects/index.ts b/x-pack/plugins/task_manager/server/saved_objects/index.ts
index 53d09d4baf131..0bb12906708de 100644
--- a/x-pack/plugins/task_manager/server/saved_objects/index.ts
+++ b/x-pack/plugins/task_manager/server/saved_objects/index.ts
@@ -50,6 +50,7 @@ export function setupSavedObjects(
'actions:.jira',
'actions:.resilient',
'actions:.teams',
+ 'actions:.sentinelone',
],
},
},
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/text_area_with_message_variables.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/text_area_with_message_variables.tsx
index 3bb65037d5c93..346dce44af60b 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/components/text_area_with_message_variables.tsx
+++ b/x-pack/plugins/triggers_actions_ui/public/application/components/text_area_with_message_variables.tsx
@@ -20,6 +20,7 @@ interface Props {
isDisabled?: boolean;
editAction: (property: string, value: any, index: number) => void;
label: string;
+ helpText?: string;
errors?: string[];
}
@@ -32,6 +33,7 @@ export const TextAreaWithMessageVariables: React.FunctionComponent = ({
editAction,
label,
errors,
+ helpText,
}) => {
const [currentTextElement, setCurrentTextElement] = useState(null);
@@ -64,6 +66,7 @@ export const TextAreaWithMessageVariables: React.FunctionComponent = ({
paramsProperty={paramsProperty}
/>
}
+ helpText={helpText}
>