mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Security solution] [Endpoint] Improve policy response info debugging (#134351)
* [Security Solution] bubble up policy response errors * Adds policy troubleshooting links in doc links package * Bubble up certain errors on policy response like full_disk_access error * Fixes unit test and removes unused component * Removes unused style * Use link instead of button for documentation links Co-authored-by: Joey F. Poon <joey.poon@elastic.co> Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
d071e52c51
commit
60be4e3db8
7 changed files with 536 additions and 290 deletions
|
@ -343,6 +343,9 @@ export const getDocLinks = ({ kibanaBranch }: GetDocLinkOptions): DocLinks => {
|
|||
trustedApps: `${ELASTIC_WEBSITE_URL}guide/en/security/${DOC_LINK_VERSION}/trusted-apps-ov.html`,
|
||||
eventFilters: `${ELASTIC_WEBSITE_URL}guide/en/security/${DOC_LINK_VERSION}/event-filters.html`,
|
||||
blocklist: `${ELASTIC_WEBSITE_URL}guide/en/security/${DOC_LINK_VERSION}/blocklist.html`,
|
||||
policyResponseTroubleshooting: {
|
||||
full_disk_access: `${ELASTIC_WEBSITE_URL}guide/en/security/${DOC_LINK_VERSION}/deploy-elastic-endpoint.html#enable-fda-endpoint`,
|
||||
},
|
||||
},
|
||||
query: {
|
||||
eql: `${ELASTICSEARCH_DOCS}eql.html`,
|
||||
|
|
|
@ -248,6 +248,9 @@ export interface DocLinks {
|
|||
readonly trustedApps: string;
|
||||
readonly eventFilters: string;
|
||||
readonly blocklist: string;
|
||||
readonly policyResponseTroubleshooting: {
|
||||
full_disk_access: string;
|
||||
};
|
||||
};
|
||||
readonly query: {
|
||||
readonly eql: string;
|
||||
|
|
|
@ -8,7 +8,9 @@
|
|||
import React, { memo, useCallback } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { DocLinksStart } from '@kbn/core/public';
|
||||
import { EuiHealth, EuiText, EuiTreeView, EuiNotificationBadge } from '@elastic/eui';
|
||||
import { useKibana } from '../../../common/lib/kibana';
|
||||
import {
|
||||
HostPolicyResponseActionStatus,
|
||||
HostPolicyResponseAppliedAction,
|
||||
|
@ -17,7 +19,7 @@ import {
|
|||
ImmutableArray,
|
||||
ImmutableObject,
|
||||
} from '../../../../common/endpoint/types';
|
||||
import { formatResponse } from './policy_response_friendly_names';
|
||||
import { formatResponse, PolicyResponseActionFormatter } from './policy_response_friendly_names';
|
||||
import { PolicyResponseActionItem } from './policy_response_action_item';
|
||||
|
||||
// Most of them are needed in order to display large react nodes (PolicyResponseActionItem) in child levels.
|
||||
|
@ -59,6 +61,7 @@ export const PolicyResponse = memo(
|
|||
policyResponseActions,
|
||||
policyResponseAttentionCount,
|
||||
}: PolicyResponseProps) => {
|
||||
const { docLinks } = useKibana().services;
|
||||
const getEntryIcon = useCallback(
|
||||
(status: HostPolicyResponseActionStatus, unsuccessCounts: number) =>
|
||||
status === HostPolicyResponseActionStatus.success ? (
|
||||
|
@ -88,6 +91,12 @@ export const PolicyResponse = memo(
|
|||
(currentAction) => currentAction.name === actionKey
|
||||
) as ImmutableObject<HostPolicyResponseAppliedAction>;
|
||||
|
||||
const policyResponseActionFormatter = new PolicyResponseActionFormatter(
|
||||
action || {},
|
||||
docLinks.links.securitySolution.policyResponseTroubleshooting[
|
||||
action.name as keyof DocLinksStart['links']['securitySolution']['policyResponseTroubleshooting']
|
||||
]
|
||||
);
|
||||
return {
|
||||
label: (
|
||||
<EuiText
|
||||
|
@ -99,7 +108,7 @@ export const PolicyResponse = memo(
|
|||
}
|
||||
data-test-subj="endpointPolicyResponseAction"
|
||||
>
|
||||
{formatResponse(actionKey)}
|
||||
{policyResponseActionFormatter.title}
|
||||
</EuiText>
|
||||
),
|
||||
id: actionKey,
|
||||
|
@ -116,11 +125,7 @@ export const PolicyResponse = memo(
|
|||
{
|
||||
label: (
|
||||
<PolicyResponseActionItem
|
||||
status={action.status}
|
||||
actionTitle={action.name}
|
||||
actionMessage={action.message}
|
||||
// actionButtonLabel="Do something" // TODO
|
||||
// actionButtonOnClick={() => {}} // TODO
|
||||
policyResponseActionFormatter={policyResponseActionFormatter}
|
||||
/>
|
||||
),
|
||||
id: `action_message_${actionKey}`,
|
||||
|
@ -135,7 +140,11 @@ export const PolicyResponse = memo(
|
|||
};
|
||||
});
|
||||
},
|
||||
[getEntryIcon, policyResponseActions]
|
||||
[
|
||||
docLinks.links.securitySolution.policyResponseTroubleshooting,
|
||||
getEntryIcon,
|
||||
policyResponseActions,
|
||||
]
|
||||
);
|
||||
|
||||
const getResponseConfigs = useCallback(
|
||||
|
|
|
@ -7,8 +7,8 @@
|
|||
|
||||
import React, { memo } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { EuiButton, EuiCallOut, EuiText, EuiSpacer } from '@elastic/eui';
|
||||
import { HostPolicyResponseActionStatus } from '../../../../common/endpoint/types';
|
||||
import { EuiLink, EuiCallOut, EuiText } from '@elastic/eui';
|
||||
import { PolicyResponseActionFormatter } from './policy_response_friendly_names';
|
||||
|
||||
const StyledEuiCallout = styled(EuiCallOut)`
|
||||
padding: ${({ theme }) => theme.eui.paddingSizes.s};
|
||||
|
@ -19,39 +19,36 @@ const StyledEuiCallout = styled(EuiCallOut)`
|
|||
`;
|
||||
|
||||
interface PolicyResponseActionItemProps {
|
||||
status: HostPolicyResponseActionStatus;
|
||||
actionTitle: string;
|
||||
actionMessage: string;
|
||||
actionButtonLabel?: string;
|
||||
actionButtonOnClick?: () => void;
|
||||
policyResponseActionFormatter: PolicyResponseActionFormatter;
|
||||
}
|
||||
/**
|
||||
* A policy response action item
|
||||
*/
|
||||
export const PolicyResponseActionItem = memo(
|
||||
({
|
||||
status,
|
||||
actionTitle,
|
||||
actionMessage,
|
||||
actionButtonLabel,
|
||||
actionButtonOnClick,
|
||||
}: PolicyResponseActionItemProps) => {
|
||||
return status !== HostPolicyResponseActionStatus.success &&
|
||||
status !== HostPolicyResponseActionStatus.unsupported ? (
|
||||
<StyledEuiCallout title={actionTitle} color="danger" iconType="alert">
|
||||
({ policyResponseActionFormatter }: PolicyResponseActionItemProps) => {
|
||||
return policyResponseActionFormatter.hasError ? (
|
||||
<StyledEuiCallout
|
||||
title={policyResponseActionFormatter.errorTitle}
|
||||
color="danger"
|
||||
iconType="alert"
|
||||
data-test-subj="endpointPolicyResponseErrorCallOut"
|
||||
>
|
||||
<EuiText size="s" className="action-message" data-test-subj="endpointPolicyResponseMessage">
|
||||
{actionMessage}
|
||||
{policyResponseActionFormatter.errorDescription}
|
||||
{policyResponseActionFormatter.linkText && policyResponseActionFormatter.linkUrl && (
|
||||
<EuiLink
|
||||
target="_blank"
|
||||
href={`${policyResponseActionFormatter.linkUrl}`}
|
||||
data-test-subj="endpointPolicyResponseErrorCallOutLink"
|
||||
>
|
||||
{policyResponseActionFormatter.linkText}
|
||||
</EuiLink>
|
||||
)}
|
||||
</EuiText>
|
||||
<EuiSpacer size="s" />
|
||||
{actionButtonLabel && actionButtonOnClick && (
|
||||
<EuiButton onClick={actionButtonOnClick} color="danger">
|
||||
{actionButtonLabel}
|
||||
</EuiButton>
|
||||
)}
|
||||
</StyledEuiCallout>
|
||||
) : (
|
||||
<EuiText size="xs" data-test-subj="endpointPolicyResponseMessage">
|
||||
{actionMessage}
|
||||
{policyResponseActionFormatter.description || policyResponseActionFormatter.title}
|
||||
</EuiText>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -6,270 +6,418 @@
|
|||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
HostPolicyResponseActionStatus,
|
||||
HostPolicyResponseAppliedAction,
|
||||
ImmutableObject,
|
||||
} from '../../../../common/endpoint/types';
|
||||
|
||||
const policyResponses: Array<[string, string]> = [
|
||||
[
|
||||
'configure_dns_events',
|
||||
i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.configure_dns_events', {
|
||||
defaultMessage: 'Configure DNS Events',
|
||||
}),
|
||||
],
|
||||
[
|
||||
'configure_elasticsearch_connection',
|
||||
i18n.translate(
|
||||
'xpack.securitySolution.endpoint.details.policyResponse.configure_elasticsearch_connection',
|
||||
{ defaultMessage: 'Configure Elasticsearch Connection' }
|
||||
),
|
||||
],
|
||||
[
|
||||
'configure_file_events',
|
||||
i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.configure_file_events', {
|
||||
defaultMessage: 'Configure File Events',
|
||||
}),
|
||||
],
|
||||
[
|
||||
'configure_imageload_events',
|
||||
i18n.translate(
|
||||
'xpack.securitySolution.endpoint.details.policyResponse.configure_imageload_events',
|
||||
{ defaultMessage: 'Configure Image Load Events' }
|
||||
),
|
||||
],
|
||||
[
|
||||
'configure_kernel',
|
||||
i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.configure_kernel', {
|
||||
defaultMessage: 'Configure Kernel',
|
||||
}),
|
||||
],
|
||||
[
|
||||
'configure_logging',
|
||||
i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.configure_logging', {
|
||||
defaultMessage: 'Configure Logging',
|
||||
}),
|
||||
],
|
||||
[
|
||||
'configure_malware',
|
||||
i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.configure_malware', {
|
||||
defaultMessage: 'Configure Malware',
|
||||
}),
|
||||
],
|
||||
[
|
||||
'configure_network_events',
|
||||
i18n.translate(
|
||||
'xpack.securitySolution.endpoint.details.policyResponse.configure_network_events',
|
||||
{ defaultMessage: 'Configure Network Events' }
|
||||
),
|
||||
],
|
||||
[
|
||||
'configure_process_events',
|
||||
i18n.translate(
|
||||
'xpack.securitySolution.endpoint.details.policyResponse.configure_process_events',
|
||||
{ defaultMessage: 'Configure Process Events' }
|
||||
),
|
||||
],
|
||||
[
|
||||
'configure_registry_events',
|
||||
i18n.translate(
|
||||
'xpack.securitySolution.endpoint.details.policyResponse.configure_registry_events',
|
||||
{ defaultMessage: 'Configure Registry Events' }
|
||||
),
|
||||
],
|
||||
[
|
||||
'configure_security_events',
|
||||
i18n.translate(
|
||||
'xpack.securitySolution.endpoint.details.policyResponse.configure_security_events',
|
||||
{ defaultMessage: 'Configure Security Events' }
|
||||
),
|
||||
],
|
||||
[
|
||||
'connect_kernel',
|
||||
i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.connect_kernel', {
|
||||
defaultMessage: 'Connect Kernel',
|
||||
}),
|
||||
],
|
||||
[
|
||||
'detect_async_image_load_events',
|
||||
i18n.translate(
|
||||
'xpack.securitySolution.endpoint.details.policyResponse.detect_async_image_load_events',
|
||||
{ defaultMessage: 'Detect Async Image Load Events' }
|
||||
),
|
||||
],
|
||||
[
|
||||
'detect_file_open_events',
|
||||
i18n.translate(
|
||||
'xpack.securitySolution.endpoint.details.policyResponse.detect_file_open_events',
|
||||
{ defaultMessage: 'Detect File Open Events' }
|
||||
),
|
||||
],
|
||||
[
|
||||
'detect_file_write_events',
|
||||
i18n.translate(
|
||||
'xpack.securitySolution.endpoint.details.policyResponse.detect_file_write_events',
|
||||
{ defaultMessage: 'Detect File Write Events' }
|
||||
),
|
||||
],
|
||||
[
|
||||
'detect_network_events',
|
||||
i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.detect_network_events', {
|
||||
defaultMessage: 'Detect Network Events',
|
||||
}),
|
||||
],
|
||||
[
|
||||
'detect_process_events',
|
||||
i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.detect_process_events', {
|
||||
defaultMessage: 'Detect Process Events',
|
||||
}),
|
||||
],
|
||||
[
|
||||
'detect_registry_events',
|
||||
i18n.translate(
|
||||
'xpack.securitySolution.endpoint.details.policyResponse.detect_registry_events',
|
||||
{ defaultMessage: 'Detect Registry Events' }
|
||||
),
|
||||
],
|
||||
[
|
||||
'detect_sync_image_load_events',
|
||||
i18n.translate(
|
||||
'xpack.securitySolution.endpoint.details.policyResponse.detect_sync_image_load_events',
|
||||
{ defaultMessage: 'Detect Sync Image Load Events' }
|
||||
),
|
||||
],
|
||||
[
|
||||
'download_global_artifacts',
|
||||
i18n.translate(
|
||||
'xpack.securitySolution.endpoint.details.policyResponse.download_global_artifacts',
|
||||
{ defaultMessage: 'Download Global Artifacts' }
|
||||
),
|
||||
],
|
||||
[
|
||||
'download_user_artifacts',
|
||||
i18n.translate(
|
||||
'xpack.securitySolution.endpoint.details.policyResponse.download_user_artifacts',
|
||||
{ defaultMessage: 'Download User Artifacts' }
|
||||
),
|
||||
],
|
||||
[
|
||||
'load_config',
|
||||
i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.load_config', {
|
||||
defaultMessage: 'Load Config',
|
||||
}),
|
||||
],
|
||||
[
|
||||
'load_malware_model',
|
||||
i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.load_malware_model', {
|
||||
defaultMessage: 'Load Malware Model',
|
||||
}),
|
||||
],
|
||||
[
|
||||
'read_elasticsearch_config',
|
||||
i18n.translate(
|
||||
'xpack.securitySolution.endpoint.details.policyResponse.read_elasticsearch_config',
|
||||
{ defaultMessage: 'Read Elasticsearch Config' }
|
||||
),
|
||||
],
|
||||
[
|
||||
'read_events_config',
|
||||
i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.read_events_config', {
|
||||
defaultMessage: 'Read Events Config',
|
||||
}),
|
||||
],
|
||||
[
|
||||
'read_kernel_config',
|
||||
i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.read_kernel_config', {
|
||||
defaultMessage: 'Read Kernel Config',
|
||||
}),
|
||||
],
|
||||
[
|
||||
'read_logging_config',
|
||||
i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.read_logging_config', {
|
||||
defaultMessage: 'Read Logging Config',
|
||||
}),
|
||||
],
|
||||
[
|
||||
'read_malware_config',
|
||||
i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.read_malware_config', {
|
||||
defaultMessage: 'Read Malware Config',
|
||||
}),
|
||||
],
|
||||
[
|
||||
'workflow',
|
||||
i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.workflow', {
|
||||
defaultMessage: 'Workflow',
|
||||
}),
|
||||
],
|
||||
];
|
||||
type PolicyResponseSections =
|
||||
| 'logging'
|
||||
| 'streaming'
|
||||
| 'malware'
|
||||
| 'events'
|
||||
| 'memory_protection'
|
||||
| 'behavior_protection';
|
||||
|
||||
const responseMap = new Map<string, string>(policyResponses);
|
||||
|
||||
// Additional values used in the Policy Response UI
|
||||
responseMap.set(
|
||||
'success',
|
||||
i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.success', {
|
||||
defaultMessage: 'Success',
|
||||
})
|
||||
);
|
||||
responseMap.set(
|
||||
'warning',
|
||||
i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.warning', {
|
||||
defaultMessage: 'Warning',
|
||||
})
|
||||
);
|
||||
responseMap.set(
|
||||
'failure',
|
||||
i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.failed', {
|
||||
defaultMessage: 'Failed',
|
||||
})
|
||||
);
|
||||
responseMap.set(
|
||||
'logging',
|
||||
i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.logging', {
|
||||
defaultMessage: 'Logging',
|
||||
})
|
||||
);
|
||||
responseMap.set(
|
||||
'streaming',
|
||||
i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.streaming', {
|
||||
defaultMessage: 'Streaming',
|
||||
})
|
||||
);
|
||||
responseMap.set(
|
||||
'malware',
|
||||
i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.malware', {
|
||||
defaultMessage: 'Malware',
|
||||
})
|
||||
);
|
||||
responseMap.set(
|
||||
'events',
|
||||
i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.events', {
|
||||
defaultMessage: 'Events',
|
||||
})
|
||||
);
|
||||
responseMap.set(
|
||||
'memory_protection',
|
||||
i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.memory_protection', {
|
||||
defaultMessage: 'Memory Threat',
|
||||
})
|
||||
);
|
||||
responseMap.set(
|
||||
'behavior_protection',
|
||||
i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.behavior_protection', {
|
||||
defaultMessage: 'Malicious Behavior',
|
||||
})
|
||||
const policyResponseSections = Object.freeze(
|
||||
new Map<PolicyResponseSections | string, string>([
|
||||
[
|
||||
'logging',
|
||||
i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.logging', {
|
||||
defaultMessage: 'Logging',
|
||||
}),
|
||||
],
|
||||
[
|
||||
'streaming',
|
||||
i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.streaming', {
|
||||
defaultMessage: 'Streaming',
|
||||
}),
|
||||
],
|
||||
[
|
||||
'malware',
|
||||
i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.malware', {
|
||||
defaultMessage: 'Malware',
|
||||
}),
|
||||
],
|
||||
[
|
||||
'events',
|
||||
i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.events', {
|
||||
defaultMessage: 'Events',
|
||||
}),
|
||||
],
|
||||
[
|
||||
'memory_protection',
|
||||
i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.memory_protection', {
|
||||
defaultMessage: 'Memory Threat',
|
||||
}),
|
||||
],
|
||||
[
|
||||
'behavior_protection',
|
||||
i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.behavior_protection', {
|
||||
defaultMessage: 'Malicious Behavior',
|
||||
}),
|
||||
],
|
||||
])
|
||||
);
|
||||
|
||||
/**
|
||||
* Maps a server provided value to corresponding i18n'd string.
|
||||
*/
|
||||
export function formatResponse(responseString: string) {
|
||||
if (responseMap.has(responseString)) {
|
||||
return responseMap.get(responseString);
|
||||
export function formatResponse(responseString: PolicyResponseSections | string) {
|
||||
if (policyResponseSections.has(responseString)) {
|
||||
return policyResponseSections.get(responseString);
|
||||
}
|
||||
|
||||
// Its possible for the UI to receive an Action name that it does not yet have a translation,
|
||||
// thus we generate a label for it here by making it more user fiendly
|
||||
responseMap.set(
|
||||
policyResponseSections.set(
|
||||
responseString,
|
||||
responseString.replace(/_/g, ' ').replace(/\b(\w)/g, (m) => m.toUpperCase())
|
||||
);
|
||||
|
||||
return responseMap.get(responseString);
|
||||
return policyResponseSections.get(responseString);
|
||||
}
|
||||
|
||||
type PolicyResponseAction =
|
||||
| 'configure_dns_events'
|
||||
| 'configure_dns_events'
|
||||
| 'configure_elasticsearch_connection'
|
||||
| 'configure_file_events'
|
||||
| 'configure_imageload_events'
|
||||
| 'configure_kernel'
|
||||
| 'configure_logging'
|
||||
| 'configure_malware'
|
||||
| 'configure_network_events'
|
||||
| 'configure_process_events'
|
||||
| 'configure_registry_events'
|
||||
| 'configure_security_events'
|
||||
| 'connect_kernel'
|
||||
| 'detect_async_image_load_events'
|
||||
| 'detect_file_open_events'
|
||||
| 'detect_file_write_events'
|
||||
| 'detect_network_events'
|
||||
| 'detect_process_events'
|
||||
| 'detect_registry_events'
|
||||
| 'detect_sync_image_load_events'
|
||||
| 'download_global_artifacts'
|
||||
| 'download_user_artifacts'
|
||||
| 'load_config'
|
||||
| 'load_malware_model'
|
||||
| 'read_elasticsearch_config'
|
||||
| 'read_events_config'
|
||||
| 'read_kernel_config'
|
||||
| 'read_logging_config'
|
||||
| 'read_malware_config'
|
||||
| 'workflow'
|
||||
| 'full_disk_access';
|
||||
|
||||
const policyResponseTitles = Object.freeze(
|
||||
new Map<PolicyResponseAction | string, string>([
|
||||
[
|
||||
'configure_dns_events',
|
||||
i18n.translate(
|
||||
'xpack.securitySolution.endpoint.details.policyResponse.configure_dns_events',
|
||||
{
|
||||
defaultMessage: 'Configure DNS Events',
|
||||
}
|
||||
),
|
||||
],
|
||||
[
|
||||
'configure_elasticsearch_connection',
|
||||
i18n.translate(
|
||||
'xpack.securitySolution.endpoint.details.policyResponse.configure_elasticsearch_connection',
|
||||
{ defaultMessage: 'Configure Elasticsearch Connection' }
|
||||
),
|
||||
],
|
||||
[
|
||||
'configure_file_events',
|
||||
i18n.translate(
|
||||
'xpack.securitySolution.endpoint.details.policyResponse.configure_file_events',
|
||||
{
|
||||
defaultMessage: 'Configure File Events',
|
||||
}
|
||||
),
|
||||
],
|
||||
[
|
||||
'configure_imageload_events',
|
||||
i18n.translate(
|
||||
'xpack.securitySolution.endpoint.details.policyResponse.configure_imageload_events',
|
||||
{ defaultMessage: 'Configure Image Load Events' }
|
||||
),
|
||||
],
|
||||
[
|
||||
'configure_kernel',
|
||||
i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.configure_kernel', {
|
||||
defaultMessage: 'Configure Kernel',
|
||||
}),
|
||||
],
|
||||
[
|
||||
'configure_logging',
|
||||
i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.configure_logging', {
|
||||
defaultMessage: 'Configure Logging',
|
||||
}),
|
||||
],
|
||||
[
|
||||
'configure_malware',
|
||||
i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.configure_malware', {
|
||||
defaultMessage: 'Configure Malware',
|
||||
}),
|
||||
],
|
||||
[
|
||||
'configure_network_events',
|
||||
i18n.translate(
|
||||
'xpack.securitySolution.endpoint.details.policyResponse.configure_network_events',
|
||||
{ defaultMessage: 'Configure Network Events' }
|
||||
),
|
||||
],
|
||||
[
|
||||
'configure_process_events',
|
||||
i18n.translate(
|
||||
'xpack.securitySolution.endpoint.details.policyResponse.configure_process_events',
|
||||
{ defaultMessage: 'Configure Process Events' }
|
||||
),
|
||||
],
|
||||
[
|
||||
'configure_registry_events',
|
||||
i18n.translate(
|
||||
'xpack.securitySolution.endpoint.details.policyResponse.configure_registry_events',
|
||||
{ defaultMessage: 'Configure Registry Events' }
|
||||
),
|
||||
],
|
||||
[
|
||||
'configure_security_events',
|
||||
i18n.translate(
|
||||
'xpack.securitySolution.endpoint.details.policyResponse.configure_security_events',
|
||||
{ defaultMessage: 'Configure Security Events' }
|
||||
),
|
||||
],
|
||||
[
|
||||
'connect_kernel',
|
||||
i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.connect_kernel', {
|
||||
defaultMessage: 'Connect Kernel',
|
||||
}),
|
||||
],
|
||||
[
|
||||
'detect_async_image_load_events',
|
||||
i18n.translate(
|
||||
'xpack.securitySolution.endpoint.details.policyResponse.detect_async_image_load_events',
|
||||
{ defaultMessage: 'Detect Async Image Load Events' }
|
||||
),
|
||||
],
|
||||
[
|
||||
'detect_file_open_events',
|
||||
i18n.translate(
|
||||
'xpack.securitySolution.endpoint.details.policyResponse.detect_file_open_events',
|
||||
{ defaultMessage: 'Detect File Open Events' }
|
||||
),
|
||||
],
|
||||
[
|
||||
'detect_file_write_events',
|
||||
i18n.translate(
|
||||
'xpack.securitySolution.endpoint.details.policyResponse.detect_file_write_events',
|
||||
{ defaultMessage: 'Detect File Write Events' }
|
||||
),
|
||||
],
|
||||
[
|
||||
'detect_network_events',
|
||||
i18n.translate(
|
||||
'xpack.securitySolution.endpoint.details.policyResponse.detect_network_events',
|
||||
{
|
||||
defaultMessage: 'Detect Network Events',
|
||||
}
|
||||
),
|
||||
],
|
||||
[
|
||||
'detect_process_events',
|
||||
i18n.translate(
|
||||
'xpack.securitySolution.endpoint.details.policyResponse.detect_process_events',
|
||||
{
|
||||
defaultMessage: 'Detect Process Events',
|
||||
}
|
||||
),
|
||||
],
|
||||
[
|
||||
'detect_registry_events',
|
||||
i18n.translate(
|
||||
'xpack.securitySolution.endpoint.details.policyResponse.detect_registry_events',
|
||||
{ defaultMessage: 'Detect Registry Events' }
|
||||
),
|
||||
],
|
||||
[
|
||||
'detect_sync_image_load_events',
|
||||
i18n.translate(
|
||||
'xpack.securitySolution.endpoint.details.policyResponse.detect_sync_image_load_events',
|
||||
{ defaultMessage: 'Detect Sync Image Load Events' }
|
||||
),
|
||||
],
|
||||
[
|
||||
'download_global_artifacts',
|
||||
i18n.translate(
|
||||
'xpack.securitySolution.endpoint.details.policyResponse.download_global_artifacts',
|
||||
{ defaultMessage: 'Download Global Artifacts' }
|
||||
),
|
||||
],
|
||||
[
|
||||
'download_user_artifacts',
|
||||
i18n.translate(
|
||||
'xpack.securitySolution.endpoint.details.policyResponse.download_user_artifacts',
|
||||
{ defaultMessage: 'Download User Artifacts' }
|
||||
),
|
||||
],
|
||||
[
|
||||
'load_config',
|
||||
i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.load_config', {
|
||||
defaultMessage: 'Load Config',
|
||||
}),
|
||||
],
|
||||
[
|
||||
'load_malware_model',
|
||||
i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.load_malware_model', {
|
||||
defaultMessage: 'Load Malware Model',
|
||||
}),
|
||||
],
|
||||
[
|
||||
'read_elasticsearch_config',
|
||||
i18n.translate(
|
||||
'xpack.securitySolution.endpoint.details.policyResponse.read_elasticsearch_config',
|
||||
{ defaultMessage: 'Read Elasticsearch Config' }
|
||||
),
|
||||
],
|
||||
[
|
||||
'read_events_config',
|
||||
i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.read_events_config', {
|
||||
defaultMessage: 'Read Events Config',
|
||||
}),
|
||||
],
|
||||
[
|
||||
'read_kernel_config',
|
||||
i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.read_kernel_config', {
|
||||
defaultMessage: 'Read Kernel Config',
|
||||
}),
|
||||
],
|
||||
[
|
||||
'read_logging_config',
|
||||
i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.read_logging_config', {
|
||||
defaultMessage: 'Read Logging Config',
|
||||
}),
|
||||
],
|
||||
[
|
||||
'read_malware_config',
|
||||
i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.read_malware_config', {
|
||||
defaultMessage: 'Read Malware Config',
|
||||
}),
|
||||
],
|
||||
[
|
||||
'workflow',
|
||||
i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.workflow', {
|
||||
defaultMessage: 'Workflow',
|
||||
}),
|
||||
],
|
||||
[
|
||||
'full_disk_access',
|
||||
i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.full_disk_access', {
|
||||
defaultMessage: 'Full Disk Access',
|
||||
}),
|
||||
],
|
||||
])
|
||||
);
|
||||
|
||||
type PolicyResponseStatus = `${HostPolicyResponseActionStatus}`;
|
||||
|
||||
const policyResponseStatuses = Object.freeze(
|
||||
new Map<PolicyResponseStatus, string>([
|
||||
[
|
||||
'success',
|
||||
i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.success', {
|
||||
defaultMessage: 'Success',
|
||||
}),
|
||||
],
|
||||
[
|
||||
'warning',
|
||||
i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.warning', {
|
||||
defaultMessage: 'Warning',
|
||||
}),
|
||||
],
|
||||
[
|
||||
'failure',
|
||||
i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.failed', {
|
||||
defaultMessage: 'Failed',
|
||||
}),
|
||||
],
|
||||
[
|
||||
'unsupported',
|
||||
i18n.translate('xpack.securitySolution.endpoint.details.policyResponse.unsupported', {
|
||||
defaultMessage: 'Unsupported',
|
||||
}),
|
||||
],
|
||||
])
|
||||
);
|
||||
|
||||
const descriptions = Object.freeze(
|
||||
new Map<Partial<PolicyResponseAction> | string, string>([
|
||||
[
|
||||
'full_disk_access',
|
||||
i18n.translate(
|
||||
'xpack.securitySolution.endpoint.details.policyResponse.description.full_disk_access',
|
||||
{
|
||||
defaultMessage: 'You must enable full disk access for Elastic Endpoint on your machine. ',
|
||||
}
|
||||
),
|
||||
],
|
||||
])
|
||||
);
|
||||
|
||||
const linkTexts = Object.freeze(
|
||||
new Map<Partial<PolicyResponseAction> | string, string>([
|
||||
[
|
||||
'full_disk_access',
|
||||
i18n.translate(
|
||||
'xpack.securitySolution.endpoint.details.policyResponse.link.text.full_disk_access',
|
||||
{
|
||||
defaultMessage: 'Learn more.',
|
||||
}
|
||||
),
|
||||
],
|
||||
])
|
||||
);
|
||||
|
||||
/**
|
||||
* An array with errors we want to bubble up in policy response
|
||||
*/
|
||||
const GENERIC_ACTION_ERRORS: readonly string[] = Object.freeze(['full_disk_access']);
|
||||
|
||||
export class PolicyResponseActionFormatter {
|
||||
public key: string;
|
||||
public title: string;
|
||||
public description: string;
|
||||
public hasError: boolean;
|
||||
public errorTitle: string;
|
||||
public errorDescription?: string;
|
||||
public status?: string;
|
||||
public linkText?: string;
|
||||
public linkUrl?: string;
|
||||
|
||||
constructor(
|
||||
policyResponseAppliedAction: ImmutableObject<HostPolicyResponseAppliedAction>,
|
||||
link?: string
|
||||
) {
|
||||
this.key = policyResponseAppliedAction.name;
|
||||
this.title =
|
||||
policyResponseTitles.get(this.key) ??
|
||||
this.key.replace(/_/g, ' ').replace(/\b(\w)/g, (m) => m.toUpperCase());
|
||||
this.hasError =
|
||||
policyResponseAppliedAction.status === 'failure' ||
|
||||
policyResponseAppliedAction.status === 'warning';
|
||||
this.description = descriptions.get(this.key) || policyResponseAppliedAction.message;
|
||||
this.errorDescription = descriptions.get(this.key) || policyResponseAppliedAction.message;
|
||||
this.errorTitle = this.errorDescription ? this.title : policyResponseAppliedAction.name;
|
||||
this.status = policyResponseStatuses.get(policyResponseAppliedAction.status);
|
||||
this.linkText = linkTexts.get(this.key);
|
||||
this.linkUrl = link;
|
||||
}
|
||||
|
||||
public isGeneric(): boolean {
|
||||
return GENERIC_ACTION_ERRORS.includes(this.key);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -219,4 +219,47 @@ describe('when on the policy response', () => {
|
|||
const component = await renderOpenedTree();
|
||||
expect(component.getByText('A New Unknown Action')).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should not display error callout if status success', async () => {
|
||||
const policyResponse = createPolicyResponse();
|
||||
policyResponse.Endpoint.policy.applied.actions.forEach(
|
||||
(action) => (action.status = HostPolicyResponseActionStatus.success)
|
||||
);
|
||||
runMock(policyResponse);
|
||||
const component = await renderOpenedTree();
|
||||
expect(component.queryAllByTestId('endpointPolicyResponseErrorCallOut')).toHaveLength(0);
|
||||
});
|
||||
|
||||
describe('error callout', () => {
|
||||
let policyResponse: HostPolicyResponse;
|
||||
|
||||
beforeEach(() => {
|
||||
policyResponse = createPolicyResponse(HostPolicyResponseActionStatus.failure);
|
||||
runMock(policyResponse);
|
||||
});
|
||||
|
||||
it('should not display link if type is NOT mapped', async () => {
|
||||
const component = await renderOpenedTree();
|
||||
const calloutLink = component.queryByTestId('endpointPolicyResponseErrorCallOutLink');
|
||||
expect(calloutLink).toBeNull();
|
||||
});
|
||||
|
||||
it('should display link if type is mapped', async () => {
|
||||
const action = {
|
||||
name: 'full_disk_access',
|
||||
message:
|
||||
'You must enable full disk access for Elastic Endpoint on your machine. See our troubleshooting documentation for more information',
|
||||
status: HostPolicyResponseActionStatus.failure,
|
||||
};
|
||||
|
||||
policyResponse.Endpoint.policy.applied.actions.push(action);
|
||||
policyResponse.Endpoint.policy.applied.response.configurations.malware.concerned_actions.push(
|
||||
'full_disk_access'
|
||||
);
|
||||
|
||||
const component = await renderOpenedTree();
|
||||
const calloutLinks = component.queryAllByTestId('endpointPolicyResponseErrorCallOutLink');
|
||||
expect(calloutLinks.length).toEqual(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -4,14 +4,18 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import React, { memo, useEffect, useState } from 'react';
|
||||
import React, { memo, useEffect, useState, useMemo } from 'react';
|
||||
import { EuiEmptyPrompt, EuiLoadingSpinner, EuiSpacer, EuiText } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { DocLinksStart } from '@kbn/core/public';
|
||||
import { useKibana } from '../../../common/lib/kibana';
|
||||
import type { HostPolicyResponse } from '../../../../common/endpoint/types';
|
||||
import { PreferenceFormattedDateFromPrimitive } from '../../../common/components/formatted_date';
|
||||
import { useGetEndpointPolicyResponse } from '../../hooks/endpoint/use_get_endpoint_policy_response';
|
||||
import { PolicyResponse } from './policy_response';
|
||||
import { getFailedOrWarningActionCountFromPolicyResponse } from '../../pages/endpoint_hosts/store/utils';
|
||||
import { PolicyResponseActionItem } from './policy_response_action_item';
|
||||
import { PolicyResponseActionFormatter } from './policy_response_friendly_names';
|
||||
|
||||
export interface PolicyResponseWrapperProps {
|
||||
endpointId: string;
|
||||
|
@ -23,6 +27,8 @@ export const PolicyResponseWrapper = memo<PolicyResponseWrapperProps>(
|
|||
({ endpointId, showRevisionMessage = true, onShowNeedsAttentionBadge }) => {
|
||||
const { data, isLoading, isFetching, isError } = useGetEndpointPolicyResponse(endpointId);
|
||||
|
||||
const { docLinks } = useKibana().services;
|
||||
|
||||
const [policyResponseConfig, setPolicyResponseConfig] =
|
||||
useState<HostPolicyResponse['Endpoint']['policy']['applied']['response']['configurations']>();
|
||||
const [policyResponseActions, setPolicyResponseActions] =
|
||||
|
@ -58,6 +64,34 @@ export const PolicyResponseWrapper = memo<PolicyResponseWrapperProps>(
|
|||
}
|
||||
}, [policyResponseAttentionCount, onShowNeedsAttentionBadge]);
|
||||
|
||||
const genericErrors = useMemo(() => {
|
||||
if (!policyResponseConfig && !policyResponseActions) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return policyResponseActions?.reduce<PolicyResponseActionFormatter[]>(
|
||||
(acc, currentAction) => {
|
||||
const policyResponseActionFormatter = new PolicyResponseActionFormatter(
|
||||
currentAction,
|
||||
docLinks.links.securitySolution.policyResponseTroubleshooting[
|
||||
currentAction.name as keyof DocLinksStart['links']['securitySolution']['policyResponseTroubleshooting']
|
||||
]
|
||||
);
|
||||
|
||||
if (policyResponseActionFormatter.isGeneric() && policyResponseActionFormatter.hasError) {
|
||||
acc.push(policyResponseActionFormatter);
|
||||
}
|
||||
|
||||
return acc;
|
||||
},
|
||||
[]
|
||||
);
|
||||
}, [
|
||||
docLinks.links.securitySolution.policyResponseTroubleshooting,
|
||||
policyResponseActions,
|
||||
policyResponseConfig,
|
||||
]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{showRevisionMessage && (
|
||||
|
@ -91,11 +125,20 @@ export const PolicyResponseWrapper = memo<PolicyResponseWrapperProps>(
|
|||
)}
|
||||
{isLoading && <EuiLoadingSpinner size="m" />}
|
||||
{policyResponseConfig !== undefined && policyResponseActions !== undefined && (
|
||||
<PolicyResponse
|
||||
policyResponseConfig={policyResponseConfig}
|
||||
policyResponseActions={policyResponseActions}
|
||||
policyResponseAttentionCount={policyResponseAttentionCount}
|
||||
/>
|
||||
<>
|
||||
<PolicyResponse
|
||||
policyResponseConfig={policyResponseConfig}
|
||||
policyResponseActions={policyResponseActions}
|
||||
policyResponseAttentionCount={policyResponseAttentionCount}
|
||||
/>
|
||||
<EuiSpacer size="m" />
|
||||
{genericErrors?.map((genericActionError) => (
|
||||
<React.Fragment key={genericActionError.key}>
|
||||
<PolicyResponseActionItem policyResponseActionFormatter={genericActionError} />
|
||||
<EuiSpacer size="m" />
|
||||
</React.Fragment>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue