Skip to content

Commit

Permalink
[Security Solution][Endpoint] Include actions and responses for endpo…
Browse files Browse the repository at this point in the history
…ints only (#103159)
  • Loading branch information
ashokaditya committed Jun 29, 2021
1 parent 21dad7e commit fea63a2
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 76 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export class FleetActionGenerator extends BaseDataGenerator {
return merge(this.generate({ data: { command: 'unisolate' } }), overrides);
}

/** Generates an action response */
/** Generates an endpoint action response */
generateResponse(overrides: DeepPartial<EndpointActionResponse> = {}): EndpointActionResponse {
const timeStamp = new Date();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -421,7 +421,7 @@ const indexFleetActionsForHost = async (

action.agents = [agentId];

await esClient.index(
esClient.index(
{
index: AGENT_ACTIONS_INDEX,
body: action,
Expand All @@ -436,7 +436,7 @@ const indexFleetActionsForHost = async (
action_data: action.data,
});

await esClient.index(
esClient.index(
{
index: AGENT_ACTIONS_RESULTS_INDEX,
body: actionResponse,
Expand All @@ -449,7 +449,7 @@ const indexFleetActionsForHost = async (
if (fleetActionGenerator.randomFloat() < 0.3) {
const randomFloat = fleetActionGenerator.randomFloat();

// 60% of the time just add either an Isoalte -OR- an UnIsolate action
// 60% of the time just add either an Isolate -OR- an UnIsolate action
if (randomFloat < 0.6) {
let action: EndpointAction;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -412,9 +412,11 @@ export const endpointMiddlewareFactory: ImmutableMiddlewareFactory<EndpointState

const lastLoadedLogData = getLastLoadedActivityLogData(getState());
if (lastLoadedLogData !== undefined) {
const updatedLogDataItems = [
const updatedLogDataItems = ([
...new Set([...lastLoadedLogData.data, ...activityLog.data]),
] as ActivityLog['data'];
] as ActivityLog['data']).sort((a, b) =>
new Date(b.item.data['@timestamp']) > new Date(a.item.data['@timestamp']) ? 1 : -1
);

const updatedLogData = {
page: activityLog.page,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,20 +157,14 @@ describe('Action Log API', () => {

it('should have actions and action responses', async () => {
havingActionsAndResponses(
[
aMockAction().withAgent(mockID).withAction('isolate'),
aMockAction().withAgent(mockID).withAction('unisolate'),
aMockAction().withAgent(mockID).withAction('isolate'),
],
[aMockResponse(actionID, mockID), aMockResponse(actionID, mockID)]
[aMockAction().withAgent(mockID).withAction('isolate').withID(actionID)],
[aMockResponse(actionID, mockID)]
);
const response = await getActivityLog();
const responseBody = response.ok.mock.calls[0][0]?.body as ActivityLog;

expect(response.ok).toBeCalled();
expect(responseBody.data).toHaveLength(5);
expect(responseBody.data.filter((x: any) => x.type === 'response')).toHaveLength(2);
expect(responseBody.data.filter((x: any) => x.type === 'action')).toHaveLength(3);
expect(responseBody.data).toHaveLength(2);
});

it('should throw errors when no results for some agentID', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,43 +5,10 @@
* 2.0.
*/

import { Logger } from 'kibana/server';
import type { estypes } from '@elastic/elasticsearch';
import { ElasticsearchClient, Logger } from 'kibana/server';
import { AGENT_ACTIONS_INDEX, AGENT_ACTIONS_RESULTS_INDEX } from '../../../../../fleet/common';
import { SecuritySolutionRequestHandlerContext } from '../../../types';

export const getAuditLogESQuery = ({
elasticAgentId,
from,
size,
}: {
elasticAgentId: string;
from: number;
size: number;
}): estypes.SearchRequest => {
return {
index: [AGENT_ACTIONS_INDEX, AGENT_ACTIONS_RESULTS_INDEX],
size,
from,
body: {
query: {
bool: {
should: [
{ terms: { agents: [elasticAgentId] } },
{ terms: { agent_id: [elasticAgentId] } },
],
},
},
sort: [
{
'@timestamp': {
order: 'desc',
},
},
],
},
};
};
import { ActivityLog, EndpointAction } from '../../../../common/endpoint/types';

export const getAuditLogResponse = async ({
elasticAgentId,
Expand All @@ -58,48 +25,113 @@ export const getAuditLogResponse = async ({
}): Promise<{
page: number;
pageSize: number;
data: Array<{
type: 'action' | 'response';
item: {
id: string;
data: unknown;
};
}>;
data: ActivityLog['data'];
}> => {
const size = pageSize;
const from = page <= 1 ? 0 : page * pageSize - pageSize + 1;
const size = Math.floor(pageSize / 2);
const from = page <= 1 ? 0 : page * size - size + 1;
const esClient = context.core.elasticsearch.client.asCurrentUser;

const data = await getActivityLog({ esClient, from, size, elasticAgentId, logger });

return {
page,
pageSize,
data,
};
};

const getActivityLog = async ({
esClient,
size,
from,
elasticAgentId,
logger,
}: {
esClient: ElasticsearchClient;
elasticAgentId: string;
size: number;
from: number;
logger: Logger;
}) => {
const options = {
headers: {
'X-elastic-product-origin': 'fleet',
},
ignore: [404],
};
const esClient = context.core.elasticsearch.client.asCurrentUser;
let result;
const params = getAuditLogESQuery({
elasticAgentId,
from,
size,
});

let actionsResult;
let responsesResult;

try {
result = await esClient.search(params, options);
actionsResult = await esClient.search(
{
index: AGENT_ACTIONS_INDEX,
size,
from,
body: {
query: {
bool: {
filter: [
{ term: { agents: elasticAgentId } },
{ term: { input_type: 'endpoint' } },
{ term: { type: 'INPUT_ACTION' } },
],
},
},
sort: [
{
'@timestamp': {
order: 'desc',
},
},
],
},
},
options
);
const actionIds = actionsResult?.body?.hits?.hits?.map(
(e) => (e._source as EndpointAction).action_id
);

responsesResult = await esClient.search(
{
index: AGENT_ACTIONS_RESULTS_INDEX,
size: 1000,
body: {
query: {
bool: {
filter: [{ term: { agent_id: elasticAgentId } }, { terms: { action_id: actionIds } }],
},
},
},
},
options
);
} catch (error) {
logger.error(error);
throw error;
}
if (result?.statusCode !== 200) {
if (actionsResult?.statusCode !== 200) {
logger.error(`Error fetching actions log for agent_id ${elasticAgentId}`);
throw new Error(`Error fetching actions log for agent_id ${elasticAgentId}`);
}

return {
page,
pageSize,
data: result.body.hits.hits.map((e) => ({
type: e._index.startsWith('.fleet-actions') ? 'action' : 'response',
item: { id: e._id, data: e._source },
})),
};
const responses = responsesResult?.body?.hits?.hits?.length
? responsesResult?.body?.hits?.hits?.map((e) => ({
type: 'response',
item: { id: e._id, data: e._source },
}))
: [];
const actions = actionsResult?.body?.hits?.hits?.length
? actionsResult?.body?.hits?.hits?.map((e) => ({
type: 'action',
item: { id: e._id, data: e._source },
}))
: [];
const sortedData = ([...responses, ...actions] as ActivityLog['data']).sort((a, b) =>
new Date(b.item.data['@timestamp']) > new Date(a.item.data['@timestamp']) ? 1 : -1
);

return sortedData;
};

0 comments on commit fea63a2

Please sign in to comment.