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][Endpoint]Activity Log API/UX changes #114905

Merged
merged 31 commits into from
Oct 19, 2021
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
59ed20c
rename legacy actions/responses
ashokaditya Oct 13, 2021
e32e958
use correct name for responses index
ashokaditya Oct 13, 2021
94b2fe0
extract helper method to utils
ashokaditya Oct 13, 2021
e4fda32
append endpoint responses docs to activity log
ashokaditya Oct 13, 2021
e12f48a
Show completed responses on activity log
ashokaditya Oct 14, 2021
ce74a8e
remove width restriction on date picker
ashokaditya Oct 14, 2021
45153a4
Merge branch 'master' into feat/olm-activity_log_API_UX-1702
kibanamachine Oct 14, 2021
6867573
add a simple test to verify endpoint responses
ashokaditya Oct 14, 2021
e0c62ad
find unique action_ids from `.fleet-actions` and `.logs-endpoint.acti…
ashokaditya Oct 14, 2021
7e6d091
do not filter out endpoint only actions/responses that did not make i…
ashokaditya Oct 15, 2021
fca0757
use a constant to manage various doc types
ashokaditya Oct 15, 2021
59df284
refactor `getActivityLog`
ashokaditya Oct 15, 2021
a340eb7
skip this for now
ashokaditya Oct 15, 2021
c15c978
Merge branch 'master' into feat/olm-activity_log_API_UX-1702
kibanamachine Oct 15, 2021
10a1a88
Merge branch 'master' into feat/olm-activity_log_API_UX-1702
kibanamachine Oct 15, 2021
b1dc33d
improve types
ashokaditya Oct 17, 2021
983ced3
display endpoint actions similar to fleet actions, but with success i…
ashokaditya Oct 17, 2021
94420bf
Correctly do mocks for tests
ashokaditya Oct 17, 2021
3a5e576
Merge branch 'master' into feat/olm-activity_log_API_UX-1702
kibanamachine Oct 18, 2021
c740867
Include only errored endpoint actions, remove successful duplicates
ashokaditya Oct 18, 2021
bc80259
Update tests to use non duplicate action_ids
ashokaditya Oct 18, 2021
74a8340
show correct action title
ashokaditya Oct 18, 2021
ba8b5b4
statusCode constant
ashokaditya Oct 18, 2021
4fea956
rename
ashokaditya Oct 18, 2021
faa6759
Update translations.ts
ashokaditya Oct 18, 2021
d4a0819
Merge branch 'master' into feat/olm-activity_log_API_UX-1702
kibanamachine Oct 18, 2021
859ca57
fix type
ashokaditya Oct 18, 2021
03ec833
Merge branch 'master' into feat/olm-activity_log_API_UX-1702
kibanamachine Oct 18, 2021
aa89075
Merge branch 'master' into feat/olm-activity_log_API_UX-1702
kibanamachine Oct 18, 2021
f5ac130
Merge branch 'master' into feat/olm-activity_log_API_UX-1702
kibanamachine Oct 19, 2021
1b1196c
Merge branch 'master' into feat/olm-activity_log_API_UX-1702
kibanamachine Oct 19, 2021
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
11 changes: 10 additions & 1 deletion x-pack/plugins/security_solution/common/endpoint/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
export const ENDPOINT_ACTIONS_DS = '.logs-endpoint.actions';
export const ENDPOINT_ACTIONS_INDEX = `${ENDPOINT_ACTIONS_DS}-default`;
export const ENDPOINT_ACTION_RESPONSES_DS = '.logs-endpoint.action.responses';
export const ENDPOINT_ACTION_RESPONSES_INDEX = `${ENDPOINT_ACTIONS_DS}-default`;
export const ENDPOINT_ACTION_RESPONSES_INDEX = `${ENDPOINT_ACTION_RESPONSES_DS}-default`;

export const eventsIndexPattern = 'logs-endpoint.events.*';
export const alertsIndexPattern = 'logs-endpoint.alerts-*';
Expand Down Expand Up @@ -60,3 +60,12 @@ export const UNISOLATE_HOST_ROUTE = `${BASE_ENDPOINT_ROUTE}/unisolate`;
/** Endpoint Actions Log Routes */
export const ENDPOINT_ACTION_LOG_ROUTE = `/api/endpoint/action_log/{agent_id}`;
export const ACTION_STATUS_ROUTE = `/api/endpoint/action_status`;

// From https://github.com/for-GET/know-your-http-well/blob/master/json/status-codes.json
// similar to x-pack/plugins/apm/public/components/shared/Summary/HttpStatusBadge/statusCodes.ts
export const statusCodes: { [key: string]: { code: string; meaning: string } } = {
'424': {
code: '424',
meaning: 'Failed Dependency',
},
ashokaditya marked this conversation as resolved.
Show resolved Hide resolved
};
33 changes: 30 additions & 3 deletions x-pack/plugins/security_solution/common/endpoint/types/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ import { ActionStatusRequestSchema, HostIsolationRequestSchema } from '../schema

export type ISOLATION_ACTIONS = 'isolate' | 'unisolate';

export const ActivityLogItemTypes = {
ACTION: 'action' as const,
RESPONSE: 'response' as const,
FLEET_ACTION: 'fleetAction' as const,
FLEET_RESPONSE: 'fleetResponse' as const,
};

interface EcsError {
code?: string;
id?: string;
Expand Down Expand Up @@ -87,8 +94,24 @@ export interface EndpointActionResponse {
action_data: EndpointActionData;
}

export interface EndpointActivityLogAction {
type: typeof ActivityLogItemTypes.ACTION;
item: {
id: string;
data: LogsEndpointAction;
};
}

export interface EndpointActivityLogActionResponse {
type: typeof ActivityLogItemTypes.RESPONSE;
item: {
id: string;
data: LogsEndpointActionResponse;
};
}

export interface ActivityLogAction {
type: 'action';
type: typeof ActivityLogItemTypes.FLEET_ACTION;
item: {
// document _id
id: string;
Expand All @@ -97,15 +120,19 @@ export interface ActivityLogAction {
};
}
export interface ActivityLogActionResponse {
type: 'response';
type: typeof ActivityLogItemTypes.FLEET_RESPONSE;
item: {
// document id
id: string;
// document _source
data: EndpointActionResponse;
};
}
export type ActivityLogEntry = ActivityLogAction | ActivityLogActionResponse;
export type ActivityLogEntry =
| ActivityLogAction
| ActivityLogActionResponse
| EndpointActivityLogAction
| EndpointActivityLogActionResponse;
export interface ActivityLog {
page: number;
pageSize: number;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ interface Range {

const DatePickerWrapper = styled.div`
width: ${(props) => props.theme.eui.fractions.single.percentage};
max-width: 350px;
`;
const StickyFlexItem = styled(EuiFlexItem)`
background: ${(props) => `${props.theme.eui.euiHeaderBackgroundColor}`};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,40 +9,54 @@ import React, { memo, useMemo } from 'react';
import styled from 'styled-components';

import { EuiComment, EuiText, EuiAvatarProps, EuiCommentProps, IconType } from '@elastic/eui';
import { Immutable, ActivityLogEntry } from '../../../../../../../common/endpoint/types';
import {
Immutable,
ActivityLogEntry,
ActivityLogItemTypes,
} from '../../../../../../../common/endpoint/types';
import { FormattedRelativePreferenceDate } from '../../../../../../common/components/formatted_date';
import { LogEntryTimelineIcon } from './log_entry_timeline_icon';
import { useEuiTheme } from '../../../../../../common/lib/theme/use_eui_theme';

import * as i18 from '../../translations';

const useLogEntryUIProps = (
logEntry: Immutable<ActivityLogEntry>
logEntry: Immutable<ActivityLogEntry>,
theme: ReturnType<typeof useEuiTheme>
): {
actionEventTitle: string;
avatarColor: EuiAvatarProps['color'];
avatarIconColor: EuiAvatarProps['iconColor'];
avatarSize: EuiAvatarProps['size'];
commentText: string;
commentType: EuiCommentProps['type'];
displayComment: boolean;
displayResponseEvent: boolean;
failedActionEventTitle: string;
iconType: IconType;
isResponseEvent: boolean;
isSuccessful: boolean;
isCompleted: boolean;
responseEventTitle: string;
username: string | React.ReactNode;
} => {
return useMemo(() => {
let iconType: IconType = 'dot';
let commentType: EuiCommentProps['type'] = 'update';
let commentText: string = '';
let avatarColor: EuiAvatarProps['color'] = theme.euiColorLightestShade;
let avatarIconColor: EuiAvatarProps['iconColor'];
let avatarSize: EuiAvatarProps['size'] = 's';
let failedActionEventTitle: string = '';
let isIsolateAction: boolean = false;
let isResponseEvent: boolean = false;
let isSuccessful: boolean = false;
let isCompleted: boolean = false;
let displayComment: boolean = false;
let displayResponseEvent: boolean = true;
let username: EuiCommentProps['username'] = '';

if (logEntry.type === 'action') {
if (logEntry.type === ActivityLogItemTypes.FLEET_ACTION) {
avatarSize = 'm';
commentType = 'regular';
commentText = logEntry.item.data.data.comment?.trim() ?? '';
Expand All @@ -59,13 +73,51 @@ const useLogEntryUIProps = (
displayComment = true;
}
}
} else if (logEntry.type === 'response') {
}
if (logEntry.type === ActivityLogItemTypes.ACTION) {
avatarSize = 'm';
commentType = 'regular';
commentText = logEntry.item.data.EndpointActions.data.comment?.trim() ?? '';
displayResponseEvent = false;
iconType = 'lockOpen';
username = logEntry.item.data.user.id;
avatarIconColor = theme.euiColorVis9_behindText;
failedActionEventTitle = i18.ACTIVITY_LOG.LogEntry.action.failedEndpointReleaseAction;
if (logEntry.item.data.EndpointActions.data) {
const data = logEntry.item.data.EndpointActions.data;
if (data.command === 'isolate') {
iconType = 'lock';
failedActionEventTitle = i18.ACTIVITY_LOG.LogEntry.action.failedEndpointIsolateAction;
}
if (commentText) {
displayComment = true;
}
}
} else if (logEntry.type === ActivityLogItemTypes.FLEET_RESPONSE) {
isResponseEvent = true;
if (logEntry.item.data.action_data.command === 'isolate') {
isIsolateAction = true;
}
if (!!logEntry.item.data.completed_at && !logEntry.item.data.error) {
isSuccessful = true;
} else {
avatarColor = theme.euiColorVis9_behindText;
}
} else if (logEntry.type === ActivityLogItemTypes.RESPONSE) {
iconType = 'check';
isResponseEvent = true;
if (logEntry.item.data.EndpointActions.data.command === 'isolate') {
isIsolateAction = true;
}
if (logEntry.item.data.EndpointActions.completed_at) {
isCompleted = true;
if (!logEntry.item.data.error) {
isSuccessful = true;
avatarColor = theme.euiColorVis0_behindText;
} else {
isSuccessful = false;
avatarColor = theme.euiColorVis9_behindText;
}
}
}

Expand All @@ -75,13 +127,23 @@ const useLogEntryUIProps = (

const getResponseEventTitle = () => {
if (isIsolateAction) {
if (isSuccessful) {
if (isCompleted) {
if (isSuccessful) {
return i18.ACTIVITY_LOG.LogEntry.response.unisolationCompletedAndSuccessful;
}
return i18.ACTIVITY_LOG.LogEntry.response.unisolationCompletedAndUnsuccessful;
} else if (isSuccessful) {
return i18.ACTIVITY_LOG.LogEntry.response.isolationSuccessful;
} else {
return i18.ACTIVITY_LOG.LogEntry.response.isolationFailed;
}
} else {
if (isSuccessful) {
if (isCompleted) {
if (isSuccessful) {
return i18.ACTIVITY_LOG.LogEntry.response.unisolationCompletedAndSuccessful;
}
return i18.ACTIVITY_LOG.LogEntry.response.unisolationCompletedAndUnsuccessful;
} else if (isSuccessful) {
return i18.ACTIVITY_LOG.LogEntry.response.unisolationSuccessful;
} else {
return i18.ACTIVITY_LOG.LogEntry.response.unisolationFailed;
Expand All @@ -91,18 +153,22 @@ const useLogEntryUIProps = (

return {
actionEventTitle,
avatarColor,
avatarIconColor,
avatarSize,
commentText,
commentType,
displayComment,
displayResponseEvent,
failedActionEventTitle,
iconType,
isResponseEvent,
isSuccessful,
isCompleted,
responseEventTitle: getResponseEventTitle(),
username,
};
}, [logEntry]);
}, [logEntry, theme]);
};

const StyledEuiComment = styled(EuiComment)`
Expand All @@ -126,28 +192,41 @@ const StyledEuiComment = styled(EuiComment)`
`;

export const LogEntry = memo(({ logEntry }: { logEntry: Immutable<ActivityLogEntry> }) => {
const theme = useEuiTheme();
const {
actionEventTitle,
avatarColor,
avatarIconColor,
avatarSize,
commentText,
commentType,
displayComment,
displayResponseEvent,
failedActionEventTitle,
iconType,
isResponseEvent,
isSuccessful,
responseEventTitle,
username,
} = useLogEntryUIProps(logEntry);
} = useLogEntryUIProps(logEntry, theme);

return (
<StyledEuiComment
type={(commentType ?? 'regular') as EuiCommentProps['type']}
username={username}
timestamp={<FormattedRelativePreferenceDate value={logEntry.item.data['@timestamp']} />}
event={<b>{displayResponseEvent ? responseEventTitle : actionEventTitle}</b>}
event={
<b>
{displayResponseEvent
? responseEventTitle
: failedActionEventTitle
? failedActionEventTitle
: actionEventTitle}
</b>
}
timelineIcon={
<LogEntryTimelineIcon {...{ avatarSize, iconType, isResponseEvent, isSuccessful }} />
<LogEntryTimelineIcon
{...{ avatarSize, iconType, isResponseEvent, avatarColor, avatarIconColor }}
/>
}
data-test-subj="timelineEntry"
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,32 +7,27 @@

import React, { memo } from 'react';
import { EuiAvatar, EuiAvatarProps } from '@elastic/eui';
import { useEuiTheme } from '../../../../../../common/lib/theme/use_eui_theme';

export const LogEntryTimelineIcon = memo(
({
avatarColor,
avatarIconColor,
avatarSize,
isResponseEvent,
isSuccessful,
iconType,
isResponseEvent,
}: {
avatarColor: EuiAvatarProps['color'];
avatarIconColor?: EuiAvatarProps['iconColor'];
avatarSize: EuiAvatarProps['size'];
isResponseEvent: boolean;
isSuccessful: boolean;
iconType: EuiAvatarProps['iconType'];
isResponseEvent: boolean;
}) => {
const euiTheme = useEuiTheme();

return (
<EuiAvatar
name="Timeline Icon"
size={avatarSize ?? 's'}
color={
isResponseEvent && !isSuccessful
? euiTheme.euiColorVis9_behindText
: euiTheme.euiColorLightestShade
}
iconColor="default"
color={avatarColor}
iconColor={avatarIconColor ?? 'default'}
iconType={iconType ?? 'dot'}
/>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@
import React, { ComponentType } from 'react';
import moment from 'moment';

import { ActivityLog, Immutable } from '../../../../../../common/endpoint/types';
import {
ActivityLog,
Immutable,
ActivityLogItemTypes,
} from '../../../../../../common/endpoint/types';
import { EndpointDetailsFlyoutTabs } from './components/endpoint_details_tabs';
import { EndpointActivityLog } from './endpoint_activity_log';
import { EndpointDetailsFlyout } from '.';
Expand All @@ -26,7 +30,7 @@ export const dummyEndpointActivityLog = (
endDate: moment().toString(),
data: [
{
type: 'action',
type: ActivityLogItemTypes.FLEET_ACTION,
item: {
id: '',
data: {
Expand All @@ -44,7 +48,7 @@ export const dummyEndpointActivityLog = (
},
},
{
type: 'action',
type: ActivityLogItemTypes.FLEET_ACTION,
item: {
id: '',
data: {
Expand All @@ -63,7 +67,7 @@ export const dummyEndpointActivityLog = (
},
},
{
type: 'action',
type: ActivityLogItemTypes.FLEET_ACTION,
item: {
id: '',
data: {
Expand All @@ -82,7 +86,7 @@ export const dummyEndpointActivityLog = (
},
},
{
type: 'action',
type: ActivityLogItemTypes.FLEET_ACTION,
item: {
id: '',
data: {
Expand Down
Loading