diff --git a/x-pack/plugins/endpoint/common/types.ts b/x-pack/plugins/endpoint/common/types.ts index 6d904fda6f747..d804350a9002d 100644 --- a/x-pack/plugins/endpoint/common/types.ts +++ b/x-pack/plugins/endpoint/common/types.ts @@ -96,6 +96,59 @@ export interface EndpointResultList { request_page_index: number; } +export interface OSFields { + full: string; + name: string; + version: string; + variant: string; +} +export interface HostFields { + id: string; + hostname: string; + ip: string[]; + mac: string[]; + os: OSFields; +} +export interface HashFields { + md5: string; + sha1: string; + sha256: string; +} +export interface MalwareClassifierFields { + identifier: string; + score: number; + threshold: number; + version: string; +} +export interface PrivilegesFields { + description: string; + name: string; + enabled: boolean; +} +export interface ThreadFields { + id: number; + service_name: string; + start: number; + start_address: number; + start_address_module: string; +} +export interface DllFields { + pe: { + architecture: string; + imphash: string; + }; + code_signature: { + subject_name: string; + trusted: boolean; + }; + compile_time: number; + hash: HashFields; + malware_classifier: MalwareClassifierFields; + mapped_address: number; + mapped_size: number; + path: string; +} + /** * Describes an Alert Event. * Should be in line with ECS schema. @@ -109,26 +162,78 @@ export type AlertEvent = Immutable<{ event: { id: string; action: string; + category: string; + kind: string; + dataset: string; + module: string; + type: string; }; - file_classification: { - malware_classification: { - score: number; + process: { + code_signature: { + subject_name: string; + trusted: boolean; }; - }; - process?: { - unique_pid: number; + command_line: string; + domain: string; pid: number; + ppid: number; + entity_id: string; + parent: { + pid: number; + entity_id: string; + }; + name: string; + hash: HashFields; + pe: { + imphash: string; + }; + executable: string; + sid: string; + start: number; + malware_classifier: MalwareClassifierFields; + token: { + domain: string; + type: string; + user: string; + sid: string; + integrity_level: number; + integrity_level_name: string; + privileges: PrivilegesFields[]; + }; + thread: ThreadFields[]; + uptime: number; + user: string; }; - host: { - hostname: string; - ip: string; - os: { - name: string; + file: { + owner: string; + name: string; + path: string; + accessed: number; + mtime: number; + created: number; + size: number; + hash: HashFields; + pe: { + imphash: string; + }; + code_signature: { + trusted: boolean; + subject_name: string; }; + malware_classifier: { + features: { + data: { + buffer: string; + decompressed_size: number; + encoding: string; + }; + }; + } & MalwareClassifierFields; + temp_file_path: string; }; + host: HostFields; thread: {}; - endpoint?: {}; - endgame?: {}; + dll: DllFields[]; }>; /** @@ -161,18 +266,7 @@ export interface EndpointMetadata { id: string; name: string; }; - host: { - id: string; - hostname: string; - ip: string[]; - mac: string[]; - os: { - name: string; - full: string; - version: string; - variant: string; - }; - }; + host: HostFields; } /** diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/action.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/action.ts index a628a95003a7f..6c6310a7349ed 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/action.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/action.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Immutable } from '../../../../../common/types'; +import { Immutable, AlertData } from '../../../../../common/types'; import { AlertListData } from '../../types'; interface ServerReturnedAlertsData { @@ -12,4 +12,9 @@ interface ServerReturnedAlertsData { readonly payload: Immutable; } -export type AlertAction = ServerReturnedAlertsData; +interface ServerReturnedAlertDetailsData { + readonly type: 'serverReturnedAlertDetailsData'; + readonly payload: Immutable; +} + +export type AlertAction = ServerReturnedAlertsData | ServerReturnedAlertDetailsData; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/alert_details.test.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/alert_details.test.ts new file mode 100644 index 0000000000000..4edc31831eb14 --- /dev/null +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/alert_details.test.ts @@ -0,0 +1,65 @@ +/* + * 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 { Store, createStore, applyMiddleware } from 'redux'; +import { History } from 'history'; +import { alertListReducer } from './reducer'; +import { AlertListState } from '../../types'; +import { alertMiddlewareFactory } from './middleware'; +import { AppAction } from '../action'; +import { coreMock } from 'src/core/public/mocks'; +import { createBrowserHistory } from 'history'; + +describe('alert details tests', () => { + let store: Store; + let coreStart: ReturnType; + let history: History; + /** + * A function that waits until a selector returns true. + */ + let selectorIsTrue: (selector: (state: AlertListState) => boolean) => Promise; + beforeEach(() => { + coreStart = coreMock.createStart(); + history = createBrowserHistory(); + const middleware = alertMiddlewareFactory(coreStart); + store = createStore(alertListReducer, applyMiddleware(middleware)); + + selectorIsTrue = async selector => { + // If the selector returns true, we're done + while (selector(store.getState()) !== true) { + // otherwise, wait til the next state change occurs + await new Promise(resolve => { + const unsubscribe = store.subscribe(() => { + unsubscribe(); + resolve(); + }); + }); + } + }; + }); + describe('when the user is on the alert list page with a selected alert in the url', () => { + beforeEach(() => { + const firstResponse: Promise = Promise.resolve(1); + const secondResponse: Promise = Promise.resolve(2); + coreStart.http.get.mockReturnValueOnce(firstResponse).mockReturnValueOnce(secondResponse); + + // Simulates user navigating to the /alerts page + store.dispatch({ + type: 'userChangedUrl', + payload: { + ...history.location, + pathname: '/alerts', + search: '?selected_alert=q9ncfh4q9ctrmc90umcq4', + }, + }); + }); + + it('should return alert details data', async () => { + // wait for alertDetails to be defined + await selectorIsTrue(state => state.alertDetails !== undefined); + }); + }); +}); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/middleware.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/middleware.ts index 76a6867418bd8..2cb381e901b4e 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/middleware.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/middleware.ts @@ -4,10 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { AlertResultList } from '../../../../../common/types'; +import { AlertResultList, AlertData } from '../../../../../common/types'; import { AppAction } from '../action'; import { MiddlewareFactory, AlertListState } from '../../types'; -import { isOnAlertPage, apiQueryParams } from './selectors'; +import { isOnAlertPage, apiQueryParams, hasSelectedAlert, uiQueryParams } from './selectors'; export const alertMiddlewareFactory: MiddlewareFactory = coreStart => { return api => next => async (action: AppAction) => { @@ -19,5 +19,12 @@ export const alertMiddlewareFactory: MiddlewareFactory = coreSta }); api.dispatch({ type: 'serverReturnedAlertsData', payload: response }); } + if (action.type === 'userChangedUrl' && isOnAlertPage(state) && hasSelectedAlert(state)) { + const uiParams = uiQueryParams(state); + const response: AlertData = await coreStart.http.get( + `/api/endpoint/alerts/${uiParams.selected_alert}` + ); + api.dispatch({ type: 'serverReturnedAlertDetailsData', payload: response }); + } }; }; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/mock_alert_result_list.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/mock_alert_result_list.ts index 8eadb3e7fb3df..7db94fc9d4266 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/mock_alert_result_list.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/mock_alert_result_list.ts @@ -32,29 +32,152 @@ export const mockAlertResultList: (options?: { id: 'ced9c68e-b94a-4d66-bb4c-6106514f0a2f', version: '3.0.0', }, - event: { - id: '2f1c0928-3876-4e11-acbb-9199257c7b1c', - action: 'open', - }, - file_classification: { - malware_classification: { - score: 3, - }, - }, - process: { - pid: 107, - unique_pid: 1, - }, host: { + id: 'xrctvybuni', hostname: 'HD-c15-bc09190a', - ip: '10.179.244.14', + ip: ['10.179.244.14'], + mac: ['xsertcyvbunimkn56edtyf'], os: { - name: 'Windows', + full: 'Windows 10', + name: 'windows', + version: '10', + variant: '3', }, }, thread: {}, prev: null, next: null, + event: { + id: '2f1c0928-3876-4e11-acbb-9199257c7b1c', + action: 'creation', + category: 'malware', + dataset: 'endpoint', + kind: 'alert', + module: 'endpoint', + type: 'creation', + }, + file: { + accessed: 1542789400, + created: 1542789400, + hash: { + md5: '4ace3baaa509d08510405e1b169e325b', + sha1: '27fb21cf5db95ffca43b234affa99becc4023b9d', + sha256: '6ed1c836dbf099be7845bdab7671def2c157643761b52251e04e9b6ee109ec75', + }, + pe: { + imphash: '835d619dfdf3cc727cebd91300ab3462', + }, + mtime: 1542789400, + owner: 'Administrators', + name: 'test name', + path: 'C:\\Windows\\TEMP\\tmp0000008f\\tmp00001be5', + size: 188416, + code_signature: { + subject_name: 'Cybereason Inc', + trusted: false, + }, + malware_classifier: { + features: { + data: { + buffer: + 'eAHtnU1oHHUUwHsQ7MGDiIIUD4sH8WBBxJtopiLoUY0pYo2ZTbJJ0yQ17m4+ms/NRzeVWpuUWCL4sWlEYvFQ8KJQ6NCTEA8eRD30sIo3PdSriLi7837Pko3LbHZ2M5m+XObHm/d/X////83O7jCZvzacHBpPplNdfalkdjSdyty674Ft59dN71Dpb9v5eKh8LMEHjsCF2wIfVlRKsHROYPGkQO5+gY2vBSYYdWZFYGwEO/cITHMqkxPYnBBY+07gtCuQ9gSGigJ5lPPYGXcE+jA4z3Ad1ZtAUiDUyrEEPYzqRnIKgxd/Rgc7gygPo5wn95PouN7OeEYJ1UXiJgRmvscgp/LOziIkkSyT+xRVnXhZ4DKh5goCkzidRHkGO4uvCyw9LDDtCay8ILCAzrJOJaGuZwUuvSewivJVIPsklq8JbL4qMJsTSCcExrGs83WKU295ZFo5lr2TaZbcUw5FeJy8tgTeLpCy2iGeS67ABXzlgbEi1UC5FxcZnA4y/CLK82Qxi847FGGZRTLsCUxR1aWEwOp1AmOjDRYYzgwusL9WfqBiGJxnVAanixTq7Dp22LBdlWMJzlOx8wmBK2Rx5WmBLJIRwtAijOQE+ooCb2B5xBOYRtlfNeXpLpA7oyZRTqHzGenkmIJPnhBIMrzTwSA6H93CO5l+c1NA99f6IwLH8fUKdjTmDpTbgS50+gGVnECnE4PpooC2guPoaPADSHrcncNHmEHtAFkq3+EI+A37zsrrTvH3WTkvJLoOTyBp10wx2JcgVCRahA4NrICE4a+hrMXsA3qAHItW188E8ejO7XV3eh/KCYwxlamEwCgL8lN2wTntfrhY/U0g/5KAdvUpT+AszWqBdqH7VLeeZrExK9Cv1UgIDKA8g/cx7QAEP+AhAfRaMKB2HOJh+BSFSqKjSytNGBlc6PrpxvK7lCVDxbSG3Z7AhCMwx6gelwgLAltXBXJUTH29j+U1LHdipx/QprfKfGnF0sBpdBYxmEQyTzW0h6/0khcuhhJYRufym+i4VKMocJMs/KvfoW3/UJb4PeZOSZVONThZz4djP/75TAXa/CVfOvX3RgVLIDreLPN1pP1osW7lGmHsEhjBOzf+EPBE4vndvWz5xb/cChxGcv1LAb+tluALKnZ47isf1MXvz1ZMlsCXbXtPceqhrcp1ps6YHwQeBXLEPCf7q23tl9uJui0bGBgYRAccv7uXr/g5Af+2oNTrpgTa/vnpjBvpLAwM4gRBPvIZGBgYGBgYGBgYGBgYGBgYGBgYGBgYNAOc9oMXs4GBgYFBcNBnww5QzDXgRtPSaZ5lg/itsRaslgZ3bnWEEVnhMetIBwiiVnlbCbWrEftrt11zdwWnseFW1QO63w1is3ptD1pV9xG0t+zvfUrzrvh380qwXWAVCw6h78GIfG7ZlzltXu6hd+y92fECRFhjuH3bXG8N43oXEHperdzvUbteaDxhVTUeq25fqhG1X6Ai8mtF6BDXz2wR+dzSgg4Qsxls5T11XMG+82y8GkG+b7kL69xg7mF1SFvhBgYGsYH/Xi7HE+PVkiB2jt1bNZxT+k4558jR53ydz5//1m1KOgYGBgYGBgYGEQfnsYaG2z1sdPJS79XQSu91ndobOAHCaN5vNzUk1bceQVzUpbw3iOuT+UFmR18bHrp3gyhDC56lCd1y85w2+HSNUwVhhdGC7blLf+bV/fqtvhMg1NDjCcugB1QXswbs8ekj/v1BgzFHBIIsyP+HfwFdMpzu', + decompressed_size: 27831, + encoding: 'zlib', + }, + }, + identifier: 'endpointpe', + score: 1, + threshold: 0.66, + version: '3.0.33', + }, + temp_file_path: 'C:\\Windows\\TEMP\\1bb9abfc-ca14-47b2-9f2c-10c323df42f9', + }, + process: { + pid: 1076, + ppid: 432, + entity_id: 'wertqwer', + parent: { + pid: 432, + entity_id: 'adsfsdaf', + }, + name: 'test name', + code_signature: { + subject_name: 'Cybereason Inc', + trusted: true, + }, + command_line: '"C:\\Program Files\\Cybereason ActiveProbe\\AmSvc.exe"', + domain: 'NT AUTHORITY', + executable: 'C:\\Program Files\\Cybereason ActiveProbe\\AmSvc.exe', + hash: { + md5: '1f2d082566b0fc5f2c238a5180db7451', + sha1: 'ca85243c0af6a6471bdaa560685c51eefd6dbc0d', + sha256: '8ad40c90a611d36eb8f9eb24fa04f7dbca713db383ff55a03aa0f382e92061a2', + }, + pe: { + imphash: 'c30d230b81c734e82e86e2e2fe01cd01', + }, + malware_classifier: { + identifier: 'Whitelisted', + score: 0, + threshold: 0, + version: '3.0.0', + }, + thread: [ + { + id: 1652, + service_name: 'CybereasonAntiMalware', + start: 1542788400, + start_address: 8791698721056, + start_address_module: 'C:\\Program Files\\Cybereason ActiveProbe\\gzfltum.dll', + }, + ], + sid: 'S-1-5-18', + start: 1542788400, + token: { + domain: 'NT AUTHORITY', + integrity_level: 16384, + integrity_level_name: 'system', + privileges: [ + { + description: 'Replace a process level token', + enabled: false, + name: 'SeAssignPrimaryTokenPrivilege', + }, + ], + sid: 'S-1-5-18', + type: 'tokenPrimary', + user: 'SYSTEM', + }, + uptime: 1025, + user: 'SYSTEM', + }, + dll: [ + { + pe: { + architecture: 'x64', + imphash: 'c30d230b81c734e82e86e2e2fe01cd01', + }, + code_signature: { + subject_name: 'Cybereason Inc', + trusted: true, + }, + compile_time: 1534424710, + hash: { + md5: '1f2d082566b0fc5f2c238a5180db7451', + sha1: 'ca85243c0af6a6471bdaa560685c51eefd6dbc0d', + sha256: '8ad40c90a611d36eb8f9eb24fa04f7dbca713db383ff55a03aa0f382e92061a2', + }, + malware_classifier: { + identifier: 'Whitelisted', + score: 0, + threshold: 0, + version: '3.0.0', + }, + mapped_address: 5362483200, + mapped_size: 0, + path: 'C:\\Program Files\\Cybereason ActiveProbe\\AmSvc.exe', + }, + ], }); } const mock: AlertResultList = { diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/reducer.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/reducer.ts index 77d7397d72581..ee172fa80f1fe 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/reducer.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/reducer.ts @@ -11,6 +11,7 @@ import { AppAction } from '../action'; const initialState = (): AlertListState => { return { alerts: [], + alertDetails: undefined, pageSize: 10, pageIndex: 0, total: 0, @@ -43,6 +44,11 @@ export const alertListReducer: Reducer = ( ...state, location: action.payload, }; + } else if (action.type === 'serverReturnedAlertDetailsData') { + return { + ...state, + alertDetails: action.payload, + }; } return state; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/selectors.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/selectors.ts index f217e3cda9191..7ce7c2d08691e 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/selectors.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/selectors.ts @@ -15,7 +15,7 @@ import { AlertsAPIQueryParams, CreateStructuredSelector, } from '../../types'; -import { Immutable, LegacyEndpointEvent } from '../../../../../common/types'; +import { Immutable } from '../../../../../common/types'; const createStructuredSelector: CreateStructuredSelector = createStructuredSelectorWithBadType; /** @@ -23,6 +23,8 @@ const createStructuredSelector: CreateStructuredSelector = createStructuredSelec */ export const alertListData = (state: AlertListState) => state.alerts; +export const selectedAlertDetailsData = (state: AlertListState) => state.alertDetails; + /** * Returns the alert list pagination data from state */ @@ -96,20 +98,11 @@ export const hasSelectedAlert: (state: AlertListState) => boolean = createSelect /** * Determine if the alert event is most likely compatible with LegacyEndpointEvent. */ -function isAlertEventLegacyEndpointEvent(event: { endgame?: {} }): event is LegacyEndpointEvent { - return event.endgame !== undefined && 'unique_pid' in event.endgame; -} - -export const selectedEvent: ( +export const selectedAlertIsLegacyEndpointEvent: ( state: AlertListState -) => LegacyEndpointEvent | undefined = createSelector( - uiQueryParams, - alertListData, - ({ selected_alert: selectedAlert }, alertList) => { - const found = alertList.find(alert => alert.event.id === selectedAlert); - if (!found) { - return found; - } - return isAlertEventLegacyEndpointEvent(found) ? found : undefined; +) => boolean = createSelector(selectedAlertDetailsData, function(event) { + if (event === undefined) { + return false; } -); + return 'endgame' in event; +}); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/types.ts b/x-pack/plugins/endpoint/public/applications/endpoint/types.ts index 6498462a8fc06..b46785d3190e5 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/types.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/types.ts @@ -93,19 +93,22 @@ export type AlertListData = AlertResultList; export interface AlertListState { /** Array of alert items. */ - alerts: ImmutableArray; + readonly alerts: ImmutableArray; /** The total number of alerts on the page. */ - total: number; + readonly total: number; /** Number of alerts per page. */ - pageSize: number; + readonly pageSize: number; /** Page number, starting at 0. */ - pageIndex: number; + readonly pageIndex: number; /** Current location object from React Router history. */ readonly location?: Immutable; + + /** Specific Alert data to be shown in the details view */ + readonly alertDetails?: Immutable; } /** diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/details/index.ts b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/details/index.ts new file mode 100644 index 0000000000000..1c78309474737 --- /dev/null +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/details/index.ts @@ -0,0 +1,7 @@ +/* + * 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. + */ + +export { AlertDetailsOverview } from './overview'; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/details/metadata/file_accordion.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/details/metadata/file_accordion.tsx new file mode 100644 index 0000000000000..ac67e54f38779 --- /dev/null +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/details/metadata/file_accordion.tsx @@ -0,0 +1,80 @@ +/* + * 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 React, { memo, useMemo } from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiAccordion, EuiDescriptionList } from '@elastic/eui'; +import { Immutable, AlertData } from '../../../../../../../common/types'; +import { FormattedDate } from '../../formatted_date'; + +export const FileAccordion = memo(({ alertData }: { alertData: Immutable }) => { + const columns = useMemo(() => { + return [ + { + title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.fileName', { + defaultMessage: 'File Name', + }), + description: alertData.file.name, + }, + { + title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.filePath', { + defaultMessage: 'File Path', + }), + description: alertData.file.path, + }, + { + title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.fileSize', { + defaultMessage: 'File Size', + }), + description: alertData.file.size, + }, + { + title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.fileCreated', { + defaultMessage: 'File Created', + }), + description: , + }, + { + title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.fileModified', { + defaultMessage: 'File Modified', + }), + description: , + }, + { + title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.fileAccessed', { + defaultMessage: 'File Accessed', + }), + description: , + }, + { + title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.signer', { + defaultMessage: 'Signer', + }), + description: alertData.file.code_signature.subject_name, + }, + { + title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.owner', { + defaultMessage: 'Owner', + }), + description: alertData.file.owner, + }, + ]; + }, [alertData]); + + return ( + + + + ); +}); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/details/metadata/general_accordion.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/details/metadata/general_accordion.tsx new file mode 100644 index 0000000000000..070c78c968585 --- /dev/null +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/details/metadata/general_accordion.tsx @@ -0,0 +1,68 @@ +/* + * 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 React, { memo, useMemo } from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiAccordion, EuiDescriptionList } from '@elastic/eui'; +import { Immutable, AlertData } from '../../../../../../../common/types'; +import { FormattedDate } from '../../formatted_date'; + +export const GeneralAccordion = memo(({ alertData }: { alertData: Immutable }) => { + const columns = useMemo(() => { + return [ + { + title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.alertType', { + defaultMessage: 'Alert Type', + }), + description: alertData.event.category, + }, + { + title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.eventType', { + defaultMessage: 'Event Type', + }), + description: alertData.event.kind, + }, + { + title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.status', { + defaultMessage: 'Status', + }), + description: 'TODO', + }, + { + title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.dateCreated', { + defaultMessage: 'Date Created', + }), + description: , + }, + { + title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.malwareScore', { + defaultMessage: 'MalwareScore', + }), + description: alertData.file.malware_classifier.score, + }, + { + title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.fileName', { + defaultMessage: 'File Name', + }), + description: alertData.file.name, + }, + ]; + }, [alertData]); + return ( + + + + ); +}); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/details/metadata/hash_accordion.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/details/metadata/hash_accordion.tsx new file mode 100644 index 0000000000000..b2be083ce8f59 --- /dev/null +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/details/metadata/hash_accordion.tsx @@ -0,0 +1,49 @@ +/* + * 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 React, { memo, useMemo } from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiAccordion, EuiDescriptionList } from '@elastic/eui'; +import { Immutable, AlertData } from '../../../../../../../common/types'; + +export const HashAccordion = memo(({ alertData }: { alertData: Immutable }) => { + const columns = useMemo(() => { + return [ + { + title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.md5', { + defaultMessage: 'MD5', + }), + description: alertData.file.hash.md5, + }, + { + title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.sha1', { + defaultMessage: 'SHA1', + }), + description: alertData.file.hash.sha1, + }, + { + title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.sha256', { + defaultMessage: 'SHA256', + }), + description: alertData.file.hash.sha256, + }, + ]; + }, [alertData]); + + return ( + + + + ); +}); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/details/metadata/host_accordion.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/details/metadata/host_accordion.tsx new file mode 100644 index 0000000000000..4108781f0a79b --- /dev/null +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/details/metadata/host_accordion.tsx @@ -0,0 +1,55 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React, { memo, useMemo } from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiAccordion, EuiDescriptionList } from '@elastic/eui'; +import { Immutable, AlertData } from '../../../../../../../common/types'; + +export const HostAccordion = memo(({ alertData }: { alertData: Immutable }) => { + const columns = useMemo(() => { + return [ + { + title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.hostName', { + defaultMessage: 'Host Name', + }), + description: alertData.host.hostname, + }, + { + title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.hostIP', { + defaultMessage: 'Host IP', + }), + description: alertData.host.ip.join(', '), + }, + { + title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.status', { + defaultMessage: 'Status', + }), + description: 'TODO', + }, + { + title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.os', { + defaultMessage: 'OS', + }), + description: alertData.host.os.name, + }, + ]; + }, [alertData]); + + return ( + + + + ); +}); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/details/metadata/index.ts b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/details/metadata/index.ts new file mode 100644 index 0000000000000..1eb755242d701 --- /dev/null +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/details/metadata/index.ts @@ -0,0 +1,12 @@ +/* + * 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. + */ + +export { GeneralAccordion } from './general_accordion'; +export { HostAccordion } from './host_accordion'; +export { HashAccordion } from './hash_accordion'; +export { FileAccordion } from './file_accordion'; +export { SourceProcessAccordion } from './source_process_accordion'; +export { SourceProcessTokenAccordion } from './source_process_token_accordion'; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/details/metadata/source_process_accordion.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/details/metadata/source_process_accordion.tsx new file mode 100644 index 0000000000000..4c961ad4b4964 --- /dev/null +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/details/metadata/source_process_accordion.tsx @@ -0,0 +1,97 @@ +/* + * 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 React, { memo, useMemo } from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiAccordion, EuiDescriptionList } from '@elastic/eui'; +import { Immutable, AlertData } from '../../../../../../../common/types'; + +export const SourceProcessAccordion = memo(({ alertData }: { alertData: Immutable }) => { + const columns = useMemo(() => { + return [ + { + title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.processID', { + defaultMessage: 'Process ID', + }), + description: alertData.process.pid, + }, + { + title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.processName', { + defaultMessage: 'Process Name', + }), + description: alertData.process.name, + }, + { + title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.processPath', { + defaultMessage: 'Process Path', + }), + description: alertData.process.executable, + }, + { + title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.md5', { + defaultMessage: 'MD5', + }), + description: alertData.process.hash.md5, + }, + { + title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.sha1', { + defaultMessage: 'SHA1', + }), + description: alertData.process.hash.sha1, + }, + { + title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.sha256', { + defaultMessage: 'SHA256', + }), + description: alertData.process.hash.sha256, + }, + { + title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.malwareScore', { + defaultMessage: 'MalwareScore', + }), + description: alertData.process.malware_classifier.score, + }, + { + title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.parentProcessID', { + defaultMessage: 'Parent Process ID', + }), + description: alertData.process.parent.pid, + }, + { + title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.signer', { + defaultMessage: 'Signer', + }), + description: alertData.process.code_signature.subject_name, + }, + { + title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.username', { + defaultMessage: 'Username', + }), + description: alertData.process.token.user, + }, + { + title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.domain', { + defaultMessage: 'Domain', + }), + description: alertData.process.token.domain, + }, + ]; + }, [alertData]); + + return ( + + + + ); +}); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/details/metadata/source_process_token_accordion.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/details/metadata/source_process_token_accordion.tsx new file mode 100644 index 0000000000000..7d75d4478afb3 --- /dev/null +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/details/metadata/source_process_token_accordion.tsx @@ -0,0 +1,45 @@ +/* + * 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 React, { memo, useMemo } from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiAccordion, EuiDescriptionList } from '@elastic/eui'; +import { Immutable, AlertData } from '../../../../../../../common/types'; + +export const SourceProcessTokenAccordion = memo( + ({ alertData }: { alertData: Immutable }) => { + const columns = useMemo(() => { + return [ + { + title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.sid', { + defaultMessage: 'SID', + }), + description: alertData.process.token.sid, + }, + { + title: i18n.translate('xpack.endpoint.application.endpoint.alertDetails.integrityLevel', { + defaultMessage: 'Integrity Level', + }), + description: alertData.process.token.integrity_level, + }, + ]; + }, [alertData]); + + return ( + + + + ); + } +); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/details/overview/index.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/details/overview/index.tsx new file mode 100644 index 0000000000000..080c70ca43bae --- /dev/null +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/details/overview/index.tsx @@ -0,0 +1,94 @@ +/* + * 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 React, { memo, useMemo } from 'react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiSpacer, EuiTitle, EuiText, EuiHealth, EuiTabbedContent } from '@elastic/eui'; +import { useAlertListSelector } from '../../hooks/use_alerts_selector'; +import * as selectors from '../../../../store/alerts/selectors'; +import { MetadataPanel } from './metadata_panel'; +import { FormattedDate } from '../../formatted_date'; +import { AlertDetailResolver } from '../../resolver'; + +export const AlertDetailsOverview = memo(() => { + const alertDetailsData = useAlertListSelector(selectors.selectedAlertDetailsData); + if (alertDetailsData === undefined) { + return null; + } + const selectedAlertIsLegacyEndpointEvent = useAlertListSelector( + selectors.selectedAlertIsLegacyEndpointEvent + ); + + const tabs = useMemo(() => { + return [ + { + id: 'overviewMetadata', + name: i18n.translate( + 'xpack.endpoint.application.endpoint.alertDetails.overview.tabs.overview', + { + defaultMessage: 'Overview', + } + ), + content: ( + <> + + + + ), + }, + { + id: 'overviewResolver', + name: i18n.translate( + 'xpack.endpoint.application.endpoint.alertDetails.overview.tabs.resolver', + { + defaultMessage: 'Resolver', + } + ), + content: ( + <> + + {selectedAlertIsLegacyEndpointEvent && } + + ), + }, + ]; + }, [selectedAlertIsLegacyEndpointEvent]); + + return ( + <> +
+ +

+ +

+
+ + +

+ , + }} + /> +

+
+ + + Endpoint Status: Online + + Alert Status: Open + +
+ + + ); +}); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/details/overview/metadata_panel.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/details/overview/metadata_panel.tsx new file mode 100644 index 0000000000000..556d7bea2e310 --- /dev/null +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/details/overview/metadata_panel.tsx @@ -0,0 +1,40 @@ +/* + * 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 React, { memo } from 'react'; +import { EuiSpacer } from '@elastic/eui'; +import { useAlertListSelector } from '../../hooks/use_alerts_selector'; +import * as selectors from '../../../../store/alerts/selectors'; +import { + GeneralAccordion, + HostAccordion, + HashAccordion, + FileAccordion, + SourceProcessAccordion, + SourceProcessTokenAccordion, +} from '../metadata'; + +export const MetadataPanel = memo(() => { + const alertDetailsData = useAlertListSelector(selectors.selectedAlertDetailsData); + if (alertDetailsData === undefined) { + return null; + } + return ( +
+ + + + + + + + + + + +
+ ); +}); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/formatted_date.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/formatted_date.tsx new file mode 100644 index 0000000000000..731bd31b26cef --- /dev/null +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/formatted_date.tsx @@ -0,0 +1,22 @@ +/* + * 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 React, { memo } from 'react'; +import { FormattedDate as ReactIntlFormattedDate } from '@kbn/i18n/react'; + +export const FormattedDate = memo(({ timestamp }: { timestamp: number }) => { + const date = new Date(timestamp); + return ( + + ); +}); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/index.test.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/index.test.tsx index fe362f21a178e..aae44824c3164 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/index.test.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/index.test.tsx @@ -140,9 +140,6 @@ describe('when on the alerting page', () => { it('should show the flyout', async () => { await render().findByTestId('alertDetailFlyout'); }); - it('should render resolver', async () => { - await render().findByTestId('alertResolver'); - }); describe('when the user clicks the close button on the flyout', () => { let renderResult: reactTestingLibrary.RenderResult; beforeEach(async () => { diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/index.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/index.tsx index 3c229484ede4e..5d405f8c6fbae 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/index.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/index.tsx @@ -17,15 +17,21 @@ import { EuiFlyoutBody, EuiTitle, EuiBadge, + EuiLoadingSpinner, + EuiPageContentHeader, + EuiPageContentHeaderSection, + EuiPageContentBody, + EuiLink, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { useHistory, Link } from 'react-router-dom'; -import { FormattedDate } from 'react-intl'; +import { useHistory } from 'react-router-dom'; +import { FormattedMessage } from '@kbn/i18n/react'; import { urlFromQueryParams } from './url_from_query_params'; import { AlertData } from '../../../../../common/types'; import * as selectors from '../../store/alerts/selectors'; import { useAlertListSelector } from './hooks/use_alerts_selector'; -import { AlertDetailResolver } from './resolver'; +import { AlertDetailsOverview } from './details'; +import { FormattedDate } from './formatted_date'; export const AlertIndex = memo(() => { const history = useHistory(); @@ -87,7 +93,6 @@ export const AlertIndex = memo(() => { const alertListData = useAlertListSelector(selectors.alertListData); const hasSelectedAlert = useAlertListSelector(selectors.hasSelectedAlert); const queryParams = useAlertListSelector(selectors.uiQueryParams); - const selectedEvent = useAlertListSelector(selectors.selectedEvent); const onChangeItemsPerPage = useCallback( newPageSize => { @@ -119,10 +124,10 @@ export const AlertIndex = memo(() => { history.push(urlFromQueryParams(paramsWithoutSelectedAlert)); }, [history, queryParams]); - const datesForRows: Map = useMemo(() => { + const timestampForRows: Map = useMemo(() => { return new Map( alertListData.map(alertData => { - return [alertData, new Date(alertData['@timestamp'])]; + return [alertData, alertData['@timestamp']]; }) ); }, [alertListData]); @@ -136,9 +141,11 @@ export const AlertIndex = memo(() => { const row = alertListData[rowIndex % pageSize]; if (columnId === 'alert_type') { return ( - + history.push(urlFromQueryParams({ ...queryParams, selected_alert: row.id })) + } > {i18n.translate( 'xpack.endpoint.application.endpoint.alerts.alertType.maliciousFileDescription', @@ -146,7 +153,7 @@ export const AlertIndex = memo(() => { defaultMessage: 'Malicious File', } )} - + ); } else if (columnId === 'event_type') { return row.event.action; @@ -157,19 +164,9 @@ export const AlertIndex = memo(() => { } else if (columnId === 'host_name') { return row.host.hostname; } else if (columnId === 'timestamp') { - const date = datesForRows.get(row)!; - if (date && isFinite(date.getTime())) { - return ( - - ); + const timestamp = timestampForRows.get(row)!; + if (timestamp) { + return ; } else { return ( @@ -185,11 +182,11 @@ export const AlertIndex = memo(() => { } else if (columnId === 'archived') { return null; } else if (columnId === 'malware_score') { - return row.file_classification.malware_classification.score; + return row.file.malware_classifier.score; } return null; }; - }, [alertListData, datesForRows, pageSize, queryParams, total]); + }, [total, alertListData, pageSize, history, queryParams, timestampForRows]); const pagination = useMemo(() => { return { @@ -201,6 +198,16 @@ export const AlertIndex = memo(() => { }; }, [onChangeItemsPerPage, onChangePage, pageIndex, pageSize]); + const columnVisibility = useMemo( + () => ({ + visibleColumns, + setVisibleColumns, + }), + [setVisibleColumns, visibleColumns] + ); + + const selectedAlertData = useAlertListSelector(selectors.selectedAlertDetailsData); + return ( <> {hasSelectedAlert && ( @@ -215,29 +222,37 @@ export const AlertIndex = memo(() => { - + {selectedAlertData ? : } )} - ({ - visibleColumns, - setVisibleColumns, - }), - [setVisibleColumns, visibleColumns] - )} - renderCellValue={renderCellValue} - pagination={pagination} - data-test-subj="alertListGrid" - data-testid="alertListGrid" - /> + + + +

+ +

+
+
+
+ + +
diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/resolver.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/resolver.tsx index c7ef7f73dfe05..ec1dab45d50ab 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/resolver.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/alerts/resolver.tsx @@ -18,6 +18,7 @@ export const AlertDetailResolver = styled( ({ className, selectedEvent }: { className?: string; selectedEvent?: LegacyEndpointEvent }) => { const context = useKibana(); const { store } = storeFactory(context); + return (