Skip to content

Commit

Permalink
[Security Solution][Endpoint] Actions Log API (elastic#101032)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
2 people authored and John Dorlus committed Jun 15, 2021
1 parent 99883e2 commit b37ac58
Show file tree
Hide file tree
Showing 22 changed files with 919 additions and 183 deletions.
1 change: 1 addition & 0 deletions x-pack/plugins/fleet/common/constants/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,4 @@ export const AGENT_POLICY_ROLLOUT_RATE_LIMIT_REQUEST_PER_INTERVAL = 5;

export const AGENTS_INDEX = '.fleet-agents';
export const AGENT_ACTIONS_INDEX = '.fleet-actions';
export const AGENT_ACTIONS_RESULTS_INDEX = '.fleet-actions-results';
1 change: 1 addition & 0 deletions x-pack/plugins/fleet/common/constants/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export const FLEET_SERVER_SERVERS_INDEX = '.fleet-servers';

export const FLEET_SERVER_INDICES = [
'.fleet-actions',
'.fleet-actions-results',
'.fleet-agents',
FLEET_SERVER_ARTIFACTS_INDEX,
'.fleet-enrollment-api-keys',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* 2.0.
*/

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

export const HostIsolationRequestSchema = {
body: schema.object({
Expand All @@ -22,13 +22,18 @@ export const HostIsolationRequestSchema = {
};

export const EndpointActionLogRequestSchema = {
// TODO improve when using pagination with query params
query: schema.object({}),
query: schema.object({
page: schema.number({ defaultValue: 1, min: 1 }),
page_size: schema.number({ defaultValue: 10, min: 1, max: 100 }),
}),
params: schema.object({
agent_id: schema.string(),
}),
};

export type EndpointActionLogRequestParams = TypeOf<typeof EndpointActionLogRequestSchema.params>;
export type EndpointActionLogRequestQuery = TypeOf<typeof EndpointActionLogRequestSchema.query>;

export const ActionStatusRequestSchema = {
query: schema.object({
agent_ids: schema.oneOf([
Expand Down
26 changes: 26 additions & 0 deletions x-pack/plugins/security_solution/common/endpoint/types/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,32 @@ export interface EndpointActionResponse {
action_data: EndpointActionData;
}

export interface ActivityLogAction {
type: 'action';
item: {
// document _id
id: string;
// document _source
data: EndpointAction;
};
}
export interface ActivityLogActionResponse {
type: 'response';
item: {
// document id
id: string;
// document _source
data: EndpointActionResponse;
};
}
export type ActivityLogEntry = ActivityLogAction | ActivityLogActionResponse;
export interface ActivityLog {
total: number;
page: number;
pageSize: number;
data: ActivityLogEntry[];
}

export type HostIsolationRequestBody = TypeOf<typeof HostIsolationRequestSchema.body>;

export interface HostIsolationResponse {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,15 +148,23 @@ export type EndpointIsolationRequestStateChange = Action<'endpointIsolationReque
payload: EndpointState['isolationRequestState'];
};

export interface AppRequestedEndpointActivityLog {
type: 'appRequestedEndpointActivityLog';
payload: {
page: number;
pageSize: number;
};
}
export type EndpointDetailsActivityLogChanged = Action<'endpointDetailsActivityLogChanged'> & {
payload: EndpointState['endpointDetails']['activityLog'];
payload: EndpointState['endpointDetails']['activityLog']['logData'];
};

export type EndpointAction =
| ServerReturnedEndpointList
| ServerFailedToReturnEndpointList
| ServerReturnedEndpointDetails
| ServerFailedToReturnEndpointDetails
| AppRequestedEndpointActivityLog
| EndpointDetailsActivityLogChanged
| ServerReturnedEndpointPolicyResponse
| ServerFailedToReturnEndpointPolicyResponse
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,11 @@ export const initialEndpointPageState = (): Immutable<EndpointState> => {
loading: false,
error: undefined,
endpointDetails: {
activityLog: createUninitialisedResourceState(),
activityLog: {
page: 1,
pageSize: 50,
logData: createUninitialisedResourceState(),
},
hostDetails: {
details: undefined,
detailsLoading: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@ describe('EndpointList store concerns', () => {
error: undefined,
endpointDetails: {
activityLog: {
type: 'UninitialisedResourceState',
page: 1,
pageSize: 50,
logData: { type: 'UninitialisedResourceState' },
},
hostDetails: {
details: undefined,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
Immutable,
HostResultList,
HostIsolationResponse,
EndpointAction,
ActivityLog,
ISOLATION_ACTIONS,
} from '../../../../../common/endpoint/types';
import { AppAction } from '../../../../common/store/actions';
Expand Down Expand Up @@ -233,16 +233,40 @@ describe('endpoint list middleware', () => {
},
});
};

const fleetActionGenerator = new FleetActionGenerator(Math.random().toString());
const activityLog = [
fleetActionGenerator.generate({
agents: [endpointList.hosts[0].metadata.agent.id],
}),
];
const actionData = fleetActionGenerator.generate({
agents: [endpointList.hosts[0].metadata.agent.id],
});
const responseData = fleetActionGenerator.generateResponse({
agent_id: endpointList.hosts[0].metadata.agent.id,
});
const getMockEndpointActivityLog = () =>
({
total: 2,
page: 1,
pageSize: 50,
data: [
{
type: 'response',
item: {
id: '',
data: responseData,
},
},
{
type: 'action',
item: {
id: '',
data: actionData,
},
},
],
} as ActivityLog);
const dispatchGetActivityLog = () => {
dispatch({
type: 'endpointDetailsActivityLogChanged',
payload: createLoadedResourceState(activityLog),
payload: createLoadedResourceState(getMockEndpointActivityLog()),
});
};

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

dispatchGetActivityLog();
const loadedDispatchedResponse = await loadedDispatched;
const activityLogData = (loadedDispatchedResponse.payload as LoadedResourceState<
EndpointAction[]
>).data;
const activityLogData = (loadedDispatchedResponse.payload as LoadedResourceState<ActivityLog>)
.data;

expect(activityLogData).toEqual(activityLog);
expect(activityLogData).toEqual(getMockEndpointActivityLog());
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import { Dispatch } from 'redux';
import { CoreStart, HttpStart } from 'kibana/public';
import {
EndpointAction,
ActivityLog,
HostInfo,
HostIsolationRequestBody,
HostIsolationResponse,
Expand All @@ -32,6 +32,8 @@ import {
getIsIsolationRequestPending,
getCurrentIsolationRequestState,
getActivityLogData,
getActivityLogDataPaging,
getLastLoadedActivityLogData,
} from './selectors';
import { EndpointState, PolicyIds } from '../types';
import {
Expand Down Expand Up @@ -336,21 +338,25 @@ export const endpointMiddlewareFactory: ImmutableMiddlewareFactory<EndpointState
type: 'endpointDetailsActivityLogChanged',
// ts error to be fixed when AsyncResourceState is refactored (#830)
// @ts-expect-error
payload: createLoadingResourceState<EndpointAction[]>(getActivityLogData(getState())),
payload: createLoadingResourceState<ActivityLog>(getActivityLogData(getState())),
});

try {
const activityLog = await coreStart.http.get<EndpointAction[]>(
resolvePathVariables(ENDPOINT_ACTION_LOG_ROUTE, { agent_id: selectedAgent(getState()) })
);
const { page, pageSize } = getActivityLogDataPaging(getState());
const route = resolvePathVariables(ENDPOINT_ACTION_LOG_ROUTE, {
agent_id: selectedAgent(getState()),
});
const activityLog = await coreStart.http.get<ActivityLog>(route, {
query: { page, page_size: pageSize },
});
dispatch({
type: 'endpointDetailsActivityLogChanged',
payload: createLoadedResourceState<EndpointAction[]>(activityLog),
payload: createLoadedResourceState<ActivityLog>(activityLog),
});
} catch (error) {
dispatch({
type: 'endpointDetailsActivityLogChanged',
payload: createFailedResourceState<EndpointAction[]>(error.body ?? error),
payload: createFailedResourceState<ActivityLog>(error.body ?? error),
});
}

Expand All @@ -371,6 +377,56 @@ export const endpointMiddlewareFactory: ImmutableMiddlewareFactory<EndpointState
}
}

// page activity log API
if (action.type === 'appRequestedEndpointActivityLog' && hasSelectedEndpoint(getState())) {
dispatch({
type: 'endpointDetailsActivityLogChanged',
// ts error to be fixed when AsyncResourceState is refactored (#830)
// @ts-expect-error
payload: createLoadingResourceState<ActivityLog>(getActivityLogData(getState())),
});

try {
const { page, pageSize } = getActivityLogDataPaging(getState());
const route = resolvePathVariables(ENDPOINT_ACTION_LOG_ROUTE, {
agent_id: selectedAgent(getState()),
});
const activityLog = await coreStart.http.get<ActivityLog>(route, {
query: { page, page_size: pageSize },
});

const lastLoadedLogData = getLastLoadedActivityLogData(getState());
if (lastLoadedLogData !== undefined) {
const updatedLogDataItems = [
...new Set([...lastLoadedLogData.data, ...activityLog.data]),
] as ActivityLog['data'];

const updatedLogData = {
total: activityLog.total,
page: activityLog.page,
pageSize: activityLog.pageSize,
data: updatedLogDataItems,
};
dispatch({
type: 'endpointDetailsActivityLogChanged',
payload: createLoadedResourceState<ActivityLog>(updatedLogData),
});
// TODO dispatch 'noNewLogData' if !activityLog.length
// resets paging to previous state
} else {
dispatch({
type: 'endpointDetailsActivityLogChanged',
payload: createLoadedResourceState<ActivityLog>(activityLog),
});
}
} catch (error) {
dispatch({
type: 'endpointDetailsActivityLogChanged',
payload: createFailedResourceState<ActivityLog>(error.body ?? error),
});
}
}

// Isolate Host
if (action.type === 'endpointIsolationRequest') {
return handleIsolateEndpointHost(store, action);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@ const handleEndpointDetailsActivityLogChanged: CaseReducer<EndpointDetailsActivi
...state!,
endpointDetails: {
...state.endpointDetails!,
activityLog: action.payload,
activityLog: {
...state.endpointDetails.activityLog,
logData: action.payload,
},
},
};
};
Expand Down Expand Up @@ -121,6 +124,21 @@ export const endpointListReducer: StateReducer = (state = initialEndpointPageSta
},
},
};
} else if (action.type === 'appRequestedEndpointActivityLog') {
const pageData = {
page: action.payload.page,
pageSize: action.payload.pageSize,
};
return {
...state,
endpointDetails: {
...state.endpointDetails!,
activityLog: {
...state.endpointDetails.activityLog,
...pageData,
},
},
};
} else if (action.type === 'endpointDetailsActivityLogChanged') {
return handleEndpointDetailsActivityLogChanged(state, action);
} else if (action.type === 'serverReturnedPoliciesForOnboarding') {
Expand Down Expand Up @@ -220,6 +238,12 @@ export const endpointListReducer: StateReducer = (state = initialEndpointPageSta
policyResponseError: undefined,
};

const activityLog = {
logData: createUninitialisedResourceState(),
page: 1,
pageSize: 50,
};

// Reset `isolationRequestState` if needed
if (
uiQueryParams(newState).show !== 'isolate' &&
Expand All @@ -236,6 +260,7 @@ export const endpointListReducer: StateReducer = (state = initialEndpointPageSta
...stateUpdates,
endpointDetails: {
...state.endpointDetails,
activityLog,
hostDetails: {
...state.endpointDetails.hostDetails,
detailsError: undefined,
Expand All @@ -253,6 +278,7 @@ export const endpointListReducer: StateReducer = (state = initialEndpointPageSta
...stateUpdates,
endpointDetails: {
...state.endpointDetails,
activityLog,
hostDetails: {
...state.endpointDetails.hostDetails,
detailsLoading: true,
Expand All @@ -269,6 +295,7 @@ export const endpointListReducer: StateReducer = (state = initialEndpointPageSta
...stateUpdates,
endpointDetails: {
...state.endpointDetails,
activityLog,
hostDetails: {
...state.endpointDetails.hostDetails,
detailsLoading: true,
Expand All @@ -287,6 +314,7 @@ export const endpointListReducer: StateReducer = (state = initialEndpointPageSta
...stateUpdates,
endpointDetails: {
...state.endpointDetails,
activityLog,
hostDetails: {
...state.endpointDetails.hostDetails,
detailsError: undefined,
Expand Down
Loading

0 comments on commit b37ac58

Please sign in to comment.