Skip to content

Commit

Permalink
[Connectors] Add support to SentinelOne connector for Security Soluti…
Browse files Browse the repository at this point in the history
…on `get-file` response action (elastic#180637)

## Summary

Changes done in SentinelOne connector in support of Security Solution
`get-file` response action (forthcoming):

- Added `fetchAgentFiles()` sub-action
- Added `downloadAgentFile()` sub-action
- Added `getActivities()` sub-action
- Improved error messages for SentinelOne API failures
- Added `logger.debug()` to Sub-Actions connector `validateResponse()`
to output data that failed validation
  • Loading branch information
paul-tavares authored Apr 16, 2024
1 parent f002b8f commit 28d7148
Show file tree
Hide file tree
Showing 14 changed files with 1,276 additions and 5,853 deletions.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { finished } from 'stream/promises';
import { IncomingMessage } from 'http';
import { PassThrough } from 'stream';
import { KibanaRequest } from '@kbn/core-http-server';
import { inspect } from 'util';
import { assertURL } from './helpers/validators';
import { ActionsConfigurationUtilities } from '../actions_config';
import { SubAction, SubActionRequestParams } from './types';
Expand Down Expand Up @@ -94,7 +95,9 @@ export abstract class SubActionConnector<Config, Secrets> {
try {
responseSchema.validate(data);
} catch (resValidationError) {
throw new Error(`Response validation failed (${resValidationError})`);
const err = new Error(`Response validation failed (${resValidationError})`);
this.logger.debug(`${err.message}:\n${inspect(data, { depth: 10 })}`);
throw err;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@ 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_HOST = 'isolateHost',
RELEASE_HOST = 'releaseHost',
GET_REMOTE_SCRIPTS = 'getRemoteScripts',
GET_REMOTE_SCRIPT_STATUS = 'getRemoteScriptStatus',
GET_REMOTE_SCRIPT_RESULTS = 'getRemoteScriptResults',
FETCH_AGENT_FILES = 'fetchAgentFiles',
DOWNLOAD_AGENT_FILE = 'downloadAgentFile',
GET_ACTIVITIES = 'getActivities',
}
161 changes: 132 additions & 29 deletions x-pack/plugins/stack_connectors/common/sentinelone/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,104 @@ export const SentinelOneGetRemoteScriptsParamsSchema = schema.object({
osTypes: schema.nullable(schema.string()),
});

export const SentinelOneFetchAgentFilesParamsSchema = schema.object({
agentUUID: schema.string({ minLength: 1 }),
zipPassCode: schema.string({ minLength: 10 }),
files: schema.arrayOf(schema.string({ minLength: 1 })),
});

export const SentinelOneFetchAgentFilesResponseSchema = schema.object({
errors: schema.nullable(schema.arrayOf(schema.string())),
data: schema.maybe(
schema.object(
{
success: schema.boolean(),
},
{ unknowns: 'allow' }
)
),
});

export const SentinelOneDownloadAgentFileParamsSchema = schema.object({
agentUUID: schema.string({ minLength: 1 }),
activityId: schema.string({ minLength: 1 }),
});

export const SentinelOneDownloadAgentFileResponseSchema = schema.stream();

export const SentinelOneGetActivitiesParamsSchema = schema.maybe(
schema.object({
accountIds: schema.maybe(schema.string({ minLength: 1 })),
activityTypes: schema.maybe(schema.string()),
activityUuids: schema.maybe(schema.string({ minLength: 1 })),
agentIds: schema.maybe(schema.string({ minLength: 1 })),
alertIds: schema.maybe(schema.string({ minLength: 1 })),
countOnly: schema.maybe(schema.boolean()),
createdAt__between: schema.maybe(schema.string({ minLength: 1 })),
createdAt__gt: schema.maybe(schema.string({ minLength: 1 })),
createdAt__gte: schema.maybe(schema.string({ minLength: 1 })),
createdAt__lt: schema.maybe(schema.string({ minLength: 1 })),
createdAt__lte: schema.maybe(schema.string({ minLength: 1 })),
cursor: schema.maybe(schema.string({ minLength: 1 })),
groupIds: schema.maybe(schema.string({ minLength: 1 })),
ids: schema.maybe(schema.string({ minLength: 1 })),
includeHidden: schema.maybe(schema.boolean()),
limit: schema.maybe(schema.number()),
ruleIds: schema.maybe(schema.string({ minLength: 1 })),
siteIds: schema.maybe(schema.string({ minLength: 1 })),
skip: schema.maybe(schema.number()),
skipCount: schema.maybe(schema.boolean()),
sortBy: schema.maybe(schema.string({ minLength: 1 })),
sortOrder: schema.maybe(schema.string({ minLength: 1 })),
threatIds: schema.maybe(schema.string({ minLength: 1 })),
userEmails: schema.maybe(schema.string({ minLength: 1 })),
userIds: schema.maybe(schema.string({ minLength: 1 })),
})
);

export const SentinelOneGetActivitiesResponseSchema = schema.object({
errors: schema.maybe(schema.arrayOf(schema.string())),
pagination: schema.object({
nextCursor: schema.nullable(schema.string()),
totalItems: schema.number(),
}),
data: schema.arrayOf(
schema.object(
{
accountId: schema.string(),
accountName: schema.string(),
activityType: schema.number(),
activityUuid: schema.string(),
agentId: schema.nullable(schema.string()),
agentUpdatedVersion: schema.nullable(schema.string()),
comments: schema.nullable(schema.string()),
createdAt: schema.string(),
data: schema.object(
{
// Empty by design.
// The SentinelOne Activity Log can place any (unknown) data here
},
{ unknowns: 'allow' }
),
description: schema.nullable(schema.string()),
groupId: schema.nullable(schema.string()),
groupName: schema.nullable(schema.string()),
hash: schema.nullable(schema.string()),
id: schema.string(),
osFamily: schema.nullable(schema.string()),
primaryDescription: schema.nullable(schema.string()),
secondaryDescription: schema.nullable(schema.string()),
siteId: schema.string(),
siteName: schema.string(),
threatId: schema.nullable(schema.string()),
updatedAt: schema.string(),
userId: schema.nullable(schema.string()),
},
{ unknowns: 'allow' }
)
),
});

export const AlertIds = schema.maybe(schema.arrayOf(schema.string()));

export const SentinelOneGetRemoteScriptsResponseSchema = schema.object({
Expand Down Expand Up @@ -226,29 +324,54 @@ export const SentinelOneGetRemoteScriptsResponseSchema = schema.object({
});

export const SentinelOneExecuteScriptParamsSchema = schema.object({
computerName: schema.maybe(schema.string()),
// Only a sub-set of filters are defined below. This API, however, support many more filters
// which can be added in the future if needed.
filter: schema.object({
uuids: schema.maybe(schema.string({ minLength: 1 })),
ids: schema.maybe(schema.string({ minLength: 1 })),
}),
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()),
outputDirectory: schema.maybe(schema.string()),
outputDestination: schema.maybe(
schema.oneOf([
schema.literal('Local'),
schema.literal('None'),
schema.literal('SentinelCloud'),
schema.literal('SingularityXDR'),
])
),
passwordFromScope: schema.maybe(
schema.object({
scopeLevel: schema.maybe(schema.string()),
scopeId: schema.maybe(schema.string()),
})
),
password: schema.maybe(schema.string()),
requiresApproval: schema.maybe(schema.boolean()),
scriptId: schema.string(),
scriptName: schema.maybe(schema.string()),
scriptRuntimeTimeoutSeconds: schema.maybe(schema.number()),
singularityxdrKeyword: schema.maybe(schema.string()),
singularityxdrUrl: schema.maybe(schema.string()),
taskDescription: schema.maybe(schema.string()),
}),
alertIds: AlertIds,
});

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 SentinelOneGetRemoteScriptStatusParamsSchema = schema.object(
{
parentTaskId: schema.string(),
Expand Down Expand Up @@ -462,25 +585,6 @@ 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 SentinelOneIsolateHostSchema = schema.object({
subAction: schema.literal(SUB_ACTION.ISOLATE_HOST),
subActionParams: SentinelOneIsolateHostParamsSchema,
Expand All @@ -497,7 +601,6 @@ export const SentinelOneExecuteScriptSchema = schema.object({
});

export const SentinelOneActionParamsSchema = schema.oneOf([
SentinelOneKillProcessSchema,
SentinelOneIsolateHostSchema,
SentinelOneReleaseHostSchema,
SentinelOneExecuteScriptSchema,
Expand Down
37 changes: 34 additions & 3 deletions x-pack/plugins/stack_connectors/common/sentinelone/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,14 @@ import {
SentinelOneGetRemoteScriptsResponseSchema,
SentinelOneGetRemoteScriptsStatusParams,
SentinelOneIsolateHostParamsSchema,
SentinelOneKillProcessParamsSchema,
SentinelOneSecretsSchema,
SentinelOneActionParamsSchema,
SentinelOneFetchAgentFilesParamsSchema,
SentinelOneFetchAgentFilesResponseSchema,
SentinelOneDownloadAgentFileParamsSchema,
SentinelOneGetActivitiesParamsSchema,
SentinelOneGetActivitiesResponseSchema,
SentinelOneExecuteScriptResponseSchema,
} from './schema';

export type SentinelOneConfig = TypeOf<typeof SentinelOneConfigSchema>;
Expand All @@ -29,9 +34,10 @@ export type SentinelOneBaseApiResponse = TypeOf<typeof SentinelOneBaseApiRespons
export type SentinelOneGetAgentsParams = Partial<TypeOf<typeof SentinelOneGetAgentsParamsSchema>>;
export type SentinelOneGetAgentsResponse = TypeOf<typeof SentinelOneGetAgentsResponseSchema>;

export type SentinelOneKillProcessParams = TypeOf<typeof SentinelOneKillProcessParamsSchema>;

export type SentinelOneExecuteScriptParams = TypeOf<typeof SentinelOneExecuteScriptParamsSchema>;
export type SentinelOneExecuteScriptResponse = TypeOf<
typeof SentinelOneExecuteScriptResponseSchema
>;

export type SentinelOneGetRemoteScriptStatusParams = TypeOf<
typeof SentinelOneGetRemoteScriptsStatusParams
Expand All @@ -45,6 +51,31 @@ export type SentinelOneGetRemoteScriptsResponse = TypeOf<
typeof SentinelOneGetRemoteScriptsResponseSchema
>;

export type SentinelOneFetchAgentFilesParams = TypeOf<
typeof SentinelOneFetchAgentFilesParamsSchema
>;
export type SentinelOneFetchAgentFilesResponse = TypeOf<
typeof SentinelOneFetchAgentFilesResponseSchema
>;

export type SentinelOneDownloadAgentFileParams = TypeOf<
typeof SentinelOneDownloadAgentFileParamsSchema
>;

export type SentinelOneActivityRecord<TData = unknown> = Omit<
TypeOf<typeof SentinelOneGetActivitiesResponseSchema>['data'][number],
'data'
> & {
data: TData;
};

export type SentinelOneGetActivitiesParams = TypeOf<typeof SentinelOneGetActivitiesParamsSchema>;

export type SentinelOneGetActivitiesResponse<TData = unknown> = Omit<
TypeOf<typeof SentinelOneGetActivitiesResponseSchema>,
'data'
> & { data: Array<SentinelOneActivityRecord<TData>> };

export type SentinelOneIsolateHostParams = TypeOf<typeof SentinelOneIsolateHostParamsSchema>;

export type SentinelOneActionParams = TypeOf<typeof SentinelOneActionParamsSchema>;
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,5 @@ export function getConnectorType(): ConnectorTypeModel<
},
actionConnectorFields: lazy(() => import('./sentinelone_connector')),
actionParamsFields: lazy(() => import('./sentinelone_params_empty')),
// TODO: Enable once we add support for automated response actions
// actionParamsFields: lazy(() => import('./sentinelone_params')),
};
}
Loading

0 comments on commit 28d7148

Please sign in to comment.