Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* 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 React from 'react';
import { EuiBadge } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { HostStatus } from '../../../../common/endpoint/types';
import { HOST_STATUS_TO_BADGE_COLOR } from '../../../management/pages/endpoint_hosts/view/host_constants';

export const AgentStatus = React.memo(({ hostStatus }: { hostStatus: HostStatus }) => {
return (
<EuiBadge
color={hostStatus != null ? HOST_STATUS_TO_BADGE_COLOR[hostStatus] : 'warning'}
data-test-subj="rowHostStatus"
className="eui-textTruncate"
>
<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.displayName = 'AgentStatus';
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
EuiDescriptionListTitle,
EuiSpacer,
} from '@elastic/eui';
import { get, getOr } from 'lodash/fp';
import { get, getOr, find } from 'lodash/fp';
import React, { useMemo } from 'react';
import styled from 'styled-components';

Expand Down Expand Up @@ -53,6 +53,7 @@ const fields = [
{ id: 'signal.rule.severity', label: ALERTS_HEADERS_SEVERITY },
{ id: 'signal.rule.risk_score', label: ALERTS_HEADERS_RISK_SCORE },
{ id: 'host.name' },
{ id: 'host.status' },
{ id: 'user.name' },
{ id: SOURCE_IP_FIELD_NAME, fieldType: IP_FIELD_TYPE },
{ id: DESTINATION_IP_FIELD_NAME, fieldType: IP_FIELD_TYPE },
Expand Down Expand Up @@ -177,6 +178,24 @@ const AlertSummaryViewComponent: React.FC<{
timelineId,
]);

const agentId = useMemo(() => {
const findAgentId = find({ category: 'agent', field: 'agent.id' }, data)?.values;
return findAgentId ? findAgentId[0] : '';
}, [data]);

const agentStatusRow = {
title: i18n.AGENT_STATUS,
description: {
contextId: timelineId,
eventId,
fieldName: 'host.status',
value: agentId,
linkValue: undefined,
},
};

const summaryRowsWithAgentStatus = [...summaryRows, agentStatusRow];

const ruleId = useMemo(() => {
const item = data.find((d) => d.field === 'signal.rule.id');
return Array.isArray(item?.originalValue)
Expand All @@ -188,7 +207,11 @@ const AlertSummaryViewComponent: React.FC<{
return (
<>
<EuiSpacer size="l" />
<SummaryView summaryColumns={summaryColumns} summaryRows={summaryRows} title={title} />
<SummaryView
summaryColumns={summaryColumns}
summaryRows={summaryRowsWithAgentStatus}
title={title}
/>
{maybeRule?.note && (
<StyledEuiDescriptionList data-test-subj={`summary-view-guide`} compressed>
<EuiDescriptionListTitle>{i18n.INVESTIGATION_GUIDE}</EuiDescriptionListTitle>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,7 @@ export const NESTED_COLUMN = (field: string) =>
defaultMessage:
'The {field} field is an object, and is broken down into nested fields which can be added as column',
});

export const AGENT_STATUS = i18n.translate('xpack.securitySolution.detections.alerts.agentStatus', {
defaultMessage: 'Agent status',
});
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import { UpdateDocumentByQueryResponse } from 'elasticsearch';
import { getCasesFromAlertsUrl } from '../../../../../../cases/common';
import { HostIsolationResponse, HostMetadataInfo } from '../../../../../common/endpoint/types';
import { HostIsolationResponse, HostInfo } from '../../../../../common/endpoint/types';
import {
DETECTION_ENGINE_QUERY_SIGNALS_URL,
DETECTION_ENGINE_SIGNALS_STATUS_URL,
Expand Down Expand Up @@ -178,12 +178,8 @@ export const getCaseIdsFromAlertId = async ({
*
* @param host id
*/
export const getHostMetadata = async ({
agentId,
}: {
agentId: string;
}): Promise<HostMetadataInfo> =>
KibanaServices.get().http.fetch<HostMetadataInfo>(
export const getHostMetadata = async ({ agentId }: { agentId: string }): Promise<HostInfo> =>
KibanaServices.get().http.fetch<HostInfo>(
resolvePathVariables(HOST_METADATA_GET_ROUTE, { id: agentId }),
{ method: 'get' }
);
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,27 @@

import { isEmpty } from 'lodash';
import { useEffect, useState } from 'react';
import { Maybe } from '../../../../../../observability/common/typings';
import { useAppToasts } from '../../../../common/hooks/use_app_toasts';
import { getHostMetadata } from './api';
import { ISOLATION_STATUS_FAILURE } from './translations';
import { isEndpointHostIsolated } from '../../../../common/utils/validators';
import { HostStatus } from '../../../../../common/endpoint/types';

interface HostIsolationStatusResponse {
loading: boolean;
isIsolated: Maybe<boolean>;
isIsolated: boolean;
agentStatus: HostStatus;
}

/*
* Retrieves the current isolation status of a host */
* Retrieves the current isolation status of a host and the agent/host status */
export const useHostIsolationStatus = ({
agentId,
}: {
agentId: string;
}): HostIsolationStatusResponse => {
const [isIsolated, setIsIsolated] = useState<Maybe<boolean>>();
const [isIsolated, setIsIsolated] = useState<boolean>(false);
const [agentStatus, setAgentStatus] = useState<HostStatus>(HostStatus.UNHEALTHY);
const [loading, setLoading] = useState(false);

const { addError } = useAppToasts();
Expand All @@ -38,6 +40,7 @@ export const useHostIsolationStatus = ({
const metadataResponse = await getHostMetadata({ agentId });
if (isMounted) {
setIsIsolated(isEndpointHostIsolated(metadataResponse.metadata));
setAgentStatus(metadataResponse.host_status);
}
} catch (error) {
addError(error.message, { title: ISOLATION_STATUS_FAILURE });
Expand All @@ -61,5 +64,5 @@ export const useHostIsolationStatus = ({
isMounted = false;
};
}, [addError, agentId]);
return { loading, isIsolated };
return { loading, isIsolated, agentStatus };
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* 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 React from 'react';
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { DefaultDraggable } from '../../../../../common/components/draggables';
import { EndpointHostIsolationStatus } from '../../../../../common/components/endpoint/host_isolation';
import { useHostIsolationStatus } from '../../../../../detections/containers/detection_engine/alerts/use_host_isolation_status';
import { AgentStatus } from '../../../../../common/components/endpoint/agent_status';

export const AgentStatuses = React.memo(
({
fieldName,
contextId,
eventId,
value,
}: {
fieldName: string;
contextId: string;
eventId: string;
value: string;
}) => {
const { isIsolated, agentStatus } = useHostIsolationStatus({ agentId: value });
const isolationFieldName = 'host.isolation';
return (
<EuiFlexGroup gutterSize="none">
<EuiFlexItem grow={false}>
<DefaultDraggable
field={fieldName}
id={`event-details-value-default-draggable-${contextId}-${eventId}-${fieldName}-${value}`}
tooltipContent={fieldName}
value={`${agentStatus}`}
>
<AgentStatus hostStatus={agentStatus} />
</DefaultDraggable>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<DefaultDraggable
field={isolationFieldName}
id={`event-details-value-default-draggable-${contextId}-${eventId}-${isolationFieldName}-${value}`}
tooltipContent={isolationFieldName}
value={`${isIsolated}`}
>
<EndpointHostIsolationStatus isIsolated={isIsolated} />
</DefaultDraggable>
</EuiFlexItem>
</EuiFlexGroup>
);
}
);

AgentStatuses.displayName = 'AgentStatuses';
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ export const REFERENCE_URL_FIELD_NAME = 'reference.url';
export const EVENT_URL_FIELD_NAME = 'event.url';
export const SIGNAL_RULE_NAME_FIELD_NAME = 'signal.rule.name';
export const SIGNAL_STATUS_FIELD_NAME = 'signal.status';
export const HOST_STATUS_FIELD_NAME = 'host.status';
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
* 2.0.
*/

/* eslint-disable complexity */

import { EuiFlexGroup, EuiFlexItem, EuiToolTip } from '@elastic/eui';
import { isNumber, isEmpty } from 'lodash/fp';
import React from 'react';
Expand All @@ -30,11 +32,13 @@ import {
REFERENCE_URL_FIELD_NAME,
EVENT_URL_FIELD_NAME,
SIGNAL_STATUS_FIELD_NAME,
HOST_STATUS_FIELD_NAME,
GEO_FIELD_TYPE,
} from './constants';
import { RenderRuleName, renderEventModule, renderUrl } from './formatted_field_helpers';
import { RuleStatus } from './rule_status';
import { HostName } from './host_name';
import { AgentStatuses } from './agent_statuses';

// simple black-list to prevent dragging and dropping fields such as message name
const columnNamesNotDraggable = [MESSAGE_FIELD_NAME];
Expand Down Expand Up @@ -116,6 +120,15 @@ const FormattedFieldValueComponent: React.FC<{
return (
<RuleStatus contextId={contextId} eventId={eventId} fieldName={fieldName} value={value} />
);
} else if (fieldName === HOST_STATUS_FIELD_NAME) {
return (
<AgentStatuses
contextId={contextId}
eventId={eventId}
fieldName={fieldName}
value={value as string}
/>
);
} else if (
[
RULE_REFERENCE_FIELD_NAME,
Expand Down