"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.useProcessTree = exports.ProcessImpl = void 0; var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var _memoizeOne = _interopRequireDefault(require("memoize-one")); var _lodash = require("lodash"); var _react = require("react"); var _helpers = require("./helpers"); var _sort_processes = require("../../../common/utils/sort_processes"); /* * 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. */ class ProcessImpl { constructor(id) { (0, _defineProperty2.default)(this, "id", void 0); (0, _defineProperty2.default)(this, "events", void 0); (0, _defineProperty2.default)(this, "alerts", void 0); (0, _defineProperty2.default)(this, "children", void 0); (0, _defineProperty2.default)(this, "parent", void 0); (0, _defineProperty2.default)(this, "autoExpand", void 0); (0, _defineProperty2.default)(this, "searchMatched", void 0); (0, _defineProperty2.default)(this, "orphans", void 0); (0, _defineProperty2.default)(this, "getChildrenMemo", (0, _memoizeOne.default)((children, orphans, verboseMode) => { if (children.length === 0 && orphans.length === 0) { return []; } // if there are orphans, we just render them inline with the other child processes (currently only session leader does this) if (orphans.length) { children = [...children, ...orphans]; } // When verboseMode is false, we filter out noise via a few techniques. // This option is driven by the "verbose mode" toggle in SessionView/index.tsx if (!verboseMode) { children = children.filter(child => { if (child.events.length === 0) { return false; } // processes with alerts will never be filtered out if (child.autoExpand || child.hasAlerts()) { return true; } if (child.isVerbose()) { return false; } return true; }); } return (0, _lodash.sortedUniqBy)(children.sort(_sort_processes.sortProcesses), child => child.id); })); (0, _defineProperty2.default)(this, "findEventByAction", (0, _memoizeOne.default)((events, action) => { return events.find(({ event }) => { var _event$action; return event === null || event === void 0 ? void 0 : (_event$action = event.action) === null || _event$action === void 0 ? void 0 : _event$action.includes(action); }); })); (0, _defineProperty2.default)(this, "findEventByKind", (0, _memoizeOne.default)((events, kind) => { return events.find(({ event }) => (event === null || event === void 0 ? void 0 : event.kind) === kind); })); (0, _defineProperty2.default)(this, "filterEventsByAction", (0, _memoizeOne.default)((events, action) => { return events.filter(({ event }) => { var _event$action2; return event === null || event === void 0 ? void 0 : (_event$action2 = event.action) === null || _event$action2 === void 0 ? void 0 : _event$action2.includes(action); }); })); (0, _defineProperty2.default)(this, "filterEventsByKind", (0, _memoizeOne.default)((events, kind) => { return events.filter(({ event }) => (event === null || event === void 0 ? void 0 : event.kind) === kind); })); // returns the most recent fork, exec, or end event // to be used as a source for the most up to date details // on the processes lifecycle. (0, _defineProperty2.default)(this, "getDetailsMemo", (0, _memoizeOne.default)(events => { var _filtered; const filtered = events.filter(processEvent => { var _processEvent$event; const action = processEvent === null || processEvent === void 0 ? void 0 : (_processEvent$event = processEvent.event) === null || _processEvent$event === void 0 ? void 0 : _processEvent$event.action; return (action === null || action === void 0 ? void 0 : action.includes('fork')) || (action === null || action === void 0 ? void 0 : action.includes('exec')) || (action === null || action === void 0 ? void 0 : action.includes('end')); }); // there are some anomalous processes which are omitting event.action // we return whatever we have regardless so we at least render something in process tree if (filtered.length === 0 && events.length > 0) { return events[events.length - 1]; } // because events is already ordered by @timestamp we take the last event // which could be a fork (w no exec or exit), most recent exec event (there can be multiple), or end event. // If a process has an 'end' event will always be returned (since it is last and includes details like exit_code and end time) return (_filtered = filtered[filtered.length - 1]) !== null && _filtered !== void 0 ? _filtered : {}; })); this.id = id; this.events = []; this.alerts = []; this.children = []; this.orphans = []; this.autoExpand = false; this.searchMatched = null; } addEvent(newEvent) { const exists = this.events.find(event => { var _event$event, _newEvent$event; return ((_event$event = event.event) === null || _event$event === void 0 ? void 0 : _event$event.id) === ((_newEvent$event = newEvent.event) === null || _newEvent$event === void 0 ? void 0 : _newEvent$event.id); }); if (!exists) { this.events = this.events.concat(newEvent); } } addAlert(alert) { const exists = this.alerts.find(event => { var _event$event2, _alert$event; return ((_event$event2 = event.event) === null || _event$event2 === void 0 ? void 0 : _event$event2.id) === ((_alert$event = alert.event) === null || _alert$event === void 0 ? void 0 : _alert$event.id); }); if (!exists) { this.alerts = this.alerts.concat(alert); } } addChild(newChild) { this.children = this.children.concat(newChild); } clearSearch() { this.searchMatched = null; } getChildren(verboseMode) { return this.getChildrenMemo(this.children, this.orphans, verboseMode); } isVerbose() { var _this$getDetails$proc; const { group_leader: groupLeader, session_leader: sessionLeader, entry_leader: entryLeader } = (_this$getDetails$proc = this.getDetails().process) !== null && _this$getDetails$proc !== void 0 ? _this$getDetails$proc : {}; // Processes that have their session leader as their process group leader are considered "verbose" // This accounts for a lot of noise from bash and other shells forking, running auto completion processes and // other shell startup activities (e.g bashrc .profile etc) if (this.id !== (entryLeader === null || entryLeader === void 0 ? void 0 : entryLeader.entity_id) && (!groupLeader || !sessionLeader || groupLeader.pid === sessionLeader.pid)) { return true; } return false; } hasOutput() { return !!this.findEventByAction(this.events, 'text_output'); } hasAlerts() { return !!this.alerts.length; } hasAlert(alertUuid) { if (!alertUuid) { return false; } return !!this.alerts.find(event => { var _event$kibana, _event$kibana$alert; return ((_event$kibana = event.kibana) === null || _event$kibana === void 0 ? void 0 : (_event$kibana$alert = _event$kibana.alert) === null || _event$kibana$alert === void 0 ? void 0 : _event$kibana$alert.uuid) === alertUuid; }); } getAlerts() { return this.alerts; } updateAlertsStatus(updatedAlertsStatus) { this.alerts = (0, _helpers.updateAlertEventStatus)(this.alerts, updatedAlertsStatus); } hasExec() { return !!this.findEventByAction(this.events, 'exec'); } hasExited() { return !!this.findEventByAction(this.events, 'end'); } getDetails() { return this.getDetailsMemo(this.events); } getOutput() { // not implemented, output ECS schema not defined (for a future release) return ''; } getEndTime() { const endEvent = this.findEventByAction(this.events, 'end'); return (endEvent === null || endEvent === void 0 ? void 0 : endEvent['@timestamp']) || ''; } // isUserEntered is a best guess at which processes were initiated by a real person // In most situations a user entered command in a shell such as bash, will cause bash // to fork, create a new process group, and exec the command (e.g ls). If the session // has a controlling tty (aka an interactive session), we assume process group leaders // with a session leader for a parent are "user entered". // Because of the presence of false positives in this calculation, it is currently // only used to auto expand parts of the tree that could be of interest. isUserEntered() { var _event$process; const event = this.getDetails(); if (!event) { return false; } const { pid, tty, parent, session_leader: sessionLeader, group_leader: groupLeader } = (_event$process = event.process) !== null && _event$process !== void 0 ? _event$process : {}; const parentIsASessionLeader = parent && sessionLeader && parent.pid === sessionLeader.pid; const processIsAGroupLeader = groupLeader && pid === groupLeader.pid; const sessionIsInteractive = !!tty; return !!(sessionIsInteractive && parentIsASessionLeader && processIsAGroupLeader); } isDescendantOf(process) { let parent = this.parent; while (parent) { if (parent === process) { return true; } parent = parent.parent; } return false; } } exports.ProcessImpl = ProcessImpl; const useProcessTree = ({ sessionEntityId, data, searchQuery, updatedAlertsStatus, verboseMode, jumpToEntityId }) => { var _data$, _data$$events; const firstEvent = (_data$ = data[0]) === null || _data$ === void 0 ? void 0 : (_data$$events = _data$.events) === null || _data$$events === void 0 ? void 0 : _data$$events[0]; const sessionLeaderProcess = (0, _react.useMemo)(() => { var _firstEvent$process; const entryLeader = firstEvent === null || firstEvent === void 0 ? void 0 : (_firstEvent$process = firstEvent.process) === null || _firstEvent$process === void 0 ? void 0 : _firstEvent$process.entry_leader; return (0, _helpers.inferProcessFromLeaderInfo)(firstEvent, entryLeader); }, [firstEvent]); const initializedProcessMap = { [sessionEntityId]: sessionLeaderProcess }; const [processMap, setProcessMap] = (0, _react.useState)(initializedProcessMap); const [processedPages, setProcessedPages] = (0, _react.useState)([]); const [searchResults, setSearchResults] = (0, _react.useState)([]); const [orphans, setOrphans] = (0, _react.useState)([]); (0, _react.useEffect)(() => { let updatedProcessMap = processMap; let newOrphans = orphans; const newProcessedPages = []; data.forEach((page, i) => { const processed = processedPages.find(p => { var _p$events, _page$events; return p.cursor === page.cursor && ((_p$events = p.events) === null || _p$events === void 0 ? void 0 : _p$events.length) === ((_page$events = page.events) === null || _page$events === void 0 ? void 0 : _page$events.length); }); if (!processed) { const backwards = i < processedPages.length; const result = (0, _helpers.processNewEvents)(updatedProcessMap, page.events, orphans, sessionEntityId, backwards); updatedProcessMap = result[0]; newOrphans = result[1]; newProcessedPages.push(page); } }); if (newProcessedPages.length > 0) { setProcessMap({ ...updatedProcessMap }); setProcessedPages([...processedPages, ...newProcessedPages]); setOrphans(newOrphans); (0, _helpers.autoExpandProcessTree)(updatedProcessMap, jumpToEntityId); } }, [data, processMap, orphans, processedPages, sessionEntityId, jumpToEntityId]); (0, _react.useEffect)(() => { setSearchResults((0, _helpers.searchProcessTree)(processMap, searchQuery, verboseMode)); }, [searchQuery, processMap, verboseMode]); // set new orphans array on the session leader const sessionLeader = processMap[sessionEntityId]; 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 }; }; exports.useProcessTree = useProcessTree;