[Security Solution][Investigations] - Use index alias in place of backing index (#138331)

This commit is contained in:
Michael Olorunnisola 2022-08-16 12:30:18 -04:00 committed by GitHub
parent c7ee95079c
commit 3ad0484d3e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 116 additions and 20 deletions

View file

@ -9,7 +9,7 @@ import { act, fireEvent, render, screen, waitFor } from '@testing-library/react'
import { OpenInDevConsoleButton } from '.';
import { TestProviders } from '../../mock';
jest.mock('../../../risk_score/containers/common', () => ({
jest.mock('../../hooks/use_space_id', () => ({
useSpaceId: jest.fn().mockReturnValue('myspace'),
}));

View file

@ -6,7 +6,7 @@
*/
import React from 'react';
import { EuiButton, EuiFlexItem, EuiToolTip } from '@elastic/eui';
import { useSpaceId } from '../../../risk_score/containers/common';
import { useSpaceId } from '../../hooks/use_space_id';
interface OpenInDevConsoleButtonProps {
enableButton: boolean;

View file

@ -6,7 +6,7 @@
*/
import { useState, useEffect } from 'react';
import { useKibana } from '../../../common/lib/kibana';
import { useKibana } from '../lib/kibana';
export const useSpaceId = () => {
const { spaces } = useKibana().services;

View file

@ -16,7 +16,7 @@ import { devToolPrebuiltContentUrl } from '../../../../common/constants';
import { OpenInDevConsoleButton } from '../../../common/components/open_in_dev_console';
import { useChcekSignalIndex } from '../../../detections/containers/detection_engine/alerts/use_check_signal_index';
import type { LinkPanelListItem } from '../link_panel';
import { useSpaceId } from '../../../risk_score/containers/common';
import { useSpaceId } from '../../../common/hooks/use_space_id';
export const RISKY_HOSTS_DOC_LINK =
'https://www.github.com/elastic/detection-rules/blob/main/docs/experimental-machine-learning/host-risk-score.md';

View file

@ -34,7 +34,7 @@ import { useAppToasts } from '../../../common/hooks/use_app_toasts';
import { isIndexNotFoundError } from '../../../common/utils/exceptions';
import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features';
import type { inputsModel } from '../../../common/store';
import { useSpaceId } from '../common';
import { useSpaceId } from '../../../common/hooks/use_space_id';
export interface RiskScoreState<RiskScoreType extends HostsRiskScore[] | UsersRiskScore[]> {
data?: RiskScoreType;

View file

@ -30,7 +30,7 @@ import { isIndexNotFoundError } from '../../../common/utils/exceptions';
import type { ESTermQuery } from '../../../../common/typed_json';
import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features';
import type { SeverityCount } from '../../../common/components/severity/types';
import { useSpaceId } from '../common';
import { useSpaceId } from '../../../common/hooks/use_space_id';
type GetHostsRiskScoreProps = KpiRiskScoreRequestOptions & {
data: DataPublicPluginStart;

View file

@ -9,6 +9,7 @@ import { some } from 'lodash/fp';
import { useMemo } from 'react';
import type { TimelineEventsDetailsItem } from '../../../../../common/search_strategy';
import { getFieldValue } from '../../../../detections/components/host_isolation/helpers';
import { DEFAULT_ALERTS_INDEX, DEFAULT_PREVIEW_INDEX } from '../../../../../common/constants';
interface GetBasicDataFromDetailsData {
alertId: string;
@ -51,3 +52,20 @@ export const useBasicDataFromDetailsData = (
[alertId, hostName, isAlert, ruleName, timestamp]
);
};
/*
The referenced alert _index in the flyout uses the `.internal.` such as
`.internal.alerts-security.alerts-spaceId` in the alert page flyout and
.internal.preview.alerts-security.alerts-spaceId` in the rule creation preview flyout
but we always want to use their respective aliase indices rather than accessing their backing .internal. indices.
*/
export const getAlertIndexAlias = (
index: string,
spaceId: string = 'default'
): string | undefined => {
if (index.startsWith(`.internal${DEFAULT_ALERTS_INDEX}`)) {
return `${DEFAULT_ALERTS_INDEX}-${spaceId}`;
} else if (index.startsWith(`.internal${DEFAULT_PREVIEW_INDEX}`)) {
return `${DEFAULT_PREVIEW_INDEX}-${spaceId}`;
}
};

View file

@ -11,8 +11,6 @@ import '../../../../common/mock/match_media';
import { TestProviders } from '../../../../common/mock';
import { TimelineId, TimelineTabs } from '../../../../../common/types/timeline';
import type { Ecs } from '../../../../../common/ecs';
import { mockAlertDetailsData } from '../../../../common/components/event_details/__mocks__';
import type { TimelineEventsDetailsItem } from '../../../../../common/search_strategy';
import {
KibanaServices,
useKibana,
@ -21,7 +19,9 @@ import {
import { mockBrowserFields, mockRuntimeMappings } from '../../../../common/containers/source/mock';
import { coreMock } from '@kbn/core/public/mocks';
import { mockCasesContext } from '@kbn/cases-plugin/public/mocks/mock_cases_context';
import { useTimelineEventsDetails } from '../../../containers/details';
import { allCasesPermissions } from '../../../../cases_test_utils';
import { DEFAULT_ALERTS_INDEX, DEFAULT_PREVIEW_INDEX } from '../../../../../common/constants';
const ecsData: Ecs = {
_id: '1',
@ -37,19 +37,16 @@ const ecsData: Ecs = {
},
};
const mockAlertDetailsDataWithIsObject = mockAlertDetailsData.map((detail) => {
return {
...detail,
isObjectArray: false,
};
}) as TimelineEventsDetailsItem[];
jest.mock('../../../../../common/endpoint/service/host_isolation/utils', () => {
return {
isIsolationSupported: jest.fn().mockReturnValue(true),
};
});
jest.mock('../../../../common/hooks/use_space_id', () => ({
useSpaceId: jest.fn().mockReturnValue('testSpace'),
}));
jest.mock(
'../../../../detections/containers/detection_engine/alerts/use_host_isolation_status',
() => {
@ -101,18 +98,23 @@ const mockSearchStrategy = jest.fn();
const defaultProps = {
timelineId: TimelineId.test,
loadingEventDetails: false,
detailsEcsData: ecsData,
isHostIsolationPanelOpen: false,
handleOnEventClosed: jest.fn(),
onAddIsolationStatusClick: jest.fn(),
expandedEvent: { eventId: ecsData._id, indexName: '' },
detailsData: mockAlertDetailsDataWithIsObject,
tabType: TimelineTabs.query,
browserFields: mockBrowserFields,
runtimeMappings: mockRuntimeMappings,
};
jest.mock('../../../containers/details', () => {
const actual = jest.requireActual('../../../containers/details');
return {
...actual,
useTimelineEventsDetails: jest.fn().mockImplementation(() => []),
};
});
describe('event details footer component', () => {
beforeEach(() => {
const coreStartMock = coreMock.createStart();
@ -169,4 +171,76 @@ describe('event details footer component', () => {
const element = wrapper.queryByTestId('side-panel-flyout-footer');
expect(element).toBeNull();
});
describe('Alerts', () => {
const propsWithAlertIndex = {
...defaultProps,
expandedEvent: {
eventId: ecsData._id,
indexName: `.internal${DEFAULT_ALERTS_INDEX}-testSpace`,
},
};
test('it uses the alias alerts index', () => {
render(
<TestProviders>
<EventDetailsPanel {...{ ...propsWithAlertIndex }} />
</TestProviders>
);
expect(useTimelineEventsDetails).toHaveBeenCalledWith({
entityType: 'events',
indexName: `${DEFAULT_ALERTS_INDEX}-testSpace`,
eventId: propsWithAlertIndex.expandedEvent.eventId ?? '',
runtimeMappings: mockRuntimeMappings,
skip: false,
});
});
test('it uses the alias alerts preview index', () => {
const alertPreviewProps = {
...propsWithAlertIndex,
expandedEvent: {
...propsWithAlertIndex.expandedEvent,
indexName: `.internal${DEFAULT_PREVIEW_INDEX}-testSpace`,
},
};
render(
<TestProviders>
<EventDetailsPanel {...{ ...alertPreviewProps }} />
</TestProviders>
);
expect(useTimelineEventsDetails).toHaveBeenCalledWith({
entityType: 'events',
indexName: `${DEFAULT_PREVIEW_INDEX}-testSpace`,
eventId: propsWithAlertIndex.expandedEvent.eventId,
runtimeMappings: mockRuntimeMappings,
skip: false,
});
});
test(`it does NOT use the alerts alias when regular events happen to include a trailing '${DEFAULT_ALERTS_INDEX}' in the index name`, () => {
const indexName = `.ds-logs-endpoint.alerts-default-2022.08.09-000001${DEFAULT_ALERTS_INDEX}`; // a regular event, that happens to include a trailing `.alerts-security.alerts`
const propsWithEventIndex = {
...defaultProps,
expandedEvent: {
eventId: ecsData._id,
indexName,
},
};
render(
<TestProviders>
<EventDetailsPanel {...{ ...propsWithEventIndex }} />
</TestProviders>
);
expect(useTimelineEventsDetails).toHaveBeenCalledWith({
entityType: 'events',
indexName, // <-- use the original index name, not the alerts alias
eventId: propsWithEventIndex.expandedEvent.eventId,
runtimeMappings: mockRuntimeMappings,
skip: false,
});
});
});
});

View file

@ -20,7 +20,8 @@ import type { HostRisk } from '../../../../risk_score/containers';
import { useHostRiskScore } from '../../../../risk_score/containers';
import { useHostIsolationTools } from './use_host_isolation_tools';
import { FlyoutBody, FlyoutHeader, FlyoutFooter } from './flyout';
import { useBasicDataFromDetailsData } from './helpers';
import { useBasicDataFromDetailsData, getAlertIndexAlias } from './helpers';
import { useSpaceId } from '../../../../common/hooks/use_space_id';
interface EventDetailsPanelProps {
browserFields: BrowserFields;
@ -51,10 +52,13 @@ const EventDetailsPanelComponent: React.FC<EventDetailsPanelProps> = ({
timelineId,
isReadOnly,
}) => {
const currentSpaceId = useSpaceId();
const { indexName } = expandedEvent;
const eventIndex = getAlertIndexAlias(indexName, currentSpaceId) ?? indexName;
const [loading, detailsData, rawEventData, ecsData, refetchFlyoutData] = useTimelineEventsDetails(
{
entityType,
indexName: expandedEvent.indexName ?? '',
indexName: eventIndex ?? '',
eventId: expandedEvent.eventId ?? '',
runtimeMappings,
skip: !expandedEvent.eventId,