[Session View] Fixes to slow process event queries + xterm.js updated. (#155326)

## Summary

Issue: https://github.com/elastic/kibana/issues/155183

This PR improves the process_events_route and io_event_route queries. We
now pass in the index and **process.entry_leader.start** time to greatly
improve the loading time of session view. Prior to this change, we were
doing a cross cluster search across the entire
logs-endpoint.events.process datastream. Session view will now use the
index from the entry leader event to limit the scope of the query. It
also ensures a range query is added with the entry leader start time, to
prevent data prior to the session from being hit.

I've also updated the npm package for xterm.js which addresses this
renovate ticket: https://github.com/elastic/kibana/pull/147815

### Checklist

Delete any items that are not applicable to this PR.

- [x] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)
- [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
- [x] Any UI touched in this PR is usable by keyboard only (learn more
about [keyboard accessibility](https://webaim.org/techniques/keyboard/))
- [x] Any UI touched in this PR does not create any new axe failures
(run axe in browser:
[FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/),
[Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US))
- [x] This renders correctly on smaller devices using a responsive
layout. (You can test this [in your
browser](https://www.browserstack.com/guide/responsive-testing-on-local-server))
- [x] This was checked for [cross-browser
compatibility](https://www.elastic.co/support/matrix#matrix_browsers)

---------

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Karl Godard 2023-04-25 09:25:01 -07:00 committed by GitHub
parent e933ad50fe
commit 672e9925b0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
47 changed files with 447 additions and 115 deletions

View file

@ -952,7 +952,7 @@
"whatwg-fetch": "^3.0.0",
"xml2js": "^0.4.22",
"xstate": "^4.35.2",
"xterm": "^5.0.0",
"xterm": "^5.1.0",
"yauzl": "^2.10.0",
"yazl": "^2.5.1"
},

View file

@ -33,6 +33,7 @@ export interface ProcessSessionData {
entity_id?: string[];
pid?: string[];
name?: string[];
start?: string[];
}
export interface ProcessHashData {

View file

@ -26,4 +26,7 @@ export type SignalEcsAAD = Exclude<SignalEcs, 'rule' | 'status'> & {
suppression?: {
docs_count: string[];
};
ancestors?: {
index?: string;
};
};

View file

@ -6,7 +6,9 @@
*/
export interface SessionViewConfig {
processIndex: string;
sessionEntityId: string;
sessionStartTime: string;
jumpToEntityId?: string;
jumpToCursor?: string;
investigatedAlertId?: string;

View file

@ -7,7 +7,6 @@
import { schema } from '@kbn/config-schema';
import { IRouter } from '@kbn/core/server';
import type { ElasticsearchClient } from '@kbn/core/server';
import { PROCESS_EVENTS_INDEX } from '@kbn/session-view-plugin/common/constants';
import { AGENT_ID_ROUTE } from '../../common/constants';
export const registerAgentIdRoute = (router: IRouter) => {
@ -34,10 +33,10 @@ export const registerAgentIdRoute = (router: IRouter) => {
);
};
export const getAgentId = async (client: ElasticsearchClient, query: string, index?: string) => {
export const getAgentId = async (client: ElasticsearchClient, query: string, index: string) => {
const queryDSL = JSON.parse(query);
const search = await client.search({
index: [index || PROCESS_EVENTS_INDEX],
index: [index],
body: {
query: queryDSL,
size: 1,

View file

@ -8,7 +8,6 @@ import type { SortCombinations } from '@elastic/elasticsearch/lib/api/typesWithB
import { schema } from '@kbn/config-schema';
import type { ElasticsearchClient } from '@kbn/core/server';
import { IRouter } from '@kbn/core/server';
import { PROCESS_EVENTS_INDEX } from '@kbn/session-view-plugin/common/constants';
import {
AGGREGATE_ROUTE,
AGGREGATE_PAGE_SIZE,
@ -26,12 +25,12 @@ export const registerAggregateRoute = (router: IRouter) => {
path: AGGREGATE_ROUTE,
validate: {
query: schema.object({
index: schema.string(),
query: schema.string(),
countBy: schema.maybe(schema.string()),
groupBy: schema.string(),
page: schema.number(),
perPage: schema.maybe(schema.number()),
index: schema.maybe(schema.string()),
sortByCount: schema.maybe(schema.string()),
}),
},
@ -43,11 +42,11 @@ export const registerAggregateRoute = (router: IRouter) => {
try {
const body = await doSearch(
client,
index,
query,
groupBy,
page,
perPage,
index,
countBy,
sortByCount
);
@ -62,11 +61,11 @@ export const registerAggregateRoute = (router: IRouter) => {
export const doSearch = async (
client: ElasticsearchClient,
index: string,
query: string,
groupBy: string,
page: number, // zero based
perPage = AGGREGATE_PAGE_SIZE,
index?: string,
countBy?: string,
sortByCount?: string
): Promise<AggregateBucketPaginationResult> => {
@ -88,7 +87,7 @@ export const doSearch = async (
}
const search = await client.search({
index: [index || PROCESS_EVENTS_INDEX],
index: [index],
body: {
query: queryDSL,
size: 0,

View file

@ -7,7 +7,6 @@
import { schema } from '@kbn/config-schema';
import type { ElasticsearchClient } from '@kbn/core/server';
import { IRouter } from '@kbn/core/server';
import { PROCESS_EVENTS_INDEX } from '@kbn/session-view-plugin/common/constants';
import { COUNT_ROUTE } from '../../common/constants';
export const registerCountRoute = (router: IRouter) => {
@ -16,9 +15,9 @@ export const registerCountRoute = (router: IRouter) => {
path: COUNT_ROUTE,
validate: {
query: schema.object({
index: schema.string(),
query: schema.string(),
field: schema.string(),
index: schema.maybe(schema.string()),
}),
},
},
@ -27,7 +26,7 @@ export const registerCountRoute = (router: IRouter) => {
const { query, field, index } = request.query;
try {
const body = await doCount(client, query, field, index);
const body = await doCount(client, index, query, field);
return response.ok({ body });
} catch (err) {
@ -39,14 +38,14 @@ export const registerCountRoute = (router: IRouter) => {
export const doCount = async (
client: ElasticsearchClient,
index: string,
query: string,
field: string,
index?: string
field: string
) => {
const queryDSL = JSON.parse(query);
const search = await client.search({
index: [index || PROCESS_EVENTS_INDEX],
index: [index],
body: {
query: queryDSL,
size: 0,

View file

@ -7,7 +7,6 @@
import { schema } from '@kbn/config-schema';
import type { ElasticsearchClient } from '@kbn/core/server';
import { IRouter } from '@kbn/core/server';
import { PROCESS_EVENTS_INDEX } from '@kbn/session-view-plugin/common/constants';
import { MULTI_TERMS_AGGREGATE_ROUTE, AGGREGATE_PAGE_SIZE } from '../../common/constants';
import {
MultiTermsAggregateGroupBy,
@ -20,6 +19,7 @@ export const registerMultiTermsAggregateRoute = (router: IRouter) => {
path: MULTI_TERMS_AGGREGATE_ROUTE,
validate: {
query: schema.object({
index: schema.string(),
query: schema.string(),
countBy: schema.maybe(schema.string()),
groupBys: schema.arrayOf(
@ -31,7 +31,6 @@ export const registerMultiTermsAggregateRoute = (router: IRouter) => {
),
page: schema.number(),
perPage: schema.maybe(schema.number()),
index: schema.maybe(schema.string()),
}),
},
},
@ -40,7 +39,7 @@ export const registerMultiTermsAggregateRoute = (router: IRouter) => {
const { query, countBy, groupBys, page, perPage, index } = request.query;
try {
const body = await doSearch(client, query, groupBys, page, perPage, index, countBy);
const body = await doSearch(client, index, query, groupBys, page, perPage, countBy);
return response.ok({ body });
} catch (err) {
@ -52,11 +51,11 @@ export const registerMultiTermsAggregateRoute = (router: IRouter) => {
export const doSearch = async (
client: ElasticsearchClient,
index: string,
query: string,
groupBys: MultiTermsAggregateGroupBy[],
page: number, // zero based
perPage = AGGREGATE_PAGE_SIZE,
index?: string,
countBy?: string
): Promise<MultiTermsAggregateBucketPaginationResult> => {
const queryDSL = JSON.parse(query);
@ -72,7 +71,7 @@ export const doSearch = async (
: undefined;
const search = await client.search({
index: [index || PROCESS_EVENTS_INDEX],
index: [index],
body: {
query: queryDSL,
size: 0,

View file

@ -6,7 +6,9 @@
*/
export interface SessionViewConfig {
processIndex: string;
sessionEntityId: string;
sessionStartTime: string;
jumpToEntityId?: string;
jumpToCursor?: string;
investigatedAlertId?: string;

View file

@ -383,7 +383,8 @@ describe('Actions', () => {
...mockTimelineData[0].ecs,
event: { kind: ['alert'] },
agent: { type: ['endpoint'] },
process: { entry_leader: { entity_id: ['test_id'] } },
process: { entry_leader: { entity_id: ['test_id'], start: ['2022-05-08T13:44:00.13Z'] } },
_index: '.ds-logs-endpoint.events.process-default',
};
const wrapper = mount(
@ -400,7 +401,8 @@ describe('Actions', () => {
...mockTimelineData[0].ecs,
event: { kind: ['alert'] },
agent: { type: ['endpoint'] },
process: { entry_leader: { entity_id: ['test_id'] } },
process: { entry_leader: { entity_id: ['test_id'], start: ['2022-05-08T13:44:00.13Z'] } },
_index: '.ds-logs-endpoint.events.process-default',
};
const wrapper = mount(
@ -425,7 +427,8 @@ describe('Actions', () => {
...mockTimelineData[0].ecs,
event: { kind: ['alert'] },
agent: { type: ['endpoint'] },
process: { entry_leader: { entity_id: ['test_id'] } },
process: { entry_leader: { entity_id: ['test_id'], start: ['2022-05-08T13:44:00.13Z'] } },
_index: '.ds-logs-endpoint.events.process-default',
};
const wrapper = mount(

View file

@ -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 } from './helpers';
import { DEFAULT_ACTION_BUTTON_WIDTH, isAlert, getSessionViewProcessIndex } from './helpers';
const ActionsContainer = styled.div`
align-items: center;
@ -149,10 +149,16 @@ const ActionsComponent: React.FC<ActionProps> = ({
]);
const sessionViewConfig = useMemo(() => {
const { process, _id, timestamp } = ecsData;
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);
if (sessionEntityId === undefined) {
if (
processIndex === undefined ||
sessionEntityId === undefined ||
sessionStartTime === undefined
) {
return null;
}
@ -162,7 +168,9 @@ const ActionsComponent: React.FC<ActionProps> = ({
(investigatedAlertId && ecsData.kibana?.alert.original_time?.[0]) || timestamp;
return {
processIndex,
sessionEntityId,
sessionStartTime,
jumpToEntityId,
jumpToCursor,
investigatedAlertId,

View file

@ -6,7 +6,12 @@
*/
import { euiThemeVars } from '@kbn/ui-theme';
import { DEFAULT_ACTION_BUTTON_WIDTH, getActionsColumnWidth, isAlert } from './helpers';
import {
DEFAULT_ACTION_BUTTON_WIDTH,
getActionsColumnWidth,
isAlert,
getSessionViewProcessIndex,
} from './helpers';
describe('isAlert', () => {
test('it returns true when the eventType is an alert', () => {
@ -48,3 +53,67 @@ 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*');
});
});

View file

@ -50,3 +50,23 @@ 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*`;
}
};

View file

@ -12,7 +12,26 @@ import type { LeftPanelContext } from '../context';
import { LeftFlyoutContext } from '../context';
import { TestProviders } from '../../../common/mock';
import { SESSION_VIEW_ERROR_TEST_ID, SESSION_VIEW_TEST_ID } from './test_ids';
import { SessionView } from './session_view';
import {
SessionView,
SESSION_ENTITY_ID,
SESSION_START_TIME,
KIBANA_ANCESTOR_INDEX,
} from './session_view';
interface MockData {
[key: string]: string;
}
const mockData: MockData = {
[SESSION_ENTITY_ID]: 'id',
[SESSION_START_TIME]: '2023-04-25T04:33:23.676Z',
[KIBANA_ANCESTOR_INDEX]: '.ds-logs-endpoint.events.process-default',
};
const mockFieldsData = (prop: string) => {
return mockData[prop];
};
jest.mock('../../../common/lib/kibana', () => {
const originalModule = jest.requireActual('../../../common/lib/kibana');
@ -31,7 +50,24 @@ jest.mock('../../../common/lib/kibana', () => {
describe('<SessionView />', () => {
it('renders session view correctly', () => {
const contextValue = {
getFieldsData: () => 'id',
getFieldsData: mockFieldsData,
indexName: '.ds-logs-endpoint.events.process-default',
} as unknown as LeftPanelContext;
const wrapper = render(
<TestProviders>
<LeftFlyoutContext.Provider value={contextValue}>
<SessionView />
</LeftFlyoutContext.Provider>
</TestProviders>
);
expect(wrapper.getByTestId(SESSION_VIEW_TEST_ID)).toBeInTheDocument();
});
it('renders session view from an alert correctly', () => {
const contextValue = {
getFieldsData: mockFieldsData,
indexName: '.alerts-security', // it should prioritize KIBANA_ANCESTOR_INDEX above indexName
} as unknown as LeftPanelContext;
const wrapper = render(

View file

@ -14,20 +14,27 @@ 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';
const SESSION_ENTITY_ID = 'process.entry_leader.entity_id';
export const SESSION_ENTITY_ID = 'process.entry_leader.entity_id';
export const SESSION_START_TIME = 'process.entry_leader.start';
export const KIBANA_ANCESTOR_INDEX = 'kibana.alert.ancestors.index';
/**
* Session view displayed in the document details expandable flyout left section under the Visualize tab
*/
export const SessionView: FC = () => {
const { sessionView } = useKibana().services;
const { getFieldsData } = useLeftPanelContext();
const { getFieldsData, indexName } = useLeftPanelContext();
const processIndex = getSessionViewProcessIndex(
getField(getFieldsData(KIBANA_ANCESTOR_INDEX)) || indexName
);
const sessionEntityId = getField(getFieldsData(SESSION_ENTITY_ID));
const sessionStartTime = getField(getFieldsData(SESSION_START_TIME));
if (!sessionEntityId) {
if (!processIndex || !sessionEntityId || !sessionStartTime) {
return (
<EuiEmptyPrompt
iconType="error"
@ -42,7 +49,9 @@ export const SessionView: FC = () => {
return (
<div data-test-subj={SESSION_VIEW_TEST_ID}>
{sessionView.getSessionView({
processIndex,
sessionEntityId,
sessionStartTime,
isFullScreen: true,
})}
</div>

View file

@ -253,7 +253,9 @@ describe('GraphOverlay', () => {
[timelineId]: {
...mockGlobalState.timeline.timelineById[timelineId],
sessionViewConfig: {
processIndex: 'logs-endpoint.events.process*',
sessionEntityId: 'testId',
sessionStartTime: '2021-10-14T08:05:34.853Z',
},
},
},

View file

@ -18,10 +18,6 @@ export const SECURITY_APP_ID = 'security';
export const POLICIES_PAGE_PATH = '/administration/policy';
// index patterns
const ENDPOINT_PROCESS_EVENTS_INDEX =
'*:logs-endpoint.events.process*,logs-endpoint.events.process*';
const CLOUD_DEFEND_PROCESS_EVENTS_INDEX = '*:logs-cloud_defend.process*,logs-cloud_defend.process*';
export const PROCESS_EVENTS_INDEX = `${ENDPOINT_PROCESS_EVENTS_INDEX},${CLOUD_DEFEND_PROCESS_EVENTS_INDEX}`; // match on both cross cluster and local indices
export const PREVIEW_ALERTS_INDEX = '.preview.alerts-security.alerts-default';
// field properties

View file

@ -17,6 +17,9 @@ import {
ProcessEventAlertCategory,
} from '../../types/process_tree';
export const TEST_PROCESS_INDEX = 'logs-endpoint.events.process*';
export const TEST_SESSION_START_TIME = '2021-10-14T08:05:34.853Z';
export const mockEvents: ProcessEvent[] = [
{
'@timestamp': '2021-11-23T15:25:04.210Z',

View file

@ -193,6 +193,7 @@ export interface ProcessEvent {
kind?: EventKind;
category?: string | string[];
action?: EventAction | EventAction[];
type?: string | string[];
id?: string;
};
file?: {
@ -289,6 +290,7 @@ export interface ProcessEventCloud {
};
project?: {
id?: string;
name?: string;
};
provider?: string;
region?: string;

View file

@ -132,6 +132,7 @@ export const getCloudData = (cloud: ProcessEventCloud | undefined): DetailPanelC
},
project: {
id: DASH,
name: DASH,
},
provider: DASH,
region: DASH,
@ -144,6 +145,7 @@ export const getCloudData = (cloud: ProcessEventCloud | undefined): DetailPanelC
detailPanelCloud.instance.name = dataOrDash(cloud?.instance?.name).toString();
detailPanelCloud.account.id = dataOrDash(cloud?.account?.id).toString();
detailPanelCloud.project.id = dataOrDash(cloud?.project?.id).toString();
detailPanelCloud.project.name = dataOrDash(cloud?.project?.name).toString();
detailPanelCloud.provider = dataOrDash(cloud?.provider).toString();
detailPanelCloud.region = dataOrDash(cloud?.region).toString();

View file

@ -136,7 +136,7 @@ describe('DetailPanelMetadataTab component', () => {
expect(renderResult.queryByText(TEST_NAME)).toBeVisible();
// expand host os accordion
renderResult.queryByText('Host OS')?.click();
renderResult.queryByText('OS')?.click();
expect(renderResult.queryByText('architecture')).toBeVisible();
expect(renderResult.queryByText('os.family')).toBeVisible();
expect(renderResult.queryByText('os.full')).toBeVisible();
@ -182,7 +182,7 @@ describe('DetailPanelMetadataTab component', () => {
expect(renderResult.queryAllByText('name').length).toBe(2);
// expand host os accordion
renderResult.queryByText('Host OS')?.click();
renderResult.queryByText('OS')?.click();
expect(renderResult.queryByText('architecture')).toBeVisible();
expect(renderResult.queryByText('os.family')).toBeVisible();
expect(renderResult.queryByText('os.full')).toBeVisible();

View file

@ -50,24 +50,11 @@ export const DetailPanelMetadataTab = ({
<>
<DetailPanelAccordion
id="metadataHost"
title={i18n.translate('xpack.sessionView.metadataDetailsTab.metadata', {
defaultMessage: 'Metadata',
title={i18n.translate('xpack.sessionView.metadataDetailsTab.metadataHost', {
defaultMessage: 'Host',
})}
initialIsOpen={true}
listItems={[
{
title: <DetailPanelListItem>hostname</DetailPanelListItem>,
description: (
<DetailPanelCopy
textToCopy={`host.hostname: "${hostData.hostname}"`}
tooltipContent={hostData.hostname}
>
<EuiTextColor color="subdued" css={styles.descriptionSemibold}>
{hostData.hostname}
</EuiTextColor>
</DetailPanelCopy>
),
},
{
title: <DetailPanelListItem>id</DetailPanelListItem>,
description: (
@ -81,6 +68,19 @@ export const DetailPanelMetadataTab = ({
</DetailPanelCopy>
),
},
{
title: <DetailPanelListItem>hostname</DetailPanelListItem>,
description: (
<DetailPanelCopy
textToCopy={`host.hostname: "${hostData.hostname}"`}
tooltipContent={hostData.hostname}
>
<EuiTextColor color="subdued" css={styles.descriptionSemibold}>
{hostData.hostname}
</EuiTextColor>
</DetailPanelCopy>
),
},
{
title: <DetailPanelListItem>ip</DetailPanelListItem>,
description: (
@ -133,7 +133,7 @@ export const DetailPanelMetadataTab = ({
<DetailPanelAccordion
id="hostOS"
title={i18n.translate('xpack.sessionView.metadataDetailsTab.host', {
defaultMessage: 'Host OS',
defaultMessage: 'OS',
})}
listItems={[
{
@ -305,6 +305,19 @@ export const DetailPanelMetadataTab = ({
</DetailPanelCopy>
),
},
{
title: <DetailPanelListItem>project.name</DetailPanelListItem>,
description: (
<DetailPanelCopy
textToCopy={`cloud.project.name: "${cloudData.project.name}"`}
tooltipContent={cloudData.project.name}
>
<EuiTextColor color="subdued" css={styles.descriptionSemibold}>
{cloudData.project.name}
</EuiTextColor>
</DetailPanelCopy>
),
},
]}
/>
</>

View file

@ -453,17 +453,6 @@ export const DetailPanelProcessTab = ({ selectedProcess }: DetailPanelProcessTab
</DetailPanelCopy>
),
},
{
title: <DetailPanelListItem>user.id</DetailPanelListItem>,
description: (
<DetailPanelCopy
textToCopy={`${PROCESS_FIELD_PREFIX}.user.id: "${userId}"`}
tooltipContent={userId}
>
{userId}
</DetailPanelCopy>
),
},
{
title: <DetailPanelListItem>user.name</DetailPanelListItem>,
description: (

View file

@ -95,6 +95,9 @@ Object {
</span>
</span>
</div>
<div
class="euiFlexItem emotion-euiFlexItem-growZero"
/>
</div>
</div>
</div>
@ -190,6 +193,9 @@ Object {
</span>
</span>
</div>
<div
class="euiFlexItem emotion-euiFlexItem-growZero"
/>
</div>
</div>
</div>,

View file

@ -15,6 +15,7 @@ import {
EuiToolTip,
EuiPanel,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import { ALERT_ICONS } from '../../../common/constants';
import {
ProcessEvent,
@ -78,6 +79,7 @@ export const ProcessTreeAlert = ({
const processEventAlertCategory = category ?? ProcessEventAlertCategory.process;
const alertCategoryDetailDisplayText = getAlertCategoryDisplayText(alert, category);
const alertIconTooltipContent = getAlertIconTooltipContent(processEventAlertCategory);
const eventType = Array.isArray(event?.type) ? event?.type?.[0] : event?.type;
return (
<div key={uuid} css={styles.alert} data-id={uuid}>
@ -137,6 +139,13 @@ export const ProcessTreeAlert = ({
<EuiFlexItem grow={false}>
<EuiBadge css={styles.actionBadge}>{event?.action}</EuiBadge>
</EuiFlexItem>
<EuiFlexItem grow={false}>
{eventType === 'denied' && (
<EuiBadge css={styles.actionBadge} color="danger">
<FormattedMessage id="xpack.sessionView.blockedBadge" defaultMessage="Blocked" />
</EuiBadge>
)}
</EuiFlexItem>
</EuiFlexGroup>
</div>
);

View file

@ -44,6 +44,10 @@ export const EXEC_USER_CHANGE = i18n.translate('xpack.sessionView.execUserChange
defaultMessage: 'Exec user change',
});
export const COLLAPSE_ALL = i18n.translate('xpack.sessionView.collapseAll', {
defaultMessage: 'Collapse all',
});
export interface ProcessDeps {
process: Process;
isSessionLeader?: boolean;
@ -263,7 +267,7 @@ export function ProcessTreeNode({
const shouldRenderChildren = isSessionLeader || (childrenExpanded && children?.length > 0);
const childrenTreeDepth = depth + 1;
const showUserEscalation = !isSessionLeader && !!user?.name && user.name !== parent?.user?.name;
const showUserEscalation = !isSessionLeader && !!user?.id && user.id !== parent?.user?.id;
const interactiveSession = !!tty;
const sessionIcon = interactiveSession ? 'desktop' : 'gear';
const iconTestSubj = hasExec
@ -303,12 +307,11 @@ export function ProcessTreeNode({
<Nbsp />
<b css={styles.darkText}>{userName}</b>
<Nbsp />
<EuiButtonIcon
size="xs"
iconType="fold"
onClick={handleCollapseProcessTree}
css={styles.jumpToTop}
/>
<span css={styles.jumpToTop}>
<EuiToolTip title={COLLAPSE_ALL}>
<EuiButtonIcon size="xs" iconType="fold" onClick={handleCollapseProcessTree} />
</EuiToolTip>
</span>
</span>
) : (
<>
@ -344,7 +347,7 @@ export function ProcessTreeNode({
css={buttonStyles.userChangedButton}
aria-label={EXEC_USER_CHANGE}
>
{EXEC_USER_CHANGE} :<span>{user.name}</span>
{EXEC_USER_CHANGE} ({userName})
</EuiButton>
)}
{!isSessionLeader && children.length > 0 && (

View file

@ -28,14 +28,16 @@ import {
} from '../../../common/constants';
export const useFetchSessionViewProcessEvents = (
index: string,
sessionEntityId: string,
sessionStartTime: string,
jumpToCursor?: string
) => {
const { http } = useKibana<CoreStart>().services;
const [currentJumpToCursor, setCurrentJumpToCursor] = useState<string>('');
const cachingKeys = useMemo(
() => [QUERY_KEY_PROCESS_EVENTS, sessionEntityId, jumpToCursor],
[sessionEntityId, jumpToCursor]
() => [QUERY_KEY_PROCESS_EVENTS, index, sessionEntityId, jumpToCursor],
[index, sessionEntityId, jumpToCursor]
);
const query = useInfiniteQuery(
@ -50,7 +52,9 @@ export const useFetchSessionViewProcessEvents = (
const res = await http.get<ProcessEventResults>(PROCESS_EVENTS_ROUTE, {
query: {
index,
sessionEntityId,
sessionStartTime,
cursor,
forward,
},
@ -126,6 +130,7 @@ export const useFetchSessionViewProcessEvents = (
export const useFetchSessionViewAlerts = (
sessionEntityId: string,
sessionStartTime: string,
investigatedAlertId?: string
) => {
const { http } = useKibana<CoreStart>().services;
@ -139,6 +144,7 @@ export const useFetchSessionViewAlerts = (
const res = await http.get<ProcessEventResults>(ALERTS_ROUTE, {
query: {
sessionEntityId,
sessionStartTime,
investigatedAlertId,
cursor,
},
@ -210,15 +216,21 @@ export const useFetchAlertStatus = (
return query;
};
export const useFetchGetTotalIOBytes = (sessionEntityId: string) => {
export const useFetchGetTotalIOBytes = (
index: string,
sessionEntityId: string,
sessionStartTime: string
) => {
const { http } = useKibana<CoreStart>().services;
const cachingKeys = [QUERY_KEY_GET_TOTAL_IO_BYTES, sessionEntityId];
const cachingKeys = [QUERY_KEY_GET_TOTAL_IO_BYTES, index, sessionEntityId];
const query = useQuery<{ total: number }, Error>(
cachingKeys,
async () => {
return http.get<{ total: number }>(GET_TOTAL_IO_BYTES_ROUTE, {
query: {
index,
sessionEntityId,
sessionStartTime,
},
});
},

View file

@ -7,6 +7,10 @@
import { waitFor, waitForElementToBeRemoved } from '@testing-library/react';
import React from 'react';
import {
TEST_PROCESS_INDEX,
TEST_SESSION_START_TIME,
} from '../../../common/mocks/constants/session_view_process.mock';
import { sessionViewProcessEventsMock } from '../../../common/mocks/responses/session_view_process_events.mock';
import { sessionViewProcessEventsMergedMock } from '../../../common/mocks/responses/session_view_process_events_merged.mock';
import { AppContextTestRender, createAppRootMockRenderer } from '../../test';
@ -48,7 +52,13 @@ describe('SessionView component', () => {
mockedContext = createAppRootMockRenderer();
mockedApi = mockedContext.coreStart.http.get;
render = () =>
(renderResult = mockedContext.render(<SessionView sessionEntityId="test-entity-id" />));
(renderResult = mockedContext.render(
<SessionView
processIndex={TEST_PROCESS_INDEX}
sessionStartTime={TEST_SESSION_START_TIME}
sessionEntityId="test-entity-id"
/>
));
mockUseDateFormat.mockImplementation(() => 'MMM D, YYYY @ HH:mm:ss.SSS');
});

View file

@ -46,7 +46,9 @@ import { REFRESH_SESSION, TOGGLE_TTY_PLAYER, DETAIL_PANEL } from './translations
* The main wrapper component for the session view.
*/
export const SessionView = ({
processIndex,
sessionEntityId,
sessionStartTime,
height,
isFullScreen = false,
jumpToEntityId,
@ -129,7 +131,12 @@ export const SessionView = ({
fetchPreviousPage,
hasPreviousPage,
refetch,
} = useFetchSessionViewProcessEvents(sessionEntityId, currentJumpToCursor);
} = useFetchSessionViewProcessEvents(
processIndex,
sessionEntityId,
sessionStartTime,
currentJumpToCursor
);
const {
data: alertsData,
@ -138,10 +145,13 @@ export const SessionView = ({
hasNextPage: hasNextPageAlerts,
error: alertsError,
refetch: refetchAlerts,
} = useFetchSessionViewAlerts(sessionEntityId, investigatedAlertId);
} = useFetchSessionViewAlerts(sessionEntityId, sessionStartTime, investigatedAlertId);
const { data: totalTTYOutputBytes, refetch: refetchTotalTTYOutput } =
useFetchGetTotalIOBytes(sessionEntityId);
const { data: totalTTYOutputBytes, refetch: refetchTotalTTYOutput } = useFetchGetTotalIOBytes(
processIndex,
sessionEntityId,
sessionStartTime
);
const hasTTYOutput = !!totalTTYOutputBytes?.total;
const bytesOfOutput = useMemo(() => {
const { unit, value } = byteSize(totalTTYOutputBytes?.total || 0);
@ -421,8 +431,10 @@ export const SessionView = ({
}}
</EuiResizableContainer>
<TTYPlayer
index={processIndex}
show={showTTY}
sessionEntityId={sessionEntityId}
sessionStartTime={sessionStartTime}
onClose={onToggleTTY}
isFullscreen={isFullScreen}
onJumpToEvent={onJumpToEvent}

View file

@ -33,7 +33,11 @@ import {
TTY_LINES_PRE_SEEK,
} from '../../../common/constants';
export const useFetchIOEvents = (sessionEntityId: string) => {
export const useFetchIOEvents = (
index: string,
sessionEntityId: string,
sessionStartTime: string
) => {
const { http } = useKibana<CoreStart>().services;
const cachingKeys = useMemo(() => [QUERY_KEY_IO_EVENTS, sessionEntityId], [sessionEntityId]);
@ -43,7 +47,9 @@ export const useFetchIOEvents = (sessionEntityId: string) => {
const { cursor } = pageParam;
const res = await http.get<ProcessEventResults>(IO_EVENTS_ROUTE, {
query: {
index,
sessionEntityId,
sessionStartTime,
cursor,
},
});

View file

@ -7,6 +7,10 @@
import React from 'react';
import { waitFor, act } from '@testing-library/react';
import {
TEST_PROCESS_INDEX,
TEST_SESSION_START_TIME,
} from '../../../common/mocks/constants/session_view_process.mock';
import { sessionViewIOEventsMock } from '../../../common/mocks/responses/session_view_io_events.mock';
import { AppContextTestRender, createAppRootMockRenderer } from '../../test';
import { TTYPlayerDeps, TTYPlayer } from '.';
@ -51,7 +55,9 @@ describe('TTYPlayer component', () => {
props = {
show: true,
index: TEST_PROCESS_INDEX,
sessionEntityId: mockSessionEntityId,
sessionStartTime: TEST_SESSION_START_TIME,
onClose: jest.fn(),
onJumpToEvent: jest.fn(),
isFullscreen: false,

View file

@ -33,8 +33,10 @@ import { TTYPlayerControls } from '../tty_player_controls';
import { BETA, TOGGLE_TTY_PLAYER, DETAIL_PANEL } from '../session_view/translations';
export interface TTYPlayerDeps {
show: boolean;
index: string;
sessionEntityId: string;
sessionStartTime: string;
show: boolean;
onClose(): void;
isFullscreen: boolean;
onJumpToEvent(event: ProcessEvent): void;
@ -43,8 +45,10 @@ export interface TTYPlayerDeps {
}
export const TTYPlayer = ({
show,
index,
sessionEntityId,
sessionStartTime,
show,
onClose,
isFullscreen,
onJumpToEvent,
@ -54,8 +58,11 @@ export const TTYPlayer = ({
const ref = useRef<HTMLDivElement>(null);
const { ref: scrollRef, height: containerHeight = 1 } = useResizeObserver<HTMLDivElement>({});
const { data, fetchNextPage, hasNextPage, isFetching, refetch } =
useFetchIOEvents(sessionEntityId);
const { data, fetchNextPage, hasNextPage, isFetching, refetch } = useFetchIOEvents(
index,
sessionEntityId,
sessionStartTime
);
const { lines, processStartMarkers } = useIOLines(data?.pages);
const [fontSize, setFontSize] = useState(DEFAULT_TTY_FONT_SIZE);
const [isPlaying, setIsPlaying] = useState(false);
@ -88,7 +95,7 @@ export const TTYPlayer = ({
useEffect(() => {
if (show) {
// refetch the most recent page when tty player is loaded
refetch({ refetchPage: (_page, index, allPages) => allPages.length - 1 === index });
refetch({ refetchPage: (_page, i, allPages) => allPages.length - 1 === i });
}
}, [refetch, show]);

View file

@ -10,8 +10,15 @@ import { CoreStart } from '@kbn/core/public';
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;
// the root node of the process tree to render. e.g process.entry.entity_id or process.session_leader.entity_id
sessionEntityId: string;
// start time is passed in order to scope session_view queries to the appropriate time range, and avoid querying data across all time.
sessionStartTime: string;
height?: number;
isFullScreen?: boolean;
// if provided, the session view will jump to and select the provided event if it belongs to the session leader
@ -132,6 +139,7 @@ export interface DetailPanelCloud {
};
project: {
id: string;
name: string;
};
provider: string;
region: string;

View file

@ -31,15 +31,15 @@ export const registerAlertsRoute = (
validate: {
query: schema.object({
sessionEntityId: schema.string(),
sessionStartTime: schema.string(),
investigatedAlertId: schema.maybe(schema.string()),
cursor: schema.maybe(schema.string()),
range: schema.maybe(schema.arrayOf(schema.string())),
}),
},
},
async (_context, request, response) => {
const client = await ruleRegistry.getRacClientWithRequest(request);
const { sessionEntityId, investigatedAlertId, range, cursor } = request.query;
const { sessionEntityId, sessionStartTime, investigatedAlertId, cursor } = request.query;
try {
const body = await searchAlerts(
@ -47,7 +47,7 @@ export const registerAlertsRoute = (
sessionEntityId,
ALERTS_PER_PAGE,
investigatedAlertId,
range,
[sessionStartTime],
cursor
);

View file

@ -7,9 +7,9 @@ import { IRouter } from '@kbn/core/server';
import { EVENT_ACTION } from '@kbn/rule-data-utils';
import {
GET_TOTAL_IO_BYTES_ROUTE,
PROCESS_EVENTS_INDEX,
TOTAL_BYTES_CAPTURED_PROPERTY,
ENTRY_SESSION_ENTITY_ID_PROPERTY,
TIMESTAMP_PROPERTY,
} from '../../common/constants';
export const registerGetTotalIOBytesRoute = (router: IRouter) => {
@ -18,23 +18,33 @@ export const registerGetTotalIOBytesRoute = (router: IRouter) => {
path: GET_TOTAL_IO_BYTES_ROUTE,
validate: {
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 { sessionEntityId } = request.query;
const { index, sessionEntityId, sessionStartTime } = request.query;
try {
const search = await client.search({
index: [PROCESS_EVENTS_INDEX],
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,
},
},
},
],
},
},

View file

@ -8,6 +8,8 @@ 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*';
const getEmptyResponse = async () => {
return {
aggregations: {
@ -45,7 +47,7 @@ describe('io_events_route.ts', () => {
describe('searchProcessWithIOEvents(client, sessionEntityId, range)', () => {
it('should return an empty events array for a non existant entity_id', async () => {
const esClient = elasticsearchServiceMock.createElasticsearchClient(getEmptyResponse());
const body = await searchProcessWithIOEvents(esClient, 'asdf');
const body = await searchProcessWithIOEvents(esClient, TEST_PROCESS_INDEX, 'asdf');
expect(body.length).toBe(0);
});
@ -53,7 +55,7 @@ describe('io_events_route.ts', () => {
it('returns results for a particular session entity_id', async () => {
const esClient = elasticsearchServiceMock.createElasticsearchClient(getResponse());
const body = await searchProcessWithIOEvents(esClient, 'mockId');
const body = await searchProcessWithIOEvents(esClient, TEST_PROCESS_INDEX, 'mockId');
expect(body.length).toBe(1);
@ -69,7 +71,10 @@ describe('io_events_route.ts', () => {
const start = '2021-11-23T15:25:04.210Z';
const end = '2021-20-23T15:25:04.210Z';
const body = await searchProcessWithIOEvents(esClient, 'mockId', [start, end]);
const body = await searchProcessWithIOEvents(esClient, TEST_PROCESS_INDEX, 'mockId', [
start,
end,
]);
expect(body.length).toBe(1);
});

View file

@ -13,8 +13,8 @@ import { EventAction, EventKind } from '../../common/types/process_tree';
import {
IO_EVENTS_ROUTE,
IO_EVENTS_PER_PAGE,
PROCESS_EVENTS_INDEX,
ENTRY_SESSION_ENTITY_ID_PROPERTY,
TIMESTAMP_PROPERTY,
PROCESS_ENTITY_ID_PROPERTY,
PROCESS_EVENTS_PER_PAGE,
} from '../../common/constants';
@ -25,7 +25,9 @@ export const registerIOEventsRoute = (router: IRouter) => {
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()),
}),
@ -33,17 +35,31 @@ export const registerIOEventsRoute = (router: IRouter) => {
},
async (context, request, response) => {
const client = (await context.core).elasticsearch.client.asCurrentUser;
const { sessionEntityId, cursor, pageSize = IO_EVENTS_PER_PAGE } = request.query;
const {
index,
sessionEntityId,
sessionStartTime,
cursor,
pageSize = IO_EVENTS_PER_PAGE,
} = request.query;
try {
const search = await client.search({
index: [PROCESS_EVENTS_INDEX],
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,
},
},
},
],
},
},
@ -72,6 +88,7 @@ export const registerIOEventsRoute = (router: IRouter) => {
export const searchProcessWithIOEvents = async (
client: ElasticsearchClient,
index: string,
sessionEntityId: string,
range?: string[]
) => {
@ -90,7 +107,7 @@ export const searchProcessWithIOEvents = async (
try {
const search = await client.search({
index: [PROCESS_EVENTS_INDEX],
index: [index],
body: {
query: {
bool: {

View file

@ -6,7 +6,12 @@
*/
import { elasticsearchServiceMock } from '@kbn/core/server/mocks';
import { fetchEventsAndScopedAlerts } from './process_events_route';
import { mockEvents, mockAlerts } from '../../common/mocks/constants/session_view_process.mock';
import {
TEST_PROCESS_INDEX,
TEST_SESSION_START_TIME,
mockEvents,
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';
@ -42,7 +47,14 @@ describe('process_events_route.ts', () => {
const client = elasticsearchServiceMock.createElasticsearchClient(getEmptyResponse());
const alertsClient = getAlertsClientMockInstance(client);
const body = await fetchEventsAndScopedAlerts(client, alertsClient, 'asdf', undefined);
const body = await fetchEventsAndScopedAlerts(
client,
alertsClient,
TEST_PROCESS_INDEX,
'asdf',
'',
undefined
);
expect(body.events.length).toBe(0);
expect(body.total).toBe(0);
@ -52,7 +64,14 @@ describe('process_events_route.ts', () => {
const client = elasticsearchServiceMock.createElasticsearchClient(getResponse());
const alertsClient = getAlertsClientMockInstance();
const body = await fetchEventsAndScopedAlerts(client, alertsClient, 'mockId', undefined);
const body = await fetchEventsAndScopedAlerts(
client,
alertsClient,
TEST_PROCESS_INDEX,
'mockId',
TEST_SESSION_START_TIME,
undefined
);
expect(body.events.length).toBe(mockEvents.length + mockAlerts.length);
@ -74,7 +93,9 @@ describe('process_events_route.ts', () => {
const body = await fetchEventsAndScopedAlerts(
client,
alertsClient,
TEST_PROCESS_INDEX,
'mockId',
TEST_SESSION_START_TIME,
undefined,
false
);

View file

@ -17,8 +17,8 @@ import {
ALERTS_PER_PROCESS_EVENTS_PAGE,
PROCESS_EVENTS_ROUTE,
PROCESS_EVENTS_PER_PAGE,
PROCESS_EVENTS_INDEX,
ENTRY_SESSION_ENTITY_ID_PROPERTY,
TIMESTAMP_PROPERTY,
} from '../../common/constants';
import { ProcessEvent } from '../../common/types/process_tree';
import { searchAlerts } from './alerts_route';
@ -33,7 +33,9 @@ export const registerProcessEventsRoute = (
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()),
@ -43,13 +45,15 @@ export const registerProcessEventsRoute = (
async (context, request, response) => {
const client = (await context.core).elasticsearch.client.asCurrentUser;
const alertsClient = await ruleRegistry.getRacClientWithRequest(request);
const { sessionEntityId, cursor, forward, pageSize } = request.query;
const { index, sessionEntityId, sessionStartTime, cursor, forward, pageSize } = request.query;
try {
const body = await fetchEventsAndScopedAlerts(
client,
alertsClient,
index,
sessionEntityId,
sessionStartTime,
cursor,
forward,
pageSize
@ -71,7 +75,9 @@ export const registerProcessEventsRoute = (
export const fetchEventsAndScopedAlerts = async (
client: ElasticsearchClient,
alertsClient: AlertsClient,
index: string,
sessionEntityId: string,
sessionStartTime: string,
cursor?: string,
forward = true,
pageSize = PROCESS_EVENTS_PER_PAGE
@ -79,7 +85,7 @@ export const fetchEventsAndScopedAlerts = async (
const cursorMillis = cursor && new Date(cursor).getTime() + (forward ? -1 : 1);
const search = await client.search({
index: [PROCESS_EVENTS_INDEX],
index: [index],
body: {
query: {
bool: {
@ -94,6 +100,14 @@ export const fetchEventsAndScopedAlerts = async (
],
},
},
{
range: {
// optimization to prevent data before this session from being hit.
[TIMESTAMP_PROPERTY]: {
gte: sessionStartTime,
},
},
},
],
},
},
@ -131,7 +145,12 @@ export const fetchEventsAndScopedAlerts = async (
range
);
const processesWithIOEvents = await searchProcessWithIOEvents(client, sessionEntityId, range);
const processesWithIOEvents = await searchProcessWithIOEvents(
client,
index,
sessionEntityId,
range
);
events = [...events, ...alertsBody.events, ...processesWithIOEvents];
}

View file

@ -49,6 +49,7 @@ export const CTI_ROW_RENDERER_FIELDS = [
export const TIMELINE_EVENTS_FIELDS = [
ALERT_RULE_CONSUMER,
'@timestamp',
'kibana.alert.ancestors.index',
'kibana.alert.workflow_status',
'kibana.alert.group.id',
'kibana.alert.original_time',
@ -230,6 +231,7 @@ export const TIMELINE_EVENTS_FIELDS = [
'process.entry_leader.entity_id',
'process.entry_leader.name',
'process.entry_leader.pid',
'process.entry_leader.start',
'process.session_leader.entity_id',
'process.session_leader.name',
'process.session_leader.pid',

View file

@ -32288,7 +32288,6 @@
"xpack.sessionView.metadataDetailsTab.cloud": "Cloud",
"xpack.sessionView.metadataDetailsTab.container": "Conteneur",
"xpack.sessionView.metadataDetailsTab.host": "Système d'exploitation de l'hôte",
"xpack.sessionView.metadataDetailsTab.metadata": "Métadonnées",
"xpack.sessionView.metadataDetailsTab.orchestrator": "Orchestrateur",
"xpack.sessionView.networkTooltip": "Alerte réseau",
"xpack.sessionView.output": "Sortie",

View file

@ -32267,7 +32267,6 @@
"xpack.sessionView.metadataDetailsTab.cloud": "クラウド",
"xpack.sessionView.metadataDetailsTab.container": "コンテナー",
"xpack.sessionView.metadataDetailsTab.host": "ホストOS",
"xpack.sessionView.metadataDetailsTab.metadata": "メタデータ",
"xpack.sessionView.metadataDetailsTab.orchestrator": "オーケストレーター",
"xpack.sessionView.networkTooltip": "ネットワークアラート",
"xpack.sessionView.output": "アウトプット",

View file

@ -32284,7 +32284,6 @@
"xpack.sessionView.metadataDetailsTab.cloud": "云",
"xpack.sessionView.metadataDetailsTab.container": "容器",
"xpack.sessionView.metadataDetailsTab.host": "主机 OS",
"xpack.sessionView.metadataDetailsTab.metadata": "元数据",
"xpack.sessionView.metadataDetailsTab.orchestrator": "Orchestrator",
"xpack.sessionView.networkTooltip": "网络告警",
"xpack.sessionView.output": "输出",

View file

@ -9,6 +9,8 @@ import expect from '@kbn/expect';
import { GET_TOTAL_IO_BYTES_ROUTE } from '@kbn/session-view-plugin/common/constants';
import { FtrProviderContext } from '../../common/ftr_provider_context';
const MOCK_INDEX = 'logs-endpoint.events.process*';
const MOCK_SESSION_START_TIME = '2022-05-08T13:44:00.13Z';
const MOCK_SESSION_ENTITY_ID =
'MDEwMTAxMDEtMDEwMS0wMTAxLTAxMDEtMDEwMTAxMDEwMTAxLTUyMDU3LTEzMjk2NDkxMDQwLjEzMDAwMDAwMA==';
const MOCK_TOTAL_BYTES = 8192; // sum of total_captured_bytes field in io_events es archive
@ -31,7 +33,9 @@ export default function getTotalIOBytesTests({ getService }: FtrProviderContext)
it(`${GET_TOTAL_IO_BYTES_ROUTE} returns a page of IO events`, async () => {
const response = await supertest.get(GET_TOTAL_IO_BYTES_ROUTE).set('kbn-xsrf', 'foo').query({
index: MOCK_INDEX,
sessionEntityId: MOCK_SESSION_ENTITY_ID,
sessionStartTime: MOCK_SESSION_START_TIME,
});
expect(response.status).to.be(200);
expect(response.body.total).to.be(MOCK_TOTAL_BYTES);
@ -39,6 +43,8 @@ export default function getTotalIOBytesTests({ getService }: FtrProviderContext)
it(`${GET_TOTAL_IO_BYTES_ROUTE} returns 0 for invalid query`, async () => {
const response = await supertest.get(GET_TOTAL_IO_BYTES_ROUTE).set('kbn-xsrf', 'foo').query({
index: MOCK_INDEX,
sessionStartTime: MOCK_SESSION_START_TIME,
sessionEntityId: 'xyz',
});
expect(response.status).to.be(200);

View file

@ -9,6 +9,8 @@ import expect from '@kbn/expect';
import { IO_EVENTS_ROUTE } from '@kbn/session-view-plugin/common/constants';
import { FtrProviderContext } from '../../common/ftr_provider_context';
const MOCK_INDEX = 'logs-endpoint.events.process*';
const MOCK_SESSION_START_TIME = '2022-05-08T13:44:00.13Z';
const MOCK_SESSION_ENTITY_ID =
'MDEwMTAxMDEtMDEwMS0wMTAxLTAxMDEtMDEwMTAxMDEwMTAxLTUyMDU3LTEzMjk2NDkxMDQwLjEzMDAwMDAwMA==';
const MOCK_IO_EVENT_TOTAL = 8;
@ -33,7 +35,9 @@ export default function ioEventsTests({ getService }: FtrProviderContext) {
it(`${IO_EVENTS_ROUTE} returns a page of IO events`, async () => {
const response = await supertest.get(IO_EVENTS_ROUTE).set('kbn-xsrf', 'foo').query({
index: MOCK_INDEX,
sessionEntityId: MOCK_SESSION_ENTITY_ID,
sessionStartTime: MOCK_SESSION_START_TIME,
pageSize: MOCK_PAGE_SIZE,
});
expect(response.status).to.be(200);
@ -53,7 +57,9 @@ 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({
index: MOCK_INDEX,
sessionEntityId: MOCK_SESSION_ENTITY_ID,
sessionStartTime: MOCK_SESSION_START_TIME,
pageSize: MOCK_PAGE_SIZE,
cursor: MOCK_CURSOR,
});

View file

@ -26,6 +26,8 @@ import {
noKibanaPrivileges,
} from '../../../rule_registry/common/lib/authentication/users';
const MOCK_INDEX = 'logs-endpoint.events.process*';
const MOCK_SESSION_START_TIME = '2022-05-08T13:44:00.13Z';
const MOCK_SESSION_ENTITY_ID =
'MDEwMTAxMDEtMDEwMS0wMTAxLTAxMDEtMDEwMTAxMDEwMTAxLTUyMDU3LTEzMjk2NDkxMDQwLjEzMDAwMDAwMA==';
@ -58,7 +60,9 @@ 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({
index: MOCK_INDEX,
sessionEntityId: MOCK_SESSION_ENTITY_ID,
sessionStartTime: MOCK_SESSION_START_TIME,
pageSize: MOCK_PAGE_SIZE, // overriding to test pagination, as we only have 419 records of mock data
});
expect(response.status).to.be(200);
@ -68,7 +72,9 @@ 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({
index: MOCK_INDEX,
sessionEntityId: MOCK_SESSION_ENTITY_ID,
sessionStartTime: MOCK_SESSION_START_TIME,
pageSize: MOCK_PAGE_SIZE, // overriding to test pagination, as we only have 419 records of mock data
cursor: '2022-05-10T20:39:23.6817084Z', // paginating from the timestamp of the first alert.
});
@ -83,7 +89,9 @@ 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({
index: MOCK_INDEX,
sessionEntityId: MOCK_SESSION_ENTITY_ID,
sessionStartTime: MOCK_SESSION_START_TIME,
pageSize: MOCK_PAGE_SIZE, // overriding to test pagination, as we only have 419 records of mock data
cursor: '2022-05-10T20:39:23.6817084Z',
forward: false,
@ -113,7 +121,9 @@ export default function processEventsTests({ getService }: FtrProviderContext) {
.auth(username, password)
.set('kbn-xsrf', 'true')
.query({
index: MOCK_INDEX,
sessionEntityId: MOCK_SESSION_ENTITY_ID,
sessionStartTime: MOCK_SESSION_START_TIME,
pageSize: MOCK_PAGE_SIZE, // overriding to test pagination, as we only have 419 records of mock data
cursor: '2022-05-10T20:39:23.6817084Z', // paginating from the timestamp of the first alert.
});
@ -134,7 +144,9 @@ export default function processEventsTests({ getService }: FtrProviderContext) {
.auth(username, password)
.set('kbn-xsrf', 'true')
.query({
index: MOCK_INDEX,
sessionEntityId: MOCK_SESSION_ENTITY_ID,
sessionStartTime: MOCK_SESSION_START_TIME,
cursor: '2022-05-10T20:39:23.6817084Z', // paginating from the timestamp of the first alert.
});
expect(response.status).to.be(200);
@ -185,7 +197,9 @@ 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({
index: MOCK_INDEX,
sessionEntityId: MOCK_SESSION_ENTITY_ID,
sessionStartTime: MOCK_SESSION_START_TIME,
pageSize: MOCK_PAGE_SIZE, // overriding to test pagination, as we only have 419 records of mock data
});
expect(response.status).to.be(200);

View file

@ -29590,10 +29590,10 @@ xtend@~2.1.1:
dependencies:
object-keys "~0.4.0"
xterm@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.0.0.tgz#0af50509b33d0dc62fde7a4ec17750b8e453cc5c"
integrity sha512-tmVsKzZovAYNDIaUinfz+VDclraQpPUnAME+JawosgWRMphInDded/PuY0xmU5dOhyeYZsI0nz5yd8dPYsdLTA==
xterm@^5.1.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.1.0.tgz#3e160d60e6801c864b55adf19171c49d2ff2b4fc"
integrity sha512-LovENH4WDzpwynj+OTkLyZgJPeDom9Gra4DMlGAgz6pZhIDCQ+YuO7yfwanY+gVbn/mmZIStNOnVRU/ikQuAEQ==
y18n@^3.2.0:
version "3.2.2"