From a78c5221d99c7794c8dc926d47fd0fde5bcf3f80 Mon Sep 17 00:00:00 2001 From: Paul Tavares <56442535+paul-tavares@users.noreply.github.com> Date: Thu, 11 Apr 2024 10:59:26 -0400 Subject: [PATCH] [Security Solution][Endpoint] Fix Response Console `status` command for SentinelOne and error output for commands that hidden from `help` (#180529) ## Summary Fixes: - Disable the `status` command for SentinelOne hosts (not supported) - For console commands that are hidden from Help, if a user attempts to still enter them in the console, the usage information should NOT be displayed following the error message. --- .../console/components/bad_argument.test.tsx | 57 +++++++++++++++++++ .../console/components/bad_argument.tsx | 39 +++++++------ .../handle_execute_command.tsx | 4 +- .../components/validation_error.test.tsx | 50 ++++++++++++++++ .../console/components/validation_error.tsx | 39 +++++++------ .../hooks/use_with_show_responder.tsx | 12 +++- .../endpoint/sentinelone_host/index.ts | 1 + 7 files changed, 166 insertions(+), 36 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/management/components/console/components/bad_argument.test.tsx create mode 100644 x-pack/plugins/security_solution/public/management/components/console/components/validation_error.test.tsx diff --git a/x-pack/plugins/security_solution/public/management/components/console/components/bad_argument.test.tsx b/x-pack/plugins/security_solution/public/management/components/console/components/bad_argument.test.tsx new file mode 100644 index 0000000000000..7a1cd8e9d5afb --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/components/console/components/bad_argument.test.tsx @@ -0,0 +1,57 @@ +/* + * 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 { CommandDefinition, ConsoleProps } from '..'; +import type { AppContextTestRender } from '../../../../common/mock/endpoint'; +import type { ConsoleTestSetup } from '../mocks'; +import { getConsoleTestSetup } from '../mocks'; + +describe('BadArgument component', () => { + let render: (props?: Partial) => ReturnType; + let renderResult: ReturnType; + let command: CommandDefinition; + let enterCommand: ConsoleTestSetup['enterCommand']; + + beforeEach(() => { + const testSetup = getConsoleTestSetup(); + let commands: CommandDefinition[]; + + ({ commands, enterCommand } = testSetup); + command = commands[0]; + command.args = { + foo: { + about: 'foo', + required: true, + mustHaveValue: 'number-greater-than-zero', + allowMultiples: false, + }, + }; + + render = (props = {}) => (renderResult = testSetup.renderConsole(props)); + }); + + it('should display message and help output if command is not hidden from help', () => { + render(); + enterCommand('cmd1 --foo'); + + expect(renderResult.getByTestId('test-badArgument-message').textContent).toEqual( + 'Argument --foo must have a value' + ); + expect(renderResult.getByTestId('test-badArgument-commandUsage')); + }); + + it('should only display message (no help) if command is hidden from help', () => { + command.helpHidden = true; + render(); + enterCommand('cmd1 --foo'); + + expect(renderResult.getByTestId('test-badArgument-message').textContent).toEqual( + 'Argument --foo must have a value' + ); + expect(renderResult.queryByTestId('test-badArgument-commandUsage')).toBeNull(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/management/components/console/components/bad_argument.tsx b/x-pack/plugins/security_solution/public/management/components/console/components/bad_argument.tsx index 32d66bada9a9d..1c75e4e9d3d95 100644 --- a/x-pack/plugins/security_solution/public/management/components/console/components/bad_argument.tsx +++ b/x-pack/plugins/security_solution/public/management/components/console/components/bad_argument.tsx @@ -41,23 +41,28 @@ export const BadArgument = memo
{store.errorMessage}
- - - - - {`${command.commandDefinition.name} --help`} - ), - }} - /> - + + {!command.commandDefinition.helpHidden && ( +
+ + + + + {`${command.commandDefinition.name} --help`} + ), + }} + /> + +
+ )} ); } diff --git a/x-pack/plugins/security_solution/public/management/components/console/components/console_state/state_update_handlers/handle_execute_command.tsx b/x-pack/plugins/security_solution/public/management/components/console/components/console_state/state_update_handlers/handle_execute_command.tsx index 75e7facf7a801..589028c4f9447 100644 --- a/x-pack/plugins/security_solution/public/management/components/console/components/console_state/state_update_handlers/handle_execute_command.tsx +++ b/x-pack/plugins/security_solution/public/management/components/console/components/console_state/state_update_handlers/handle_execute_command.tsx @@ -351,7 +351,9 @@ export const handleExecuteCommand: ConsoleStoreReducer< case 'number': case 'number-greater-than-zero': - { + if (typeof argValue === 'boolean') { + dataValidationError = executionTranslations.mustHaveValue(argName); + } else { const valueNumber = Number(argValue); if (!Number.isSafeInteger(valueNumber)) { diff --git a/x-pack/plugins/security_solution/public/management/components/console/components/validation_error.test.tsx b/x-pack/plugins/security_solution/public/management/components/console/components/validation_error.test.tsx new file mode 100644 index 0000000000000..ccabc2d163068 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/components/console/components/validation_error.test.tsx @@ -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 type { CommandDefinition, ConsoleProps } from '..'; +import type { AppContextTestRender } from '../../../../common/mock/endpoint'; +import type { ConsoleTestSetup } from '../mocks'; +import { getConsoleTestSetup } from '../mocks'; + +describe('ValidationError component', () => { + let render: (props?: Partial) => ReturnType; + let renderResult: ReturnType; + let command: CommandDefinition; + let enterCommand: ConsoleTestSetup['enterCommand']; + + beforeEach(() => { + const testSetup = getConsoleTestSetup(); + let commands: CommandDefinition[]; + + ({ commands, enterCommand } = testSetup); + command = commands[0]; + command.validate = () => 'this command is not active'; + + render = (props = {}) => (renderResult = testSetup.renderConsole(props)); + }); + + it('should display message and help output if command is not hidden from help', () => { + render(); + enterCommand('cmd1'); + + expect(renderResult.getByTestId('test-validationError-message').textContent).toEqual( + 'this command is not active' + ); + expect(renderResult.getByTestId('test-validationError-commandUsage')); + }); + + it('should only display message (no help) if command is hidden from help', () => { + command.helpHidden = true; + render(); + enterCommand('cmd1'); + + expect(renderResult.getByTestId('test-validationError-message').textContent).toEqual( + 'this command is not active' + ); + expect(renderResult.queryByTestId('test-validationError-commandUsage')).toBeNull(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/management/components/console/components/validation_error.tsx b/x-pack/plugins/security_solution/public/management/components/console/components/validation_error.tsx index 2b372fd6c8f55..b7dee3f613f3b 100644 --- a/x-pack/plugins/security_solution/public/management/components/console/components/validation_error.tsx +++ b/x-pack/plugins/security_solution/public/management/components/console/components/validation_error.tsx @@ -42,23 +42,28 @@ export const ValidationError = memo< data-test-subj={getTestId('validationError')} >
{store.errorMessage}
- - - - - {`${command.commandDefinition.name} --help`} - ), - }} - /> - + + {!command.commandDefinition.helpHidden && ( +
+ + + + + {`${command.commandDefinition.name} --help`} + ), + }} + /> + +
+ )} ); }); diff --git a/x-pack/plugins/security_solution/public/management/hooks/use_with_show_responder.tsx b/x-pack/plugins/security_solution/public/management/hooks/use_with_show_responder.tsx index 2c2966f065858..c36b02d90ccf0 100644 --- a/x-pack/plugins/security_solution/public/management/hooks/use_with_show_responder.tsx +++ b/x-pack/plugins/security_solution/public/management/hooks/use_with_show_responder.tsx @@ -7,7 +7,11 @@ import React, { useCallback } from 'react'; import { EuiBetaBadge, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { TECHNICAL_PREVIEW, TECHNICAL_PREVIEW_TOOLTIP } from '../../common/translations'; +import { + TECHNICAL_PREVIEW, + TECHNICAL_PREVIEW_TOOLTIP, + UPGRADE_AGENT_FOR_RESPONDER, +} from '../../common/translations'; import { useLicense } from '../../common/hooks/use_license'; import type { ImmutableArray } from '../../../common/endpoint/types'; import { @@ -99,6 +103,12 @@ export const useWithShowResponder = (): ShowResponseActionsConsole => { return { ...command, helpHidden: true, + validate: () => { + return UPGRADE_AGENT_FOR_RESPONDER( + agentType, + command.name as ConsoleResponseActionCommands + ); + }, }; } return command; diff --git a/x-pack/plugins/security_solution/scripts/endpoint/sentinelone_host/index.ts b/x-pack/plugins/security_solution/scripts/endpoint/sentinelone_host/index.ts index 1d59f4d9cfef0..d2f4190821413 100644 --- a/x-pack/plugins/security_solution/scripts/endpoint/sentinelone_host/index.ts +++ b/x-pack/plugins/security_solution/scripts/endpoint/sentinelone_host/index.ts @@ -205,6 +205,7 @@ const runCli: RunFn = async ({ log, flags }) => { ]); // Trigger an alert on the SentinelOn host so that we get an alert back in Kibana + log.info(`Triggering SentinelOne alert`); await s1HostVm.exec('nslookup elastic.co'); log.info(`Done!