mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[EDR Workflows] POC - Show Sentinel One data in analyzer (#170829)
This commit is contained in:
parent
9b822b96c2
commit
3ca265abe2
14 changed files with 204 additions and 93 deletions
|
@ -147,6 +147,11 @@ export const allowedExperimentalValues = Object.freeze({
|
|||
*/
|
||||
entityAnalyticsAssetCriticalityEnabled: false,
|
||||
|
||||
/*
|
||||
* Enables experimental Experimental S1 integration data to be available in Analyzer
|
||||
*/
|
||||
sentinelOneDataInAnalyzerEnabled: false,
|
||||
|
||||
/**
|
||||
* Enables SentinelOne manual host manipulation actions
|
||||
*/
|
||||
|
|
|
@ -17,7 +17,7 @@ import {
|
|||
getPinOnClick,
|
||||
} from '../../../timelines/components/timeline/body/helpers';
|
||||
import { getScopedActions, isTimelineScope } from '../../../helpers';
|
||||
import { isInvestigateInResolverActionEnabled } from '../../../detections/components/alerts_table/timeline_actions/investigate_in_resolver';
|
||||
import { useIsInvestigateInResolverActionEnabled } from '../../../detections/components/alerts_table/timeline_actions/investigate_in_resolver';
|
||||
import { timelineActions, timelineSelectors } from '../../../timelines/store/timeline';
|
||||
import type { ActionProps, OnPinEvent } from '../../../../common/types';
|
||||
import { TimelineId } from '../../../../common/types';
|
||||
|
@ -117,7 +117,7 @@ const ActionsComponent: React.FC<ActionProps> = ({
|
|||
);
|
||||
}, [ecsData, eventType]);
|
||||
|
||||
const isDisabled = useMemo(() => !isInvestigateInResolverActionEnabled(ecsData), [ecsData]);
|
||||
const isDisabled = !useIsInvestigateInResolverActionEnabled(ecsData);
|
||||
const { setGlobalFullScreen } = useGlobalFullScreen();
|
||||
const { setTimelineFullScreen } = useTimelineFullScreen();
|
||||
const scopedActions = getScopedActions(timelineId);
|
||||
|
|
|
@ -6,26 +6,40 @@
|
|||
*/
|
||||
|
||||
import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs';
|
||||
import { isInvestigateInResolverActionEnabled } from './investigate_in_resolver';
|
||||
import { useIsInvestigateInResolverActionEnabled } from './investigate_in_resolver';
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import { TestProviders } from '../../../../common/mock';
|
||||
|
||||
describe('InvestigateInResolverAction', () => {
|
||||
describe('isInvestigateInResolverActionEnabled', () => {
|
||||
describe('useIsInvestigateInResolverActionEnabled', () => {
|
||||
it('returns false if agent.type does not equal endpoint', () => {
|
||||
const data: Ecs = { _id: '1', agent: { type: ['blah'] } };
|
||||
|
||||
expect(isInvestigateInResolverActionEnabled(data)).toBeFalsy();
|
||||
const { result } = renderHook(() => useIsInvestigateInResolverActionEnabled(data), {
|
||||
wrapper: TestProviders,
|
||||
});
|
||||
|
||||
expect(result.current).toBeFalsy();
|
||||
});
|
||||
|
||||
it('returns false if agent.type does not have endpoint in first array index', () => {
|
||||
const data: Ecs = { _id: '1', agent: { type: ['blah', 'endpoint'] } };
|
||||
|
||||
expect(isInvestigateInResolverActionEnabled(data)).toBeFalsy();
|
||||
const { result } = renderHook(() => useIsInvestigateInResolverActionEnabled(data), {
|
||||
wrapper: TestProviders,
|
||||
});
|
||||
|
||||
expect(result.current).toBeFalsy();
|
||||
});
|
||||
|
||||
it('returns false if process.entity_id is not defined', () => {
|
||||
const data: Ecs = { _id: '1', agent: { type: ['endpoint'] } };
|
||||
|
||||
expect(isInvestigateInResolverActionEnabled(data)).toBeFalsy();
|
||||
const { result } = renderHook(() => useIsInvestigateInResolverActionEnabled(data), {
|
||||
wrapper: TestProviders,
|
||||
});
|
||||
|
||||
expect(result.current).toBeFalsy();
|
||||
});
|
||||
|
||||
it('returns true if agent.type has endpoint in first array index', () => {
|
||||
|
@ -35,7 +49,11 @@ describe('InvestigateInResolverAction', () => {
|
|||
process: { entity_id: ['5'] },
|
||||
};
|
||||
|
||||
expect(isInvestigateInResolverActionEnabled(data)).toBeTruthy();
|
||||
const { result } = renderHook(() => useIsInvestigateInResolverActionEnabled(data), {
|
||||
wrapper: TestProviders,
|
||||
});
|
||||
|
||||
expect(result.current).toBeTruthy();
|
||||
});
|
||||
|
||||
it('returns false if multiple entity_ids', () => {
|
||||
|
@ -45,7 +63,11 @@ describe('InvestigateInResolverAction', () => {
|
|||
process: { entity_id: ['5', '10'] },
|
||||
};
|
||||
|
||||
expect(isInvestigateInResolverActionEnabled(data)).toBeFalsy();
|
||||
const { result } = renderHook(() => useIsInvestigateInResolverActionEnabled(data), {
|
||||
wrapper: TestProviders,
|
||||
});
|
||||
|
||||
expect(result.current).toBeFalsy();
|
||||
});
|
||||
|
||||
it('returns false if entity_id is an empty string', () => {
|
||||
|
@ -55,7 +77,11 @@ describe('InvestigateInResolverAction', () => {
|
|||
process: { entity_id: [''] },
|
||||
};
|
||||
|
||||
expect(isInvestigateInResolverActionEnabled(data)).toBeFalsy();
|
||||
const { result } = renderHook(() => useIsInvestigateInResolverActionEnabled(data), {
|
||||
wrapper: TestProviders,
|
||||
});
|
||||
|
||||
expect(result.current).toBeFalsy();
|
||||
});
|
||||
|
||||
it('returns true for process event from sysmon via filebeat', () => {
|
||||
|
@ -66,7 +92,11 @@ describe('InvestigateInResolverAction', () => {
|
|||
process: { entity_id: ['always_unique'] },
|
||||
};
|
||||
|
||||
expect(isInvestigateInResolverActionEnabled(data)).toBeTruthy();
|
||||
const { result } = renderHook(() => useIsInvestigateInResolverActionEnabled(data), {
|
||||
wrapper: TestProviders,
|
||||
});
|
||||
|
||||
expect(result.current).toBeTruthy();
|
||||
});
|
||||
|
||||
it('returns false for process event from filebeat but not from sysmon', () => {
|
||||
|
@ -77,7 +107,11 @@ describe('InvestigateInResolverAction', () => {
|
|||
process: { entity_id: ['always_unique'] },
|
||||
};
|
||||
|
||||
expect(isInvestigateInResolverActionEnabled(data)).toBeFalsy();
|
||||
const { result } = renderHook(() => useIsInvestigateInResolverActionEnabled(data), {
|
||||
wrapper: TestProviders,
|
||||
});
|
||||
|
||||
expect(result.current).toBeFalsy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,23 +5,36 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { useMemo } from 'react';
|
||||
import { get } from 'lodash/fp';
|
||||
import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs';
|
||||
import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features';
|
||||
|
||||
export const isInvestigateInResolverActionEnabled = (ecsData?: Ecs) => {
|
||||
const agentType = get(['agent', 'type', 0], ecsData);
|
||||
const processEntityIds = get(['process', 'entity_id'], ecsData);
|
||||
const firstProcessEntityId = get(['process', 'entity_id', 0], ecsData);
|
||||
const eventModule = get(['event', 'module', 0], ecsData);
|
||||
const eventDataStream = get(['event', 'dataset'], ecsData);
|
||||
const datasetIncludesSysmon =
|
||||
Array.isArray(eventDataStream) &&
|
||||
eventDataStream.some((datastream) => datastream.includes('windows.sysmon'));
|
||||
const agentTypeIsEndpoint = agentType === 'endpoint';
|
||||
const agentTypeIsWinlogBeat = agentType === 'winlogbeat' && eventModule === 'sysmon';
|
||||
const isEndpointOrSysmonFromWinlogBeat =
|
||||
agentTypeIsEndpoint || agentTypeIsWinlogBeat || datasetIncludesSysmon;
|
||||
const hasProcessEntityId =
|
||||
processEntityIds != null && processEntityIds.length === 1 && firstProcessEntityId !== '';
|
||||
return isEndpointOrSysmonFromWinlogBeat && hasProcessEntityId;
|
||||
export const useIsInvestigateInResolverActionEnabled = (ecsData?: Ecs) => {
|
||||
const sentinelOneDataInAnalyzerEnabled = useIsExperimentalFeatureEnabled(
|
||||
'sentinelOneDataInAnalyzerEnabled'
|
||||
);
|
||||
return useMemo(() => {
|
||||
const fileBeatModules = [
|
||||
...(sentinelOneDataInAnalyzerEnabled ? ['sentinel_one_cloud_funnel', 'sentinel_one'] : []),
|
||||
] as const;
|
||||
|
||||
const agentType = get(['agent', 'type', 0], ecsData);
|
||||
const processEntityIds = get(['process', 'entity_id'], ecsData);
|
||||
const firstProcessEntityId = get(['process', 'entity_id', 0], ecsData);
|
||||
const eventModule = get(['event', 'module', 0], ecsData);
|
||||
const eventDataStream = get(['event', 'dataset'], ecsData);
|
||||
const datasetIncludesSysmon =
|
||||
Array.isArray(eventDataStream) &&
|
||||
eventDataStream.some((datastream) => datastream.includes('windows.sysmon'));
|
||||
const agentTypeIsEndpoint = agentType === 'endpoint';
|
||||
const agentTypeIsWinlogBeat = agentType === 'winlogbeat' && eventModule === 'sysmon';
|
||||
const agentTypeIsFilebeat = agentType === 'filebeat' && fileBeatModules.includes(eventModule);
|
||||
const isAcceptedAgentType =
|
||||
agentTypeIsEndpoint || agentTypeIsWinlogBeat || datasetIncludesSysmon || agentTypeIsFilebeat;
|
||||
const hasProcessEntityId =
|
||||
processEntityIds != null && processEntityIds.length === 1 && firstProcessEntityId !== '';
|
||||
|
||||
return isAcceptedAgentType && hasProcessEntityId;
|
||||
}, [ecsData, sentinelOneDataInAnalyzerEnabled]);
|
||||
};
|
||||
|
|
|
@ -11,7 +11,7 @@ import React from 'react';
|
|||
import { RightPanelContext } from '../context';
|
||||
import { mockContextValue } from '../mocks/mock_context';
|
||||
import { AnalyzerPreviewContainer } from './analyzer_preview_container';
|
||||
import { isInvestigateInResolverActionEnabled } from '../../../../detections/components/alerts_table/timeline_actions/investigate_in_resolver';
|
||||
import { useIsInvestigateInResolverActionEnabled } from '../../../../detections/components/alerts_table/timeline_actions/investigate_in_resolver';
|
||||
import { ANALYZER_PREVIEW_TEST_ID } from './test_ids';
|
||||
import { useAlertPrevalenceFromProcessTree } from '../../../../common/containers/alerts/use_alert_prevalence_from_process_tree';
|
||||
import * as mock from '../mocks/mock_analyzer_data';
|
||||
|
@ -68,7 +68,7 @@ describe('AnalyzerPreviewContainer', () => {
|
|||
});
|
||||
|
||||
it('should render component and link in header', () => {
|
||||
(isInvestigateInResolverActionEnabled as jest.Mock).mockReturnValue(true);
|
||||
(useIsInvestigateInResolverActionEnabled as jest.Mock).mockReturnValue(true);
|
||||
(useAlertPrevalenceFromProcessTree as jest.Mock).mockReturnValue({
|
||||
loading: false,
|
||||
error: false,
|
||||
|
@ -103,7 +103,7 @@ describe('AnalyzerPreviewContainer', () => {
|
|||
});
|
||||
|
||||
it('should render error message and text in header', () => {
|
||||
(isInvestigateInResolverActionEnabled as jest.Mock).mockReturnValue(false);
|
||||
(useIsInvestigateInResolverActionEnabled as jest.Mock).mockReturnValue(false);
|
||||
(useInvestigateInTimeline as jest.Mock).mockReturnValue({
|
||||
investigateInTimelineAlertClick: jest.fn(),
|
||||
});
|
||||
|
@ -118,7 +118,7 @@ describe('AnalyzerPreviewContainer', () => {
|
|||
});
|
||||
|
||||
it('should navigate to analyzer in timeline when clicking on title', () => {
|
||||
(isInvestigateInResolverActionEnabled as jest.Mock).mockReturnValue(true);
|
||||
(useIsInvestigateInResolverActionEnabled as jest.Mock).mockReturnValue(true);
|
||||
(useAlertPrevalenceFromProcessTree as jest.Mock).mockReturnValue({
|
||||
loading: false,
|
||||
error: false,
|
||||
|
@ -138,7 +138,7 @@ describe('AnalyzerPreviewContainer', () => {
|
|||
});
|
||||
|
||||
it('should not navigate to analyzer when in preview and clicking on title', () => {
|
||||
(isInvestigateInResolverActionEnabled as jest.Mock).mockReturnValue(true);
|
||||
(useIsInvestigateInResolverActionEnabled as jest.Mock).mockReturnValue(true);
|
||||
(useAlertPrevalenceFromProcessTree as jest.Mock).mockReturnValue({
|
||||
loading: false,
|
||||
error: false,
|
||||
|
|
|
@ -16,7 +16,7 @@ import { ALERTS_ACTIONS } from '../../../../common/lib/apm/user_actions';
|
|||
import { getScopedActions } from '../../../../helpers';
|
||||
import { setActiveTabTimeline } from '../../../../timelines/store/timeline/actions';
|
||||
import { useRightPanelContext } from '../context';
|
||||
import { isInvestigateInResolverActionEnabled } from '../../../../detections/components/alerts_table/timeline_actions/investigate_in_resolver';
|
||||
import { useIsInvestigateInResolverActionEnabled } from '../../../../detections/components/alerts_table/timeline_actions/investigate_in_resolver';
|
||||
import { AnalyzerPreview } from './analyzer_preview';
|
||||
import { ANALYZER_PREVIEW_TEST_ID } from './test_ids';
|
||||
import { ExpandablePanel } from '../../../shared/components/expandable_panel';
|
||||
|
@ -30,7 +30,7 @@ export const AnalyzerPreviewContainer: React.FC = () => {
|
|||
const { dataAsNestedObject, isPreview } = useRightPanelContext();
|
||||
|
||||
// decide whether to show the analyzer preview or not
|
||||
const isEnabled = isInvestigateInResolverActionEnabled(dataAsNestedObject);
|
||||
const isEnabled = useIsInvestigateInResolverActionEnabled(dataAsNestedObject);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const { startTransaction } = useStartTransaction();
|
||||
|
|
|
@ -10,7 +10,7 @@ import { useMemo } from 'react';
|
|||
import { find } from 'lodash/fp';
|
||||
import type { TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common';
|
||||
import type { GetFieldsData } from '../../../../common/hooks/use_get_fields_data';
|
||||
import { isInvestigateInResolverActionEnabled } from '../../../../detections/components/alerts_table/timeline_actions/investigate_in_resolver';
|
||||
import { useIsInvestigateInResolverActionEnabled } from '../../../../detections/components/alerts_table/timeline_actions/investigate_in_resolver';
|
||||
import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features';
|
||||
import { useLicense } from '../../../../common/hooks/use_license';
|
||||
import { getField } from '../utils';
|
||||
|
@ -57,7 +57,7 @@ export const useShowRelatedAlertsByAncestry = ({
|
|||
const isRelatedAlertsByProcessAncestryEnabled = useIsExperimentalFeatureEnabled(
|
||||
'insightsRelatedAlertsByProcessAncestry'
|
||||
);
|
||||
const hasProcessEntityInfo = isInvestigateInResolverActionEnabled(dataAsNestedObject);
|
||||
const hasProcessEntityInfo = useIsInvestigateInResolverActionEnabled(dataAsNestedObject);
|
||||
|
||||
const originalDocumentId = getField(getFieldsData(ANCESTOR_ID));
|
||||
|
||||
|
|
|
@ -50,6 +50,6 @@ export const registerResolverRoutes = async (
|
|||
validate: validateEntities,
|
||||
options: { authRequired: true },
|
||||
},
|
||||
handleEntities()
|
||||
handleEntities(config.experimentalFeatures)
|
||||
);
|
||||
};
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import type { RequestHandler } from '@kbn/core/server';
|
||||
import type { TypeOf } from '@kbn/config-schema';
|
||||
import type { ExperimentalFeatures } from '../../../../../common';
|
||||
import { EXCLUDE_COLD_AND_FROZEN_TIERS_IN_ANALYZER } from '../../../../../common/constants';
|
||||
import type { validateEntities } from '../../../../../common/endpoint/schema/resolver';
|
||||
import type { ResolverEntityIndex } from '../../../../../common/endpoint/types';
|
||||
|
@ -17,7 +18,9 @@ import { createSharedFilters } from '../utils/shared_filters';
|
|||
* This is used to get an 'entity_id' which is an internal-to-Resolver concept, from an `_id`, which
|
||||
* is the artificial ID generated by ES for each document.
|
||||
*/
|
||||
export function handleEntities(): RequestHandler<unknown, TypeOf<typeof validateEntities.query>> {
|
||||
export function handleEntities(
|
||||
experimentalFeatures: ExperimentalFeatures
|
||||
): RequestHandler<unknown, TypeOf<typeof validateEntities.query>> {
|
||||
return async (context, request, response) => {
|
||||
const {
|
||||
query: { _id, indices },
|
||||
|
@ -50,7 +53,10 @@ export function handleEntities(): RequestHandler<unknown, TypeOf<typeof validate
|
|||
},
|
||||
});
|
||||
|
||||
const responseBody: ResolverEntityIndex = resolverEntity(queryResponse.hits.hits);
|
||||
const responseBody: ResolverEntityIndex = resolverEntity(
|
||||
queryResponse.hits.hits,
|
||||
experimentalFeatures
|
||||
);
|
||||
return response.ok({ body: responseBody });
|
||||
};
|
||||
}
|
||||
|
|
|
@ -6,11 +6,18 @@
|
|||
*/
|
||||
|
||||
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
import { getFieldAsString, supportedSchemas } from './supported_schemas';
|
||||
import type { ExperimentalFeatures } from '../../../../../../common';
|
||||
import { getFieldAsString, getSupportedSchemas } from './supported_schemas';
|
||||
import type { ResolverEntityIndex } from '../../../../../../common/endpoint/types';
|
||||
|
||||
export function resolverEntity(hits: Array<estypes.SearchHit<unknown>>) {
|
||||
const toArray = <T>(input: T | T[]) => ([] as T[]).concat(input);
|
||||
|
||||
export function resolverEntity(
|
||||
hits: Array<estypes.SearchHit<unknown>>,
|
||||
experimentalFeatures: ExperimentalFeatures | undefined
|
||||
) {
|
||||
const responseBody: ResolverEntityIndex = [];
|
||||
const supportedSchemas = getSupportedSchemas(experimentalFeatures);
|
||||
for (const hit of hits) {
|
||||
for (const supportedSchema of supportedSchemas) {
|
||||
let foundSchema = true;
|
||||
|
@ -20,7 +27,12 @@ export function resolverEntity(hits: Array<estypes.SearchHit<unknown>>) {
|
|||
const fieldValue = getFieldAsString(hit._source, constraint.field);
|
||||
// track that all the constraints are true, if one of them is false then this schema is not valid so mark it
|
||||
// that we did not find the schema
|
||||
foundSchema = foundSchema && fieldValue?.toLowerCase() === constraint.value.toLowerCase();
|
||||
|
||||
foundSchema =
|
||||
foundSchema &&
|
||||
toArray(constraint.value).some(
|
||||
(constraintValue) => constraintValue.toLowerCase() === fieldValue?.toLowerCase()
|
||||
);
|
||||
}
|
||||
|
||||
if (foundSchema && id !== undefined && id !== '') {
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import type { ExperimentalFeatures } from '../../../../../../common';
|
||||
import type { ResolverSchema } from '../../../../../../common/endpoint/types';
|
||||
|
||||
interface SupportedSchema {
|
||||
|
@ -17,7 +18,7 @@ interface SupportedSchema {
|
|||
/**
|
||||
* A constraint to search for in the documented returned by Elasticsearch
|
||||
*/
|
||||
constraints: Array<{ field: string; value: string }>;
|
||||
constraints: Array<{ field: string; value: string | string[] }>;
|
||||
|
||||
/**
|
||||
* Schema to return to the frontend so that it can be passed in to call to the /tree API
|
||||
|
@ -29,59 +30,90 @@ interface SupportedSchema {
|
|||
* This structure defines the preset supported schemas for a resolver graph. We'll probably want convert this
|
||||
* implementation to something similar to how row renderers is implemented.
|
||||
*/
|
||||
export const supportedSchemas: SupportedSchema[] = [
|
||||
{
|
||||
name: 'endpoint',
|
||||
constraints: [
|
||||
{
|
||||
field: 'agent.type',
|
||||
value: 'endpoint',
|
||||
|
||||
export const getSupportedSchemas = (
|
||||
experimentalFeatures: ExperimentalFeatures | undefined
|
||||
): SupportedSchema[] => {
|
||||
const sentinelOneDataInAnalyzerEnabled = experimentalFeatures?.sentinelOneDataInAnalyzerEnabled;
|
||||
|
||||
const supportedFileBeatDataSets = [
|
||||
...(sentinelOneDataInAnalyzerEnabled
|
||||
? ['sentinel_one_cloud_funnel.event', 'sentinel_one.alert']
|
||||
: []),
|
||||
];
|
||||
|
||||
return [
|
||||
{
|
||||
name: 'endpoint',
|
||||
constraints: [
|
||||
{
|
||||
field: 'agent.type',
|
||||
value: 'endpoint',
|
||||
},
|
||||
],
|
||||
schema: {
|
||||
id: 'process.entity_id',
|
||||
parent: 'process.parent.entity_id',
|
||||
ancestry: 'process.Ext.ancestry',
|
||||
name: 'process.name',
|
||||
},
|
||||
],
|
||||
schema: {
|
||||
id: 'process.entity_id',
|
||||
parent: 'process.parent.entity_id',
|
||||
ancestry: 'process.Ext.ancestry',
|
||||
name: 'process.name',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'winlogbeat',
|
||||
constraints: [
|
||||
{
|
||||
field: 'agent.type',
|
||||
value: 'winlogbeat',
|
||||
{
|
||||
name: 'winlogbeat',
|
||||
constraints: [
|
||||
{
|
||||
field: 'agent.type',
|
||||
value: 'winlogbeat',
|
||||
},
|
||||
{
|
||||
field: 'event.module',
|
||||
value: 'sysmon',
|
||||
},
|
||||
],
|
||||
schema: {
|
||||
id: 'process.entity_id',
|
||||
parent: 'process.parent.entity_id',
|
||||
name: 'process.name',
|
||||
},
|
||||
{
|
||||
field: 'event.module',
|
||||
value: 'sysmon',
|
||||
},
|
||||
],
|
||||
schema: {
|
||||
id: 'process.entity_id',
|
||||
parent: 'process.parent.entity_id',
|
||||
name: 'process.name',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'sysmonViaFilebeat',
|
||||
constraints: [
|
||||
{
|
||||
field: 'agent.type',
|
||||
value: 'filebeat',
|
||||
{
|
||||
name: 'sysmonViaFilebeat',
|
||||
constraints: [
|
||||
{
|
||||
field: 'agent.type',
|
||||
value: 'filebeat',
|
||||
},
|
||||
{
|
||||
field: 'event.dataset',
|
||||
value: 'windows.sysmon_operational',
|
||||
},
|
||||
],
|
||||
schema: {
|
||||
id: 'process.entity_id',
|
||||
parent: 'process.parent.entity_id',
|
||||
name: 'process.name',
|
||||
},
|
||||
{
|
||||
field: 'event.dataset',
|
||||
value: 'windows.sysmon_operational',
|
||||
},
|
||||
],
|
||||
schema: {
|
||||
id: 'process.entity_id',
|
||||
parent: 'process.parent.entity_id',
|
||||
name: 'process.name',
|
||||
},
|
||||
},
|
||||
];
|
||||
{
|
||||
name: 'filebeat',
|
||||
constraints: [
|
||||
{
|
||||
field: 'agent.type',
|
||||
value: 'filebeat',
|
||||
},
|
||||
{
|
||||
field: 'event.dataset',
|
||||
value: supportedFileBeatDataSets,
|
||||
},
|
||||
],
|
||||
schema: {
|
||||
id: 'process.entity_id',
|
||||
parent: 'process.parent.entity_id',
|
||||
name: 'process.name',
|
||||
},
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
export function getFieldAsString(doc: unknown, field: string): string | undefined {
|
||||
const value = _.get(doc, field);
|
||||
|
|
|
@ -85,6 +85,7 @@ export const createMockTelemetryReceiver = (
|
|||
openPointInTime: jest.fn().mockReturnValue(Promise.resolve('test-pit-id')),
|
||||
getAlertsIndex: jest.fn().mockReturnValue('alerts-*'),
|
||||
fetchDiagnosticAlertsBatch: jest.fn().mockReturnValue(diagnosticsAlert ?? jest.fn()),
|
||||
getExperimentalFeatures: jest.fn().mockReturnValue(undefined),
|
||||
fetchEndpointMetrics: jest.fn().mockReturnValue(stubEndpointMetricsResponse),
|
||||
fetchEndpointPolicyResponses: jest.fn(),
|
||||
fetchPrebuiltRuleAlertsBatch: jest.fn().mockReturnValue(prebuiltRuleAlertsResponse),
|
||||
|
|
|
@ -382,7 +382,7 @@ export class TelemetryTimelineFetcher {
|
|||
const eventId = event._source ? event._source['event.id'] : 'unknown';
|
||||
const alertUUID = event._source ? event._source['kibana.alert.uuid'] : 'unknown';
|
||||
|
||||
const entities = resolverEntity([event]);
|
||||
const entities = resolverEntity([event], this.receiver.getExperimentalFeatures());
|
||||
|
||||
// Build Tree
|
||||
const tree = await this.receiver.buildProcessTree(
|
||||
|
|
|
@ -44,6 +44,7 @@ import type {
|
|||
} from '@kbn/fleet-plugin/server';
|
||||
import type { ExceptionListClient } from '@kbn/lists-plugin/server';
|
||||
import moment from 'moment';
|
||||
import type { ExperimentalFeatures } from '../../../common';
|
||||
import type { EndpointAppContextService } from '../../endpoint/endpoint_app_context_services';
|
||||
import {
|
||||
exceptionListItemToTelemetryEntry,
|
||||
|
@ -197,6 +198,8 @@ export interface ITelemetryReceiver {
|
|||
fetchValueListMetaData(interval: number): Promise<ValueListResponse>;
|
||||
|
||||
getAlertsIndex(): string | undefined;
|
||||
|
||||
getExperimentalFeatures(): ExperimentalFeatures | undefined;
|
||||
}
|
||||
|
||||
export class TelemetryReceiver implements ITelemetryReceiver {
|
||||
|
@ -211,6 +214,7 @@ export class TelemetryReceiver implements ITelemetryReceiver {
|
|||
private clusterInfo?: ESClusterInfo;
|
||||
private processTreeFetcher?: Fetcher;
|
||||
private packageService?: PackageService;
|
||||
private experimentalFeatures: ExperimentalFeatures | undefined;
|
||||
private readonly maxRecords = 10_000 as const;
|
||||
|
||||
constructor(logger: Logger) {
|
||||
|
@ -235,7 +239,7 @@ export class TelemetryReceiver implements ITelemetryReceiver {
|
|||
this.soClient =
|
||||
core?.savedObjects.createInternalRepository() as unknown as SavedObjectsClientContract;
|
||||
this.clusterInfo = await this.fetchClusterInfo();
|
||||
|
||||
this.experimentalFeatures = endpointContextService?.experimentalFeatures;
|
||||
const elasticsearch = core?.elasticsearch.client as unknown as IScopedClusterClient;
|
||||
this.processTreeFetcher = new Fetcher(elasticsearch);
|
||||
}
|
||||
|
@ -248,6 +252,10 @@ export class TelemetryReceiver implements ITelemetryReceiver {
|
|||
return this.alertsIndex;
|
||||
}
|
||||
|
||||
public getExperimentalFeatures(): ExperimentalFeatures | undefined {
|
||||
return this.experimentalFeatures;
|
||||
}
|
||||
|
||||
public async fetchDetectionRulesPackageVersion(): Promise<Installation | undefined> {
|
||||
return this.packageService?.asInternalUser.getInstallation(PREBUILT_RULES_PACKAGE_NAME);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue