Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[EDR Workflows] CrowdStrike RunScript: Log Actions and UI Output #204044

Merged
merged 18 commits into from
Dec 17, 2024
28 changes: 13 additions & 15 deletions x-pack/plugins/stack_connectors/common/crowdstrike/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -318,16 +318,16 @@ export const CrowdstrikeExecuteRTRResponseSchema = schema.object(
schema.string(),
schema.object(
{
session_id: schema.maybe(schema.string()),
task_id: schema.maybe(schema.string()),
complete: schema.maybe(schema.boolean()),
stdout: schema.maybe(schema.string()),
stderr: schema.maybe(schema.string()),
base_command: schema.maybe(schema.string()),
aid: schema.maybe(schema.string()),
errors: schema.maybe(schema.arrayOf(schema.any())),
query_time: schema.maybe(schema.number()),
offline_queued: schema.maybe(schema.boolean()),
session_id: schema.string(),
task_id: schema.string(),
complete: schema.boolean(),
stdout: schema.string(),
stderr: schema.string(),
base_command: schema.string(),
aid: schema.string(),
errors: schema.arrayOf(schema.any()),
query_time: schema.number(),
offline_queued: schema.boolean(),
},
{ unknowns: 'allow' }
)
Expand All @@ -337,9 +337,9 @@ export const CrowdstrikeExecuteRTRResponseSchema = schema.object(
),
meta: schema.object(
{
query_time: schema.maybe(schema.number()),
powered_by: schema.maybe(schema.string()),
trace_id: schema.maybe(schema.string()),
query_time: schema.number(),
powered_by: schema.string(),
trace_id: schema.string(),
},
{ unknowns: 'allow' }
),
Expand All @@ -348,7 +348,5 @@ export const CrowdstrikeExecuteRTRResponseSchema = schema.object(
{ unknowns: 'allow' }
);

export type CrowdStrikeExecuteRTRResponse = typeof CrowdstrikeExecuteRTRResponseSchema;

// TODO: will be part of a next PR
export const CrowdstrikeGetScriptsParamsSchema = schema.any({});
5 changes: 3 additions & 2 deletions x-pack/plugins/stack_connectors/common/crowdstrike/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ import {
CrowdstrikeGetTokenResponseSchema,
CrowdstrikeGetAgentsResponseSchema,
RelaxedCrowdstrikeBaseApiResponseSchema,
CrowdstrikeInitRTRResponseSchema,
CrowdstrikeInitRTRParamsSchema,
CrowdstrikeExecuteRTRResponseSchema,
} from './schema';

export type CrowdstrikeConfig = TypeOf<typeof CrowdstrikeConfigSchema>;
Expand All @@ -35,9 +35,10 @@ export type CrowdstrikeGetAgentOnlineStatusResponse = TypeOf<
typeof CrowdstrikeGetAgentOnlineStatusResponseSchema
>;
export type CrowdstrikeGetTokenResponse = TypeOf<typeof CrowdstrikeGetTokenResponseSchema>;
export type CrowdstrikeInitRTRResponse = TypeOf<typeof CrowdstrikeInitRTRResponseSchema>;

export type CrowdstrikeHostActionsParams = TypeOf<typeof CrowdstrikeHostActionsParamsSchema>;

export type CrowdstrikeActionParams = TypeOf<typeof CrowdstrikeActionParamsSchema>;
export type CrowdstrikeInitRTRParams = TypeOf<typeof CrowdstrikeInitRTRParamsSchema>;

export type CrowdStrikeExecuteRTRResponse = TypeOf<typeof CrowdstrikeExecuteRTRResponseSchema>;
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import type {
CrowdstrikeGetTokenResponse,
CrowdstrikeGetAgentOnlineStatusResponse,
RelaxedCrowdstrikeBaseApiResponse,
CrowdStrikeExecuteRTRResponse,
} from '../../../common/crowdstrike/types';
import {
CrowdstrikeHostActionsParamsSchema,
Expand All @@ -31,7 +32,6 @@ import {
CrowdstrikeRTRCommandParamsSchema,
CrowdstrikeExecuteRTRResponseSchema,
CrowdstrikeGetScriptsParamsSchema,
CrowdStrikeExecuteRTRResponse,
CrowdstrikeApiDoNotValidateResponsesSchema,
CrowdstrikeGetTokenResponseSchema,
} from '../../../common/crowdstrike/schema';
Expand Down Expand Up @@ -292,15 +292,9 @@ export class CrowdstrikeConnector extends SubActionConnector<
payload: {
command: string;
endpoint_ids: string[];
overwriteUrl?: 'batchExecuteRTR' | 'batchActiveResponderExecuteRTR' | 'batchAdminExecuteRTR';
},
connectorUsageCollector: ConnectorUsageCollector
): Promise<CrowdStrikeExecuteRTRResponse> => {
// Some commands are only available in specific API endpoints, however there's an additional requirement check for the command's argument
// Eg. runscript command is available with the batchExecuteRTR endpoint, but if it goes with --Raw parameter, it should go to batchAdminExecuteRTR endpoint
// This overwrite value will be coming from kibana response actions api
const csUrl = payload.overwriteUrl ? this.urls[payload.overwriteUrl] : url;

const batchId = await this.crowdStrikeSessionManager.initializeSession(
{ endpoint_ids: payload.endpoint_ids },
connectorUsageCollector
Expand All @@ -313,7 +307,7 @@ export class CrowdstrikeConnector extends SubActionConnector<
}
return await this.crowdstrikeApiRequest<CrowdStrikeExecuteRTRResponse>(
{
url: csUrl,
url,
method: 'post',
data: {
base_command: baseCommand,
Expand All @@ -335,7 +329,6 @@ export class CrowdstrikeConnector extends SubActionConnector<
payload: {
command: string;
endpoint_ids: string[];
overwriteUrl?: 'batchActiveResponderExecuteRTR' | 'batchAdminExecuteRTR';
},
connectorUsageCollector: ConnectorUsageCollector
): Promise<CrowdStrikeExecuteRTRResponse> {
Expand All @@ -351,7 +344,6 @@ export class CrowdstrikeConnector extends SubActionConnector<
payload: {
command: string;
endpoint_ids: string[];
overwriteUrl?: 'batchAdminExecuteRTR';
},
connectorUsageCollector: ConnectorUsageCollector
): Promise<CrowdStrikeExecuteRTRResponse> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { GetProcessesRouteRequestSchema } from '../response_actions/running_proc
import { KillProcessRouteRequestSchema } from '../response_actions/kill_process';
import { SuspendProcessRouteRequestSchema } from '../response_actions/suspend_process';
import { UploadActionRequestSchema } from '../response_actions/upload';
import { RunScriptActionRequestSchema } from '../response_actions/run_script';

export const ResponseActionBodySchema = schema.oneOf([
IsolateRouteRequestSchema.body,
Expand All @@ -28,6 +29,7 @@ export const ResponseActionBodySchema = schema.oneOf([
ExecuteActionRequestSchema.body,
UploadActionRequestSchema.body,
ScanActionRequestSchema.body,
RunScriptActionRequestSchema.body,
]);

export type ResponseActionsRequestBody = TypeOf<typeof ResponseActionBodySchema>;
Original file line number Diff line number Diff line change
Expand Up @@ -26,27 +26,27 @@ export const RunScriptActionRequestSchema = {
/**
* The script to run
*/
Raw: schema.maybe(NonEmptyString),
raw: schema.maybe(NonEmptyString),
/**
* The path to the script on the host to run
*/
HostPath: schema.maybe(NonEmptyString),
hostPath: schema.maybe(NonEmptyString),
/**
* The path to the script in the cloud to run
*/
CloudFile: schema.maybe(NonEmptyString),
cloudFile: schema.maybe(NonEmptyString),
/**
* The command line to run
*/
CommandLine: schema.maybe(NonEmptyString),
commandLine: schema.maybe(NonEmptyString),
/**
* The max timeout value before the command is killed. Number represents milliseconds
*/
Timeout: schema.maybe(schema.number({ min: 1 })),
timeout: schema.maybe(schema.number({ min: 1 })),
},
{
validate: (params) => {
if (!params.Raw && !params.HostPath && !params.CloudFile) {
if (!params.raw && !params.hostPath && !params.cloudFile) {
return 'At least one of Raw, HostPath, or CloudFile must be provided';
}
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import type {
ResponseActionUploadOutputContent,
ResponseActionUploadParameters,
GetProcessesActionOutputContent,
ResponseActionRunScriptOutputContent,
ResponseActionRunScriptParameters,
} from '../../types';
import { RESPONSE_ACTION_AGENT_TYPE, RESPONSE_ACTION_TYPE } from './constants';

Expand Down Expand Up @@ -47,6 +49,15 @@ export const isProcessesAction = (
return action.command === 'running-processes';
};

export const isRunScriptAction = (
action: MaybeImmutable<SomeObjectWithCommand>
): action is ActionDetails<
ResponseActionRunScriptOutputContent,
ResponseActionRunScriptParameters
> => {
return action.command === 'runscript';
};

// type guards to ensure only the matching string values are attached to the types filter type
export const isAgentType = (type: string): type is (typeof RESPONSE_ACTION_AGENT_TYPE)[number] =>
RESPONSE_ACTION_AGENT_TYPE.includes(type as (typeof RESPONSE_ACTION_AGENT_TYPE)[number]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,8 @@ export interface ResponseActionScanOutputContent {
}

export interface ResponseActionRunScriptOutputContent {
output: string;
stdout: string;
stderr: string;
code: string;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@ Command Examples for Running Scripts:

3. Executes a raw script provided entirely within the "--Raw" flag.

runscript --Raw="Get-ChildItem."
runscript --Raw=\`\`\`Get-ChildItem.\`\`\`

4. Executes a script located on the remote host at the specified path with the provided command-line arguments.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import type {
ActionDetails,
MaybeImmutable,
ResponseActionExecuteOutputContent,
ResponseActionRunScriptOutputContent,
ResponseActionRunScriptParameters,
ResponseActionsExecuteParameters,
} from '../../../../common/endpoint/types';
import { EXECUTE_FILE_LINK_TITLE } from '../endpoint_response_actions_list/translations';
Expand All @@ -18,21 +20,27 @@ import { ExecuteActionHostResponseOutput } from './execute_action_host_response_

export interface ExecuteActionHostResponseProps {
action: MaybeImmutable<
ActionDetails<ResponseActionExecuteOutputContent, ResponseActionsExecuteParameters>
| ActionDetails<ResponseActionExecuteOutputContent, ResponseActionsExecuteParameters>
| ActionDetails<ResponseActionRunScriptOutputContent, ResponseActionRunScriptParameters>
>;
agentId?: string;
canAccessFileDownloadLink: boolean;
'data-test-subj'?: string;
textSize?: 'xs' | 's';
hideFile?: boolean;
hideContext?: boolean;
}

// Note: also used for RunScript command
export const ExecuteActionHostResponse = memo<ExecuteActionHostResponseProps>(
({
action,
agentId = action.agents[0],
canAccessFileDownloadLink,
textSize = 's',
'data-test-subj': dataTestSubj,
hideFile,
hideContext,
}) => {
const outputContent = useMemo(
() =>
Expand All @@ -44,21 +52,24 @@ export const ExecuteActionHostResponse = memo<ExecuteActionHostResponseProps>(

return (
<>
<EuiFlexItem>
<ResponseActionFileDownloadLink
action={action}
buttonTitle={EXECUTE_FILE_LINK_TITLE}
canAccessFileDownloadLink={canAccessFileDownloadLink}
data-test-subj={`${dataTestSubj}-getExecuteLink`}
textSize={textSize}
/>
<EuiSpacer size="xxl" />
</EuiFlexItem>
{!hideFile && (
<EuiFlexItem>
<ResponseActionFileDownloadLink
action={action}
buttonTitle={EXECUTE_FILE_LINK_TITLE}
canAccessFileDownloadLink={canAccessFileDownloadLink}
data-test-subj={`${dataTestSubj}-getExecuteLink`}
textSize={textSize}
/>
<EuiSpacer size="xxl" />
</EuiFlexItem>
)}
{outputContent && (
<ExecuteActionHostResponseOutput
outputContent={outputContent}
data-test-subj={`${dataTestSubj}-executeResponseOutput`}
textSize={textSize}
hideContext={hideContext}
/>
)}
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ interface ShellInfoContentProps {
textSize?: 's' | 'xs';
title: string;
}

const ShellInfoContent = memo<ShellInfoContentProps>(({ content, textSize, title }) => (
<StyledEuiText size={textSize}>
<strong>
Expand Down Expand Up @@ -178,10 +179,12 @@ export interface ExecuteActionHostResponseOutputProps {
outputContent: ResponseActionExecuteOutputContent;
'data-test-subj'?: string;
textSize?: 's' | 'xs';
hideContext?: boolean;
}

// Note: also used for RunScript command
export const ExecuteActionHostResponseOutput = memo<ExecuteActionHostResponseOutputProps>(
({ outputContent, 'data-test-subj': dataTestSubj, textSize = 'xs' }) => {
({ outputContent, 'data-test-subj': dataTestSubj, textSize = 'xs', hideContext }) => {
const contextContent = useMemo(
() => (
<>
Expand Down Expand Up @@ -216,14 +219,16 @@ export const ExecuteActionHostResponseOutput = memo<ExecuteActionHostResponseOut

return (
<>
<EuiFlexItem>
<ExecutionActionOutputAccordion
content={contextContent}
data-test-subj={`${dataTestSubj}-context`}
textSize={textSize}
type="context"
/>
</EuiFlexItem>
{!hideContext && (
<EuiFlexItem>
<ExecutionActionOutputAccordion
content={contextContent}
data-test-subj={`${dataTestSubj}-context`}
textSize={textSize}
type="context"
/>
</EuiFlexItem>
)}
<EuiFlexItem>
{outputContent.stderr.length > 0 && (
<>
Expand Down
Loading
Loading