Skip to content

Commit 68a1773

Browse files
[Security Solution][Endpoint] Actions Log API (#101032)
* WIP add tabs for endpoint details * fetch activity log for endpoint this is work in progress with dummy data * refactor to hold host details and activity log within endpointDetails * api for fetching actions log * add a selector for getting selected agent id * use the new api to show actions log * review changes * move util function to common/utils in order to use it in endpoint_hosts as well as in trusted _apps review suggestion * use util function to get API path review suggestion * sync url params with details active tab review suggestion * fix types due to merge commit refs 3722552 * use AsyncResourseState type review suggestions * sort entries chronologically with recent at the top * adjust icon sizes within entries to match mocks * remove endpoint list paging stuff (not for now) * fix import after sync with master * make the search bar work (sort of) this needs to be fleshed out in a later PR * add tests to middleware for now * use snake case for naming routes review changes * rename and use own relative time function review change * use euiTheme tokens review change * add a comment review changes * log errors to kibana log and unwind stack review changes * search on two indices * fix types * use modified data * distinguish between responses and actions and respective states in UI * use indices explicitly and tune the query * fix types after sync with master * fix lint * do better types review suggestion * add paging to API call * add paging info to redux store for activityLog * decouple paging action from other API requests * use a button for now to fetch more data * add index to fleet indices else we get a type check error about the constant not being exported correctly from `x-pack/plugins/fleet/common/constants/agent` * add tests for audit log API * do semantic paging from first request * fix ts error review changes * add document id and total to API review suggestions * update test * update frontend to consume the modified api correctly * update mock * rename action review changes * wrap mock into function to create anew on each test review changes * wrap with schema.maybe and increase page size review changes * ignore 404 review changes * use i18n review changes * abstract logEntry component logic review changes * move handler logic to a service review changes * update response object review changes * fix paging to use 50 as initial fetch size * fix translations and move custom hook to component file review changes * add return type review changes * update default value for page_size review changes * remove default values review changes https://github.com/elastic/kibana/tree/master/packages/kbn-config-schema#schemamaybe https://github.com/elastic/kibana/tree/master/packages/kbn-config-schema#default-values * fix mock data refs 1f9ae70 * add selectors for data review changes Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
1 parent d700291 commit 68a1773

22 files changed

Lines changed: 919 additions & 183 deletions

File tree

x-pack/plugins/fleet/common/constants/agent.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,4 @@ export const AGENT_POLICY_ROLLOUT_RATE_LIMIT_REQUEST_PER_INTERVAL = 5;
2525

2626
export const AGENTS_INDEX = '.fleet-agents';
2727
export const AGENT_ACTIONS_INDEX = '.fleet-actions';
28+
export const AGENT_ACTIONS_RESULTS_INDEX = '.fleet-actions-results';

x-pack/plugins/fleet/common/constants/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ export const FLEET_SERVER_SERVERS_INDEX = '.fleet-servers';
3131

3232
export const FLEET_SERVER_INDICES = [
3333
'.fleet-actions',
34+
'.fleet-actions-results',
3435
'.fleet-agents',
3536
FLEET_SERVER_ARTIFACTS_INDEX,
3637
'.fleet-enrollment-api-keys',

x-pack/plugins/security_solution/common/endpoint/schema/actions.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
* 2.0.
66
*/
77

8-
import { schema } from '@kbn/config-schema';
8+
import { schema, TypeOf } from '@kbn/config-schema';
99

1010
export const HostIsolationRequestSchema = {
1111
body: schema.object({
@@ -22,13 +22,18 @@ export const HostIsolationRequestSchema = {
2222
};
2323

2424
export const EndpointActionLogRequestSchema = {
25-
// TODO improve when using pagination with query params
26-
query: schema.object({}),
25+
query: schema.object({
26+
page: schema.number({ defaultValue: 1, min: 1 }),
27+
page_size: schema.number({ defaultValue: 10, min: 1, max: 100 }),
28+
}),
2729
params: schema.object({
2830
agent_id: schema.string(),
2931
}),
3032
};
3133

34+
export type EndpointActionLogRequestParams = TypeOf<typeof EndpointActionLogRequestSchema.params>;
35+
export type EndpointActionLogRequestQuery = TypeOf<typeof EndpointActionLogRequestSchema.query>;
36+
3237
export const ActionStatusRequestSchema = {
3338
query: schema.object({
3439
agent_ids: schema.oneOf([

x-pack/plugins/security_solution/common/endpoint/types/actions.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,32 @@ export interface EndpointActionResponse {
3838
action_data: EndpointActionData;
3939
}
4040

41+
export interface ActivityLogAction {
42+
type: 'action';
43+
item: {
44+
// document _id
45+
id: string;
46+
// document _source
47+
data: EndpointAction;
48+
};
49+
}
50+
export interface ActivityLogActionResponse {
51+
type: 'response';
52+
item: {
53+
// document id
54+
id: string;
55+
// document _source
56+
data: EndpointActionResponse;
57+
};
58+
}
59+
export type ActivityLogEntry = ActivityLogAction | ActivityLogActionResponse;
60+
export interface ActivityLog {
61+
total: number;
62+
page: number;
63+
pageSize: number;
64+
data: ActivityLogEntry[];
65+
}
66+
4167
export type HostIsolationRequestBody = TypeOf<typeof HostIsolationRequestSchema.body>;
4268

4369
export interface HostIsolationResponse {

x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/action.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,15 +148,23 @@ export type EndpointIsolationRequestStateChange = Action<'endpointIsolationReque
148148
payload: EndpointState['isolationRequestState'];
149149
};
150150

151+
export interface AppRequestedEndpointActivityLog {
152+
type: 'appRequestedEndpointActivityLog';
153+
payload: {
154+
page: number;
155+
pageSize: number;
156+
};
157+
}
151158
export type EndpointDetailsActivityLogChanged = Action<'endpointDetailsActivityLogChanged'> & {
152-
payload: EndpointState['endpointDetails']['activityLog'];
159+
payload: EndpointState['endpointDetails']['activityLog']['logData'];
153160
};
154161

155162
export type EndpointAction =
156163
| ServerReturnedEndpointList
157164
| ServerFailedToReturnEndpointList
158165
| ServerReturnedEndpointDetails
159166
| ServerFailedToReturnEndpointDetails
167+
| AppRequestedEndpointActivityLog
160168
| EndpointDetailsActivityLogChanged
161169
| ServerReturnedEndpointPolicyResponse
162170
| ServerFailedToReturnEndpointPolicyResponse

x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/builders.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,11 @@ export const initialEndpointPageState = (): Immutable<EndpointState> => {
1919
loading: false,
2020
error: undefined,
2121
endpointDetails: {
22-
activityLog: createUninitialisedResourceState(),
22+
activityLog: {
23+
page: 1,
24+
pageSize: 50,
25+
logData: createUninitialisedResourceState(),
26+
},
2327
hostDetails: {
2428
details: undefined,
2529
detailsLoading: false,

x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/index.test.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,9 @@ describe('EndpointList store concerns', () => {
4343
error: undefined,
4444
endpointDetails: {
4545
activityLog: {
46-
type: 'UninitialisedResourceState',
46+
page: 1,
47+
pageSize: 50,
48+
logData: { type: 'UninitialisedResourceState' },
4749
},
4850
hostDetails: {
4951
details: undefined,

x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.test.ts

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import {
1818
Immutable,
1919
HostResultList,
2020
HostIsolationResponse,
21-
EndpointAction,
21+
ActivityLog,
2222
ISOLATION_ACTIONS,
2323
} from '../../../../../common/endpoint/types';
2424
import { AppAction } from '../../../../common/store/actions';
@@ -233,16 +233,40 @@ describe('endpoint list middleware', () => {
233233
},
234234
});
235235
};
236+
236237
const fleetActionGenerator = new FleetActionGenerator(Math.random().toString());
237-
const activityLog = [
238-
fleetActionGenerator.generate({
239-
agents: [endpointList.hosts[0].metadata.agent.id],
240-
}),
241-
];
238+
const actionData = fleetActionGenerator.generate({
239+
agents: [endpointList.hosts[0].metadata.agent.id],
240+
});
241+
const responseData = fleetActionGenerator.generateResponse({
242+
agent_id: endpointList.hosts[0].metadata.agent.id,
243+
});
244+
const getMockEndpointActivityLog = () =>
245+
({
246+
total: 2,
247+
page: 1,
248+
pageSize: 50,
249+
data: [
250+
{
251+
type: 'response',
252+
item: {
253+
id: '',
254+
data: responseData,
255+
},
256+
},
257+
{
258+
type: 'action',
259+
item: {
260+
id: '',
261+
data: actionData,
262+
},
263+
},
264+
],
265+
} as ActivityLog);
242266
const dispatchGetActivityLog = () => {
243267
dispatch({
244268
type: 'endpointDetailsActivityLogChanged',
245-
payload: createLoadedResourceState(activityLog),
269+
payload: createLoadedResourceState(getMockEndpointActivityLog()),
246270
});
247271
};
248272

@@ -270,11 +294,10 @@ describe('endpoint list middleware', () => {
270294

271295
dispatchGetActivityLog();
272296
const loadedDispatchedResponse = await loadedDispatched;
273-
const activityLogData = (loadedDispatchedResponse.payload as LoadedResourceState<
274-
EndpointAction[]
275-
>).data;
297+
const activityLogData = (loadedDispatchedResponse.payload as LoadedResourceState<ActivityLog>)
298+
.data;
276299

277-
expect(activityLogData).toEqual(activityLog);
300+
expect(activityLogData).toEqual(getMockEndpointActivityLog());
278301
});
279302
});
280303
});

x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts

Lines changed: 63 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import { Dispatch } from 'redux';
99
import { CoreStart, HttpStart } from 'kibana/public';
1010
import {
11-
EndpointAction,
11+
ActivityLog,
1212
HostInfo,
1313
HostIsolationRequestBody,
1414
HostIsolationResponse,
@@ -32,6 +32,8 @@ import {
3232
getIsIsolationRequestPending,
3333
getCurrentIsolationRequestState,
3434
getActivityLogData,
35+
getActivityLogDataPaging,
36+
getLastLoadedActivityLogData,
3537
} from './selectors';
3638
import { EndpointState, PolicyIds } from '../types';
3739
import {
@@ -336,21 +338,25 @@ export const endpointMiddlewareFactory: ImmutableMiddlewareFactory<EndpointState
336338
type: 'endpointDetailsActivityLogChanged',
337339
// ts error to be fixed when AsyncResourceState is refactored (#830)
338340
// @ts-expect-error
339-
payload: createLoadingResourceState<EndpointAction[]>(getActivityLogData(getState())),
341+
payload: createLoadingResourceState<ActivityLog>(getActivityLogData(getState())),
340342
});
341343

342344
try {
343-
const activityLog = await coreStart.http.get<EndpointAction[]>(
344-
resolvePathVariables(ENDPOINT_ACTION_LOG_ROUTE, { agent_id: selectedAgent(getState()) })
345-
);
345+
const { page, pageSize } = getActivityLogDataPaging(getState());
346+
const route = resolvePathVariables(ENDPOINT_ACTION_LOG_ROUTE, {
347+
agent_id: selectedAgent(getState()),
348+
});
349+
const activityLog = await coreStart.http.get<ActivityLog>(route, {
350+
query: { page, page_size: pageSize },
351+
});
346352
dispatch({
347353
type: 'endpointDetailsActivityLogChanged',
348-
payload: createLoadedResourceState<EndpointAction[]>(activityLog),
354+
payload: createLoadedResourceState<ActivityLog>(activityLog),
349355
});
350356
} catch (error) {
351357
dispatch({
352358
type: 'endpointDetailsActivityLogChanged',
353-
payload: createFailedResourceState<EndpointAction[]>(error.body ?? error),
359+
payload: createFailedResourceState<ActivityLog>(error.body ?? error),
354360
});
355361
}
356362

@@ -371,6 +377,56 @@ export const endpointMiddlewareFactory: ImmutableMiddlewareFactory<EndpointState
371377
}
372378
}
373379

380+
// page activity log API
381+
if (action.type === 'appRequestedEndpointActivityLog' && hasSelectedEndpoint(getState())) {
382+
dispatch({
383+
type: 'endpointDetailsActivityLogChanged',
384+
// ts error to be fixed when AsyncResourceState is refactored (#830)
385+
// @ts-expect-error
386+
payload: createLoadingResourceState<ActivityLog>(getActivityLogData(getState())),
387+
});
388+
389+
try {
390+
const { page, pageSize } = getActivityLogDataPaging(getState());
391+
const route = resolvePathVariables(ENDPOINT_ACTION_LOG_ROUTE, {
392+
agent_id: selectedAgent(getState()),
393+
});
394+
const activityLog = await coreStart.http.get<ActivityLog>(route, {
395+
query: { page, page_size: pageSize },
396+
});
397+
398+
const lastLoadedLogData = getLastLoadedActivityLogData(getState());
399+
if (lastLoadedLogData !== undefined) {
400+
const updatedLogDataItems = [
401+
...new Set([...lastLoadedLogData.data, ...activityLog.data]),
402+
] as ActivityLog['data'];
403+
404+
const updatedLogData = {
405+
total: activityLog.total,
406+
page: activityLog.page,
407+
pageSize: activityLog.pageSize,
408+
data: updatedLogDataItems,
409+
};
410+
dispatch({
411+
type: 'endpointDetailsActivityLogChanged',
412+
payload: createLoadedResourceState<ActivityLog>(updatedLogData),
413+
});
414+
// TODO dispatch 'noNewLogData' if !activityLog.length
415+
// resets paging to previous state
416+
} else {
417+
dispatch({
418+
type: 'endpointDetailsActivityLogChanged',
419+
payload: createLoadedResourceState<ActivityLog>(activityLog),
420+
});
421+
}
422+
} catch (error) {
423+
dispatch({
424+
type: 'endpointDetailsActivityLogChanged',
425+
payload: createFailedResourceState<ActivityLog>(error.body ?? error),
426+
});
427+
}
428+
}
429+
374430
// Isolate Host
375431
if (action.type === 'endpointIsolationRequest') {
376432
return handleIsolateEndpointHost(store, action);

x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/reducer.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,10 @@ const handleEndpointDetailsActivityLogChanged: CaseReducer<EndpointDetailsActivi
3333
...state!,
3434
endpointDetails: {
3535
...state.endpointDetails!,
36-
activityLog: action.payload,
36+
activityLog: {
37+
...state.endpointDetails.activityLog,
38+
logData: action.payload,
39+
},
3740
},
3841
};
3942
};
@@ -121,6 +124,21 @@ export const endpointListReducer: StateReducer = (state = initialEndpointPageSta
121124
},
122125
},
123126
};
127+
} else if (action.type === 'appRequestedEndpointActivityLog') {
128+
const pageData = {
129+
page: action.payload.page,
130+
pageSize: action.payload.pageSize,
131+
};
132+
return {
133+
...state,
134+
endpointDetails: {
135+
...state.endpointDetails!,
136+
activityLog: {
137+
...state.endpointDetails.activityLog,
138+
...pageData,
139+
},
140+
},
141+
};
124142
} else if (action.type === 'endpointDetailsActivityLogChanged') {
125143
return handleEndpointDetailsActivityLogChanged(state, action);
126144
} else if (action.type === 'serverReturnedPoliciesForOnboarding') {
@@ -220,6 +238,12 @@ export const endpointListReducer: StateReducer = (state = initialEndpointPageSta
220238
policyResponseError: undefined,
221239
};
222240

241+
const activityLog = {
242+
logData: createUninitialisedResourceState(),
243+
page: 1,
244+
pageSize: 50,
245+
};
246+
223247
// Reset `isolationRequestState` if needed
224248
if (
225249
uiQueryParams(newState).show !== 'isolate' &&
@@ -236,6 +260,7 @@ export const endpointListReducer: StateReducer = (state = initialEndpointPageSta
236260
...stateUpdates,
237261
endpointDetails: {
238262
...state.endpointDetails,
263+
activityLog,
239264
hostDetails: {
240265
...state.endpointDetails.hostDetails,
241266
detailsError: undefined,
@@ -253,6 +278,7 @@ export const endpointListReducer: StateReducer = (state = initialEndpointPageSta
253278
...stateUpdates,
254279
endpointDetails: {
255280
...state.endpointDetails,
281+
activityLog,
256282
hostDetails: {
257283
...state.endpointDetails.hostDetails,
258284
detailsLoading: true,
@@ -269,6 +295,7 @@ export const endpointListReducer: StateReducer = (state = initialEndpointPageSta
269295
...stateUpdates,
270296
endpointDetails: {
271297
...state.endpointDetails,
298+
activityLog,
272299
hostDetails: {
273300
...state.endpointDetails.hostDetails,
274301
detailsLoading: true,
@@ -287,6 +314,7 @@ export const endpointListReducer: StateReducer = (state = initialEndpointPageSta
287314
...stateUpdates,
288315
endpointDetails: {
289316
...state.endpointDetails,
317+
activityLog,
290318
hostDetails: {
291319
...state.endpointDetails.hostDetails,
292320
detailsError: undefined,

0 commit comments

Comments
 (0)