mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[Session view] [serverless] versioned APIs and other improvements (#158982)
## Summary ### Issues: - https://github.com/elastic/kibana/issues/158687 - https://github.com/elastic/kibana/issues/158686 ### Fixes - API routes now versioned - Moved index selection logic to session_view plugin, added tests - fields now defined for all ES queries - types consolidated, and put under a latest/v1 export paradigm ### 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 --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
006499c12e
commit
ed0d341757
77 changed files with 727 additions and 566 deletions
|
@ -61,7 +61,7 @@
|
|||
"tags": [],
|
||||
"label": "EventAction",
|
||||
"description": [],
|
||||
"path": "x-pack/plugins/session_view/common/types/process_tree/index.ts",
|
||||
"path": "x-pack/plugins/session_view/common/index.ts",
|
||||
"deprecated": false,
|
||||
"trackAdoption": false,
|
||||
"initialIsOpen": false
|
||||
|
@ -106,7 +106,7 @@
|
|||
"tags": [],
|
||||
"label": "EventAction",
|
||||
"description": [],
|
||||
"path": "x-pack/plugins/session_view/common/types/process_tree/index.ts",
|
||||
"path": "x-pack/plugins/session_view/common/index.ts",
|
||||
"deprecated": false,
|
||||
"trackAdoption": false,
|
||||
"initialIsOpen": false
|
||||
|
@ -131,4 +131,4 @@
|
|||
],
|
||||
"objects": []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
export interface SessionViewConfig {
|
||||
processIndex: string;
|
||||
index: string;
|
||||
sessionEntityId: string;
|
||||
sessionStartTime: string;
|
||||
jumpToEntityId?: string;
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
export interface SessionViewConfig {
|
||||
processIndex: string;
|
||||
index: string;
|
||||
sessionEntityId: string;
|
||||
sessionStartTime: string;
|
||||
jumpToEntityId?: string;
|
||||
|
|
|
@ -39,7 +39,7 @@ import { useTourContext } from '../guided_onboarding_tour';
|
|||
import { AlertsCasesTourSteps, SecurityStepId } from '../guided_onboarding_tour/tour_config';
|
||||
import { isDetectionsAlertsTable } from '../top_n/helpers';
|
||||
import { GuidedOnboardingTourStep } from '../guided_onboarding_tour/tour_step';
|
||||
import { DEFAULT_ACTION_BUTTON_WIDTH, isAlert, getSessionViewProcessIndex } from './helpers';
|
||||
import { DEFAULT_ACTION_BUTTON_WIDTH, isAlert } from './helpers';
|
||||
|
||||
const ActionsContainer = styled.div`
|
||||
align-items: center;
|
||||
|
@ -152,13 +152,9 @@ const ActionsComponent: React.FC<ActionProps> = ({
|
|||
const { process, _id, _index, timestamp, kibana } = ecsData;
|
||||
const sessionEntityId = process?.entry_leader?.entity_id?.[0];
|
||||
const sessionStartTime = process?.entry_leader?.start?.[0];
|
||||
const processIndex = getSessionViewProcessIndex(kibana?.alert?.ancestors?.index?.[0] || _index);
|
||||
const index = kibana?.alert?.ancestors?.index?.[0] || _index;
|
||||
|
||||
if (
|
||||
processIndex === undefined ||
|
||||
sessionEntityId === undefined ||
|
||||
sessionStartTime === undefined
|
||||
) {
|
||||
if (index === undefined || sessionEntityId === undefined || sessionStartTime === undefined) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -168,7 +164,7 @@ const ActionsComponent: React.FC<ActionProps> = ({
|
|||
(investigatedAlertId && ecsData.kibana?.alert.original_time?.[0]) || timestamp;
|
||||
|
||||
return {
|
||||
processIndex,
|
||||
index,
|
||||
sessionEntityId,
|
||||
sessionStartTime,
|
||||
jumpToEntityId,
|
||||
|
|
|
@ -6,12 +6,7 @@
|
|||
*/
|
||||
|
||||
import { euiThemeVars } from '@kbn/ui-theme';
|
||||
import {
|
||||
DEFAULT_ACTION_BUTTON_WIDTH,
|
||||
getActionsColumnWidth,
|
||||
isAlert,
|
||||
getSessionViewProcessIndex,
|
||||
} from './helpers';
|
||||
import { DEFAULT_ACTION_BUTTON_WIDTH, getActionsColumnWidth, isAlert } from './helpers';
|
||||
|
||||
describe('isAlert', () => {
|
||||
test('it returns true when the eventType is an alert', () => {
|
||||
|
@ -53,67 +48,3 @@ describe('getActionsColumnWidth', () => {
|
|||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getSessionViewProcessIndex', () => {
|
||||
test('it returns process index for cloud_defend alert event index', () => {
|
||||
const result = getSessionViewProcessIndex(
|
||||
'.ds-logs-cloud_defend.alerts-default-2023.04.25-000001'
|
||||
);
|
||||
|
||||
expect(result).toEqual('logs-cloud_defend.process*');
|
||||
});
|
||||
|
||||
test('it returns process index for cloud_defend file event index', () => {
|
||||
const result = getSessionViewProcessIndex(
|
||||
'.ds-logs-cloud_defend.file-default-2023.04.25-000001'
|
||||
);
|
||||
|
||||
expect(result).toEqual('logs-cloud_defend.process*');
|
||||
});
|
||||
|
||||
test('it returns process index for cloud_defend process event index', () => {
|
||||
const result = getSessionViewProcessIndex(
|
||||
'.ds-logs-cloud_defend.process-default-2023.04.25-000001'
|
||||
);
|
||||
|
||||
expect(result).toEqual('logs-cloud_defend.process*');
|
||||
});
|
||||
|
||||
test('it returns process index for cloud_defend that includes cluster', () => {
|
||||
const result = getSessionViewProcessIndex(
|
||||
'aws_ec2:.ds-logs-cloud_defend.process-default-2023.04.25-000001'
|
||||
);
|
||||
|
||||
expect(result).toEqual('aws_ec2:logs-cloud_defend.process*');
|
||||
});
|
||||
|
||||
test('it returns process index for endpoint file index', () => {
|
||||
const result = getSessionViewProcessIndex(
|
||||
'.ds-logs-endpoint.events.file-default-2023.04.25-000001'
|
||||
);
|
||||
|
||||
expect(result).toEqual('logs-endpoint.events.process*');
|
||||
});
|
||||
|
||||
test('it returns process index for endpoint alerts index', () => {
|
||||
const result = getSessionViewProcessIndex('.ds-logs-endpoint.alerts-default-2023.04.25-000001');
|
||||
|
||||
expect(result).toEqual('logs-endpoint.events.process*');
|
||||
});
|
||||
|
||||
test('it returns process index for endpoint process index', () => {
|
||||
const result = getSessionViewProcessIndex(
|
||||
'.ds-logs-endpoint.events.process-default-2023.04.25-000001'
|
||||
);
|
||||
|
||||
expect(result).toEqual('logs-endpoint.events.process*');
|
||||
});
|
||||
|
||||
test('it returns process index for endpoint that includes cluster', () => {
|
||||
const result = getSessionViewProcessIndex(
|
||||
'azure-01:.ds-logs-endpoint.events.process-default-2023.04.25-000001'
|
||||
);
|
||||
|
||||
expect(result).toEqual('azure-01:logs-endpoint.events.process*');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -50,23 +50,3 @@ export const getActionsColumnWidth = (actionButtonCount: number): number => {
|
|||
|
||||
return contentWidth + leftRightCellPadding;
|
||||
};
|
||||
|
||||
// Currently both logs-endpoint.events.process* and logs-cloud_defend.process* are valid sources for session data.
|
||||
// To avoid cross cluster searches, the original index of the event is used to infer the index to find data for the
|
||||
// rest of the session.
|
||||
export const getSessionViewProcessIndex = (eventIndex?: string | null) => {
|
||||
if (!eventIndex) {
|
||||
return;
|
||||
}
|
||||
|
||||
const match = eventIndex.match(/([a-z0-9_-]+:)?\.ds-logs-(endpoint|cloud_defend)/i);
|
||||
const cluster = match?.[1];
|
||||
const clusterStr = cluster ? `${cluster}` : '';
|
||||
const service = match?.[2];
|
||||
|
||||
if (service === 'endpoint') {
|
||||
return `${clusterStr}logs-endpoint.events.process*`;
|
||||
} else if (service === 'cloud_defend') {
|
||||
return `${clusterStr}logs-cloud_defend.process*`;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import React, { useMemo, useEffect } from 'react';
|
||||
import type { Filter } from '@kbn/es-query';
|
||||
import { ENTRY_SESSION_ENTITY_ID_PROPERTY, EventAction } from '@kbn/session-view-plugin/public';
|
||||
import { ENTRY_SESSION_ENTITY_ID_PROPERTY } from '@kbn/session-view-plugin/public';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { EVENT_ACTION } from '@kbn/rule-data-utils';
|
||||
import { TableId, dataTableActions } from '@kbn/securitysolution-data-table';
|
||||
|
@ -38,9 +38,9 @@ export const defaultSessionsFilter: Required<Pick<Filter, 'meta' | 'query'>> = {
|
|||
bool: {
|
||||
// show sessions table results by filtering events where event.action is fork, exec, or end
|
||||
should: [
|
||||
{ term: { [EVENT_ACTION]: EventAction.exec } },
|
||||
{ term: { [EVENT_ACTION]: EventAction.fork } },
|
||||
{ term: { [EVENT_ACTION]: EventAction.end } },
|
||||
{ term: { [EVENT_ACTION]: 'exec' } },
|
||||
{ term: { [EVENT_ACTION]: 'fork' } },
|
||||
{ term: { [EVENT_ACTION]: 'end' } },
|
||||
],
|
||||
},
|
||||
},
|
||||
|
|
|
@ -14,7 +14,6 @@ import { SESSION_VIEW_ERROR_MESSAGE } from './translations';
|
|||
import { SESSION_VIEW_ERROR_TEST_ID, SESSION_VIEW_TEST_ID } from './test_ids';
|
||||
import { useKibana } from '../../../common/lib/kibana';
|
||||
import { useLeftPanelContext } from '../context';
|
||||
import { getSessionViewProcessIndex } from '../../../common/components/header_actions/helpers';
|
||||
|
||||
export const SESSION_VIEW_ID = 'session_view';
|
||||
export const SESSION_ENTITY_ID = 'process.entry_leader.entity_id';
|
||||
|
@ -28,13 +27,12 @@ export const SessionView: FC = () => {
|
|||
const { sessionView } = useKibana().services;
|
||||
const { getFieldsData, indexName } = useLeftPanelContext();
|
||||
|
||||
const processIndex = getSessionViewProcessIndex(
|
||||
getField(getFieldsData(KIBANA_ANCESTOR_INDEX)) || indexName
|
||||
);
|
||||
const ancestorIndex = getField(getFieldsData(KIBANA_ANCESTOR_INDEX)); // e.g in case of alert, we want to grab it's origin index
|
||||
const sessionEntityId = getField(getFieldsData(SESSION_ENTITY_ID));
|
||||
const sessionStartTime = getField(getFieldsData(SESSION_START_TIME));
|
||||
const index = ancestorIndex || indexName;
|
||||
|
||||
if (!processIndex || !sessionEntityId || !sessionStartTime) {
|
||||
if (!index || !sessionEntityId || !sessionStartTime) {
|
||||
return (
|
||||
<EuiEmptyPrompt
|
||||
iconType="error"
|
||||
|
@ -49,7 +47,7 @@ export const SessionView: FC = () => {
|
|||
return (
|
||||
<div data-test-subj={SESSION_VIEW_TEST_ID}>
|
||||
{sessionView.getSessionView({
|
||||
processIndex,
|
||||
index,
|
||||
sessionEntityId,
|
||||
sessionStartTime,
|
||||
isFullScreen: true,
|
||||
|
|
|
@ -253,7 +253,7 @@ describe('GraphOverlay', () => {
|
|||
[timelineId]: {
|
||||
...mockGlobalState.timeline.timelineById[timelineId],
|
||||
sessionViewConfig: {
|
||||
processIndex: 'logs-endpoint.events.process*',
|
||||
index: 'logs-endpoint.events.process*',
|
||||
sessionEntityId: 'testId',
|
||||
sessionStartTime: '2021-10-14T08:05:34.853Z',
|
||||
},
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
export const SESSION_VIEW_APP_ID = 'sessionView';
|
||||
|
||||
// routes
|
||||
export const CURRENT_API_VERSION = '1';
|
||||
export const PROCESS_EVENTS_ROUTE = '/internal/session_view/process_events';
|
||||
export const ALERTS_ROUTE = '/internal/session_view/alerts';
|
||||
export const ALERT_STATUS_ROUTE = '/internal/session_view/alert_status';
|
||||
|
@ -73,3 +74,105 @@ export const ALERT_ICONS: { [key: string]: string } = {
|
|||
};
|
||||
export const DEFAULT_ALERT_FILTER_VALUE = 'all';
|
||||
export const ALERT = 'alert';
|
||||
|
||||
// a list of fields to pull in ES queries for process_events_route, io_events_route and alerts.
|
||||
export const PROCESS_EVENT_FIELDS = [
|
||||
'@timestamp',
|
||||
'event.id',
|
||||
'event.kind',
|
||||
'event.type',
|
||||
'event.action',
|
||||
'event.category',
|
||||
'user.id',
|
||||
'user.name',
|
||||
'group.id',
|
||||
'group.name',
|
||||
'host.architecture',
|
||||
'host.hostname',
|
||||
'host.id',
|
||||
'host.ip',
|
||||
'host.mac',
|
||||
'host.name',
|
||||
'host.os.family',
|
||||
'host.os.full',
|
||||
'host.os.kernel',
|
||||
'host.os.name',
|
||||
'host.platform',
|
||||
'host.version',
|
||||
'host.boot.id',
|
||||
'process.entity_id',
|
||||
'process.args',
|
||||
'process.command_line',
|
||||
'process.executable',
|
||||
'process.name',
|
||||
'process.interactive',
|
||||
'process.working_directory',
|
||||
'process.pid',
|
||||
'process.start',
|
||||
'process.end',
|
||||
'process.user.id',
|
||||
'process.user.name',
|
||||
'process.group.id',
|
||||
'process.group.name',
|
||||
'process.supplemental_groups',
|
||||
'process.exit_code',
|
||||
'process.tty.*',
|
||||
'process.entry_leader.*',
|
||||
'process.session_leader.*',
|
||||
'process.group_leader.*',
|
||||
'process.parent.*',
|
||||
'process.previous',
|
||||
'container.id',
|
||||
'container.name',
|
||||
'container.image.name',
|
||||
'container.image.tag',
|
||||
'container.image.hash.all',
|
||||
'orchestrator.resource.name',
|
||||
'orchestrator.resource.type',
|
||||
'orchestrator.resource.ip',
|
||||
'orchestrator.resource.parent.type',
|
||||
'orchestrator.resource.labels',
|
||||
'orchestrator.resource.annotations',
|
||||
'orchestrator.namespace',
|
||||
'orchestrator.cluster.name',
|
||||
'orchestrator.cluster.id',
|
||||
'cloud.instance.name',
|
||||
'cloud.account.id',
|
||||
'cloud.project.id',
|
||||
'cloud.project.name',
|
||||
'cloud.provider',
|
||||
'cloud.region',
|
||||
];
|
||||
|
||||
export const IO_EVENT_FIELDS = PROCESS_EVENT_FIELDS.concat(['process.io.*']);
|
||||
|
||||
export const ALERT_FIELDS = PROCESS_EVENT_FIELDS.concat([
|
||||
'file.extension',
|
||||
'file.path',
|
||||
'file.name',
|
||||
'network.type',
|
||||
'network.transport',
|
||||
'network.protocol',
|
||||
'destination.address',
|
||||
'destination.ip',
|
||||
'destination.protocol',
|
||||
'source.address',
|
||||
'source.ip',
|
||||
'source.protocol',
|
||||
'kibana.alert.uuid',
|
||||
'kibana.alert.reason',
|
||||
'kibana.alert.workflow_status',
|
||||
'kibana.alert.status',
|
||||
'kibana.alert.original_time',
|
||||
'kibana.alert.original_event.action',
|
||||
'kibana.alert.rule.category',
|
||||
'kibana.alert.rule.consumer',
|
||||
'kibana.alert.rule.description',
|
||||
'kibana.alert.rule.enabled',
|
||||
'kibana.alert.rule.name',
|
||||
'kibana.alert.rule.risk_score',
|
||||
'kibana.alert.rule.severity',
|
||||
'kibana.alert.rule.uuid',
|
||||
'kibana.alert.rule.parameters.query',
|
||||
'kibana.alert.rule.query',
|
||||
]);
|
||||
|
|
|
@ -5,6 +5,30 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { ENTRY_SESSION_ENTITY_ID_PROPERTY } from './constants';
|
||||
import { EventAction } from './types/process_tree';
|
||||
export { ENTRY_SESSION_ENTITY_ID_PROPERTY, EventAction };
|
||||
export { ENTRY_SESSION_ENTITY_ID_PROPERTY } from './constants';
|
||||
export type {
|
||||
Aggregate,
|
||||
AlertStatusEventEntityIdMap,
|
||||
AlertTypeCount,
|
||||
EventAction,
|
||||
EventKind,
|
||||
IOLine,
|
||||
Process,
|
||||
ProcessEvent,
|
||||
ProcessEventAlert,
|
||||
ProcessEventAlertCategory,
|
||||
ProcessEventIPAddress,
|
||||
ProcessEventResults,
|
||||
ProcessEventsPage,
|
||||
ProcessFields,
|
||||
ProcessEventHost,
|
||||
ProcessEventContainer,
|
||||
ProcessEventOrchestrator,
|
||||
ProcessEventCloud,
|
||||
ProcessMap,
|
||||
ProcessStartMarker,
|
||||
Teletype,
|
||||
} from './types/latest';
|
||||
|
||||
import type * as v1 from './types/v1';
|
||||
export type { v1 };
|
||||
|
|
|
@ -5,17 +5,14 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import {
|
||||
import type {
|
||||
Process,
|
||||
ProcessEvent,
|
||||
ProcessEventsPage,
|
||||
ProcessFields,
|
||||
EventAction,
|
||||
EventKind,
|
||||
ProcessMap,
|
||||
AlertTypeCount,
|
||||
ProcessEventAlertCategory,
|
||||
} from '../../types/process_tree';
|
||||
} from '../..';
|
||||
|
||||
export const TEST_PROCESS_INDEX = 'logs-endpoint.events.process*';
|
||||
export const TEST_SESSION_START_TIME = '2021-10-14T08:05:34.853Z';
|
||||
|
@ -162,9 +159,9 @@ export const mockEvents: ProcessEvent[] = [
|
|||
start: '2021-11-23T15:25:04.210Z',
|
||||
},
|
||||
event: {
|
||||
action: EventAction.fork,
|
||||
action: 'fork',
|
||||
category: ['process'],
|
||||
kind: EventKind.event,
|
||||
kind: 'event',
|
||||
id: '1',
|
||||
},
|
||||
host: {
|
||||
|
@ -321,9 +318,9 @@ export const mockEvents: ProcessEvent[] = [
|
|||
start: '2021-11-23T15:25:04.218Z',
|
||||
},
|
||||
event: {
|
||||
action: EventAction.exec,
|
||||
action: 'exec',
|
||||
category: ['process'],
|
||||
kind: EventKind.event,
|
||||
kind: 'event',
|
||||
id: '2',
|
||||
},
|
||||
},
|
||||
|
@ -465,9 +462,9 @@ export const mockEvents: ProcessEvent[] = [
|
|||
working_directory: '/home/vagrant',
|
||||
},
|
||||
event: {
|
||||
action: EventAction.end,
|
||||
action: 'end',
|
||||
category: ['process'],
|
||||
kind: EventKind.event,
|
||||
kind: 'event',
|
||||
id: '3',
|
||||
},
|
||||
host: {
|
||||
|
@ -629,9 +626,9 @@ export const mockEvents: ProcessEvent[] = [
|
|||
working_directory: '/home/vagrant',
|
||||
},
|
||||
event: {
|
||||
action: EventAction.end,
|
||||
action: 'end',
|
||||
category: ['process'],
|
||||
kind: EventKind.event,
|
||||
kind: 'event',
|
||||
id: '4',
|
||||
},
|
||||
host: {
|
||||
|
@ -654,9 +651,9 @@ export const mockEvents: ProcessEvent[] = [
|
|||
] as ProcessEvent[];
|
||||
|
||||
export const mockAlertTypeCounts: AlertTypeCount[] = [
|
||||
{ category: ProcessEventAlertCategory.file, count: 0 },
|
||||
{ category: ProcessEventAlertCategory.network, count: 2 },
|
||||
{ category: ProcessEventAlertCategory.process, count: 1 },
|
||||
{ category: 'file', count: 0 },
|
||||
{ category: 'network', count: 2 },
|
||||
{ category: 'process', count: 1 },
|
||||
];
|
||||
|
||||
export const mockAlerts: ProcessEvent[] = [
|
||||
|
@ -679,7 +676,7 @@ export const mockAlerts: ProcessEvent[] = [
|
|||
reason: 'process event created low alert cmd test alert.',
|
||||
original_time: '2021-11-23T15:25:04.218Z',
|
||||
original_event: {
|
||||
action: EventAction.exec,
|
||||
action: 'exec',
|
||||
},
|
||||
uuid: '6bb22512e0e588d1a2449b61f164b216e366fba2de39e65d002ae734d71a6c38',
|
||||
},
|
||||
|
@ -824,9 +821,9 @@ export const mockAlerts: ProcessEvent[] = [
|
|||
},
|
||||
},
|
||||
event: {
|
||||
action: EventAction.exec,
|
||||
action: 'exec',
|
||||
category: ['process'],
|
||||
kind: EventKind.signal,
|
||||
kind: 'signal',
|
||||
id: '5',
|
||||
},
|
||||
host: {
|
||||
|
@ -865,7 +862,7 @@ export const mockAlerts: ProcessEvent[] = [
|
|||
reason: 'process event created low alert cmd test alert.',
|
||||
original_time: '2021-11-23T15:25:05.202Z',
|
||||
original_event: {
|
||||
action: EventAction.end,
|
||||
action: 'end',
|
||||
},
|
||||
uuid: '2873463965b70d37ab9b2b3a90ac5a03b88e76e94ad33568285cadcefc38ed75',
|
||||
},
|
||||
|
@ -1011,9 +1008,9 @@ export const mockAlerts: ProcessEvent[] = [
|
|||
},
|
||||
},
|
||||
event: {
|
||||
action: EventAction.end,
|
||||
action: 'end',
|
||||
category: ['process'],
|
||||
kind: EventKind.signal,
|
||||
kind: 'signal',
|
||||
id: '6',
|
||||
},
|
||||
host: {
|
||||
|
@ -1035,7 +1032,7 @@ export const mockAlerts: ProcessEvent[] = [
|
|||
},
|
||||
];
|
||||
|
||||
export const mockFileAlert = {
|
||||
export const mockFileAlert: ProcessEvent = {
|
||||
kibana: {
|
||||
alert: {
|
||||
rule: {
|
||||
|
@ -1054,7 +1051,7 @@ export const mockFileAlert = {
|
|||
reason: 'process event created low alert File telemetry.',
|
||||
original_time: '2021-11-23T15:25:05.202Z',
|
||||
original_event: {
|
||||
action: EventAction.end,
|
||||
action: 'end',
|
||||
},
|
||||
uuid: '2873463965b70d37ab9b2b3a90ac5a03b88e76e94ad33568285cadcefc38ed75',
|
||||
},
|
||||
|
@ -1205,9 +1202,9 @@ export const mockFileAlert = {
|
|||
name: 'new_file.txt',
|
||||
},
|
||||
event: {
|
||||
action: EventAction.exec,
|
||||
action: 'exec',
|
||||
category: ['file'],
|
||||
kind: EventKind.signal,
|
||||
kind: 'signal',
|
||||
id: '6',
|
||||
},
|
||||
host: {
|
||||
|
@ -1228,7 +1225,7 @@ export const mockFileAlert = {
|
|||
},
|
||||
};
|
||||
|
||||
export const mockNetworkAlert = {
|
||||
export const mockNetworkAlert: ProcessEvent = {
|
||||
kibana: {
|
||||
alert: {
|
||||
rule: {
|
||||
|
@ -1247,7 +1244,7 @@ export const mockNetworkAlert = {
|
|||
reason: 'process event created low alert File telemetry.',
|
||||
original_time: '2021-11-23T15:25:05.202Z',
|
||||
original_event: {
|
||||
action: EventAction.end,
|
||||
action: 'end',
|
||||
},
|
||||
uuid: '2873463965b70d37ab9b2b3a90ac5a03b88e76e94ad33568285cadcefc38ed75',
|
||||
},
|
||||
|
@ -1408,9 +1405,9 @@ export const mockNetworkAlert = {
|
|||
port: 1111,
|
||||
},
|
||||
event: {
|
||||
action: EventAction.exec,
|
||||
action: 'exec',
|
||||
category: ['network'],
|
||||
kind: EventKind.signal,
|
||||
kind: 'signal',
|
||||
id: '6',
|
||||
},
|
||||
host: {
|
||||
|
@ -1687,9 +1684,9 @@ export const childProcessMock: Process = {
|
|||
({
|
||||
'@timestamp': '2021-11-23T15:25:05.210Z',
|
||||
event: {
|
||||
kind: EventKind.event,
|
||||
kind: 'event',
|
||||
category: ['process'],
|
||||
action: EventAction.exec,
|
||||
action: 'exec',
|
||||
id: '1',
|
||||
},
|
||||
host: {
|
||||
|
@ -1772,9 +1769,9 @@ export const processMock: Process = {
|
|||
({
|
||||
'@timestamp': '2021-11-23T15:25:04.210Z',
|
||||
event: {
|
||||
kind: EventKind.event,
|
||||
kind: 'event',
|
||||
category: ['process'],
|
||||
action: EventAction.exec,
|
||||
action: 'exec',
|
||||
id: '2',
|
||||
},
|
||||
host: {
|
||||
|
@ -1962,15 +1959,15 @@ export const mockProcessMap = mockEvents.reduce(
|
|||
autoExpand: false,
|
||||
searchMatched: null,
|
||||
orphans: [],
|
||||
addEvent: (_) => undefined,
|
||||
addAlert: (_) => undefined,
|
||||
addChild: (_) => undefined,
|
||||
addEvent: () => undefined,
|
||||
addAlert: () => undefined,
|
||||
addChild: () => undefined,
|
||||
clearSearch: () => undefined,
|
||||
getChildren: () => [],
|
||||
hasOutput: () => false,
|
||||
hasAlerts: () => false,
|
||||
getAlerts: () => [],
|
||||
updateAlertsStatus: (_) => undefined,
|
||||
updateAlertsStatus: () => undefined,
|
||||
hasExec: () => false,
|
||||
getOutput: () => '',
|
||||
getDetails: () => event,
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { ProcessEventResults } from '../../types/process_tree';
|
||||
import type { ProcessEventResults } from '../..';
|
||||
|
||||
export const sessionViewIOEventsMock: ProcessEventResults = {
|
||||
events: [
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { ProcessEventResults } from '../../types/process_tree';
|
||||
import type { ProcessEventResults } from '../..';
|
||||
|
||||
export const sessionViewProcessEventsMock: ProcessEventResults = {
|
||||
events: [
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { ProcessEventResults } from '../../types/process_tree';
|
||||
import type { ProcessEventResults } from '../..';
|
||||
|
||||
export const sessionViewProcessEventsMergedMock: ProcessEventResults = {
|
||||
events: [
|
||||
|
|
|
@ -4,8 +4,4 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
export interface Aggregate {
|
||||
key: string | number;
|
||||
doc_count: number;
|
||||
}
|
||||
export * from './v1';
|
|
@ -1,11 +0,0 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export interface DisplayOptionsState {
|
||||
timestamp: boolean;
|
||||
verboseMode: boolean;
|
||||
}
|
|
@ -4,6 +4,10 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
export interface Aggregate {
|
||||
key: string | number;
|
||||
doc_count: number;
|
||||
}
|
||||
|
||||
export interface AlertStatusEventEntityIdMap {
|
||||
[alertUuid: string]: {
|
||||
|
@ -12,30 +16,15 @@ export interface AlertStatusEventEntityIdMap {
|
|||
};
|
||||
}
|
||||
|
||||
export enum ProcessEventAlertCategory {
|
||||
all = 'all',
|
||||
file = 'file',
|
||||
network = 'network',
|
||||
process = 'process',
|
||||
}
|
||||
|
||||
export interface AlertTypeCount {
|
||||
category: ProcessEventAlertCategory;
|
||||
count: number;
|
||||
}
|
||||
export type DefaultAlertFilterType = 'all';
|
||||
export type EventKind = 'event' | 'signal';
|
||||
export type EventCategory = 'process' | 'file' | 'network';
|
||||
export type EventAction = 'fork' | 'exec' | 'end' | 'text_output';
|
||||
|
||||
export enum EventKind {
|
||||
event = 'event',
|
||||
signal = 'signal',
|
||||
}
|
||||
|
||||
export enum EventAction {
|
||||
fork = 'fork',
|
||||
exec = 'exec',
|
||||
end = 'end',
|
||||
text_output = 'text_output',
|
||||
}
|
||||
export type ProcessEventAlertCategory = EventCategory | 'all';
|
||||
|
||||
export interface User {
|
||||
id?: string;
|
||||
|
@ -107,10 +96,6 @@ export interface ProcessFields {
|
|||
end?: string;
|
||||
user?: User;
|
||||
group?: Group;
|
||||
real_user?: User;
|
||||
real_group?: Group;
|
||||
saved_user?: User;
|
||||
saved_group?: Group;
|
||||
supplemental_groups?: Group[];
|
||||
exit_code?: number;
|
||||
entry_meta?: EntryMeta;
|
||||
|
@ -191,7 +176,7 @@ export interface ProcessEvent {
|
|||
'@timestamp'?: string;
|
||||
event?: {
|
||||
kind?: EventKind;
|
||||
category?: string | string[];
|
||||
category?: EventCategory | EventCategory[];
|
||||
action?: EventAction | EventAction[];
|
||||
type?: string | string[];
|
||||
id?: string;
|
||||
|
@ -273,6 +258,8 @@ export interface ProcessEventOrchestrator {
|
|||
parent?: {
|
||||
type?: string;
|
||||
};
|
||||
labels?: string[];
|
||||
annotations?: string[];
|
||||
};
|
||||
namespace?: string;
|
||||
cluster?: {
|
|
@ -5,19 +5,18 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { ProcessEventAlertCategory } from '../types/process_tree';
|
||||
import { getAlertIconTooltipContent } from './alert_icon_tooltip_content';
|
||||
|
||||
describe('getAlertTypeTooltipContent(category)', () => {
|
||||
it('should display `File alert` for tooltip content', () => {
|
||||
expect(getAlertIconTooltipContent(ProcessEventAlertCategory.file)).toEqual('File alert');
|
||||
expect(getAlertIconTooltipContent('file')).toEqual('File alert');
|
||||
});
|
||||
|
||||
it('should display `Process alert` for tooltip content', () => {
|
||||
expect(getAlertIconTooltipContent(ProcessEventAlertCategory.process)).toEqual('Process alert');
|
||||
expect(getAlertIconTooltipContent('process')).toEqual('Process alert');
|
||||
});
|
||||
|
||||
it('should display `Network alert` for tooltip content', () => {
|
||||
expect(getAlertIconTooltipContent(ProcessEventAlertCategory.network)).toEqual('Network alert');
|
||||
expect(getAlertIconTooltipContent('network')).toEqual('Network alert');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,16 +5,18 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { ProcessEventAlertCategory } from '../types/process_tree';
|
||||
import { ProcessEventAlertCategory } from '..';
|
||||
import * as i18n from '../translations';
|
||||
|
||||
export const getAlertIconTooltipContent = (processEventAlertCategory: string) => {
|
||||
export const getAlertIconTooltipContent = (
|
||||
processEventAlertCategory: ProcessEventAlertCategory
|
||||
) => {
|
||||
let tooltipContent = '';
|
||||
switch (processEventAlertCategory) {
|
||||
case ProcessEventAlertCategory.file:
|
||||
case 'file':
|
||||
tooltipContent = i18n.ALERT_TYPE_TOOLTIP_FILE;
|
||||
break;
|
||||
case ProcessEventAlertCategory.network:
|
||||
case 'network':
|
||||
tooltipContent = i18n.ALERT_TYPE_TOOLTIP_NETWORK;
|
||||
break;
|
||||
default:
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { Process } from '../types/process_tree';
|
||||
import { Process } from '..';
|
||||
|
||||
export const sortProcesses = (a: Process, b: Process) => {
|
||||
const eventAStartTime = a.getDetails()?.process?.start || 0;
|
||||
|
|
|
@ -16,7 +16,7 @@ import {
|
|||
JUMP_TO_PROCESS_TEST_ID,
|
||||
} from '.';
|
||||
import { mockAlerts } from '../../../common/mocks/constants/session_view_process.mock';
|
||||
import { ProcessEvent } from '../../../common/types/process_tree';
|
||||
import { ProcessEvent } from '../../../common';
|
||||
|
||||
describe('DetailPanelAlertActions component', () => {
|
||||
let render: () => ReturnType<AppContextTestRender['render']>;
|
||||
|
|
|
@ -8,7 +8,7 @@ import React, { useState, useCallback } from 'react';
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { EuiPopover, EuiContextMenuPanel, EuiButtonIcon, EuiContextMenuItem } from '@elastic/eui';
|
||||
import { ProcessEvent } from '../../../common/types/process_tree';
|
||||
import { ProcessEvent } from '../../../common';
|
||||
|
||||
export const BUTTON_TEST_ID = 'sessionView:detailPanelAlertActionsBtn';
|
||||
export const SHOW_DETAILS_TEST_ID = 'sessionView:detailPanelAlertActionShowDetails';
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
import React from 'react';
|
||||
import { EuiIcon, EuiText, EuiAccordion, EuiNotificationBadge } from '@elastic/eui';
|
||||
import { ProcessEvent } from '../../../common/types/process_tree';
|
||||
import type { ProcessEvent } from '../../../common';
|
||||
import { dataOrDash } from '../../utils/data_or_dash';
|
||||
import { useStyles } from '../detail_panel_alert_list_item/styles';
|
||||
import { DetailPanelAlertListItem } from '../detail_panel_alert_list_item';
|
||||
|
|
|
@ -20,7 +20,7 @@ import {
|
|||
} from '@elastic/eui';
|
||||
import { getAlertIconTooltipContent } from '../../../common/utils/alert_icon_tooltip_content';
|
||||
import { ALERT_ICONS } from '../../../common/constants';
|
||||
import { ProcessEvent, ProcessEventAlertCategory } from '../../../common/types/process_tree';
|
||||
import type { ProcessEvent, ProcessEventAlertCategory } from '../../../common';
|
||||
import { useStyles } from './styles';
|
||||
import { DetailPanelAlertActions } from '../detail_panel_alert_actions';
|
||||
import { dataOrDash } from '../../utils/data_or_dash';
|
||||
|
@ -64,12 +64,12 @@ export const DetailPanelAlertListItem = ({
|
|||
const { args, name: processName } = event.process ?? {};
|
||||
const { event: processEvent } = event;
|
||||
const forceState = !isInvestigated ? 'open' : undefined;
|
||||
const category = Array.isArray(processEvent?.category)
|
||||
? processEvent?.category?.[0]
|
||||
: processEvent?.category;
|
||||
const processEventAlertCategory = category ?? ProcessEventAlertCategory.process;
|
||||
const category = (
|
||||
Array.isArray(processEvent?.category) ? processEvent?.category?.[0] : processEvent?.category
|
||||
) as ProcessEventAlertCategory;
|
||||
const processEventAlertCategory: ProcessEventAlertCategory = category ?? 'process';
|
||||
const alertCategoryDetailDisplayText =
|
||||
category !== ProcessEventAlertCategory.process
|
||||
category !== 'process'
|
||||
? `${dataOrDash(processName)} ${getAlertCategoryDisplayText(event, category)}`
|
||||
: dataOrDash(args?.join(' '));
|
||||
const alertIconTooltipContent = getAlertIconTooltipContent(processEventAlertCategory);
|
||||
|
|
|
@ -9,7 +9,7 @@ import { EuiEmptyPrompt, EuiButtonGroup, EuiHorizontalRule, EuiButton } from '@e
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { groupBy } from 'lodash';
|
||||
import { ProcessEvent } from '../../../common/types/process_tree';
|
||||
import type { ProcessEvent } from '../../../common';
|
||||
import { useStyles } from './styles';
|
||||
import { DetailPanelAlertListItem } from '../detail_panel_alert_list_item';
|
||||
import { DetailPanelAlertGroupItem } from '../detail_panel_alert_group_item';
|
||||
|
|
|
@ -4,12 +4,12 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import {
|
||||
import type {
|
||||
ProcessEventHost,
|
||||
ProcessEventContainer,
|
||||
ProcessEventOrchestrator,
|
||||
ProcessEventCloud,
|
||||
} from '../../../common/types/process_tree';
|
||||
} from '../../../common';
|
||||
import { DASH } from '../../constants';
|
||||
import { getHostData, getContainerData, getOrchestratorData, getCloudData } from './helpers';
|
||||
|
||||
|
|
|
@ -5,12 +5,12 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import {
|
||||
import type {
|
||||
ProcessEventHost,
|
||||
ProcessEventContainer,
|
||||
ProcessEventOrchestrator,
|
||||
ProcessEventCloud,
|
||||
} from '../../../common/types/process_tree';
|
||||
} from '../../../common';
|
||||
import { DASH } from '../../constants';
|
||||
import {
|
||||
DetailPanelHost,
|
||||
|
|
|
@ -7,12 +7,12 @@
|
|||
|
||||
import React from 'react';
|
||||
import { AppContextTestRender, createAppRootMockRenderer } from '../../test';
|
||||
import {
|
||||
import type {
|
||||
ProcessEventHost,
|
||||
ProcessEventContainer,
|
||||
ProcessEventOrchestrator,
|
||||
ProcessEventCloud,
|
||||
} from '../../../common/types/process_tree';
|
||||
} from '../../../common';
|
||||
import { DetailPanelMetadataTab } from '.';
|
||||
|
||||
const TEST_ARCHITECTURE = 'x86_64';
|
||||
|
|
|
@ -7,12 +7,12 @@
|
|||
import React, { useMemo } from 'react';
|
||||
import { EuiTextColor, EuiPanel } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
import type {
|
||||
ProcessEventHost,
|
||||
ProcessEventContainer,
|
||||
ProcessEventOrchestrator,
|
||||
ProcessEventCloud,
|
||||
} from '../../../common/types/process_tree';
|
||||
} from '../../../common';
|
||||
import { DetailPanelAccordion } from '../detail_panel_accordion';
|
||||
import { DetailPanelCopy } from '../detail_panel_copy';
|
||||
import { DetailPanelListItem } from '../detail_panel_list_item';
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EventAction, Process, ProcessFields } from '../../../common/types/process_tree';
|
||||
import type { Process, ProcessFields } from '../../../common';
|
||||
import { DetailPanelProcess, DetailPanelProcessLeader } from '../../types';
|
||||
import { DASH } from '../../constants';
|
||||
import { dataOrDash } from '../../utils/data_or_dash';
|
||||
|
@ -132,7 +132,7 @@ export const getDetailPanelProcess = (process: Process | null): DetailPanelProce
|
|||
}
|
||||
|
||||
processData.executable = executables.map((exe, i) => {
|
||||
const action = i === 0 ? EventAction.fork : EventAction.exec;
|
||||
const action = i === 0 ? 'fork' : 'exec';
|
||||
|
||||
return [exe, `(${action})`];
|
||||
});
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
import React, { ReactNode, useCallback, useMemo } from 'react';
|
||||
import { EuiTextColor } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { Process } from '../../../common/types/process_tree';
|
||||
import type { Process } from '../../../common';
|
||||
import { DetailPanelAccordion } from '../detail_panel_accordion';
|
||||
import { DetailPanelCopy } from '../detail_panel_copy';
|
||||
import { DetailPanelDescriptionList } from '../detail_panel_description_list';
|
||||
|
|
|
@ -10,12 +10,12 @@ import {
|
|||
mockAlerts,
|
||||
mockProcessMap,
|
||||
} from '../../../common/mocks/constants/session_view_process.mock';
|
||||
import {
|
||||
import type {
|
||||
AlertStatusEventEntityIdMap,
|
||||
Process,
|
||||
ProcessMap,
|
||||
ProcessEvent,
|
||||
} from '../../../common/types/process_tree';
|
||||
} from '../../../common';
|
||||
import { ALERT_STATUS } from '../../../common/constants';
|
||||
import {
|
||||
updateAlertEventStatus,
|
||||
|
|
|
@ -7,14 +7,13 @@
|
|||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { escapeRegExp } from 'lodash';
|
||||
import { sortProcesses } from '../../../common/utils/sort_processes';
|
||||
import {
|
||||
import type {
|
||||
AlertStatusEventEntityIdMap,
|
||||
EventKind,
|
||||
Process,
|
||||
ProcessEvent,
|
||||
ProcessMap,
|
||||
ProcessFields,
|
||||
} from '../../../common/types/process_tree';
|
||||
} from '../../../common';
|
||||
import { ProcessImpl } from './hooks';
|
||||
|
||||
// Creates an instance of Process, from a nested leader process fieldset
|
||||
|
@ -94,7 +93,7 @@ export const updateProcessMap = (processMap: ProcessMap, events: ProcessEvent[])
|
|||
|
||||
if (event.kibana?.alert) {
|
||||
process.addAlert(event);
|
||||
} else if (event.event?.kind === EventKind.event) {
|
||||
} else if (event.event?.kind === 'event') {
|
||||
process.addEvent(event);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EventAction } from '../../../common/types/process_tree';
|
||||
import { mockEvents } from '../../../common/mocks/constants/session_view_process.mock';
|
||||
import { ProcessImpl } from './hooks';
|
||||
|
||||
|
@ -23,7 +22,7 @@ describe('ProcessTree hooks', () => {
|
|||
|
||||
result = process.getDetails();
|
||||
|
||||
expect(result.event?.action).toEqual(EventAction.exec);
|
||||
expect(result.event?.action).toEqual('exec');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
import memoizeOne from 'memoize-one';
|
||||
import { sortedUniqBy } from 'lodash';
|
||||
import { useState, useEffect, useMemo } from 'react';
|
||||
import {
|
||||
import type {
|
||||
AlertStatusEventEntityIdMap,
|
||||
EventAction,
|
||||
EventKind,
|
||||
|
@ -15,7 +15,7 @@ import {
|
|||
ProcessEvent,
|
||||
ProcessMap,
|
||||
ProcessEventsPage,
|
||||
} from '../../../common/types/process_tree';
|
||||
} from '../../../common';
|
||||
import {
|
||||
inferProcessFromLeaderInfo,
|
||||
updateAlertEventStatus,
|
||||
|
@ -140,7 +140,7 @@ export class ProcessImpl implements Process {
|
|||
}
|
||||
|
||||
hasOutput() {
|
||||
return !!this.findEventByAction(this.events, EventAction.text_output);
|
||||
return !!this.findEventByAction(this.events, 'text_output');
|
||||
}
|
||||
|
||||
hasAlerts() {
|
||||
|
@ -164,11 +164,11 @@ export class ProcessImpl implements Process {
|
|||
}
|
||||
|
||||
hasExec() {
|
||||
return !!this.findEventByAction(this.events, EventAction.exec);
|
||||
return !!this.findEventByAction(this.events, 'exec');
|
||||
}
|
||||
|
||||
hasExited() {
|
||||
return !!this.findEventByAction(this.events, EventAction.end);
|
||||
return !!this.findEventByAction(this.events, 'end');
|
||||
}
|
||||
|
||||
getDetails() {
|
||||
|
@ -181,7 +181,7 @@ export class ProcessImpl implements Process {
|
|||
}
|
||||
|
||||
getEndTime() {
|
||||
const endEvent = this.findEventByAction(this.events, EventAction.end);
|
||||
const endEvent = this.findEventByAction(this.events, 'end');
|
||||
return endEvent?.['@timestamp'] || '';
|
||||
}
|
||||
|
||||
|
@ -241,11 +241,7 @@ export class ProcessImpl implements Process {
|
|||
const filtered = events.filter((processEvent) => {
|
||||
const action = processEvent?.event?.action;
|
||||
|
||||
return (
|
||||
action?.includes(EventAction.fork) ||
|
||||
action?.includes(EventAction.exec) ||
|
||||
action?.includes(EventAction.end)
|
||||
);
|
||||
return action?.includes('fork') || action?.includes('exec') || action?.includes('end');
|
||||
});
|
||||
|
||||
// there are some anomalous processes which are omitting event.action
|
||||
|
|
|
@ -11,7 +11,7 @@ import {
|
|||
nullMockData,
|
||||
deepNullMockData,
|
||||
} from '../../../common/mocks/constants/session_view_process.mock';
|
||||
import { Process } from '../../../common/types/process_tree';
|
||||
import type { Process } from '../../../common';
|
||||
import { AppContextTestRender, createAppRootMockRenderer } from '../../test';
|
||||
import { ProcessTreeDeps, ProcessTree } from '.';
|
||||
import { useDateFormat } from '../../hooks';
|
||||
|
|
|
@ -11,11 +11,7 @@ import { BackToInvestigatedAlert } from '../back_to_investigated_alert';
|
|||
import { useProcessTree } from './hooks';
|
||||
import { collapseProcessTree } from './helpers';
|
||||
import { ProcessTreeLoadMoreButton } from '../process_tree_load_more_button';
|
||||
import {
|
||||
AlertStatusEventEntityIdMap,
|
||||
Process,
|
||||
ProcessEventsPage,
|
||||
} from '../../../common/types/process_tree';
|
||||
import type { AlertStatusEventEntityIdMap, Process, ProcessEventsPage } from '../../../common';
|
||||
import { useScroll } from '../../hooks/use_scroll';
|
||||
import { useStyles } from './styles';
|
||||
import { PROCESS_EVENTS_PER_PAGE } from '../../../common/constants';
|
||||
|
|
|
@ -17,11 +17,7 @@ import {
|
|||
} from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { ALERT_ICONS } from '../../../common/constants';
|
||||
import {
|
||||
ProcessEvent,
|
||||
ProcessEventAlert,
|
||||
ProcessEventAlertCategory,
|
||||
} from '../../../common/types/process_tree';
|
||||
import type { ProcessEvent, ProcessEventAlert, ProcessEventAlertCategory } from '../../../common';
|
||||
import { dataOrDash } from '../../utils/data_or_dash';
|
||||
import { getBadgeColorFromAlertStatus } from './helpers';
|
||||
import { useStyles } from './styles';
|
||||
|
@ -48,7 +44,7 @@ export const ProcessTreeAlert = ({
|
|||
|
||||
const { event } = alert;
|
||||
const { uuid, rule, workflow_status: status } = alert.kibana?.alert || {};
|
||||
const category = event?.category?.[0];
|
||||
const category = event?.category?.[0] as ProcessEventAlertCategory;
|
||||
const alertIconType = useMemo(() => {
|
||||
if (category && category in ALERT_ICONS) return ALERT_ICONS[category];
|
||||
return ALERT_ICONS.process;
|
||||
|
@ -76,7 +72,7 @@ export const ProcessTreeAlert = ({
|
|||
return null;
|
||||
}
|
||||
const { name } = rule;
|
||||
const processEventAlertCategory = category ?? ProcessEventAlertCategory.process;
|
||||
const processEventAlertCategory: ProcessEventAlertCategory = category ?? 'process';
|
||||
const alertCategoryDetailDisplayText = getAlertCategoryDisplayText(alert, category);
|
||||
const alertIconTooltipContent = getAlertIconTooltipContent(processEventAlertCategory);
|
||||
const eventType = Array.isArray(event?.type) ? event?.type?.[0] : event?.type;
|
||||
|
|
|
@ -7,13 +7,12 @@
|
|||
|
||||
import React, { useState, useEffect, useRef, MouseEvent, useCallback, useMemo } from 'react';
|
||||
import { useStyles } from './styles';
|
||||
import {
|
||||
import type {
|
||||
ProcessEventAlertCategory,
|
||||
DefaultAlertFilterType,
|
||||
ProcessEvent,
|
||||
ProcessEventAlert,
|
||||
AlertTypeCount,
|
||||
} from '../../../common/types/process_tree';
|
||||
} from '../../../common';
|
||||
import { ProcessTreeAlert } from '../process_tree_alert';
|
||||
import { DEFAULT_ALERT_FILTER_VALUE, MOUSE_EVENT_PLACEHOLDER } from '../../../common/constants';
|
||||
import { ProcessTreeAlertsFilter } from '../process_tree_alerts_filter';
|
||||
|
@ -36,9 +35,8 @@ export function ProcessTreeAlerts({
|
|||
onShowAlertDetails,
|
||||
}: ProcessTreeAlertsDeps) {
|
||||
const [selectedAlert, setSelectedAlert] = useState<ProcessEventAlert | null>(null);
|
||||
const [selectedProcessEventAlertCategory, setSelectedProcessEventAlertCategory] = useState<
|
||||
ProcessEventAlertCategory | DefaultAlertFilterType
|
||||
>(DEFAULT_ALERT_FILTER_VALUE);
|
||||
const [selectedProcessEventAlertCategory, setSelectedProcessEventAlertCategory] =
|
||||
useState<ProcessEventAlertCategory>(DEFAULT_ALERT_FILTER_VALUE);
|
||||
const styles = useStyles();
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -83,11 +81,7 @@ export function ProcessTreeAlerts({
|
|||
);
|
||||
|
||||
const handleProcessEventAlertCategorySelected = useCallback((eventCategory) => {
|
||||
if (ProcessEventAlertCategory.hasOwnProperty(eventCategory)) {
|
||||
setSelectedProcessEventAlertCategory(eventCategory);
|
||||
} else {
|
||||
setSelectedProcessEventAlertCategory(ProcessEventAlertCategory.all);
|
||||
}
|
||||
setSelectedProcessEventAlertCategory(eventCategory);
|
||||
}, []);
|
||||
|
||||
const filteredProcessEventAlerts = useMemo(() => {
|
||||
|
|
|
@ -18,11 +18,7 @@ import {
|
|||
} from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { DEFAULT_ALERT_FILTER_VALUE } from '../../../common/constants';
|
||||
import {
|
||||
ProcessEventAlertCategory,
|
||||
DefaultAlertFilterType,
|
||||
AlertTypeCount,
|
||||
} from '../../../common/types/process_tree';
|
||||
import type { ProcessEventAlertCategory, AlertTypeCount } from '../../../common';
|
||||
import { useStyles } from './styles';
|
||||
import { FILTER_MENU_OPTIONS, SELECTED_PROCESS } from './translations';
|
||||
|
||||
|
@ -30,7 +26,7 @@ export interface ProcessTreeAlertsFilterDeps {
|
|||
totalAlertsCount: number;
|
||||
alertTypeCounts: AlertTypeCount[];
|
||||
filteredAlertsCount: number;
|
||||
onAlertEventCategorySelected: (value: ProcessEventAlertCategory | DefaultAlertFilterType) => void;
|
||||
onAlertEventCategorySelected: (value: ProcessEventAlertCategory) => void;
|
||||
}
|
||||
|
||||
export const ProcessTreeAlertsFilter = ({
|
||||
|
@ -42,7 +38,7 @@ export const ProcessTreeAlertsFilter = ({
|
|||
const { filterStatus, popover } = useStyles();
|
||||
|
||||
const [selectedProcessEventAlertCategory, setSelectedProcessEventAlertCategory] =
|
||||
useState<ProcessEventAlertCategory>(ProcessEventAlertCategory.all);
|
||||
useState<ProcessEventAlertCategory>('all');
|
||||
|
||||
const [isPopoverOpen, setPopover] = useState(false);
|
||||
|
||||
|
@ -88,7 +84,7 @@ export const ProcessTreeAlertsFilter = ({
|
|||
);
|
||||
|
||||
const alertEventCategoryFilterMenuItems = useMemo(() => {
|
||||
const getIconType = (eventCategory: ProcessEventAlertCategory | DefaultAlertFilterType) => {
|
||||
const getIconType = (eventCategory: ProcessEventAlertCategory) => {
|
||||
return eventCategory === selectedProcessEventAlertCategory ? 'check' : 'empty';
|
||||
};
|
||||
|
||||
|
@ -114,7 +110,7 @@ export const ProcessTreeAlertsFilter = ({
|
|||
icon={getIconType(DEFAULT_ALERT_FILTER_VALUE)}
|
||||
onClick={onSelectedProcessEventAlertCategory}
|
||||
>
|
||||
{FILTER_MENU_OPTIONS[ProcessEventAlertCategory.all]}
|
||||
{FILTER_MENU_OPTIONS.all}
|
||||
</EuiContextMenuItem>,
|
||||
...alertEventFilterMenuItems,
|
||||
];
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
import React, { useMemo } from 'react';
|
||||
import { EuiButton, EuiIcon } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { AlertTypeCount } from '../../../common/types/process_tree';
|
||||
import type { AlertTypeCount } from '../../../common';
|
||||
import { useButtonStyles } from './use_button_styles';
|
||||
import { ALERT_ICONS } from '../../../common/constants';
|
||||
|
||||
|
|
|
@ -24,11 +24,7 @@ import { EuiButton, EuiIcon, EuiToolTip, formatDate, EuiButtonIcon } from '@elas
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { chain } from 'lodash';
|
||||
import {
|
||||
AlertTypeCount,
|
||||
Process,
|
||||
ProcessEventAlertCategory,
|
||||
} from '../../../common/types/process_tree';
|
||||
import type { AlertTypeCount, Process, ProcessEventAlertCategory } from '../../../common';
|
||||
import { dataOrDash } from '../../utils/data_or_dash';
|
||||
import { useVisible } from '../../hooks/use_visible';
|
||||
import { ProcessTreeAlerts } from '../process_tree_alerts';
|
||||
|
|
|
@ -9,12 +9,12 @@ import { useQuery, useInfiniteQuery } from '@tanstack/react-query';
|
|||
import { EuiSearchBarOnChangeArgs } from '@elastic/eui';
|
||||
import { CoreStart } from '@kbn/core/public';
|
||||
import { useKibana } from '@kbn/kibana-react-plugin/public';
|
||||
import {
|
||||
import type {
|
||||
AlertStatusEventEntityIdMap,
|
||||
EventAction,
|
||||
ProcessEvent,
|
||||
ProcessEventResults,
|
||||
} from '../../../common/types/process_tree';
|
||||
EventAction,
|
||||
} from '../../../common';
|
||||
import {
|
||||
ALERTS_ROUTE,
|
||||
PROCESS_EVENTS_ROUTE,
|
||||
|
@ -25,6 +25,7 @@ import {
|
|||
QUERY_KEY_PROCESS_EVENTS,
|
||||
QUERY_KEY_ALERTS,
|
||||
QUERY_KEY_GET_TOTAL_IO_BYTES,
|
||||
CURRENT_API_VERSION,
|
||||
} from '../../../common/constants';
|
||||
|
||||
export const useFetchSessionViewProcessEvents = (
|
||||
|
@ -51,6 +52,7 @@ export const useFetchSessionViewProcessEvents = (
|
|||
}
|
||||
|
||||
const res = await http.get<ProcessEventResults>(PROCESS_EVENTS_ROUTE, {
|
||||
version: CURRENT_API_VERSION,
|
||||
query: {
|
||||
index,
|
||||
sessionEntityId,
|
||||
|
@ -69,12 +71,10 @@ export const useFetchSessionViewProcessEvents = (
|
|||
const isRefetch = pages.length === 1 && jumpToCursor;
|
||||
if (isRefetch || lastPage.events.length >= PROCESS_EVENTS_PER_PAGE) {
|
||||
const filtered = lastPage.events.filter((event) => {
|
||||
const action = event.event?.action;
|
||||
const action = event.event?.action as EventAction;
|
||||
return (
|
||||
action &&
|
||||
(action.includes(EventAction.fork) ||
|
||||
action.includes(EventAction.exec) ||
|
||||
action.includes(EventAction.end))
|
||||
(action.includes('fork') || action.includes('exec') || action.includes('end'))
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -90,12 +90,9 @@ export const useFetchSessionViewProcessEvents = (
|
|||
},
|
||||
getPreviousPageParam: (firstPage, pages) => {
|
||||
const filtered = firstPage.events.filter((event) => {
|
||||
const action = event.event?.action;
|
||||
const action = event.event?.action as EventAction;
|
||||
return (
|
||||
action &&
|
||||
(action.includes(EventAction.fork) ||
|
||||
action.includes(EventAction.exec) ||
|
||||
action.includes(EventAction.end))
|
||||
action && (action.includes('fork') || action.includes('exec') || action.includes('end'))
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -142,6 +139,7 @@ export const useFetchSessionViewAlerts = (
|
|||
const { cursor } = pageParam;
|
||||
|
||||
const res = await http.get<ProcessEventResults>(ALERTS_ROUTE, {
|
||||
version: CURRENT_API_VERSION,
|
||||
query: {
|
||||
sessionEntityId,
|
||||
sessionStartTime,
|
||||
|
@ -189,6 +187,7 @@ export const useFetchAlertStatus = (
|
|||
}
|
||||
|
||||
const res = await http.get<ProcessEventResults>(ALERT_STATUS_ROUTE, {
|
||||
version: CURRENT_API_VERSION,
|
||||
query: {
|
||||
alertUuid,
|
||||
},
|
||||
|
@ -227,6 +226,7 @@ export const useFetchGetTotalIOBytes = (
|
|||
cachingKeys,
|
||||
async () => {
|
||||
return http.get<{ total: number }>(GET_TOTAL_IO_BYTES_ROUTE, {
|
||||
version: CURRENT_API_VERSION,
|
||||
query: {
|
||||
index,
|
||||
sessionEntityId,
|
||||
|
|
|
@ -54,7 +54,7 @@ describe('SessionView component', () => {
|
|||
render = () =>
|
||||
(renderResult = mockedContext.render(
|
||||
<SessionView
|
||||
processIndex={TEST_PROCESS_INDEX}
|
||||
index={TEST_PROCESS_INDEX}
|
||||
sessionStartTime={TEST_SESSION_START_TIME}
|
||||
sessionEntityId="test-entity-id"
|
||||
/>
|
||||
|
|
|
@ -21,13 +21,9 @@ import useLocalStorage from 'react-use/lib/useLocalStorage';
|
|||
import byteSize from 'byte-size';
|
||||
import { SectionLoading } from '../../shared_imports';
|
||||
import { ProcessTree } from '../process_tree';
|
||||
import {
|
||||
AlertStatusEventEntityIdMap,
|
||||
Process,
|
||||
ProcessEvent,
|
||||
} from '../../../common/types/process_tree';
|
||||
import { DisplayOptionsState } from '../../../common/types/session_view';
|
||||
import { SessionViewDeps } from '../../types';
|
||||
import type { AlertStatusEventEntityIdMap, Process, ProcessEvent } from '../../../common';
|
||||
import type { DisplayOptionsState } from '../session_view_display_options';
|
||||
import type { SessionViewDeps } from '../../types';
|
||||
import { SessionViewDetailPanel } from '../session_view_detail_panel';
|
||||
import { SessionViewSearchBar } from '../session_view_search_bar';
|
||||
import { SessionViewDisplayOptions } from '../session_view_display_options';
|
||||
|
@ -46,7 +42,7 @@ import { REFRESH_SESSION, TOGGLE_TTY_PLAYER, DETAIL_PANEL } from './translations
|
|||
* The main wrapper component for the session view.
|
||||
*/
|
||||
export const SessionView = ({
|
||||
processIndex,
|
||||
index,
|
||||
sessionEntityId,
|
||||
sessionStartTime,
|
||||
height,
|
||||
|
@ -132,7 +128,7 @@ export const SessionView = ({
|
|||
hasPreviousPage,
|
||||
refetch,
|
||||
} = useFetchSessionViewProcessEvents(
|
||||
processIndex,
|
||||
index,
|
||||
sessionEntityId,
|
||||
sessionStartTime,
|
||||
currentJumpToCursor
|
||||
|
@ -148,7 +144,7 @@ export const SessionView = ({
|
|||
} = useFetchSessionViewAlerts(sessionEntityId, sessionStartTime, investigatedAlertId);
|
||||
|
||||
const { data: totalTTYOutputBytes, refetch: refetchTotalTTYOutput } = useFetchGetTotalIOBytes(
|
||||
processIndex,
|
||||
index,
|
||||
sessionEntityId,
|
||||
sessionStartTime
|
||||
);
|
||||
|
@ -160,8 +156,8 @@ export const SessionView = ({
|
|||
}, [totalTTYOutputBytes?.total]);
|
||||
|
||||
const handleRefresh = useCallback(() => {
|
||||
refetch({ refetchPage: (_page, index, allPages) => allPages.length - 1 === index });
|
||||
refetchAlerts({ refetchPage: (_page, index, allPages) => allPages.length - 1 === index });
|
||||
refetch({ refetchPage: (_page, i, allPages) => allPages.length - 1 === i });
|
||||
refetchAlerts({ refetchPage: (_page, i, allPages) => allPages.length - 1 === i });
|
||||
refetchTotalTTYOutput();
|
||||
}, [refetch, refetchAlerts, refetchTotalTTYOutput]);
|
||||
|
||||
|
@ -199,9 +195,9 @@ export const SessionView = ({
|
|||
}, [newUpdatedAlertsStatus, fetchAlertStatus]);
|
||||
|
||||
const onSearchIndexChange = useCallback(
|
||||
(index: number) => {
|
||||
(i: number) => {
|
||||
if (searchResults) {
|
||||
const process = searchResults[index];
|
||||
const process = searchResults[i];
|
||||
|
||||
if (process) {
|
||||
onProcessSelected(process);
|
||||
|
@ -431,7 +427,7 @@ export const SessionView = ({
|
|||
}}
|
||||
</EuiResizableContainer>
|
||||
<TTYPlayer
|
||||
index={processIndex}
|
||||
index={index}
|
||||
show={showTTY}
|
||||
sessionEntityId={sessionEntityId}
|
||||
sessionStartTime={sessionStartTime}
|
||||
|
|
|
@ -8,7 +8,7 @@ import React, { useState, useMemo, useCallback } from 'react';
|
|||
import { EuiTabs, EuiTab, EuiNotificationBadge } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiTabProps } from '../../types';
|
||||
import { Process, ProcessEvent } from '../../../common/types/process_tree';
|
||||
import type { Process, ProcessEvent } from '../../../common';
|
||||
import { getSelectedTabContent } from './helpers';
|
||||
import { DetailPanelProcessTab } from '../detail_panel_process_tab';
|
||||
import { DetailPanelMetadataTab } from '../detail_panel_metadata_tab';
|
||||
|
|
|
@ -18,7 +18,6 @@ import {
|
|||
} from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { EuiSelectableOption } from '@elastic/eui/src/components/selectable/selectable_option';
|
||||
import { DisplayOptionsState } from '../../../common/types/session_view';
|
||||
import { useStyles } from './styles';
|
||||
|
||||
const TIMESTAMP_OPTION_KEY = 'Timestamp';
|
||||
|
@ -40,6 +39,11 @@ const VERBOSE_TOOLTIP_CONTENT = i18n.translate(
|
|||
}
|
||||
);
|
||||
|
||||
export interface DisplayOptionsState {
|
||||
timestamp: boolean;
|
||||
verboseMode: boolean;
|
||||
}
|
||||
|
||||
export const SessionViewDisplayOptions = ({
|
||||
onChange,
|
||||
displayOptions,
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { Teletype } from '../../../common/types/process_tree';
|
||||
import type { Teletype } from '../../../common';
|
||||
import {
|
||||
PROCESS_DATA_LIMIT_EXCEEDED_START,
|
||||
PROCESS_DATA_LIMIT_EXCEEDED_END,
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
import { renderHook, act } from '@testing-library/react-hooks';
|
||||
import { sessionViewIOEventsMock } from '../../../common/mocks/responses/session_view_io_events.mock';
|
||||
import { useIOLines, useXtermPlayer, XtermPlayerDeps } from './hooks';
|
||||
import { ProcessEventsPage } from '../../../common/types/process_tree';
|
||||
import type { ProcessEventsPage } from '../../../common';
|
||||
import { DEFAULT_TTY_FONT_SIZE, DEFAULT_TTY_PLAYSPEED_MS } from '../../../common/constants';
|
||||
|
||||
const VIM_LINE_START = 22;
|
||||
|
|
|
@ -14,13 +14,13 @@ import { SearchAddon } from './xterm_search';
|
|||
import { useEuiTheme } from '../../hooks';
|
||||
import { renderTruncatedMsg } from './ansi_helpers';
|
||||
|
||||
import {
|
||||
import type {
|
||||
IOLine,
|
||||
ProcessStartMarker,
|
||||
ProcessEvent,
|
||||
ProcessEventResults,
|
||||
ProcessEventsPage,
|
||||
} from '../../../common/types/process_tree';
|
||||
} from '../../../common';
|
||||
import {
|
||||
IO_EVENTS_ROUTE,
|
||||
IO_EVENTS_PER_PAGE,
|
||||
|
@ -31,6 +31,7 @@ import {
|
|||
DEFAULT_TTY_COLS,
|
||||
TTY_LINE_SPLITTER_REGEX,
|
||||
TTY_LINES_PRE_SEEK,
|
||||
CURRENT_API_VERSION,
|
||||
} from '../../../common/constants';
|
||||
|
||||
export const useFetchIOEvents = (
|
||||
|
@ -46,6 +47,7 @@ export const useFetchIOEvents = (
|
|||
async ({ pageParam = {} }) => {
|
||||
const { cursor } = pageParam;
|
||||
const res = await http.get<ProcessEventResults>(IO_EVENTS_ROUTE, {
|
||||
version: CURRENT_API_VERSION,
|
||||
query: {
|
||||
index,
|
||||
sessionEntityId,
|
||||
|
|
|
@ -17,7 +17,7 @@ import { useKibana } from '@kbn/kibana-react-plugin/public';
|
|||
import { CoreStart } from '@kbn/core/public';
|
||||
import useResizeObserver from 'use-resize-observer';
|
||||
import { throttle } from 'lodash';
|
||||
import { ProcessEvent } from '../../../common/types/process_tree';
|
||||
import type { ProcessEvent } from '../../../common';
|
||||
import { TTYSearchBar } from '../tty_search_bar';
|
||||
import { TTYTextSizer } from '../tty_text_sizer';
|
||||
import { useStyles } from './styles';
|
||||
|
|
|
@ -9,7 +9,7 @@ import { useMemo } from 'react';
|
|||
import { CSSObject, css } from '@emotion/react';
|
||||
import { transparentize } from '@elastic/eui';
|
||||
import { useEuiTheme } from '../../hooks';
|
||||
import { Teletype } from '../../../common/types/process_tree';
|
||||
import type { Teletype } from '../../../common';
|
||||
|
||||
export const useStyles = (tty?: Teletype, show?: boolean) => {
|
||||
const { euiTheme, euiVars } = useEuiTheme();
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
import React from 'react';
|
||||
import { AppContextTestRender, createAppRootMockRenderer } from '../../test';
|
||||
import { ProcessEvent } from '../../../common/types/process_tree';
|
||||
import type { ProcessEvent } from '../../../common';
|
||||
import { TTYPlayerControls, TTYPlayerControlsDeps } from '.';
|
||||
import { TTYPlayerLineMarkerType } from './tty_player_controls_markers';
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ import {
|
|||
EuiRangeProps,
|
||||
} from '@elastic/eui';
|
||||
import { findIndex } from 'lodash';
|
||||
import { ProcessStartMarker, ProcessEvent } from '../../../common/types/process_tree';
|
||||
import type { ProcessStartMarker, ProcessEvent } from '../../../common';
|
||||
import { useStyles } from './styles';
|
||||
import {
|
||||
TTY_END,
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import React, { useMemo } from 'react';
|
||||
import { EuiRange, EuiRangeProps, EuiToolTip } from '@elastic/eui';
|
||||
import type { ProcessStartMarker } from '../../../../common/types/process_tree';
|
||||
import type { ProcessStartMarker } from '../../../../common';
|
||||
import { useStyles } from './styles';
|
||||
import { PlayHead } from './play_head';
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ import userEvent from '@testing-library/user-event';
|
|||
import { AppContextTestRender, createAppRootMockRenderer } from '../../test';
|
||||
import { sessionViewIOEventsMock } from '../../../common/mocks/responses/session_view_io_events.mock';
|
||||
import { useIOLines } from '../tty_player/hooks';
|
||||
import { ProcessEventsPage } from '../../../common/types/process_tree';
|
||||
import type { ProcessEventsPage } from '../../../common';
|
||||
import { TTYSearchBar, TTYSearchBarDeps } from '.';
|
||||
|
||||
// TTYSearchBar is a HOC to SessionViewSearchBar which is already well tested
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
import React, { useMemo, useState, useCallback } from 'react';
|
||||
import stripAnsi from 'strip-ansi';
|
||||
import { SessionViewSearchBar } from '../session_view_search_bar';
|
||||
import { IOLine } from '../../../common/types/process_tree';
|
||||
import type { IOLine } from '../../../common';
|
||||
|
||||
interface SearchResult {
|
||||
line: IOLine;
|
||||
|
|
|
@ -12,7 +12,7 @@ import {
|
|||
EuiFlexItem,
|
||||
EuiToolTip,
|
||||
} from '@elastic/eui';
|
||||
import { Teletype } from '../../../common/types/process_tree';
|
||||
import type { Teletype } from '../../../common';
|
||||
import { DEFAULT_TTY_FONT_SIZE } from '../../../common/constants';
|
||||
import { ZOOM_IN, ZOOM_FIT, ZOOM_OUT } from './translations';
|
||||
import { useStyles } from './styles';
|
||||
|
|
|
@ -5,12 +5,10 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { ENTRY_SESSION_ENTITY_ID_PROPERTY } from '../common/constants';
|
||||
import { EventAction } from '../common/types/process_tree';
|
||||
import { SessionViewPlugin } from './plugin';
|
||||
|
||||
export type { SessionViewStart } from './types';
|
||||
export { ENTRY_SESSION_ENTITY_ID_PROPERTY, EventAction };
|
||||
export { ENTRY_SESSION_ENTITY_ID_PROPERTY } from '../common';
|
||||
|
||||
export function plugin() {
|
||||
return new SessionViewPlugin();
|
||||
|
|
32
x-pack/plugins/session_view/public/methods/index.test.tsx
Normal file
32
x-pack/plugins/session_view/public/methods/index.test.tsx
Normal file
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* 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 { getIndexPattern, DEFAULT_INDEX, CLOUD_DEFEND_INDEX, ENDPOINT_INDEX } from '.';
|
||||
|
||||
const ENDPOINT_EVENT_INDEX = '.ds-logs-endpoint.events.process-default';
|
||||
const CLOUD_DEFEND_EVENT_INDEX = '.ds-logs-cloud_defend.process-default';
|
||||
const TEST_CLUSTER = 'aws';
|
||||
|
||||
describe('getIndexPattern', () => {
|
||||
it('gets endpoint index pattern for events from endpoint', () => {
|
||||
expect(getIndexPattern(ENDPOINT_EVENT_INDEX)).toEqual(ENDPOINT_INDEX);
|
||||
});
|
||||
|
||||
it('gets cloud_defend index pattern for events from cloud-defend', () => {
|
||||
expect(getIndexPattern(CLOUD_DEFEND_EVENT_INDEX)).toEqual(CLOUD_DEFEND_INDEX);
|
||||
});
|
||||
|
||||
it('gets logs-* for everything else', () => {
|
||||
expect(getIndexPattern('asdfasdfasdf')).toEqual(DEFAULT_INDEX);
|
||||
});
|
||||
|
||||
it('preserves the cluster portion of the endpoint event index', () => {
|
||||
expect(getIndexPattern(TEST_CLUSTER + ':' + ENDPOINT_EVENT_INDEX)).toEqual(
|
||||
TEST_CLUSTER + ':' + ENDPOINT_INDEX
|
||||
);
|
||||
});
|
||||
});
|
|
@ -15,11 +15,46 @@ const queryClient = new QueryClient();
|
|||
|
||||
const SessionViewLazy = lazy(() => import('../components/session_view'));
|
||||
|
||||
const SUPPORTED_PACKAGES = ['endpoint', 'cloud_defend'];
|
||||
const INDEX_REGEX = new RegExp(
|
||||
`([a-z0-9_-]+\:)?[a-z0-9\-.]*(${SUPPORTED_PACKAGES.join('|')})`,
|
||||
'i'
|
||||
);
|
||||
|
||||
export const DEFAULT_INDEX = 'logs-*';
|
||||
export const CLOUD_DEFEND_INDEX = 'logs-cloud_defend.*';
|
||||
export const ENDPOINT_INDEX = 'logs-endpoint.events.process*';
|
||||
|
||||
// Currently both logs-endpoint.events.process* and logs-cloud_defend.process* are valid sources for session data.
|
||||
// To avoid cross cluster searches, the original index of the event is used to infer the index to find data for the
|
||||
// rest of the session.
|
||||
export const getIndexPattern = (eventIndex?: string | null) => {
|
||||
if (!eventIndex) {
|
||||
return DEFAULT_INDEX;
|
||||
}
|
||||
|
||||
const match = eventIndex.match(INDEX_REGEX);
|
||||
const cluster = match?.[1];
|
||||
const clusterStr = cluster ? `${cluster}` : '';
|
||||
const service = match?.[2];
|
||||
|
||||
let index = DEFAULT_INDEX;
|
||||
if (service === 'endpoint') {
|
||||
index = ENDPOINT_INDEX;
|
||||
} else if (service === 'cloud_defend') {
|
||||
index = CLOUD_DEFEND_INDEX;
|
||||
}
|
||||
|
||||
return clusterStr + index;
|
||||
};
|
||||
|
||||
export const getSessionViewLazy = (props: SessionViewDeps) => {
|
||||
const index = getIndexPattern(props.index);
|
||||
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Suspense fallback={<EuiLoadingSpinner />}>
|
||||
<SessionViewLazy {...props} />
|
||||
<SessionViewLazy {...props} index={index} />
|
||||
</Suspense>
|
||||
</QueryClientProvider>
|
||||
);
|
||||
|
|
|
@ -11,7 +11,7 @@ export type SessionViewServices = CoreStart;
|
|||
|
||||
export interface SessionViewDeps {
|
||||
// we pass in the index of the session leader that spawned session_view, this avoids having to query multiple cross cluster indices
|
||||
processIndex: string;
|
||||
index: string;
|
||||
|
||||
// the root node of the process tree to render. e.g process.entry.entity_id or process.session_leader.entity_id
|
||||
sessionEntityId: string;
|
||||
|
|
|
@ -10,19 +10,14 @@ import {
|
|||
mockNetworkAlert,
|
||||
} from '../../common/mocks/constants/session_view_process.mock';
|
||||
import { getAlertCategoryDisplayText, getAlertNetworkDisplay } from './alert_category_display_text';
|
||||
import { ProcessEventAlertCategory } from '../../common/types/process_tree';
|
||||
|
||||
describe('getAlertCategoryDisplayText(alert, category)', () => {
|
||||
it('should display file path when alert category is file', () => {
|
||||
expect(getAlertCategoryDisplayText(mockFileAlert, ProcessEventAlertCategory?.file)).toEqual(
|
||||
mockFileAlert?.file?.path
|
||||
);
|
||||
expect(getAlertCategoryDisplayText(mockFileAlert, 'file')).toEqual(mockFileAlert?.file?.path);
|
||||
});
|
||||
|
||||
it('should display rule name when alert category is process', () => {
|
||||
expect(getAlertCategoryDisplayText(mockAlerts[0], ProcessEventAlertCategory.process)).toEqual(
|
||||
''
|
||||
);
|
||||
expect(getAlertCategoryDisplayText(mockAlerts[0], 'process')).toEqual('');
|
||||
});
|
||||
|
||||
it('should display rule name when alert category is undefined', () => {
|
||||
|
@ -31,20 +26,18 @@ describe('getAlertCategoryDisplayText(alert, category)', () => {
|
|||
|
||||
it('should display rule name when file path is undefined', () => {
|
||||
const fileAlert = { ...mockFileAlert, file: {} };
|
||||
expect(getAlertCategoryDisplayText(fileAlert, ProcessEventAlertCategory.file)).toEqual('');
|
||||
expect(getAlertCategoryDisplayText(fileAlert, 'file')).toEqual('');
|
||||
});
|
||||
it('should display rule name when destination address is undefined and alert category is network', () => {
|
||||
const networkAlert = { ...mockNetworkAlert, destination: undefined };
|
||||
expect(getAlertCategoryDisplayText(networkAlert, ProcessEventAlertCategory.network)).toEqual(
|
||||
''
|
||||
);
|
||||
expect(getAlertCategoryDisplayText(networkAlert, 'network')).toEqual('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getAlertNetworkDisplay(destination)', () => {
|
||||
it('should show destination address and port', () => {
|
||||
const text = `${mockNetworkAlert.destination.address}:${mockNetworkAlert.destination.port}`;
|
||||
expect(getAlertNetworkDisplay(mockNetworkAlert.destination)).toEqual(text);
|
||||
const text = `${mockNetworkAlert?.destination?.address}:${mockNetworkAlert?.destination?.port}`;
|
||||
expect(getAlertNetworkDisplay(mockNetworkAlert?.destination)).toEqual(text);
|
||||
});
|
||||
|
||||
it('should show only ip address when port does not exist', () => {
|
||||
|
|
|
@ -5,24 +5,20 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import {
|
||||
ProcessEvent,
|
||||
ProcessEventAlertCategory,
|
||||
ProcessEventIPAddress,
|
||||
} from '../../common/types/process_tree';
|
||||
import type { ProcessEvent, ProcessEventIPAddress } from '../../common';
|
||||
import { dataOrDash } from './data_or_dash';
|
||||
|
||||
export const getAlertCategoryDisplayText = (alert: ProcessEvent, category: string | undefined) => {
|
||||
export const getAlertCategoryDisplayText = (alert: ProcessEvent, category?: string) => {
|
||||
const destination = alert?.destination;
|
||||
const filePath = alert?.file?.path;
|
||||
|
||||
if (filePath && category === ProcessEventAlertCategory.file) return dataOrDash(filePath);
|
||||
if (destination?.address && category === ProcessEventAlertCategory.network)
|
||||
if (filePath && category === 'file') return dataOrDash(filePath);
|
||||
if (destination?.address && category === 'network')
|
||||
return dataOrDash(getAlertNetworkDisplay(destination));
|
||||
return '';
|
||||
};
|
||||
|
||||
export const getAlertNetworkDisplay = (destination: ProcessEventIPAddress) => {
|
||||
export const getAlertNetworkDisplay = (destination?: ProcessEventIPAddress) => {
|
||||
const hasIpAddressPort = !!destination?.address && !!destination?.port;
|
||||
const ipAddressPort = `${destination?.address}:${destination?.port}`;
|
||||
return `${hasIpAddressPort ? ipAddressPort : destination?.address}`;
|
||||
|
|
|
@ -38,7 +38,7 @@ export class SessionViewPlugin implements Plugin {
|
|||
|
||||
// Register server routes
|
||||
if (this.router) {
|
||||
registerRoutes(this.router, plugins.ruleRegistry);
|
||||
registerRoutes(this.router, this.logger, plugins.ruleRegistry);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,8 @@
|
|||
* 2.0.
|
||||
*/
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { IRouter } from '@kbn/core/server';
|
||||
import { IRouter, Logger } from '@kbn/core/server';
|
||||
import { transformError } from '@kbn/securitysolution-es-utils';
|
||||
import type {
|
||||
AlertsClient,
|
||||
RuleRegistryPluginStartContract,
|
||||
|
@ -19,25 +20,43 @@ import { expandDottedObject } from '../../common/utils/expand_dotted_object';
|
|||
|
||||
export const registerAlertStatusRoute = (
|
||||
router: IRouter,
|
||||
logger: Logger,
|
||||
ruleRegistry: RuleRegistryPluginStartContract
|
||||
) => {
|
||||
router.get(
|
||||
{
|
||||
router.versioned
|
||||
.get({
|
||||
access: 'internal',
|
||||
path: ALERT_STATUS_ROUTE,
|
||||
validate: {
|
||||
query: schema.object({
|
||||
alertUuid: schema.string(),
|
||||
}),
|
||||
})
|
||||
.addVersion(
|
||||
{
|
||||
version: '1',
|
||||
validate: {
|
||||
request: {
|
||||
query: schema.object({
|
||||
alertUuid: schema.string(),
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async (_context, request, response) => {
|
||||
const client = await ruleRegistry.getRacClientWithRequest(request);
|
||||
const { alertUuid } = request.query;
|
||||
const body = await searchAlertByUuid(client, alertUuid);
|
||||
async (_context, request, response) => {
|
||||
const client = await ruleRegistry.getRacClientWithRequest(request);
|
||||
const { alertUuid } = request.query;
|
||||
try {
|
||||
const body = await searchAlertByUuid(client, alertUuid);
|
||||
|
||||
return response.ok({ body });
|
||||
}
|
||||
);
|
||||
return response.ok({ body });
|
||||
} catch (err) {
|
||||
const error = transformError(err);
|
||||
logger.error(`Failed to fetch alert status: ${err}`);
|
||||
|
||||
return response.customError({
|
||||
body: { message: error.message },
|
||||
statusCode: error.statusCode,
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export const searchAlertByUuid = async (client: AlertsClient, alertUuid: string) => {
|
||||
|
|
|
@ -5,7 +5,8 @@
|
|||
* 2.0.
|
||||
*/
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { IRouter } from '@kbn/core/server';
|
||||
import { transformError } from '@kbn/securitysolution-es-utils';
|
||||
import { IRouter, Logger } from '@kbn/core/server';
|
||||
import type {
|
||||
AlertsClient,
|
||||
RuleRegistryPluginStartContract,
|
||||
|
@ -17,46 +18,61 @@ import {
|
|||
ALERT_UUID_PROPERTY,
|
||||
ALERT_ORIGINAL_TIME_PROPERTY,
|
||||
PREVIEW_ALERTS_INDEX,
|
||||
ALERT_FIELDS,
|
||||
} from '../../common/constants';
|
||||
|
||||
import { expandDottedObject } from '../../common/utils/expand_dotted_object';
|
||||
|
||||
export const registerAlertsRoute = (
|
||||
router: IRouter,
|
||||
logger: Logger,
|
||||
ruleRegistry: RuleRegistryPluginStartContract
|
||||
) => {
|
||||
router.get(
|
||||
{
|
||||
router.versioned
|
||||
.get({
|
||||
access: 'internal',
|
||||
path: ALERTS_ROUTE,
|
||||
validate: {
|
||||
query: schema.object({
|
||||
sessionEntityId: schema.string(),
|
||||
sessionStartTime: schema.string(),
|
||||
investigatedAlertId: schema.maybe(schema.string()),
|
||||
cursor: schema.maybe(schema.string()),
|
||||
}),
|
||||
})
|
||||
.addVersion(
|
||||
{
|
||||
version: '1',
|
||||
validate: {
|
||||
request: {
|
||||
query: schema.object({
|
||||
sessionEntityId: schema.string(),
|
||||
sessionStartTime: schema.string(),
|
||||
investigatedAlertId: schema.maybe(schema.string()),
|
||||
cursor: schema.maybe(schema.string()),
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async (_context, request, response) => {
|
||||
const client = await ruleRegistry.getRacClientWithRequest(request);
|
||||
const { sessionEntityId, sessionStartTime, investigatedAlertId, cursor } = request.query;
|
||||
async (_context, request, response) => {
|
||||
const client = await ruleRegistry.getRacClientWithRequest(request);
|
||||
const { sessionEntityId, sessionStartTime, investigatedAlertId, cursor } = request.query;
|
||||
|
||||
try {
|
||||
const body = await searchAlerts(
|
||||
client,
|
||||
sessionEntityId,
|
||||
ALERTS_PER_PAGE,
|
||||
investigatedAlertId,
|
||||
[sessionStartTime],
|
||||
cursor
|
||||
);
|
||||
try {
|
||||
const body = await searchAlerts(
|
||||
client,
|
||||
sessionEntityId,
|
||||
ALERTS_PER_PAGE,
|
||||
investigatedAlertId,
|
||||
[sessionStartTime],
|
||||
cursor
|
||||
);
|
||||
|
||||
return response.ok({ body });
|
||||
} catch (err) {
|
||||
return response.badRequest(err.message);
|
||||
return response.ok({ body });
|
||||
} catch (err) {
|
||||
const error = transformError(err);
|
||||
logger.error(`Failed to fetch alerts: ${err}`);
|
||||
|
||||
return response.customError({
|
||||
body: { message: error.message },
|
||||
statusCode: error.statusCode,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
);
|
||||
};
|
||||
|
||||
export const searchAlerts = async (
|
||||
|
@ -101,12 +117,14 @@ export const searchAlerts = async (
|
|||
index: indices.join(','),
|
||||
sort: [{ '@timestamp': 'asc' }],
|
||||
search_after: cursor ? [cursor] : undefined,
|
||||
_source: ALERT_FIELDS,
|
||||
});
|
||||
|
||||
// if an alert is being investigated, fetch it on it's own, as it's not guaranteed to come back in the above request.
|
||||
// we only need to do this for the first page of alerts.
|
||||
if (!cursor && investigatedAlertId) {
|
||||
const investigatedAlertSearch = await client.find({
|
||||
_source: ALERT_FIELDS,
|
||||
query: {
|
||||
match: {
|
||||
[ALERT_UUID_PROPERTY]: investigatedAlertId,
|
||||
|
|
|
@ -3,7 +3,8 @@
|
|||
* 2.0.
|
||||
*/
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { IRouter } from '@kbn/core/server';
|
||||
import { transformError } from '@kbn/securitysolution-es-utils';
|
||||
import { IRouter, Logger } from '@kbn/core/server';
|
||||
import { EVENT_ACTION } from '@kbn/rule-data-utils';
|
||||
import {
|
||||
GET_TOTAL_IO_BYTES_ROUTE,
|
||||
|
@ -12,64 +13,77 @@ import {
|
|||
TIMESTAMP_PROPERTY,
|
||||
} from '../../common/constants';
|
||||
|
||||
export const registerGetTotalIOBytesRoute = (router: IRouter) => {
|
||||
router.get(
|
||||
{
|
||||
export const registerGetTotalIOBytesRoute = (router: IRouter, logger: Logger) => {
|
||||
router.versioned
|
||||
.get({
|
||||
access: 'internal',
|
||||
path: GET_TOTAL_IO_BYTES_ROUTE,
|
||||
validate: {
|
||||
query: schema.object({
|
||||
index: schema.string(),
|
||||
sessionEntityId: schema.string(),
|
||||
sessionStartTime: schema.string(),
|
||||
}),
|
||||
})
|
||||
.addVersion(
|
||||
{
|
||||
version: '1',
|
||||
validate: {
|
||||
request: {
|
||||
query: schema.object({
|
||||
index: schema.string(),
|
||||
sessionEntityId: schema.string(),
|
||||
sessionStartTime: schema.string(),
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async (context, request, response) => {
|
||||
const client = (await context.core).elasticsearch.client.asCurrentUser;
|
||||
const { index, sessionEntityId, sessionStartTime } = request.query;
|
||||
async (context, request, response) => {
|
||||
const client = (await context.core).elasticsearch.client.asCurrentUser;
|
||||
const { index, sessionEntityId, sessionStartTime } = request.query;
|
||||
|
||||
try {
|
||||
const search = await client.search({
|
||||
index: [index],
|
||||
body: {
|
||||
query: {
|
||||
bool: {
|
||||
must: [
|
||||
{ term: { [ENTRY_SESSION_ENTITY_ID_PROPERTY]: sessionEntityId } },
|
||||
{ term: { [EVENT_ACTION]: 'text_output' } },
|
||||
{
|
||||
range: {
|
||||
// optimization to prevent data before this session from being hit.
|
||||
[TIMESTAMP_PROPERTY]: {
|
||||
gte: sessionStartTime,
|
||||
try {
|
||||
const search = await client.search({
|
||||
index: [index],
|
||||
body: {
|
||||
query: {
|
||||
bool: {
|
||||
must: [
|
||||
{ term: { [ENTRY_SESSION_ENTITY_ID_PROPERTY]: sessionEntityId } },
|
||||
{ term: { [EVENT_ACTION]: 'text_output' } },
|
||||
{
|
||||
range: {
|
||||
// optimization to prevent data before this session from being hit.
|
||||
[TIMESTAMP_PROPERTY]: {
|
||||
gte: sessionStartTime,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
size: 0,
|
||||
aggs: {
|
||||
total_bytes_captured: {
|
||||
sum: {
|
||||
field: TOTAL_BYTES_CAPTURED_PROPERTY,
|
||||
size: 0,
|
||||
aggs: {
|
||||
total_bytes_captured: {
|
||||
sum: {
|
||||
field: TOTAL_BYTES_CAPTURED_PROPERTY,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
const agg: any = search.aggregations?.total_bytes_captured;
|
||||
const agg: any = search.aggregations?.total_bytes_captured;
|
||||
|
||||
return response.ok({ body: { total: agg?.value || 0 } });
|
||||
} catch (err) {
|
||||
// unauthorized
|
||||
if (err?.meta?.statusCode === 403) {
|
||||
return response.ok({ body: { total: 0 } });
|
||||
return response.ok({ body: { total: agg?.value || 0 } });
|
||||
} catch (err) {
|
||||
const error = transformError(err);
|
||||
logger.error(`Failed to fetch total io bytes: ${err}`);
|
||||
|
||||
// unauthorized
|
||||
if (err?.meta?.statusCode === 403) {
|
||||
return response.ok({ body: { total: 0 } });
|
||||
}
|
||||
|
||||
return response.customError({
|
||||
body: { message: error.message },
|
||||
statusCode: error.statusCode,
|
||||
});
|
||||
}
|
||||
|
||||
return response.badRequest(err.message);
|
||||
}
|
||||
}
|
||||
);
|
||||
);
|
||||
};
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import { IRouter } from '@kbn/core/server';
|
||||
import { IRouter, Logger } from '@kbn/core/server';
|
||||
import { RuleRegistryPluginStartContract } from '@kbn/rule-registry-plugin/server';
|
||||
import { registerProcessEventsRoute } from './process_events_route';
|
||||
import { registerAlertsRoute } from './alerts_route';
|
||||
|
@ -12,10 +12,14 @@ import { registerAlertStatusRoute } from './alert_status_route';
|
|||
import { registerIOEventsRoute } from './io_events_route';
|
||||
import { registerGetTotalIOBytesRoute } from './get_total_io_bytes_route';
|
||||
|
||||
export const registerRoutes = (router: IRouter, ruleRegistry: RuleRegistryPluginStartContract) => {
|
||||
registerProcessEventsRoute(router, ruleRegistry);
|
||||
registerAlertsRoute(router, ruleRegistry);
|
||||
registerAlertStatusRoute(router, ruleRegistry);
|
||||
registerIOEventsRoute(router);
|
||||
registerGetTotalIOBytesRoute(router);
|
||||
export const registerRoutes = (
|
||||
router: IRouter,
|
||||
logger: Logger,
|
||||
ruleRegistry: RuleRegistryPluginStartContract
|
||||
) => {
|
||||
registerProcessEventsRoute(router, logger, ruleRegistry);
|
||||
registerAlertsRoute(router, logger, ruleRegistry);
|
||||
registerAlertStatusRoute(router, logger, ruleRegistry);
|
||||
registerIOEventsRoute(router, logger);
|
||||
registerGetTotalIOBytesRoute(router, logger);
|
||||
};
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
* 2.0.
|
||||
*/
|
||||
import { elasticsearchServiceMock } from '@kbn/core/server/mocks';
|
||||
import { EventAction, EventKind } from '../../common/types/process_tree';
|
||||
import { searchProcessWithIOEvents } from './io_events_route';
|
||||
|
||||
const TEST_PROCESS_INDEX = 'logs-endpoint.events.process*';
|
||||
|
@ -60,8 +59,8 @@ describe('io_events_route.ts', () => {
|
|||
expect(body.length).toBe(1);
|
||||
|
||||
body.forEach((event) => {
|
||||
expect(event._source?.event?.action).toBe(EventAction.text_output);
|
||||
expect(event._source?.event?.kind).toBe(EventKind.event);
|
||||
expect(event._source?.event?.action).toBe('text_output');
|
||||
expect(event._source?.event?.kind).toBe('event');
|
||||
expect(event._source?.process?.entity_id).toBe('mockId');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,11 +5,11 @@
|
|||
* 2.0.
|
||||
*/
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { IRouter } from '@kbn/core/server';
|
||||
import { transformError } from '@kbn/securitysolution-es-utils';
|
||||
import { IRouter, Logger } from '@kbn/core/server';
|
||||
import { EVENT_ACTION, TIMESTAMP } from '@kbn/rule-data-utils';
|
||||
import type { ElasticsearchClient } from '@kbn/core/server';
|
||||
import { Aggregate } from '../../common/types/aggregate';
|
||||
import { EventAction, EventKind } from '../../common/types/process_tree';
|
||||
import type { Aggregate } from '../../common';
|
||||
import {
|
||||
IO_EVENTS_ROUTE,
|
||||
IO_EVENTS_PER_PAGE,
|
||||
|
@ -17,73 +17,88 @@ import {
|
|||
TIMESTAMP_PROPERTY,
|
||||
PROCESS_ENTITY_ID_PROPERTY,
|
||||
PROCESS_EVENTS_PER_PAGE,
|
||||
IO_EVENT_FIELDS,
|
||||
} from '../../common/constants';
|
||||
|
||||
export const registerIOEventsRoute = (router: IRouter) => {
|
||||
router.get(
|
||||
{
|
||||
export const registerIOEventsRoute = (router: IRouter, logger: Logger) => {
|
||||
router.versioned
|
||||
.get({
|
||||
access: 'internal',
|
||||
path: IO_EVENTS_ROUTE,
|
||||
validate: {
|
||||
query: schema.object({
|
||||
index: schema.string(),
|
||||
sessionEntityId: schema.string(),
|
||||
sessionStartTime: schema.string(),
|
||||
cursor: schema.maybe(schema.string()),
|
||||
pageSize: schema.maybe(schema.number()),
|
||||
}),
|
||||
})
|
||||
.addVersion(
|
||||
{
|
||||
version: '1',
|
||||
validate: {
|
||||
request: {
|
||||
query: schema.object({
|
||||
index: schema.string(),
|
||||
sessionEntityId: schema.string(),
|
||||
sessionStartTime: schema.string(),
|
||||
cursor: schema.maybe(schema.string()),
|
||||
pageSize: schema.maybe(schema.number({ min: 1, max: IO_EVENTS_PER_PAGE })), // currently only set in FTR tests to test pagination
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async (context, request, response) => {
|
||||
const client = (await context.core).elasticsearch.client.asCurrentUser;
|
||||
const {
|
||||
index,
|
||||
sessionEntityId,
|
||||
sessionStartTime,
|
||||
cursor,
|
||||
pageSize = IO_EVENTS_PER_PAGE,
|
||||
} = request.query;
|
||||
async (context, request, response) => {
|
||||
const client = (await context.core).elasticsearch.client.asCurrentUser;
|
||||
const {
|
||||
index,
|
||||
sessionEntityId,
|
||||
sessionStartTime,
|
||||
cursor,
|
||||
pageSize = IO_EVENTS_PER_PAGE,
|
||||
} = request.query;
|
||||
|
||||
try {
|
||||
const search = await client.search({
|
||||
index: [index],
|
||||
body: {
|
||||
query: {
|
||||
bool: {
|
||||
must: [
|
||||
{ term: { [ENTRY_SESSION_ENTITY_ID_PROPERTY]: sessionEntityId } },
|
||||
{ term: { [EVENT_ACTION]: 'text_output' } },
|
||||
{
|
||||
range: {
|
||||
// optimization to prevent data before this session from being hit.
|
||||
[TIMESTAMP_PROPERTY]: {
|
||||
gte: sessionStartTime,
|
||||
try {
|
||||
const search = await client.search({
|
||||
index: [index],
|
||||
body: {
|
||||
query: {
|
||||
bool: {
|
||||
must: [
|
||||
{ term: { [ENTRY_SESSION_ENTITY_ID_PROPERTY]: sessionEntityId } },
|
||||
{ term: { [EVENT_ACTION]: 'text_output' } },
|
||||
{
|
||||
range: {
|
||||
// optimization to prevent data before this session from being hit.
|
||||
[TIMESTAMP_PROPERTY]: {
|
||||
gte: sessionStartTime,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
},
|
||||
size: Math.min(pageSize, IO_EVENTS_PER_PAGE),
|
||||
sort: [{ [TIMESTAMP]: 'asc' }],
|
||||
search_after: cursor ? [cursor] : undefined,
|
||||
fields: IO_EVENT_FIELDS,
|
||||
},
|
||||
size: Math.min(pageSize, IO_EVENTS_PER_PAGE),
|
||||
sort: [{ [TIMESTAMP]: 'asc' }],
|
||||
search_after: cursor ? [cursor] : undefined,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
const events = search.hits.hits;
|
||||
const total =
|
||||
typeof search.hits.total === 'number' ? search.hits.total : search.hits.total?.value;
|
||||
const events = search.hits.hits;
|
||||
const total =
|
||||
typeof search.hits.total === 'number' ? search.hits.total : search.hits.total?.value;
|
||||
|
||||
return response.ok({ body: { total, events } });
|
||||
} catch (err) {
|
||||
// unauthorized
|
||||
if (err?.meta?.statusCode === 403) {
|
||||
return response.ok({ body: { total: 0, events: [] } });
|
||||
return response.ok({ body: { total, events } });
|
||||
} catch (err) {
|
||||
const error = transformError(err);
|
||||
logger.error(`Failed to fetch io events: ${err}`);
|
||||
|
||||
// unauthorized
|
||||
if (err?.meta?.statusCode === 403) {
|
||||
return response.ok({ body: { total: 0, events: [] } });
|
||||
}
|
||||
|
||||
return response.customError({
|
||||
body: { message: error.message },
|
||||
statusCode: error.statusCode,
|
||||
});
|
||||
}
|
||||
|
||||
return response.badRequest(err.message);
|
||||
}
|
||||
}
|
||||
);
|
||||
);
|
||||
};
|
||||
|
||||
export const searchProcessWithIOEvents = async (
|
||||
|
@ -136,8 +151,8 @@ export const searchProcessWithIOEvents = async (
|
|||
return buckets.map((bucket) => ({
|
||||
_source: {
|
||||
event: {
|
||||
kind: EventKind.event,
|
||||
action: EventAction.text_output,
|
||||
kind: 'event',
|
||||
action: 'text_output',
|
||||
id: bucket.key,
|
||||
},
|
||||
process: {
|
||||
|
|
|
@ -13,7 +13,7 @@ import {
|
|||
mockAlerts,
|
||||
} from '../../common/mocks/constants/session_view_process.mock';
|
||||
import { getAlertsClientMockInstance, resetAlertingAuthMock } from './alerts_client_mock.test';
|
||||
import { EventAction, EventKind, ProcessEvent } from '../../common/types/process_tree';
|
||||
import type { ProcessEvent } from '../../common';
|
||||
|
||||
const getEmptyResponse = async () => {
|
||||
return {
|
||||
|
@ -76,10 +76,10 @@ describe('process_events_route.ts', () => {
|
|||
expect(body.events.length).toBe(mockEvents.length + mockAlerts.length);
|
||||
|
||||
const eventsOnly = body.events.filter(
|
||||
(event) => (event._source as ProcessEvent)?.event?.kind === EventKind.event
|
||||
(event) => (event._source as ProcessEvent)?.event?.kind === 'event'
|
||||
);
|
||||
const alertsOnly = body.events.filter(
|
||||
(event) => (event._source as ProcessEvent)?.event?.kind === EventKind.signal
|
||||
(event) => (event._source as ProcessEvent)?.event?.kind === 'signal'
|
||||
);
|
||||
expect(eventsOnly.length).toBe(mockEvents.length);
|
||||
expect(alertsOnly.length).toBe(mockAlerts.length);
|
||||
|
@ -104,7 +104,7 @@ describe('process_events_route.ts', () => {
|
|||
const eventsWithoutOutput = body.events.filter((event) => {
|
||||
const { action, kind } = (event._source as ProcessEvent)?.event || {};
|
||||
|
||||
return action !== EventAction.text_output && kind === EventKind.event;
|
||||
return action !== 'text_output' && kind === 'event';
|
||||
});
|
||||
|
||||
expect(eventsWithoutOutput[0]._source).toEqual(mockEvents[mockEvents.length - 1]);
|
||||
|
|
|
@ -5,9 +5,10 @@
|
|||
* 2.0.
|
||||
*/
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { transformError } from '@kbn/securitysolution-es-utils';
|
||||
import _ from 'lodash';
|
||||
import type { ElasticsearchClient } from '@kbn/core/server';
|
||||
import { IRouter } from '@kbn/core/server';
|
||||
import { IRouter, Logger } from '@kbn/core/server';
|
||||
import type {
|
||||
AlertsClient,
|
||||
RuleRegistryPluginStartContract,
|
||||
|
@ -19,57 +20,73 @@ import {
|
|||
PROCESS_EVENTS_PER_PAGE,
|
||||
ENTRY_SESSION_ENTITY_ID_PROPERTY,
|
||||
TIMESTAMP_PROPERTY,
|
||||
PROCESS_EVENT_FIELDS,
|
||||
} from '../../common/constants';
|
||||
import { ProcessEvent } from '../../common/types/process_tree';
|
||||
import { ProcessEvent } from '../../common';
|
||||
import { searchAlerts } from './alerts_route';
|
||||
import { searchProcessWithIOEvents } from './io_events_route';
|
||||
|
||||
export const registerProcessEventsRoute = (
|
||||
router: IRouter,
|
||||
logger: Logger,
|
||||
ruleRegistry: RuleRegistryPluginStartContract
|
||||
) => {
|
||||
router.get(
|
||||
{
|
||||
router.versioned
|
||||
.get({
|
||||
access: 'internal',
|
||||
path: PROCESS_EVENTS_ROUTE,
|
||||
validate: {
|
||||
query: schema.object({
|
||||
index: schema.string(),
|
||||
sessionEntityId: schema.string(),
|
||||
sessionStartTime: schema.string(),
|
||||
cursor: schema.maybe(schema.string()),
|
||||
forward: schema.maybe(schema.boolean()),
|
||||
pageSize: schema.maybe(schema.number()),
|
||||
}),
|
||||
})
|
||||
.addVersion(
|
||||
{
|
||||
version: '1',
|
||||
validate: {
|
||||
request: {
|
||||
query: schema.object({
|
||||
index: schema.string(),
|
||||
sessionEntityId: schema.string(),
|
||||
sessionStartTime: schema.string(),
|
||||
cursor: schema.maybe(schema.string()),
|
||||
forward: schema.maybe(schema.boolean()),
|
||||
pageSize: schema.maybe(schema.number({ min: 1, max: PROCESS_EVENTS_PER_PAGE })), // currently only set in FTR tests to test pagination
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async (context, request, response) => {
|
||||
const client = (await context.core).elasticsearch.client.asCurrentUser;
|
||||
const alertsClient = await ruleRegistry.getRacClientWithRequest(request);
|
||||
const { index, sessionEntityId, sessionStartTime, cursor, forward, pageSize } = request.query;
|
||||
async (context, request, response) => {
|
||||
const client = (await context.core).elasticsearch.client.asCurrentUser;
|
||||
const alertsClient = await ruleRegistry.getRacClientWithRequest(request);
|
||||
const { index, sessionEntityId, sessionStartTime, cursor, forward, pageSize } =
|
||||
request.query;
|
||||
|
||||
try {
|
||||
const body = await fetchEventsAndScopedAlerts(
|
||||
client,
|
||||
alertsClient,
|
||||
index,
|
||||
sessionEntityId,
|
||||
sessionStartTime,
|
||||
cursor,
|
||||
forward,
|
||||
pageSize
|
||||
);
|
||||
try {
|
||||
const body = await fetchEventsAndScopedAlerts(
|
||||
client,
|
||||
alertsClient,
|
||||
index,
|
||||
sessionEntityId,
|
||||
sessionStartTime,
|
||||
cursor,
|
||||
forward,
|
||||
pageSize
|
||||
);
|
||||
|
||||
return response.ok({ body });
|
||||
} catch (err) {
|
||||
// unauthorized
|
||||
if (err.meta.statusCode === 403) {
|
||||
return response.ok({ body: { total: 0, events: [] } });
|
||||
return response.ok({ body });
|
||||
} catch (err) {
|
||||
const error = transformError(err);
|
||||
logger.error(`Failed to fetch process events: ${err}`);
|
||||
|
||||
// unauthorized
|
||||
if (err?.meta?.statusCode === 403) {
|
||||
return response.ok({ body: { total: 0, events: [] } });
|
||||
}
|
||||
|
||||
return response.customError({
|
||||
body: { message: error.message },
|
||||
statusCode: error.statusCode,
|
||||
});
|
||||
}
|
||||
|
||||
return response.badRequest(err.message);
|
||||
}
|
||||
}
|
||||
);
|
||||
);
|
||||
};
|
||||
|
||||
export const fetchEventsAndScopedAlerts = async (
|
||||
|
@ -114,6 +131,7 @@ export const fetchEventsAndScopedAlerts = async (
|
|||
size: Math.min(pageSize, PROCESS_EVENTS_PER_PAGE),
|
||||
sort: [{ '@timestamp': forward ? 'asc' : 'desc' }],
|
||||
search_after: cursorMillis ? [cursorMillis] : undefined,
|
||||
fields: PROCESS_EVENT_FIELDS,
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -34,6 +34,7 @@
|
|||
"@kbn/config-schema",
|
||||
"@kbn/alerting-plugin",
|
||||
"@kbn/rule-data-utils",
|
||||
"@kbn/securitysolution-es-utils",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import { IO_EVENTS_ROUTE } from '@kbn/session-view-plugin/common/constants';
|
||||
import { IO_EVENTS_ROUTE, CURRENT_API_VERSION } from '@kbn/session-view-plugin/common/constants';
|
||||
import { FtrProviderContext } from '../../common/ftr_provider_context';
|
||||
|
||||
const MOCK_INDEX = 'logs-endpoint.events.process*';
|
||||
|
@ -22,6 +22,13 @@ export default function ioEventsTests({ getService }: FtrProviderContext) {
|
|||
const supertest = getService('supertest');
|
||||
const esArchiver = getService('esArchiver');
|
||||
|
||||
function getTestRoute() {
|
||||
return supertest
|
||||
.get(IO_EVENTS_ROUTE)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.set('Elastic-Api-Version', CURRENT_API_VERSION);
|
||||
}
|
||||
|
||||
describe(`Session view - ${IO_EVENTS_ROUTE} - with a basic license`, () => {
|
||||
before(async () => {
|
||||
await esArchiver.load('x-pack/test/functional/es_archives/session_view/process_events');
|
||||
|
@ -33,8 +40,21 @@ export default function ioEventsTests({ getService }: FtrProviderContext) {
|
|||
await esArchiver.unload('x-pack/test/functional/es_archives/session_view/io_events');
|
||||
});
|
||||
|
||||
it(`${IO_EVENTS_ROUTE} fails when an invalid api version is specified`, async () => {
|
||||
const response = await supertest
|
||||
.get(IO_EVENTS_ROUTE)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.set('Elastic-Api-Version', '999999')
|
||||
.query({
|
||||
index: MOCK_INDEX,
|
||||
sessionEntityId: MOCK_SESSION_ENTITY_ID,
|
||||
sessionStartTime: MOCK_SESSION_START_TIME,
|
||||
});
|
||||
expect(response.status).to.be(400);
|
||||
});
|
||||
|
||||
it(`${IO_EVENTS_ROUTE} returns a page of IO events`, async () => {
|
||||
const response = await supertest.get(IO_EVENTS_ROUTE).set('kbn-xsrf', 'foo').query({
|
||||
const response = await getTestRoute().query({
|
||||
index: MOCK_INDEX,
|
||||
sessionEntityId: MOCK_SESSION_ENTITY_ID,
|
||||
sessionStartTime: MOCK_SESSION_START_TIME,
|
||||
|
@ -56,7 +76,7 @@ export default function ioEventsTests({ getService }: FtrProviderContext) {
|
|||
});
|
||||
|
||||
it(`${IO_EVENTS_ROUTE} returns a page of IO events (w cursor)`, async () => {
|
||||
const response = await supertest.get(IO_EVENTS_ROUTE).set('kbn-xsrf', 'foo').query({
|
||||
const response = await getTestRoute().query({
|
||||
index: MOCK_INDEX,
|
||||
sessionEntityId: MOCK_SESSION_ENTITY_ID,
|
||||
sessionStartTime: MOCK_SESSION_START_TIME,
|
||||
|
|
|
@ -6,7 +6,10 @@
|
|||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import { PROCESS_EVENTS_ROUTE } from '@kbn/session-view-plugin/common/constants';
|
||||
import {
|
||||
PROCESS_EVENTS_ROUTE,
|
||||
CURRENT_API_VERSION,
|
||||
} from '@kbn/session-view-plugin/common/constants';
|
||||
import { FtrProviderContext } from '../../common/ftr_provider_context';
|
||||
import { User } from '../../../rule_registry/common/lib/authentication/types';
|
||||
|
||||
|
@ -44,6 +47,13 @@ export default function processEventsTests({ getService }: FtrProviderContext) {
|
|||
const supertestWithoutAuth = getService('supertestWithoutAuth');
|
||||
const esArchiver = getService('esArchiver');
|
||||
|
||||
function getTestRoute() {
|
||||
return supertest
|
||||
.get(PROCESS_EVENTS_ROUTE)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.set('Elastic-Api-Version', CURRENT_API_VERSION);
|
||||
}
|
||||
|
||||
describe(`Session view - ${PROCESS_EVENTS_ROUTE} - with a basic license`, () => {
|
||||
describe(`using typical process event data`, () => {
|
||||
before(async () => {
|
||||
|
@ -58,8 +68,21 @@ export default function processEventsTests({ getService }: FtrProviderContext) {
|
|||
await esArchiver.unload('x-pack/test/functional/es_archives/session_view/io_events');
|
||||
});
|
||||
|
||||
it(`${PROCESS_EVENTS_ROUTE} fails when an invalid api version is specified`, async () => {
|
||||
const response = await supertest
|
||||
.get(PROCESS_EVENTS_ROUTE)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.set('Elastic-Api-Version', '999999')
|
||||
.query({
|
||||
index: MOCK_INDEX,
|
||||
sessionEntityId: MOCK_SESSION_ENTITY_ID,
|
||||
sessionStartTime: MOCK_SESSION_START_TIME,
|
||||
});
|
||||
expect(response.status).to.be(400);
|
||||
});
|
||||
|
||||
it(`${PROCESS_EVENTS_ROUTE} returns a page of process events`, async () => {
|
||||
const response = await supertest.get(PROCESS_EVENTS_ROUTE).set('kbn-xsrf', 'foo').query({
|
||||
const response = await getTestRoute().query({
|
||||
index: MOCK_INDEX,
|
||||
sessionEntityId: MOCK_SESSION_ENTITY_ID,
|
||||
sessionStartTime: MOCK_SESSION_START_TIME,
|
||||
|
@ -71,7 +94,7 @@ export default function processEventsTests({ getService }: FtrProviderContext) {
|
|||
});
|
||||
|
||||
it(`${PROCESS_EVENTS_ROUTE} returns a page of process events (w alerts) (paging forward)`, async () => {
|
||||
const response = await supertest.get(PROCESS_EVENTS_ROUTE).set('kbn-xsrf', 'foo').query({
|
||||
const response = await getTestRoute().query({
|
||||
index: MOCK_INDEX,
|
||||
sessionEntityId: MOCK_SESSION_ENTITY_ID,
|
||||
sessionStartTime: MOCK_SESSION_START_TIME,
|
||||
|
@ -88,7 +111,7 @@ export default function processEventsTests({ getService }: FtrProviderContext) {
|
|||
});
|
||||
|
||||
it(`${PROCESS_EVENTS_ROUTE} returns a page of process events (w alerts) (paging backwards)`, async () => {
|
||||
const response = await supertest.get(PROCESS_EVENTS_ROUTE).set('kbn-xsrf', 'foo').query({
|
||||
const response = await getTestRoute().query({
|
||||
index: MOCK_INDEX,
|
||||
sessionEntityId: MOCK_SESSION_ENTITY_ID,
|
||||
sessionStartTime: MOCK_SESSION_START_TIME,
|
||||
|
@ -196,7 +219,7 @@ export default function processEventsTests({ getService }: FtrProviderContext) {
|
|||
});
|
||||
|
||||
it(`${PROCESS_EVENTS_ROUTE} returns a page of process events`, async () => {
|
||||
const response = await supertest.get(PROCESS_EVENTS_ROUTE).set('kbn-xsrf', 'foo').query({
|
||||
const response = await getTestRoute().query({
|
||||
index: MOCK_INDEX,
|
||||
sessionEntityId: MOCK_SESSION_ENTITY_ID,
|
||||
sessionStartTime: MOCK_SESSION_START_TIME,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue