[Security Solution][Endpoint] Fix the display of failed automated response actions (under the expanded alert details) so that it shows errors (#187094)

## Summary

- Fixes the UI for failed automated response actions so that it does
remain "stuck" in `pending` and instead shows the errors that were
encountered


> [!NOTE]
> This bug was knowingly introduced just earlier today, but the fix was
not yet know at that time. The underlying root cause will take much
longer to address (requires a refactor of how the automated response
actions are retrieved from the server), thus this PR provides a
**temporary** fix that ensures the UI continues to work as it did
before.
This commit is contained in:
Paul Tavares 2024-06-28 10:03:01 -04:00 committed by GitHub
parent e13e8ff321
commit 7ec1db2a46
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 116 additions and 92 deletions

View file

@ -67,6 +67,7 @@ export const EndpointResponseActionResults = ({
expandedAction ? (
<ActionsLogExpandedTray
action={expandedAction}
fromAlertWorkaround
data-test-subj={`response-results-${hostName}`}
/>
) : (

View file

@ -15,6 +15,7 @@ import {
} from '@elastic/eui';
import { css, euiStyled } from '@kbn/kibana-react-plugin/common';
import { reduce } from 'lodash';
import { i18n } from '@kbn/i18n';
import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features';
import { getAgentTypeName } from '../../../../common/translations';
import { RESPONSE_ACTION_API_COMMAND_TO_CONSOLE_COMMAND_MAP } from '../../../../../common/endpoint/service/response_actions/constants';
@ -89,102 +90,123 @@ const StyledEuiFlexGroup = euiStyled(EuiFlexGroup).attrs({
overflow-y: auto;
`;
const OutputContent = memo<{ action: MaybeImmutable<ActionDetails>; 'data-test-subj'?: string }>(
({ action, 'data-test-subj': dataTestSubj }) => {
const getTestId = useTestIdGenerator(dataTestSubj);
const OutputContent = memo<{
action: MaybeImmutable<ActionDetails>;
fromAlertWorkaround?: boolean;
'data-test-subj'?: string;
}>(({ action, fromAlertWorkaround = false, 'data-test-subj': dataTestSubj }) => {
const getTestId = useTestIdGenerator(dataTestSubj);
const {
canWriteFileOperations,
canReadActionsLogManagement,
canAccessEndpointActionsLogManagement,
} = useUserPrivileges().endpointPrivileges;
const {
canWriteFileOperations,
canReadActionsLogManagement,
canAccessEndpointActionsLogManagement,
} = useUserPrivileges().endpointPrivileges;
const { command: _command, isCompleted, isExpired, wasSuccessful } = action;
const command = RESPONSE_ACTION_API_COMMAND_TO_CONSOLE_COMMAND_MAP[_command];
const { command: _command, isCompleted, isExpired, wasSuccessful } = action;
const command = RESPONSE_ACTION_API_COMMAND_TO_CONSOLE_COMMAND_MAP[_command];
if (isExpired) {
return <>{OUTPUT_MESSAGES.hasExpired(command)}</>;
}
if (!isCompleted) {
return <>{OUTPUT_MESSAGES.isPending(command)}</>;
}
if (!wasSuccessful) {
return (
<>
{OUTPUT_MESSAGES.hasFailed(command)}
<EuiSpacer size="s" />
<EndpointActionFailureMessage
action={action}
data-test-subj={getTestId('failureMessage')}
/>
</>
);
}
if (isGetFileAction(action)) {
return (
<>
{OUTPUT_MESSAGES.wasSuccessful(command)}
<ResponseActionFileDownloadLink
action={action}
canAccessFileDownloadLink={canWriteFileOperations}
textSize="xs"
data-test-subj={getTestId('getFileDownloadLink')}
/>
</>
);
}
if (isExecuteAction(action)) {
return (
<EuiFlexGroup direction="column" data-test-subj={getTestId('executeDetails')}>
{action.agents.map((agentId) => (
<div key={agentId}>
{OUTPUT_MESSAGES.wasSuccessful(command)}
<ExecuteActionHostResponse
action={action}
agentId={agentId}
canAccessFileDownloadLink={
canAccessEndpointActionsLogManagement || canReadActionsLogManagement
}
textSize="xs"
data-test-subj={getTestId('actionsLogTray')}
/>
</div>
))}
</EuiFlexGroup>
);
}
if (isUploadAction(action)) {
return (
<EuiFlexGroup direction="column" data-test-subj={getTestId('uploadDetails')}>
<p>{OUTPUT_MESSAGES.wasSuccessful(command)}</p>
<EndpointUploadActionResult
action={action}
data-test-subj={getTestId('uploadOutput')}
textSize="xs"
/>
</EuiFlexGroup>
);
}
if (action.agentType === 'crowdstrike') {
return <>{OUTPUT_MESSAGES.submittedSuccessfully(command)}</>;
}
return <>{OUTPUT_MESSAGES.wasSuccessful(command)}</>;
// FIXME:PT remove once automated response actions are corrected to use `ActionDetails` (team issue 9822)
if (fromAlertWorkaround && action.errors?.length) {
return (
<>
{(
action.errors ?? [
i18n.translate('xpack.securitySolution.actionLogExpandedTray.missingErrors', {
defaultMessage: 'Action did not specify any errors',
}),
]
).map((error) => (
<EuiFlexItem>{error}</EuiFlexItem>
))}
</>
);
}
);
if (isExpired) {
return <>{OUTPUT_MESSAGES.hasExpired(command)}</>;
}
if (!isCompleted) {
return <>{OUTPUT_MESSAGES.isPending(command)}</>;
}
if (!wasSuccessful) {
return (
<>
{OUTPUT_MESSAGES.hasFailed(command)}
<EuiSpacer size="s" />
<EndpointActionFailureMessage
action={action}
data-test-subj={getTestId('failureMessage')}
/>
</>
);
}
if (isGetFileAction(action)) {
return (
<>
{OUTPUT_MESSAGES.wasSuccessful(command)}
<ResponseActionFileDownloadLink
action={action}
canAccessFileDownloadLink={canWriteFileOperations}
textSize="xs"
data-test-subj={getTestId('getFileDownloadLink')}
/>
</>
);
}
if (isExecuteAction(action)) {
return (
<EuiFlexGroup direction="column" data-test-subj={getTestId('executeDetails')}>
{action.agents.map((agentId) => (
<div key={agentId}>
{OUTPUT_MESSAGES.wasSuccessful(command)}
<ExecuteActionHostResponse
action={action}
agentId={agentId}
canAccessFileDownloadLink={
canAccessEndpointActionsLogManagement || canReadActionsLogManagement
}
textSize="xs"
data-test-subj={getTestId('actionsLogTray')}
/>
</div>
))}
</EuiFlexGroup>
);
}
if (isUploadAction(action)) {
return (
<EuiFlexGroup direction="column" data-test-subj={getTestId('uploadDetails')}>
<p>{OUTPUT_MESSAGES.wasSuccessful(command)}</p>
<EndpointUploadActionResult
action={action}
data-test-subj={getTestId('uploadOutput')}
textSize="xs"
/>
</EuiFlexGroup>
);
}
if (action.agentType === 'crowdstrike') {
return <>{OUTPUT_MESSAGES.submittedSuccessfully(command)}</>;
}
return <>{OUTPUT_MESSAGES.wasSuccessful(command)}</>;
});
OutputContent.displayName = 'OutputContent';
export const ActionsLogExpandedTray = memo<{
action: MaybeImmutable<ActionDetails>;
// Delete prop `fromAlert` once we refactor automated response actions
fromAlertWorkaround?: boolean;
'data-test-subj'?: string;
}>(({ action, 'data-test-subj': dataTestSubj }) => {
}>(({ action, fromAlertWorkaround = false, 'data-test-subj': dataTestSubj }) => {
const getTestId = useTestIdGenerator(dataTestSubj);
const isSentinelOneV1Enabled = useIsExperimentalFeatureEnabled(
@ -295,12 +317,16 @@ export const ActionsLogExpandedTray = memo<{
description: (
// codeblock for output
<StyledEuiCodeBlock data-test-subj={getTestId('details-tray-output')}>
<OutputContent action={action} data-test-subj={dataTestSubj} />
<OutputContent
action={action}
data-test-subj={dataTestSubj}
fromAlertWorkaround={fromAlertWorkaround}
/>
</StyledEuiCodeBlock>
),
},
],
[action, dataTestSubj, getTestId]
[action, dataTestSubj, fromAlertWorkaround, getTestId]
);
return (

View file

@ -20,10 +20,7 @@ import { createEndpointHost } from '../../tasks/create_endpoint_host';
import { deleteAllLoadedEndpointData } from '../../tasks/delete_all_endpoint_data';
import { enableAllPolicyProtections } from '../../tasks/endpoint_policy';
// 8.15.0
// TODO: Re-enable when action requests history can be filtered by alert ids
// security-team issue #9822
describe.skip(
describe(
'Automated Response Actions',
{
tags: ['@ess', '@serverless'],