mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
Add SentinelOne connector (#159157)
## Summary Adds new connector type to support https://www.sentinelone.com/ The scope of this PR was limited to the Connector logic, schemas, and types to make PR more digestible. In the current PR, the connector is NOT registered, so it's not going to be available to the users. In the follow-up PR I'm going to improve the UX of Param's form and then enable the connector <img width="1685" alt="Zrzut ekranu 2023-08-3 o 11 18 54" src="965ef8ef
-497f-42a8-983e-38fd0370cba8"> visual changes include a screenshot or gif. <img width="1685" alt="image" src="119d2255
-ed9f-4923-886d-eb139223a47d"> <img width="1690" alt="image" src="e2c569d2
-b497-4641-a6a6-454494223ffc">
This commit is contained in:
parent
6673e9dc59
commit
4637b744d8
23 changed files with 1883 additions and 2 deletions
5
.github/CODEOWNERS
vendored
5
.github/CODEOWNERS
vendored
|
@ -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
|
||||
|
|
|
@ -129,7 +129,7 @@ pageLoadAssetSize:
|
|||
snapshotRestore: 79032
|
||||
spaces: 57868
|
||||
stackAlerts: 58316
|
||||
stackConnectors: 36314
|
||||
stackConnectors: 52131
|
||||
synthetics: 40958
|
||||
telemetry: 51957
|
||||
telemetryManagementSection: 38586
|
||||
|
|
|
@ -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<P> {
|
||||
connectorId: string;
|
||||
subAction: string;
|
||||
subActionParams?: P;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export const useSubAction = <P, R>({
|
||||
connectorId,
|
||||
subAction,
|
||||
subActionParams,
|
||||
disabled = false,
|
||||
...rest
|
||||
}: UseSubActionParams<P>) => {
|
||||
const { http } = useKibana().services;
|
||||
|
||||
return useQuery({
|
||||
queryKey: ['useSubAction', connectorId, subAction, subActionParams],
|
||||
queryFn: ({ signal }) =>
|
||||
executeAction<R>({
|
||||
id: connectorId,
|
||||
params: {
|
||||
subAction,
|
||||
subActionParams,
|
||||
},
|
||||
http,
|
||||
signal,
|
||||
}),
|
||||
enabled: !disabled && !!connectorId && !!subAction,
|
||||
...rest,
|
||||
});
|
||||
};
|
|
@ -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<P> {
|
||||
connectorId: string;
|
||||
subAction: string;
|
||||
subActionParams?: P;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export const useSubActionMutation = <P, R>({
|
||||
connectorId,
|
||||
subAction,
|
||||
subActionParams,
|
||||
disabled = false,
|
||||
}: UseSubActionParams<P>) => {
|
||||
const { http } = useKibana().services;
|
||||
|
||||
return useMutation({
|
||||
mutationFn: () =>
|
||||
executeAction<R>({
|
||||
id: connectorId,
|
||||
params: {
|
||||
subAction,
|
||||
subActionParams,
|
||||
},
|
||||
http,
|
||||
}),
|
||||
});
|
||||
};
|
|
@ -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',
|
||||
}
|
495
x-pack/plugins/stack_connectors/common/sentinelone/schema.ts
Normal file
495
x-pack/plugins/stack_connectors/common/sentinelone/schema.ts
Normal file
|
@ -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,
|
||||
]);
|
50
x-pack/plugins/stack_connectors/common/sentinelone/types.ts
Normal file
50
x-pack/plugins/stack_connectors/common/sentinelone/types.ts
Normal file
|
@ -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<typeof SentinelOneConfigSchema>;
|
||||
export type SentinelOneSecrets = TypeOf<typeof SentinelOneSecretsSchema>;
|
||||
|
||||
export type SentinelOneBaseApiResponse = TypeOf<typeof SentinelOneBaseApiResponseSchema>;
|
||||
|
||||
export type SentinelOneGetAgentsParams = TypeOf<typeof SentinelOneGetAgentsParamsSchema>;
|
||||
export type SentinelOneGetAgentsResponse = TypeOf<typeof SentinelOneGetAgentsResponseSchema>;
|
||||
|
||||
export type SentinelOneKillProcessParams = TypeOf<typeof SentinelOneKillProcessParamsSchema>;
|
||||
|
||||
export type SentinelOneExecuteScriptParams = TypeOf<typeof SentinelOneExecuteScriptParamsSchema>;
|
||||
|
||||
export type SentinelOneGetRemoteScriptStatusParams = TypeOf<
|
||||
typeof SentinelOneGetRemoteScriptsStatusParams
|
||||
>;
|
||||
|
||||
export type SentinelOneGetRemoteScriptsParams = TypeOf<
|
||||
typeof SentinelOneGetRemoteScriptsParamsSchema
|
||||
>;
|
||||
|
||||
export type SentinelOneGetRemoteScriptsResponse = TypeOf<
|
||||
typeof SentinelOneGetRemoteScriptsResponseSchema
|
||||
>;
|
||||
|
||||
export type SentinelOneIsolateAgentParams = TypeOf<typeof SentinelOneIsolateAgentParamsSchema>;
|
||||
|
||||
export type SentinelOneActionParams = TypeOf<typeof SentinelOneActionParamsSchema>;
|
|
@ -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';
|
|
@ -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 = () => (
|
||||
<svg width="32" height="32" xmlns="http://www.w3.org/2000/svg" xmlSpace="preserve" version="1.1">
|
||||
<g id="Layer_1">
|
||||
<g stroke="null" id="svg_1" transform="matrix(0.028688 0 0 0.028688 -136.328 247.841)" />
|
||||
<g stroke="null">
|
||||
<g stroke="null" />
|
||||
</g>
|
||||
<g stroke="null" transform="matrix(0.332422 0 0 0.332422 -135.68 245.363)">
|
||||
<rect
|
||||
stroke="none"
|
||||
fillRule="nonzero"
|
||||
fill="rgb(107,10,234)"
|
||||
strokeMiterlimit="4"
|
||||
strokeLinejoin="miter"
|
||||
strokeDashoffset="0"
|
||||
strokeLinecap="butt"
|
||||
strokeDasharray="none"
|
||||
height="77.25"
|
||||
width="12.92"
|
||||
y="-737.83562"
|
||||
x="448.36749"
|
||||
vectorEffect="non-scaling-stroke"
|
||||
/>
|
||||
</g>
|
||||
<g stroke="null" transform="matrix(0.332422 0 0 0.332422 -135.686 251.011)">
|
||||
<path
|
||||
stroke="none"
|
||||
fillRule="nonzero"
|
||||
fill="rgb(107,10,234)"
|
||||
strokeMiterlimit="4"
|
||||
strokeLinejoin="miter"
|
||||
strokeDashoffset="0"
|
||||
strokeDasharray="none"
|
||||
strokeLinecap="butt"
|
||||
d="m502.95749,-603.33062l12.91,-8l0,-66.88c-3.82729,-2.83807 -8.23656,-4.79167 -12.91,-5.72l0,80.6zm-32.13,-7.96l12.92,8l0,-80.6c-4.67381,0.91429 -9.08653,2.85424 -12.92,5.68l0,66.92z"
|
||||
transform="translate(-38.52 -55.6)"
|
||||
vectorEffect="non-scaling-stroke"
|
||||
/>
|
||||
</g>
|
||||
<g stroke="null" transform="matrix(0.332422 0 0 0.332422 -125.01 247.509)">
|
||||
<path
|
||||
stroke="none"
|
||||
fillRule="nonzero"
|
||||
fill="rgb(107,10,234)"
|
||||
strokeMiterlimit="4"
|
||||
strokeLinejoin="miter"
|
||||
strokeDashoffset="0"
|
||||
strokeDasharray="none"
|
||||
strokeLinecap="butt"
|
||||
d="m518.99749,-695.54062l0,82.81l6,-3.72c4.36622,-2.87218 6.96435,-7.77432 6.89,-13l0,-39.11c0.03,-11.28 -12.89,-26.98 -12.89,-26.98z"
|
||||
transform="translate(-70.62 -45.08)"
|
||||
vectorEffect="non-scaling-stroke"
|
||||
/>
|
||||
</g>
|
||||
<g stroke="null" transform="matrix(0.332422 0 0 0.332422 -146.356 247.503)">
|
||||
<path
|
||||
stroke="none"
|
||||
fillRule="nonzero"
|
||||
fill="rgb(107,10,234)"
|
||||
strokeMiterlimit="4"
|
||||
strokeLinejoin="miter"
|
||||
strokeDashoffset="0"
|
||||
strokeDasharray="none"
|
||||
strokeLinecap="butt"
|
||||
d="m454.82749,-629.48062c-0.08001,5.22704 2.51948,10.13174 6.89,13l6,3.72l0,-82.78c0,0 -12.89,15.7 -12.89,26.98l0,39.08z"
|
||||
transform="translate(-6.44 -45.06)"
|
||||
vectorEffect="non-scaling-stroke"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export { Logo as default };
|
|
@ -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<GenericValidationResult<ValidationErrors>> => {
|
||||
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')),
|
||||
};
|
||||
}
|
|
@ -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<ActionConnectorFieldsProps> = ({
|
||||
readOnly,
|
||||
isEdit,
|
||||
}) => (
|
||||
<SimpleConnectorForm
|
||||
isEdit={isEdit}
|
||||
readOnly={readOnly}
|
||||
configFormSchema={configFormSchema}
|
||||
secretsFormSchema={secretsFormSchema}
|
||||
/>
|
||||
);
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export { SentinelOneActionConnectorFields as default };
|
|
@ -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<SentinelOneActionParams>
|
||||
> = ({ actionConnector, actionParams, editAction, index, executionMode, errors, ...rest }) => {
|
||||
const { toasts } = useKibana().notifications;
|
||||
const { subAction, subActionParams } = actionParams;
|
||||
const [selectedScript, setSelectedScript] = useState<ScriptOption | undefined>();
|
||||
|
||||
const [selectedAgent, setSelectedAgent] = useState<Array<{ label: string }>>(() => {
|
||||
if (subActionParams?.computerName) {
|
||||
return [{ label: subActionParams?.computerName }];
|
||||
}
|
||||
return [];
|
||||
});
|
||||
const [connectorId] = useState<string | undefined>(actionConnector?.id);
|
||||
|
||||
const isTest = useMemo(() => executionMode === ActionConnectorMode.Test, [executionMode]);
|
||||
|
||||
const editSubActionParams = useCallback(
|
||||
(params: Partial<SentinelOneExecuteSubActionParams>) => {
|
||||
editAction('subActionParams', { ...subActionParams, ...params }, index);
|
||||
},
|
||||
[editAction, index, subActionParams]
|
||||
);
|
||||
|
||||
const {
|
||||
response: { data: agents } = {},
|
||||
isLoading: isLoadingAgents,
|
||||
error: agentsError,
|
||||
} = useSubAction<SentinelOneGetAgentsParams, SentinelOneGetAgentsResponse>({
|
||||
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<SentinelOneGetRemoteScriptsParams, SentinelOneGetRemoteScriptsResponse>({
|
||||
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<Record<string, ReactNode>>(
|
||||
{}
|
||||
);
|
||||
|
||||
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<EuiBasicTableColumn<SentinelOneGetRemoteScriptsResponse['data'][0]>> = [
|
||||
{
|
||||
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 (
|
||||
<EuiButtonIcon
|
||||
onClick={() => 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 (
|
||||
<EuiFlexGroup direction="column">
|
||||
{isTest && (
|
||||
<EuiFlexItem>
|
||||
<EuiFormRow fullWidth label={i18n.AGENTS_FIELD_LABEL}>
|
||||
<EuiComboBox
|
||||
fullWidth
|
||||
placeholder={i18n.AGENTS_FIELD_PLACEHOLDER}
|
||||
singleSelection={{ asPlainText: true }}
|
||||
options={agentOptions}
|
||||
selectedOptions={selectedAgent}
|
||||
onChange={(item) => {
|
||||
setSelectedAgent(item);
|
||||
editSubActionParams({ computerName: item[0].label });
|
||||
}}
|
||||
isDisabled={isLoadingAgents}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
<EuiFlexItem>
|
||||
<EuiFormRow fullWidth label={i18n.ACTION_TYPE_LABEL}>
|
||||
<EuiSuperSelect
|
||||
fullWidth
|
||||
options={actionTypeOptions}
|
||||
valueOfSelected={subAction}
|
||||
onChange={handleEditSubAction}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiFlexItem>
|
||||
{subAction === SUB_ACTION.EXECUTE_SCRIPT && (
|
||||
<>
|
||||
<EuiFlexItem>
|
||||
<EuiFormRow
|
||||
fullWidth
|
||||
error={errors.script}
|
||||
isInvalid={!!errors.script?.length}
|
||||
label={'Script'}
|
||||
labelAppend={
|
||||
selectedScript ? (
|
||||
<EuiLink onClick={() => setSelectedScript(undefined)}>
|
||||
{i18n.CHANGE_ACTION_LABEL}
|
||||
</EuiLink>
|
||||
) : null
|
||||
}
|
||||
>
|
||||
{selectedScript?.scriptName ? (
|
||||
<EuiFieldText fullWidth value={selectedScript.scriptName} />
|
||||
) : (
|
||||
<EuiInMemoryTable<ScriptOption>
|
||||
items={remoteScripts ?? []}
|
||||
itemId="scriptId"
|
||||
loading={isLoadingScripts}
|
||||
columns={columns}
|
||||
search={search}
|
||||
pagination={pagination}
|
||||
sorting
|
||||
hasActions
|
||||
itemIdToExpandedRowMap={itemIdToExpandedRowMap}
|
||||
/>
|
||||
)}
|
||||
</EuiFormRow>
|
||||
</EuiFlexItem>
|
||||
|
||||
<>
|
||||
{selectedScript && (
|
||||
<EuiFlexItem>
|
||||
<TextAreaWithMessageVariables
|
||||
index={index}
|
||||
editAction={editAction}
|
||||
messageVariables={[]}
|
||||
paramsProperty={'subActionParams.script.inputParams'}
|
||||
label={i18n.COMMAND_LABEL}
|
||||
inputTargetValue={subActionParams?.script?.inputParams ?? undefined}
|
||||
helpText={
|
||||
selectedScript?.inputExample
|
||||
? `Example: ${selectedScript?.inputExample}`
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</>
|
||||
</>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export { SentinelOneParamsFields as default };
|
|
@ -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.',
|
||||
}
|
||||
);
|
|
@ -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;
|
||||
}
|
|
@ -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,
|
||||
});
|
|
@ -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<string, unknown>
|
||||
) => {
|
||||
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);
|
||||
};
|
|
@ -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<SentinelOneConfig, SentinelOneSecrets>) {
|
||||
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<SentinelOneGetAgentsResponse> {
|
||||
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<R extends SentinelOneBaseApiResponse>(
|
||||
req: SubActionRequestParams<R>
|
||||
): Promise<R> {
|
||||
const response = await this.request<R>({
|
||||
...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<SentinelOneGetRemoteScriptsResponse> {
|
||||
return this.sentinelOneApiRequest({
|
||||
url: this.urls.remoteScripts,
|
||||
params: {
|
||||
limit: API_MAX_RESULTS,
|
||||
...payload,
|
||||
},
|
||||
responseSchema: SentinelOneGetRemoteScriptsResponseSchema,
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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/**/*",
|
||||
|
|
|
@ -44,6 +44,7 @@ export const getOldestIdleActionTask = async (
|
|||
'actions:.jira',
|
||||
'actions:.resilient',
|
||||
'actions:.teams',
|
||||
'actions:.sentinelone',
|
||||
],
|
||||
},
|
||||
},
|
||||
|
|
|
@ -50,6 +50,7 @@ export function setupSavedObjects(
|
|||
'actions:.jira',
|
||||
'actions:.resilient',
|
||||
'actions:.teams',
|
||||
'actions:.sentinelone',
|
||||
],
|
||||
},
|
||||
},
|
||||
|
|
|
@ -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<Props> = ({
|
|||
editAction,
|
||||
label,
|
||||
errors,
|
||||
helpText,
|
||||
}) => {
|
||||
const [currentTextElement, setCurrentTextElement] = useState<HTMLTextAreaElement | null>(null);
|
||||
|
||||
|
@ -64,6 +66,7 @@ export const TextAreaWithMessageVariables: React.FunctionComponent<Props> = ({
|
|||
paramsProperty={paramsProperty}
|
||||
/>
|
||||
}
|
||||
helpText={helpText}
|
||||
>
|
||||
<EuiTextArea
|
||||
disabled={isDisabled}
|
||||
|
|
|
@ -8,5 +8,5 @@
|
|||
export { templateActionVariable } from './template_action_variable';
|
||||
export { hasMustacheTokens } from './has_mustache_tokens';
|
||||
export { AlertProvidedActionVariables } from './action_variables';
|
||||
export { updateActionConnector } from './action_connector_api';
|
||||
export { updateActionConnector, executeAction } from './action_connector_api';
|
||||
export { isRuleSnoozed } from './is_rule_snoozed';
|
||||
|
|
|
@ -89,6 +89,7 @@ export {
|
|||
hasMustacheTokens,
|
||||
templateActionVariable,
|
||||
updateActionConnector,
|
||||
executeAction,
|
||||
} from './application/lib';
|
||||
|
||||
export type { ActionGroupWithCondition } from './application/sections';
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue