mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Security Solution][Endpoint] New API for creating a get-file
response action request (#142671)
* Register API for creating a `get-file` response action request * Change command parameter to `path` instead of `file` * add tests for new `get-file` request route * Moved some `const` from `common/endpoint/constants` to `common/endpoint/service/response_actions/constants` * Renames Response Action const for clarity * Changed `useActionHistoryUrlParams()` test to include all response actions * Added `get_file` to endpoint capabilities and added support into the generator * Adjusted types to use `ConsoleResponseActionCommands`
This commit is contained in:
parent
db6dacaa7c
commit
b5bacc3cbe
28 changed files with 201 additions and 105 deletions
|
@ -7,11 +7,10 @@
|
|||
|
||||
import './feature_table.scss';
|
||||
|
||||
import type { EuiAccordionProps, EuiButtonGroupOptionProps } from '@elastic/eui';
|
||||
import {
|
||||
EuiAccordion,
|
||||
EuiAccordionProps,
|
||||
EuiButtonGroup,
|
||||
EuiButtonGroupOptionProps,
|
||||
EuiCallOut,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
|
|
|
@ -67,6 +67,7 @@ export const UNISOLATE_HOST_ROUTE_V2 = `${BASE_ENDPOINT_ACTION_ROUTE}/unisolate`
|
|||
export const GET_PROCESSES_ROUTE = `${BASE_ENDPOINT_ACTION_ROUTE}/running_procs`;
|
||||
export const KILL_PROCESS_ROUTE = `${BASE_ENDPOINT_ACTION_ROUTE}/kill_process`;
|
||||
export const SUSPEND_PROCESS_ROUTE = `${BASE_ENDPOINT_ACTION_ROUTE}/suspend_process`;
|
||||
export const GET_FILE_ROUTE = `${BASE_ENDPOINT_ACTION_ROUTE}/get_file`;
|
||||
|
||||
/** Endpoint Actions Routes */
|
||||
export const ENDPOINT_ACTION_LOG_ROUTE = `${BASE_ENDPOINT_ROUTE}/action_log/{agent_id}`;
|
||||
|
@ -78,26 +79,3 @@ export const failedFleetActionErrorCode = '424';
|
|||
|
||||
export const ENDPOINT_DEFAULT_PAGE = 0;
|
||||
export const ENDPOINT_DEFAULT_PAGE_SIZE = 10;
|
||||
|
||||
/**
|
||||
* The list of possible capabilities, reported by the endpoint in the metadata document
|
||||
*/
|
||||
export const RESPONDER_CAPABILITIES = [
|
||||
'isolation',
|
||||
'kill_process',
|
||||
'suspend_process',
|
||||
'running_processes',
|
||||
] as const;
|
||||
|
||||
export type ResponderCapabilities = typeof RESPONDER_CAPABILITIES[number];
|
||||
|
||||
/** The list of possible responder command names **/
|
||||
export const RESPONDER_COMMANDS = [
|
||||
'isolate',
|
||||
'release',
|
||||
'kill-process',
|
||||
'suspend-process',
|
||||
'processes',
|
||||
] as const;
|
||||
|
||||
export type ResponderCommands = typeof RESPONDER_COMMANDS[number];
|
||||
|
|
|
@ -22,7 +22,7 @@ import type {
|
|||
ActionResponseOutput,
|
||||
} from '../types';
|
||||
import { ActivityLogItemTypes } from '../types';
|
||||
import { RESPONSE_ACTION_COMMANDS } from '../service/response_actions/constants';
|
||||
import { RESPONSE_ACTION_API_COMMANDS_NAMES } from '../service/response_actions/constants';
|
||||
|
||||
export class EndpointActionGenerator extends BaseDataGenerator {
|
||||
/** Generate a random endpoint Action request (isolate or unisolate) */
|
||||
|
@ -245,6 +245,6 @@ export class EndpointActionGenerator extends BaseDataGenerator {
|
|||
}
|
||||
|
||||
protected randomResponseActionCommand() {
|
||||
return this.randomChoice(RESPONSE_ACTION_COMMANDS);
|
||||
return this.randomChoice(RESPONSE_ACTION_API_COMMANDS_NAMES);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
import type { DeepPartial } from 'utility-types';
|
||||
import { merge } from 'lodash';
|
||||
import { gte } from 'semver';
|
||||
import type { EndpointCapabilities } from '../service/response_actions/constants';
|
||||
import { BaseDataGenerator } from './base_data_generator';
|
||||
import type { HostMetadataInterface, OSFields } from '../types';
|
||||
import { EndpointStatus, HostPolicyResponseActionStatus } from '../types';
|
||||
|
@ -23,13 +24,17 @@ export class EndpointMetadataGenerator extends BaseDataGenerator {
|
|||
const agentVersion = overrides?.agent?.version ?? this.randomVersion();
|
||||
const agentId = this.seededUUIDv4();
|
||||
const isIsolated = this.randomBoolean(0.3);
|
||||
const capabilities = ['isolation'];
|
||||
const capabilities: EndpointCapabilities[] = ['isolation'];
|
||||
|
||||
// v8.4 introduced additional endpoint capabilities
|
||||
if (gte(agentVersion, '8.4.0')) {
|
||||
capabilities.push('kill_process', 'suspend_process', 'running_processes');
|
||||
}
|
||||
|
||||
if (gte(agentVersion, '8.6.0')) {
|
||||
capabilities.push('get_file');
|
||||
}
|
||||
|
||||
const hostMetadataDoc: HostMetadataInterface = {
|
||||
'@timestamp': ts,
|
||||
event: {
|
||||
|
|
|
@ -17,7 +17,7 @@ import type {
|
|||
EndpointActionResponse,
|
||||
} from '../types';
|
||||
import { ActivityLogItemTypes } from '../types';
|
||||
import { RESPONSE_ACTION_COMMANDS } from '../service/response_actions/constants';
|
||||
import { RESPONSE_ACTION_API_COMMANDS_NAMES } from '../service/response_actions/constants';
|
||||
|
||||
export class FleetActionGenerator extends BaseDataGenerator {
|
||||
/** Generate a random endpoint Action (isolate or unisolate) */
|
||||
|
@ -143,6 +143,6 @@ export class FleetActionGenerator extends BaseDataGenerator {
|
|||
}
|
||||
|
||||
protected randomResponseActionCommand() {
|
||||
return this.randomChoice(RESPONSE_ACTION_COMMANDS);
|
||||
return this.randomChoice(RESPONSE_ACTION_API_COMMANDS_NAMES);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ import type { TypeOf } from '@kbn/config-schema';
|
|||
import { schema } from '@kbn/config-schema';
|
||||
import { ENDPOINT_DEFAULT_PAGE_SIZE } from '../constants';
|
||||
import {
|
||||
RESPONSE_ACTION_COMMANDS,
|
||||
RESPONSE_ACTION_API_COMMANDS_NAMES,
|
||||
RESPONSE_ACTION_STATUS,
|
||||
} from '../service/response_actions/constants';
|
||||
|
||||
|
@ -78,7 +78,7 @@ export const ActionDetailsRequestSchema = {
|
|||
// TODO: fix the odd TS error
|
||||
const commandsSchema = schema.oneOf(
|
||||
// @ts-expect-error TS2769: No overload matches this call
|
||||
RESPONSE_ACTION_COMMANDS.map((command) => schema.literal(command))
|
||||
RESPONSE_ACTION_API_COMMANDS_NAMES.map((command) => schema.literal(command))
|
||||
);
|
||||
|
||||
// TODO: fix the odd TS error
|
||||
|
@ -115,3 +115,13 @@ export const EndpointActionListRequestSchema = {
|
|||
};
|
||||
|
||||
export type EndpointActionListRequestQuery = TypeOf<typeof EndpointActionListRequestSchema.query>;
|
||||
|
||||
export const EndpointActionGetFileSchema = {
|
||||
body: schema.object({
|
||||
...BaseActionRequestSchema,
|
||||
|
||||
parameters: schema.object({
|
||||
path: schema.string({ minLength: 1 }),
|
||||
}),
|
||||
}),
|
||||
};
|
||||
|
|
|
@ -7,7 +7,10 @@
|
|||
export const RESPONSE_ACTION_STATUS = ['failed', 'pending', 'successful'] as const;
|
||||
export type ResponseActionStatus = typeof RESPONSE_ACTION_STATUS[number];
|
||||
|
||||
export const RESPONSE_ACTION_COMMANDS = [
|
||||
/**
|
||||
* The Command names that are used in the API payload for the `{ command: '' }` attribute
|
||||
*/
|
||||
export const RESPONSE_ACTION_API_COMMANDS_NAMES = [
|
||||
'isolate',
|
||||
'unisolate',
|
||||
'kill-process',
|
||||
|
@ -15,4 +18,33 @@ export const RESPONSE_ACTION_COMMANDS = [
|
|||
'running-processes',
|
||||
'get-file',
|
||||
] as const;
|
||||
export type ResponseActions = typeof RESPONSE_ACTION_COMMANDS[number];
|
||||
|
||||
export type ResponseActionsApiCommandNames = typeof RESPONSE_ACTION_API_COMMANDS_NAMES[number];
|
||||
|
||||
/**
|
||||
* The list of possible capabilities, reported by the endpoint in the metadata document
|
||||
*/
|
||||
export const ENDPOINT_CAPABILITIES = [
|
||||
'isolation',
|
||||
'kill_process',
|
||||
'suspend_process',
|
||||
'running_processes',
|
||||
'get_file',
|
||||
] as const;
|
||||
|
||||
export type EndpointCapabilities = typeof ENDPOINT_CAPABILITIES[number];
|
||||
|
||||
/**
|
||||
* The list of possible console command names that generate a Response Action to be dispatched
|
||||
* to the Endpoint. (FYI: not all console commands are response actions)
|
||||
*/
|
||||
export const CONSOLE_RESPONSE_ACTION_COMMANDS = [
|
||||
'isolate',
|
||||
'release',
|
||||
'kill-process',
|
||||
'suspend-process',
|
||||
'processes',
|
||||
'get-file',
|
||||
] as const;
|
||||
|
||||
export type ConsoleResponseActionCommands = typeof CONSOLE_RESPONSE_ACTION_COMMANDS[number];
|
||||
|
|
|
@ -12,7 +12,10 @@ import type {
|
|||
ResponseActionBodySchema,
|
||||
KillOrSuspendProcessRequestSchema,
|
||||
} from '../schema/actions';
|
||||
import type { ResponseActionStatus, ResponseActions } from '../service/response_actions/constants';
|
||||
import type {
|
||||
ResponseActionStatus,
|
||||
ResponseActionsApiCommandNames,
|
||||
} from '../service/response_actions/constants';
|
||||
|
||||
export type ISOLATION_ACTIONS = 'isolate' | 'unisolate';
|
||||
|
||||
|
@ -140,7 +143,7 @@ export interface EndpointActionData<
|
|||
T extends EndpointActionDataParameterTypes = never,
|
||||
TOutputContent extends object = object
|
||||
> {
|
||||
command: ResponseActions;
|
||||
command: ResponseActionsApiCommandNames;
|
||||
comment?: string;
|
||||
parameters?: T;
|
||||
output?: ActionResponseOutput<TOutputContent>;
|
||||
|
@ -282,7 +285,7 @@ export interface ActionDetails<TOutputContent extends object = object> {
|
|||
* The Endpoint type of action (ex. `isolate`, `release`) that is being requested to be
|
||||
* performed on the endpoint
|
||||
*/
|
||||
command: ResponseActions;
|
||||
command: ResponseActionsApiCommandNames;
|
||||
/**
|
||||
* Will be set to true only if action is not yet completed and elapsed time has exceeded
|
||||
* the request's expiration date
|
||||
|
|
|
@ -5,14 +5,14 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { RESPONDER_CAPABILITIES } from '../../../../common/endpoint/constants';
|
||||
import { ENDPOINT_CAPABILITIES } from '../../../../common/endpoint/service/response_actions/constants';
|
||||
import type { HostMetadata, MaybeImmutable } from '../../../../common/endpoint/types';
|
||||
|
||||
export const useDoesEndpointSupportResponder = (
|
||||
endpointMetadata: MaybeImmutable<HostMetadata> | undefined
|
||||
): boolean => {
|
||||
if (endpointMetadata) {
|
||||
return RESPONDER_CAPABILITIES.every((capability) =>
|
||||
return ENDPOINT_CAPABILITIES.every((capability) =>
|
||||
endpointMetadata?.Endpoint.capabilities?.includes(capability)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -36,7 +36,7 @@ import {
|
|||
import { getUserPrivilegesMockDefaultValue } from '../../../common/components/user_privileges/__mocks__';
|
||||
import { allCasesPermissions } from '../../../cases_test_utils';
|
||||
import { HostStatus } from '../../../../common/endpoint/types';
|
||||
import { RESPONDER_CAPABILITIES } from '../../../../common/endpoint/constants';
|
||||
import { ENDPOINT_CAPABILITIES } from '../../../../common/endpoint/service/response_actions/constants';
|
||||
|
||||
jest.mock('../../../common/components/user_privileges');
|
||||
|
||||
|
@ -470,7 +470,7 @@ describe('take action dropdown', () => {
|
|||
...getApiResponse().metadata,
|
||||
Endpoint: {
|
||||
...getApiResponse().metadata.Endpoint,
|
||||
capabilities: [...RESPONDER_CAPABILITIES],
|
||||
capabilities: [...ENDPOINT_CAPABILITIES],
|
||||
},
|
||||
},
|
||||
host_status: HostStatus.UNENROLLED,
|
||||
|
|
|
@ -6,6 +6,10 @@
|
|||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import type {
|
||||
EndpointCapabilities,
|
||||
ConsoleResponseActionCommands,
|
||||
} from '../../../../common/endpoint/service/response_actions/constants';
|
||||
import type { Command, CommandDefinition } from '../console';
|
||||
import { IsolateActionResult } from './isolate_action';
|
||||
import { ReleaseActionResult } from './release_action';
|
||||
|
@ -16,10 +20,6 @@ import { GetProcessesActionResult } from './get_processes_action';
|
|||
import type { ParsedArgData } from '../console/service/parsed_command_input';
|
||||
import type { ImmutableArray } from '../../../../common/endpoint/types';
|
||||
import { UPGRADE_ENDPOINT_FOR_RESPONDER } from '../../../common/translations';
|
||||
import type {
|
||||
ResponderCapabilities,
|
||||
ResponderCommands,
|
||||
} from '../../../../common/endpoint/constants';
|
||||
import { getCommandAboutInfo } from './get_command_about_info';
|
||||
|
||||
const emptyArgumentValidator = (argData: ParsedArgData): true | string => {
|
||||
|
@ -45,7 +45,7 @@ const pidValidator = (argData: ParsedArgData): true | string => {
|
|||
}
|
||||
};
|
||||
|
||||
const commandToCapabilitiesMap = new Map<ResponderCommands, ResponderCapabilities>([
|
||||
const commandToCapabilitiesMap = new Map<ConsoleResponseActionCommands, EndpointCapabilities>([
|
||||
['isolate', 'isolation'],
|
||||
['release', 'isolation'],
|
||||
['kill-process', 'kill_process'],
|
||||
|
@ -54,9 +54,9 @@ const commandToCapabilitiesMap = new Map<ResponderCommands, ResponderCapabilitie
|
|||
]);
|
||||
|
||||
const capabilitiesValidator = (command: Command): true | string => {
|
||||
const endpointCapabilities: ResponderCapabilities[] = command.commandDefinition.meta.capabilities;
|
||||
const endpointCapabilities: EndpointCapabilities[] = command.commandDefinition.meta.capabilities;
|
||||
const responderCapability = commandToCapabilitiesMap.get(
|
||||
command.commandDefinition.name as ResponderCommands
|
||||
command.commandDefinition.name as ConsoleResponseActionCommands
|
||||
);
|
||||
if (responderCapability) {
|
||||
if (endpointCapabilities.includes(responderCapability)) {
|
||||
|
@ -97,7 +97,7 @@ export const getEndpointResponseActionsConsoleCommands = ({
|
|||
endpointAgentId: string;
|
||||
endpointCapabilities: ImmutableArray<string>;
|
||||
}): CommandDefinition[] => {
|
||||
const doesEndpointSupportCommand = (commandName: ResponderCommands) => {
|
||||
const doesEndpointSupportCommand = (commandName: ConsoleResponseActionCommands) => {
|
||||
const responderCapability = commandToCapabilitiesMap.get(commandName);
|
||||
if (responderCapability) {
|
||||
return endpointCapabilities.includes(responderCapability);
|
||||
|
|
|
@ -16,12 +16,12 @@ import { getEndpointResponseActionsConsoleCommands } from '../endpoint_response_
|
|||
import { responseActionsHttpMocks } from '../../../mocks/response_actions_http_mocks';
|
||||
import { enterConsoleCommand } from '../../console/mocks';
|
||||
import { waitFor } from '@testing-library/react';
|
||||
import type { ResponderCapabilities } from '../../../../../common/endpoint/constants';
|
||||
import { RESPONDER_CAPABILITIES } from '../../../../../common/endpoint/constants';
|
||||
import type { EndpointCapabilities } from '../../../../../common/endpoint/service/response_actions/constants';
|
||||
import { ENDPOINT_CAPABILITIES } from '../../../../../common/endpoint/service/response_actions/constants';
|
||||
|
||||
describe('When using processes action from response actions console', () => {
|
||||
let render: (
|
||||
capabilities?: ResponderCapabilities[]
|
||||
capabilities?: EndpointCapabilities[]
|
||||
) => Promise<ReturnType<AppContextTestRender['render']>>;
|
||||
let renderResult: ReturnType<AppContextTestRender['render']>;
|
||||
let apiMocks: ReturnType<typeof responseActionsHttpMocks>;
|
||||
|
@ -34,7 +34,7 @@ describe('When using processes action from response actions console', () => {
|
|||
|
||||
apiMocks = responseActionsHttpMocks(mockedContext.coreStart.http);
|
||||
|
||||
render = async (capabilities: ResponderCapabilities[] = [...RESPONDER_CAPABILITIES]) => {
|
||||
render = async (capabilities: EndpointCapabilities[] = [...ENDPOINT_CAPABILITIES]) => {
|
||||
renderResult = mockedContext.render(
|
||||
<ConsoleManagerTestComponent
|
||||
registerConsoleProps={() => {
|
||||
|
|
|
@ -16,13 +16,13 @@ import { getEndpointResponseActionsConsoleCommands } from '../endpoint_response_
|
|||
import { responseActionsHttpMocks } from '../../../mocks/response_actions_http_mocks';
|
||||
import { enterConsoleCommand } from '../../console/mocks';
|
||||
import { waitFor } from '@testing-library/react';
|
||||
import type { ResponderCapabilities } from '../../../../../common/endpoint/constants';
|
||||
import { RESPONDER_CAPABILITIES } from '../../../../../common/endpoint/constants';
|
||||
import { getDeferred } from '../../../mocks/utils';
|
||||
import type { EndpointCapabilities } from '../../../../../common/endpoint/service/response_actions/constants';
|
||||
import { ENDPOINT_CAPABILITIES } from '../../../../../common/endpoint/service/response_actions/constants';
|
||||
|
||||
describe('When using isolate action from response actions console', () => {
|
||||
let render: (
|
||||
capabilities?: ResponderCapabilities[]
|
||||
capabilities?: EndpointCapabilities[]
|
||||
) => Promise<ReturnType<AppContextTestRender['render']>>;
|
||||
let renderResult: ReturnType<AppContextTestRender['render']>;
|
||||
let apiMocks: ReturnType<typeof responseActionsHttpMocks>;
|
||||
|
@ -35,7 +35,7 @@ describe('When using isolate action from response actions console', () => {
|
|||
|
||||
apiMocks = responseActionsHttpMocks(mockedContext.coreStart.http);
|
||||
|
||||
render = async (capabilities: ResponderCapabilities[] = [...RESPONDER_CAPABILITIES]) => {
|
||||
render = async (capabilities: EndpointCapabilities[] = [...ENDPOINT_CAPABILITIES]) => {
|
||||
renderResult = mockedContext.render(
|
||||
<ConsoleManagerTestComponent
|
||||
registerConsoleProps={() => {
|
||||
|
|
|
@ -16,12 +16,12 @@ import { getEndpointResponseActionsConsoleCommands } from '../endpoint_response_
|
|||
import { enterConsoleCommand } from '../../console/mocks';
|
||||
import { waitFor } from '@testing-library/react';
|
||||
import { responseActionsHttpMocks } from '../../../mocks/response_actions_http_mocks';
|
||||
import type { ResponderCapabilities } from '../../../../../common/endpoint/constants';
|
||||
import { RESPONDER_CAPABILITIES } from '../../../../../common/endpoint/constants';
|
||||
import type { EndpointCapabilities } from '../../../../../common/endpoint/service/response_actions/constants';
|
||||
import { ENDPOINT_CAPABILITIES } from '../../../../../common/endpoint/service/response_actions/constants';
|
||||
|
||||
describe('When using the kill-process action from response actions console', () => {
|
||||
let render: (
|
||||
capabilities?: ResponderCapabilities[]
|
||||
capabilities?: EndpointCapabilities[]
|
||||
) => Promise<ReturnType<AppContextTestRender['render']>>;
|
||||
let renderResult: ReturnType<AppContextTestRender['render']>;
|
||||
let apiMocks: ReturnType<typeof responseActionsHttpMocks>;
|
||||
|
@ -34,7 +34,7 @@ describe('When using the kill-process action from response actions console', ()
|
|||
|
||||
apiMocks = responseActionsHttpMocks(mockedContext.coreStart.http);
|
||||
|
||||
render = async (capabilities: ResponderCapabilities[] = [...RESPONDER_CAPABILITIES]) => {
|
||||
render = async (capabilities: EndpointCapabilities[] = [...ENDPOINT_CAPABILITIES]) => {
|
||||
renderResult = mockedContext.render(
|
||||
<ConsoleManagerTestComponent
|
||||
registerConsoleProps={() => {
|
||||
|
|
|
@ -16,13 +16,13 @@ import { getEndpointResponseActionsConsoleCommands } from '../endpoint_response_
|
|||
import { enterConsoleCommand } from '../../console/mocks';
|
||||
import { waitFor } from '@testing-library/react';
|
||||
import { responseActionsHttpMocks } from '../../../mocks/response_actions_http_mocks';
|
||||
import type { ResponderCapabilities } from '../../../../../common/endpoint/constants';
|
||||
import { RESPONDER_CAPABILITIES } from '../../../../../common/endpoint/constants';
|
||||
import { getDeferred } from '../../../mocks/utils';
|
||||
import type { EndpointCapabilities } from '../../../../../common/endpoint/service/response_actions/constants';
|
||||
import { ENDPOINT_CAPABILITIES } from '../../../../../common/endpoint/service/response_actions/constants';
|
||||
|
||||
describe('When using the release action from response actions console', () => {
|
||||
let render: (
|
||||
capabilities?: ResponderCapabilities[]
|
||||
capabilities?: EndpointCapabilities[]
|
||||
) => Promise<ReturnType<AppContextTestRender['render']>>;
|
||||
let renderResult: ReturnType<AppContextTestRender['render']>;
|
||||
let apiMocks: ReturnType<typeof responseActionsHttpMocks>;
|
||||
|
@ -35,7 +35,7 @@ describe('When using the release action from response actions console', () => {
|
|||
|
||||
apiMocks = responseActionsHttpMocks(mockedContext.coreStart.http);
|
||||
|
||||
render = async (capabilities: ResponderCapabilities[] = [...RESPONDER_CAPABILITIES]) => {
|
||||
render = async (capabilities: EndpointCapabilities[] = [...ENDPOINT_CAPABILITIES]) => {
|
||||
renderResult = mockedContext.render(
|
||||
<ConsoleManagerTestComponent
|
||||
registerConsoleProps={() => {
|
||||
|
|
|
@ -16,12 +16,12 @@ import { getEndpointResponseActionsConsoleCommands } from '../endpoint_response_
|
|||
import { enterConsoleCommand } from '../../console/mocks';
|
||||
import { waitFor } from '@testing-library/react';
|
||||
import { responseActionsHttpMocks } from '../../../mocks/response_actions_http_mocks';
|
||||
import type { ResponderCapabilities } from '../../../../../common/endpoint/constants';
|
||||
import { RESPONDER_CAPABILITIES } from '../../../../../common/endpoint/constants';
|
||||
import type { EndpointCapabilities } from '../../../../../common/endpoint/service/response_actions/constants';
|
||||
import { ENDPOINT_CAPABILITIES } from '../../../../../common/endpoint/service/response_actions/constants';
|
||||
|
||||
describe('When using the suspend-process action from response actions console', () => {
|
||||
let render: (
|
||||
capabilities?: ResponderCapabilities[]
|
||||
capabilities?: EndpointCapabilities[]
|
||||
) => Promise<ReturnType<AppContextTestRender['render']>>;
|
||||
let renderResult: ReturnType<AppContextTestRender['render']>;
|
||||
let apiMocks: ReturnType<typeof responseActionsHttpMocks>;
|
||||
|
@ -34,7 +34,7 @@ describe('When using the suspend-process action from response actions console',
|
|||
|
||||
apiMocks = responseActionsHttpMocks(mockedContext.coreStart.http);
|
||||
|
||||
render = async (capabilities: ResponderCapabilities[] = [...RESPONDER_CAPABILITIES]) => {
|
||||
render = async (capabilities: EndpointCapabilities[] = [...ENDPOINT_CAPABILITIES]) => {
|
||||
renderResult = mockedContext.render(
|
||||
<ConsoleManagerTestComponent
|
||||
registerConsoleProps={() => {
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
import { orderBy } from 'lodash/fp';
|
||||
import React, { memo, useEffect, useMemo, useState, useCallback, useRef } from 'react';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiSelectable, EuiPopoverTitle } from '@elastic/eui';
|
||||
import type { ResponseActions } from '../../../../../common/endpoint/service/response_actions/constants';
|
||||
import type { ResponseActionsApiCommandNames } from '../../../../../common/endpoint/service/response_actions/constants';
|
||||
import { ActionsLogFilterPopover } from './actions_log_filter_popover';
|
||||
import { type FilterItems, type FilterName, useActionsLogFilter, getUiCommand } from './hooks';
|
||||
import { ClearAllButton } from './clear_all_button';
|
||||
|
@ -105,7 +105,9 @@ export const ActionsLogFilter = memo(
|
|||
// update URL params
|
||||
if (filterName === 'actions') {
|
||||
setUrlActionsFilters(
|
||||
selectedItems.map((item) => getUiCommand(item as ResponseActions)).join()
|
||||
selectedItems
|
||||
.map((item) => getUiCommand(item as ResponseActionsApiCommandNames))
|
||||
.join()
|
||||
);
|
||||
} else if (filterName === 'hosts') {
|
||||
setUrlHostsFilters(selectedItems.join());
|
||||
|
|
|
@ -11,11 +11,12 @@ import type {
|
|||
OnRefreshChangeProps,
|
||||
} from '@elastic/eui/src/components/date_picker/types';
|
||||
import type {
|
||||
ResponseActions,
|
||||
ConsoleResponseActionCommands,
|
||||
ResponseActionsApiCommandNames,
|
||||
ResponseActionStatus,
|
||||
} from '../../../../../common/endpoint/service/response_actions/constants';
|
||||
import {
|
||||
RESPONSE_ACTION_COMMANDS,
|
||||
RESPONSE_ACTION_API_COMMANDS_NAMES,
|
||||
RESPONSE_ACTION_STATUS,
|
||||
} from '../../../../../common/endpoint/service/response_actions/constants';
|
||||
import type { DateRangePickerValues } from './actions_log_date_range_picker';
|
||||
|
@ -131,8 +132,8 @@ export const getActionStatus = (status: ResponseActionStatus): string => {
|
|||
* running-processes -> processes
|
||||
*/
|
||||
export const getUiCommand = (
|
||||
command: ResponseActions
|
||||
): Exclude<ResponseActions, 'unisolate' | 'running-processes'> | 'release' | 'processes' => {
|
||||
command: ResponseActionsApiCommandNames
|
||||
): ConsoleResponseActionCommands => {
|
||||
if (command === 'unisolate') {
|
||||
return 'release';
|
||||
} else if (command === 'running-processes') {
|
||||
|
@ -148,8 +149,8 @@ export const getUiCommand = (
|
|||
* processes -> running-processes
|
||||
*/
|
||||
export const getCommandKey = (
|
||||
uiCommand: Exclude<ResponseActions, 'unisolate' | 'running-processes'> | 'release' | 'processes'
|
||||
): ResponseActions => {
|
||||
uiCommand: ConsoleResponseActionCommands
|
||||
): ResponseActionsApiCommandNames => {
|
||||
if (uiCommand === 'release') {
|
||||
return 'unisolate';
|
||||
} else if (uiCommand === 'processes') {
|
||||
|
@ -231,7 +232,7 @@ export const useActionsLogFilter = ({
|
|||
}))
|
||||
: isHostsFilter
|
||||
? []
|
||||
: RESPONSE_ACTION_COMMANDS.map((commandName) => ({
|
||||
: RESPONSE_ACTION_API_COMMANDS_NAMES.map((commandName) => ({
|
||||
key: commandName,
|
||||
label: getUiCommand(commandName),
|
||||
checked:
|
||||
|
|
|
@ -5,8 +5,18 @@
|
|||
* 2.0.
|
||||
*/
|
||||
import { actionsLogFiltersFromUrlParams } from './use_action_history_url_params';
|
||||
import type { ConsoleResponseActionCommands } from '../../../../../common/endpoint/service/response_actions/constants';
|
||||
import { CONSOLE_RESPONSE_ACTION_COMMANDS } from '../../../../../common/endpoint/service/response_actions/constants';
|
||||
|
||||
describe('#actionsLogFiltersFromUrlParams', () => {
|
||||
const getConsoleCommandsAsString = (): string => {
|
||||
return [...CONSOLE_RESPONSE_ACTION_COMMANDS].sort().join(',');
|
||||
};
|
||||
|
||||
const getConsoleCommandsAsArray = (): ConsoleResponseActionCommands[] => {
|
||||
return [...CONSOLE_RESPONSE_ACTION_COMMANDS].sort();
|
||||
};
|
||||
|
||||
it('should not use invalid command values from URL params', () => {
|
||||
expect(actionsLogFiltersFromUrlParams({ commands: 'asa,was' })).toEqual({
|
||||
commands: undefined,
|
||||
|
@ -21,10 +31,10 @@ describe('#actionsLogFiltersFromUrlParams', () => {
|
|||
it('should use valid command values from URL params', () => {
|
||||
expect(
|
||||
actionsLogFiltersFromUrlParams({
|
||||
commands: 'kill-process,isolate,processes,release,suspend-process',
|
||||
commands: getConsoleCommandsAsString(),
|
||||
})
|
||||
).toEqual({
|
||||
commands: ['isolate', 'kill-process', 'processes', 'release', 'suspend-process'],
|
||||
commands: getConsoleCommandsAsArray(),
|
||||
endDate: undefined,
|
||||
hosts: undefined,
|
||||
startDate: undefined,
|
||||
|
@ -62,7 +72,7 @@ describe('#actionsLogFiltersFromUrlParams', () => {
|
|||
it('should use valid command and status along with given host, user and date values from URL params', () => {
|
||||
expect(
|
||||
actionsLogFiltersFromUrlParams({
|
||||
commands: 'release,kill-process,isolate,processes,suspend-process',
|
||||
commands: getConsoleCommandsAsString(),
|
||||
statuses: 'successful,pending,failed',
|
||||
hosts: 'host-1,host-2',
|
||||
users: 'user-1,user-2',
|
||||
|
@ -70,7 +80,7 @@ describe('#actionsLogFiltersFromUrlParams', () => {
|
|||
endDate: '2022-09-12T08:30:33.140Z',
|
||||
})
|
||||
).toEqual({
|
||||
commands: ['isolate', 'kill-process', 'processes', 'release', 'suspend-process'],
|
||||
commands: getConsoleCommandsAsArray(),
|
||||
endDate: '2022-09-12T08:30:33.140Z',
|
||||
hosts: ['host-1', 'host-2'],
|
||||
startDate: '2022-09-12T08:00:00.000Z',
|
||||
|
|
|
@ -6,10 +6,11 @@
|
|||
*/
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useHistory, useLocation } from 'react-router-dom';
|
||||
import type { ConsoleResponseActionCommands } from '../../../../../common/endpoint/service/response_actions/constants';
|
||||
import {
|
||||
RESPONSE_ACTION_COMMANDS,
|
||||
RESPONSE_ACTION_API_COMMANDS_NAMES,
|
||||
RESPONSE_ACTION_STATUS,
|
||||
type ResponseActions,
|
||||
type ResponseActionsApiCommandNames,
|
||||
type ResponseActionStatus,
|
||||
} from '../../../../../common/endpoint/service/response_actions/constants';
|
||||
import { useUrlParams } from '../../../hooks/use_url_params';
|
||||
|
@ -24,9 +25,7 @@ interface UrlParamsActionsLogFilters {
|
|||
}
|
||||
|
||||
interface ActionsLogFiltersFromUrlParams {
|
||||
commands?: Array<
|
||||
Exclude<ResponseActions, 'unisolate' | 'running-processes'> | 'release' | 'processes'
|
||||
>;
|
||||
commands?: ConsoleResponseActionCommands[];
|
||||
hosts?: string[];
|
||||
statuses?: ResponseActionStatus[];
|
||||
startDate?: string;
|
||||
|
@ -61,7 +60,7 @@ export const actionsLogFiltersFromUrlParams = (
|
|||
.split(',')
|
||||
.reduce<Required<ActionsLogFiltersFromUrlParams>['commands']>((acc, curr) => {
|
||||
if (
|
||||
RESPONSE_ACTION_COMMANDS.includes(curr as ResponseActions) ||
|
||||
RESPONSE_ACTION_API_COMMANDS_NAMES.includes(curr as ResponseActionsApiCommandNames) ||
|
||||
curr === 'release' ||
|
||||
curr === 'processes'
|
||||
) {
|
||||
|
|
|
@ -20,7 +20,7 @@ import { MANAGEMENT_PATH } from '../../../../common/constants';
|
|||
import { getActionListMock } from './mocks';
|
||||
import { useGetEndpointsList } from '../../hooks/endpoint/use_get_endpoints_list';
|
||||
import uuid from 'uuid';
|
||||
import { RESPONSE_ACTION_COMMANDS } from '../../../../common/endpoint/service/response_actions/constants';
|
||||
import { RESPONSE_ACTION_API_COMMANDS_NAMES } from '../../../../common/endpoint/service/response_actions/constants';
|
||||
|
||||
let mockUseGetEndpointActionList: {
|
||||
isFetched?: boolean;
|
||||
|
@ -557,7 +557,9 @@ describe('Response actions history', () => {
|
|||
userEvent.click(getByTestId(`${testPrefix}-${filterPrefix}-popoverButton`));
|
||||
const filterList = getByTestId(`${testPrefix}-${filterPrefix}-popoverList`);
|
||||
expect(filterList).toBeTruthy();
|
||||
expect(filterList.querySelectorAll('ul>li').length).toEqual(RESPONSE_ACTION_COMMANDS.length);
|
||||
expect(filterList.querySelectorAll('ul>li').length).toEqual(
|
||||
RESPONSE_ACTION_API_COMMANDS_NAMES.length
|
||||
);
|
||||
expect(
|
||||
Array.from(filterList.querySelectorAll('ul>li')).map((option) => option.textContent)
|
||||
).toEqual(['isolate', 'release', 'kill-process', 'suspend-process', 'processes', 'get-file']);
|
||||
|
|
|
@ -11,7 +11,7 @@ import type { CriteriaWithPagination } from '@elastic/eui';
|
|||
import React, { memo, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import type {
|
||||
ResponseActions,
|
||||
ResponseActionsApiCommandNames,
|
||||
ResponseActionStatus,
|
||||
} from '../../../../common/endpoint/service/response_actions/constants';
|
||||
|
||||
|
@ -142,7 +142,7 @@ export const ResponseActionsLog = memo<
|
|||
(selectedCommands: string[]) => {
|
||||
setQueryParams((prevState) => ({
|
||||
...prevState,
|
||||
commands: selectedCommands as ResponseActions[],
|
||||
commands: selectedCommands as ResponseActionsApiCommandNames[],
|
||||
}));
|
||||
},
|
||||
[setQueryParams]
|
||||
|
|
|
@ -50,11 +50,11 @@ import {
|
|||
HOST_METADATA_LIST_ROUTE,
|
||||
metadataTransformPrefix,
|
||||
METADATA_UNITED_TRANSFORM,
|
||||
RESPONDER_CAPABILITIES,
|
||||
} from '../../../../../common/endpoint/constants';
|
||||
import { useUserPrivileges } from '../../../../common/components/user_privileges';
|
||||
import { initialUserPrivilegesState as mockInitialUserPrivilegesState } from '../../../../common/components/user_privileges/user_privileges_context';
|
||||
import { getUserPrivilegesMockDefaultValue } from '../../../../common/components/user_privileges/__mocks__';
|
||||
import { ENDPOINT_CAPABILITIES } from '../../../../../common/endpoint/service/response_actions/constants';
|
||||
|
||||
// not sure why this can't be imported from '../../../../common/mock/formatted_relative';
|
||||
// but sure enough it needs to be inline in this one file
|
||||
|
@ -1019,7 +1019,7 @@ describe('when on the endpoint list page', () => {
|
|||
...hosts[0].metadata,
|
||||
Endpoint: {
|
||||
...hosts[0].metadata.Endpoint,
|
||||
capabilities: [...RESPONDER_CAPABILITIES],
|
||||
capabilities: [...ENDPOINT_CAPABILITIES],
|
||||
state: {
|
||||
...hosts[0].metadata.Endpoint.state,
|
||||
isolation: false,
|
||||
|
|
|
@ -19,7 +19,7 @@ import type { SecuritySolutionRequestHandlerContext } from '../../../types';
|
|||
import type { EndpointAppContext } from '../../types';
|
||||
import { errorHandler } from '../error_handler';
|
||||
import type {
|
||||
ResponseActions,
|
||||
ResponseActionsApiCommandNames,
|
||||
ResponseActionStatus,
|
||||
} from '../../../../common/endpoint/service/response_actions/constants';
|
||||
import { doesLogsEndpointActionsIndexExist } from '../../utils';
|
||||
|
@ -28,8 +28,8 @@ const formatStringIds = (value: string | string[] | undefined): undefined | stri
|
|||
typeof value === 'string' ? [value] : value;
|
||||
|
||||
const formatCommandValues = (
|
||||
value: ResponseActions | ResponseActions[] | undefined
|
||||
): undefined | ResponseActions[] => (typeof value === 'string' ? [value] : value);
|
||||
value: ResponseActionsApiCommandNames | ResponseActionsApiCommandNames[] | undefined
|
||||
): undefined | ResponseActionsApiCommandNames[] => (typeof value === 'string' ? [value] : value);
|
||||
|
||||
const formatStatusValues = (
|
||||
value: ResponseActionStatus | ResponseActionStatus[]
|
||||
|
|
|
@ -43,6 +43,7 @@ import {
|
|||
GET_PROCESSES_ROUTE,
|
||||
ISOLATE_HOST_ROUTE,
|
||||
UNISOLATE_HOST_ROUTE,
|
||||
GET_FILE_ROUTE,
|
||||
} from '../../../../common/endpoint/constants';
|
||||
import type {
|
||||
ActionDetails,
|
||||
|
@ -412,6 +413,17 @@ describe('Response actions', () => {
|
|||
expect(actionDoc.data.command).toEqual('running-processes');
|
||||
});
|
||||
|
||||
it('sends the get-file command payload from the get file route', async () => {
|
||||
const ctx = await callRoute(GET_FILE_ROUTE, {
|
||||
body: { endpoint_ids: ['XYZ'], parameters: { path: '/one/two/three' } },
|
||||
});
|
||||
const actionDoc: EndpointAction = (
|
||||
ctx.core.elasticsearch.client.asInternalUser.index.mock
|
||||
.calls[0][0] as estypes.IndexRequest<EndpointAction>
|
||||
).body!;
|
||||
expect(actionDoc.data.command).toEqual('get-file');
|
||||
});
|
||||
|
||||
describe('With endpoint data streams', () => {
|
||||
it('handles unisolation', async () => {
|
||||
const ctx = await callRoute(
|
||||
|
@ -553,6 +565,33 @@ describe('Response actions', () => {
|
|||
expect(responseBody.action).toBeUndefined();
|
||||
});
|
||||
|
||||
it('handles get-file', async () => {
|
||||
const ctx = await callRoute(
|
||||
GET_FILE_ROUTE,
|
||||
{
|
||||
body: { endpoint_ids: ['XYZ'], parameters: { path: '/one/two/three' } },
|
||||
},
|
||||
{ endpointDsExists: true }
|
||||
);
|
||||
const indexDoc = ctx.core.elasticsearch.client.asInternalUser.index;
|
||||
const actionDocs: [
|
||||
{ index: string; body?: LogsEndpointAction },
|
||||
{ index: string; body?: EndpointAction }
|
||||
] = [
|
||||
indexDoc.mock.calls[0][0] as estypes.IndexRequest<LogsEndpointAction>,
|
||||
indexDoc.mock.calls[1][0] as estypes.IndexRequest<EndpointAction>,
|
||||
];
|
||||
|
||||
expect(actionDocs[0].index).toEqual(ENDPOINT_ACTIONS_INDEX);
|
||||
expect(actionDocs[1].index).toEqual(AGENT_ACTIONS_INDEX);
|
||||
expect(actionDocs[0].body!.EndpointActions.data.command).toEqual('get-file');
|
||||
expect(actionDocs[1].body!.data.command).toEqual('get-file');
|
||||
|
||||
expect(mockResponse.ok).toBeCalled();
|
||||
const responseBody = mockResponse.ok.mock.calls[0][0]?.body as ResponseActionApiResponse;
|
||||
expect(responseBody.action).toBeUndefined();
|
||||
});
|
||||
|
||||
it('handles errors', async () => {
|
||||
const ErrMessage = 'Uh oh!';
|
||||
await callRoute(
|
||||
|
|
|
@ -18,6 +18,7 @@ import type { ResponseActionBodySchema } from '../../../../common/endpoint/schem
|
|||
import {
|
||||
NoParametersRequestSchema,
|
||||
KillOrSuspendProcessRequestSchema,
|
||||
EndpointActionGetFileSchema,
|
||||
} from '../../../../common/endpoint/schema/actions';
|
||||
import { APP_ID } from '../../../../common/constants';
|
||||
import {
|
||||
|
@ -32,6 +33,7 @@ import {
|
|||
ISOLATE_HOST_ROUTE,
|
||||
UNISOLATE_HOST_ROUTE,
|
||||
ENDPOINT_ACTIONS_INDEX,
|
||||
GET_FILE_ROUTE,
|
||||
} from '../../../../common/endpoint/constants';
|
||||
import type {
|
||||
EndpointAction,
|
||||
|
@ -42,7 +44,7 @@ import type {
|
|||
LogsEndpointActionResponse,
|
||||
ResponseActionParametersWithPidOrEntityId,
|
||||
} from '../../../../common/endpoint/types';
|
||||
import type { ResponseActions } from '../../../../common/endpoint/service/response_actions/constants';
|
||||
import type { ResponseActionsApiCommandNames } from '../../../../common/endpoint/service/response_actions/constants';
|
||||
import type {
|
||||
SecuritySolutionPluginRouter,
|
||||
SecuritySolutionRequestHandlerContext,
|
||||
|
@ -157,21 +159,35 @@ export function registerResponseActionRoutes(
|
|||
responseActionRequestHandler(endpointContext, 'running-processes')
|
||||
)
|
||||
);
|
||||
|
||||
router.post(
|
||||
{
|
||||
path: GET_FILE_ROUTE,
|
||||
validate: EndpointActionGetFileSchema,
|
||||
options: { authRequired: true, tags: ['access:securitySolution'] },
|
||||
},
|
||||
withEndpointAuthz(
|
||||
{ all: ['canWriteFileOperations'] },
|
||||
logger,
|
||||
responseActionRequestHandler(endpointContext, 'get-file')
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const commandToFeatureKeyMap = new Map<ResponseActions, FeatureKeys>([
|
||||
const commandToFeatureKeyMap = new Map<ResponseActionsApiCommandNames, FeatureKeys>([
|
||||
['isolate', 'HOST_ISOLATION'],
|
||||
['unisolate', 'HOST_ISOLATION'],
|
||||
['kill-process', 'KILL_PROCESS'],
|
||||
['suspend-process', 'SUSPEND_PROCESS'],
|
||||
['running-processes', 'RUNNING_PROCESSES'],
|
||||
['get-file', 'GET_FILE'],
|
||||
]);
|
||||
|
||||
const returnActionIdCommands: ResponseActions[] = ['isolate', 'unisolate'];
|
||||
const returnActionIdCommands: ResponseActionsApiCommandNames[] = ['isolate', 'unisolate'];
|
||||
|
||||
function responseActionRequestHandler<T extends EndpointActionDataParameterTypes>(
|
||||
endpointContext: EndpointAppContext,
|
||||
command: ResponseActions
|
||||
command: ResponseActionsApiCommandNames
|
||||
): RequestHandler<
|
||||
unknown,
|
||||
unknown,
|
||||
|
@ -233,8 +249,7 @@ function responseActionRequestHandler<T extends EndpointActionDataParameterTypes
|
|||
} as EndpointActionData<T>,
|
||||
} as Omit<EndpointAction, 'agents' | 'user_id' | '@timestamp'>,
|
||||
user: {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
id: user!.username,
|
||||
id: user ? user.username : 'unknown',
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import type { ElasticsearchClient } from '@kbn/core/server';
|
||||
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
import type { ResponseActions } from '../../../../common/endpoint/service/response_actions/constants';
|
||||
import type { ResponseActionsApiCommandNames } from '../../../../common/endpoint/service/response_actions/constants';
|
||||
import {
|
||||
ENDPOINT_ACTIONS_DS,
|
||||
ENDPOINT_ACTION_RESPONSES_DS,
|
||||
|
@ -55,7 +55,7 @@ interface NormalizedActionRequest {
|
|||
agents: string[];
|
||||
createdBy: string;
|
||||
createdAt: string;
|
||||
command: ResponseActions;
|
||||
command: ResponseActionsApiCommandNames;
|
||||
comment?: string;
|
||||
parameters?: EndpointActionDataParameterTypes;
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ const FEATURES = {
|
|||
KILL_PROCESS: 'Kill process',
|
||||
SUSPEND_PROCESS: 'Suspend process',
|
||||
RUNNING_PROCESSES: 'Get running processes',
|
||||
GET_FILE: 'Get file',
|
||||
ALERTS_BY_PROCESS_ANCESTRY: 'Get related alerts by process ancestry',
|
||||
} as const;
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue