Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions x-pack/plugins/session_view/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,18 @@
*/

export const PROCESS_EVENTS_ROUTE = '/internal/session_view/process_events_route';
export const ALERT_STATUS_ROUTE = '/internal/session_view/alert_status_route';
export const SESSION_ENTRY_LEADERS_ROUTE = '/internal/session_view/session_entry_leaders_route';
export const PROCESS_EVENTS_INDEX = 'logs-endpoint.events.process-default';
export const ALERTS_INDEX = '.siem-signals-default';
export const ENTRY_SESSION_ENTITY_ID_PROPERTY = 'process.entry_leader.entity_id';
export const ALERT_UUID_PROPERTY = 'kibana.alert.uuid';
export const KIBANA_DATE_FORMAT = 'MMM DD, YYYY @ hh:mm:ss.SSS';
export const ALERT_STATUS = {
OPEN: 'open',
ACKNOWLEDGED: 'acknowledged',
CLOSED: 'closed',
};

// We fetch a large number of events per page to mitigate a few design caveats in session viewer
// 1. Due to the hierarchical nature of the data (e.g we are rendering a time ordered pid tree) there are common scenarios where there
Expand All @@ -26,6 +33,23 @@ export const KIBANA_DATE_FORMAT = 'MMM DD, YYYY @ hh:mm:ss.SSS';
// search functionality will instead use a separate ES backend search to avoid this.
// 3. Fewer round trips to the backend!
export const PROCESS_EVENTS_PER_PAGE = 1000;

// As an initial approach, we won't be implementing pagination for alerts.
// Instead we will load this fixed amount of alerts as a maximum for a session.
// This could cause an edge case, where a noisy rule that alerts on every process event
// causes a session to only list and highlight up to 1000 alerts, even though there could
// be far greater than this amount. UX should be added to let the end user know this is
// happening and to revise their rule to be more specific.
export const ALERTS_PER_PAGE = 1000;

// when showing the count of alerts in details panel tab, if the number
// exceeds ALERT_COUNT_THRESHOLD we put a + next to it, e.g 999+
export const ALERT_COUNT_THRESHOLD = 999;

// react-query caching keys
export const QUERY_KEY_PROCESS_EVENTS = 'sessionViewProcessEvents';
export const QUERY_KEY_ALERTS = 'sessionViewAlerts';

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

export const MOUSE_EVENT_PLACEHOLDER = { stopPropagation: () => undefined } as React.MouseEvent;

export const DEBOUNCE_TIMEOUT = 500;
Original file line number Diff line number Diff line change
Expand Up @@ -920,6 +920,7 @@ export const childProcessMock: Process = {
hasOutput: () => false,
hasAlerts: () => false,
getAlerts: () => [],
updateAlertsStatus: (_) => undefined,
hasExec: () => false,
getOutput: () => '',
getDetails: () =>
Expand Down Expand Up @@ -998,6 +999,7 @@ export const processMock: Process = {
hasOutput: () => false,
hasAlerts: () => false,
getAlerts: () => [],
updateAlertsStatus: (_) => undefined,
hasExec: () => false,
getOutput: () => '',
getDetails: () =>
Expand Down Expand Up @@ -1173,6 +1175,7 @@ export const mockProcessMap = mockEvents.reduce(
hasOutput: () => false,
hasAlerts: () => false,
getAlerts: () => [],
updateAlertsStatus: (_) => undefined,
hasExec: () => false,
getOutput: () => '',
getDetails: () => event,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@
* 2.0.
*/

export interface AlertStatusEventEntityIdMap {
[alertUuid: string]: {
status: string;
processEntityId: string;
};
}

export const enum EventKind {
event = 'event',
signal = 'signal',
Expand Down Expand Up @@ -150,6 +157,7 @@ export interface Process {
hasOutput(): boolean;
hasAlerts(): boolean;
getAlerts(): ProcessEvent[];
updateAlertsStatus(updatedAlertsStatus: AlertStatusEventEntityIdMap): void;
hasExec(): boolean;
getOutput(): string;
getDetails(): ProcessEvent;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,21 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { cloneDeep } from 'lodash';
import {
mockData,
mockEvents,
mockAlerts,
mockProcessMap,
} from '../../../common/mocks/constants/session_view_process.mock';
import { Process, ProcessMap } from '../../../common/types/process_tree';
import {
AlertStatusEventEntityIdMap,
Process,
ProcessMap,
ProcessEvent,
} from '../../../common/types/process_tree';
import { ALERT_STATUS } from '../../../common/constants';
import {
updateAlertEventStatus,
updateProcessMap,
buildProcessTree,
searchProcessTree,
Expand All @@ -20,8 +29,6 @@ const SESSION_ENTITY_ID = '3d0192c6-7c54-5ee6-a110-3539a7cf42bc';
const SEARCH_QUERY = 'vi';
const SEARCH_RESULT_PROCESS_ID = '8e4daeb2-4a4e-56c4-980e-f0dcfdbc3727';

const mockEvents = mockData[0].events;

describe('process tree hook helpers tests', () => {
let processMap: ProcessMap;

Expand Down Expand Up @@ -73,4 +80,42 @@ describe('process tree hook helpers tests', () => {
// session leader should have autoExpand to be true
expect(processMap[SESSION_ENTITY_ID].autoExpand).toBeTruthy();
});

it('updateAlertEventStatus works', () => {
let events: ProcessEvent[] = cloneDeep([...mockEvents, ...mockAlerts]);
const updatedAlertsStatus: AlertStatusEventEntityIdMap = {
[mockAlerts[0].kibana?.alert.uuid!]: {
status: ALERT_STATUS.CLOSED,
processEntityId: mockAlerts[0].process.entity_id,
},
};

expect(
events.find(
(event) =>
event.kibana?.alert.uuid && event.kibana?.alert.uuid === mockAlerts[0].kibana?.alert.uuid
)?.kibana?.alert.workflow_status
).toEqual(ALERT_STATUS.OPEN);
expect(
events.find(
(event) =>
event.kibana?.alert.uuid && event.kibana?.alert.uuid === mockAlerts[1].kibana?.alert.uuid
)?.kibana?.alert.workflow_status
).toEqual(ALERT_STATUS.OPEN);

events = updateAlertEventStatus(events, updatedAlertsStatus);

expect(
events.find(
(event) =>
event.kibana?.alert.uuid && event.kibana?.alert.uuid === mockAlerts[0].kibana?.alert.uuid
)?.kibana?.alert.workflow_status
).toEqual(ALERT_STATUS.CLOSED);
expect(
events.find(
(event) =>
event.kibana?.alert.uuid && event.kibana?.alert.uuid === mockAlerts[1].kibana?.alert.uuid
)?.kibana?.alert.workflow_status
).toEqual(ALERT_STATUS.OPEN);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,40 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { Process, ProcessEvent, ProcessMap } from '../../../common/types/process_tree';
import {
AlertStatusEventEntityIdMap,
Process,
ProcessEvent,
ProcessMap,
} from '../../../common/types/process_tree';
import { ProcessImpl } from './hooks';

// if given event is an alert, and it exist in updatedAlertsStatus, update the alert's status
// with the updated status value in updatedAlertsStatus Map
export const updateAlertEventStatus = (
events: ProcessEvent[],
updatedAlertsStatus: AlertStatusEventEntityIdMap
) =>
events.map((event) => {
// do nothing if event is not an alert
if (!event.kibana) {
Copy link
Copy Markdown
Contributor

@michaelolo24 michaelolo24 Mar 22, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can do in a follow up PR, but in the future event.kibana may have other sub-properties, so may be best to use event?.kibana?.alert to be explicit

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks Michael, will update in my follow up PR!

return event;
}

return {
...event,
kibana: {
...event.kibana,
alert: {
...event.kibana.alert,
workflow_status:
updatedAlertsStatus[event.kibana.alert?.uuid]?.status ??
event.kibana.alert?.workflow_status,
},
},
};
});

// given a page of new events, add these events to the appropriate process class model
// create a new process if none are created and return the mutated processMap
export const updateProcessMap = (processMap: ProcessMap, events: ProcessEvent[]) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,27 @@ import _ from 'lodash';
import memoizeOne from 'memoize-one';
import { useState, useEffect } from 'react';
import {
AlertStatusEventEntityIdMap,
EventAction,
EventKind,
Process,
ProcessEvent,
ProcessMap,
ProcessEventsPage,
} from '../../../common/types/process_tree';
import { processNewEvents, searchProcessTree, autoExpandProcessTree } from './helpers';
import {
updateAlertEventStatus,
processNewEvents,
searchProcessTree,
autoExpandProcessTree,
} from './helpers';
import { sortProcesses } from '../../../common/utils/sort_processes';

interface UseProcessTreeDeps {
sessionEntityId: string;
data: ProcessEventsPage[];
searchQuery?: string;
updatedAlertsStatus: AlertStatusEventEntityIdMap;
}

export class ProcessImpl implements Process {
Expand Down Expand Up @@ -103,6 +110,10 @@ export class ProcessImpl implements Process {
return this.filterEventsByKind(this.events, EventKind.signal);
}

updateAlertsStatus(updatedAlertsStatus: AlertStatusEventEntityIdMap) {
this.events = updateAlertEventStatus(this.events, updatedAlertsStatus);
}

hasExec() {
return !!this.findEventByAction(this.events, EventAction.exec);
}
Expand All @@ -129,6 +140,7 @@ export class ProcessImpl implements Process {
// only used to auto expand parts of the tree that could be of interest.
isUserEntered() {
const event = this.getDetails();

const {
pid,
tty,
Expand Down Expand Up @@ -181,7 +193,12 @@ export class ProcessImpl implements Process {
});
}

export const useProcessTree = ({ sessionEntityId, data, searchQuery }: UseProcessTreeDeps) => {
export const useProcessTree = ({
sessionEntityId,
data,
searchQuery,
updatedAlertsStatus,
}: UseProcessTreeDeps) => {
// initialize map, as well as a placeholder for session leader process
// we add a fake session leader event, sourced from wide event data.
// this is because we might not always have a session leader event
Expand Down Expand Up @@ -250,5 +267,13 @@ export const useProcessTree = ({ sessionEntityId, data, searchQuery }: UseProces

sessionLeader.orphans = orphans;

// update alert status in processMap for alerts in updatedAlertsStatus
Object.keys(updatedAlertsStatus).forEach((alertUuid) => {
const process = processMap[updatedAlertsStatus[alertUuid].processEntityId];
if (process) {
process.updateAlertsStatus(updatedAlertsStatus);
}
});

return { sessionLeader: processMap[sessionEntityId], processMap, searchResults };
};
Loading