[Security Solution][Hosts] Show Fleet Agent status and Isolation status for Endpoint Hosts when on the Host Details page (#103781)
* Refactor: extract agent status to endpoint host status to reusable utiltiy * Show Fleet Agent status + isolation status * Refactor EndpoinAgentStatus component to use `<AgentStatus>` common component * Move actions service to `endpoint/services` directory * Add pending actions to the search strategy for endpoint data
This commit is contained in:
parent
a3f86bda3e
commit
aa5c56c418
|
@ -8,6 +8,7 @@
|
|||
import { CloudEcs } from '../../../../ecs/cloud';
|
||||
import { HostEcs, OsEcs } from '../../../../ecs/host';
|
||||
import { Hit, Hits, Maybe, SearchHit, StringOrNumber, TotalValue } from '../../../common';
|
||||
import { EndpointPendingActions, HostStatus } from '../../../../endpoint/types';
|
||||
|
||||
export enum HostPolicyResponseActionStatus {
|
||||
success = 'success',
|
||||
|
@ -25,6 +26,11 @@ export interface EndpointFields {
|
|||
endpointPolicy?: Maybe<string>;
|
||||
sensorVersion?: Maybe<string>;
|
||||
policyStatus?: Maybe<HostPolicyResponseActionStatus>;
|
||||
/** if the host is currently isolated */
|
||||
isolation?: Maybe<boolean>;
|
||||
/** A count of pending endpoint actions against the host */
|
||||
pendingActions?: Maybe<EndpointPendingActions['pending_actions']>;
|
||||
elasticAgentStatus?: Maybe<HostStatus>;
|
||||
id?: Maybe<string>;
|
||||
}
|
||||
|
||||
|
|
|
@ -6,14 +6,13 @@
|
|||
*/
|
||||
|
||||
import React, { memo } from 'react';
|
||||
import { EuiBadge, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import styled from 'styled-components';
|
||||
import { HostInfo, HostMetadata } from '../../../../../../common/endpoint/types';
|
||||
import { HOST_STATUS_TO_BADGE_COLOR } from '../host_constants';
|
||||
import { EndpointHostIsolationStatus } from '../../../../../common/components/endpoint/host_isolation';
|
||||
import { useEndpointSelector } from '../hooks';
|
||||
import { getEndpointHostIsolationStatusPropsCallback } from '../../store/selectors';
|
||||
import { AgentStatus } from '../../../../../common/components/endpoint/agent_status';
|
||||
|
||||
const EuiFlexGroupStyled = styled(EuiFlexGroup)`
|
||||
.isolation-status {
|
||||
|
@ -34,16 +33,7 @@ export const EndpointAgentStatus = memo<EndpointAgentStatusProps>(
|
|||
return (
|
||||
<EuiFlexGroupStyled gutterSize="none" responsive={false} className="eui-textTruncate">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiBadge
|
||||
color={hostStatus != null ? HOST_STATUS_TO_BADGE_COLOR[hostStatus] : 'warning'}
|
||||
data-test-subj="rowHostStatus"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.list.hostStatusValue"
|
||||
defaultMessage="{hostStatus, select, healthy {Healthy} unhealthy {Unhealthy} updating {Updating} offline {Offline} inactive {Inactive} other {Unhealthy}}"
|
||||
values={{ hostStatus }}
|
||||
/>
|
||||
</EuiBadge>
|
||||
<AgentStatus hostStatus={hostStatus} />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false} className="eui-textTruncate isolation-status">
|
||||
<EndpointHostIsolationStatus
|
||||
|
|
|
@ -13,42 +13,71 @@ import '../../../../common/mock/react_beautiful_dnd';
|
|||
import { TestProviders } from '../../../../common/mock';
|
||||
|
||||
import { EndpointOverview } from './index';
|
||||
import { HostPolicyResponseActionStatus } from '../../../../../common/search_strategy/security_solution/hosts';
|
||||
import {
|
||||
EndpointFields,
|
||||
HostPolicyResponseActionStatus,
|
||||
} from '../../../../../common/search_strategy/security_solution/hosts';
|
||||
import { HostStatus } from '../../../../../common/endpoint/types';
|
||||
|
||||
jest.mock('../../../../common/lib/kibana');
|
||||
|
||||
describe('EndpointOverview Component', () => {
|
||||
test('it renders with endpoint data', () => {
|
||||
const endpointData = {
|
||||
let endpointData: EndpointFields;
|
||||
let wrapper: ReturnType<typeof mount>;
|
||||
let findData: ReturnType<typeof wrapper['find']>;
|
||||
const render = (data: EndpointFields | null = endpointData) => {
|
||||
wrapper = mount(
|
||||
<TestProviders>
|
||||
<EndpointOverview data={data} />
|
||||
</TestProviders>
|
||||
);
|
||||
findData = wrapper.find(
|
||||
'dl[data-test-subj="endpoint-overview"] dd.euiDescriptionList__description'
|
||||
);
|
||||
|
||||
return wrapper;
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
endpointData = {
|
||||
endpointPolicy: 'demo',
|
||||
policyStatus: HostPolicyResponseActionStatus.success,
|
||||
sensorVersion: '7.9.0-SNAPSHOT',
|
||||
isolation: false,
|
||||
elasticAgentStatus: HostStatus.HEALTHY,
|
||||
pendingActions: {},
|
||||
};
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<EndpointOverview data={endpointData} />
|
||||
</TestProviders>
|
||||
);
|
||||
});
|
||||
|
||||
const findData = wrapper.find(
|
||||
'dl[data-test-subj="endpoint-overview"] dd.euiDescriptionList__description'
|
||||
);
|
||||
test('it renders with endpoint data', () => {
|
||||
render();
|
||||
expect(findData.at(0).text()).toEqual(endpointData.endpointPolicy);
|
||||
expect(findData.at(1).text()).toEqual(endpointData.policyStatus);
|
||||
expect(findData.at(2).text()).toContain(endpointData.sensorVersion); // contain because drag adds a space
|
||||
expect(findData.at(3).text()).toEqual('Healthy');
|
||||
});
|
||||
test('it renders with null data', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<EndpointOverview data={null} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
const findData = wrapper.find(
|
||||
'dl[data-test-subj="endpoint-overview"] dd.euiDescriptionList__description'
|
||||
);
|
||||
test('it renders with null data', () => {
|
||||
render(null);
|
||||
expect(findData.at(0).text()).toEqual('—');
|
||||
expect(findData.at(1).text()).toEqual('—');
|
||||
expect(findData.at(2).text()).toContain('—'); // contain because drag adds a space
|
||||
expect(findData.at(3).text()).toEqual('—');
|
||||
});
|
||||
|
||||
test('it shows isolation status', () => {
|
||||
endpointData.isolation = true;
|
||||
render();
|
||||
expect(findData.at(3).text()).toEqual('HealthyIsolated');
|
||||
});
|
||||
|
||||
test.each([
|
||||
['isolate', 'Isolating'],
|
||||
['unisolate', 'Releasing'],
|
||||
])('it shows pending %s status', (action, expectedLabel) => {
|
||||
endpointData.isolation = true;
|
||||
endpointData.pendingActions![action] = 1;
|
||||
render();
|
||||
expect(findData.at(3).text()).toEqual(`Healthy${expectedLabel}`);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -18,6 +18,8 @@ import {
|
|||
EndpointFields,
|
||||
HostPolicyResponseActionStatus,
|
||||
} from '../../../../../common/search_strategy/security_solution/hosts';
|
||||
import { AgentStatus } from '../../../../common/components/endpoint/agent_status';
|
||||
import { EndpointHostIsolationStatus } from '../../../../common/components/endpoint/host_isolation';
|
||||
|
||||
interface Props {
|
||||
contextID?: string;
|
||||
|
@ -73,7 +75,24 @@ export const EndpointOverview = React.memo<Props>(({ contextID, data }) => {
|
|||
: getEmptyTagValue(),
|
||||
},
|
||||
],
|
||||
[], // needs 4 columns for design
|
||||
[
|
||||
{
|
||||
title: i18n.FLEET_AGENT_STATUS,
|
||||
description:
|
||||
data != null && data.elasticAgentStatus ? (
|
||||
<>
|
||||
<AgentStatus hostStatus={data.elasticAgentStatus} />
|
||||
<EndpointHostIsolationStatus
|
||||
isIsolated={Boolean(data.isolation)}
|
||||
pendingIsolate={data.pendingActions?.isolate ?? 0}
|
||||
pendingUnIsolate={data.pendingActions?.unisolate ?? 0}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
getEmptyTagValue()
|
||||
),
|
||||
},
|
||||
],
|
||||
],
|
||||
[data, getDefaultRenderer]
|
||||
);
|
||||
|
|
|
@ -10,7 +10,7 @@ import { i18n } from '@kbn/i18n';
|
|||
export const ENDPOINT_POLICY = i18n.translate(
|
||||
'xpack.securitySolution.host.details.endpoint.endpointPolicy',
|
||||
{
|
||||
defaultMessage: 'Integration',
|
||||
defaultMessage: 'Endpoint integration policy',
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -24,6 +24,13 @@ export const POLICY_STATUS = i18n.translate(
|
|||
export const SENSORVERSION = i18n.translate(
|
||||
'xpack.securitySolution.host.details.endpoint.sensorversion',
|
||||
{
|
||||
defaultMessage: 'Sensor Version',
|
||||
defaultMessage: 'Endpoint version',
|
||||
}
|
||||
);
|
||||
|
||||
export const FLEET_AGENT_STATUS = i18n.translate(
|
||||
'xpack.securitySolution.host.details.endpoint.fleetAgentStatus',
|
||||
{
|
||||
defaultMessage: 'Agent status',
|
||||
}
|
||||
);
|
||||
|
|
|
@ -10,7 +10,7 @@ import {
|
|||
EndpointActionLogRequestParams,
|
||||
EndpointActionLogRequestQuery,
|
||||
} from '../../../../common/endpoint/schema/actions';
|
||||
import { getAuditLogResponse } from './service';
|
||||
import { getAuditLogResponse } from '../../services';
|
||||
import { SecuritySolutionRequestHandlerContext } from '../../../types';
|
||||
import { EndpointAppContext } from '../../types';
|
||||
|
||||
|
|
|
@ -5,15 +5,8 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { ElasticsearchClient, RequestHandler } from 'kibana/server';
|
||||
import { RequestHandler } from 'kibana/server';
|
||||
import { TypeOf } from '@kbn/config-schema';
|
||||
import { SearchRequest } from '@elastic/elasticsearch/api/types';
|
||||
import {
|
||||
EndpointAction,
|
||||
EndpointActionResponse,
|
||||
EndpointPendingActions,
|
||||
} from '../../../../common/endpoint/types';
|
||||
import { AGENT_ACTIONS_INDEX } from '../../../../../fleet/common';
|
||||
import { ActionStatusRequestSchema } from '../../../../common/endpoint/schema/actions';
|
||||
import { ACTION_STATUS_ROUTE } from '../../../../common/endpoint/constants';
|
||||
import {
|
||||
|
@ -21,6 +14,7 @@ import {
|
|||
SecuritySolutionRequestHandlerContext,
|
||||
} from '../../../types';
|
||||
import { EndpointAppContext } from '../../types';
|
||||
import { getPendingActionCounts } from '../../services';
|
||||
|
||||
/**
|
||||
* Registers routes for checking status of endpoints based on pending actions
|
||||
|
@ -53,7 +47,7 @@ export const actionStatusRequestHandler = function (
|
|||
? [...new Set(req.query.agent_ids)]
|
||||
: [req.query.agent_ids];
|
||||
|
||||
const response = await getPendingActions(esClient, agentIDs);
|
||||
const response = await getPendingActionCounts(esClient, agentIDs);
|
||||
|
||||
return res.ok({
|
||||
body: {
|
||||
|
@ -62,94 +56,3 @@ export const actionStatusRequestHandler = function (
|
|||
});
|
||||
};
|
||||
};
|
||||
|
||||
const getPendingActions = async (
|
||||
esClient: ElasticsearchClient,
|
||||
agentIDs: string[]
|
||||
): Promise<EndpointPendingActions[]> => {
|
||||
// retrieve the unexpired actions for the given hosts
|
||||
|
||||
const recentActions = await searchUntilEmpty<EndpointAction>(esClient, {
|
||||
index: AGENT_ACTIONS_INDEX,
|
||||
body: {
|
||||
query: {
|
||||
bool: {
|
||||
filter: [
|
||||
{ term: { type: 'INPUT_ACTION' } }, // actions that are directed at agent children
|
||||
{ term: { input_type: 'endpoint' } }, // filter for agent->endpoint actions
|
||||
{ range: { expiration: { gte: 'now' } } }, // that have not expired yet
|
||||
{ terms: { agents: agentIDs } }, // for the requested agent IDs
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// retrieve any responses to those action IDs from these agents
|
||||
const actionIDs = recentActions.map((a) => a.action_id);
|
||||
const responses = await searchUntilEmpty<EndpointActionResponse>(esClient, {
|
||||
index: '.fleet-actions-results',
|
||||
body: {
|
||||
query: {
|
||||
bool: {
|
||||
filter: [
|
||||
{ terms: { action_id: actionIDs } }, // get results for these actions
|
||||
{ terms: { agent_id: agentIDs } }, // ignoring responses from agents we're not looking for
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// respond with action-count per agent
|
||||
const pending: EndpointPendingActions[] = agentIDs.map((aid) => {
|
||||
const responseIDsFromAgent = responses
|
||||
.filter((r) => r.agent_id === aid)
|
||||
.map((r) => r.action_id);
|
||||
return {
|
||||
agent_id: aid,
|
||||
pending_actions: recentActions
|
||||
.filter((a) => a.agents.includes(aid) && !responseIDsFromAgent.includes(a.action_id))
|
||||
.map((a) => a.data.command)
|
||||
.reduce((acc, cur) => {
|
||||
if (cur in acc) {
|
||||
acc[cur] += 1;
|
||||
} else {
|
||||
acc[cur] = 1;
|
||||
}
|
||||
return acc;
|
||||
}, {} as EndpointPendingActions['pending_actions']),
|
||||
};
|
||||
});
|
||||
|
||||
return pending;
|
||||
};
|
||||
|
||||
const searchUntilEmpty = async <T>(
|
||||
esClient: ElasticsearchClient,
|
||||
query: SearchRequest,
|
||||
pageSize: number = 1000
|
||||
): Promise<T[]> => {
|
||||
const results: T[] = [];
|
||||
|
||||
for (let i = 0; ; i++) {
|
||||
const result = await esClient.search<T>(
|
||||
{
|
||||
size: pageSize,
|
||||
from: i * pageSize,
|
||||
...query,
|
||||
},
|
||||
{
|
||||
ignore: [404],
|
||||
}
|
||||
);
|
||||
if (!result || !result.body?.hits?.hits || result.body?.hits?.hits?.length === 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
const response = result.body?.hits?.hits?.map((a) => a._source!) || [];
|
||||
results.push(...response);
|
||||
}
|
||||
|
||||
return results;
|
||||
};
|
||||
|
|
|
@ -25,13 +25,14 @@ import {
|
|||
import type { SecuritySolutionRequestHandlerContext } from '../../../types';
|
||||
|
||||
import { getESQueryHostMetadataByID, kibanaRequestToMetadataListESQuery } from './query_builders';
|
||||
import { Agent, AgentStatus, PackagePolicy } from '../../../../../fleet/common/types/models';
|
||||
import { Agent, PackagePolicy } from '../../../../../fleet/common/types/models';
|
||||
import { AgentNotFoundError } from '../../../../../fleet/server';
|
||||
import { EndpointAppContext, HostListQueryResult } from '../../types';
|
||||
import { GetMetadataListRequestSchema, GetMetadataRequestSchema } from './index';
|
||||
import { findAllUnenrolledAgentIds } from './support/unenroll';
|
||||
import { findAgentIDsByStatus } from './support/agent_status';
|
||||
import { EndpointAppContextService } from '../../endpoint_app_context_services';
|
||||
import { fleetAgentStatusToEndpointHostStatus } from '../../utils';
|
||||
|
||||
export interface MetadataRequestContext {
|
||||
esClient?: IScopedClusterClient;
|
||||
|
@ -41,18 +42,6 @@ export interface MetadataRequestContext {
|
|||
savedObjectsClient?: SavedObjectsClientContract;
|
||||
}
|
||||
|
||||
const HOST_STATUS_MAPPING = new Map<AgentStatus, HostStatus>([
|
||||
['online', HostStatus.HEALTHY],
|
||||
['offline', HostStatus.OFFLINE],
|
||||
['inactive', HostStatus.INACTIVE],
|
||||
['unenrolling', HostStatus.UPDATING],
|
||||
['enrolling', HostStatus.UPDATING],
|
||||
['updating', HostStatus.UPDATING],
|
||||
['warning', HostStatus.UNHEALTHY],
|
||||
['error', HostStatus.UNHEALTHY],
|
||||
['degraded', HostStatus.UNHEALTHY],
|
||||
]);
|
||||
|
||||
/**
|
||||
* 00000000-0000-0000-0000-000000000000 is initial Elastic Agent id sent by Endpoint before policy is configured
|
||||
* 11111111-1111-1111-1111-111111111111 is Elastic Agent id sent by Endpoint when policy does not contain an id
|
||||
|
@ -375,7 +364,7 @@ export async function enrichHostMetadata(
|
|||
const status = await metadataRequestContext.endpointAppContextService
|
||||
?.getAgentService()
|
||||
?.getAgentStatusById(esClient.asCurrentUser, elasticAgentId);
|
||||
hostStatus = HOST_STATUS_MAPPING.get(status!) || HostStatus.UNHEALTHY;
|
||||
hostStatus = fleetAgentStatusToEndpointHostStatus(status!);
|
||||
} catch (e) {
|
||||
if (e instanceof AgentNotFoundError) {
|
||||
log.warn(`agent with id ${elasticAgentId} not found`);
|
||||
|
|
|
@ -6,9 +6,14 @@
|
|||
*/
|
||||
|
||||
import { ElasticsearchClient, Logger } from 'kibana/server';
|
||||
import { AGENT_ACTIONS_INDEX, AGENT_ACTIONS_RESULTS_INDEX } from '../../../../../fleet/common';
|
||||
import { SecuritySolutionRequestHandlerContext } from '../../../types';
|
||||
import { ActivityLog, EndpointAction } from '../../../../common/endpoint/types';
|
||||
import { AGENT_ACTIONS_INDEX, AGENT_ACTIONS_RESULTS_INDEX } from '../../../../fleet/common';
|
||||
import { SecuritySolutionRequestHandlerContext } from '../../types';
|
||||
import {
|
||||
ActivityLog,
|
||||
EndpointAction,
|
||||
EndpointActionResponse,
|
||||
EndpointPendingActions,
|
||||
} from '../../../common/endpoint/types';
|
||||
|
||||
export const getAuditLogResponse = async ({
|
||||
elasticAgentId,
|
||||
|
@ -135,3 +140,78 @@ const getActivityLog = async ({
|
|||
|
||||
return sortedData;
|
||||
};
|
||||
|
||||
export const getPendingActionCounts = async (
|
||||
esClient: ElasticsearchClient,
|
||||
agentIDs: string[]
|
||||
): Promise<EndpointPendingActions[]> => {
|
||||
// retrieve the unexpired actions for the given hosts
|
||||
const recentActions = await esClient
|
||||
.search<EndpointAction>(
|
||||
{
|
||||
index: AGENT_ACTIONS_INDEX,
|
||||
size: 10000,
|
||||
from: 0,
|
||||
body: {
|
||||
query: {
|
||||
bool: {
|
||||
filter: [
|
||||
{ term: { type: 'INPUT_ACTION' } }, // actions that are directed at agent children
|
||||
{ term: { input_type: 'endpoint' } }, // filter for agent->endpoint actions
|
||||
{ range: { expiration: { gte: 'now' } } }, // that have not expired yet
|
||||
{ terms: { agents: agentIDs } }, // for the requested agent IDs
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{ ignore: [404] }
|
||||
)
|
||||
.then((result) => result.body?.hits?.hits?.map((a) => a._source!) || []);
|
||||
|
||||
// retrieve any responses to those action IDs from these agents
|
||||
const actionIDs = recentActions.map((a) => a.action_id);
|
||||
const responses = await esClient
|
||||
.search<EndpointActionResponse>(
|
||||
{
|
||||
index: AGENT_ACTIONS_RESULTS_INDEX,
|
||||
size: 10000,
|
||||
from: 0,
|
||||
body: {
|
||||
query: {
|
||||
bool: {
|
||||
filter: [
|
||||
{ terms: { action_id: actionIDs } }, // get results for these actions
|
||||
{ terms: { agent_id: agentIDs } }, // ignoring responses from agents we're not looking for
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{ ignore: [404] }
|
||||
)
|
||||
.then((result) => result.body?.hits?.hits?.map((a) => a._source!) || []);
|
||||
|
||||
// respond with action-count per agent
|
||||
const pending: EndpointPendingActions[] = agentIDs.map((aid) => {
|
||||
const responseIDsFromAgent = responses
|
||||
.filter((r) => r.agent_id === aid)
|
||||
.map((r) => r.action_id);
|
||||
return {
|
||||
agent_id: aid,
|
||||
pending_actions: recentActions
|
||||
.filter((a) => a.agents.includes(aid) && !responseIDsFromAgent.includes(a.action_id))
|
||||
.map((a) => a.data.command)
|
||||
.reduce((acc, cur) => {
|
||||
if (cur in acc) {
|
||||
acc[cur] += 1;
|
||||
} else {
|
||||
acc[cur] = 1;
|
||||
}
|
||||
return acc;
|
||||
}, {} as EndpointPendingActions['pending_actions']),
|
||||
};
|
||||
});
|
||||
|
||||
return pending;
|
||||
};
|
|
@ -7,3 +7,4 @@
|
|||
|
||||
export * from './artifacts';
|
||||
export { getMetadataForEndpoints } from './metadata';
|
||||
export * from './actions';
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* 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 { AgentStatus } from '../../../../fleet/common';
|
||||
import { HostStatus } from '../../../common/endpoint/types';
|
||||
|
||||
const STATUS_MAPPING: ReadonlyMap<AgentStatus, HostStatus> = new Map([
|
||||
['online', HostStatus.HEALTHY],
|
||||
['offline', HostStatus.OFFLINE],
|
||||
['inactive', HostStatus.INACTIVE],
|
||||
['unenrolling', HostStatus.UPDATING],
|
||||
['enrolling', HostStatus.UPDATING],
|
||||
['updating', HostStatus.UPDATING],
|
||||
['warning', HostStatus.UNHEALTHY],
|
||||
['error', HostStatus.UNHEALTHY],
|
||||
['degraded', HostStatus.UNHEALTHY],
|
||||
]);
|
||||
|
||||
/**
|
||||
* A Map of Fleet Agent Status to Endpoint Host Status.
|
||||
* Default status is `HostStatus.UNHEALTHY`
|
||||
*/
|
||||
export const fleetAgentStatusToEndpointHostStatus = (status: AgentStatus): HostStatus => {
|
||||
return STATUS_MAPPING.get(status) || HostStatus.UNHEALTHY;
|
||||
};
|
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export * from './fleet_agent_status_to_endpoint_host_status';
|
|
@ -24,6 +24,8 @@ import {
|
|||
import { toObjectArrayOfStrings } from '../../../../../../common/utils/to_array';
|
||||
import { getHostMetaData } from '../../../../../endpoint/routes/metadata/handlers';
|
||||
import { EndpointAppContext } from '../../../../../endpoint/types';
|
||||
import { fleetAgentStatusToEndpointHostStatus } from '../../../../../endpoint/utils';
|
||||
import { getPendingActionCounts } from '../../../../../endpoint/services';
|
||||
|
||||
export const HOST_FIELDS = [
|
||||
'_id',
|
||||
|
@ -200,15 +202,30 @@ export const getHostEndpoint = async (
|
|||
? await getHostMetaData(metadataRequestContext, id, undefined)
|
||||
: null;
|
||||
|
||||
const fleetAgentId = endpointData?.metadata.elastic.agent.id;
|
||||
const [fleetAgentStatus, pendingActions] = !fleetAgentId
|
||||
? [undefined, {}]
|
||||
: await Promise.all([
|
||||
// Get Agent Status
|
||||
agentService.getAgentStatusById(esClient.asCurrentUser, fleetAgentId),
|
||||
// Get a list of pending actions (if any)
|
||||
getPendingActionCounts(esClient.asCurrentUser, [fleetAgentId]).then((results) => {
|
||||
return results[0].pending_actions;
|
||||
}),
|
||||
]);
|
||||
|
||||
return endpointData != null && endpointData.metadata
|
||||
? {
|
||||
endpointPolicy: endpointData.metadata.Endpoint.policy.applied.name,
|
||||
policyStatus: endpointData.metadata.Endpoint.policy.applied.status,
|
||||
sensorVersion: endpointData.metadata.agent.version,
|
||||
elasticAgentStatus: fleetAgentStatusToEndpointHostStatus(fleetAgentStatus!),
|
||||
isolation: endpointData.metadata.Endpoint.state?.isolation ?? false,
|
||||
pendingActions,
|
||||
}
|
||||
: null;
|
||||
} catch (err) {
|
||||
logger.warn(JSON.stringify(err, null, 2));
|
||||
logger.warn(err);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue