Skip to content

Commit

Permalink
[Security Solution][Endpoint][Host Isolation] Isolation status badge …
Browse files Browse the repository at this point in the history
…from alert details (elastic#102274)
  • Loading branch information
parkiino committed Jun 21, 2021
1 parent b3a59f5 commit a6c898a
Show file tree
Hide file tree
Showing 8 changed files with 140 additions and 14 deletions.
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

0 comments on commit a6c898a

Please sign in to comment.