Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Security Solution] Use session view plugin to render session viewer in alerts, events and timeline #127520

Merged
merged 29 commits into from
Mar 29, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
af077cd
Cherry-pick security/timelines changes
kqualters-elastic Mar 10, 2022
0abc22b
Changes to make session_view work with generated data
kqualters-elastic Mar 10, 2022
821c0c2
Demo tweaks
kqualters-elastic Mar 17, 2022
21889d3
Merge branch 'main' into session-view-updated
kqualters-elastic Mar 21, 2022
86aa348
Cherry-pick security/timelines changes
kqualters-elastic Mar 10, 2022
c3e7708
Changes to make session_view work with generated data
kqualters-elastic Mar 10, 2022
d7cbe6a
create detail panel hook for session view
michaelolo24 Mar 23, 2022
dd9452c
add some tests
michaelolo24 Mar 23, 2022
19ad8df
Merge branch 'main' of github.com:elastic/kibana into session-view-up…
kqualters-elastic Mar 24, 2022
fa00f8b
Changes to make session_view work with generated data
kqualters-elastic Mar 10, 2022
b6d2b49
Merge branch 'safest-session-view-flyout' of github.com:michaelolo24/…
michaelolo24 Mar 24, 2022
c811902
Merge branch 'session-view-updated' into safest-session-view-flyout
michaelolo24 Mar 24, 2022
8e9c818
remove duplicate sessionViewId prop
michaelolo24 Mar 25, 2022
a8d9d13
Update with main
kqualters-elastic Mar 25, 2022
608f7c8
Merge remote-tracking branch 'upstream/main' into session-view-updated
kqualters-elastic Mar 25, 2022
638bcfe
Merge branch 'session-view-updated' into integrate-session-view-with-…
michaelolo24 Mar 25, 2022
9b18ccd
Merge pull request #3 from michaelolo24/integrate-session-view-with-d…
kqualters-elastic Mar 25, 2022
ebe4d56
Update tests and types, improve styles around full screen
kqualters-elastic Mar 29, 2022
63d2a5d
Merge remote-tracking branch 'upstream/main' into session-view-updated
kqualters-elastic Mar 29, 2022
e99a71b
Fix mistake in intl message, clean up tests
kqualters-elastic Mar 29, 2022
9b592b5
Create a hook for session view components and handlers
kqualters-elastic Mar 29, 2022
169e9d0
Use hook to share logic between tgrid and timeline, clean up css
kqualters-elastic Mar 29, 2022
794efea
Merge remote-tracking branch 'upstream/main' into session-view-updated
kqualters-elastic Mar 29, 2022
545cddd
Fix flyout usage with new hook
kqualters-elastic Mar 29, 2022
83502cd
Update failing snapshot
kqualters-elastic Mar 29, 2022
678fdc6
Remove feature flag
kqualters-elastic Mar 29, 2022
5293a14
Use correct sourcerer scope for panel
kqualters-elastic Mar 29, 2022
bf284c6
Fix embarrassment
kqualters-elastic Mar 29, 2022
bac80f5
Merge branch 'main' into session-view-updated
opauloh Mar 29, 2022
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
Original file line number Diff line number Diff line change
Expand Up @@ -2401,6 +2401,21 @@ export const ecsFieldMap = {
array: false,
required: false,
},
'process.entry_leader.entity_id': {
type: 'keyword',
array: false,
required: false,
},
'process.session_leader.entity_id': {
type: 'keyword',
array: false,
required: false,
},
'process.group_leader.entity_id': {
type: 'keyword',
array: false,
required: false,
},
'process.executable': {
type: 'keyword',
array: false,
Expand Down
9 changes: 9 additions & 0 deletions x-pack/plugins/security_solution/common/ecs/process/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ export interface ProcessEcs {
Ext?: Ext;
command_line?: string[];
entity_id?: string[];
entry_leader?: ProcessSessionData;
session_leader?: ProcessSessionData;
group_leader?: ProcessSessionData;
exit_code?: number[];
hash?: ProcessHashData;
parent?: ProcessParentData;
Expand All @@ -25,6 +28,12 @@ export interface ProcessEcs {
working_directory?: string[];
}

export interface ProcessSessionData {
entity_id?: string[];
pid?: string[];
name?: string[];
}

export interface ProcessHashData {
md5?: string[];
sha1?: string[];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,7 @@ export enum TimelineTabs {
notes = 'notes',
pinned = 'pinned',
eql = 'eql',
session = 'session',
}

/**
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/security_solution/kibana.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"licensing",
"maps",
"ruleRegistry",
"sessionView",
"taskManager",
"timelines",
"triggersActionsUi",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ const StatefulEventsViewerComponent: React.FC<Props> = ({
rowRenderers,
start,
scopeId,
sessionViewId,
showCheckboxes,
sort,
timelineQuery,
Expand Down Expand Up @@ -153,11 +154,11 @@ const StatefulEventsViewerComponent: React.FC<Props> = ({

const globalFilters = useMemo(() => [...filters, ...(pageFilters ?? [])], [filters, pageFilters]);
const trailingControlColumns: ControlColumnProps[] = EMPTY_CONTROL_COLUMNS;
const graphOverlay = useMemo(
() =>
graphEventId != null && graphEventId.length > 0 ? <GraphOverlay timelineId={id} /> : null,
[graphEventId, id]
);
const graphOverlay = useMemo(() => {
const shouldShowOverlay =
(graphEventId != null && graphEventId.length > 0) || sessionViewId !== null;
Copy link
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.

do we need graphEventId.length > 0 ? Can we get away with shouldShowOverlay = graphEventId ?? sessionViewId?

Copy link
Contributor

Choose a reason for hiding this comment

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

Can we unify the != and !== approach in this statement just to use one? Could be graphEventId or sessionViewId as undefined?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm nervous to change it, for whatever reason, graph event id is set to empty string sometimes and is undefined at other times

return shouldShowOverlay ? <GraphOverlay timelineId={id} /> : null;
}, [graphEventId, id, sessionViewId]);
const setQuery = useCallback(
(inspect, loading, refetch) => {
dispatch(inputsActions.setQuery({ id, inputId: 'global', inspect, loading, refetch }));
Expand Down Expand Up @@ -271,6 +272,7 @@ const makeMapStateToProps = () => {
kqlMode,
sort,
showCheckboxes,
sessionViewId,
} = timeline;

return {
Expand All @@ -288,6 +290,7 @@ const makeMapStateToProps = () => {
query: getGlobalQuerySelector(state),
sort,
showCheckboxes,
sessionViewId,
// Used to determine whether the footer should show (since it is hidden if the graph is showing.)
// `getTimeline` actually returns `TimelineModel | undefined`
graphEventId,
Expand Down Expand Up @@ -333,6 +336,7 @@ export const StatefulEventsViewer = connector(
prevProps.start === nextProps.start &&
deepEqual(prevProps.pageFilters, nextProps.pageFilters) &&
prevProps.showCheckboxes === nextProps.showCheckboxes &&
prevProps.sessionViewId === nextProps.sessionViewId &&
prevProps.start === nextProps.start &&
prevProps.utilityBar === nextProps.utilityBar &&
prevProps.additionalFilters === nextProps.additionalFilters &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,4 +176,5 @@ export const requiredFieldsForActions = [
'file.hash.sha256',
'host.os.family',
'event.code',
'process.entry_leader.entity_id',
];
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ export const AlertsTableComponent: React.FC<AlertsTableComponentProps> = ({
const kibana = useKibana();
const [, dispatchToaster] = useStateToaster();
const { addWarning } = useAppToasts();
const ACTION_BUTTON_COUNT = 4;
const ACTION_BUTTON_COUNT = 5;

const getGlobalQuery = useCallback(
(customFilters: Filter[]) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ const EventsQueryTabBodyComponent: React.FC<HostsComponentsQueryProps> = ({
}) => {
const dispatch = useDispatch();
const { globalFullScreen } = useGlobalFullScreen();
const ACTION_BUTTON_COUNT = 4;
const ACTION_BUTTON_COUNT = 5;
const tGridEnabled = useIsExperimentalFeatureEnabled('tGridEnabled');

useEffect(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,17 @@ import {
useGlobalFullScreen,
useTimelineFullScreen,
} from '../../../common/containers/use_full_screen';
import { useKibana } from '../../../common/lib/kibana';
import { useDeepEqualSelector } from '../../../common/hooks/use_selector';
import { TimelineId } from '../../../../common/types/timeline';
import { timelineSelectors } from '../../store/timeline';
import { timelineDefaults } from '../../store/timeline/defaults';
import { isFullScreen } from '../timeline/body/column_headers';
import { updateTimelineGraphEventId } from '../../../timelines/store/timeline/actions';
import {
updateTimelineGraphEventId,
updateTimelineSessionViewEventId,
updateTimelineSessionViewSessionId,
} from '../../../timelines/store/timeline/actions';
import { inputsActions } from '../../../common/store/actions';
import { Resolver } from '../../../resolver/view';
import {
Expand Down Expand Up @@ -70,6 +75,12 @@ const FullScreenButtonIcon = styled(EuiButtonIcon)`
margin: 4px 0 4px 0;
`;

const ScrollableFlexItem = styled(EuiFlexItem)`
${({ theme }) => `margin: 0 ${theme.eui.euiSizeM};`}
overflow: hidden;
width: 100%;
`;

interface OwnProps {
timelineId: TimelineId;
}
Expand Down Expand Up @@ -131,6 +142,14 @@ const GraphOverlayComponent: React.FC<OwnProps> = ({ timelineId }) => {
const graphEventId = useDeepEqualSelector(
(state) => (getTimeline(state, timelineId) ?? timelineDefaults).graphEventId
);
const { sessionView } = useKibana().services;
const sessionViewId = useDeepEqualSelector(
(state) => (getTimeline(state, timelineId) ?? timelineDefaults).sessionViewId
);
const sessionViewMain = useMemo(() => {
return sessionViewId !== null ? sessionView.getSessionView(sessionViewId) : null;
Copy link
Contributor

Choose a reason for hiding this comment

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

Any situation where it can be undefined? Would we want sessionViewId != null?

}, [sessionView, sessionViewId]);

const getStartSelector = useMemo(() => startSelector(), []);
const getEndSelector = useMemo(() => endSelector(), []);
const getIsLoadingSelector = useMemo(() => isLoadingSelector(), []);
Expand Down Expand Up @@ -180,6 +199,8 @@ const GraphOverlayComponent: React.FC<OwnProps> = ({ timelineId }) => {
}
}
dispatch(updateTimelineGraphEventId({ id: timelineId, graphEventId: '' }));
dispatch(updateTimelineSessionViewEventId({ id: timelineId, eventId: null }));
dispatch(updateTimelineSessionViewSessionId({ id: timelineId, eventId: null }));
}, [dispatch, timelineId, setTimelineFullScreen, setGlobalFullScreen]);

useEffect(() => {
Expand Down Expand Up @@ -219,7 +240,18 @@ const GraphOverlayComponent: React.FC<OwnProps> = ({ timelineId }) => {
[defaultDataView.patternList, isInTimeline, timelinePatterns]
);

if (fullScreen && !isInTimeline) {
if (!isInTimeline && sessionViewId !== null) {
Copy link
Contributor

Choose a reason for hiding this comment

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

We don't need the fullScreen check here anymore?

Copy link
Contributor

Choose a reason for hiding this comment

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

How it should behave in the fullScreen mode?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

like analyzer i think, which it does, we just have a small issue with the hard coded unless specified height of 500px coming from here https://github.com/elastic/kibana/blob/main/x-pack/plugins/session_view/public/components/session_view/styles.ts#L16 @zizhouW @mitodrummer @opauloh I think we should change the styles here so that the search bar/detail panel button take up as much space as the constituent components need, and then the process tree grows to fill the remaining part of a top level container. Passing a height will be buggy/hard for no reason with the full screen functionality in data grid/timeline I think. What do y'all think?

Copy link
Contributor

Choose a reason for hiding this comment

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

so, the current way it works, it can't have auto height, because of infinite scrolling, so in order to enable full screen you would need to manually change the height you're passing to SessionView on FullScreen mode, something like this:

sessionView.getSessionView({
   sessionEntityId,
   height: fullScreen ? 'calc(100vh - 118px)' : '500px'
}) 

but having to know the height of the SessionView's search bar (118px), and the default height of the session view (500px) isn't ideal, so we are willing to change later to something more like this

sessionView.getSessionView({
   sessionEntityId,
   isFullScreen: fullScreen
}) 

return (
<EuiFlexGroup alignItems="flexStart" gutterSize="none" direction="column">
<EuiFlexItem grow={false}>
<EuiButtonEmpty iconType="cross" onClick={onCloseOverlay} size="xs">
{i18n.CLOSE_SESSION}
</EuiButtonEmpty>
</EuiFlexItem>
<ScrollableFlexItem grow={2}>{sessionViewMain}</ScrollableFlexItem>
</EuiFlexGroup>
);
} else if (fullScreen && !isInTimeline) {
return (
<FullScreenOverlayContainer data-test-subj="overlayContainer">
<EuiHorizontalRule margin="none" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,10 @@ export const CLOSE_ANALYZER = i18n.translate(
defaultMessage: 'Close analyzer',
}
);

export const CLOSE_SESSION = i18n.translate(
'xpack.securitySolution.timeline.graphOverlay.closeSessionButton',
{
defaultMessage: 'Close Session',
}
);
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import { useShallowEqualSelector } from '../../../../../common/hooks/use_selecto
import {
setActiveTabTimeline,
updateTimelineGraphEventId,
updateTimelineSessionViewSessionId,
updateTimelineSessionViewEventId,
} from '../../../../store/timeline/actions';
import {
useGlobalFullScreen,
Expand Down Expand Up @@ -128,6 +130,24 @@ const ActionsComponent: React.FC<ActionProps> = ({
}
}, [dispatch, ecsData._id, timelineId, setGlobalFullScreen, setTimelineFullScreen]);

const entryLeader = useMemo(() => {
const { process } = ecsData;
const entryLeaderIds = process?.entry_leader?.entity_id;
if (entryLeaderIds !== undefined) {
return entryLeaderIds[0];
Copy link
Contributor

Choose a reason for hiding this comment

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

Always going to be the first one? And it's guaranteed to be an array of 1?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm not sure, but that's exactly why I wanted to get ecs changes in and then use real endpoint data to verify first. Seems to be the case so far.

Copy link
Contributor

Choose a reason for hiding this comment

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

We need to check array length at least.

} else {
return null;
}
}, [ecsData]);
Copy link
Contributor

Choose a reason for hiding this comment

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

Don't expect ecsData to change, but any thoughts on this being ecsData.process?.entry_leader?.entity_id to be more specific? Not sure it would make a difference or if the linter would take it, but just a suggestion.

Copy link
Contributor

Choose a reason for hiding this comment

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

Agree, the better is to have a more specific to check.


const openSessionView = useCallback(() => {
if (entryLeader !== null) {
dispatch(setActiveTabTimeline({ id: timelineId, activeTab: TimelineTabs.session }));
dispatch(updateTimelineSessionViewSessionId({ id: timelineId, eventId: entryLeader }));
dispatch(updateTimelineSessionViewEventId({ id: timelineId, eventId: entryLeader }));
}
}, [dispatch, timelineId, entryLeader]);

return (
<ActionsContainer>
{showCheckboxes && !tGridEnabled && (
Expand Down Expand Up @@ -220,6 +240,21 @@ const ActionsComponent: React.FC<ActionProps> = ({
</EventsTdContent>
</div>
) : null}
{entryLeader !== null ? (
<div>
<EventsTdContent textAlign="center" width={DEFAULT_ACTION_BUTTON_WIDTH}>
<EuiToolTip data-test-subj="expand-event-tool-tip" content={i18n.OPEN_SESSION_VIEW}>
<EuiButtonIcon
aria-label={i18n.VIEW_DETAILS_FOR_ROW({ ariaRowindex, columnValues })}
data-test-subj="session-view-button"
iconType="console"
Copy link
Contributor

Choose a reason for hiding this comment

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

Any word on when they expect the icon in the mocks to be added to eui?

Copy link
Contributor

@opauloh opauloh Mar 23, 2022

Choose a reason for hiding this comment

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

the Icons are merged in Eui, and it seems we will have a quick release today 🤞 or at most tomorrow

Copy link
Contributor Author

Choose a reason for hiding this comment

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

pr here #128313

onClick={openSessionView}
size="s"
/>
</EuiToolTip>
</EventsTdContent>
</div>
) : null}
</>
</ActionsContainer>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ export const BodyComponent = React.memo<StatefulBodyProps>(
const { queryFields, selectAll } = useDeepEqualSelector((state) =>
getManageTimeline(state, id)
);
const ACTION_BUTTON_COUNT = 5;
const ACTION_BUTTON_COUNT = 6;

const onRowSelected: OnRowSelected = useCallback(
({ eventIds, isSelected }: { eventIds: string[]; isSelected: boolean }) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@ export const COPY_TO_CLIPBOARD = i18n.translate(
}
);

export const OPEN_SESSION_VIEW = i18n.translate(
'xpack.securitySolution.timeline.body.openSessionViewLabel',
{
defaultMessage: 'Open Session View',
}
);

export const INVESTIGATE = i18n.translate(
'xpack.securitySolution.timeline.body.actions.investigateLabel',
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ export const QueryTabContentComponent: React.FC<Props> = ({
} = useSourcererDataView(SourcererScopeName.timeline);

const { uiSettings } = useKibana().services;
const ACTION_BUTTON_COUNT = 5;
const ACTION_BUTTON_COUNT = 6;

const getManageTimeline = useMemo(() => timelineSelectors.getManageTimelineById(), []);
const { filterManager: activeFilterManager } = useDeepEqualSelector((state) =>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* 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 { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import React, { useMemo } from 'react';
import styled from 'styled-components';
import { timelineSelectors } from '../../../store/timeline';
import { useKibana } from '../../../../common/lib/kibana';
import { TimelineId } from '../../../../../common/types/timeline';
import { timelineDefaults } from '../../../../timelines/store/timeline/defaults';
import { useDeepEqualSelector } from '../../../../common/hooks/use_selector';

const FullWidthFlexGroup = styled(EuiFlexGroup)`
margin: 0;
width: 100%;
overflow: hidden;
`;

const ScrollableFlexItem = styled(EuiFlexItem)`
${({ theme }) => `margin: 0 ${theme.eui.euiSizeM};`}
overflow: hidden;
`;

interface Props {
timelineId: TimelineId;
}

const SessionTabContent: React.FC<Props> = ({ timelineId }) => {
const { sessionView } = useKibana().services;
Copy link
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.

I wonder if you can just have a useSessionView hook that internalizes all this logic and you can use that here and in the graph_overlay component https://github.com/elastic/kibana/pull/127520/files#diff-76ce75e94a9924487bf7b684f4a7404b250414469330abfbbf6ab0d34e770597, and you can just test that hook once.


const getTimeline = useMemo(() => timelineSelectors.getTimelineByIdSelector(), []);

const sessionViewId = useDeepEqualSelector(
(state) => (getTimeline(state, timelineId) ?? timelineDefaults).sessionViewId
);
const sessionViewMain = useMemo(() => {
return sessionViewId !== null ? sessionView.getSessionView(sessionViewId) : null;
}, [sessionView, sessionViewId]);

return <ScrollableFlexItem grow={2}>{sessionViewMain}</ScrollableFlexItem>;
};

// eslint-disable-next-line import/no-default-export
export default SessionTabContent;
Loading