mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Security Solution] Enable endpoint actions for events (#206857)
## Summary This PR enabled endpoint actions for generic events (in addition to alerts). We want to allow users to perform endpoint related actions like isolate host and respond in the flyout. Main use case is to perform endpoint actions when investigating in analyzer. **Before**  **After** Enabled for events when host uses elastic defend  Disabled when host cannot be isolated  ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [x] The PR description includes the appropriate Release Notes section, and the correct `release_note:*` label is applied per the [guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)
This commit is contained in:
parent
448a0364d1
commit
c329ccf87b
11 changed files with 19 additions and 43 deletions
|
@ -41512,7 +41512,6 @@
|
|||
"xpack.securitySolution.uncommonProcessTable.rows": "{numRows} {numRows, plural, =0 {ligne} =1 {ligne} other {lignes}}",
|
||||
"xpack.securitySolution.uncommonProcessTable.unit": "{totalCount, plural, other {processus}}",
|
||||
"xpack.securitySolution.useAlertResponseActionsSupport.missingAgentIdField": "Alerte, les données d'événement ne comportent pas le champ d'identificateur d'agent {agentTypeName} ({missingField}",
|
||||
"xpack.securitySolution.useAlertResponseActionsSupport.notAnAlert": "Les actions de réponse ne sont prises en charge que pour les alertes (et non pour les événements)",
|
||||
"xpack.securitySolution.useConsoleActionSubmitter.actionRequestFailure": "Échec de la création d'une requête d'action.",
|
||||
"xpack.securitySolution.useInputHints.exampleInstructions": "Ex : [ {exampleUsage} ]",
|
||||
"xpack.securitySolution.useInputHints.noArguments": "Appuyez sur Entrée pour exécuter",
|
||||
|
|
|
@ -41487,7 +41487,6 @@
|
|||
"xpack.securitySolution.uncommonProcessTable.rows": "{numRows} {numRows, plural, other {行}}",
|
||||
"xpack.securitySolution.uncommonProcessTable.unit": "{totalCount, plural, other {プロセス}}",
|
||||
"xpack.securitySolution.useAlertResponseActionsSupport.missingAgentIdField": "{agentTypeName}エージェントIDフィールド({missingField})が見つからないイベントデータを警告",
|
||||
"xpack.securitySolution.useAlertResponseActionsSupport.notAnAlert": "対応アクションはアラートでのみサポートされています(イベントはサポートされていません)",
|
||||
"xpack.securitySolution.useConsoleActionSubmitter.actionRequestFailure": "アクションリクエストを作成できませんでした。",
|
||||
"xpack.securitySolution.useInputHints.exampleInstructions": "例:[ {exampleUsage} ]",
|
||||
"xpack.securitySolution.useInputHints.noArguments": "Enterを押すと実行します",
|
||||
|
|
|
@ -41550,7 +41550,6 @@
|
|||
"xpack.securitySolution.uncommonProcessTable.rows": "{numRows} {numRows, plural, other {行}}",
|
||||
"xpack.securitySolution.uncommonProcessTable.unit": "{totalCount, plural, other {个进程}}",
|
||||
"xpack.securitySolution.useAlertResponseActionsSupport.missingAgentIdField": "告警事件数据缺少 {agentTypeName} 代理标识符字段 ({missingField})",
|
||||
"xpack.securitySolution.useAlertResponseActionsSupport.notAnAlert": "仅告警(而不是事件)支持响应操作",
|
||||
"xpack.securitySolution.useConsoleActionSubmitter.actionRequestFailure": "无法创建操作请求。",
|
||||
"xpack.securitySolution.useInputHints.exampleInstructions": "例如:[ {exampleUsage} ]",
|
||||
"xpack.securitySolution.useInputHints.noArguments": "按 Enter 键以执行",
|
||||
|
|
|
@ -108,13 +108,13 @@ describe('useHostIsolationAction', () => {
|
|||
expect(hookProps.closePopover).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should NOT return the menu item for Events', () => {
|
||||
it('should return the menu item for Events', () => {
|
||||
hookProps.detailsData = endpointAlertDataMock.generateAlertDetailsItemDataForAgentType('foo', {
|
||||
'kibana.alert.rule.uuid': undefined,
|
||||
});
|
||||
const { result } = render();
|
||||
|
||||
expect(result.current).toHaveLength(0);
|
||||
expect(result.current).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('should NOT return menu item if user does not have authz', async () => {
|
||||
|
|
|
@ -25,6 +25,8 @@ export interface UseHostIsolationActionProps {
|
|||
onAddIsolationStatusClick: (action: 'isolateHost' | 'unisolateHost') => void;
|
||||
}
|
||||
|
||||
const emptyArray: AlertTableContextMenuItem[] = [];
|
||||
|
||||
export const useHostIsolationAction = ({
|
||||
closePopover,
|
||||
detailsData,
|
||||
|
@ -33,7 +35,6 @@ export const useHostIsolationAction = ({
|
|||
}: UseHostIsolationActionProps): AlertTableContextMenuItem[] => {
|
||||
const {
|
||||
isSupported: hostSupportsResponseActions,
|
||||
isAlert,
|
||||
unsupportedReason,
|
||||
details: {
|
||||
agentType,
|
||||
|
@ -76,9 +77,9 @@ export const useHostIsolationAction = ({
|
|||
}, [hostSupportsResponseActions, agentStatus]);
|
||||
|
||||
return useMemo<AlertTableContextMenuItem[]>(() => {
|
||||
// If not an Alert OR user has no Authz, then don't show the menu item at all
|
||||
if (!isAlert || (isHostIsolated && !canUnIsolateHost) || !canIsolateHost) {
|
||||
return [];
|
||||
// If user has no Authz, then don't show the menu item at all
|
||||
if ((isHostIsolated && !canUnIsolateHost) || !canIsolateHost) {
|
||||
return emptyArray;
|
||||
}
|
||||
|
||||
const menuItem: AlertTableContextMenuItem = {
|
||||
|
@ -109,7 +110,6 @@ export const useHostIsolationAction = ({
|
|||
|
||||
return [menuItem];
|
||||
}, [
|
||||
isAlert,
|
||||
isHostIsolated,
|
||||
canUnIsolateHost,
|
||||
canIsolateHost,
|
||||
|
|
|
@ -51,11 +51,11 @@ describe('useResponderActionItem', () => {
|
|||
expect(renderHook().result.current).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should NOT return the Respond action menu item for Events', () => {
|
||||
it('should return the Respond action menu item for Events', () => {
|
||||
alertDetailItemData = endpointAlertDataMock.generateAlertDetailsItemDataForAgentType('foo', {
|
||||
'kibana.alert.rule.uuid': undefined,
|
||||
});
|
||||
|
||||
expect(renderHook().result.current).toHaveLength(0);
|
||||
expect(renderHook().result.current).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
import React, { useMemo } from 'react';
|
||||
import type { TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { useAlertResponseActionsSupport } from '../../../../hooks/endpoint/use_alert_response_actions_support';
|
||||
import { useUserPrivileges } from '../../../user_privileges';
|
||||
import type { AlertTableContextMenuItem } from '../../../../../detections/components/alerts_table/types';
|
||||
import { useWithResponderActionDataFromAlert } from './use_responder_action_data';
|
||||
|
@ -19,7 +18,6 @@ export const useResponderActionItem = (
|
|||
): AlertTableContextMenuItem[] => {
|
||||
const { loading: isAuthzLoading, canAccessResponseConsole } =
|
||||
useUserPrivileges().endpointPrivileges;
|
||||
const { isAlert } = useAlertResponseActionsSupport(eventDetailsData);
|
||||
const { handleResponseActionsClick, isDisabled, tooltip } = useWithResponderActionDataFromAlert({
|
||||
onClick,
|
||||
eventData: eventDetailsData,
|
||||
|
@ -28,7 +26,7 @@ export const useResponderActionItem = (
|
|||
return useMemo(() => {
|
||||
const actions: AlertTableContextMenuItem[] = [];
|
||||
|
||||
if (!isAuthzLoading && canAccessResponseConsole && isAlert) {
|
||||
if (!isAuthzLoading && canAccessResponseConsole) {
|
||||
actions.push({
|
||||
key: 'endpointResponseActions-action-item',
|
||||
'data-test-subj': 'endpointResponseActions-action-item',
|
||||
|
@ -46,12 +44,5 @@ export const useResponderActionItem = (
|
|||
}
|
||||
|
||||
return actions;
|
||||
}, [
|
||||
canAccessResponseConsole,
|
||||
handleResponseActionsClick,
|
||||
isAlert,
|
||||
isAuthzLoading,
|
||||
isDisabled,
|
||||
tooltip,
|
||||
]);
|
||||
}, [canAccessResponseConsole, handleResponseActionsClick, isAuthzLoading, isDisabled, tooltip]);
|
||||
};
|
||||
|
|
|
@ -17,7 +17,6 @@ import {
|
|||
import type { AlertResponseActionsSupport } from './use_alert_response_actions_support';
|
||||
import {
|
||||
ALERT_EVENT_DATA_MISSING_AGENT_ID_FIELD,
|
||||
RESPONSE_ACTIONS_ONLY_SUPPORTED_ON_ALERTS,
|
||||
useAlertResponseActionsSupport,
|
||||
} from './use_alert_response_actions_support';
|
||||
import { isAgentTypeAndActionSupported } from '../../lib/endpoint';
|
||||
|
@ -96,7 +95,6 @@ describe('When using `useAlertResponseActionsSupport()` hook', () => {
|
|||
{
|
||||
isAlert: false,
|
||||
isSupported: false,
|
||||
unsupportedReason: RESPONSE_ACTIONS_ONLY_SUPPORTED_ON_ALERTS,
|
||||
details: {
|
||||
agentId: '',
|
||||
agentIdField: '',
|
||||
|
@ -109,7 +107,7 @@ describe('When using `useAlertResponseActionsSupport()` hook', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it('should set `isSupported` to `false` for if not an Alert', () => {
|
||||
it('should set `isSupported` to `true` for if it is not an Alert but supported', () => {
|
||||
alertDetailItemData = endpointAlertDataMock.generateAlertDetailsItemDataForAgentType(
|
||||
'sentinel_one',
|
||||
{ 'kibana.alert.rule.uuid': undefined }
|
||||
|
@ -118,8 +116,7 @@ describe('When using `useAlertResponseActionsSupport()` hook', () => {
|
|||
expect(renderHook().result.current).toEqual(
|
||||
getExpectedResult({
|
||||
isAlert: false,
|
||||
isSupported: false,
|
||||
unsupportedReason: RESPONSE_ACTIONS_ONLY_SUPPORTED_ON_ALERTS,
|
||||
isSupported: true,
|
||||
details: {
|
||||
agentType: 'sentinel_one',
|
||||
agentIdField: RESPONSE_ACTIONS_ALERT_AGENT_ID_FIELDS.sentinel_one[0],
|
||||
|
|
|
@ -37,11 +37,6 @@ export const ALERT_EVENT_DATA_MISSING_AGENT_ID_FIELD = (
|
|||
);
|
||||
};
|
||||
|
||||
export const RESPONSE_ACTIONS_ONLY_SUPPORTED_ON_ALERTS = i18n.translate(
|
||||
'xpack.securitySolution.useAlertResponseActionsSupport.notAnAlert',
|
||||
{ defaultMessage: 'Response actions are only supported for Alerts (not events)' }
|
||||
);
|
||||
|
||||
export interface AlertResponseActionsSupport {
|
||||
/** Does the host/agent for the given alert have support for response actions */
|
||||
isSupported: boolean;
|
||||
|
@ -135,8 +130,8 @@ export const useAlertResponseActionsSupport = (
|
|||
}, [agentType, eventData]);
|
||||
|
||||
const doesHostSupportResponseActions = useMemo(() => {
|
||||
return Boolean(isFeatureEnabled && isAlert && agentId && agentType);
|
||||
}, [agentId, agentType, isAlert, isFeatureEnabled]);
|
||||
return Boolean(isFeatureEnabled && agentId && agentType);
|
||||
}, [agentId, agentType, isFeatureEnabled]);
|
||||
|
||||
const supportedActions = useMemo(() => {
|
||||
return RESPONSE_ACTION_API_COMMANDS_NAMES.reduce<AlertAgentActionsSupported>(
|
||||
|
@ -167,10 +162,6 @@ export const useAlertResponseActionsSupport = (
|
|||
|
||||
const unsupportedReason = useMemo(() => {
|
||||
if (!doesHostSupportResponseActions) {
|
||||
if (!isAlert) {
|
||||
return RESPONSE_ACTIONS_ONLY_SUPPORTED_ON_ALERTS;
|
||||
}
|
||||
|
||||
if (!agentType) {
|
||||
// No message is provided for this condition because the
|
||||
// return from this hook will always default to `endpoint`
|
||||
|
@ -181,7 +172,7 @@ export const useAlertResponseActionsSupport = (
|
|||
return ALERT_EVENT_DATA_MISSING_AGENT_ID_FIELD(getAgentTypeName(agentType), agentIdField);
|
||||
}
|
||||
}
|
||||
}, [agentId, agentIdField, agentType, doesHostSupportResponseActions, isAlert]);
|
||||
}, [agentId, agentIdField, agentType, doesHostSupportResponseActions]);
|
||||
|
||||
return useMemo<AlertResponseActionsSupport>(() => {
|
||||
return {
|
||||
|
|
|
@ -60,7 +60,7 @@ export const PanelContent: FC = () => {
|
|||
export const IsolateHostPanelContent: FC<{
|
||||
isIsolateActionSuccessBannerVisible: boolean;
|
||||
hostName: string;
|
||||
alertId: string;
|
||||
alertId?: string;
|
||||
isolateAction: 'isolateHost' | 'unisolateHost';
|
||||
dataFormattedForFieldBrowser: TimelineEventsDetailsItem[];
|
||||
showAlertDetails: () => void;
|
||||
|
|
|
@ -203,7 +203,7 @@ export const TakeActionButton: FC = () => {
|
|||
/>
|
||||
)}
|
||||
|
||||
{isHostIsolationPanelOpen && alertId && (
|
||||
{isHostIsolationPanelOpen && (
|
||||
// EUI TODO: This z-index override of EuiOverlayMask is a workaround, and ideally should be resolved with a cleaner UI/UX flow long-term
|
||||
<EuiFlyout onClose={showAlertDetails} size="m" maskProps={flyoutZIndex}>
|
||||
<IsolateHostPanelHeader
|
||||
|
@ -213,7 +213,7 @@ export const TakeActionButton: FC = () => {
|
|||
<IsolateHostPanelContent
|
||||
isIsolateActionSuccessBannerVisible={isIsolateActionSuccessBannerVisible}
|
||||
hostName={hostName}
|
||||
alertId={alertId}
|
||||
alertId={alertId ?? undefined}
|
||||
isolateAction={isolateAction}
|
||||
dataFormattedForFieldBrowser={dataFormattedForFieldBrowser}
|
||||
showAlertDetails={showAlertDetails}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue