mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Security Solution][Endpoint] update CLI tool that responds to actions to also send metadata updates for Isolate and Release actions (#135601)
* endpoint metadata service to retrieve individual endpoint metadata * Fleet service with method to check in an agent into fleet * Endpoint metadata service method to send a metadata update * update action responder to also send endpoint metadata updates for isolate and release actions Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
5460e38361
commit
4d3cab8983
3 changed files with 174 additions and 2 deletions
|
@ -8,6 +8,7 @@
|
|||
import { KbnClient } from '@kbn/test';
|
||||
import { Client } from '@elastic/elasticsearch';
|
||||
import { AGENT_ACTIONS_RESULTS_INDEX } from '@kbn/fleet-plugin/common';
|
||||
import { sendEndpointMetadataUpdate } from '../common/endpoint_metadata_services';
|
||||
import { FleetActionGenerator } from '../../../common/endpoint/data_generators/fleet_action_generator';
|
||||
import {
|
||||
ENDPOINT_ACTION_RESPONSES_INDEX,
|
||||
|
@ -87,8 +88,6 @@ export const sendEndpointActionResponse = async (
|
|||
action: ActionDetails,
|
||||
{ state }: { state?: 'success' | 'failure' } = {}
|
||||
): Promise<LogsEndpointActionResponse> => {
|
||||
// FIXME:PT Generate command specific responses
|
||||
|
||||
const endpointResponse = endpointActionGenerator.generateResponse({
|
||||
agent: { id: action.agents[0] },
|
||||
EndpointActions: {
|
||||
|
@ -115,6 +114,36 @@ export const sendEndpointActionResponse = async (
|
|||
refresh: 'wait_for',
|
||||
});
|
||||
|
||||
// ------------------------------------------
|
||||
// Post Action Response tasks
|
||||
// ------------------------------------------
|
||||
|
||||
// For isolate, If the response is not an error, then also send a metadata update
|
||||
if (action.command === 'isolate' && !endpointResponse.error) {
|
||||
for (const agentId of action.agents) {
|
||||
await sendEndpointMetadataUpdate(esClient, agentId, {
|
||||
Endpoint: {
|
||||
state: {
|
||||
isolation: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// For UnIsolate, if response is not an Error, then also send metadata update
|
||||
if (action.command === 'unisolate' && !endpointResponse.error) {
|
||||
for (const agentId of action.agents) {
|
||||
await sendEndpointMetadataUpdate(esClient, agentId, {
|
||||
Endpoint: {
|
||||
state: {
|
||||
isolation: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return endpointResponse;
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,116 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { Client } from '@elastic/elasticsearch';
|
||||
import { KbnClient } from '@kbn/test';
|
||||
import { WriteResponseBase } from '@elastic/elasticsearch/lib/api/types';
|
||||
import { clone, merge } from 'lodash';
|
||||
import { DeepPartial } from 'utility-types';
|
||||
import { resolvePathVariables } from '../../../public/common/utils/resolve_path_variables';
|
||||
import { HOST_METADATA_GET_ROUTE, METADATA_DATASTREAM } from '../../../common/endpoint/constants';
|
||||
import { HostInfo, HostMetadata } from '../../../common/endpoint/types';
|
||||
import { EndpointDocGenerator } from '../../../common/endpoint/generate_data';
|
||||
import { checkInFleetAgent } from './fleet_services';
|
||||
|
||||
const endpointGenerator = new EndpointDocGenerator();
|
||||
|
||||
export const fetchEndpointMetadata = async (
|
||||
kbnClient: KbnClient,
|
||||
agentId: string
|
||||
): Promise<HostInfo> => {
|
||||
return (
|
||||
await kbnClient.request<HostInfo>({
|
||||
method: 'GET',
|
||||
path: resolvePathVariables(HOST_METADATA_GET_ROUTE, { id: agentId }),
|
||||
})
|
||||
).data;
|
||||
};
|
||||
|
||||
export const sendEndpointMetadataUpdate = async (
|
||||
esClient: Client,
|
||||
agentId: string,
|
||||
overrides: DeepPartial<HostMetadata> = {},
|
||||
{ checkInAgent = true }: Partial<{ checkInAgent: boolean }> = {}
|
||||
): Promise<WriteResponseBase> => {
|
||||
const lastStreamedDoc = await fetchLastStreamedEndpointUpdate(esClient, agentId);
|
||||
|
||||
if (!lastStreamedDoc) {
|
||||
throw new Error(`An endpoint with agent.id of [${agentId}] not found!`);
|
||||
}
|
||||
|
||||
if (checkInAgent) {
|
||||
// Trigger an agent checkin and just let it run
|
||||
checkInFleetAgent(esClient, agentId);
|
||||
}
|
||||
|
||||
const generatedHostMetadataDoc = clone(endpointGenerator.generateHostMetadata());
|
||||
const newUpdate: HostMetadata = merge(
|
||||
lastStreamedDoc,
|
||||
{
|
||||
event: generatedHostMetadataDoc.event, // Make sure to use a new event object
|
||||
'@timestamp': generatedHostMetadataDoc['@timestamp'],
|
||||
},
|
||||
overrides
|
||||
);
|
||||
|
||||
return esClient.index({
|
||||
index: METADATA_DATASTREAM,
|
||||
body: newUpdate,
|
||||
op_type: 'create',
|
||||
});
|
||||
};
|
||||
|
||||
const fetchLastStreamedEndpointUpdate = async (
|
||||
esClient: Client,
|
||||
agentId: string
|
||||
): Promise<HostMetadata | undefined> => {
|
||||
const queryResult = await esClient.search<HostMetadata>(
|
||||
{
|
||||
index: METADATA_DATASTREAM,
|
||||
size: 1,
|
||||
body: {
|
||||
query: {
|
||||
bool: {
|
||||
filter: [
|
||||
{
|
||||
bool: {
|
||||
should: [{ term: { 'elastic.agent.id': agentId } }],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
// Am I doing this right? I want only the last document for the host.id that was sent
|
||||
collapse: {
|
||||
field: 'host.id',
|
||||
inner_hits: {
|
||||
name: 'most_recent',
|
||||
size: 1,
|
||||
sort: [{ 'event.created': 'desc' }],
|
||||
},
|
||||
},
|
||||
aggs: {
|
||||
total: {
|
||||
cardinality: {
|
||||
field: 'host.id',
|
||||
},
|
||||
},
|
||||
},
|
||||
sort: [
|
||||
{
|
||||
'event.created': {
|
||||
order: 'desc',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{ ignore: [404] }
|
||||
);
|
||||
|
||||
return queryResult.hits?.hits[0]?._source;
|
||||
};
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { Client } from '@elastic/elasticsearch';
|
||||
import { AGENTS_INDEX } from '@kbn/fleet-plugin/common';
|
||||
|
||||
export const checkInFleetAgent = async (esClient: Client, agentId: string) => {
|
||||
const checkinNow = new Date().toISOString();
|
||||
|
||||
await esClient.update({
|
||||
index: AGENTS_INDEX,
|
||||
id: agentId,
|
||||
refresh: 'wait_for',
|
||||
retry_on_conflict: 5,
|
||||
body: {
|
||||
doc: {
|
||||
active: true,
|
||||
last_checkin: checkinNow,
|
||||
updated_at: checkinNow,
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue