diff --git a/x-pack/plugins/session_view/common/utils/expand_dotted_object.ts b/x-pack/plugins/session_view/common/utils/expand_dotted_object.ts
new file mode 100644
index 0000000000000..f90f589486ff5
--- /dev/null
+++ b/x-pack/plugins/session_view/common/utils/expand_dotted_object.ts
@@ -0,0 +1,55 @@
+/*
+ * 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 { merge } from '@kbn/std';
+
+const expandDottedField = (dottedFieldName: string, val: unknown): object => {
+ const parts = dottedFieldName.split('.');
+ if (parts.length === 1) {
+ return { [parts[0]]: val };
+ } else {
+ return { [parts[0]]: expandDottedField(parts.slice(1).join('.'), val) };
+ }
+};
+
+/*
+ * Expands an object with "dotted" fields to a nested object with unflattened fields.
+ *
+ * Example:
+ * expandDottedObject({
+ * "kibana.alert.depth": 1,
+ * "kibana.alert.ancestors": [{
+ * id: "d5e8eb51-a6a0-456d-8a15-4b79bfec3d71",
+ * type: "event",
+ * index: "signal_index",
+ * depth: 0,
+ * }],
+ * })
+ *
+ * => {
+ * kibana: {
+ * alert: {
+ * ancestors: [
+ * id: "d5e8eb51-a6a0-456d-8a15-4b79bfec3d71",
+ * type: "event",
+ * index: "signal_index",
+ * depth: 0,
+ * ],
+ * depth: 1,
+ * },
+ * },
+ * }
+ */
+export const expandDottedObject = (dottedObj: object) => {
+ if (Array.isArray(dottedObj)) {
+ return dottedObj;
+ }
+ return Object.entries(dottedObj).reduce(
+ (acc, [key, val]) => merge(acc, expandDottedField(key, val)),
+ {}
+ );
+};
diff --git a/x-pack/plugins/session_view/public/components/ProcessTree/index.tsx b/x-pack/plugins/session_view/public/components/ProcessTree/index.tsx
index e18b480dbf28f..bf1fe3dd54742 100644
--- a/x-pack/plugins/session_view/public/components/ProcessTree/index.tsx
+++ b/x-pack/plugins/session_view/public/components/ProcessTree/index.tsx
@@ -10,6 +10,8 @@ import { useProcessTree, ProcessEvent, Process } from '../../hooks/use_process_t
import { useScroll } from '../../hooks/use_scroll';
import { useStyles } from './styles';
+const HIDE_ORPHANS = true;
+
interface ProcessTreeDeps {
// process.entity_id to act as root node (typically a session (or entry session) leader).
sessionEntityId: string;
@@ -122,6 +124,21 @@ export const ProcessTree = ({
// eslint-disable-next-line no-console
console.log(searchResults);
+ const renderOrphans = () => {
+ if (!HIDE_ORPHANS) {
+ return orphans.map((process) => {
+ return (
+
+ );
+ })
+ }
+ }
+
return (
{sessionLeader && (
@@ -131,16 +148,7 @@ export const ProcessTree = ({
onProcessSelected={onProcessSelected}
/>
)}
- {orphans.map((process) => {
- return (
-
- );
- })}
+ {renderOrphans()}
);
diff --git a/x-pack/plugins/session_view/public/components/ProcessTree/styles.ts b/x-pack/plugins/session_view/public/components/ProcessTree/styles.ts
index 8304ed455ea37..2a8a76d3b5ce0 100644
--- a/x-pack/plugins/session_view/public/components/ProcessTree/styles.ts
+++ b/x-pack/plugins/session_view/public/components/ProcessTree/styles.ts
@@ -22,6 +22,7 @@ export const useStyles = () => {
background-color: ${euiTheme.colors.lightestShade};
padding-top: ${padding};
padding-left: ${padding};
+ padding-bottom: ${padding};
display: flex;
flex-direction: column;
`;
diff --git a/x-pack/plugins/session_view/public/components/ProcessTreeAlerts/index.tsx b/x-pack/plugins/session_view/public/components/ProcessTreeAlerts/index.tsx
new file mode 100644
index 0000000000000..9294669faf4e8
--- /dev/null
+++ b/x-pack/plugins/session_view/public/components/ProcessTreeAlerts/index.tsx
@@ -0,0 +1,96 @@
+/*
+ * 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 React from 'react';
+import {
+ EuiButton,
+ EuiText,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiHorizontalRule,
+ EuiSpacer,
+} from '@elastic/eui';
+import { FormattedMessage } from '@kbn/i18n/react';
+import { useStyles } from './styles';
+import { ProcessEvent } from '../../hooks/use_process_tree';
+import { useKibana } from '../../../../../../src/plugins/kibana_react/public';
+import { CoreStart } from '../../../../../../src/core/public';
+
+interface ProcessTreeAlertsDeps {
+ alerts: ProcessEvent[];
+}
+
+export function ProcessTreeAlerts({ alerts }: ProcessTreeAlertsDeps) {
+ const styles = useStyles();
+ const { http } = useKibana().services;
+
+ if (alerts.length === 0) {
+ return null;
+ }
+
+ const getRuleUrl = (alert: ProcessEvent) => {
+ return http.basePath.prepend(`/app/security/rules/id/${alert.kibana?.alert.rule.uuid}`);
+ };
+
+ const renderAlertDetails = (alert: ProcessEvent, index: number) => {
+ if (!alert.kibana) {
+ return null;
+ }
+
+ const { uuid, rule, original_event: event, workflow_status: status } = alert.kibana.alert;
+ const { name, query, severity } = rule;
+
+ return (
+
+
+
+
+
+
+ {name}
+
+
+
+ {query}
+
+
+
+
+
+ {severity}
+
+
+
+ {status}
+
+
+
+
+
+ {event.action}
+
+
+
+
+
+
+
+
+ {index < alerts.length - 1 && (
+
+
+
+ )}
+
+ );
+ };
+
+ return {alerts.map(renderAlertDetails)}
;
+}
diff --git a/x-pack/plugins/session_view/public/components/ProcessTreeAlerts/styles.ts b/x-pack/plugins/session_view/public/components/ProcessTreeAlerts/styles.ts
new file mode 100644
index 0000000000000..be9bd7a2c2c14
--- /dev/null
+++ b/x-pack/plugins/session_view/public/components/ProcessTreeAlerts/styles.ts
@@ -0,0 +1,37 @@
+/*
+ * 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 { useMemo } from 'react';
+import { useEuiTheme } from '@elastic/eui';
+import { CSSObject } from '@emotion/react';
+
+export const useStyles = () => {
+ const { euiTheme } = useEuiTheme();
+
+ const cached = useMemo(() => {
+ const { size, colors, border } = euiTheme;
+
+ const container: CSSObject = {
+ marginTop: size.s,
+ marginRight: size.s,
+ color: colors.text,
+ padding: size.m,
+ borderStyle: 'solid',
+ borderColor: colors.lightShade,
+ borderWidth: border.width.thin,
+ borderRadius: border.radius.medium,
+ maxWidth: 800,
+ backgroundColor: 'white',
+ };
+
+ return {
+ container,
+ };
+ }, [euiTheme]);
+
+ return cached;
+};
diff --git a/x-pack/plugins/session_view/public/components/ProcessTreeNode/index.tsx b/x-pack/plugins/session_view/public/components/ProcessTreeNode/index.tsx
index 60146b48ff7f6..c48d8e0b9bd2a 100644
--- a/x-pack/plugins/session_view/public/components/ProcessTreeNode/index.tsx
+++ b/x-pack/plugins/session_view/public/components/ProcessTreeNode/index.tsx
@@ -16,6 +16,7 @@ import { EuiButton, EuiIcon } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { Process } from '../../hooks/use_process_tree';
import { useStyles } from './styles';
+import { ProcessTreeAlerts } from '../ProcessTreeAlerts';
interface ProcessDeps {
process: Process;
@@ -36,16 +37,35 @@ export function ProcessTreeNode({
depth = 0,
onProcessSelected,
}: ProcessDeps) {
- const styles = useStyles({ depth });
const textRef = useRef(null);
const [childrenExpanded, setChildrenExpanded] = useState(isSessionLeader || process.autoExpand);
+ const [alertsExpanded, setAlertsExpanded] = useState(false);
const { searchMatched } = process;
useEffect(() => {
setChildrenExpanded(isSessionLeader || process.autoExpand);
}, [isSessionLeader, process.autoExpand]);
+
+ const processDetails = useMemo(() => {
+ return process.getDetails();
+ }, [process.events.length]);
+
+ const hasExec = useMemo(() => {
+ return process.hasExec();
+ }, [process.events.length]);
+
+ const alerts = useMemo(() => {
+ return process.getAlerts();
+ }, [process.events.length]);
+
+ if (!processDetails) {
+ return null;
+ }
+
+ const styles = useStyles({ depth, hasAlerts: !!alerts.length });
+
useLayoutEffect(() => {
if (searchMatched !== null && textRef.current) {
const regex = new RegExp(searchMatched);
@@ -59,18 +79,6 @@ export function ProcessTreeNode({
}
}, [searchMatched]);
- const processDetails = useMemo(() => {
- return process.getDetails();
- }, [process.events.length]);
-
- const hasExec = useMemo(() => {
- return process.hasExec();
- }, [process.events.length]);
-
- if (!processDetails) {
- return null;
- }
-
const { interactive } = processDetails.process;
const renderChildren = () => {
@@ -98,16 +106,27 @@ export function ProcessTreeNode({
);
};
+ const getExpandedIcon = (expanded: boolean) => {
+ return expanded ? 'arrowUp' : 'arrowDown';
+ }
+
const renderButtons = () => {
const buttons = [];
if (!isSessionLeader && process.children.length > 0) {
- const childrenExpandedIcon = childrenExpanded ? 'arrowUp' : 'arrowDown';
-
buttons.push(
- setChildrenExpanded(!childrenExpanded)}>
+ setChildrenExpanded(!childrenExpanded)}>
-
+
+
+ );
+ }
+
+ if (alerts.length) {
+ buttons.push(
+ setAlertsExpanded(!alertsExpanded)}>
+
+
);
}
@@ -166,6 +185,16 @@ export function ProcessTreeNode({
);
};
+ const renderRootEscalation = () => {
+ const { user, parent } = processDetails.process;
+
+ if (user.name === 'root' && user.id !== parent.user.id) {
+ return
+
+
+ }
+ }
+
const onProcessClicked = (e: MouseEvent) => {
e.stopPropagation();
@@ -187,9 +216,11 @@ export function ProcessTreeNode({
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events */}
{isSessionLeader ? renderSessionLeader() : renderProcess()}
+ {renderRootEscalation()}
{renderButtons()}
+ {alertsExpanded && }
{renderChildren()}
>
);
diff --git a/x-pack/plugins/session_view/public/components/ProcessTreeNode/styles.ts b/x-pack/plugins/session_view/public/components/ProcessTreeNode/styles.ts
index c451460dc8f2c..7407c2feac680 100644
--- a/x-pack/plugins/session_view/public/components/ProcessTreeNode/styles.ts
+++ b/x-pack/plugins/session_view/public/components/ProcessTreeNode/styles.ts
@@ -13,14 +13,22 @@ const TREE_INDENT = 32;
interface StylesDeps {
depth: number;
+ hasAlerts: boolean;
}
-export const useStyles = ({ depth }: StylesDeps) => {
+export const useStyles = ({ depth, hasAlerts }: StylesDeps) => {
const { euiTheme } = useEuiTheme();
const cached = useMemo(() => {
const { colors, border, font, size } = euiTheme;
+ enum ButtonType {
+ children = 'children',
+ alerts = 'alerts',
+ output = 'output',
+ userChanged = 'user'
+ }
+
const darkText: CSSObject = {
color: colors.text,
};
@@ -56,25 +64,51 @@ export const useStyles = ({ depth }: StylesDeps) => {
fontSize: '11px',
fontFamily: font.familyCode,
borderRadius: border.radius.medium,
- background: 'rgba(0, 119, 204, 0.1)',
- border: '1px solid rgba(96, 146, 192, 0.3)',
color: colors.text,
marginLeft: size.s,
+ minWidth: 0,
};
const buttonArrow: CSSObject = {
marginLeft: size.s,
};
+ const getButtonStyle = (type: string) => {
+ let background = 'rgba(170, 101, 86, 0.04)';
+ let borderStyle = '1px solid rgba(170, 101, 86, 0.48)';
+
+ switch (type) {
+ case ButtonType.alerts:
+ background = 'rgba(189, 39, 30, 0.04)';
+ borderStyle = '1px solid rgba(189, 39, 30, 0.48)';
+ break;
+ case ButtonType.userChanged:
+ case ButtonType.output:
+ background = 'rgba(0, 119, 204, 0.04)';
+ borderStyle = '1px solid rgba(0, 119, 204, 0.48)';
+ break;
+ }
+
+ return {
+ ...button,
+ background,
+ border: borderStyle,
+ };
+ };
+
/**
* gets border, bg and hover colors for a process
*/
const getHighlightColors = () => {
- const bgColor = 'none';
- const hoverColor = '#6B5FC6';
- const borderColor = 'transparent';
+ let bgColor = 'none';
+ let hoverColor = '#6B5FC6';
+ let borderColor = 'transparent';
// TODO: alerts highlight colors
+ if (hasAlerts) {
+ bgColor = 'rgba(189, 39, 30, 0.04)';
+ borderColor = 'rgba(189, 39, 30, 0.48)';
+ }
return { bgColor, borderColor, hoverColor };
};
@@ -126,18 +160,26 @@ export const useStyles = ({ depth }: StylesDeps) => {
marginTop: '8px',
};
+ const alertDetails: CSSObject = {
+ padding: size.s,
+ border: `3px dotted ${colors.lightShade}`,
+ borderRadius: border.radius.medium,
+ };
+
return {
darkText,
searchHighlight,
children,
- button,
- buttonArrow,
processNode,
wrapper,
workingDir,
userEnteredIcon,
+ buttonArrow,
+ ButtonType,
+ getButtonStyle,
+ alertDetails,
};
- }, [depth, euiTheme]);
+ }, [depth, euiTheme, hasAlerts]);
return cached;
};
diff --git a/x-pack/plugins/session_view/public/components/SessionView/index.tsx b/x-pack/plugins/session_view/public/components/SessionView/index.tsx
index 3bb23c3da3535..d1b6e2606995e 100644
--- a/x-pack/plugins/session_view/public/components/SessionView/index.tsx
+++ b/x-pack/plugins/session_view/public/components/SessionView/index.tsx
@@ -21,8 +21,14 @@ interface SessionViewDeps {
}
interface ProcessEventResults {
- hits: any[];
- length: number;
+ events: {
+ hits: any[];
+ total: number;
+ };
+ alerts: {
+ hits: any[];
+ total: number;
+ };
}
/**
@@ -61,23 +67,34 @@ export const SessionView = ({ sessionEntityId, height }: SessionViewDeps) => {
() =>
http.get(PROCESS_EVENTS_ROUTE, {
query: {
- indexes: ['cmd*', '.siem-signals-*'],
- sessionEntityId
+ sessionEntityId,
},
})
);
- useEffect(() => {
- if (!getData) {
- return;
+ const sortEvents = (a: ProcessEvent, b: ProcessEvent) => {
+ if (a['@timestamp'].valueOf() < b['@timestamp'].valueOf()) {
+ return -1;
+ } else if (a['@timestamp'].valueOf() > b['@timestamp'].valueOf()) {
+ return 1;
}
- if (getData.length <= data.length) {
+ return 0;
+ };
+
+ useEffect(() => {
+ if (!getData) {
return;
}
- setData(getData.hits.map((event: any) => event._source as ProcessEvent));
- // eslint-disable-next-line react-hooks/exhaustive-deps
+ const events: ProcessEvent[] = getData.events.hits.map(
+ (event: any) => event._source as ProcessEvent
+ );
+ const alerts: ProcessEvent[] = getData.alerts.hits.map((event: any) => {
+ return event._source as ProcessEvent;
+ });
+ const all: ProcessEvent[] = events.concat(alerts).sort(sortEvents);
+ setData(all);
}, [getData]);
const renderNoData = () => {
@@ -96,15 +113,17 @@ export const SessionView = ({ sessionEntityId, height }: SessionViewDeps) => {
return (
<>
-
+ {data && (
+
+ )}
>
);
};
diff --git a/x-pack/plugins/session_view/public/components/SessionViewPage/index.tsx b/x-pack/plugins/session_view/public/components/SessionViewPage/index.tsx
index 9c97a1e894b94..cadfcdafe7b4e 100644
--- a/x-pack/plugins/session_view/public/components/SessionViewPage/index.tsx
+++ b/x-pack/plugins/session_view/public/components/SessionViewPage/index.tsx
@@ -52,15 +52,14 @@ export const SessionViewPage = (props: RouteComponentProps) => {
}
}, [data]);
+
return (
{sessionEntityId && }
diff --git a/x-pack/plugins/session_view/public/hooks/use_process_tree.ts b/x-pack/plugins/session_view/public/hooks/use_process_tree.ts
index 03b3c774a7dee..dd6f02ba24477 100644
--- a/x-pack/plugins/session_view/public/hooks/use_process_tree.ts
+++ b/x-pack/plugins/session_view/public/hooks/use_process_tree.ts
@@ -55,7 +55,7 @@ interface ProcessSelf extends ProcessFields {
}
export interface ProcessEvent {
- '@timestamp': string;
+ '@timestamp': Date;
event: {
kind: EventKind;
category: string;
@@ -80,8 +80,29 @@ export interface ProcessEvent {
};
};
process: ProcessSelf;
-
- // TODO: alerts? output? file_descriptors?
+ kibana?: {
+ alert: {
+ uuid: string;
+ reason: string;
+ workflow_status: string;
+ status: string;
+ original_time: Date;
+ original_event: {
+ action: string;
+ },
+ rule: {
+ category: string;
+ consumer: string;
+ description: string;
+ enabled: boolean;
+ name: string;
+ query: string;
+ risk_score: number;
+ severity: string;
+ uuid: string;
+ }
+ }
+ }
}
export interface Process {
@@ -93,6 +114,7 @@ export interface Process {
searchMatched: string | null; // either false, or set to searchQuery
hasOutput(): boolean;
hasAlerts(): boolean;
+ getAlerts(): ProcessEvent[];
hasExec(): boolean;
getOutput(): string;
getDetails(): ProcessEvent;
@@ -124,6 +146,10 @@ class ProcessImpl implements Process {
hasAlerts() {
return !!this.events.find(({ event }) => event.kind === EventKind.signal);
}
+
+ getAlerts() {
+ return this.events.filter(({ event }) => event.kind === EventKind.signal);
+ }
hasExec() {
return !!this.events.find(({ event }) => event.action === EventAction.exec);
@@ -134,8 +160,12 @@ class ProcessImpl implements Process {
}
getDetails() {
- const execsForks = this.events.filter(({ event }) => [EventAction.exec, EventAction.fork].includes(event.action));
+ const execsForks = this.events.filter(({ event }) => event.action === EventAction.exec || event.action === EventAction.fork);
+ if (execsForks.length === 0) {
+ debugger;
+ }
+
return execsForks[execsForks.length - 1];
}
@@ -173,8 +203,19 @@ export const useProcessTree = ({
searchQuery,
}: 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
+ // especially if we are paging in reverse from deep within a large session
+ const fakeLeaderEvent = forward.find(event => event.event.kind === EventKind.event);
+ const sessionLeaderProcess = new ProcessImpl(sessionEntityId);
+
+ if (fakeLeaderEvent) {
+ fakeLeaderEvent.process = { ...fakeLeaderEvent.process, ...fakeLeaderEvent.process.entry};
+ sessionLeaderProcess.events.push(fakeLeaderEvent);
+ }
+
const initializedProcessMap: ProcessMap = {
- [sessionEntityId]: new ProcessImpl(sessionEntityId),
+ [sessionEntityId]: sessionLeaderProcess,
};
const [processMap, setProcessMap] = useState(initializedProcessMap);
@@ -195,13 +236,6 @@ export const useProcessTree = ({
process.events.push(event);
});
-
- if (processMap[sessionEntityId].events.length === 0) {
- processMap[sessionEntityId].events.push({
- ...events[0],
- ...events[0].process.entry
- })
- }
};
const buildProcessTree = (events: ProcessEvent[], backwardDirection: boolean = false) => {
diff --git a/x-pack/plugins/session_view/server/routes/process_events_route.ts b/x-pack/plugins/session_view/server/routes/process_events_route.ts
index 65c04b3fefaad..5d53ebf67d096 100644
--- a/x-pack/plugins/session_view/server/routes/process_events_route.ts
+++ b/x-pack/plugins/session_view/server/routes/process_events_route.ts
@@ -7,6 +7,7 @@
import { schema } from '@kbn/config-schema';
import { IRouter } from '../../../../../src/core/server';
import { PROCESS_EVENTS_ROUTE, PROCESS_EVENTS_PER_PAGE } from '../../common/constants';
+import { expandDottedObject } from '../../common/utils/expand_dotted_object';
export const registerProcessEventsRoute = (router: IRouter) => {
router.get(
@@ -14,28 +15,58 @@ export const registerProcessEventsRoute = (router: IRouter) => {
path: PROCESS_EVENTS_ROUTE,
validate: {
query: schema.object({
- indexes: schema.maybe(schema.arrayOf(schema.string())),
sessionEntityId: schema.maybe(schema.string()),
}),
},
},
async (context, request, response) => {
const client = context.core.elasticsearch.client.asCurrentUser;
+
+ // TODO: would be good to figure out how to add securitySolution as a dep
+ // and make use of this way of getting the siem-signals index, instead of
+ // hardcoding it.
+ // const siemClient = context.securitySolution.getAppClient();
+ // const alertsIndex = siemClient.getSignalsIndex(),
- const { indexes, sessionEntityId } = request.query;
+ const { sessionEntityId } = request.query;
const search = await client.search({
- index: indexes,
- query: {
- match: {
- 'process.entry.entity_id': sessionEntityId,
+ index: ['cmd'],
+ body: {
+ query: {
+ match: {
+ 'process.entry.entity_id': sessionEntityId,
+ },
},
- },
- size: PROCESS_EVENTS_PER_PAGE,
- sort: '@timestamp',
+ size: PROCESS_EVENTS_PER_PAGE,
+ sort: [{ '@timestamp': 'asc' }],
+ }
+ });
+
+ // temporary approach. ideally we'd pull from both these indexes above, but unfortunately
+ // our new fields like process.entry.entity_id won't have a mapping in the .siem-signals index
+ // this should hopefully change once we update ECS or endpoint-package..
+ // for demo purpose we just load all alerts, and stich it together on the frontend.
+ const alerts = await client.search({
+ index: ['.siem-signals-default'],
+ body: {
+ size: PROCESS_EVENTS_PER_PAGE,
+ sort: [{ '@timestamp': 'asc' }],
+ }
+ });
+
+ alerts.body.hits.hits = alerts.body.hits.hits.map((hit: any) => {
+ hit._source = expandDottedObject(hit._source);
+
+ return hit;
});
- return response.ok({ body: search.body.hits });
+ return response.ok({
+ body: {
+ events: search.body.hits,
+ alerts: alerts.body.hits,
+ },
+ });
}
);
};
diff --git a/x-pack/plugins/session_view/server/routes/recent_session_route.ts b/x-pack/plugins/session_view/server/routes/recent_session_route.ts
index 71a95f3d4d0b9..6eb1b415f9002 100644
--- a/x-pack/plugins/session_view/server/routes/recent_session_route.ts
+++ b/x-pack/plugins/session_view/server/routes/recent_session_route.ts
@@ -25,12 +25,17 @@ export const registerRecentSessionRoute = (router: IRouter) => {
const search = await client.search({
index: indexes,
- query: {
- match: {
- 'process.entry.interactive': true,
+ body: {
+ query: {
+ match: {
+ 'process.entry.interactive': true,
+ },
},
+ size: 1,
+ sort: [
+ {'@timestamp' :'desc'}
+ ],
},
- size: 1
});
return response.ok({ body: search.body.hits });