Skip to content

Commit

Permalink
[EDR Workflows] Add RunScript CS Command - UI (elastic#202012)
Browse files Browse the repository at this point in the history
  • Loading branch information
tomsonpl authored Dec 10, 2024
1 parent 2818a7c commit 9b27804
Show file tree
Hide file tree
Showing 24 changed files with 259 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ export const GET_FILE_ROUTE = `${BASE_ENDPOINT_ACTION_ROUTE}/get_file`;
export const EXECUTE_ROUTE = `${BASE_ENDPOINT_ACTION_ROUTE}/execute`;
export const UPLOAD_ROUTE = `${BASE_ENDPOINT_ACTION_ROUTE}/upload`;
export const SCAN_ROUTE = `${BASE_ENDPOINT_ACTION_ROUTE}/scan`;
export const RUN_SCRIPT_ROUTE = `${BASE_ENDPOINT_ACTION_ROUTE}/run_script`;

/** Endpoint Actions Routes */
export const ENDPOINT_ACTION_LOG_ROUTE = `${BASE_ENDPOINT_ROUTE}/action_log/{agent_id}`;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,17 @@ export class EndpointActionGenerator extends BaseDataGenerator {
}
}

if (command === 'runscript') {
if (!output) {
output = {
type: 'json',
content: {
code: '200',
},
};
}
}

if (command === 'execute') {
if (!output) {
output = this.generateExecuteActionResponseOutput();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export const RESPONSE_ACTION_API_COMMANDS_NAMES = [
'execute',
'upload',
'scan',
'runscript',
] as const;

export type ResponseActionsApiCommandNames = (typeof RESPONSE_ACTION_API_COMMANDS_NAMES)[number];
Expand All @@ -54,6 +55,7 @@ export const ENDPOINT_CAPABILITIES = [
'execute',
'upload_file',
'scan',
'runscript',
] as const;

export type EndpointCapabilities = (typeof ENDPOINT_CAPABILITIES)[number];
Expand All @@ -72,6 +74,7 @@ export const CONSOLE_RESPONSE_ACTION_COMMANDS = [
'execute',
'upload',
'scan',
'runscript',
] as const;

export type ConsoleResponseActionCommands = (typeof CONSOLE_RESPONSE_ACTION_COMMANDS)[number];
Expand Down Expand Up @@ -100,6 +103,7 @@ export const RESPONSE_CONSOLE_ACTION_COMMANDS_TO_RBAC_FEATURE_CONTROL: Record<
execute: 'writeExecuteOperations',
upload: 'writeFileOperations',
scan: 'writeScanOperations',
runscript: 'writeExecuteOperations',
});

export const RESPONSE_ACTION_API_COMMAND_TO_CONSOLE_COMMAND_MAP = Object.freeze<
Expand All @@ -114,6 +118,7 @@ export const RESPONSE_ACTION_API_COMMAND_TO_CONSOLE_COMMAND_MAP = Object.freeze<
'suspend-process': 'suspend-process',
upload: 'upload',
scan: 'scan',
runscript: 'runscript',
});

export const RESPONSE_CONSOLE_COMMAND_TO_API_COMMAND_MAP = Object.freeze<
Expand All @@ -128,6 +133,7 @@ export const RESPONSE_CONSOLE_COMMAND_TO_API_COMMAND_MAP = Object.freeze<
'suspend-process': 'suspend-process',
upload: 'upload',
scan: 'scan',
runscript: 'runscript',
});

export const RESPONSE_CONSOLE_ACTION_COMMANDS_TO_ENDPOINT_CAPABILITY = Object.freeze<
Expand All @@ -142,6 +148,7 @@ export const RESPONSE_CONSOLE_ACTION_COMMANDS_TO_ENDPOINT_CAPABILITY = Object.fr
'suspend-process': 'suspend_process',
upload: 'upload_file',
scan: 'scan',
runscript: 'runscript',
});

/**
Expand All @@ -159,6 +166,7 @@ export const RESPONSE_CONSOLE_ACTION_COMMANDS_TO_REQUIRED_AUTHZ = Object.freeze<
'kill-process': 'canKillProcess',
'suspend-process': 'canSuspendProcess',
scan: 'canWriteScanOperations',
runscript: 'canWriteExecuteOperations',
});

// 4 hrs in seconds
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,18 @@ const RESPONSE_ACTIONS_SUPPORT_MAP: SupportMap = {
crowdstrike: false,
},
},
runscript: {
automated: {
endpoint: false,
sentinel_one: false,
crowdstrike: false,
},
manual: {
endpoint: false,
sentinel_one: false,
crowdstrike: true,
},
},
};

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,12 @@ export const allowedExperimentalValues = Object.freeze({
* Enables the Defend Insights feature
*/
defendInsights: false,

/**
* Enables CrowdStrike's RunScript RTR command
*/

crowdstrikeRunScriptEnabled: false,
});

type ExperimentalConfigKeys = Array<keyof ExperimentalFeatures>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,88 @@ export const CONSOLE_COMMANDS = {
},
};

export const CROWDSTRIKE_CONSOLE_COMMANDS = {
runscript: {
args: {
raw: {
about: i18n.translate(
'xpack.securitySolution.crowdStrikeConsoleCommands.runscript.args.raw.about',
{
defaultMessage: 'Raw script content',
}
),
},
cloudFile: {
about: i18n.translate(
'xpack.securitySolution.crowdStrikeConsoleCommands.runscript.args.cloudFile.about',
{
defaultMessage: 'Script name in cloud storage',
}
),
},
commandLine: {
about: i18n.translate(
'xpack.securitySolution.crowdStrikeConsoleCommands.runscript.args.commandLine.about',
{
defaultMessage: 'Command line arguments',
}
),
},
hostPath: {
about: i18n.translate(
'xpack.securitySolution.crowdStrikeConsoleCommands.runscript.args.hostPath.about',
{
defaultMessage: 'Absolute or relative path of script on host machine',
}
),
},
timeout: {
about: i18n.translate(
'xpack.securitySolution.crowdStrikeConsoleCommands.runscript.args.timeout.about',
{
defaultMessage: 'Timeout in seconds',
}
),
},
},
title: i18n.translate('xpack.securitySolution.crowdStrikeConsoleCommands.runscript.title', {
defaultMessage: 'Isolate',
}),
about: i18n.translate('xpack.securitySolution.crowdStrikeConsoleCommands.runscript.about', {
defaultMessage: 'Run a script on the host',
}),
helpUsage: i18n.translate('xpack.securitySolution.crowdStrikeConsoleCommands.runscript.about', {
defaultMessage: `
Command Examples for Running Scripts:
1. Executes a script saved in the CrowdStrike cloud with the specified command-line arguments.
runscript --CloudFile="CloudScript1.ps1" --CommandLine="-Verbose true"
2. Executes a script saved in the CrowdStrike cloud with the specified command-line arguments and a 180-second timeout.
runscript --CloudFile="CloudScript1.ps1" --CommandLine="-Verbose true" -Timeout=180
3. Executes a raw script provided entirely within the "--Raw" flag.
runscript --Raw="Get-ChildItem."
4. Executes a script located on the remote host at the specified path with the provided command-line arguments.
runscript --HostPath="C:\\temp\\LocalScript.ps1" --CommandLine="-Verbose true"
`,
}),
privileges: i18n.translate(
'xpack.securitySolution.crowdStrikeConsoleCommands.runscript.privileges',
{
defaultMessage:
'Insufficient privileges to run script. Contact your Kibana administrator if you think you should have this permission.',
}
),
},
};

export const CONFIRM_WARNING_MODAL_LABELS = (entryType: string) => {
return {
title: i18n.translate('xpack.securitySolution.artifacts.confirmWarningModal.title', {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@ export const CommandInputUsage = memo<Pick<CommandUsageProps, 'commandDef'>>(({
});
}, [commandDef]);

const helpExample = useMemo(() => {
if (commandDef.helpUsage) {
return commandDef.helpUsage;
}
return commandDef.exampleUsage;
}, [commandDef]);

return (
<>
<EuiDescriptionList
Expand All @@ -55,7 +62,7 @@ export const CommandInputUsage = memo<Pick<CommandUsageProps, 'commandDef'>>(({
titleProps={additionalProps}
/>
<EuiSpacer size="s" />
{commandDef.exampleUsage && (
{helpExample && (
<EuiDescriptionList
compressed
type="column"
Expand All @@ -69,7 +76,7 @@ export const CommandInputUsage = memo<Pick<CommandUsageProps, 'commandDef'>>(({
})}
</ConsoleCodeBlock>
),
description: <ConsoleCodeBlock>{commandDef.exampleUsage}</ConsoleCodeBlock>,
description: <ConsoleCodeBlock>{helpExample}</ConsoleCodeBlock>,
},
]}
descriptionProps={additionalProps}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,12 @@ export interface CommandArgDefinition {
* - `truthy`: The argument must have a value and the values must be "truthy" (evaluate to `Boolean` true)
*/
mustHaveValue?: boolean | 'non-empty-string' | 'number' | 'number-greater-than-zero' | 'truthy';

/**
* Specifies that one or more arguments might be required, but only one of them can be used at a time.
*/
exclusiveOr?: boolean;

/**
* Validate the individual values given to this argument.
* Should return `true` if valid or a string with the error message
Expand Down Expand Up @@ -124,10 +129,17 @@ export interface CommandDefinition<TMeta = any> {
/**
* Displayed in the input hint area when the user types the command as well as in the output of
* this command's `--help`. This value will override the command usage generated by the console
* from the Command Definition.
* from the Command Definition. It's value displayed in `--help` would overriden by `helpUsage` if defined.
*/
exampleUsage?: string;

/**
* Displayed in the output of this command's `--help`.
* This value will override the command usage generated by the console
* from the Command Definition.
*/
helpUsage?: string;

/**
* Validate the command entered by the user. This is called only after the Console has ran
* through all of its builtin validations (based on `CommandDefinition`).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ import {
import { getCommandAboutInfo } from './get_command_about_info';

import { validateUnitOfTime } from './utils';
import { CONSOLE_COMMANDS } from '../../../common/translations';
import { CONSOLE_COMMANDS, CROWDSTRIKE_CONSOLE_COMMANDS } from '../../../common/translations';
import { ScanActionResult } from '../command_render_components/scan_action';

const emptyArgumentValidator = (argData: ParsedArgData): true | string => {
Expand Down Expand Up @@ -167,6 +167,7 @@ export const getEndpointConsoleCommands = ({
const featureFlags = ExperimentalFeaturesService.get();

const isUploadEnabled = featureFlags.responseActionUploadEnabled;
const crowdstrikeRunScriptEnabled = featureFlags.crowdstrikeRunScriptEnabled;

const doesEndpointSupportCommand = (commandName: ConsoleResponseActionCommands) => {
// Agent capabilities is only validated for Endpoint agent types
Expand Down Expand Up @@ -523,6 +524,71 @@ export const getEndpointConsoleCommands = ({
privileges: endpointPrivileges,
}),
});
if (crowdstrikeRunScriptEnabled) {
consoleCommands.push({
name: 'runscript',
about: getCommandAboutInfo({
aboutInfo: CROWDSTRIKE_CONSOLE_COMMANDS.runscript.about,
isSupported: doesEndpointSupportCommand('runscript'),
}),
RenderComponent: () => null,
meta: {
agentType,
endpointId: endpointAgentId,
capabilities: endpointCapabilities,
privileges: endpointPrivileges,
},
exampleUsage: `runscript --Raw=\`\`\`Get-ChildItem .\`\`\` -CommandLine=""`,
helpUsage: CROWDSTRIKE_CONSOLE_COMMANDS.runscript.helpUsage,
exampleInstruction: CROWDSTRIKE_CONSOLE_COMMANDS.runscript.about,
validate: capabilitiesAndPrivilegesValidator(agentType),
mustHaveArgs: true,
args: {
Raw: {
required: false,
allowMultiples: false,
about: CROWDSTRIKE_CONSOLE_COMMANDS.runscript.args.raw.about,
mustHaveValue: 'non-empty-string',
exclusiveOr: true,
},
CloudFile: {
required: false,
allowMultiples: false,
about: CROWDSTRIKE_CONSOLE_COMMANDS.runscript.args.cloudFile.about,
mustHaveValue: 'non-empty-string',
exclusiveOr: true,
},
CommandLine: {
required: false,
allowMultiples: false,
about: CROWDSTRIKE_CONSOLE_COMMANDS.runscript.args.commandLine.about,
mustHaveValue: 'non-empty-string',
},
HostPath: {
required: false,
allowMultiples: false,
about: CROWDSTRIKE_CONSOLE_COMMANDS.runscript.args.hostPath.about,
mustHaveValue: 'non-empty-string',
exclusiveOr: true,
},
Timeout: {
required: false,
allowMultiples: false,
about: CROWDSTRIKE_CONSOLE_COMMANDS.runscript.args.timeout.about,
mustHaveValue: 'number-greater-than-zero',
},
...commandCommentArgument(),
},
helpGroupLabel: HELP_GROUPS.responseActions.label,
helpGroupPosition: HELP_GROUPS.responseActions.position,
helpCommandPosition: 9,
helpDisabled: !doesEndpointSupportCommand('runscript'),
helpHidden: !getRbacControl({
commandName: 'runscript',
privileges: endpointPrivileges,
}),
});
}

switch (agentType) {
case 'sentinel_one':
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,10 @@ describe('When displaying Endpoint Response Actions', () => {
HELP_GROUPS.responseActions.label
);

const expectedCommands: string[] = [...CONSOLE_RESPONSE_ACTION_COMMANDS];
const endpointCommands = CONSOLE_RESPONSE_ACTION_COMMANDS.filter(
(command) => command !== 'runscript'
);
const expectedCommands: string[] = [...endpointCommands];
// add status to the list of expected commands in that order
expectedCommands.splice(2, 0, 'status');

Expand Down Expand Up @@ -149,6 +152,7 @@ describe('When displaying Endpoint Response Actions', () => {
beforeEach(() => {
(ExperimentalFeaturesService.get as jest.Mock).mockReturnValue({
responseActionsCrowdstrikeManualHostIsolationEnabled: true,
crowdstrikeRunScriptEnabled: true,
});
commands = getEndpointConsoleCommands({
agentType: 'crowdstrike',
Expand Down Expand Up @@ -176,7 +180,7 @@ describe('When displaying Endpoint Response Actions', () => {
HELP_GROUPS.responseActions.label
);

expect(commandsInPanel).toEqual(['isolate', 'release']);
expect(commandsInPanel).toEqual(['isolate', 'release', 'runscript --Raw']);
});
});
});
Loading

0 comments on commit 9b27804

Please sign in to comment.