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

[Endpoint] Alert Details Overview #58412

Merged
merged 16 commits into from
Mar 3, 2020
Merged
144 changes: 119 additions & 25 deletions x-pack/plugins/endpoint/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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;
Copy link
Contributor

Choose a reason for hiding this comment

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

These fields are not going to be used by the UI, right? Anything the UI won't use should be optional or even removed from the types.

Copy link
Contributor

Choose a reason for hiding this comment

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

but we can do that in subsequent PRs

Copy link
Contributor Author

Choose a reason for hiding this comment

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

adding this to this ticket elastic/endpoint-app-team#189

};
};
} & MalwareClassifierFields;
temp_file_path: string;
};
host: HostFields;
thread: {};
endpoint?: {};
endgame?: {};
dll: DllFields[];
}>;

/**
Expand Down Expand Up @@ -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;
Copy link
Contributor

Choose a reason for hiding this comment

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

ping @nnamdifrankie - Just FYI. Does not change the type, just breaks out into smaller interfaces for reuse purposes 😃

}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,17 @@
* 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 {
readonly type: 'serverReturnedAlertsData';
readonly payload: Immutable<AlertListData>;
}

export type AlertAction = ServerReturnedAlertsData;
interface ServerReturnedAlertDetailsData {
readonly type: 'serverReturnedAlertDetailsData';
readonly payload: Immutable<AlertData>;
}

export type AlertAction = ServerReturnedAlertsData | ServerReturnedAlertDetailsData;
Original file line number Diff line number Diff line change
@@ -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<AlertListState, AppAction>;
let coreStart: ReturnType<typeof coreMock.createStart>;
let history: History<never>;
/**
* A function that waits until a selector returns true.
*/
let selectorIsTrue: (selector: (state: AlertListState) => boolean) => Promise<void>;
beforeEach(() => {
coreStart = coreMock.createStart();
history = createBrowserHistory();
const middleware = alertMiddlewareFactory(coreStart);
store = createStore(alertListReducer, applyMiddleware(middleware));

selectorIsTrue = async selector => {
Copy link
Contributor

Choose a reason for hiding this comment

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

FYI: I was going to create something similar as well - some of my Policy middleware tests started to fail in Release branch and kibana-operation turn them off. The only think I was thinking about doing that differs from this approach was that I was going to reject the Promise after some x times.

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 do agree the test is kind of brittle, i think the goal is to remove it when qualters creates a functional test for the flyout/resolver in his next pr

Copy link
Contributor

Choose a reason for hiding this comment

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

@paul-tavares we originally had it reject after 10 times, but since no additional actions are ever dispatched (no user exists after all) that limit was never hit. At the moment jest just times out after 4.5 seconds

// 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<unknown> = Promise.resolve(1);
const secondResponse: Promise<unknown> = 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);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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<AlertListState> = coreStart => {
return api => next => async (action: AppAction) => {
Expand All @@ -19,5 +19,12 @@ export const alertMiddlewareFactory: MiddlewareFactory<AlertListState> = 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}`
Copy link
Contributor

Choose a reason for hiding this comment

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

at some point - need to add error handling

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, i have a task for it in the github issue

);
api.dispatch({ type: 'serverReturnedAlertDetailsData', payload: response });
}
};
};
Loading