Skip to content

Commit

Permalink
[Endpoint] resolver v1 events (#59233)
Browse files Browse the repository at this point in the history
* Unifying the test index name for resolver and alerts

* Endpoint isn't sending the agent field so check for it

* Update resolver to use either legacy or ecs events

* Use correct format for child events api

* Adding string or array for category and type

* Add return types to process event models

* Create a common/models.ts for common event logic

* Decrease resolver min height

* Update types to match cli tool

* Add a smoke test for resolver rendering nodes, remove unused selector

* Add common/models/event

* Internationalize some strings, address pr comments

Co-authored-by: Jonathan Buttner <jonathan.buttner@elastic.co>
  • Loading branch information
kqualters-elastic and jonathan-buttner authored Mar 18, 2020
1 parent 52dd5e0 commit 64af780
Show file tree
Hide file tree
Showing 18 changed files with 308 additions and 182 deletions.
31 changes: 31 additions & 0 deletions x-pack/plugins/endpoint/common/models/event.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { EndpointEvent, LegacyEndpointEvent } from '../types';

export function isLegacyEvent(
event: EndpointEvent | LegacyEndpointEvent
): event is LegacyEndpointEvent {
return (event as LegacyEndpointEvent).endgame !== undefined;
}

export function eventTimestamp(
event: EndpointEvent | LegacyEndpointEvent
): string | undefined | number {
if (isLegacyEvent(event)) {
return event.endgame.timestamp_utc;
} else {
return event['@timestamp'];
}
}

export function eventName(event: EndpointEvent | LegacyEndpointEvent): string {
if (isLegacyEvent(event)) {
return event.endgame.process_name ? event.endgame.process_name : '';
} else {
return event.process.name;
}
}
5 changes: 3 additions & 2 deletions x-pack/plugins/endpoint/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -311,8 +311,8 @@ export interface EndpointEvent {
version: string;
};
event: {
category: string;
type: string;
category: string | string[];
type: string | string[];
id: string;
kind: string;
};
Expand All @@ -328,6 +328,7 @@ export interface EndpointEvent {
name: string;
parent?: {
entity_id: string;
name?: string;
};
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,15 +165,3 @@ export const hasSelectedAlert: (state: AlertListState) => boolean = createSelect
uiQueryParams,
({ selected_alert: selectedAlert }) => selectedAlert !== undefined
);

/**
* Determine if the alert event is most likely compatible with LegacyEndpointEvent.
*/
export const selectedAlertIsLegacyEndpointEvent: (
state: AlertListState
) => boolean = createSelector(selectedAlertDetailsData, function(event) {
if (event === undefined) {
return false;
}
return 'endgame' in event;
});
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import React, { memo, useMemo } from 'react';
import styled from 'styled-components';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import {
Expand All @@ -19,87 +20,104 @@ import * as selectors from '../../../../store/alerts/selectors';
import { MetadataPanel } from './metadata_panel';
import { FormattedDate } from '../../formatted_date';
import { AlertDetailResolver } from '../../resolver';
import { ResolverEvent } from '../../../../../../../common/types';
import { TakeActionDropdown } from './take_action_dropdown';

export const AlertDetailsOverview = memo(() => {
const alertDetailsData = useAlertListSelector(selectors.selectedAlertDetailsData);
if (alertDetailsData === undefined) {
return null;
}
const selectedAlertIsLegacyEndpointEvent = useAlertListSelector(
selectors.selectedAlertIsLegacyEndpointEvent
);
export const AlertDetailsOverview = styled(
memo(() => {
const alertDetailsData = useAlertListSelector(selectors.selectedAlertDetailsData);
if (alertDetailsData === undefined) {
return null;
}

const tabs: EuiTabbedContentTab[] = useMemo(() => {
return [
{
id: 'overviewMetadata',
'data-test-subj': 'overviewMetadata',
name: i18n.translate(
'xpack.endpoint.application.endpoint.alertDetails.overview.tabs.overview',
{
defaultMessage: 'Overview',
}
),
content: (
<>
<EuiSpacer />
<MetadataPanel />
</>
),
},
{
id: 'overviewResolver',
name: i18n.translate(
'xpack.endpoint.application.endpoint.alertDetails.overview.tabs.resolver',
{
defaultMessage: 'Resolver',
}
),
content: (
<>
<EuiSpacer />
{selectedAlertIsLegacyEndpointEvent && <AlertDetailResolver />}
</>
),
},
];
}, [selectedAlertIsLegacyEndpointEvent]);
const tabs: EuiTabbedContentTab[] = useMemo(() => {
return [
{
id: 'overviewMetadata',
'data-test-subj': 'overviewMetadata',
name: i18n.translate(
'xpack.endpoint.application.endpoint.alertDetails.overview.tabs.overview',
{
defaultMessage: 'Overview',
}
),
content: (
<>
<EuiSpacer />
<MetadataPanel />
</>
),
},
{
id: 'overviewResolver',
'data-test-subj': 'overviewResolverTab',
name: i18n.translate(
'xpack.endpoint.application.endpoint.alertDetails.overview.tabs.resolver',
{
defaultMessage: 'Resolver',
}
),
content: (
<>
<EuiSpacer />
<AlertDetailResolver selectedEvent={(alertDetailsData as unknown) as ResolverEvent} />
</>
),
},
];
}, [alertDetailsData]);

return (
<>
<section className="details-overview-summary">
<EuiTitle size="s">
<h3>
return (
<>
<section className="details-overview-summary">
<EuiTitle size="s">
<h3>
<FormattedMessage
id="xpack.endpoint.application.endpoint.alertDetails.overview.title"
defaultMessage="Detected Malicious File"
/>
</h3>
</EuiTitle>
<EuiSpacer />
<EuiText>
<p>
<FormattedMessage
id="xpack.endpoint.application.endpoint.alertDetails.overview.summary"
defaultMessage="MalwareScore detected the opening of a document on {hostname} on {date}"
values={{
hostname: alertDetailsData.host.hostname,
date: <FormattedDate timestamp={alertDetailsData['@timestamp']} />,
}}
/>
</p>
</EuiText>
<EuiSpacer />
<EuiText>
Endpoint Status:{' '}
<EuiHealth color="success">
{' '}
<FormattedMessage
id="xpack.endpoint.application.endpoint.alertDetails.endpoint.status.online"
defaultMessage="Online"
/>
</EuiHealth>
</EuiText>
<EuiText>
{' '}
<FormattedMessage
id="xpack.endpoint.application.endpoint.alertDetails.overview.title"
defaultMessage="Detected Malicious File"
id="xpack.endpoint.application.endpoint.alertDetails.alert.status.open"
defaultMessage="Alert Status: Open"
/>
</h3>
</EuiTitle>
<EuiSpacer />
<EuiText>
<p>
<FormattedMessage
id="xpack.endpoint.application.endpoint.alertDetails.overview.summary"
defaultMessage="MalwareScore detected the opening of a document on {hostname} on {date}"
values={{
hostname: alertDetailsData.host.hostname,
date: <FormattedDate timestamp={alertDetailsData['@timestamp']} />,
}}
/>
</p>
</EuiText>
<EuiSpacer />
<EuiText>
Endpoint Status: <EuiHealth color="success">Online</EuiHealth>
</EuiText>
<EuiText>Alert Status: Open</EuiText>
<EuiSpacer />
<TakeActionDropdown />
<EuiSpacer />
</section>
<EuiTabbedContent tabs={tabs} initialSelectedTab={tabs[0]} />
</>
);
});
</EuiText>
<EuiSpacer />
<TakeActionDropdown />
<EuiSpacer />
</section>
<EuiTabbedContent tabs={tabs} initialSelectedTab={tabs[0]} />
</>
);
})
)`
height: 100%;
width: 100%;
`;
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ import { Provider } from 'react-redux';
import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public';
import { Resolver } from '../../../../embeddables/resolver/view';
import { EndpointPluginServices } from '../../../../plugin';
import { LegacyEndpointEvent } from '../../../../../common/types';
import { ResolverEvent } from '../../../../../common/types';
import { storeFactory } from '../../../../embeddables/resolver/store';

export const AlertDetailResolver = styled(
React.memo(
({ className, selectedEvent }: { className?: string; selectedEvent?: LegacyEndpointEvent }) => {
({ className, selectedEvent }: { className?: string; selectedEvent?: ResolverEvent }) => {
const context = useKibana<EndpointPluginServices>();
const { store } = storeFactory(context);

Expand All @@ -33,4 +33,5 @@ export const AlertDetailResolver = styled(
width: 100%;
display: flex;
flex-grow: 1;
min-height: 500px;
`;
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@

import { uniquePidForProcess, uniqueParentPidForProcess } from './process_event';
import { IndexedProcessTree } from '../types';
import { LegacyEndpointEvent } from '../../../../common/types';
import { ResolverEvent } from '../../../../common/types';
import { levelOrder as baseLevelOrder } from '../lib/tree_sequencers';

/**
* Create a new IndexedProcessTree from an array of ProcessEvents
*/
export function factory(processes: LegacyEndpointEvent[]): IndexedProcessTree {
const idToChildren = new Map<number | undefined, LegacyEndpointEvent[]>();
const idToValue = new Map<number, LegacyEndpointEvent>();
export function factory(processes: ResolverEvent[]): IndexedProcessTree {
const idToChildren = new Map<string | undefined, ResolverEvent[]>();
const idToValue = new Map<string, ResolverEvent>();

for (const process of processes) {
idToValue.set(uniquePidForProcess(process), process);
Expand All @@ -36,10 +36,7 @@ export function factory(processes: LegacyEndpointEvent[]): IndexedProcessTree {
/**
* Returns an array with any children `ProcessEvent`s of the passed in `process`
*/
export function children(
tree: IndexedProcessTree,
process: LegacyEndpointEvent
): LegacyEndpointEvent[] {
export function children(tree: IndexedProcessTree, process: ResolverEvent): ResolverEvent[] {
const id = uniquePidForProcess(process);
const processChildren = tree.idToChildren.get(id);
return processChildren === undefined ? [] : processChildren;
Expand All @@ -50,8 +47,8 @@ export function children(
*/
export function parent(
tree: IndexedProcessTree,
childProcess: LegacyEndpointEvent
): LegacyEndpointEvent | undefined {
childProcess: ResolverEvent
): ResolverEvent | undefined {
const uniqueParentPid = uniqueParentPidForProcess(childProcess);
if (uniqueParentPid === undefined) {
return undefined;
Expand All @@ -74,7 +71,7 @@ export function root(tree: IndexedProcessTree) {
if (size(tree) === 0) {
return null;
}
let current: LegacyEndpointEvent = tree.idToProcess.values().next().value;
let current: ResolverEvent = tree.idToProcess.values().next().value;
while (parent(tree, current) !== undefined) {
current = parent(tree, current)!;
}
Expand Down
Loading

0 comments on commit 64af780

Please sign in to comment.