mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Security Solution][Endpoint][Sentinel One] Gate calls to /agent_status
on endpoint
agent (#180600)
## Summary Gates call to internal `agent_status` API call that supports only `sentinel_one` to be called on `endpoint` agent responder. ### Checklist Delete any items that are not applicable to this PR. - [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 - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed
This commit is contained in:
parent
8162230b1c
commit
3ad523d2a1
6 changed files with 123 additions and 14 deletions
|
@ -8,6 +8,7 @@
|
|||
import { EuiBadge, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import React, { useMemo } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features';
|
||||
import { getAgentStatusText } from '../../../common/components/endpoint/agent_status_text';
|
||||
import { HOST_STATUS_TO_BADGE_COLOR } from '../../../management/pages/endpoint_hosts/view/host_constants';
|
||||
import { useGetSentinelOneAgentStatus } from './use_sentinelone_host_isolation';
|
||||
|
@ -32,7 +33,13 @@ const EuiFlexGroupStyled = styled(EuiFlexGroup)`
|
|||
|
||||
export const SentinelOneAgentStatus = React.memo(
|
||||
({ agentId, 'data-test-subj': dataTestSubj }: { agentId: string; 'data-test-subj'?: string }) => {
|
||||
const { data, isLoading, isFetched } = useGetSentinelOneAgentStatus([agentId]);
|
||||
const sentinelOneManualHostActionsEnabled = useIsExperimentalFeatureEnabled(
|
||||
'sentinelOneManualHostActionsEnabled'
|
||||
);
|
||||
|
||||
const { data, isLoading, isFetched } = useGetSentinelOneAgentStatus([agentId], {
|
||||
enabled: sentinelOneManualHostActionsEnabled,
|
||||
});
|
||||
const agentStatus = data?.[`${agentId}`];
|
||||
|
||||
const label = useMemo(() => {
|
||||
|
|
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* 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 React from 'react';
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import { useHostIsolationAction } from './use_host_isolation_action';
|
||||
import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import { useGetSentinelOneAgentStatus } from './use_sentinelone_host_isolation';
|
||||
|
||||
jest.mock('./use_sentinelone_host_isolation');
|
||||
jest.mock('../../../common/hooks/use_experimental_features');
|
||||
const useIsExperimentalFeatureEnabledMock = useIsExperimentalFeatureEnabled as jest.Mock;
|
||||
const useGetSentinelOneAgentStatusMock = useGetSentinelOneAgentStatus as jest.Mock;
|
||||
|
||||
describe('useHostIsolationAction', () => {
|
||||
const createReactQueryWrapper = () => {
|
||||
const queryClient = new QueryClient();
|
||||
const wrapper: React.FC = ({ children }) => (
|
||||
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
|
||||
);
|
||||
return wrapper;
|
||||
};
|
||||
|
||||
const render = (isSentinelAlert: boolean = true) =>
|
||||
renderHook(
|
||||
() =>
|
||||
useHostIsolationAction({
|
||||
closePopover: jest.fn(),
|
||||
detailsData: isSentinelAlert
|
||||
? [
|
||||
{
|
||||
category: 'event',
|
||||
field: 'event.module',
|
||||
values: ['sentinel_one'],
|
||||
originalValue: ['sentinel_one'],
|
||||
isObjectArray: false,
|
||||
},
|
||||
{
|
||||
category: 'observer',
|
||||
field: 'observer.serial_number',
|
||||
values: ['some-agent-id'],
|
||||
originalValue: ['some-agent-id'],
|
||||
isObjectArray: false,
|
||||
},
|
||||
]
|
||||
: [
|
||||
{
|
||||
category: 'agent',
|
||||
field: 'agent.id',
|
||||
values: ['some-agent-id'],
|
||||
originalValue: ['some-agent-id'],
|
||||
isObjectArray: false,
|
||||
},
|
||||
],
|
||||
isHostIsolationPanelOpen: false,
|
||||
onAddIsolationStatusClick: jest.fn(),
|
||||
}),
|
||||
{
|
||||
wrapper: createReactQueryWrapper(),
|
||||
}
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
useIsExperimentalFeatureEnabledMock.mockReturnValue(true);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('`useGetSentinelOneAgentStatusMock` is invoked as `enabled` when SentinelOne alert and FF enabled', () => {
|
||||
render();
|
||||
|
||||
expect(useGetSentinelOneAgentStatusMock).toHaveBeenCalledWith(['some-agent-id'], {
|
||||
enabled: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('`useGetSentinelOneAgentStatusMock` is invoked as `disabled` when SentinelOne alert and FF disabled', () => {
|
||||
useIsExperimentalFeatureEnabledMock.mockReturnValue(false);
|
||||
render();
|
||||
|
||||
expect(useGetSentinelOneAgentStatusMock).toHaveBeenCalledWith(['some-agent-id'], {
|
||||
enabled: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('`useGetSentinelOneAgentStatusMock` is invoked as `disabled` when non-SentinelOne alert', () => {
|
||||
render(false);
|
||||
|
||||
expect(useGetSentinelOneAgentStatusMock).toHaveBeenCalledWith([''], {
|
||||
enabled: false,
|
||||
});
|
||||
});
|
||||
});
|
|
@ -79,7 +79,9 @@ export const useHostIsolationAction = ({
|
|||
agentType: sentinelOneAgentId ? 'sentinel_one' : 'endpoint',
|
||||
});
|
||||
|
||||
const { data: sentinelOneAgentData } = useGetSentinelOneAgentStatus([sentinelOneAgentId || '']);
|
||||
const { data: sentinelOneAgentData } = useGetSentinelOneAgentStatus([sentinelOneAgentId || ''], {
|
||||
enabled: !!sentinelOneAgentId && sentinelOneManualHostActionsEnabled,
|
||||
});
|
||||
const sentinelOneAgentStatus = sentinelOneAgentData?.[`${sentinelOneAgentId}`];
|
||||
|
||||
const isHostIsolated = useMemo(() => {
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { isEmpty } from 'lodash';
|
||||
import type { SentinelOneGetAgentsResponse } from '@kbn/stack-connectors-plugin/common/sentinelone/types';
|
||||
import type { UseQueryOptions, UseQueryResult } from '@tanstack/react-query';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
|
@ -14,7 +13,6 @@ import type { ActionTypeExecutorResult } from '@kbn/actions-plugin/common';
|
|||
import { ENDPOINT_AGENT_STATUS_ROUTE } from '../../../../common/endpoint/constants';
|
||||
import type { AgentStatusApiResponse } from '../../../../common/endpoint/types';
|
||||
import { useHttp } from '../../../common/lib/kibana';
|
||||
import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features';
|
||||
|
||||
interface ErrorType {
|
||||
statusCode: number;
|
||||
|
@ -26,19 +24,11 @@ export const useGetSentinelOneAgentStatus = (
|
|||
agentIds: string[],
|
||||
options: UseQueryOptions<AgentStatusApiResponse['data'], IHttpFetchError<ErrorType>> = {}
|
||||
): UseQueryResult<AgentStatusApiResponse['data'], IHttpFetchError<ErrorType>> => {
|
||||
const sentinelOneManualHostActionsEnabled = useIsExperimentalFeatureEnabled(
|
||||
'sentinelOneManualHostActionsEnabled'
|
||||
);
|
||||
|
||||
const http = useHttp();
|
||||
|
||||
return useQuery<AgentStatusApiResponse['data'], IHttpFetchError<ErrorType>>({
|
||||
queryKey: ['get-agent-status', agentIds],
|
||||
...options,
|
||||
enabled: !(
|
||||
sentinelOneManualHostActionsEnabled &&
|
||||
isEmpty(agentIds.filter((agentId) => agentId.trim().length))
|
||||
),
|
||||
// TODO: update this to use a function instead of a number
|
||||
refetchInterval: 2000,
|
||||
queryFn: () =>
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
import React, { memo } from 'react';
|
||||
import { useIsExperimentalFeatureEnabled } from '../../../../../../common/hooks/use_experimental_features';
|
||||
import { useGetSentinelOneAgentStatus } from '../../../../../../detections/components/host_isolation/use_sentinelone_host_isolation';
|
||||
import { SentinelOneAgentStatus } from '../../../../../../detections/components/host_isolation/sentinel_one_agent_status';
|
||||
import type { ThirdPartyAgentInfo } from '../../../../../../../common/types';
|
||||
|
@ -20,7 +21,10 @@ interface HeaderSentinelOneInfoProps {
|
|||
|
||||
export const HeaderSentinelOneInfo = memo<HeaderSentinelOneInfoProps>(
|
||||
({ agentId, platform, hostName }) => {
|
||||
const { data } = useGetSentinelOneAgentStatus([agentId]);
|
||||
const isSentinelOneV1Enabled = useIsExperimentalFeatureEnabled(
|
||||
'sentinelOneManualHostActionsEnabled'
|
||||
);
|
||||
const { data } = useGetSentinelOneAgentStatus([agentId], { enabled: isSentinelOneV1Enabled });
|
||||
const agentStatus = data?.[agentId];
|
||||
const lastCheckin = agentStatus ? agentStatus.lastSeen : '';
|
||||
|
||||
|
|
|
@ -28,12 +28,18 @@ export const OfflineCallout = memo<OfflineCalloutProps>(({ agentType, endpointId
|
|||
'responseActionsSentinelOneV1Enabled'
|
||||
);
|
||||
|
||||
const sentinelOneManualHostActionsEnabled = useIsExperimentalFeatureEnabled(
|
||||
'sentinelOneManualHostActionsEnabled'
|
||||
);
|
||||
|
||||
const { data: endpointDetails } = useGetEndpointDetails(endpointId, {
|
||||
refetchInterval: 10000,
|
||||
enabled: isEndpointAgent,
|
||||
});
|
||||
|
||||
const { data } = useGetSentinelOneAgentStatus([endpointId]);
|
||||
const { data } = useGetSentinelOneAgentStatus([endpointId], {
|
||||
enabled: sentinelOneManualHostActionsEnabled && isSentinelOneAgent,
|
||||
});
|
||||
|
||||
// TODO: simplify this to use the yet to be implemented agentStatus API hook
|
||||
const showOfflineCallout = useMemo(
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue